Merge pull request #10812 from ziggie1984/chanstate-kv-store-move
What changed, and why it matters
This commit is a large internal code reorganization for the LND Lightning node. It moves channel state storage logic from the `channeldb` package into a new `chanstate` package, while keeping the same database keys, serialization formats, and public behavior. There is no new feature or obvious security fix in the visible diff; it is primarily a refactoring to separate concerns.
Treat as a normal refactoring commit. Reviewers should verify that the moved serialization code is byte-for-byte identical to the original and that no accidental behavioral changes were introduced in the delegation wrappers. No immediate security response is warranted based on the supplied diff.
Security signals we found
Large refactoring with no change to cryptographic or consensus-critical logic
Codec serialization moved verbatim to new package; wire format unchanged
Database bucket keys and TLV record types preserved
No new input validation, bounds checks, or permission changes visible
No mention of vulnerability, CVE, or security bug in commit message
Evidence from the diff
PR #10812 moves the channel-state key-value store implementation out of channeldb and into a dedicated chanstate package. The old channeldb code is replaced by thin wrappers and type aliases that delegate to chanstate. Serialization helpers (WriteElement, ReadElement, etc.), bucket key constants, forwarding-package logic, revocation-log helpers, and channel open/close serialization are all relocated. The diff shows identical byte-level behavior: the same bucket names, TLV types, and encoding paths are preserved. A few small cleanups appear, such as using errors.Is for error comparison and exposing ChannelPackager.Source() instead of a public field, but these are minor API adjustments.
Changed components
channeldb/channel.gochanneldb/codec.gochanneldb/db.gochanneldb/error.gochanneldb/forwarding_package.gochanneldb/revocation_log.gochanstate/* (new package)Inspect captured patch +3923 / −3109
### channeldb/channel.go
@@ -2,7 +2,6 @@ package channeldb
import (
"bytes"
- "encoding/binary"
"errors"
"fmt"
"io"
@@ -11,12 +10,9 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chainhash/v2"
"github.com/btcsuite/btcd/wire/v2"
- "github.com/btcsuite/btcwallet/walletdb"
cstate "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
graphdb "github.com/lightningnetwork/lnd/graph/db"
- "github.com/lightningnetwork/lnd/graph/db/models"
- "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
@@ -38,134 +34,15 @@ const (
)
var (
- // closedChannelBucket stores summarization information concerning
- // previously open, but now closed channels.
- closedChannelBucket = []byte("closed-chan-bucket")
-
- // openChannelBucket 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.
- //
- // openChan -> nodeID -> chanPoint
- //
- // TODO(roasbeef): flesh out comment
- openChannelBucket = []byte("open-chan-bucket")
-
- // outpointBucket stores all of our channel outpoints and a tlv
- // stream containing channel data.
- //
- // outpoint -> tlv stream.
- //
- 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.
- historicalChannelBucket = []byte("historical-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")
-
- // 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")
-
- // 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")
-
- // unsignedAckedUpdatesKey is an entry in the channel bucket that
- // contains the remote updates that we have acked, but not yet signed
- // for in one of our remote commits.
- unsignedAckedUpdatesKey = []byte("unsigned-acked-updates-key")
-
- // remoteUnsignedLocalUpdatesKey is an entry in the channel bucket that
- // contains the local updates that the remote party has acked, but
- // has not yet signed for in one of their local commits.
- remoteUnsignedLocalUpdatesKey = []byte("remote-unsigned-local-updates-key")
-
- // revocationStateKey stores their current revocation hash, our
- // preimage producer and their preimage store.
- revocationStateKey = []byte("revocation-state-key")
-
- // dataLossCommitPointKey stores the commitment point received from the
- // remote peer during a channel sync in case we have lost channel state.
- dataLossCommitPointKey = []byte("data-loss-commit-point-key")
-
- // forceCloseTxKey points to a the unilateral closing tx that we
- // broadcasted when moving the channel to state CommitBroadcasted.
- forceCloseTxKey = []byte("closing-tx-key")
-
- // coopCloseTxKey points to a the cooperative closing tx that we
- // broadcasted when moving the channel to state CoopBroadcasted.
- coopCloseTxKey = []byte("coop-closing-tx-key")
-
- // shutdownInfoKey points to the serialised shutdown info that has been
- // persisted for a channel. The existence of this info means that we
- // have sent the Shutdown message before and so should re-initiate the
- // shutdown on re-establish.
- shutdownInfoKey = []byte("shutdown-info-key")
-
- // commitDiffKey stores the current pending commitment state we've
- // extended to the remote party (if any). Each time we propose a new
- // state, we store the information necessary to reconstruct this state
- // from the prior commitment. This allows us to resync the remote party
- // to their expected state in the case of message loss.
- //
- // TODO(roasbeef): rename to commit chain?
- commitDiffKey = []byte("commit-diff-key")
-
- // frozenChanKey is the key where we store the information for any
- // active "frozen" channels. This key is present only in the leaf
- // bucket for a given channel.
- frozenChanKey = []byte("frozen-chans")
-
- // 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")
-
- // finalHtlcsBucket contains the htlcs that have been resolved
- // definitively. Within this bucket, there is a sub-bucket for each
- // channel. In each channel bucket, the htlc indices are stored along
- // with final outcome.
- //
- // final-htlcs -> chanID -> htlcIndex -> outcome
- //
- // 'outcome' is a byte value that encodes:
- //
- // | true false
- // ------+------------------
- // bit 0 | settled failed
- // bit 1 | offchain onchain
- //
- // This bucket is positioned at the root level, because its contents
- // will be kept independent of the channel lifecycle. This is to avoid
- // the situation where a channel force-closes autonomously and the user
- // not being able to query for htlc outcomes anymore.
- finalHtlcsBucket = []byte("final-htlcs")
+ closedChannelBucket = cstate.ClosedChannelBucketKey()
+ openChannelBucket = cstate.OpenChannelBucketKey()
+ outpointBucket = cstate.OutpointBucketKey()
+ chanIDBucket = cstate.ChanIDBucketKey()
+ historicalChannelBucket = cstate.HistoricalChannelBucketKey()
+ unsignedAckedUpdatesKey = cstate.UnsignedAckedUpdatesKey()
+ remoteUnsignedLocalUpdatesKey = cstate.RemoteUnsignedLocalUpdatesKey()
+ commitDiffKey = cstate.CommitDiffKey()
+ lastWasRevokeKey = cstate.LastWasRevokeKey()
)
var (
@@ -216,13 +93,9 @@ var (
ErrOnionBlobLength = cstate.ErrOnionBlobLength
)
-const (
- // A tlv type definition used to serialize an outpoint's indexStatus
- // for use in the outpoint index.
- indexStatusType tlv.Type = 0
-)
-
type (
+ indexStatus = cstate.IndexStatus
+
// OpenChannel encapsulates the persistent and dynamic state of an open
// channel with a remote node.
OpenChannel = cstate.OpenChannel
@@ -243,143 +116,10 @@ type (
CommitDiff = cstate.CommitDiff
)
-// openChannelTlvData houses the new data fields that are stored for each
-// channel in a TLV stream within the root bucket. This is stored as a TLV
-// stream appended to the existing hard-coded fields in the channel's root
-// bucket. New fields being added to the channel state should be added here.
-//
-// NOTE: This struct is used for serialization purposes only and its fields
-// should be accessed via the OpenChannel struct while in memory.
-type openChannelTlvData struct {
- // revokeKeyLoc is the key locator for the revocation key.
- revokeKeyLoc tlv.RecordT[tlv.TlvType1, keyLocRecord]
-
- // initialLocalBalance is the initial local balance of the channel.
- initialLocalBalance tlv.RecordT[tlv.TlvType2, uint64]
-
- // initialRemoteBalance is the initial remote balance of the channel.
- initialRemoteBalance tlv.RecordT[tlv.TlvType3, uint64]
-
- // realScid is the real short channel ID of the channel corresponding to
- // the on-chain outpoint.
- realScid tlv.RecordT[tlv.TlvType4, lnwire.ShortChannelID]
-
- // memo is an optional text field that gives context to the user about
- // the channel.
- memo tlv.OptionalRecordT[tlv.TlvType5, []byte]
-
- // tapscriptRoot is the optional Tapscript root the channel funding
- // output commits to.
- tapscriptRoot tlv.OptionalRecordT[tlv.TlvType6, [32]byte]
-
- // customBlob is an optional TLV encoded blob of data representing
- // custom channel funding information.
- customBlob tlv.OptionalRecordT[tlv.TlvType7, tlv.Blob]
-
- // confirmationHeight records the block height at which the funding
- // transaction was first confirmed.
- confirmationHeight tlv.RecordT[tlv.TlvType8, uint32]
-
- // closeConfirmationHeight records the block height at which the closing
- // transaction was first confirmed. This is used to calculate the
- // remaining confirmations until the channel is considered fully closed.
- // Note: if not set, it means either the channel has not been
- // closed yet, or it was closed before this field was introduced.
- closeConfirmationHeight tlv.OptionalRecordT[tlv.TlvType9, uint32]
-}
-
-// encode serializes the openChannelTlvData to the given io.Writer.
-func (c *openChannelTlvData) encode(w io.Writer) error {
- tlvRecords := []tlv.Record{
- c.revokeKeyLoc.Record(),
- c.initialLocalBalance.Record(),
- c.initialRemoteBalance.Record(),
- c.realScid.Record(),
- c.confirmationHeight.Record(),
- }
- c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
- tlvRecords = append(tlvRecords, memo.Record())
- })
- c.tapscriptRoot.WhenSome(
- func(root tlv.RecordT[tlv.TlvType6, [32]byte]) {
- tlvRecords = append(tlvRecords, root.Record())
- },
- )
- c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType7, tlv.Blob]) {
- tlvRecords = append(tlvRecords, blob.Record())
- })
- c.closeConfirmationHeight.WhenSome(
- func(h tlv.RecordT[tlv.TlvType9, uint32]) {
- tlvRecords = append(tlvRecords, h.Record())
- },
- )
-
- tlv.SortRecords(tlvRecords)
-
- // Create the tlv stream.
- tlvStream, err := tlv.NewStream(tlvRecords...)
- if err != nil {
- return err
- }
-
- return tlvStream.Encode(w)
-}
-
-// decode deserializes the openChannelTlvData from the given io.Reader.
-func (c *openChannelTlvData) decode(r io.Reader) error {
- memo := c.memo.Zero()
- tapscriptRoot := c.tapscriptRoot.Zero()
- blob := c.customBlob.Zero()
- closeConfHeight := c.closeConfirmationHeight.Zero()
-
- // Create the tlv stream.
- tlvStream, err := tlv.NewStream(
- c.revokeKeyLoc.Record(),
- c.initialLocalBalance.Record(),
- c.initialRemoteBalance.Record(),
- c.realScid.Record(),
- memo.Record(),
- tapscriptRoot.Record(),
- blob.Record(),
- c.confirmationHeight.Record(),
- closeConfHeight.Record(),
- )
- if err != nil {
- return err
- }
-
- tlvs, err := tlvStream.DecodeWithParsedTypes(r)
- if err != nil {
- return err
- }
-
- if _, ok := tlvs[memo.TlvType()]; ok {
- c.memo = tlv.SomeRecordT(memo)
- }
- if _, ok := tlvs[tapscriptRoot.TlvType()]; ok {
- c.tapscriptRoot = tlv.SomeRecordT(tapscriptRoot)
- }
- if _, ok := tlvs[c.customBlob.TlvType()]; ok {
- c.customBlob = tlv.SomeRecordT(blob)
- }
- if _, ok := tlvs[closeConfHeight.TlvType()]; ok {
- c.closeConfirmationHeight = tlv.SomeRecordT(closeConfHeight)
- }
-
- return nil
-}
-
-// indexStatus is an enum-like type that describes what state the
-// outpoint is in. Currently only two possible values.
-type indexStatus uint8
-
const (
- // outpointOpen represents an outpoint that is open in the outpoint index.
- outpointOpen indexStatus = 0
-
- // outpointClosed represents an outpoint that is closed in the outpoint
- // index.
- outpointClosed indexStatus = 1
+ indexStatusType = cstate.IndexStatusType
+ outpointOpen = cstate.OutpointOpen
+ outpointClosed = cstate.OutpointClosed
)
// isOutpointClosed reports whether the supplied chanKey has been flipped to
@@ -391,26 +131,7 @@ const (
// fetch outpointBucket once and pass it in, which lets loop-style readers
// hoist the bucket lookup out of the inner loop.
func isOutpointClosed(opBucket kvdb.RBucket, chanKey []byte) (bool, error) {
- if opBucket == nil {
- return false, nil
- }
- raw := opBucket.Get(chanKey)
- if raw == nil {
- return false, nil
- }
-
- var status uint8
- statusRecord := tlv.MakePrimitiveRecord(indexStatusType, &status)
- stream, err := tlv.NewStream(statusRecord)
- if err != nil {
- return false, err
- }
- if err := stream.Decode(bytes.NewReader(raw)); err != nil {
- return false, fmt.Errorf("decode outpoint status for "+
- "chan_key=%x: %w", chanKey, err)
- }
-
- return indexStatus(status) == outpointClosed, nil
+ return cstate.IsOutpointClosed(opBucket, chanKey)
}
// ChannelType is an enum-like type that describes one of several possible
@@ -487,73 +208,6 @@ type CommitmentParams = cstate.CommitmentParams
// ChannelConfig houses the channel configuration for one side of a channel.
type ChannelConfig = cstate.ChannelConfig
-// commitTlvData stores all the optional data that may be stored as a TLV stream
-// at the _end_ of the normal serialized commit on disk.
-type commitTlvData struct {
- // customBlob is a custom blob that may store extra data for custom
- // channels.
- customBlob tlv.OptionalRecordT[tlv.TlvType1, tlv.Blob]
-}
-
-// encode encodes the aux data into the passed io.Writer.
-func (c *commitTlvData) encode(w io.Writer) error {
- var tlvRecords []tlv.Record
- c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType1, tlv.Blob]) {
- tlvRecords = append(tlvRecords, blob.Record())
- })
-
- // Create the tlv stream.
- tlvStream, err := tlv.NewStream(tlvRecords...)
- if err != nil {
- return err
- }
-
- return tlvStream.Encode(w)
-}
-
-// decode attempts to decode the aux data from the passed io.Reader.
-func (c *commitTlvData) decode(r io.Reader) error {
- blob := c.customBlob.Zero()
-
- tlvStream, err := tlv.NewStream(
- blob.Record(),
- )
- if err != nil {
- return err
- }
-
- tlvs, err := tlvStream.DecodeWithParsedTypes(r)
- if err != nil {
- return err
- }
-
- if _, ok := tlvs[c.customBlob.TlvType()]; ok {
- c.customBlob = tlv.SomeRecordT(blob)
- }
-
- return nil
-}
-
-// amendCommitTlvData updates the commitment with the given auxiliary TLV data.
-func amendCommitTlvData(c *ChannelCommitment, auxData commitTlvData) {
- auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
- c.CustomBlob = fn.Some(blob)
- })
-}
-
-// extractCommitTlvData creates a new commitTlvData from the given commitment.
-func extractCommitTlvData(c *ChannelCommitment) commitTlvData {
- var auxData commitTlvData
-
- c.CustomBlob.WhenSome(func(blob tlv.Blob) {
- auxData.customBlob = tlv.SomeRecordT(
- tlv.NewPrimitiveRecord[tlv.TlvType1](blob),
- )
- })
-
- return auxData
-}
-
// 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 = cstate.ChannelStatus
@@ -591,92 +245,20 @@ var (
ChanStatusRemoteCloseInitiator = cstate.ChanStatusRemoteCloseInitiator
)
-// FinalHtlcByte defines a byte type that encodes information about the final
-// htlc resolution.
-type FinalHtlcByte byte
+// FinalHtlcByte is a type alias for a byte that encodes information about the
+// final htlc resolution.
+type FinalHtlcByte = cstate.FinalHtlcByte
const (
// FinalHtlcSettledBit is the bit that encodes whether the htlc was
// settled or failed.
- FinalHtlcSettledBit FinalHtlcByte = 1 << 0
+ FinalHtlcSettledBit = cstate.FinalHtlcSettledBit
// FinalHtlcOffchainBit is the bit that encodes whether the htlc was
// resolved offchain or onchain.
- FinalHtlcOffchainBit FinalHtlcByte = 1 << 1
+ FinalHtlcOffchainBit = cstate.FinalHtlcOffchainBit
)
-// amendOpenChannelTlvData updates the channel with the given auxiliary TLV
-// data.
-func amendOpenChannelTlvData(channel *OpenChannel, auxData openChannelTlvData) {
- channel.RevocationKeyLocator = auxData.revokeKeyLoc.Val.KeyLocator
- channel.InitialLocalBalance = lnwire.MilliSatoshi(
- auxData.initialLocalBalance.Val,
- )
- channel.InitialRemoteBalance = lnwire.MilliSatoshi(
- auxData.initialRemoteBalance.Val,
- )
- channel.SetConfirmedScidForStore(auxData.realScid.Val)
- channel.ConfirmationHeight = auxData.confirmationHeight.Val
-
- auxData.memo.WhenSomeV(func(memo []byte) {
- channel.Memo = memo
- })
- auxData.tapscriptRoot.WhenSomeV(func(h [32]byte) {
- channel.TapscriptRoot = fn.Some[chainhash.Hash](h)
- })
- auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
- channel.CustomBlob = fn.Some(blob)
- })
- auxData.closeConfirmationHeight.WhenSomeV(func(h uint32) {
- channel.CloseConfirmationHeight = fn.Some(h)
- })
-}
-
-// extractOpenChannelTlvData creates a new openChannelTlvData from the given
-// channel.
-func extractOpenChannelTlvData(channel *OpenChannel) openChannelTlvData {
- auxData := openChannelTlvData{
- revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1](
- keyLocRecord{channel.RevocationKeyLocator},
- ),
- initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2](
- uint64(channel.InitialLocalBalance),
- ),
- initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3](
- uint64(channel.InitialRemoteBalance),
- ),
- realScid: tlv.NewRecordT[tlv.TlvType4](
- channel.ConfirmedScidForStore(),
- ),
- confirmationHeight: tlv.NewPrimitiveRecord[tlv.TlvType8](
- channel.ConfirmationHeight,
- ),
- }
-
- if len(channel.Memo) != 0 {
- auxData.memo = tlv.SomeRecordT(
- tlv.NewPrimitiveRecord[tlv.TlvType5](channel.Memo),
- )
- }
- channel.TapscriptRoot.WhenSome(func(h chainhash.Hash) {
- auxData.tapscriptRoot = tlv.SomeRecordT(
- tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h),
- )
- })
- channel.CustomBlob.WhenSome(func(blob tlv.Blob) {
- auxData.customBlob = tlv.SomeRecordT(
- tlv.NewPrimitiveRecord[tlv.TlvType7](blob),
- )
- })
- channel.CloseConfirmationHeight.WhenSome(func(h uint32) {
- auxData.closeConfirmationHeight = tlv.SomeRecordT(
- tlv.NewPrimitiveRecord[tlv.TlvType9](h),
- )
- })
-
- return auxData
-}
-
// RefreshChannel updates the in-memory channel state using the latest state
// observed on disk.
func (c *ChannelStateDB) RefreshChannel(channel *OpenChannel) error {
@@ -720,56 +302,7 @@ func (c *ChannelStateDB) RefreshChannel(channel *OpenChannel) error {
func fetchChanBucket(tx kvdb.RTx, nodeKey *btcec.PublicKey,
outPoint *wire.OutPoint, chainHash chainhash.Hash) (kvdb.RBucket, error) {
- // First fetch the top level bucket which stores all data related to
- // current, active channels.
- openChanBucket := tx.ReadBucket(openChannelBucket)
- if openChanBucket == nil {
- return nil, ErrNoChanDBExists
- }
-
- // TODO(roasbeef): CreateTopLevelBucket on the interface isn't like
- // CreateIfNotExists, will return error
-
- // Within this top level bucket, fetch the bucket dedicated to storing
- // open channel data specific to the remote node.
- nodePub := nodeKey.SerializeCompressed()
- nodeChanBucket := openChanBucket.NestedReadBucket(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.NestedReadBucket(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
- if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
- return nil, err
- }
- chanKey := chanPointBuf.Bytes()
-
- // Treat already-closed channels as gone. The chanBucket may still
- // exist on tombstone-enabled backends; the outpoint flip is the
- // source of truth.
- closed, err := isOutpointClosed(tx.ReadBucket(outpointBucket), chanKey)
- if err != nil {
- return nil, err
- }
- if closed {
- return nil, ErrChannelNotFound
- }
-
- chanBucket := chainBucket.NestedReadBucket(chanKey)
- if chanBucket == nil {
- return nil, ErrChannelNotFound
- }
-
- return chanBucket, nil
+ return cstate.FetchChanBucket(tx, nodeKey, outPoint, chainHash)
}
// fetchChanBucketRw is a helper function that returns the bucket where a
@@ -780,76 +313,13 @@ func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey,
outPoint *wire.OutPoint, chainHash chainhash.Hash) (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
- }
-
- // TODO(roasbeef): CreateTopLevelBucket on the interface isn't like
- // CreateIfNotExists, will return error
-
- // Within this top level bucket, fetch the bucket dedicated to storing
- // open channel data specific to the remote node.
- nodePub := nodeKey.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(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
- if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
- return nil, err
- }
- chanKey := chanPointBuf.Bytes()
-
- // Treat already-closed channels as gone. The chanBucket may still
- // exist on tombstone-enabled backends; the outpoint flip is the
- // source of truth.
- closed, err := isOutpointClosed(tx.ReadBucket(outpointBucket), chanKey)
- if err != nil {
- return nil, err
- }
- if closed {
- return nil, ErrChannelNotFound
- }
-
- chanBucket := chainBucket.NestedReadWriteBucket(chanKey)
- if chanBucket == nil {
- return nil, ErrChannelNotFound
- }
-
- return chanBucket, nil
+ return cstate.FetchChanBucketRw(tx, nodeKey, outPoint, chainHash)
}
func fetchFinalHtlcsBucketRw(tx kvdb.RwTx,
chanID lnwire.ShortChannelID) (kvdb.RwBucket, error) {
- finalHtlcsBucket, err := tx.CreateTopLevelBucket(finalHtlcsBucket)
- if err != nil {
- return nil, err
- }
-
- var chanIDBytes [8]byte
- byteOrder.PutUint64(chanIDBytes[:], chanID.ToUint64())
- chanBucket, err := finalHtlcsBucket.CreateBucketIfNotExists(
- chanIDBytes[:],
- )
- if err != nil {
- return nil, err
- }
-
- return chanBucket, nil
+ return cstate.FetchFinalHtlcsBucketRw(tx, chanID)
}
// fullSyncOpenChannel syncs the contents of an OpenChannel while re-using an
@@ -881,23 +351,10 @@ func fullSyncOpenChannel(tx kvdb.RwTx, c *OpenChannel) error {
return ErrChanAlreadyExists
}
- status := uint8(outpointOpen)
-
- // Write the status of this outpoint as the first entry in a tlv
- // stream.
- statusRecord := tlv.MakePrimitiveRecord(indexStatusType, &status)
- opStream, err := tlv.NewStream(statusRecord)
- if err != nil {
- return err
- }
-
- var b bytes.Buffer
- if err := opStream.Encode(&b); err != nil {
- return err
- }
-
// Add the outpoint to our outpoint index with the tlv stream.
- if err := opBucket.Put(chanPointBuf.Bytes(), b.Bytes()); err != nil {
+ if err := cstate.PutOpenOutpointIndex(
+ opBucket, chanPointBuf.Bytes(),
+ ); err != nil {
return err
}
@@ -1084,13 +541,10 @@ func (c *ChannelStateDB) MarkChannelScidAliasNegotiated(
func (c *ChannelStateDB) MarkChannelDataLoss(channel *OpenChannel,
commitPoint *btcec.PublicKey) error {
- var b bytes.Buffer
- if err := WriteElement(&b, commitPoint); err != nil {
- return err
- }
-
putCommitPoint := func(chanBucket kvdb.RwBucket) error {
- return chanBucket.Put(dataLossCommitPointKey, b.Bytes())
+ return cstate.PutChannelDataLossCommitPoint(
+ chanBucket, commitPoint,
+ )
}
return c.putChanStatus(channel, ChanStatusLocalDataLoss, putCommitPoint)
@@ -1108,24 +562,22 @@ func (c *ChannelStateDB) FetchChannelDataLossCommitPoint(
tx, channel.IdentityPub, &channel.FundingOutpoint,
channel.ChainHash,
)
- switch err {
- case nil:
- case ErrNoChanDBExists, ErrNoActiveChannels, ErrChannelNotFound:
+ switch {
+ case err == nil:
+ case errors.Is(err, ErrNoChanDBExists),
+ errors.Is(err, ErrNoActiveChannels),
+ errors.Is(err, ErrChannelNotFound):
+
return ErrNoCommitPoint
default:
return err
}
- bs := chanBucket.Get(dataLossCommitPointKey)
- if bs == nil {
- return ErrNoCommitPoint
- }
- r := bytes.NewReader(bs)
- if err := ReadElements(r, &commitPoint); err != nil {
- return err
- }
+ commitPoint, err = cstate.FetchChannelDataLossCommitPoint(
+ chanBucket,
+ )
- return nil
+ return err
}, func() {
commitPoint = nil
})
@@ -1155,12 +607,6 @@ var (
func (c *ChannelStateDB) StoreChannelShutdownInfo(channel *OpenChannel,
info *ShutdownInfo) error {
- var b bytes.Buffer
- err := encodeShutdownInfo(info, &b)
- if err != nil {
- return err
- }
-
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, channel.IdentityPub, &channel.FundingOutpoint,
@@ -1170,7 +616,7 @@ func (c *ChannelStateDB) StoreChannelShutdownInfo(channel *OpenChannel,
return err
}
- return chanBucket.Put(shutdownInfoKey, b.Bytes())
+ return cstate.PutChannelShutdownInfo(chanBucket, info)
}, func() {})
}
@@ -1196,12 +642,7 @@ func (c *ChannelStateDB) FetchChannelShutdownInfo(
return err
}
- shutdownInfoBytes := chanBucket.Get(shutdownInfoKey)
- if shutdownInfoBytes == nil {
- return ErrNoShutdownInfo
- }
-
- shutdownInfo, err = decodeShutdownInfo(shutdownInfoBytes)
+ shutdownInfo, err = cstate.FetchChannelShutdownInfo(chanBucket)
return err
}, func() {
@@ -1239,7 +680,7 @@ func (c *ChannelStateDB) MarkChannelCommitmentBroadcasted(
closer lntypes.ChannelParty) error {
return c.markBroadcasted(
- channel, ChanStatusCommitBroadcasted, forceCloseTxKey,
+ channel, ChanStatusCommitBroadcasted, cstate.ForceCloseTxKey(),
closeTx, closer,
)
}
@@ -1250,7 +691,7 @@ func (c *ChannelStateDB) MarkChannelCoopBroadcasted(channel *OpenChannel,
closeTx *wire.MsgTx, closer lntypes.ChannelParty) error {
return c.markBroadcasted(
- channel, ChanStatusCoopBroadcasted, coopCloseTxKey,
+ channel, ChanStatusCoopBroadcasted, cstate.CoopCloseTxKey(),
closeTx, closer,
)
}
@@ -1269,13 +710,8 @@ func (c *ChannelStateDB) markBroadcasted(channel *OpenChannel,
channel.Lock()
defer channel.Unlock()
- var b bytes.Buffer
- if err := WriteElement(&b, closeTx); err != nil {
- return err
- }
-
putClosingTx := func(chanBucket kvdb.RwBucket) error {
- return chanBucket.Put(key, b.Bytes())
+ return cstate.PutChannelCloseTx(chanBucket, key, closeTx)
}
// Add the initiator status to the status provided. These statuses are
@@ -1295,15 +731,15 @@ func (c *ChannelStateDB) markBroadcasted(channel *OpenChannel,
func (c *ChannelStateDB) FetchChannelBroadcastedCommitment(
channel *OpenChannel) (*wire.MsgTx, error) {
- return c.getClosingTx(channel, forceCloseTxKey)
+ return c.getClosingTx(channel, cstate.ForceCloseTxKey())
}
// FetchChannelBroadcastedCooperative fetches the stored cooperative closing
// transaction.
func (c *ChannelStateDB) FetchChannelBroadcastedCooperative(
channel *OpenChannel) (*wire.MsgTx, error) {
- return c.getClosingTx(channel, coopCloseTxKey)
+ return c.getClosingTx(channel, cstate.CoopCloseTxKey())
}
// getClosingTx returns the stored closing transaction for key. The caller
@@ -1326,12 +762,9 @@ func (c *ChannelStateDB) getClosingTx(channel *OpenChannel,
return err
}
- bs := chanBucket.Get(key)
- if bs == nil {
- return ErrNoCloseTx
- }
- r := bytes.NewReader(bs)
- return ReadElement(r, &closeTx)
+ closeTx, err = cstate.FetchChannelCloseTx(chanBucket, key)
+
+ return err
}, func() {
closeTx = nil
})
@@ -1441,81 +874,15 @@ func (c *ChannelStateDB) ClearChannelStatus(channel *OpenChannel,
// putOpenChannel serializes, and stores the current state of the channel in its
// entirety.
func putOpenChannel(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
- // First, we'll write out all the relatively static fields, that are
- // decided upon initial channel creation.
- if err := putChanInfo(chanBucket, channel); err != nil {
- return fmt.Errorf("unable to store chan info: %w", err)
- }
-
- // With the static channel info written out, we'll now write out the
- // current commitment state for both parties.
- if err := putChanCommitments(chanBucket, channel); err != nil {
- return fmt.Errorf("unable to store chan commitments: %w", err)
- }
-
- // Next, if this is a frozen channel, we'll add in the axillary
- // information we need to store.
- if channel.ChanType.IsFrozen() || channel.ChanType.HasLeaseExpiration() {
- err := storeThawHeight(
- chanBucket, channel.ThawHeight,
- )
- if err != nil {
- return fmt.Errorf("unable to store thaw height: %w",
- err)
- }
- }
-
- // Finally, we'll write out the revocation state for both parties
- // within a distinct key space.
- if err := putChanRevocationState(chanBucket, channel); err != nil {
- return fmt.Errorf("unable to store chan revocations: %w", err)
- }
-
- return nil
+ return cstate.PutOpenChannel(chanBucket, channel)
}
// fetchOpenChannel retrieves, and deserializes (including decrypting
// sensitive) the complete channel currently active with the passed nodeID.
func fetchOpenChannel(chanBucket kvdb.RBucket,
chanPoint *wire.OutPoint) (*OpenChannel, error) {
- channel := &OpenChannel{
- FundingOutpoint: *chanPoint,
- }
-
- // First, we'll read all the static information that changes less
- // frequently from disk.
- if err := fetchChanInfo(chanBucket, channel); err != nil {
- return nil, fmt.Errorf("unable to fetch chan info: %w", err)
- }
-
- // With the static information read, we'll now read the current
- // commitment state for both sides of the channel.
- if err := fetchChanCommitments(chanBucket, channel); err != nil {
- return nil, fmt.Errorf("unable to fetch chan commitments: %w",
- err)
- }
-
- // Next, if this is a frozen channel, we'll add in the axillary
- // information we need to store.
- if channel.ChanType.IsFrozen() || channel.ChanType.HasLeaseExpiration() {
- thawHeight, err := fetchThawHeight(chanBucket)
- if err != nil {
- return nil, fmt.Errorf("unable to store thaw "+
- "height: %v", err)
- }
-
- channel.ThawHeight = thawHeight
- }
-
- // Finally, we'll retrieve the current revocation state so we can
- // properly
- if err := fetchChanRevocationState(chanBucket, channel); err != nil {
- return nil, fmt.Errorf("unable to fetch chan revocations: %w",
- err)
- }
-
- return channel, nil
+ return cstate.FetchOpenChannel(chanBucket, chanPoint)
}
// SyncPendingChannel writes a pending channel to the store and records the
@@ -1702,107 +1069,11 @@ func (c *ChannelStateDB) UpdateChannelCommitment(channel *OpenChannel,
// processFinalHtlc stores a final htlc outcome in the database if signaled via
// the supplied log update. An in-memory htlcs map is updated too.
-func processFinalHtlc(finalHtlcsBucket walletdb.ReadWriteBucket, upd LogUpdate,
+func processFinalHtlc(finalHtlcsBucket kvdb.RwBucket, upd LogUpdate,
finalHtlcs map[uint64]bool) error {
- var (
- settled bool
- id uint64
- )
-
- switch msg := upd.UpdateMsg.(type) {
- case *lnwire.UpdateFulfillHTLC:
- settled = true
- id = msg.ID
-
- case *lnwire.UpdateFailHTLC:
- settled = false
- id = msg.ID
-
- case *lnwire.UpdateFailMalformedHTLC:
- settled = false
- id = msg.ID
-
- default:
- return nil
- }
-
- // Store the final resolution in the database if a bucket is provided.
- if finalHtlcsBucket != nil {
- err := putFinalHtlc(
- finalHtlcsBucket, id,
- FinalHtlcInfo{
- Settled: settled,
- Offchain: true,
- },
- )
- if err != nil {
- return err
- }
- }
-
- finalHtlcs[id] = settled
-
- return nil
-}
-
-// serializeHtlcExtraData encodes a TLV stream of extra data to be stored with a
-// HTLC. It uses the update_add_htlc TLV types, because this is where extra
-// data is passed with a HTLC. At present blinding points are the only extra
-// data that we will store, and the function is a no-op if a nil blinding
-// point is provided.
-//
-// This function MUST be called to persist all HTLC values when they are
-// serialized.
-func serializeHtlcExtraData(h *HTLC) error {
- var records []tlv.RecordProducer
- h.BlindingPoint.WhenSome(func(b tlv.RecordT[lnwire.BlindingPointTlvType,
- *btcec.PublicKey]) {
-
- records = append(records, &b)
- })
-
- records, err := h.CustomRecords.ExtendRecordProducers(records)
- if err != nil {
- return err
- }
-
- return h.ExtraData.PackRecords(records...)
-}
-
-// deserializeHtlcExtraData extracts TLVs from the extra data persisted for the
-// HTLC and populates values in the struct accordingly.
-//
-// This function MUST be called to populate the struct properly when HTLCs
-// are deserialized.
-func deserializeHtlcExtraData(h *HTLC) error {
- if len(h.ExtraData) == 0 {
- return nil
- }
-
- blindingPoint := h.BlindingPoint.Zero()
- tlvMap, err := h.ExtraData.ExtractRecords(&blindingPoint)
- if err != nil {
- return err
- }
-
- if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil {
- h.BlindingPoint = tlv.SomeRecordT(blindingPoint)
-
- // Remove the entry from the TLV map. Anything left in the map
- // will be included in the custom records field.
- delete(tlvMap, h.BlindingPoint.TlvType())
- }
-
- // Set the custom records field to the remaining TLV records.
- customRecords, err := lnwire.NewCustomRecords(tlvMap)
- if err != nil {
- return err
- }
- h.CustomRecords = customRecords
-
- return nil
-}
+ return cstate.ProcessFinalHtlc(finalHtlcsBucket, upd, finalHtlcs)
+}
// SerializeHtlcs writes out the passed set of HTLC's into the passed writer
// using the current default on-disk serialization format.
@@ -1819,36 +1090,7 @@ func deserializeHtlcExtraData(h *HTLC) error {
// NOTE: This API is NOT stable, the on-disk format will likely change in the
// future.
func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error {
- numHtlcs := uint16(len(htlcs))
- if err := WriteElement(b, numHtlcs); err != nil {
- return err
- }
-
- for _, htlc := range htlcs {
- // Populate TLV stream for any additional fields contained
- // in the TLV.
- if err := serializeHtlcExtraData(&htlc); err != nil {
- return err
- }
-
- // The onion blob and hltc data are stored as a single var
- // bytes blob.
- onionAndExtraData := make(
- []byte, lnwire.OnionPacketSize+len(htlc.ExtraData),
- )
- copy(onionAndExtraData, htlc.OnionBlob[:])
- copy(onionAndExtraData[lnwire.OnionPacketSize:], htlc.ExtraData)
-
- if err := WriteElements(b,
- htlc.Signature, htlc.RHash, htlc.Amt, htlc.RefundTimeout,
- htlc.OutputIndex, htlc.Incoming, onionAndExtraData,
- htlc.HtlcIndex, htlc.LogIndex,
- ); err != nil {
- return err
- }
- }
-
- return nil
+ return cstate.SerializeHtlcs(b, htlcs...)
}
// DeserializeHtlcs attempts to read out a slice of HTLC's from the passed
@@ -1869,232 +1111,17 @@ func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error {
// NOTE: This API is NOT stable, the on-disk format will likely change in the
// future.
func DeserializeHtlcs(r io.Reader) ([]HTLC, error) {
- var numHtlcs uint16
- if err := ReadElement(r, &numHtlcs); err != nil {
- return nil, err
- }
-
- var htlcs []HTLC
- if numHtlcs == 0 {
- return htlcs, nil
- }
-
- htlcs = make([]HTLC, numHtlcs)
- for i := uint16(0); i < numHtlcs; i++ {
- var onionAndExtraData []byte
- if err := ReadElements(r,
- &htlcs[i].Signature, &htlcs[i].RHash, &htlcs[i].Amt,
- &htlcs[i].RefundTimeout, &htlcs[i].OutputIndex,
- &htlcs[i].Incoming, &onionAndExtraData,
- &htlcs[i].HtlcIndex, &htlcs[i].LogIndex,
- ); err != nil {
- return htlcs, err
- }
-
- // Sanity check that we have at least the onion blob size we
- // expect.
- if len(onionAndExtraData) < lnwire.OnionPacketSize {
- return nil, ErrOnionBlobLength
- }
-
- // First OnionPacketSize bytes are our fixed length onion
- // packet.
- copy(
- htlcs[i].OnionBlob[:],
- onionAndExtraData[0:lnwire.OnionPacketSize],
- )
-
- // Any additional bytes belong to extra data. ExtraDataLen
- // will be >= 0, because we know that we always have a fixed
- // length onion packet.
- extraDataLen := len(onionAndExtraData) - lnwire.OnionPacketSize
- if extraDataLen > 0 {
- htlcs[i].ExtraData = make([]byte, extraDataLen)
-
- copy(
- htlcs[i].ExtraData,
- onionAndExtraData[lnwire.OnionPacketSize:],
- )
- }
-
- // Finally, deserialize any TLVs contained in that extra data
- // if they are present.
- if err := deserializeHtlcExtraData(&htlcs[i]); err != nil {
- return nil, err
- }
- }
-
- return htlcs, nil
-}
-
-// serializeLogUpdate writes a log update to the provided io.Writer.
-func serializeLogUpdate(w io.Writer, l *LogUpdate) error {
- return WriteElements(w, l.LogIndex, l.UpdateMsg)
-}
-
-// deserializeLogUpdate reads a log update from the provided io.Reader.
-func deserializeLogUpdate(r io.Reader) (*LogUpdate, error) {
- l := &LogUpdate{}
- if err := ReadElements(r, &l.LogIndex, &l.UpdateMsg); err != nil {
- return nil, err
- }
-
- return l, nil
+ return cstate.DeserializeHtlcs(r)
}
// serializeLogUpdates serializes provided list of updates to a stream.
func serializeLogUpdates(w io.Writer, logUpdates []LogUpdate) error {
- numUpdates := uint16(len(logUpdates))
- if err := binary.Write(w, byteOrder, numUpdates); err != nil {
- return err
- }
-
- for _, diff := range logUpdates {
- err := WriteElements(w, diff.LogIndex, diff.UpdateMsg)
- if err != nil {
- return err
- }
- }
-
- return nil
+ return cstate.SerializeLogUpdates(w, logUpdates)
}
// deserializeLogUpdates deserializes a list of updates from a stream.
func deserializeLogUpdates(r io.Reader) ([]LogUpdate, error) {
- var numUpdates uint16
- if err := binary.Read(r, byteOrder, &numUpdates); err != nil {
- return nil, err
- }
-
- logUpdates := make([]LogUpdate, numUpdates)
- for i := 0; i < int(numUpdates); i++ {
- err := ReadElements(r,
- &logUpdates[i].LogIndex, &logUpdates[i].UpdateMsg,
- )
- if err != nil {
- return nil, err
- }
- }
- return logUpdates, nil
-}
-
-func serializeCommitDiff(w io.Writer, diff *CommitDiff) error { // nolint: dupl
- if err := serializeChanCommit(w, &diff.Commitment); err != nil {
- return err
- }
-
- if err := WriteElements(w, diff.CommitSig); err != nil {
- return err
- }
-
- if err := serializeLogUpdates(w, diff.LogUpdates); err != nil {
- return err
- }
-
- numOpenRefs := uint16(len(diff.OpenedCircuitKeys))
- if err := binary.Write(w, byteOrder, numOpenRefs); err != nil {
- return err
- }
-
- for _, openRef := range diff.OpenedCircuitKeys {
- err := WriteElements(w, openRef.ChanID, openRef.HtlcID)
- if err != nil {
- return err
- }
- }
-
- numClosedRefs := uint16(len(diff.ClosedCircuitKeys))
- if err := binary.Write(w, byteOrder, numClosedRefs); err != nil {
- return err
- }
-
- for _, closedRef := range diff.ClosedCircuitKeys {
- err := WriteElements(w, closedRef.ChanID, closedRef.HtlcID)
- if err != nil {
- return err
- }
- }
-
- // We'll also encode the commit aux data stream here. We do this here
- // rather than above (at the call to serializeChanCommit), to ensure
- // backwards compat for reads to existing non-custom channels.
- auxData := extractCommitTlvData(&diff.Commitment)
- if err := auxData.encode(w); err != nil {
- return fmt.Errorf("unable to write aux data: %w", err)
- }
-
- return nil
-}
-
-func deserializeCommitDiff(r io.Reader) (*CommitDiff, error) {
- var (
- d CommitDiff
- err error
- )
-
- d.Commitment, err = deserializeChanCommit(r)
- if err != nil {
- return nil, err
- }
-
- var msg lnwire.Message
- if err := ReadElements(r, &msg); err != nil {
- return nil, err
- }
- commitSig, ok := msg.(*lnwire.CommitSig)
- if !ok {
- return nil, fmt.Errorf("expected lnwire.CommitSig, instead "+
- "read: %T", msg)
- }
- d.CommitSig = commitSig
-
- d.LogUpdates, err = deserializeLogUpdates(r)
- if err != nil {
- return nil, err
- }
-
- var numOpenRefs uint16
- if err := binary.Read(r, byteOrder, &numOpenRefs); err != nil {
- return nil, err
- }
-
- d.OpenedCircuitKeys = make([]models.CircuitKey, numOpenRefs)
- for i := 0; i < int(numOpenRefs); i++ {
- err := ReadElements(r,
- &d.OpenedCircuitKeys[i].ChanID,
- &d.OpenedCircuitKeys[i].HtlcID)
- if err != nil {
- return nil, err
- }
- }
-
- var numClosedRefs uint16
- if err := binary.Read(r, byteOrder, &numClosedRefs); err != nil {
- return nil, err
- }
-
- d.ClosedCircuitKeys = make([]models.CircuitKey, numClosedRefs)
- for i := 0; i < int(numClosedRefs); i++ {
- err := ReadElements(r,
- &d.ClosedCircuitKeys[i].ChanID,
- &d.ClosedCircuitKeys[i].HtlcID)
- if err != nil {
- return nil, err
- }
- }
-
- // As a final step, we'll read out any aux commit data that we have at
- // the end of this byte stream. We do this here to ensure backward
- // compatibility, as otherwise we risk erroneously reading into the
- // wrong field.
- var auxData commitTlvData
- if err := auxData.decode(r); err != nil {
- return nil, fmt.Errorf("unable to decode aux data: %w", err)
- }
-
- amendCommitTlvData(&d.Commitment, auxData)
-
- return &d, nil
+ return cstate.DeserializeLogUpdates(r)
}
// AppendRemoteCommitChain appends a new CommitDiff to the remote party's
@@ -2162,7 +1189,7 @@ func (c *ChannelStateDB) AppendRemoteCommitChain(channel *OpenChannel,
// With the bucket retrieved, we'll now serialize the commit
// diff itself, and write it to disk.
var b2 bytes.Buffer
- if err := serializeCommitDiff(&b2, diff); err != nil {
+ if err := cstate.SerializeCommitDiff(&b2, diff); err != nil {
return err
}
return chanBucket.Put(commitDiffKey, b2.Bytes())
@@ -2194,7 +1221,7 @@ func (c *ChannelStateDB) RemoteCommitChainTip(channel *OpenChannel) (
}
tipReader := bytes.NewReader(tipBytes)
- dcd, err := deserializeCommitDiff(tipReader)
+ dcd, err := cstate.DeserializeCommitDiff(tipReader)
if err != nil {
return err
}
@@ -2365,7 +1392,7 @@ func (c *ChannelStateDB) AdvanceCommitChainTail(channel *OpenChannel,
// with the current locked-in commitment for the remote party.
tipBytes := chanBucket.Get(commitDiffKey)
tipReader := bytes.NewReader(tipBytes)
- newCommit, err := deserializeCommitDiff(tipReader)
+ newCommit, err := cstate.DeserializeCommitDiff(tipReader)
if err != nil {
return err
}
@@ -2479,18 +1506,7 @@ type FinalHtlcInfo = cstate.FinalHtlcInfo
func putFinalHtlc(finalHtlcsBucket kvdb.RwBucket, id uint64,
info FinalHtlcInfo) error {
- var key [8]byte
- byteOrder.PutUint64(key[:], id)
-
- var finalHtlcByte FinalHtlcByte
- if info.Settled {
- finalHtlcByte |= FinalHtlcSettledBit
- }
- if info.Offchain {
- finalHtlcByte |= FinalHtlcOffchainBit
- }
-
- return finalHtlcsBucket.Put(key[:], []byte{byte(finalHtlcByte)})
+ return cstate.PutFinalHtlc(finalHtlcsBucket, id, info)
}
// LoadFwdPkgs scans the forwarding log for any packages that haven't been
@@ -2800,27 +1816,7 @@ func locateOpenChannel(tx kvdb.RwTx, channel *OpenChannel) (kvdb.RwBucket,
// open to closed. The index entry must already exist; it was placed there
// when the channel was opened.
func updateClosedOutpointIndex(tx kvdb.RwTx, chanKey []byte) error {
- opBucket := tx.ReadWriteBucket(outpointBucket)
- if opBucket == nil {
- return ErrNoChanDBExists
- }
- if opBucket.Get(chanKey) == nil {
- return ErrMissingIndexEntry
- }
-
- status := uint8(outpointClosed)
- statusRecord := tlv.MakePrimitiveRecord(indexStatusType, &status)
- opStream, err := tlv.NewStream(statusRecord)
- if err != nil {
- return err
- }
-
- var b bytes.Buffer
- if err := opStream.Encode(&b); err != nil {
- return err
- }
-
- return opBucket.Put(chanKey, b.Bytes())
+ return cstate.UpdateClosedOutpointIndex(tx, chanKey)
}
// archiveClosedChannel writes the immutable close-time records of the
@@ -3004,671 +2000,115 @@ func (c *ChannelStateDB) RemoteRevocationStore(channel *OpenChannel) (
func putChannelCloseSummary(tx kvdb.RwTx, chanID []byte,
summary *ChannelCloseSummary, lastChanState *OpenChannel) error {
- closedChanBucket, err := tx.CreateTopLevelBucket(closedChannelBucket)
- if err != nil {
- return err
- }
-
- summary.RemoteCurrentRevocation = lastChanState.RemoteCurrentRevocation
- summary.RemoteNextRevocation = lastChanState.RemoteNextRevocation
- summary.LocalChanConfig = lastChanState.LocalChanCfg
-
- var b bytes.Buffer
- if err := serializeChannelCloseSummary(&b, summary); err != nil {
- return err
- }
-
- return closedChanBucket.Put(chanID, b.Bytes())
+ return cstate.PutChannelCloseSummary(
+ tx, chanID, summary, lastChanState,
+ )
}
func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error {
- err := WriteElements(w,
- cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
- cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
- cs.TimeLockedBalance, cs.CloseType, cs.IsPending,
- )
- if err != nil {
- return err
- }
-
- // If this is a close channel summary created before the addition of
- // the new fields, then we can exit here.
- if cs.RemoteCurrentRevocation == nil {
- return WriteElements(w, false)
- }
-
- // If fields are present, write boolean to indicate this, and continue.
- if err := WriteElements(w, true); err != nil {
- return err
- }
-
- if err := WriteElements(w, cs.RemoteCurrentRevocation); err != nil {
- return err
- }
-
- if err := writeChanConfig(w, &cs.LocalChanConfig); err != nil {
- return err
- }
-
- // The RemoteNextRevocation field is optional, as it's possible for a
- // channel to be closed before we learn of the next unrevoked
- // revocation point for the remote party. Write a boolean indicating
- // whether this field is present or not.
- if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil {
- return err
- }
-
- // Write the field, if present.
- if cs.RemoteNextRevocation != nil {
- if err = WriteElements(w, cs.RemoteNextRevocation); err != nil {
- return err
- }
- }
-
- // Write whether the channel sync message is present.
- if err := WriteElements(w, cs.LastChanSyncMsg != nil); err != nil {
- return err
- }
-
- // Write the channel sync message, if present.
- if cs.LastChanSyncMsg != nil {
- if err := WriteElements(w, cs.LastChanSyncMsg); err != nil {
- return err
- }
- }
-
- return nil
+ return cstate.SerializeChannelCloseSummary(w, cs)
}
func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
- c := &ChannelCloseSummary{}
-
- err := ReadElements(r,
- &c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
- &c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
- &c.TimeLockedBalance, &c.CloseType, &c.IsPending,
- )
- if err != nil {
- return nil, err
- }
-
- // We'll now check to see if the channel close summary was encoded with
- // any of the additional optional fields.
- var hasNewFields bool
- err = ReadElements(r, &hasNewFields)
- if err != nil {
- return nil, err
- }
-
- // If fields are not present, we can return.
- if !hasNewFields {
- return c, nil
- }
-
- // Otherwise read the new fields.
- if err := ReadElements(r, &c.RemoteCurrentRevocation); err != nil {
- return nil, err
- }
-
- if err := readChanConfig(r, &c.LocalChanConfig); err != nil {
- return nil, err
- }
-
- // Finally, we'll attempt to read the next unrevoked commitment point
- // for the remote party. If we closed the channel before receiving a
- // channel_ready message then this might not be present. A boolean
- // indicating whether the field is present will come first.
- var hasRemoteNextRevocation bool
- err = ReadElements(r, &hasRemoteNextRevocation)
- if err != nil {
- return nil, err
- }
-
- // If this field was written, read it.
- if hasRemoteNextRevocation {
- err = ReadElements(r, &c.RemoteNextRevocation)
- if err != nil {
- return nil, err
- }
- }
-
- // Check if we have a channel sync message to read.
- var hasChanSyncMsg bool
- err = ReadElements(r, &hasChanSyncMsg)
- if err == io.EOF {
- return c, nil
- } else if err != nil {
- return nil, err
- }
-
- // If a chan sync message is present, read it.
- if hasChanSyncMsg {
- // We must pass in reference to a lnwire.Message for the codec
- // to support it.
- var msg lnwire.Message
- if err := ReadElements(r, &msg); err != nil {
- return nil, err
- }
-
- chanSync, ok := msg.(*lnwire.ChannelReestablish)
- if !ok {
- return nil, errors.New("unable cast db Message to " +
- "ChannelReestablish")
- }
- c.LastChanSyncMsg = chanSync
- }
-
- return c, nil
+ return cstate.DeserializeCloseChannelSummary(r)
}
func writeChanConfig(b io.Writer, c *ChannelConfig) error {
- return WriteElements(b,
- c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC,
- c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey,
- c.RevocationBasePoint, c.PaymentBasePoint, c.DelayBasePoint,
- c.HtlcBasePoint,
- )
-}
-
-// fundingTxPresent returns true if expect the funding transcation to be found
-// on disk or already populated within the passed open channel struct.
-func fundingTxPresent(channel *OpenChannel) bool {
- chanType := channel.ChanType
-
- return chanType.IsSingleFunder() && chanType.HasFundingTx() &&
- channel.IsInitiator &&
- !channel.HasChanStatusForStore(ChanStatusRestored)
+ return cstate.WriteChanConfig(b, c)
}
func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
- var w bytes.Buffer
- if err := WriteElements(&w,
- channel.ChanType, channel.ChainHash, channel.FundingOutpoint,
- channel.ShortChannelID, channel.IsPending, channel.IsInitiator,
- channel.ChannelStatusForStore(), channel.FundingBroadcastHeight,
- channel.NumConfsRequired, channel.ChannelFlags,
- channel.IdentityPub, channel.Capacity, channel.TotalMSatSent,
- channel.TotalMSatReceived,
- ); err != nil {
- return err
- }
-
- // For single funder channels that we initiated, and we have the
- // funding transaction, then write the funding txn.
- if fundingTxPresent(channel) {
- if err := WriteElement(&w, channel.FundingTxn); err != nil {
- return err
- }
- }
-
- if err := writeChanConfig(&w, &channel.LocalChanCfg); err != nil {
- return err
- }
- if err := writeChanConfig(&w, &channel.RemoteChanCfg); err != nil {
- return err
- }
-
- auxData := extractOpenChannelTlvData(channel)
- if err := auxData.encode(&w); err != nil {
- return fmt.Errorf("unable to encode aux data: %w", 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, channel.LocalShutdownScript,
- ); err != nil {
- return err
- }
-
- return putOptionalUpfrontShutdownScript(
- chanBucket, remoteUpfrontShutdownKey, channel.RemoteShutdownScript,
- )
-}
-
-// 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 := 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 := ReadElement(r, &tempScript); err != nil {
- return err
- }
- *script = tempScript
-
- return nil
+ return cstate.PutChanInfo(chanBucket, channel)
}
func serializeChanCommit(w io.Writer, c *ChannelCommitment) error {
- if err := WriteElements(w,
- c.CommitHeight, c.LocalLogIndex, c.LocalHtlcIndex,
- c.RemoteLogIndex, c.RemoteHtlcIndex, c.LocalBalance,
- c.RemoteBalance, c.CommitFee, c.FeePerKw, c.CommitTx,
- c.CommitSig,
- ); err != nil {
- return err
- }
-
- return SerializeHtlcs(w, c.Htlcs...)
+ return cstate.SerializeChanCommit(w, c)
}
func putChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment,
local bool) error {
- var commitKey []byte
- if local {
- commitKey = append(chanCommitmentKey, byte(0x00))
- } else {
- commitKey = append(chanCommitmentKey, byte(0x01))
- }
-
- var b bytes.Buffer
- if err := serializeChanCommit(&b, c); err != nil {
- return err
- }
-
- // Before we write to disk, we'll also write our aux data as well.
- auxData := extractCommitTlvData(c)
- if err := auxData.encode(&b); err != nil {
- return fmt.Errorf("unable to write aux data: %w", err)
- }
-
- return chanBucket.Put(commitKey, b.Bytes())
+ return cstate.PutChanCommitment(chanBucket, c, local)
}
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.HasChanStatusForStore(ChanStatusRestored) {
- return nil
- }
-
- err := putChanCommitment(
- chanBucket, &channel.LocalCommitment, true,
- )
- if err != nil {
- return err
- }
-
- return putChanCommitment(
- chanBucket, &channel.RemoteCommitment, false,
- )
+ return cstate.PutChanCommitments(chanBucket, channel)
}
func putChanRevocationState(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
- var b bytes.Buffer
- err := WriteElements(
- &b, channel.RemoteCurrentRevocation, channel.RevocationProducer,
- channel.RevocationStore,
- )
- if err != nil {
- return err
- }
-
- // If the next revocation is present, which is only the case after the
- // ChannelReady message has been sent, then we'll write it to disk.
- if channel.RemoteNextRevocation != nil {
- err = WriteElements(&b, channel.RemoteNextRevocation)
- if err != nil {
- return err
- }
- }
-
- return chanBucket.Put(revocationStateKey, b.Bytes())
+ return cstate.PutChanRevocationState(chanBucket, channel)
}
func readChanConfig(b io.Reader, c *ChannelConfig) error {
- return ReadElements(b,
- &c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve,
- &c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay,
- &c.MultiSigKey, &c.RevocationBasePoint,
- &c.PaymentBasePoint, &c.DelayBasePoint,
- &c.HtlcBasePoint,
- )
+ return cstate.ReadChanConfig(b, c)
}
func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
- infoBytes := chanBucket.Get(chanInfoKey)
- if infoBytes == nil {
- return ErrNoChanInfoFound
- }
- r := bytes.NewReader(infoBytes)
-
- var chanStatus ChannelStatus
- if err := ReadElements(r,
- &channel.ChanType, &channel.ChainHash, &channel.FundingOutpoint,
- &channel.ShortChannelID, &channel.IsPending, &channel.IsInitiator,
- &chanStatus, &channel.FundingBroadcastHeight,
- &channel.NumConfsRequired, &channel.ChannelFlags,
- &channel.IdentityPub, &channel.Capacity, &channel.TotalMSatSent,
- &channel.TotalMSatReceived,
- ); err != nil {
- return err
- }
- channel.SetChannelStatusForStore(chanStatus)
-
- // For single funder channels that we initiated and have the funding
- // transaction to, read the funding txn.
- if fundingTxPresent(channel) {
- if err := ReadElement(r, &channel.FundingTxn); err != nil {
- return err
- }
- }
-
- if err := readChanConfig(r, &channel.LocalChanCfg); err != nil {
- return err
- }
- if err := readChanConfig(r, &channel.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.
- channel.LastWasRevoke = false
- } else {
- // Otherwise, read the value into the LastWasRevoke field.
- revokeReader := bytes.NewReader(lastWasRevokeBytes)
- err := ReadElements(revokeReader, &channel.LastWasRevoke)
- if err != nil {
- return err
- }
- }
-
- var auxData openChannelTlvData
- if err := auxData.decode(r); err != nil {
- return fmt.Errorf("unable to decode aux data: %w", err)
- }
-
- // Assign all the relevant fields from the aux data into the actual
- // open channel.
- amendOpenChannelTlvData(channel, auxData)
-
- // Finally, read the optional shutdown scripts.
- if err := getOptionalUpfrontShutdownScript(
- chanBucket, localUpfrontShutdownKey, &channel.LocalShutdownScript,
- ); err != nil {
- return err
- }
-
- return getOptionalUpfrontShutdownScript(
- chanBucket, remoteUpfrontShutdownKey, &channel.RemoteShutdownScript,
- )
+ return cstate.FetchChanInfo(chanBucket, channel)
}
func deserializeChanCommit(r io.Reader) (ChannelCommitment, error) {
- var c ChannelCommitment
-
- err := ReadElements(r,
- &c.CommitHeight, &c.LocalLogIndex, &c.LocalHtlcIndex, &c.RemoteLogIndex,
- &c.RemoteHtlcIndex, &c.LocalBalance, &c.RemoteBalance,
- &c.CommitFee, &c.FeePerKw, &c.CommitTx, &c.CommitSig,
- )
- if err != nil {
- return c, err
- }
-
- c.Htlcs, err = DeserializeHtlcs(r)
- if err != nil {
- return c, err
- }
-
- return c, nil
+ return cstate.DeserializeChanCommit(r)
}
func fetchChanCommitment(chanBucket kvdb.RBucket,
local bool) (ChannelCommitment, error) {
- var commitKey []byte
- if local {
- commitKey = append(chanCommitmentKey, byte(0x00))
- } else {
- commitKey = append(chanCommitmentKey, byte(0x01))
- }
-
- commitBytes := chanBucket.Get(commitKey)
- if commitBytes == nil {
- return ChannelCommitment{}, ErrNoCommitmentsFound
- }
-
- r := bytes.NewReader(commitBytes)
- chanCommit, err := deserializeChanCommit(r)
- if err != nil {
- return ChannelCommitment{}, fmt.Errorf("unable to decode "+
- "chan commit: %w", err)
- }
-
- // We'll also check to see if we have any aux data stored as the end of
- // the stream.
- var auxData commitTlvData
- if err := auxData.decode(r); err != nil {
- return ChannelCommitment{}, fmt.Errorf("unable to decode "+
- "chan aux data: %w", err)
- }
-
- amendCommitTlvData(&chanCommit, auxData)
-
- return chanCommit, nil
+ return cstate.FetchChanCommitment(chanBucket, local)
}
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.HasChanStatusForStore(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
+ return cstate.FetchChanCommitments(chanBucket, channel)
}
func fetchChanRevocationState(chanBucket kvdb.RBucket, channel *OpenChannel) error {
- revBytes := chanBucket.Get(revocationStateKey)
- if revBytes == nil {
- return ErrNoRevocationsFound
- }
- r := bytes.NewReader(revBytes)
-
- err := ReadElements(
- r, &channel.RemoteCurrentRevocation, &channel.RevocationProducer,
- &channel.RevocationStore,
- )
- if err != nil {
- return err
- }
-
- // If there aren't any bytes left in the buffer, then we don't yet have
- // the next remote revocation, so we can exit early here.
- if r.Len() == 0 {
- return nil
- }
-
- // Otherwise we'll read the next revocation for the remote party which
- // is always the last item within the buffer.
- return ReadElements(r, &channel.RemoteNextRevocation)
+ return cstate.FetchChanRevocationState(chanBucket, channel)
}
func deleteOpenChannel(chanBucket kvdb.RwBucket) error {
- if err := chanBucket.Delete(chanInfoKey); err != nil {
- return err
- }
-
- err := chanBucket.Delete(append(chanCommitmentKey, byte(0x00)))
- if err != nil {
- return err
- }
- err = chanBucket.Delete(append(chanCommitmentKey, byte(0x01)))
- if err != nil {
- return err
- }
-
- if err := chanBucket.Delete(revocationStateKey); err != nil {
- return err
- }
-
- if diff := chanBucket.Get(commitDiffKey); diff != nil {
- return chanBucket.Delete(commitDiffKey)
- }
-
- return nil
+ return cstate.DeleteOpenChannel(chanBucket)
}
// makeLogKey converts a uint64 into an 8 byte array.
+//
+// TODO(chanstate): remove together with the deprecated revocation log
+// bucket. chanstate owns the forwarding and revocation key helpers now,
+// and fetchOldRevocationLog is the only caller left here.
func makeLogKey(updateNum uint64) [8]byte {
var key [8]byte
byteOrder.PutUint64(key[:], updateNum)
return key
}
func fetchThawHeight(chanBucket kvdb.RBucket) (uint32, error) {
- var height uint32
-
- heightBytes := chanBucket.Get(frozenChanKey)
- heightReader := bytes.NewReader(heightBytes)
-
- if err := ReadElements(heightReader, &height); err != nil {
- return 0, err
- }
-
- return height, nil
+ return cstate.FetchThawHeight(chanBucket)
}
func storeThawHeight(chanBucket kvdb.RwBucket, height uint32) error {
- var heightBuf bytes.Buffer
- if err := WriteElements(&heightBuf, height); err != nil {
- return err
- }
-
- return chanBucket.Put(frozenChanKey, heightBuf.Bytes())
+ return cstate.StoreThawHeight(chanBucket, height)
}
func deleteThawHeight(chanBucket kvdb.RwBucket) error {
- return chanBucket.Delete(frozenChanKey)
-}
-
-// keyLocRecord is a wrapper struct around keychain.KeyLocator to implement the
-// tlv.RecordProducer interface.
-type keyLocRecord struct {
- keychain.KeyLocator
-}
-
-// Record 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.
-//
-// NOTE: This is part of the tlv.RecordProducer interface.
-func (k *keyLocRecord) Record() tlv.Record {
- // Note that we set the type here as zero, as when used with a
- // tlv.RecordT, the type param will be used as the type.
- return tlv.MakeStaticRecord(
- 0, &k.KeyLocator, 8, EKeyLocator, DKeyLocator,
- )
+ return cstate.DeleteThawHeight(chanBucket)
}
// 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")
+ return cstate.EKeyLocator(w, val, buf)
}
// 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)
+ return cstate.DKeyLocator(r, val, buf, l)
}
// ShutdownInfo contains various info about the shutdown initiation of a
// channel.
type ShutdownInfo = cstate.ShutdownInfo
// NewShutdownInfo constructs a new ShutdownInfo object.
-var NewShutdownInfo = cstate.NewShutdownInfo
-
-// encodeShutdownInfo serialises the ShutdownInfo to the given io.Writer.
-func encodeShutdownInfo(s *ShutdownInfo, w io.Writer) error {
- records := []tlv.Record{
- s.DeliveryScript.Record(),
- s.LocalInitiator.Record(),
- }
-
- stream, err := tlv.NewStream(records...)
- if err != nil {
- return err
- }
-
- return stream.Encode(w)
-}
-
-// decodeShutdownInfo constructs a ShutdownInfo struct by decoding the given
-// byte slice.
-func decodeShutdownInfo(b []byte) (*ShutdownInfo, error) {
- tlvStream := lnwire.ExtraOpaqueData(b)
-
- var info ShutdownInfo
- records := []tlv.RecordProducer{
- &info.DeliveryScript,
- &info.LocalInitiator,
- }
-
- _, err := tlvStream.ExtractRecords(records...)
+func NewShutdownInfo(deliveryScript lnwire.DeliveryAddress,
+ locallyInitiated bool) *ShutdownInfo {
- return &info, err
+ return cstate.NewShutdownInfo(deliveryScript, locallyInitiated)
}
### channeldb/channel_test.go
@@ -1439,7 +1439,8 @@ func TestRefresh(t *testing.T) {
}
require.Equal(
- t, chanOpenLoc, NewChannelPackager(state.ShortChanID()).source,
+ t, chanOpenLoc,
+ NewChannelPackager(state.ShortChanID()).Source(),
)
}
### channeldb/codec.go
@@ -1,461 +1,47 @@
package channeldb
import (
- "bytes"
- "encoding/binary"
- "fmt"
"io"
- "net"
"time"
- "github.com/btcsuite/btcd/btcec/v2"
- "github.com/btcsuite/btcd/btcutil/v2"
- "github.com/btcsuite/btcd/chainhash/v2"
- "github.com/btcsuite/btcd/wire/v2"
- graphdb "github.com/lightningnetwork/lnd/graph/db"
- "github.com/lightningnetwork/lnd/keychain"
- "github.com/lightningnetwork/lnd/lnwire"
- "github.com/lightningnetwork/lnd/shachain"
- "github.com/lightningnetwork/lnd/tlv"
+ cstate "github.com/lightningnetwork/lnd/chanstate"
)
// UnknownElementType is an error returned when the codec is unable to encode or
// decode a particular type.
-type UnknownElementType struct {
- method string
- element interface{}
-}
+type UnknownElementType = cstate.UnknownElementType
// NewUnknownElementType creates a new UnknownElementType error from the passed
// method name and element.
func NewUnknownElementType(method string, el interface{}) UnknownElementType {
- return UnknownElementType{method: method, element: el}
-}
-
-// Error returns the name of the method that encountered the error, as well as
-// the type that was unsupported.
-func (e UnknownElementType) Error() string {
- return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element)
+ return cstate.NewUnknownElementType(method, el)
}
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for storage on disk. The passed
// io.Writer should be backed by an appropriately sized byte slice, or be able
// to dynamically expand to accommodate additional data.
func WriteElement(w io.Writer, element interface{}) error {
- switch e := element.(type) {
- case keychain.KeyDescriptor:
- if err := binary.Write(w, byteOrder, e.Family); err != nil {
- return err
- }
- if err := binary.Write(w, byteOrder, e.Index); err != nil {
- return err
- }
-
- if e.PubKey != nil {
- if err := binary.Write(w, byteOrder, true); err != nil {
- return fmt.Errorf("error writing serialized "+
- "element: %w", err)
- }
-
- return WriteElement(w, e.PubKey)
- }
-
- return binary.Write(w, byteOrder, false)
- case ChannelType:
- var buf [8]byte
- if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil {
- return err
- }
-
- case chainhash.Hash:
- if _, err := w.Write(e[:]); err != nil {
- return err
- }
-
- case wire.OutPoint:
- return graphdb.WriteOutpoint(w, &e)
-
- case lnwire.ShortChannelID:
- if err := binary.Write(w, byteOrder, e.ToUint64()); err != nil {
- return err
- }
-
- case lnwire.ChannelID:
- if _, err := w.Write(e[:]); err != nil {
- return err
- }
-
- case int64, uint64:
- if err := binary.Write(w, byteOrder, e); err != nil {
- return err
- }
-
- case uint32:
- if err := binary.Write(w, byteOrder, e); err != nil {
- return err
- }
-
- case int32:
- if err := binary.Write(w, byteOrder, e); err != nil {
- return err
- }
-
- case uint16:
- if err := binary.Write(w, byteOrder, e); err != nil {
- return err
- }
-
- case uint8:
- if err := binary.Write(w, byteOrder, e); err != nil {
- return err
- }
-
- case bool:
- if err := binary.Write(w, byteOrder, e); err != nil {
- return err
- }
-
- case btcutil.Amount:
- if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
- return err
- }
-
- case lnwire.MilliSatoshi:
- if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
- return err
- }
-
- case *btcec.PrivateKey:
- b := e.Serialize()
- if _, err := w.Write(b); err != nil {
- return err
- }
-
- case *btcec.PublicKey:
- b := e.SerializeCompressed()
- if _, err := w.Write(b); err != nil {
- return err
- }
-
- case shachain.Producer:
- return e.Encode(w)
-
- case shachain.Store:
- return e.Encode(w)
-
- case *wire.MsgTx:
- return e.Serialize(w)
-
- case [32]byte:
- if _, err := w.Write(e[:]); err != nil {
- return err
- }
-
- case []byte:
- if err := wire.WriteVarBytes(w, 0, e); err != nil {
- return err
- }
-
- case lnwire.Message:
- var msgBuf bytes.Buffer
- if _, err := lnwire.WriteMessage(&msgBuf, e, 0); err != nil {
- return err
- }
-
- msgLen := uint16(len(msgBuf.Bytes()))
- if err := WriteElements(w, msgLen); err != nil {
- return err
- }
-
- if _, err := w.Write(msgBuf.Bytes()); err != nil {
- return err
- }
-
- case ChannelStatus:
- var buf [8]byte
- if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil {
- return err
- }
-
- case ClosureType:
- if err := binary.Write(w, byteOrder, e); err != nil {
- return err
- }
-
- case lnwire.FundingFlag:
- if err := binary.Write(w, byteOrder, e); err != nil {
- return err
- }
-
- case net.Addr:
- if err := graphdb.SerializeAddr(w, e); err != nil {
- return err
- }
-
- case []net.Addr:
- if err := WriteElement(w, uint32(len(e))); err != nil {
- return err
- }
-
- for _, addr := range e {
- if err := graphdb.SerializeAddr(w, addr); err != nil {
- return err
- }
- }
-
- default:
- return UnknownElementType{"WriteElement", e}
- }
-
- return nil
+ return cstate.WriteElement(w, element)
}
// WriteElements is writes each element in the elements slice to the passed
// io.Writer using WriteElement.
func WriteElements(w io.Writer, elements ...interface{}) error {
- for _, element := range elements {
- err := WriteElement(w, element)
- if err != nil {
- return err
- }
- }
- return nil
+ return cstate.WriteElements(w, elements...)
}
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of the database.
func ReadElement(r io.Reader, element interface{}) error {
- switch e := element.(type) {
- case *keychain.KeyDescriptor:
- if err := binary.Read(r, byteOrder, &e.Family); err != nil {
- return err
- }
- if err := binary.Read(r, byteOrder, &e.Index); err != nil {
- return err
- }
-
- var hasPubKey bool
- if err := binary.Read(r, byteOrder, &hasPubKey); err != nil {
- return err
- }
-
- if hasPubKey {
- return ReadElement(r, &e.PubKey)
- }
-
- case *ChannelType:
- 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
- }
-
- case *wire.OutPoint:
- return graphdb.ReadOutpoint(r, e)
-
- case *lnwire.ShortChannelID:
- var a uint64
- if err := binary.Read(r, byteOrder, &a); err != nil {
- return err
- }
- *e = lnwire.NewShortChanIDFromInt(a)
-
- case *lnwire.ChannelID:
- if _, err := io.ReadFull(r, e[:]); err != nil {
- return err
- }
-
- case *int64, *uint64:
- if err := binary.Read(r, byteOrder, e); err != nil {
- return err
- }
-
- case *uint32:
- if err := binary.Read(r, byteOrder, e); err != nil {
- return err
- }
-
- case *int32:
- if err := binary.Read(r, byteOrder, e); err != nil {
- return err
- }
-
- case *uint16:
- if err := binary.Read(r, byteOrder, e); err != nil {
- return err
- }
-
- case *uint8:
- if err := binary.Read(r, byteOrder, e); err != nil {
- return err
- }
-
- case *bool:
- if err := binary.Read(r, byteOrder, e); err != nil {
- return err
- }
-
- case *btcutil.Amount:
- var a uint64
- if err := binary.Read(r, byteOrder, &a); err != nil {
- return err
- }
-
- *e = btcutil.Amount(a)
-
- case *lnwire.MilliSatoshi:
- var a uint64
- if err := binary.Read(r, byteOrder, &a); err != nil {
- return err
- }
-
- *e = lnwire.MilliSatoshi(a)
-
- case **btcec.PrivateKey:
- var b [btcec.PrivKeyBytesLen]byte
- if _, err := io.ReadFull(r, b[:]); err != nil {
- return err
- }
-
- priv, _ := btcec.PrivKeyFromBytes(b[:])
- *e = priv
-
- case **btcec.PublicKey:
- var b [btcec.PubKeyBytesLenCompressed]byte
- if _, err := io.ReadFull(r, b[:]); err != nil {
- return err
- }
-
- pubKey, err := btcec.ParsePubKey(b[:])
- if err != nil {
- return err
- }
- *e = pubKey
-
- case *shachain.Producer:
- var root [32]byte
- if _, err := io.ReadFull(r, root[:]); err != nil {
- return err
- }
-
- // TODO(roasbeef): remove
- producer, err := shachain.NewRevocationProducerFromBytes(root[:])
- if err != nil {
- return err
- }
-
- *e = producer
-
- case *shachain.Store:
- store, err := shachain.NewRevocationStoreFromBytes(r)
- if err != nil {
- return err
- }
-
- *e = store
-
- case **wire.MsgTx:
- tx := wire.NewMsgTx(2)
- if err := tx.Deserialize(r); err != nil {
- return err
- }
-
- *e = tx
-
- case *[32]byte:
- if _, err := io.ReadFull(r, e[:]); err != nil {
- return err
- }
-
- case *[]byte:
- bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
- if err != nil {
- return err
- }
-
- *e = bytes
-
- case *lnwire.Message:
- var msgLen uint16
- if err := ReadElement(r, &msgLen); err != nil {
- return err
- }
-
- msgReader := io.LimitReader(r, int64(msgLen))
- msg, err := lnwire.ReadMessage(msgReader, 0)
- if err != nil {
- return err
- }
-
- *e = msg
-
- case *ChannelStatus:
- 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
- }
-
- case *lnwire.FundingFlag:
- if err := binary.Read(r, byteOrder, e); err != nil {
- return err
- }
-
- case *net.Addr:
- addr, err := graphdb.DeserializeAddr(r)
- if err != nil {
- return err
- }
- *e = addr
-
- case *[]net.Addr:
- var numAddrs uint32
- if err := ReadElement(r, &numAddrs); err != nil {
- return err
- }
-
- *e = make([]net.Addr, numAddrs)
- for i := uint32(0); i < numAddrs; i++ {
- addr, err := graphdb.DeserializeAddr(r)
- if err != nil {
- return err
- }
- (*e)[i] = addr
- }
-
- default:
- return UnknownElementType{"ReadElement", e}
- }
-
- return nil
+ return cstate.ReadElement(r, element)
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
- for _, element := range elements {
- err := ReadElement(r, element)
- if err != nil {
- return err
- }
- }
- return nil
+ return cstate.ReadElements(r, elements...)
}
// deserializeTime deserializes time as unix nanoseconds.
### channeldb/db.go
@@ -60,13 +60,11 @@ var (
// ErrFinalHtlcsBucketNotFound signals that the top-level final htlcs
// bucket does not exist.
- ErrFinalHtlcsBucketNotFound = errors.New("final htlcs bucket not " +
- "found")
+ ErrFinalHtlcsBucketNotFound = chanstate.ErrFinalHtlcsBucketNotFound
// ErrFinalChannelBucketNotFound signals that the channel bucket for
// final htlc outcomes does not exist.
- ErrFinalChannelBucketNotFound = errors.New("final htlcs channel " +
- "bucket not found")
+ ErrFinalChannelBucketNotFound = chanstate.ErrFinalChannelBucketNotFound
)
// migration is a function which takes a prior outdated version of the database
@@ -352,11 +350,6 @@ var (
// Big endian is the preferred byte order, due to cursor scans over
// integer keys iterating in order.
byteOrder = binary.BigEndian
-
- // channelOpeningStateBucket is the database bucket used to store the
- // channelOpeningState for each channel that is currently in the process
- // of being opened.
- channelOpeningStateBucket = []byte("channelOpeningState")
)
// DB is the primary datastore for the lnd daemon. The database stores
@@ -1823,12 +1816,9 @@ func (c *ChannelStateDB) SaveChannelOpeningState(outPoint,
serializedState []byte) error {
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
- bucket, err := tx.CreateTopLevelBucket(channelOpeningStateBucket)
- if err != nil {
- return err
- }
-
- return bucket.Put(outPoint, serializedState)
+ return chanstate.SaveChannelOpeningState(
+ tx, outPoint, serializedState,
+ )
}, func() {})
}
@@ -1840,21 +1830,12 @@ func (c *ChannelStateDB) GetChannelOpeningState(outPoint []byte) ([]byte,
var serializedState []byte
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
- bucket := tx.ReadBucket(channelOpeningStateBucket)
- if bucket == nil {
- // If the bucket does not exist, it means we never added
- // a channel to the db, so return ErrChannelNotFound.
- return ErrChannelNotFound
- }
-
- stateBytes := bucket.Get(outPoint)
- if stateBytes == nil {
- return ErrChannelNotFound
- }
-
- serializedState = append(serializedState, stateBytes...)
+ var err error
+ serializedState, err = chanstate.GetChannelOpeningState(
+ tx, outPoint,
+ )
- return nil
+ return err
}, func() {
serializedState = nil
})
@@ -1864,12 +1845,7 @@ func (c *ChannelStateDB) GetChannelOpeningState(outPoint []byte) ([]byte,
// DeleteChannelOpeningState removes any state for outPoint from the database.
func (c *ChannelStateDB) DeleteChannelOpeningState(outPoint []byte) error {
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
- bucket := tx.ReadWriteBucket(channelOpeningStateBucket)
- if bucket == nil {
- return ErrChannelNotFound
- }
-
- return bucket.Delete(outPoint)
+ return chanstate.DeleteChannelOpeningState(tx, outPoint)
}, func() {})
}
@@ -2157,33 +2133,17 @@ func (c *ChannelStateDB) FetchHistoricalChannel(outPoint *wire.OutPoint) (
func fetchFinalHtlcsBucket(tx kvdb.RTx,
chanID lnwire.ShortChannelID) (kvdb.RBucket, error) {
- finalHtlcsBucket := tx.ReadBucket(finalHtlcsBucket)
- if finalHtlcsBucket == nil {
- return nil, ErrFinalHtlcsBucketNotFound
- }
-
- var chanIDBytes [8]byte
- byteOrder.PutUint64(chanIDBytes[:], chanID.ToUint64())
-
- chanBucket := finalHtlcsBucket.NestedReadBucket(chanIDBytes[:])
- if chanBucket == nil {
- return nil, ErrFinalChannelBucketNotFound
- }
-
- return chanBucket, nil
+ return chanstate.FetchFinalHtlcsBucket(tx, chanID)
}
-var ErrHtlcUnknown = errors.New("htlc unknown")
+var ErrHtlcUnknown = chanstate.ErrHtlcUnknown
// LookupFinalHtlc retrieves a final htlc resolution from the database. If the
// htlc has no final resolution yet, ErrHtlcUnknown is returned.
func (c *ChannelStateDB) LookupFinalHtlc(chanID lnwire.ShortChannelID,
htlcIndex uint64) (*FinalHtlcInfo, error) {
- var idBytes [8]byte
- byteOrder.PutUint64(idBytes[:], htlcIndex)
-
- var settledByte byte
+ var info *FinalHtlcInfo
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
finalHtlcsBucket, err := fetchFinalHtlcsBucket(
@@ -2201,31 +2161,19 @@ func (c *ChannelStateDB) LookupFinalHtlc(chanID lnwire.ShortChannelID,
err)
}
- value := finalHtlcsBucket.Get(idBytes[:])
- if value == nil {
- return ErrHtlcUnknown
- }
-
- if len(value) != 1 {
- return errors.New("unexpected final htlc value length")
- }
-
- settledByte = value[0]
+ info, err = chanstate.FetchFinalHtlc(
+ finalHtlcsBucket, htlcIndex,
+ )
- return nil
+ return err
}, func() {
- settledByte = 0
+ info = nil
})
if err != nil {
return nil, err
}
- info := FinalHtlcInfo{
- Settled: settledByte&byte(FinalHtlcSettledBit) != 0,
- Offchain: settledByte&byte(FinalHtlcOffchainBit) != 0,
- }
-
- return &info, nil
+ return info, nil
}
// PutOnchainFinalHtlcOutcome stores the final on-chain outcome of an htlc in
### channeldb/error.go
@@ -2,12 +2,14 @@ package channeldb
import (
"fmt"
+
+ cstate "github.com/lightningnetwork/lnd/chanstate"
)
var (
// ErrNoChanDBExists is returned when a channel bucket hasn't been
// created.
- ErrNoChanDBExists = fmt.Errorf("channel db has not yet been created")
+ ErrNoChanDBExists = cstate.ErrNoChanDBExists
// ErrNoHistoricalBucket is returned when the historical channel bucket
// not been created yet.
@@ -24,7 +26,7 @@ var (
// ErrNoActiveChannels is returned when there is no active (open)
// channels within the database.
- ErrNoActiveChannels = fmt.Errorf("no active channels exist")
+ ErrNoActiveChannels = cstate.ErrNoActiveChannels
// ErrNoPastDeltas is returned when the channel delta bucket hasn't been
// created.
@@ -36,7 +38,7 @@ var (
// 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")
+ ErrChannelNotFound = cstate.ErrChannelNotFound
// ErrMetaNotFound is returned when meta bucket hasn't been
// created.
### channeldb/forwarding_package.go
@@ -1,12 +1,7 @@
package channeldb
import (
- "bytes"
- "errors"
-
cstate "github.com/lightningnetwork/lnd/chanstate"
- "github.com/lightningnetwork/lnd/kvdb"
- "github.com/lightningnetwork/lnd/lnwire"
)
type (
@@ -27,6 +22,30 @@ type (
// FwdPkg records all adds, settles, and fails that were locked in as a
// result of the remote peer sending us a revocation.
FwdPkg = cstate.FwdPkg
+
+ // SettleFailAcker is a generic interface providing the ability to
+ // acknowledge settle/fail HTLCs stored in forwarding packages.
+ SettleFailAcker = cstate.SettleFailAcker
+
+ // GlobalFwdPkgReader is an interface used to retrieve the forwarding
+ // packages of any active channel.
+ GlobalFwdPkgReader = cstate.GlobalFwdPkgReader
+
+ // FwdOperator defines the interfaces for managing forwarding packages
+ // that are external to a particular channel.
+ FwdOperator = cstate.FwdOperator
+
+ // FwdPackager supports all operations required to modify fwd packages,
+ // such as creation, updates, reading, and removal.
+ FwdPackager = cstate.FwdPackager
+
+ // SwitchPackager is a concrete implementation of the FwdOperator
+ // interface.
+ SwitchPackager = cstate.SwitchPackager
+
+ // ChannelPackager is used by a channel to manage the lifecycle of its
+ // forwarding packages.
+ ChannelPackager = cstate.ChannelPackager
)
const (
@@ -43,6 +62,10 @@ const (
)
var (
+ // fwdPackagesKey is retained while the root channeldb bucket setup
+ // remains in this package.
+ fwdPackagesKey = cstate.FwdPackagesBucketKey()
+
// NewPkgFilter initializes an empty PkgFilter supporting `count`
// elements.
NewPkgFilter = cstate.NewPkgFilter
@@ -52,713 +75,11 @@ var (
// ErrCorruptedFwdPkg signals that the on-disk structure of the
// forwarding package has potentially been mangled.
- ErrCorruptedFwdPkg = errors.New("fwding package db has been corrupted")
+ ErrCorruptedFwdPkg = cstate.ErrCorruptedFwdPkg
- // fwdPackagesKey is the root-level bucket that all forwarding packages
- // are written. This bucket is further subdivided based on the short
- // channel ID of each channel.
- //
- // Bucket hierarchy:
- //
- // fwdPackagesKey(root-bucket)
- // |
- // |-- <shortChannelID>
- // | |
- // | |-- <height>
- // | | |-- ackFilterKey: <encoded bytes of PkgFilter>
- // | | |-- settleFailFilterKey: <encoded bytes of PkgFilter>
- // | | |-- fwdFilterKey: <encoded bytes of PkgFilter>
- // | | |
- // | | |-- addBucketKey
- // | | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
- // | | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
- // | | | ...
- // | | |
- // | | |-- failSettleBucketKey
- // | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
- // | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
- // | | ...
- // | |
- // | |-- <height>
- // | | |
- // | ... ...
- // |
- // |
- // |-- <shortChannelID>
- // | |
- // | ...
- // ...
- //
- fwdPackagesKey = []byte("fwd-packages")
+ // NewSwitchPackager instantiates a new SwitchPackager.
+ NewSwitchPackager = cstate.NewSwitchPackager
- // addBucketKey is the bucket to which all Add log updates are written.
- addBucketKey = []byte("add-updates")
-
- // failSettleBucketKey is the bucket to which all Settle/Fail log
- // updates are written.
- failSettleBucketKey = []byte("fail-settle-updates")
-
- // fwdFilterKey is a key used to write the set of Adds that passed
- // validation and are to be forwarded to the switch.
- // NOTE: The presence of this key within a forwarding package indicates
- // that the package has reached FwdStateProcessed.
- fwdFilterKey = []byte("fwd-filter-key")
-
- // ackFilterKey is a key used to access the PkgFilter indicating which
- // Adds have received a Settle/Fail. This response may come from a
- // number of sources, including: exitHop settle/fails, switch failures,
- // chain arbiter interjections, as well as settle/fails from the
- // next hop in the route.
- ackFilterKey = []byte("ack-filter-key")
-
- // settleFailFilterKey is a key used to access the PkgFilter indicating
- // which Settles/Fails in have been received and processed by the link
- // that originally received the Add.
- settleFailFilterKey = []byte("settle-fail-filter-key")
+ // NewChannelPackager creates a new packager for a single channel.
+ NewChannelPackager = cstate.NewChannelPackager
)
-
-// SettleFailAcker is a generic interface providing the ability to acknowledge
-// settle/fail HTLCs stored in forwarding packages.
-type SettleFailAcker interface {
- // AckSettleFails atomically updates the settle-fail filters in *other*
- // channels' forwarding packages.
- AckSettleFails(tx kvdb.RwTx, settleFailRefs ...SettleFailRef) error
-}
-
-// GlobalFwdPkgReader is an interface used to retrieve the forwarding packages
-// of any active channel.
-type GlobalFwdPkgReader interface {
- // LoadChannelFwdPkgs loads all known forwarding packages for the given
- // channel.
- LoadChannelFwdPkgs(tx kvdb.RTx,
- source lnwire.ShortChannelID) ([]*FwdPkg, error)
-}
-
-// FwdOperator defines the interfaces for managing forwarding packages that are
-// external to a particular channel. This interface is used by the switch to
-// read forwarding packages from arbitrary channels, and acknowledge settles and
-// fails for locally-sourced payments.
-type FwdOperator interface {
- // GlobalFwdPkgReader provides read access to all known forwarding
- // packages
- GlobalFwdPkgReader
-
- // SettleFailAcker grants the ability to acknowledge settles or fails
- // residing in arbitrary forwarding packages.
- SettleFailAcker
-}
-
-// SwitchPackager is a concrete implementation of the FwdOperator interface.
-// A SwitchPackager offers the ability to read any forwarding package, and ack
-// arbitrary settle and fail HTLCs.
-type SwitchPackager struct{}
-
-// NewSwitchPackager instantiates a new SwitchPackager.
-func NewSwitchPackager() *SwitchPackager {
- return &SwitchPackager{}
-}
-
-// AckSettleFails atomically updates the settle-fail filters in *other*
-// channels' forwarding packages, to mark that the switch has received a settle
-// or fail residing in the forwarding package of a link.
-func (*SwitchPackager) AckSettleFails(tx kvdb.RwTx,
- settleFailRefs ...SettleFailRef) error {
-
- return ackSettleFails(tx, settleFailRefs)
-}
-
-// LoadChannelFwdPkgs loads all forwarding packages for a particular channel.
-func (*SwitchPackager) LoadChannelFwdPkgs(tx kvdb.RTx,
- source lnwire.ShortChannelID) ([]*FwdPkg, error) {
-
- return loadChannelFwdPkgs(tx, source)
-}
-
-// FwdPackager supports all operations required to modify fwd packages, such as
-// creation, updates, reading, and removal. The interfaces are broken down in
-// this way to support future delegation of the subinterfaces.
-//
-// TODO(ziggie): This kvdb transaction-level interface can likely be removed
-// now that chanstate.OpenChannelFwdPkgStore provides the backend-independent
-// forwarding package abstraction.
-type FwdPackager interface {
- // AddFwdPkg serializes and writes a FwdPkg for this channel at the
- // remote commitment height included in the forwarding package.
- AddFwdPkg(tx kvdb.RwTx, fwdPkg *FwdPkg) error
-
- // SetFwdFilter looks up the forwarding package at the remote `height`
- // and sets the `fwdFilter`, marking the Adds for which:
- // 1) We are not the exit node
- // 2) Passed all validation
- // 3) Should be forwarded to the switch immediately after a failure
- SetFwdFilter(tx kvdb.RwTx, height uint64, fwdFilter *PkgFilter) error
-
- // AckAddHtlcs atomically updates the add filters in this channel's
- // forwarding packages to mark the resolution of an Add that was
- // received from the remote party.
- AckAddHtlcs(tx kvdb.RwTx, addRefs ...AddRef) error
-
- // SettleFailAcker allows a link to acknowledge settle/fail HTLCs
- // belonging to other channels.
- SettleFailAcker
-
- // LoadFwdPkgs loads all known forwarding packages owned by this
- // channel.
- LoadFwdPkgs(tx kvdb.RTx) ([]*FwdPkg, error)
-
- // RemovePkg deletes a forwarding package owned by this channel at
- // the provided remote `height`.
- RemovePkg(tx kvdb.RwTx, height uint64) error
-
- // Wipe deletes all the forwarding packages owned by this channel.
- Wipe(tx kvdb.RwTx) error
-}
-
-// ChannelPackager is used by a channel to manage the lifecycle of its forwarding
-// packages. The packager is tied to a particular source channel ID, allowing it
-// to create and edit its own packages. Each packager also has the ability to
-// remove fail/settle htlcs that correspond to an add contained in one of
-// source's packages.
-type ChannelPackager struct {
- source lnwire.ShortChannelID
-}
-
-// NewChannelPackager creates a new packager for a single channel.
-func NewChannelPackager(source lnwire.ShortChannelID) *ChannelPackager {
- return &ChannelPackager{
- source: source,
- }
-}
-
-// AddFwdPkg writes a newly locked in forwarding package to disk.
-func (*ChannelPackager) AddFwdPkg(tx kvdb.RwTx, fwdPkg *FwdPkg) error { // nolint: dupl
- fwdPkgBkt, err := tx.CreateTopLevelBucket(fwdPackagesKey)
- if err != nil {
- return err
- }
-
- source := makeLogKey(fwdPkg.Source.ToUint64())
- sourceBkt, err := fwdPkgBkt.CreateBucketIfNotExists(source[:])
- if err != nil {
- return err
- }
-
- heightKey := makeLogKey(fwdPkg.Height)
- heightBkt, err := sourceBkt.CreateBucketIfNotExists(heightKey[:])
- if err != nil {
- return err
- }
-
- // Write ADD updates we received at this commit height.
- addBkt, err := heightBkt.CreateBucketIfNotExists(addBucketKey)
- if err != nil {
- return err
- }
-
- // Write SETTLE/FAIL updates we received at this commit height.
- failSettleBkt, err := heightBkt.CreateBucketIfNotExists(failSettleBucketKey)
- if err != nil {
- return err
- }
-
- for i := range fwdPkg.Adds {
- err = putLogUpdate(addBkt, uint16(i), &fwdPkg.Adds[i])
- if err != nil {
- return err
- }
- }
-
- // Persist the initialized pkg filter, which will be used to determine
- // when we can remove this forwarding package from disk.
- var ackFilterBuf bytes.Buffer
- if err := fwdPkg.AckFilter.Encode(&ackFilterBuf); err != nil {
- return err
- }
-
- if err := heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes()); err != nil {
- return err
- }
-
- for i := range fwdPkg.SettleFails {
- err = putLogUpdate(failSettleBkt, uint16(i), &fwdPkg.SettleFails[i])
- if err != nil {
- return err
- }
- }
-
- var settleFailFilterBuf bytes.Buffer
- err = fwdPkg.SettleFailFilter.Encode(&settleFailFilterBuf)
- if err != nil {
- return err
- }
-
- return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes())
-}
-
-// putLogUpdate writes an htlc to the provided `bkt`, using `index` as the key.
-func putLogUpdate(bkt kvdb.RwBucket, idx uint16, htlc *LogUpdate) error {
- var b bytes.Buffer
- if err := serializeLogUpdate(&b, htlc); err != nil {
- return err
- }
-
- return bkt.Put(uint16Key(idx), b.Bytes())
-}
-
-// LoadFwdPkgs scans the forwarding log for any packages that haven't been
-// processed, and returns their deserialized log updates in a map indexed by the
-// remote commitment height at which the updates were locked in.
-func (p *ChannelPackager) LoadFwdPkgs(tx kvdb.RTx) ([]*FwdPkg, error) {
- return loadChannelFwdPkgs(tx, p.source)
-}
-
-// loadChannelFwdPkgs loads all forwarding packages owned by `source`.
-func loadChannelFwdPkgs(tx kvdb.RTx, source lnwire.ShortChannelID) ([]*FwdPkg, error) {
- fwdPkgBkt := tx.ReadBucket(fwdPackagesKey)
- if fwdPkgBkt == nil {
- return nil, nil
- }
-
- sourceKey := makeLogKey(source.ToUint64())
- sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
- if sourceBkt == nil {
- return nil, nil
- }
-
- var heights []uint64
- if err := sourceBkt.ForEach(func(k, _ []byte) error {
- if len(k) != 8 {
- return ErrCorruptedFwdPkg
- }
-
- heights = append(heights, byteOrder.Uint64(k))
-
- return nil
- }); err != nil {
- return nil, err
- }
-
- // Load the forwarding package for each retrieved height.
- fwdPkgs := make([]*FwdPkg, 0, len(heights))
- for _, height := range heights {
- fwdPkg, err := loadFwdPkg(fwdPkgBkt, source, height)
- if err != nil {
- return nil, err
- }
-
- fwdPkgs = append(fwdPkgs, fwdPkg)
- }
-
- return fwdPkgs, nil
-}
-
-// loadFwdPkg reads the packager's fwd pkg at a given height, and determines the
-// appropriate FwdState.
-func loadFwdPkg(fwdPkgBkt kvdb.RBucket, source lnwire.ShortChannelID,
- height uint64) (*FwdPkg, error) {
-
- sourceKey := makeLogKey(source.ToUint64())
- sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
- if sourceBkt == nil {
- return nil, ErrCorruptedFwdPkg
- }
-
- heightKey := makeLogKey(height)
- heightBkt := sourceBkt.NestedReadBucket(heightKey[:])
- if heightBkt == nil {
- return nil, ErrCorruptedFwdPkg
- }
-
- // Load ADDs from disk.
- addBkt := heightBkt.NestedReadBucket(addBucketKey)
- if addBkt == nil {
- return nil, ErrCorruptedFwdPkg
- }
-
- adds, err := loadHtlcs(addBkt)
- if err != nil {
- return nil, err
- }
-
- // Load ack filter from disk.
- ackFilterBytes := heightBkt.Get(ackFilterKey)
- if ackFilterBytes == nil {
- return nil, ErrCorruptedFwdPkg
- }
- ackFilterReader := bytes.NewReader(ackFilterBytes)
-
- ackFilter := &PkgFilter{}
- if err := ackFilter.Decode(ackFilterReader); err != nil {
- return nil, err
- }
-
- // Load SETTLE/FAILs from disk.
- failSettleBkt := heightBkt.NestedReadBucket(failSettleBucketKey)
- if failSettleBkt == nil {
- return nil, ErrCorruptedFwdPkg
- }
-
- failSettles, err := loadHtlcs(failSettleBkt)
- if err != nil {
- return nil, err
- }
-
- // Load settle fail filter from disk.
- settleFailFilterBytes := heightBkt.Get(settleFailFilterKey)
- if settleFailFilterBytes == nil {
- return nil, ErrCorruptedFwdPkg
- }
- settleFailFilterReader := bytes.NewReader(settleFailFilterBytes)
-
- settleFailFilter := &PkgFilter{}
- if err := settleFailFilter.Decode(settleFailFilterReader); err != nil {
- return nil, err
- }
-
- // Initialize the fwding package, which always starts in the
- // FwdStateLockedIn. We can determine what state the package was left in
- // by examining constraints on the information loaded from disk.
- fwdPkg := &FwdPkg{
- Source: source,
- State: FwdStateLockedIn,
- Height: height,
- Adds: adds,
- AckFilter: ackFilter,
- SettleFails: failSettles,
- SettleFailFilter: settleFailFilter,
- }
-
- // Check if the forward filter has been persisted to disk.
- // This indicates whether the Adds in this package have been processed.
- //
- // NOTE: We also expect packages with no Adds (settle/fail only packages
- // or empty packages) to have the fwd filter set to signal that the
- // packages have been processed.
- fwdFilterBytes := heightBkt.Get(fwdFilterKey)
-
- // Handle packages with Adds that haven't been processed yet.
- if fwdFilterBytes == nil {
- // Create a new forward filter for the unprocessed Adds.
- nAdds := uint16(len(adds))
- fwdPkg.FwdFilter = NewPkgFilter(nAdds)
-
- return fwdPkg, nil
- }
-
- // Load the existing forward filter from disk.
- fwdFilterReader := bytes.NewReader(fwdFilterBytes)
- fwdPkg.FwdFilter = &PkgFilter{}
- if err := fwdPkg.FwdFilter.Decode(fwdFilterReader); err != nil {
- return nil, err
- }
-
- // Mark the package as processed since the forward filter exists.
- fwdPkg.State = FwdStateProcessed
-
- // If every add, settle, and fail has been fully acknowledged, we can
- // safely set the package's state to FwdStateCompleted, signalling that
- // it can be garbage collected.
- if fwdPkg.AckFilter.IsFull() && fwdPkg.SettleFailFilter.IsFull() {
- fwdPkg.State = FwdStateCompleted
- }
-
- return fwdPkg, nil
-}
-
-// loadHtlcs retrieves all serialized htlcs in a bucket, returning
-// them in order of the indexes they were written under.
-func loadHtlcs(bkt kvdb.RBucket) ([]LogUpdate, error) {
- var htlcs []LogUpdate
- if err := bkt.ForEach(func(_, v []byte) error {
- htlc, err := deserializeLogUpdate(bytes.NewReader(v))
- if err != nil {
- return err
- }
-
- htlcs = append(htlcs, *htlc)
-
- return nil
- }); err != nil {
- return nil, err
- }
-
- return htlcs, nil
-}
-
-// SetFwdFilter writes the set of indexes corresponding to Adds at the
-// `height` that are to be forwarded to the switch. Calling this method causes
-// the forwarding package at `height` to be in FwdStateProcessed. We write this
-// forwarding decision so that we always arrive at the same behavior for HTLCs
-// leaving this channel. After a restart, we skip validation of these Adds,
-// since they are assumed to have already been validated, and make the switch or
-// outgoing link responsible for handling replays.
-func (p *ChannelPackager) SetFwdFilter(tx kvdb.RwTx, height uint64,
- fwdFilter *PkgFilter) error {
-
- fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
- if fwdPkgBkt == nil {
- return ErrCorruptedFwdPkg
- }
-
- source := makeLogKey(p.source.ToUint64())
- sourceBkt := fwdPkgBkt.NestedReadWriteBucket(source[:])
- if sourceBkt == nil {
- return ErrCorruptedFwdPkg
- }
-
- heightKey := makeLogKey(height)
- heightBkt := sourceBkt.NestedReadWriteBucket(heightKey[:])
- if heightBkt == nil {
- return ErrCorruptedFwdPkg
- }
-
- // If the fwd filter has already been written, we return early to avoid
- // modifying the persistent state.
- forwardedAddsBytes := heightBkt.Get(fwdFilterKey)
- if forwardedAddsBytes != nil {
- return nil
- }
-
- // Otherwise we serialize and write the provided fwd filter.
- var b bytes.Buffer
- if err := fwdFilter.Encode(&b); err != nil {
- return err
- }
-
- return heightBkt.Put(fwdFilterKey, b.Bytes())
-}
-
-// AckAddHtlcs accepts a list of references to add htlcs, and updates the
-// AckAddFilter of those forwarding packages to indicate that a settle or fail
-// has been received in response to the add.
-func (p *ChannelPackager) AckAddHtlcs(tx kvdb.RwTx, addRefs ...AddRef) error {
- if len(addRefs) == 0 {
- return nil
- }
-
- fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
- if fwdPkgBkt == nil {
- return ErrCorruptedFwdPkg
- }
-
- sourceKey := makeLogKey(p.source.ToUint64())
- sourceBkt := fwdPkgBkt.NestedReadWriteBucket(sourceKey[:])
- if sourceBkt == nil {
- return ErrCorruptedFwdPkg
- }
-
- // Organize the forward references such that we just get a single slice
- // of indexes for each unique height.
- heightDiffs := make(map[uint64][]uint16)
- for _, addRef := range addRefs {
- heightDiffs[addRef.Height] = append(
- heightDiffs[addRef.Height],
- addRef.Index,
- )
- }
-
- // Load each height bucket once and remove all acked htlcs at that
- // height.
- for height, indexes := range heightDiffs {
- err := ackAddHtlcsAtHeight(sourceBkt, height, indexes)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// ackAddHtlcsAtHeight updates the AddAckFilter of a single forwarding package
-// with a list of indexes, writing the resulting filter back in its place.
-func ackAddHtlcsAtHeight(sourceBkt kvdb.RwBucket, height uint64,
- indexes []uint16) error {
-
- heightKey := makeLogKey(height)
- heightBkt := sourceBkt.NestedReadWriteBucket(heightKey[:])
- if heightBkt == nil {
- // If the height bucket isn't found, this could be because the
- // forwarding package was already removed. We'll return nil to
- // signal that the operation is successful, as there is nothing
- // to ack.
- return nil
- }
-
- // Load ack filter from disk.
- ackFilterBytes := heightBkt.Get(ackFilterKey)
- if ackFilterBytes == nil {
- return ErrCorruptedFwdPkg
- }
-
- ackFilter := &PkgFilter{}
- ackFilterReader := bytes.NewReader(ackFilterBytes)
- if err := ackFilter.Decode(ackFilterReader); err != nil {
- return err
- }
-
- // Update the ack filter for this height.
- for _, index := range indexes {
- ackFilter.Set(index)
- }
-
- // Write the resulting filter to disk.
- var ackFilterBuf bytes.Buffer
- if err := ackFilter.Encode(&ackFilterBuf); err != nil {
- return err
- }
-
- return heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes())
-}
-
-// AckSettleFails persistently acknowledges settles or fails from a remote forwarding
-// package. This should only be called after the source of the Add has locked in
-// the settle/fail, or it becomes otherwise safe to forgo retransmitting the
-// settle/fail after a restart.
-func (p *ChannelPackager) AckSettleFails(tx kvdb.RwTx, settleFailRefs ...SettleFailRef) error {
- return ackSettleFails(tx, settleFailRefs)
-}
-
-// ackSettleFails persistently acknowledges a batch of settle fail references.
-func ackSettleFails(tx kvdb.RwTx, settleFailRefs []SettleFailRef) error {
- if len(settleFailRefs) == 0 {
- return nil
- }
-
- fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
- if fwdPkgBkt == nil {
- return ErrCorruptedFwdPkg
- }
-
- // Organize the forward references such that we just get a single slice
- // of indexes for each unique destination-height pair.
- destHeightDiffs := make(map[lnwire.ShortChannelID]map[uint64][]uint16)
- for _, settleFailRef := range settleFailRefs {
- destHeights, ok := destHeightDiffs[settleFailRef.Source]
- if !ok {
- destHeights = make(map[uint64][]uint16)
- destHeightDiffs[settleFailRef.Source] = destHeights
- }
-
- destHeights[settleFailRef.Height] = append(
- destHeights[settleFailRef.Height],
- settleFailRef.Index,
- )
- }
-
- // With the references organized by destination and height, we now load
- // each remote bucket, and update the settle fail filter for any
- // settle/fail htlcs.
- for dest, destHeights := range destHeightDiffs {
- destKey := makeLogKey(dest.ToUint64())
- destBkt := fwdPkgBkt.NestedReadWriteBucket(destKey[:])
- if destBkt == nil {
- // If the destination bucket is not found, this is
- // likely the result of the destination channel being
- // closed and having it's forwarding packages wiped. We
- // won't treat this as an error, because the response
- // will no longer be retransmitted internally.
- continue
- }
-
- for height, indexes := range destHeights {
- err := ackSettleFailsAtHeight(destBkt, height, indexes)
- if err != nil {
- return err
- }
- }
- }
-
- return nil
-}
-
-// ackSettleFailsAtHeight given a destination bucket, acks the provided indexes
-// at particular a height by updating the settle fail filter.
-func ackSettleFailsAtHeight(destBkt kvdb.RwBucket, height uint64,
- indexes []uint16) error {
-
- heightKey := makeLogKey(height)
- heightBkt := destBkt.NestedReadWriteBucket(heightKey[:])
- if heightBkt == nil {
- // If the height bucket isn't found, this could be because the
- // forwarding package was already removed. We'll return nil to
- // signal that the operation is as there is nothing to ack.
- return nil
- }
-
- // Load ack filter from disk.
- settleFailFilterBytes := heightBkt.Get(settleFailFilterKey)
- if settleFailFilterBytes == nil {
- return ErrCorruptedFwdPkg
- }
-
- settleFailFilter := &PkgFilter{}
- settleFailFilterReader := bytes.NewReader(settleFailFilterBytes)
- if err := settleFailFilter.Decode(settleFailFilterReader); err != nil {
- return err
- }
-
- // Update the ack filter for this height.
- for _, index := range indexes {
- settleFailFilter.Set(index)
- }
-
- // Write the resulting filter to disk.
- var settleFailFilterBuf bytes.Buffer
- if err := settleFailFilter.Encode(&settleFailFilterBuf); err != nil {
- return err
- }
-
- return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes())
-}
-
-// RemovePkg deletes the forwarding package at the given height from the
-// packager's source bucket.
-func (p *ChannelPackager) RemovePkg(tx kvdb.RwTx, height uint64) error {
- fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
- if fwdPkgBkt == nil {
- return nil
- }
-
- sourceBytes := makeLogKey(p.source.ToUint64())
- sourceBkt := fwdPkgBkt.NestedReadWriteBucket(sourceBytes[:])
- if sourceBkt == nil {
- return ErrCorruptedFwdPkg
- }
-
- heightKey := makeLogKey(height)
-
- return sourceBkt.DeleteNestedBucket(heightKey[:])
-}
-
-// Wipe deletes all the channel's forwarding packages, if any.
-func (p *ChannelPackager) Wipe(tx kvdb.RwTx) error {
- // If the root bucket doesn't exist, there's no need to delete.
- fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
- if fwdPkgBkt == nil {
- return nil
- }
-
- sourceBytes := makeLogKey(p.source.ToUint64())
-
- // If the nested bucket doesn't exist, there's no need to delete.
- if fwdPkgBkt.NestedReadWriteBucket(sourceBytes[:]) == nil {
- return nil
- }
-
- return fwdPkgBkt.DeleteNestedBucket(sourceBytes[:])
-}
-
-// uint16Key writes the provided 16-bit unsigned integer to a 2-byte slice.
-func uint16Key(i uint16) []byte {
- key := make([]byte, 2)
- byteOrder.PutUint16(key, i)
- return key
-}
-
-// Compile-time constraint to ensure that ChannelPackager implements the public
-// FwdPackager interface.
-var _ FwdPackager = (*ChannelPackager)(nil)
-
-// Compile-time constraint to ensure that SwitchPackager implements the public
-// FwdOperator interface.
-var _ FwdOperator = (*SwitchPackager)(nil)
### channeldb/revocation_log.go
@@ -2,9 +2,7 @@ package channeldb
import (
"bytes"
- "errors"
"io"
- "math"
cstate "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/kvdb"
@@ -64,15 +62,15 @@ var (
// sub-bucket is dedicated for storing the minimal info required to
// re-construct a past state in order to punish a counterparty
// attempting a non-cooperative channel closure.
- revocationLogBucket = []byte("revocation-log")
+ revocationLogBucket = cstate.RevocationLogBucketKey()
// ErrLogEntryNotFound is returned when we cannot find a log entry at
// the height requested in the revocation log.
- ErrLogEntryNotFound = errors.New("log entry not found")
+ ErrLogEntryNotFound = cstate.ErrLogEntryNotFound
// ErrOutputIndexTooBig is returned when the output index is greater
// than uint16.
- ErrOutputIndexTooBig = errors.New("output index is over uint16")
+ ErrOutputIndexTooBig = cstate.ErrOutputIndexTooBig
)
// putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
@@ -82,86 +80,17 @@ var (
func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
ourOutputIndex, theirOutputIndex uint32, noAmtData bool) error {
- // Sanity check that the output indexes can be safely converted.
- if ourOutputIndex > math.MaxUint16 {
- return ErrOutputIndexTooBig
- }
- if theirOutputIndex > math.MaxUint16 {
- return ErrOutputIndexTooBig
- }
-
- rl := &RevocationLog{
- OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
- uint16(ourOutputIndex),
- ),
- TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
- uint16(theirOutputIndex),
- ),
- CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2, [32]byte](
- commit.CommitTx.TxHash(),
- ),
- HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
- }
-
- commit.CustomBlob.WhenSome(func(blob tlv.Blob) {
- rl.CustomBlob = tlv.SomeRecordT(
- tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
- )
- })
-
- if !noAmtData {
- rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
- tlv.NewBigSizeT(commit.LocalBalance),
- ))
-
- rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
- tlv.NewBigSizeT(commit.RemoteBalance),
- ))
- }
-
- for _, htlc := range commit.Htlcs {
- // Skip dust HTLCs.
- if htlc.OutputIndex < 0 {
- continue
- }
-
- // Sanity check that the output indexes can be safely
- // converted.
- if htlc.OutputIndex > math.MaxUint16 {
- return ErrOutputIndexTooBig
- }
-
- entry, err := NewHTLCEntryFromHTLC(htlc)
- if err != nil {
- return err
- }
- rl.HTLCEntries = append(rl.HTLCEntries, entry)
- }
-
- var b bytes.Buffer
- err := serializeRevocationLog(&b, rl)
- if err != nil {
- return err
- }
-
- logEntrykey := makeLogKey(commit.CommitHeight)
- return bucket.Put(logEntrykey[:], b.Bytes())
+ return cstate.PutRevocationLog(
+ bucket, commit, ourOutputIndex, theirOutputIndex, noAmtData,
+ )
}
// fetchRevocationLog queries the revocation log bucket to find an log entry.
// Return an error if not found.
func fetchRevocationLog(log kvdb.RBucket,
updateNum uint64) (RevocationLog, error) {
- logEntrykey := makeLogKey(updateNum)
- commitBytes := log.Get(logEntrykey[:])
- if commitBytes == nil {
- return RevocationLog{}, ErrLogEntryNotFound
- }
-
- commitReader := bytes.NewReader(commitBytes)
-
- return deserializeRevocationLog(commitReader)
+ return cstate.FetchRevocationLog(log, updateNum)
}
// serializeRevocationLog serializes a RevocationLog record based on tlv
### chanstate/codec.go
@@ -0,0 +1,464 @@
+package chanstate
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "net"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/btcsuite/btcd/btcutil/v2"
+ "github.com/btcsuite/btcd/chainhash/v2"
+ "github.com/btcsuite/btcd/wire/v2"
+ graphdb "github.com/lightningnetwork/lnd/graph/db"
+ "github.com/lightningnetwork/lnd/keychain"
+ "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/shachain"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+var byteOrder = binary.BigEndian
+
+// UnknownElementType is an error returned when the codec is unable to encode or
+// decode a particular type.
+type UnknownElementType struct {
+ method string
+ element interface{}
+}
+
+// NewUnknownElementType creates a new UnknownElementType error from the passed
+// method name and element.
+func NewUnknownElementType(method string, el interface{}) UnknownElementType {
+ return UnknownElementType{method: method, element: el}
+}
+
+// Error returns the name of the method that encountered the error, as well as
+// the type that was unsupported.
+func (e UnknownElementType) Error() string {
+ return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element)
+}
+
+// WriteElement is a one-stop shop to write the big endian representation of
+// any element which is to be serialized for storage on disk. The passed
+// io.Writer should be backed by an appropriately sized byte slice, or be able
+// to dynamically expand to accommodate additional data.
+func WriteElement(w io.Writer, element interface{}) error { //nolint:funlen
+ switch e := element.(type) {
+ case keychain.KeyDescriptor:
+ if err := binary.Write(w, byteOrder, e.Family); err != nil {
+ return err
+ }
+ if err := binary.Write(w, byteOrder, e.Index); err != nil {
+ return err
+ }
+
+ if e.PubKey != nil {
+ if err := binary.Write(w, byteOrder, true); err != nil {
+ return fmt.Errorf("error writing serialized "+
+ "element: %w", err)
+ }
+
+ return WriteElement(w, e.PubKey)
+ }
+
+ return binary.Write(w, byteOrder, false)
+ case ChannelType:
+ var buf [8]byte
+ if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil {
+ return err
+ }
+
+ case chainhash.Hash:
+ if _, err := w.Write(e[:]); err != nil {
+ return err
+ }
+
+ case wire.OutPoint:
+ return graphdb.WriteOutpoint(w, &e)
+
+ case lnwire.ShortChannelID:
+ if err := binary.Write(w, byteOrder, e.ToUint64()); err != nil {
+ return err
+ }
+
+ case lnwire.ChannelID:
+ if _, err := w.Write(e[:]); err != nil {
+ return err
+ }
+
+ case int64, uint64:
+ if err := binary.Write(w, byteOrder, e); err != nil {
+ return err
+ }
+
+ case uint32:
+ if err := binary.Write(w, byteOrder, e); err != nil {
+ return err
+ }
+
+ case int32:
+ if err := binary.Write(w, byteOrder, e); err != nil {
+ return err
+ }
+
+ case uint16:
+ if err := binary.Write(w, byteOrder, e); err != nil {
+ return err
+ }
+
+ case uint8:
+ if err := binary.Write(w, byteOrder, e); err != nil {
+ return err
+ }
+
+ case bool:
+ if err := binary.Write(w, byteOrder, e); err != nil {
+ return err
+ }
+
+ case btcutil.Amount:
+ if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
+ return err
+ }
+
+ case lnwire.MilliSatoshi:
+ if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
+ return err
+ }
+
+ case *btcec.PrivateKey:
+ b := e.Serialize()
+ if _, err := w.Write(b); err != nil {
+ return err
+ }
+
+ case *btcec.PublicKey:
+ b := e.SerializeCompressed()
+ if _, err := w.Write(b); err != nil {
+ return err
+ }
+
+ case shachain.Producer:
+ return e.Encode(w)
+
+ case shachain.Store:
+ return e.Encode(w)
+
+ case *wire.MsgTx:
+ return e.Serialize(w)
+
+ case [32]byte:
+ if _, err := w.Write(e[:]); err != nil {
+ return err
+ }
+
+ case []byte:
+ if err := wire.WriteVarBytes(w, 0, e); err != nil {
+ return err
+ }
+
+ case lnwire.Message:
+ var msgBuf bytes.Buffer
+ if _, err := lnwire.WriteMessage(&msgBuf, e, 0); err != nil {
+ return err
+ }
+
+ msgLen := uint16(len(msgBuf.Bytes()))
+ if err := WriteElements(w, msgLen); err != nil {
+ return err
+ }
+
+ if _, err := w.Write(msgBuf.Bytes()); err != nil {
+ return err
+ }
+
+ case ChannelStatus:
+ var buf [8]byte
+ if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil {
+ return err
+ }
+
+ case ClosureType:
+ if err := binary.Write(w, byteOrder, e); err != nil {
+ return err
+ }
+
+ case lnwire.FundingFlag:
+ if err := binary.Write(w, byteOrder, e); err != nil {
+ return err
+ }
+
+ case net.Addr:
+ if err := graphdb.SerializeAddr(w, e); err != nil {
+ return err
+ }
+
+ case []net.Addr:
+ if err := WriteElement(w, uint32(len(e))); err != nil {
+ return err
+ }
+
+ for _, addr := range e {
+ if err := graphdb.SerializeAddr(w, addr); err != nil {
+ return err
+ }
+ }
+
+ default:
+ return UnknownElementType{"WriteElement", e}
+ }
+
+ return nil
+}
+
+// WriteElements is writes each element in the elements slice to the passed
+// io.Writer using WriteElement.
+func WriteElements(w io.Writer, elements ...interface{}) error {
+ for _, element := range elements {
+ err := WriteElement(w, element)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// ReadElement is a one-stop utility function to deserialize any datastructure
+// encoded using the serialization format of the database.
+func ReadElement(r io.Reader, element interface{}) error { //nolint:funlen
+ switch e := element.(type) {
+ case *keychain.KeyDescriptor:
+ if err := binary.Read(r, byteOrder, &e.Family); err != nil {
+ return err
+ }
+ if err := binary.Read(r, byteOrder, &e.Index); err != nil {
+ return err
+ }
+
+ var hasPubKey bool
+ if err := binary.Read(r, byteOrder, &hasPubKey); err != nil {
+ return err
+ }
+
+ if hasPubKey {
+ return ReadElement(r, &e.PubKey)
+ }
+
+ case *ChannelType:
+ 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
+ }
+
+ case *wire.OutPoint:
+ return graphdb.ReadOutpoint(r, e)
+
+ case *lnwire.ShortChannelID:
+ var a uint64
+ if err := binary.Read(r, byteOrder, &a); err != nil {
+ return err
+ }
+ *e = lnwire.NewShortChanIDFromInt(a)
+
+ case *lnwire.ChannelID:
+ if _, err := io.ReadFull(r, e[:]); err != nil {
+ return err
+ }
+
+ case *int64, *uint64:
+ if err := binary.Read(r, byteOrder, e); err != nil {
+ return err
+ }
+
+ case *uint32:
+ if err := binary.Read(r, byteOrder, e); err != nil {
+ return err
+ }
+
+ case *int32:
+ if err := binary.Read(r, byteOrder, e); err != nil {
+ return err
+ }
+
+ case *uint16:
+ if err := binary.Read(r, byteOrder, e); err != nil {
+ return err
+ }
+
+ case *uint8:
+ if err := binary.Read(r, byteOrder, e); err != nil {
+ return err
+ }
+
+ case *bool:
+ if err := binary.Read(r, byteOrder, e); err != nil {
+ return err
+ }
+
+ case *btcutil.Amount:
+ var a uint64
+ if err := binary.Read(r, byteOrder, &a); err != nil {
+ return err
+ }
+
+ *e = btcutil.Amount(a)
+
+ case *lnwire.MilliSatoshi:
+ var a uint64
+ if err := binary.Read(r, byteOrder, &a); err != nil {
+ return err
+ }
+
+ *e = lnwire.MilliSatoshi(a)
+
+ case **btcec.PrivateKey:
+ var b [btcec.PrivKeyBytesLen]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+
+ priv, _ := btcec.PrivKeyFromBytes(b[:])
+ *e = priv
+
+ case **btcec.PublicKey:
+ var b [btcec.PubKeyBytesLenCompressed]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+
+ pubKey, err := btcec.ParsePubKey(b[:])
+ if err != nil {
+ return err
+ }
+ *e = pubKey
+
+ case *shachain.Producer:
+ var root [32]byte
+ if _, err := io.ReadFull(r, root[:]); err != nil {
+ return err
+ }
+
+ // TODO(roasbeef): remove
+ producer, err := shachain.NewRevocationProducerFromBytes(
+ root[:],
+ )
+ if err != nil {
+ return err
+ }
+
+ *e = producer
+
+ case *shachain.Store:
+ store, err := shachain.NewRevocationStoreFromBytes(r)
+ if err != nil {
+ return err
+ }
+
+ *e = store
+
+ case **wire.MsgTx:
+ tx := wire.NewMsgTx(2)
+ if err := tx.Deserialize(r); err != nil {
+ return err
+ }
+
+ *e = tx
+
+ case *[32]byte:
+ if _, err := io.ReadFull(r, e[:]); err != nil {
+ return err
+ }
+
+ case *[]byte:
+ bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
+ if err != nil {
+ return err
+ }
+
+ *e = bytes
+
+ case *lnwire.Message:
+ var msgLen uint16
+ if err := ReadElement(r, &msgLen); err != nil {
+ return err
+ }
+
+ msgReader := io.LimitReader(r, int64(msgLen))
+ msg, err := lnwire.ReadMessage(msgReader, 0)
+ if err != nil {
+ return err
+ }
+
+ *e = msg
+
+ case *ChannelStatus:
+ 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
+ }
+
+ case *lnwire.FundingFlag:
+ if err := binary.Read(r, byteOrder, e); err != nil {
+ return err
+ }
+
+ case *net.Addr:
+ addr, err := graphdb.DeserializeAddr(r)
+ if err != nil {
+ return err
+ }
+ *e = addr
+
+ case *[]net.Addr:
+ var numAddrs uint32
+ if err := ReadElement(r, &numAddrs); err != nil {
+ return err
+ }
+
+ *e = make([]net.Addr, numAddrs)
+ for i := uint32(0); i < numAddrs; i++ {
+ addr, err := graphdb.DeserializeAddr(r)
+ if err != nil {
+ return err
+ }
+ (*e)[i] = addr
+ }
+
+ default:
+ return UnknownElementType{"ReadElement", e}
+ }
+
+ return nil
+}
+
+// ReadElements deserializes a variable number of elements into the passed
+// io.Reader, with each element being deserialized according to the ReadElement
+// function.
+func ReadElements(r io.Reader, elements ...interface{}) error {
+ for _, element := range elements {
+ err := ReadElement(r, element)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
### chanstate/errors.go
@@ -6,6 +6,10 @@ import (
)
var (
+ // ErrNoChanDBExists is returned when a channel bucket hasn't been
+ // created.
+ ErrNoChanDBExists = fmt.Errorf("channel db has not yet been created")
+
// ErrNoCommitmentsFound is returned when a channel has not set
// commitment states.
ErrNoCommitmentsFound = fmt.Errorf("no commitments found")
@@ -14,6 +18,10 @@ var (
// have any channels state.
ErrNoChanInfoFound = fmt.Errorf("no chan info found")
+ // 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")
+
// ErrNoRevocationsFound is returned when revocation state for a
// particular channel cannot be found.
ErrNoRevocationsFound = fmt.Errorf("no revocations found")
@@ -24,6 +32,10 @@ var (
// tolerant.
ErrNoPendingCommit = fmt.Errorf("no pending commits found")
+ // ErrNoActiveChannels is returned when there is no active (open)
+ // channels within the database.
+ ErrNoActiveChannels = fmt.Errorf("no active channels exist")
+
// ErrNoCommitPoint is returned when no data loss commit point is found
// in the database.
ErrNoCommitPoint = fmt.Errorf("no commit point found")
### chanstate/forwarding.go
@@ -4,7 +4,6 @@ import (
"bytes"
"encoding/binary"
"fmt"
- "io"
"github.com/lightningnetwork/lnd/lnwire"
)
@@ -21,24 +20,6 @@ type AddRef struct {
Index uint16
}
-// Encode serializes the AddRef to the given io.Writer.
-func (a *AddRef) Encode(w io.Writer) error {
- if err := binary.Write(w, binary.BigEndian, a.Height); err != nil {
- return err
- }
-
- return binary.Write(w, binary.BigEndian, a.Index)
-}
-
-// Decode deserializes the AddRef from the given io.Reader.
-func (a *AddRef) Decode(r io.Reader) error {
- if err := binary.Read(r, binary.BigEndian, &a.Height); err != nil {
- return err
- }
-
- return binary.Read(r, binary.BigEndian, &a.Index)
-}
-
// SettleFailRef is used to locate a Settle/Fail in another channel's FwdPkg. A
// channel does not remove its own Settle/Fail htlcs, so the source is provided
// to locate a db bucket belonging to another channel.
@@ -160,36 +141,6 @@ func (f *PkgFilter) IsFull() bool {
return true
}
-// Size returns number of bytes produced when the PkgFilter is serialized.
-func (f *PkgFilter) Size() uint16 {
- // 2 bytes for uint16 `count`, then round up number of bytes required to
- // represent `count` bits.
- return 2 + (f.count+7)/8
-}
-
-// Encode writes the filter to the provided io.Writer.
-func (f *PkgFilter) Encode(w io.Writer) error {
- if err := binary.Write(w, binary.BigEndian, f.count); err != nil {
- return err
- }
-
- _, err := w.Write(f.filter)
-
- return err
-}
-
-// Decode reads the filter from the provided io.Reader.
-func (f *PkgFilter) Decode(r io.Reader) error {
- if err := binary.Read(r, binary.BigEndian, &f.count); err != nil {
- return err
- }
-
- f.filter = make([]byte, f.Size()-2)
- _, err := io.ReadFull(r, f.filter)
-
- return err
-}
-
// String returns a human-readable string.
func (f *PkgFilter) String() string {
return fmt.Sprintf("count=%v, filter=%v", f.count, f.filter)
### chanstate/kv_channel_keys.go
@@ -0,0 +1,141 @@
+package chanstate
+
+var (
+ // closedChannelBucket stores summarization information concerning
+ // previously open, but now closed channels.
+ closedChannelBucket = []byte("closed-chan-bucket")
+
+ // openChannelBucket 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.
+ //
+ // openChan -> nodeID -> chanPoint
+ //
+ // TODO(roasbeef): flesh out comment.
+ openChannelBucket = []byte("open-chan-bucket")
+
+ // outpointBucket stores all of our channel outpoints and a tlv
+ // stream containing channel data.
+ //
+ // outpoint -> tlv stream.
+ //
+ 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.
+ historicalChannelBucket = []byte("historical-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")
+
+ // 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")
+
+ // 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")
+
+ // revocationStateKey stores their current revocation hash, our
+ // preimage producer and their preimage store.
+ revocationStateKey = []byte("revocation-state-key")
+
+ // unsignedAckedUpdatesKey is an entry in the channel bucket that
+ // contains the remote updates that we have acked, but not yet signed
+ // for in one of our remote commits.
+ unsignedAckedUpdatesKey = []byte("unsigned-acked-updates-key")
+
+ // remoteUnsignedLocalUpdatesKey is an entry in the channel bucket that
+ // contains the local updates that the remote party has acked, but
+ // has not yet signed for in one of their local commits.
+ remoteUnsignedLocalUpdatesKey = []byte(
+ "remote-unsigned-local-updates-key",
+ )
+
+ // commitDiffKey stores the current pending commitment state we've
+ // extended to the remote party (if any). Each time we propose a new
+ // state, we store the information necessary to reconstruct this state
+ // from the prior commitment. This allows us to resync the remote party
+ // to their expected state in the case of message loss.
+ //
+ // TODO(roasbeef): rename to commit chain?
+ commitDiffKey = []byte("commit-diff-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")
+)
+
+// ClosedChannelBucketKey returns the top-level closed-channel summary bucket
+// key.
+func ClosedChannelBucketKey() []byte {
+ return closedChannelBucket
+}
+
+// OpenChannelBucketKey returns the top-level open-channel bucket key.
+func OpenChannelBucketKey() []byte {
+ return openChannelBucket
+}
+
+// OutpointBucketKey returns the top-level outpoint index bucket key.
+func OutpointBucketKey() []byte {
+ return outpointBucket
+}
+
+// ChanIDBucketKey returns the top-level channel ID index bucket key.
+func ChanIDBucketKey() []byte {
+ return chanIDBucket
+}
+
+// HistoricalChannelBucketKey returns the top-level historical channel bucket
+// key.
+func HistoricalChannelBucketKey() []byte {
+ return historicalChannelBucket
+}
+
+// UnsignedAckedUpdatesKey returns the channel-bucket key for unsigned acked
+// remote updates.
+func UnsignedAckedUpdatesKey() []byte {
+ return unsignedAckedUpdatesKey
+}
+
+// RemoteUnsignedLocalUpdatesKey returns the channel-bucket key for remote
+// unsigned local updates.
+func RemoteUnsignedLocalUpdatesKey() []byte {
+ return remoteUnsignedLocalUpdatesKey
+}
+
+// CommitDiffKey returns the channel-bucket key for the current pending
+// commitment diff.
+func CommitDiffKey() []byte {
+ return commitDiffKey
+}
+
+// LastWasRevokeKey returns the channel-bucket key for the last update type.
+func LastWasRevokeKey() []byte {
+ return lastWasRevokeKey
+}
### chanstate/kv_channel_setup.go
@@ -0,0 +1,55 @@
+package chanstate
+
+import "github.com/lightningnetwork/lnd/kvdb"
+
+var (
+ // channelOpeningStateBucket is the database bucket used to store the
+ // channelOpeningState for each channel that is currently in the process
+ // of being opened.
+ channelOpeningStateBucket = []byte("channelOpeningState")
+)
+
+// SaveChannelOpeningState saves the serialized channel state for the provided
+// chanPoint to the channelOpeningStateBucket.
+func SaveChannelOpeningState(tx kvdb.RwTx, outPoint,
+ serializedState []byte) error {
+
+ bucket, err := tx.CreateTopLevelBucket(channelOpeningStateBucket)
+ if err != nil {
+ return err
+ }
+
+ return bucket.Put(outPoint, serializedState)
+}
+
+// GetChannelOpeningState fetches the serialized channel state for the provided
+// outPoint from the database, or returns ErrChannelNotFound if the channel is
+// not found.
+func GetChannelOpeningState(tx kvdb.RTx, outPoint []byte) ([]byte, error) {
+ bucket := tx.ReadBucket(channelOpeningStateBucket)
+ if bucket == nil {
+ // If the bucket does not exist, it means we never added
+ // a channel to the db, so return ErrChannelNotFound.
+ return nil, ErrChannelNotFound
+ }
+
+ stateBytes := bucket.Get(outPoint)
+ if stateBytes == nil {
+ return nil, ErrChannelNotFound
+ }
+
+ var serializedState []byte
+ serializedState = append(serializedState, stateBytes...)
+
+ return serializedState, nil
+}
+
+// DeleteChannelOpeningState removes any state for outPoint from the database.
+func DeleteChannelOpeningState(tx kvdb.RwTx, outPoint []byte) error {
+ bucket := tx.ReadWriteBucket(channelOpeningStateBucket)
+ if bucket == nil {
+ return ErrChannelNotFound
+ }
+
+ return bucket.Delete(outPoint)
+}
### chanstate/kv_close_summary.go
@@ -0,0 +1,176 @@
+package chanstate
+
+import (
+ "bytes"
+ "errors"
+ "io"
+
+ "github.com/lightningnetwork/lnd/kvdb"
+ "github.com/lightningnetwork/lnd/lnwire"
+)
+
+// PutChannelCloseSummary writes the immutable close-time summary of a channel
+// under the closed channel bucket.
+func PutChannelCloseSummary(tx kvdb.RwTx, chanID []byte,
+ summary *ChannelCloseSummary, lastChanState *OpenChannel) error {
+
+ closedChanBucket, err := tx.CreateTopLevelBucket(closedChannelBucket)
+ if err != nil {
+ return err
+ }
+
+ summary.RemoteCurrentRevocation = lastChanState.RemoteCurrentRevocation
+ summary.RemoteNextRevocation = lastChanState.RemoteNextRevocation
+ summary.LocalChanConfig = lastChanState.LocalChanCfg
+
+ var b bytes.Buffer
+ if err := SerializeChannelCloseSummary(&b, summary); err != nil {
+ return err
+ }
+
+ return closedChanBucket.Put(chanID, b.Bytes())
+}
+
+// SerializeChannelCloseSummary serializes a channel close summary.
+func SerializeChannelCloseSummary(w io.Writer,
+ cs *ChannelCloseSummary) error {
+
+ err := WriteElements(w,
+ cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
+ cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
+ cs.TimeLockedBalance, cs.CloseType, cs.IsPending,
+ )
+ if err != nil {
+ return err
+ }
+
+ // If this is a close channel summary created before the addition of
+ // the new fields, then we can exit here.
+ if cs.RemoteCurrentRevocation == nil {
+ return WriteElements(w, false)
+ }
+
+ // If fields are present, write boolean to indicate this, and continue.
+ if err := WriteElements(w, true); err != nil {
+ return err
+ }
+
+ if err := WriteElements(w, cs.RemoteCurrentRevocation); err != nil {
+ return err
+ }
+
+ if err := WriteChanConfig(w, &cs.LocalChanConfig); err != nil {
+ return err
+ }
+
+ // The RemoteNextRevocation field is optional, as it's possible for a
+ // channel to be closed before we learn of the next unrevoked
+ // revocation point for the remote party. Write a boolean indicating
+ // whether this field is present or not.
+ if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil {
+ return err
+ }
+
+ // Write the field, if present.
+ if cs.RemoteNextRevocation != nil {
+ if err = WriteElements(w, cs.RemoteNextRevocation); err != nil {
+ return err
+ }
+ }
+
+ // Write whether the channel sync message is present.
+ if err := WriteElements(w, cs.LastChanSyncMsg != nil); err != nil {
+ return err
+ }
+
+ // Write the channel sync message, if present.
+ if cs.LastChanSyncMsg != nil {
+ if err := WriteElements(w, cs.LastChanSyncMsg); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// DeserializeCloseChannelSummary deserializes a channel close summary.
+func DeserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
+ c := &ChannelCloseSummary{}
+
+ err := ReadElements(r,
+ &c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
+ &c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
+ &c.TimeLockedBalance, &c.CloseType, &c.IsPending,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ // We'll now check to see if the channel close summary was encoded with
+ // any of the additional optional fields.
+ var hasNewFields bool
+ err = ReadElements(r, &hasNewFields)
+ if err != nil {
+ return nil, err
+ }
+
+ // If fields are not present, we can return.
+ if !hasNewFields {
+ return c, nil
+ }
+
+ // Otherwise read the new fields.
+ if err := ReadElements(r, &c.RemoteCurrentRevocation); err != nil {
+ return nil, err
+ }
+
+ if err := ReadChanConfig(r, &c.LocalChanConfig); err != nil {
+ return nil, err
+ }
+
+ // Finally, we'll attempt to read the next unrevoked commitment point
+ // for the remote party. If we closed the channel before receiving a
+ // channel_ready message then this might not be present. A boolean
+ // indicating whether the field is present will come first.
+ var hasRemoteNextRevocation bool
+ err = ReadElements(r, &hasRemoteNextRevocation)
+ if err != nil {
+ return nil, err
+ }
+
+ // If this field was written, read it.
+ if hasRemoteNextRevocation {
+ err = ReadElements(r, &c.RemoteNextRevocation)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Check if we have a channel sync message to read.
+ var hasChanSyncMsg bool
+ err = ReadElements(r, &hasChanSyncMsg)
+ if errors.Is(err, io.EOF) {
+ return c, nil
+ } else if err != nil {
+ return nil, err
+ }
+
+ // If a chan sync message is present, read it.
+ if hasChanSyncMsg {
+ // We must pass in reference to a lnwire.Message for the codec
+ // to support it.
+ var msg lnwire.Message
+ if err := ReadElements(r, &msg); err != nil {
+ return nil, err
+ }
+
+ chanSync, ok := msg.(*lnwire.ChannelReestablish)
+ if !ok {
+ return nil, errors.New("unable cast db Message to " +
+ "ChannelReestablish")
+ }
+ c.LastChanSyncMsg = chanSync
+ }
+
+ return c, nil
+}
### chanstate/kv_close_tx.go
@@ -0,0 +1,62 @@
+package chanstate
+
+import (
+ "bytes"
+
+ "github.com/btcsuite/btcd/wire/v2"
+ "github.com/lightningnetwork/lnd/kvdb"
+)
+
+var (
+ // forceCloseTxKey points to a the unilateral closing tx that we
+ // broadcasted when moving the channel to state CommitBroadcasted.
+ forceCloseTxKey = []byte("closing-tx-key")
+
+ // coopCloseTxKey points to a the cooperative closing tx that we
+ // broadcasted when moving the channel to state CoopBroadcasted.
+ coopCloseTxKey = []byte("coop-closing-tx-key")
+)
+
+// ForceCloseTxKey returns the key used to store the unilateral closing
+// transaction in a channel bucket.
+func ForceCloseTxKey() []byte {
+ return forceCloseTxKey
+}
+
+// CoopCloseTxKey returns the key used to store the cooperative closing
+// transaction in a channel bucket.
+func CoopCloseTxKey() []byte {
+ return coopCloseTxKey
+}
+
+// PutChannelCloseTx stores the closing transaction under the requested key in
+// the target channel bucket.
+func PutChannelCloseTx(chanBucket kvdb.RwBucket, key []byte,
+ closeTx *wire.MsgTx) error {
+
+ var b bytes.Buffer
+ if err := closeTx.Serialize(&b); err != nil {
+ return err
+ }
+
+ return chanBucket.Put(key, b.Bytes())
+}
+
+// FetchChannelCloseTx retrieves the closing transaction stored under the
+// requested key in the target channel bucket.
+func FetchChannelCloseTx(chanBucket kvdb.RBucket,
+ key []byte) (*wire.MsgTx, error) {
+
+ bs := chanBucket.Get(key)
+ if bs == nil {
+ return nil, ErrNoCloseTx
+ }
+
+ closeTx := wire.NewMsgTx(2)
+ r := bytes.NewReader(bs)
+ if err := closeTx.Deserialize(r); err != nil {
+ return nil, err
+ }
+
+ return closeTx, nil
+}
### chanstate/kv_commitment.go
@@ -0,0 +1,658 @@
+package chanstate
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "io"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/lightningnetwork/lnd/fn/v2"
+ "github.com/lightningnetwork/lnd/graph/db/models"
+ "github.com/lightningnetwork/lnd/kvdb"
+ "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+// serializeHtlcExtraData encodes a TLV stream of extra data to be stored with a
+// HTLC. It uses the update_add_htlc TLV types, because this is where extra
+// data is passed with a HTLC. At present blinding points are the only extra
+// data that we will store, and the function is a no-op if a nil blinding
+// point is provided.
+//
+// This function MUST be called to persist all HTLC values when they are
+// serialized.
+func serializeHtlcExtraData(h *HTLC) error {
+ var records []tlv.RecordProducer
+ h.BlindingPoint.WhenSome(func(b tlv.RecordT[lnwire.BlindingPointTlvType,
+ *btcec.PublicKey]) {
+
+ records = append(records, &b)
+ })
+
+ records, err := h.CustomRecords.ExtendRecordProducers(records)
+ if err != nil {
+ return err
+ }
+
+ return h.ExtraData.PackRecords(records...)
+}
+
+// deserializeHtlcExtraData extracts TLVs from the extra data persisted for the
+// HTLC and populates values in the struct accordingly.
+//
+// This function MUST be called to populate the struct properly when HTLCs
+// are deserialized.
+func deserializeHtlcExtraData(h *HTLC) error {
+ if len(h.ExtraData) == 0 {
+ return nil
+ }
+
+ blindingPoint := h.BlindingPoint.Zero()
+ tlvMap, err := h.ExtraData.ExtractRecords(&blindingPoint)
+ if err != nil {
+ return err
+ }
+
+ if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil {
+ h.BlindingPoint = tlv.SomeRecordT(blindingPoint)
+
+ // Remove the entry from the TLV map. Anything left in the map
+ // will be included in the custom records field.
+ delete(tlvMap, h.BlindingPoint.TlvType())
+ }
+
+ // Set the custom records field to the remaining TLV records.
+ customRecords, err := lnwire.NewCustomRecords(tlvMap)
+ if err != nil {
+ return err
+ }
+ h.CustomRecords = customRecords
+
+ return nil
+}
+
+// SerializeHtlcs writes out the passed set of HTLC's into the passed writer
+// using the current default on-disk serialization format.
+//
+// This inline serialization has been extended to allow storage of extra data
+// associated with a HTLC in the following way:
+// - The known-length onion blob (1366 bytes) is serialized as var bytes in
+// WriteElements (ie, the length 1366 was written, followed by the 1366
+// onion bytes).
+// - To include extra data, we append any extra data present to this one
+// variable length of data. Since we know that the onion is strictly 1366
+// bytes, any length after that should be considered to be extra data.
+//
+// NOTE: This API is NOT stable, the on-disk format will likely change in the
+// future.
+func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error {
+ numHtlcs := uint16(len(htlcs))
+ if err := WriteElement(b, numHtlcs); err != nil {
+ return err
+ }
+
+ for _, htlc := range htlcs {
+ // Populate TLV stream for any additional fields contained
+ // in the TLV.
+ if err := serializeHtlcExtraData(&htlc); err != nil {
+ return err
+ }
+
+ // The onion blob and hltc data are stored as a single var
+ // bytes blob.
+ onionAndExtraData := make(
+ []byte, lnwire.OnionPacketSize+len(htlc.ExtraData),
+ )
+ copy(onionAndExtraData, htlc.OnionBlob[:])
+ copy(onionAndExtraData[lnwire.OnionPacketSize:], htlc.ExtraData)
+
+ if err := WriteElements(b,
+ //nolint:ll
+ htlc.Signature, htlc.RHash, htlc.Amt, htlc.RefundTimeout,
+ htlc.OutputIndex, htlc.Incoming, onionAndExtraData,
+ htlc.HtlcIndex, htlc.LogIndex,
+ ); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// DeserializeHtlcs attempts to read out a slice of HTLC's from the passed
+// io.Reader. The bytes within the passed reader MUST have been previously
+// written to using the SerializeHtlcs function.
+//
+// This inline deserialization has been extended to allow storage of extra data
+// associated with a HTLC in the following way:
+// - The known-length onion blob (1366 bytes) and any additional data present
+// are read out as a single blob of variable byte data.
+// - They are stored like this to take advantage of the variable space
+// available for extension without migration (see SerializeHtlcs).
+// - The first 1366 bytes are interpreted as the onion blob, and any remaining
+// bytes as extra HTLC data.
+// - This extra HTLC data is expected to be serialized as a TLV stream, and
+// its parsing is left to higher layers.
+//
+// NOTE: This API is NOT stable, the on-disk format will likely change in the
+// future.
+func DeserializeHtlcs(r io.Reader) ([]HTLC, error) {
+ var numHtlcs uint16
+ if err := ReadElement(r, &numHtlcs); err != nil {
+ return nil, err
+ }
+
+ var htlcs []HTLC
+ if numHtlcs == 0 {
+ return htlcs, nil
+ }
+
+ htlcs = make([]HTLC, numHtlcs)
+ for i := uint16(0); i < numHtlcs; i++ {
+ var onionAndExtraData []byte
+ if err := ReadElements(r,
+ &htlcs[i].Signature, &htlcs[i].RHash, &htlcs[i].Amt,
+ &htlcs[i].RefundTimeout, &htlcs[i].OutputIndex,
+ &htlcs[i].Incoming, &onionAndExtraData,
+ &htlcs[i].HtlcIndex, &htlcs[i].LogIndex,
+ ); err != nil {
+ return htlcs, err
+ }
+
+ // Sanity check that we have at least the onion blob size we
+ // expect.
+ if len(onionAndExtraData) < lnwire.OnionPacketSize {
+ return nil, ErrOnionBlobLength
+ }
+
+ // First OnionPacketSize bytes are our fixed length onion
+ // packet.
+ copy(
+ htlcs[i].OnionBlob[:],
+ onionAndExtraData[0:lnwire.OnionPacketSize],
+ )
+
+ // Any additional bytes belong to extra data. ExtraDataLen
+ // will be >= 0, because we know that we always have a fixed
+ // length onion packet.
+ extraDataLen := len(onionAndExtraData) - lnwire.OnionPacketSize
+ if extraDataLen > 0 {
+ htlcs[i].ExtraData = make([]byte, extraDataLen)
+
+ copy(
+ htlcs[i].ExtraData,
+ onionAndExtraData[lnwire.OnionPacketSize:],
+ )
+ }
+
+ // Finally, deserialize any TLVs contained in that extra data
+ // if they are present.
+ if err := deserializeHtlcExtraData(&htlcs[i]); err != nil {
+ return nil, err
+ }
+ }
+
+ return htlcs, nil
+}
+
+// SerializeChanCommit serializes the channel commitment.
+func SerializeChanCommit(w io.Writer, c *ChannelCommitment) error {
+ if err := WriteElements(w,
+ c.CommitHeight, c.LocalLogIndex, c.LocalHtlcIndex,
+ c.RemoteLogIndex, c.RemoteHtlcIndex, c.LocalBalance,
+ c.RemoteBalance, c.CommitFee, c.FeePerKw, c.CommitTx,
+ c.CommitSig,
+ ); err != nil {
+ return err
+ }
+
+ return SerializeHtlcs(w, c.Htlcs...)
+}
+
+// DeserializeChanCommit deserializes the channel commitment.
+func DeserializeChanCommit(r io.Reader) (ChannelCommitment, error) {
+ var c ChannelCommitment
+
+ err := ReadElements(r,
+ &c.CommitHeight, &c.LocalLogIndex, &c.LocalHtlcIndex,
+ &c.RemoteLogIndex, &c.RemoteHtlcIndex, &c.LocalBalance,
+ &c.RemoteBalance, &c.CommitFee, &c.FeePerKw, &c.CommitTx,
+ &c.CommitSig,
+ )
+ if err != nil {
+ return c, err
+ }
+
+ c.Htlcs, err = DeserializeHtlcs(r)
+ if err != nil {
+ return c, err
+ }
+
+ return c, nil
+}
+
+func chanCommitKey(local bool) []byte {
+ commitKey := make([]byte, 0, len(chanCommitmentKey)+1)
+ commitKey = append(commitKey, chanCommitmentKey...)
+ if local {
+ return append(commitKey, byte(0x00))
+ }
+
+ return append(commitKey, byte(0x01))
+}
+
+// PutChanCommitment writes a channel commitment to the channel bucket.
+func PutChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment,
+ local bool) error {
+
+ var b bytes.Buffer
+ if err := SerializeChanCommit(&b, c); err != nil {
+ return err
+ }
+
+ // Before we write to disk, we'll also write our aux data as well.
+ if err := EncodeCommitTlvData(&b, c); err != nil {
+ return fmt.Errorf("unable to write aux data: %w", err)
+ }
+
+ return chanBucket.Put(chanCommitKey(local), b.Bytes())
+}
+
+// PutChanCommitments writes the local and remote commitments to the channel
+// bucket.
+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.HasChanStatusForStore(ChanStatusRestored) {
+ return nil
+ }
+
+ err := PutChanCommitment(
+ chanBucket, &channel.LocalCommitment, true,
+ )
+ if err != nil {
+ return err
+ }
+
+ return PutChanCommitment(
+ chanBucket, &channel.RemoteCommitment, false,
+ )
+}
+
+// PutChanRevocationState writes the remote revocation state to the channel
+// bucket.
+func PutChanRevocationState(chanBucket kvdb.RwBucket,
+ channel *OpenChannel) error {
+
+ var b bytes.Buffer
+ err := WriteElements(
+ &b, channel.RemoteCurrentRevocation, channel.RevocationProducer,
+ channel.RevocationStore,
+ )
+ if err != nil {
+ return err
+ }
+
+ // If the next revocation is present, which is only the case after the
+ // ChannelReady message has been sent, then we'll write it to disk.
+ if channel.RemoteNextRevocation != nil {
+ err = WriteElements(&b, channel.RemoteNextRevocation)
+ if err != nil {
+ return err
+ }
+ }
+
+ return chanBucket.Put(revocationStateKey, b.Bytes())
+}
+
+// FetchChanCommitment reads a channel commitment from the channel bucket.
+func FetchChanCommitment(chanBucket kvdb.RBucket,
+ local bool) (ChannelCommitment, error) {
+
+ commitBytes := chanBucket.Get(chanCommitKey(local))
+ if commitBytes == nil {
+ return ChannelCommitment{}, ErrNoCommitmentsFound
+ }
+
+ r := bytes.NewReader(commitBytes)
+ chanCommit, err := DeserializeChanCommit(r)
+ if err != nil {
+ return ChannelCommitment{}, fmt.Errorf("unable to decode "+
+ "chan commit: %w", err)
+ }
+
+ // We'll also check to see if we have any aux data stored as the end of
+ // the stream.
+ if err := DecodeCommitTlvData(r, &chanCommit); err != nil {
+ return ChannelCommitment{}, fmt.Errorf("unable to decode "+
+ "chan aux data: %w", err)
+ }
+
+ return chanCommit, nil
+}
+
+// FetchChanCommitments reads the local and remote commitments from the channel
+// bucket.
+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.HasChanStatusForStore(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
+}
+
+// FetchChanRevocationState reads the remote revocation state from the channel
+// bucket.
+func FetchChanRevocationState(chanBucket kvdb.RBucket,
+ channel *OpenChannel) error {
+
+ revBytes := chanBucket.Get(revocationStateKey)
+ if revBytes == nil {
+ return ErrNoRevocationsFound
+ }
+ r := bytes.NewReader(revBytes)
+
+ err := ReadElements(
+ r,
+ &channel.RemoteCurrentRevocation, &channel.RevocationProducer,
+ &channel.RevocationStore,
+ )
+ if err != nil {
+ return err
+ }
+
+ // If there aren't any bytes left in the buffer, then we don't yet have
+ // the next remote revocation, so we can exit early here.
+ if r.Len() == 0 {
+ return nil
+ }
+
+ // Otherwise we'll read the next revocation for the remote party which
+ // is always the last item within the buffer.
+ return ReadElements(r, &channel.RemoteNextRevocation)
+}
+
+// DeleteOpenChannel deletes the persisted open channel state from the channel
+// bucket.
+func DeleteOpenChannel(chanBucket kvdb.RwBucket) error {
+ if err := chanBucket.Delete(chanInfoKey); err != nil {
+ return err
+ }
+
+ err := chanBucket.Delete(chanCommitKey(true))
+ if err != nil {
+ return err
+ }
+ err = chanBucket.Delete(chanCommitKey(false))
+ if err != nil {
+ return err
+ }
+
+ if err := chanBucket.Delete(revocationStateKey); err != nil {
+ return err
+ }
+
+ if diff := chanBucket.Get(commitDiffKey); diff != nil {
+ return chanBucket.Delete(commitDiffKey)
+ }
+
+ return nil
+}
+
+// commitTlvData stores all the optional data that may be stored as a TLV stream
+// at the _end_ of the normal serialized commit on disk.
+type commitTlvData struct {
+ // customBlob is a custom blob that may store extra data for custom
+ // channels.
+ customBlob tlv.OptionalRecordT[tlv.TlvType1, tlv.Blob]
+}
+
+// encode encodes the aux data into the passed io.Writer.
+func (c *commitTlvData) encode(w io.Writer) error {
+ var tlvRecords []tlv.Record
+ c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType1, tlv.Blob]) {
+ tlvRecords = append(tlvRecords, blob.Record())
+ })
+
+ // Create the tlv stream.
+ tlvStream, err := tlv.NewStream(tlvRecords...)
+ if err != nil {
+ return err
+ }
+
+ return tlvStream.Encode(w)
+}
+
+// decode attempts to decode the aux data from the passed io.Reader.
+func (c *commitTlvData) decode(r io.Reader) error {
+ blob := c.customBlob.Zero()
+
+ tlvStream, err := tlv.NewStream(
+ blob.Record(),
+ )
+ if err != nil {
+ return err
+ }
+
+ tlvs, err := tlvStream.DecodeWithParsedTypes(r)
+ if err != nil {
+ return err
+ }
+
+ if _, ok := tlvs[c.customBlob.TlvType()]; ok {
+ c.customBlob = tlv.SomeRecordT(blob)
+ }
+
+ return nil
+}
+
+// DecodeCommitTlvData decodes and applies auxiliary TLV data to a commitment.
+func DecodeCommitTlvData(r io.Reader, c *ChannelCommitment) error {
+ var auxData commitTlvData
+ if err := auxData.decode(r); err != nil {
+ return err
+ }
+
+ amendCommitTlvData(c, auxData)
+
+ return nil
+}
+
+// EncodeCommitTlvData extracts and encodes auxiliary TLV data from a
+// commitment.
+func EncodeCommitTlvData(w io.Writer, c *ChannelCommitment) error {
+ auxData := extractCommitTlvData(c)
+ return auxData.encode(w)
+}
+
+// amendCommitTlvData updates the commitment with the given auxiliary TLV data.
+func amendCommitTlvData(c *ChannelCommitment, auxData commitTlvData) {
+ auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
+ c.CustomBlob = fn.Some(blob)
+ })
+}
+
+// extractCommitTlvData creates a new commitTlvData from the given commitment.
+func extractCommitTlvData(c *ChannelCommitment) commitTlvData {
+ var auxData commitTlvData
+
+ c.CustomBlob.WhenSome(func(blob tlv.Blob) {
+ auxData.customBlob = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType1](blob),
+ )
+ })
+
+ return auxData
+}
+
+// SerializeLogUpdates serializes provided list of updates to a stream.
+func SerializeLogUpdates(w io.Writer, logUpdates []LogUpdate) error {
+ numUpdates := uint16(len(logUpdates))
+ if err := binary.Write(w, byteOrder, numUpdates); err != nil {
+ return err
+ }
+
+ for _, diff := range logUpdates {
+ err := WriteElements(w, diff.LogIndex, diff.UpdateMsg)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// DeserializeLogUpdates deserializes a list of updates from a stream.
+func DeserializeLogUpdates(r io.Reader) ([]LogUpdate, error) {
+ var numUpdates uint16
+ if err := binary.Read(r, byteOrder, &numUpdates); err != nil {
+ return nil, err
+ }
+
+ logUpdates := make([]LogUpdate, numUpdates)
+ for i := 0; i < int(numUpdates); i++ {
+ err := ReadElements(r,
+ &logUpdates[i].LogIndex, &logUpdates[i].UpdateMsg,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return logUpdates, nil
+}
+
+// SerializeCommitDiff serializes the commit diff.
+func SerializeCommitDiff(w io.Writer, diff *CommitDiff) error {
+ if err := SerializeChanCommit(w, &diff.Commitment); err != nil {
+ return err
+ }
+
+ if err := WriteElements(w, diff.CommitSig); err != nil {
+ return err
+ }
+
+ if err := SerializeLogUpdates(w, diff.LogUpdates); err != nil {
+ return err
+ }
+
+ numOpenRefs := uint16(len(diff.OpenedCircuitKeys))
+ if err := binary.Write(w, byteOrder, numOpenRefs); err != nil {
+ return err
+ }
+
+ for _, openRef := range diff.OpenedCircuitKeys {
+ err := WriteElements(w, openRef.ChanID, openRef.HtlcID)
+ if err != nil {
+ return err
+ }
+ }
+
+ numClosedRefs := uint16(len(diff.ClosedCircuitKeys))
+ if err := binary.Write(w, byteOrder, numClosedRefs); err != nil {
+ return err
+ }
+
+ for _, closedRef := range diff.ClosedCircuitKeys {
+ err := WriteElements(w, closedRef.ChanID, closedRef.HtlcID)
+ if err != nil {
+ return err
+ }
+ }
+
+ // We'll also encode the commit aux data stream here. We do this here
+ // rather than above (at the call to serializeChanCommit), to ensure
+ // backwards compat for reads to existing non-custom channels.
+ if err := EncodeCommitTlvData(w, &diff.Commitment); err != nil {
+ return fmt.Errorf("unable to write aux data: %w", err)
+ }
+
+ return nil
+}
+
+// DeserializeCommitDiff deserializes the commit diff.
+func DeserializeCommitDiff(r io.Reader) (*CommitDiff, error) {
+ var (
+ d CommitDiff
+ err error
+ )
+
+ d.Commitment, err = DeserializeChanCommit(r)
+ if err != nil {
+ return nil, err
+ }
+
+ var msg lnwire.Message
+ if err := ReadElements(r, &msg); err != nil {
+ return nil, err
+ }
+ commitSig, ok := msg.(*lnwire.CommitSig)
+ if !ok {
+ return nil, fmt.Errorf("expected lnwire.CommitSig, instead "+
+ "read: %T", msg)
+ }
+ d.CommitSig = commitSig
+
+ d.LogUpdates, err = DeserializeLogUpdates(r)
+ if err != nil {
+ return nil, err
+ }
+
+ var numOpenRefs uint16
+ if err := binary.Read(r, byteOrder, &numOpenRefs); err != nil {
+ return nil, err
+ }
+
+ d.OpenedCircuitKeys = make([]models.CircuitKey, numOpenRefs)
+ for i := 0; i < int(numOpenRefs); i++ {
+ err := ReadElements(r,
+ &d.OpenedCircuitKeys[i].ChanID,
+ &d.OpenedCircuitKeys[i].HtlcID)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var numClosedRefs uint16
+ if err := binary.Read(r, byteOrder, &numClosedRefs); err != nil {
+ return nil, err
+ }
+
+ d.ClosedCircuitKeys = make([]models.CircuitKey, numClosedRefs)
+ for i := 0; i < int(numClosedRefs); i++ {
+ err := ReadElements(r,
+ &d.ClosedCircuitKeys[i].ChanID,
+ &d.ClosedCircuitKeys[i].HtlcID)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // As a final step, we'll read out any aux commit data that we have at
+ // the end of this byte stream. We do this here to ensure backward
+ // compatibility, as otherwise we risk erroneously reading into the
+ // wrong field.
+ if err := DecodeCommitTlvData(r, &d.Commitment); err != nil {
+ return nil, fmt.Errorf("unable to decode aux data: %w", err)
+ }
+
+ return &d, nil
+}
### chanstate/kv_final_htlc.go
@@ -0,0 +1,189 @@
+package chanstate
+
+import (
+ "errors"
+
+ "github.com/lightningnetwork/lnd/kvdb"
+ "github.com/lightningnetwork/lnd/lnwire"
+)
+
+var (
+ // ErrFinalHtlcsBucketNotFound signals that the top-level final htlcs
+ // bucket does not exist.
+ ErrFinalHtlcsBucketNotFound = errors.New("final htlcs bucket not " +
+ "found")
+
+ // ErrFinalChannelBucketNotFound signals that the channel bucket for
+ // final htlc outcomes does not exist.
+ ErrFinalChannelBucketNotFound = errors.New("final htlcs channel " +
+ "bucket not found")
+
+ // ErrHtlcUnknown signals that an htlc has no final resolution yet.
+ ErrHtlcUnknown = errors.New("htlc unknown")
+)
+
+var (
+ // finalHtlcsBucket contains the htlcs that have been resolved
+ // definitively. Within this bucket, there is a sub-bucket for each
+ // channel. In each channel bucket, the htlc indices are stored along
+ // with final outcome.
+ //
+ // final-htlcs -> chanID -> htlcIndex -> outcome
+ //
+ // 'outcome' is a byte value that encodes:
+ //
+ // | true false
+ // ------+------------------
+ // bit 0 | settled failed
+ // bit 1 | offchain onchain
+ //
+ // This bucket is positioned at the root level, because its contents
+ // will be kept independent of the channel lifecycle. This is to avoid
+ // the situation where a channel force-closes autonomously and the user
+ // not being able to query for htlc outcomes anymore.
+ finalHtlcsBucket = []byte("final-htlcs")
+)
+
+// FinalHtlcByte defines a byte type that encodes information about the final
+// htlc resolution.
+type FinalHtlcByte byte
+
+const (
+ // FinalHtlcSettledBit is the bit that encodes whether the htlc was
+ // settled or failed.
+ FinalHtlcSettledBit FinalHtlcByte = 1 << 0
+
+ // FinalHtlcOffchainBit is the bit that encodes whether the htlc was
+ // resolved offchain or onchain.
+ FinalHtlcOffchainBit FinalHtlcByte = 1 << 1
+)
+
+// FetchFinalHtlcsBucket returns the read-only final htlc bucket for a channel.
+func FetchFinalHtlcsBucket(tx kvdb.RTx,
+ chanID lnwire.ShortChannelID) (kvdb.RBucket, error) {
+
+ finalHtlcsBucket := tx.ReadBucket(finalHtlcsBucket)
+ if finalHtlcsBucket == nil {
+ return nil, ErrFinalHtlcsBucketNotFound
+ }
+
+ var chanIDBytes [8]byte
+ byteOrder.PutUint64(chanIDBytes[:], chanID.ToUint64())
+
+ chanBucket := finalHtlcsBucket.NestedReadBucket(chanIDBytes[:])
+ if chanBucket == nil {
+ return nil, ErrFinalChannelBucketNotFound
+ }
+
+ return chanBucket, nil
+}
+
+// FetchFinalHtlcsBucketRw returns the writable final htlc bucket for a channel.
+func FetchFinalHtlcsBucketRw(tx kvdb.RwTx,
+ chanID lnwire.ShortChannelID) (kvdb.RwBucket, error) {
+
+ finalHtlcsBucket, err := tx.CreateTopLevelBucket(finalHtlcsBucket)
+ if err != nil {
+ return nil, err
+ }
+
+ var chanIDBytes [8]byte
+ byteOrder.PutUint64(chanIDBytes[:], chanID.ToUint64())
+ chanBucket, err := finalHtlcsBucket.CreateBucketIfNotExists(
+ chanIDBytes[:],
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return chanBucket, nil
+}
+
+// PutFinalHtlc writes the final htlc outcome to the database. Additionally it
+// records whether the htlc was resolved off-chain or on-chain.
+func PutFinalHtlc(finalHtlcsBucket kvdb.RwBucket, id uint64,
+ info FinalHtlcInfo) error {
+
+ var key [8]byte
+ byteOrder.PutUint64(key[:], id)
+
+ var finalHtlcByte FinalHtlcByte
+ if info.Settled {
+ finalHtlcByte |= FinalHtlcSettledBit
+ }
+ if info.Offchain {
+ finalHtlcByte |= FinalHtlcOffchainBit
+ }
+
+ return finalHtlcsBucket.Put(key[:], []byte{byte(finalHtlcByte)})
+}
+
+// FetchFinalHtlc reads a final htlc outcome from the final htlc channel bucket.
+func FetchFinalHtlc(finalHtlcsBucket kvdb.RBucket,
+ htlcIndex uint64) (*FinalHtlcInfo, error) {
+
+ var idBytes [8]byte
+ byteOrder.PutUint64(idBytes[:], htlcIndex)
+
+ value := finalHtlcsBucket.Get(idBytes[:])
+ if value == nil {
+ return nil, ErrHtlcUnknown
+ }
+
+ if len(value) != 1 {
+ return nil, errors.New("unexpected final htlc value length")
+ }
+
+ info := FinalHtlcInfo{
+ Settled: value[0]&byte(FinalHtlcSettledBit) != 0,
+ Offchain: value[0]&byte(FinalHtlcOffchainBit) != 0,
+ }
+
+ return &info, nil
+}
+
+// ProcessFinalHtlc stores a final htlc outcome in the database if signaled via
+// the supplied log update. An in-memory htlcs map is updated too.
+func ProcessFinalHtlc(finalHtlcsBucket kvdb.RwBucket, upd LogUpdate,
+ finalHtlcs map[uint64]bool) error {
+
+ var (
+ settled bool
+ id uint64
+ )
+
+ switch msg := upd.UpdateMsg.(type) {
+ case *lnwire.UpdateFulfillHTLC:
+ settled = true
+ id = msg.ID
+
+ case *lnwire.UpdateFailHTLC:
+ settled = false
+ id = msg.ID
+
+ case *lnwire.UpdateFailMalformedHTLC:
+ settled = false
+ id = msg.ID
+
+ default:
+ return nil
+ }
+
+ // Store the final resolution in the database if a bucket is provided.
+ if finalHtlcsBucket != nil {
+ err := PutFinalHtlc(
+ finalHtlcsBucket, id,
+ FinalHtlcInfo{
+ Settled: settled,
+ Offchain: true,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ }
+
+ finalHtlcs[id] = settled
+
+ return nil
+}
### chanstate/kv_forwarding_package.go
@@ -0,0 +1,818 @@
+package chanstate
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "io"
+
+ "github.com/lightningnetwork/lnd/kvdb"
+ "github.com/lightningnetwork/lnd/lnwire"
+)
+
+//nolint:ll
+var (
+ // ErrCorruptedFwdPkg signals that the on-disk structure of the
+ // forwarding package has potentially been mangled.
+ ErrCorruptedFwdPkg = errors.New("fwding package db has been corrupted")
+
+ // fwdPackagesKey is the root-level bucket that all forwarding packages
+ // are written. This bucket is further subdivided based on the short
+ // channel ID of each channel.
+ //
+ // Bucket hierarchy:
+ //
+ // fwdPackagesKey(root-bucket)
+ // |
+ // |-- <shortChannelID>
+ // | |
+ // | |-- <height>
+ // | | |-- ackFilterKey: <encoded bytes of PkgFilter>
+ // | | |-- settleFailFilterKey: <encoded bytes of PkgFilter>
+ // | | |-- fwdFilterKey: <encoded bytes of PkgFilter>
+ // | | |
+ // | | |-- addBucketKey
+ // | | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
+ // | | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
+ // | | | ...
+ // | | |
+ // | | |-- failSettleBucketKey
+ // | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
+ // | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
+ // | | ...
+ // | |
+ // | |-- <height>
+ // | | |
+ // | ... ...
+ // |
+ // |
+ // |-- <shortChannelID>
+ // | |
+ // | ...
+ // ...
+ //
+ fwdPackagesKey = []byte("fwd-packages")
+
+ // addBucketKey is the bucket to which all Add log updates are written.
+ addBucketKey = []byte("add-updates")
+
+ // failSettleBucketKey is the bucket to which all Settle/Fail log
+ // updates are written.
+ failSettleBucketKey = []byte("fail-settle-updates")
+
+ // fwdFilterKey is a key used to write the set of Adds that passed
+ // validation and are to be forwarded to the switch.
+ // NOTE: The presence of this key within a forwarding package indicates
+ // that the package has reached FwdStateProcessed.
+ fwdFilterKey = []byte("fwd-filter-key")
+
+ // ackFilterKey is a key used to access the PkgFilter indicating which
+ // Adds have received a Settle/Fail. This response may come from a
+ // number of sources, including: exitHop settle/fails, switch failures,
+ // chain arbiter interjections, as well as settle/fails from the
+ // next hop in the route.
+ ackFilterKey = []byte("ack-filter-key")
+
+ // settleFailFilterKey is a key used to access the PkgFilter indicating
+ // which Settles/Fails in have been received and processed by the link
+ // that originally received the Add.
+ settleFailFilterKey = []byte("settle-fail-filter-key")
+)
+
+// FwdPackagesBucketKey returns the root-level bucket key that stores
+// forwarding packages.
+func FwdPackagesBucketKey() []byte {
+ return fwdPackagesKey
+}
+
+// Encode serializes the AddRef to the given io.Writer.
+func (a *AddRef) Encode(w io.Writer) error {
+ if err := binary.Write(w, binary.BigEndian, a.Height); err != nil {
+ return err
+ }
+
+ return binary.Write(w, binary.BigEndian, a.Index)
+}
+
+// Decode deserializes the AddRef from the given io.Reader.
+func (a *AddRef) Decode(r io.Reader) error {
+ if err := binary.Read(r, binary.BigEndian, &a.Height); err != nil {
+ return err
+ }
+
+ return binary.Read(r, binary.BigEndian, &a.Index)
+}
+
+// Size returns number of bytes produced when the PkgFilter is serialized.
+func (f *PkgFilter) Size() uint16 {
+ // 2 bytes for uint16 `count`, then round up number of bytes required to
+ // represent `count` bits.
+ return 2 + (f.count+7)/8
+}
+
+// Encode writes the filter to the provided io.Writer.
+func (f *PkgFilter) Encode(w io.Writer) error {
+ if err := binary.Write(w, binary.BigEndian, f.count); err != nil {
+ return err
+ }
+
+ _, err := w.Write(f.filter)
+
+ return err
+}
+
+// Decode reads the filter from the provided io.Reader.
+func (f *PkgFilter) Decode(r io.Reader) error {
+ if err := binary.Read(r, binary.BigEndian, &f.count); err != nil {
+ return err
+ }
+
+ f.filter = make([]byte, f.Size()-2)
+ _, err := io.ReadFull(r, f.filter)
+
+ return err
+}
+
+// SettleFailAcker is a generic interface providing the ability to acknowledge
+// settle/fail HTLCs stored in forwarding packages.
+type SettleFailAcker interface {
+ // AckSettleFails atomically updates the settle-fail filters in *other*
+ // channels' forwarding packages.
+ AckSettleFails(tx kvdb.RwTx, settleFailRefs ...SettleFailRef) error
+}
+
+// GlobalFwdPkgReader is an interface used to retrieve the forwarding packages
+// of any active channel.
+type GlobalFwdPkgReader interface {
+ // LoadChannelFwdPkgs loads all known forwarding packages for the given
+ // channel.
+ LoadChannelFwdPkgs(tx kvdb.RTx,
+ source lnwire.ShortChannelID) ([]*FwdPkg, error)
+}
+
+// FwdOperator defines the interfaces for managing forwarding packages that are
+// external to a particular channel. This interface is used by the switch to
+// read forwarding packages from arbitrary channels, and acknowledge settles and
+// fails for locally-sourced payments.
+type FwdOperator interface {
+ // GlobalFwdPkgReader provides read access to all known forwarding
+ // packages
+ GlobalFwdPkgReader
+
+ // SettleFailAcker grants the ability to acknowledge settles or fails
+ // residing in arbitrary forwarding packages.
+ SettleFailAcker
+}
+
+// SwitchPackager is a concrete implementation of the FwdOperator interface.
+// A SwitchPackager offers the ability to read any forwarding package, and ack
+// arbitrary settle and fail HTLCs.
+type SwitchPackager struct{}
+
+// NewSwitchPackager instantiates a new SwitchPackager.
+func NewSwitchPackager() *SwitchPackager {
+ return &SwitchPackager{}
+}
+
+// AckSettleFails atomically updates the settle-fail filters in *other*
+// channels' forwarding packages, to mark that the switch has received a settle
+// or fail residing in the forwarding package of a link.
+func (*SwitchPackager) AckSettleFails(tx kvdb.RwTx,
+ settleFailRefs ...SettleFailRef) error {
+
+ return ackSettleFails(tx, settleFailRefs)
+}
+
+// LoadChannelFwdPkgs loads all forwarding packages for a particular channel.
+func (*SwitchPackager) LoadChannelFwdPkgs(tx kvdb.RTx,
+ source lnwire.ShortChannelID) ([]*FwdPkg, error) {
+
+ return loadChannelFwdPkgs(tx, source)
+}
+
+// FwdPackager supports all operations required to modify fwd packages, such as
+// creation, updates, reading, and removal. The interfaces are broken down in
+// this way to support future delegation of the subinterfaces.
+type FwdPackager interface {
+ // AddFwdPkg serializes and writes a FwdPkg for this channel at the
+ // remote commitment height included in the forwarding package.
+ AddFwdPkg(tx kvdb.RwTx, fwdPkg *FwdPkg) error
+
+ // SetFwdFilter looks up the forwarding package at the remote `height`
+ // and sets the `fwdFilter`, marking the Adds for which:
+ // 1) We are not the exit node
+ // 2) Passed all validation
+ // 3) Should be forwarded to the switch immediately after a failure
+ SetFwdFilter(tx kvdb.RwTx, height uint64, fwdFilter *PkgFilter) error
+
+ // AckAddHtlcs atomically updates the add filters in this channel's
+ // forwarding packages to mark the resolution of an Add that was
+ // received from the remote party.
+ AckAddHtlcs(tx kvdb.RwTx, addRefs ...AddRef) error
+
+ // SettleFailAcker allows a link to acknowledge settle/fail HTLCs
+ // belonging to other channels.
+ SettleFailAcker
+
+ // LoadFwdPkgs loads all known forwarding packages owned by this
+ // channel.
+ LoadFwdPkgs(tx kvdb.RTx) ([]*FwdPkg, error)
+
+ // RemovePkg deletes a forwarding package owned by this channel at
+ // the provided remote `height`.
+ RemovePkg(tx kvdb.RwTx, height uint64) error
+
+ // Wipe deletes all the forwarding packages owned by this channel.
+ Wipe(tx kvdb.RwTx) error
+}
+
+// ChannelPackager is used by a channel to manage the lifecycle of its
+// forwarding packages. The packager is tied to a particular source channel ID,
+// allowing it to create and edit its own packages. Each packager also has the
+// ability to
+// remove fail/settle htlcs that correspond to an add contained in one of
+// source's packages.
+type ChannelPackager struct {
+ source lnwire.ShortChannelID
+}
+
+// NewChannelPackager creates a new packager for a single channel.
+func NewChannelPackager(source lnwire.ShortChannelID) *ChannelPackager {
+ return &ChannelPackager{
+ source: source,
+ }
+}
+
+// Source returns the short channel ID of the channel this packager manages
+// forwarding packages for.
+//
+// TODO(chanstate): remove this accessor once the channel state tests have
+// moved into this package. It only exists because TestRefresh still lives
+// in channeldb and can no longer reach the unexported source field.
+func (p *ChannelPackager) Source() lnwire.ShortChannelID {
+ return p.source
+}
+
+// AddFwdPkg writes a newly locked in forwarding package to disk.
+func (*ChannelPackager) AddFwdPkg(tx kvdb.RwTx, fwdPkg *FwdPkg) error {
+ fwdPkgBkt, err := tx.CreateTopLevelBucket(fwdPackagesKey)
+ if err != nil {
+ return err
+ }
+
+ source := forwardingLogKey(fwdPkg.Source.ToUint64())
+ sourceBkt, err := fwdPkgBkt.CreateBucketIfNotExists(source[:])
+ if err != nil {
+ return err
+ }
+
+ heightKey := forwardingLogKey(fwdPkg.Height)
+ heightBkt, err := sourceBkt.CreateBucketIfNotExists(heightKey[:])
+ if err != nil {
+ return err
+ }
+
+ // Write ADD updates we received at this commit height.
+ addBkt, err := heightBkt.CreateBucketIfNotExists(addBucketKey)
+ if err != nil {
+ return err
+ }
+
+ // Write SETTLE/FAIL updates we received at this commit height.
+ failSettleBkt, err := heightBkt.CreateBucketIfNotExists(
+ failSettleBucketKey,
+ )
+ if err != nil {
+ return err
+ }
+
+ for i := range fwdPkg.Adds {
+ err = putLogUpdate(addBkt, uint16(i), &fwdPkg.Adds[i])
+ if err != nil {
+ return err
+ }
+ }
+
+ // Persist the initialized pkg filter, which will be used to determine
+ // when we can remove this forwarding package from disk.
+ var ackFilterBuf bytes.Buffer
+ if err := fwdPkg.AckFilter.Encode(&ackFilterBuf); err != nil {
+ return err
+ }
+
+ err = heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes())
+ if err != nil {
+ return err
+ }
+
+ for i := range fwdPkg.SettleFails {
+ err = putLogUpdate(
+ failSettleBkt, uint16(i), &fwdPkg.SettleFails[i],
+ )
+ if err != nil {
+ return err
+ }
+ }
+
+ var settleFailFilterBuf bytes.Buffer
+ err = fwdPkg.SettleFailFilter.Encode(&settleFailFilterBuf)
+ if err != nil {
+ return err
+ }
+
+ return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes())
+}
+
+// putLogUpdate writes an htlc to the provided `bkt`, using `index` as the key.
+func putLogUpdate(bkt kvdb.RwBucket, idx uint16, htlc *LogUpdate) error {
+ var b bytes.Buffer
+ if err := serializeLogUpdate(&b, htlc); err != nil {
+ return err
+ }
+
+ return bkt.Put(uint16Key(idx), b.Bytes())
+}
+
+// serializeLogUpdate writes a log update to the provided io.Writer.
+func serializeLogUpdate(w io.Writer, l *LogUpdate) error {
+ return WriteElements(w, l.LogIndex, l.UpdateMsg)
+}
+
+// deserializeLogUpdate reads a log update from the provided io.Reader.
+func deserializeLogUpdate(r io.Reader) (*LogUpdate, error) {
+ l := &LogUpdate{}
+ if err := ReadElements(r, &l.LogIndex, &l.UpdateMsg); err != nil {
+ return nil, err
+ }
+
+ return l, nil
+}
+
+// LoadFwdPkgs scans the forwarding log for any packages that haven't been
+// processed, and returns their deserialized log updates in a map indexed by the
+// remote commitment height at which the updates were locked in.
+func (p *ChannelPackager) LoadFwdPkgs(tx kvdb.RTx) ([]*FwdPkg, error) {
+ return loadChannelFwdPkgs(tx, p.source)
+}
+
+// loadChannelFwdPkgs loads all forwarding packages owned by `source`.
+func loadChannelFwdPkgs(tx kvdb.RTx,
+ source lnwire.ShortChannelID) ([]*FwdPkg, error) {
+
+ fwdPkgBkt := tx.ReadBucket(fwdPackagesKey)
+ if fwdPkgBkt == nil {
+ return nil, nil
+ }
+
+ sourceKey := forwardingLogKey(source.ToUint64())
+ sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
+ if sourceBkt == nil {
+ return nil, nil
+ }
+
+ var heights []uint64
+ if err := sourceBkt.ForEach(func(k, _ []byte) error {
+ if len(k) != 8 {
+ return ErrCorruptedFwdPkg
+ }
+
+ heights = append(heights, byteOrder.Uint64(k))
+
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+
+ // Load the forwarding package for each retrieved height.
+ fwdPkgs := make([]*FwdPkg, 0, len(heights))
+ for _, height := range heights {
+ fwdPkg, err := loadFwdPkg(fwdPkgBkt, source, height)
+ if err != nil {
+ return nil, err
+ }
+
+ fwdPkgs = append(fwdPkgs, fwdPkg)
+ }
+
+ return fwdPkgs, nil
+}
+
+// loadFwdPkg reads the packager's fwd pkg at a given height, and determines the
+// appropriate FwdState.
+func loadFwdPkg(fwdPkgBkt kvdb.RBucket, source lnwire.ShortChannelID,
+ height uint64) (*FwdPkg, error) {
+
+ sourceKey := forwardingLogKey(source.ToUint64())
+ sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
+ if sourceBkt == nil {
+ return nil, ErrCorruptedFwdPkg
+ }
+
+ heightKey := forwardingLogKey(height)
+ heightBkt := sourceBkt.NestedReadBucket(heightKey[:])
+ if heightBkt == nil {
+ return nil, ErrCorruptedFwdPkg
+ }
+
+ // Load ADDs from disk.
+ addBkt := heightBkt.NestedReadBucket(addBucketKey)
+ if addBkt == nil {
+ return nil, ErrCorruptedFwdPkg
+ }
+
+ adds, err := loadHtlcs(addBkt)
+ if err != nil {
+ return nil, err
+ }
+
+ // Load ack filter from disk.
+ ackFilterBytes := heightBkt.Get(ackFilterKey)
+ if ackFilterBytes == nil {
+ return nil, ErrCorruptedFwdPkg
+ }
+ ackFilterReader := bytes.NewReader(ackFilterBytes)
+
+ ackFilter := &PkgFilter{}
+ if err := ackFilter.Decode(ackFilterReader); err != nil {
+ return nil, err
+ }
+
+ // Load SETTLE/FAILs from disk.
+ failSettleBkt := heightBkt.NestedReadBucket(failSettleBucketKey)
+ if failSettleBkt == nil {
+ return nil, ErrCorruptedFwdPkg
+ }
+
+ failSettles, err := loadHtlcs(failSettleBkt)
+ if err != nil {
+ return nil, err
+ }
+
+ // Load settle fail filter from disk.
+ settleFailFilterBytes := heightBkt.Get(settleFailFilterKey)
+ if settleFailFilterBytes == nil {
+ return nil, ErrCorruptedFwdPkg
+ }
+ settleFailFilterReader := bytes.NewReader(settleFailFilterBytes)
+
+ settleFailFilter := &PkgFilter{}
+ if err := settleFailFilter.Decode(settleFailFilterReader); err != nil {
+ return nil, err
+ }
+
+ // Initialize the fwding package, which always starts in the
+ // FwdStateLockedIn. We can determine what state the package was left in
+ // by examining constraints on the information loaded from disk.
+ fwdPkg := &FwdPkg{
+ Source: source,
+ State: FwdStateLockedIn,
+ Height: height,
+ Adds: adds,
+ AckFilter: ackFilter,
+ SettleFails: failSettles,
+ SettleFailFilter: settleFailFilter,
+ }
+
+ // Check if the forward filter has been persisted to disk.
+ // This indicates whether the Adds in this package have been processed.
+ //
+ // NOTE: We also expect packages with no Adds (settle/fail only packages
+ // or empty packages) to have the fwd filter set to signal that the
+ // packages have been processed.
+ fwdFilterBytes := heightBkt.Get(fwdFilterKey)
+
+ // Handle packages with Adds that haven't been processed yet.
+ if fwdFilterBytes == nil {
+ // Create a new forward filter for the unprocessed Adds.
+ nAdds := uint16(len(adds))
+ fwdPkg.FwdFilter = NewPkgFilter(nAdds)
+
+ return fwdPkg, nil
+ }
+
+ // Load the existing forward filter from disk.
+ fwdFilterReader := bytes.NewReader(fwdFilterBytes)
+ fwdPkg.FwdFilter = &PkgFilter{}
+ if err := fwdPkg.FwdFilter.Decode(fwdFilterReader); err != nil {
+ return nil, err
+ }
+
+ // Mark the package as processed since the forward filter exists.
+ fwdPkg.State = FwdStateProcessed
+
+ // If every add, settle, and fail has been fully acknowledged, we can
+ // safely set the package's state to FwdStateCompleted, signalling that
+ // it can be garbage collected.
+ if fwdPkg.AckFilter.IsFull() && fwdPkg.SettleFailFilter.IsFull() {
+ fwdPkg.State = FwdStateCompleted
+ }
+
+ return fwdPkg, nil
+}
+
+// loadHtlcs retrieves all serialized htlcs in a bucket, returning
+// them in order of the indexes they were written under.
+func loadHtlcs(bkt kvdb.RBucket) ([]LogUpdate, error) {
+ var htlcs []LogUpdate
+ if err := bkt.ForEach(func(_, v []byte) error {
+ htlc, err := deserializeLogUpdate(bytes.NewReader(v))
+ if err != nil {
+ return err
+ }
+
+ htlcs = append(htlcs, *htlc)
+
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+
+ return htlcs, nil
+}
+
+// SetFwdFilter writes the set of indexes corresponding to Adds at the
+// `height` that are to be forwarded to the switch. Calling this method causes
+// the forwarding package at `height` to be in FwdStateProcessed. We write this
+// forwarding decision so that we always arrive at the same behavior for HTLCs
+// leaving this channel. After a restart, we skip validation of these Adds,
+// since they are assumed to have already been validated, and make the switch or
+// outgoing link responsible for handling replays.
+func (p *ChannelPackager) SetFwdFilter(tx kvdb.RwTx, height uint64,
+ fwdFilter *PkgFilter) error {
+
+ fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
+ if fwdPkgBkt == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ source := forwardingLogKey(p.source.ToUint64())
+ sourceBkt := fwdPkgBkt.NestedReadWriteBucket(source[:])
+ if sourceBkt == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ heightKey := forwardingLogKey(height)
+ heightBkt := sourceBkt.NestedReadWriteBucket(heightKey[:])
+ if heightBkt == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ // If the fwd filter has already been written, we return early to avoid
+ // modifying the persistent state.
+ forwardedAddsBytes := heightBkt.Get(fwdFilterKey)
+ if forwardedAddsBytes != nil {
+ return nil
+ }
+
+ // Otherwise we serialize and write the provided fwd filter.
+ var b bytes.Buffer
+ if err := fwdFilter.Encode(&b); err != nil {
+ return err
+ }
+
+ return heightBkt.Put(fwdFilterKey, b.Bytes())
+}
+
+// AckAddHtlcs accepts a list of references to add htlcs, and updates the
+// AckAddFilter of those forwarding packages to indicate that a settle or fail
+// has been received in response to the add.
+func (p *ChannelPackager) AckAddHtlcs(tx kvdb.RwTx, addRefs ...AddRef) error {
+ if len(addRefs) == 0 {
+ return nil
+ }
+
+ fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
+ if fwdPkgBkt == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ sourceKey := forwardingLogKey(p.source.ToUint64())
+ sourceBkt := fwdPkgBkt.NestedReadWriteBucket(sourceKey[:])
+ if sourceBkt == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ // Organize the forward references such that we just get a single slice
+ // of indexes for each unique height.
+ heightDiffs := make(map[uint64][]uint16)
+ for _, addRef := range addRefs {
+ heightDiffs[addRef.Height] = append(
+ heightDiffs[addRef.Height],
+ addRef.Index,
+ )
+ }
+
+ // Load each height bucket once and remove all acked htlcs at that
+ // height.
+ for height, indexes := range heightDiffs {
+ err := ackAddHtlcsAtHeight(sourceBkt, height, indexes)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// ackAddHtlcsAtHeight updates the AddAckFilter of a single forwarding package
+// with a list of indexes, writing the resulting filter back in its place.
+func ackAddHtlcsAtHeight(sourceBkt kvdb.RwBucket, height uint64,
+ indexes []uint16) error {
+
+ heightKey := forwardingLogKey(height)
+ heightBkt := sourceBkt.NestedReadWriteBucket(heightKey[:])
+ if heightBkt == nil {
+ // If the height bucket isn't found, this could be because the
+ // forwarding package was already removed. We'll return nil to
+ // signal that the operation is successful, as there is nothing
+ // to ack.
+ return nil
+ }
+
+ // Load ack filter from disk.
+ ackFilterBytes := heightBkt.Get(ackFilterKey)
+ if ackFilterBytes == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ ackFilter := &PkgFilter{}
+ ackFilterReader := bytes.NewReader(ackFilterBytes)
+ if err := ackFilter.Decode(ackFilterReader); err != nil {
+ return err
+ }
+
+ // Update the ack filter for this height.
+ for _, index := range indexes {
+ ackFilter.Set(index)
+ }
+
+ // Write the resulting filter to disk.
+ var ackFilterBuf bytes.Buffer
+ if err := ackFilter.Encode(&ackFilterBuf); err != nil {
+ return err
+ }
+
+ return heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes())
+}
+
+// AckSettleFails persistently acknowledges settles or fails from a remote
+// forwarding package. This should only be called after the source of the Add
+// has locked in the settle/fail, or it becomes otherwise safe to forgo
+// retransmitting the settle/fail after a restart.
+func (p *ChannelPackager) AckSettleFails(tx kvdb.RwTx,
+ settleFailRefs ...SettleFailRef) error {
+
+ return ackSettleFails(tx, settleFailRefs)
+}
+
+// ackSettleFails persistently acknowledges a batch of settle fail references.
+func ackSettleFails(tx kvdb.RwTx, settleFailRefs []SettleFailRef) error {
+ if len(settleFailRefs) == 0 {
+ return nil
+ }
+
+ fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
+ if fwdPkgBkt == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ // Organize the forward references such that we just get a single slice
+ // of indexes for each unique destination-height pair.
+ destHeightDiffs := make(map[lnwire.ShortChannelID]map[uint64][]uint16)
+ for _, settleFailRef := range settleFailRefs {
+ destHeights, ok := destHeightDiffs[settleFailRef.Source]
+ if !ok {
+ destHeights = make(map[uint64][]uint16)
+ destHeightDiffs[settleFailRef.Source] = destHeights
+ }
+
+ destHeights[settleFailRef.Height] = append(
+ destHeights[settleFailRef.Height],
+ settleFailRef.Index,
+ )
+ }
+
+ // With the references organized by destination and height, we now load
+ // each remote bucket, and update the settle fail filter for any
+ // settle/fail htlcs.
+ for dest, destHeights := range destHeightDiffs {
+ destKey := forwardingLogKey(dest.ToUint64())
+ destBkt := fwdPkgBkt.NestedReadWriteBucket(destKey[:])
+ if destBkt == nil {
+ // If the destination bucket is not found, this is
+ // likely the result of the destination channel being
+ // closed and having it's forwarding packages wiped. We
+ // won't treat this as an error, because the response
+ // will no longer be retransmitted internally.
+ continue
+ }
+
+ for height, indexes := range destHeights {
+ err := ackSettleFailsAtHeight(destBkt, height, indexes)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+// ackSettleFailsAtHeight given a destination bucket, acks the provided indexes
+// at particular a height by updating the settle fail filter.
+func ackSettleFailsAtHeight(destBkt kvdb.RwBucket, height uint64,
+ indexes []uint16) error {
+
+ heightKey := forwardingLogKey(height)
+ heightBkt := destBkt.NestedReadWriteBucket(heightKey[:])
+ if heightBkt == nil {
+ // If the height bucket isn't found, this could be because the
+ // forwarding package was already removed. We'll return nil to
+ // signal that the operation is as there is nothing to ack.
+ return nil
+ }
+
+ // Load ack filter from disk.
+ settleFailFilterBytes := heightBkt.Get(settleFailFilterKey)
+ if settleFailFilterBytes == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ settleFailFilter := &PkgFilter{}
+ settleFailFilterReader := bytes.NewReader(settleFailFilterBytes)
+ if err := settleFailFilter.Decode(settleFailFilterReader); err != nil {
+ return err
+ }
+
+ // Update the ack filter for this height.
+ for _, index := range indexes {
+ settleFailFilter.Set(index)
+ }
+
+ // Write the resulting filter to disk.
+ var settleFailFilterBuf bytes.Buffer
+ if err := settleFailFilter.Encode(&settleFailFilterBuf); err != nil {
+ return err
+ }
+
+ return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes())
+}
+
+// RemovePkg deletes the forwarding package at the given height from the
+// packager's source bucket.
+func (p *ChannelPackager) RemovePkg(tx kvdb.RwTx, height uint64) error {
+ fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
+ if fwdPkgBkt == nil {
+ return nil
+ }
+
+ sourceBytes := forwardingLogKey(p.source.ToUint64())
+ sourceBkt := fwdPkgBkt.NestedReadWriteBucket(sourceBytes[:])
+ if sourceBkt == nil {
+ return ErrCorruptedFwdPkg
+ }
+
+ heightKey := forwardingLogKey(height)
+
+ return sourceBkt.DeleteNestedBucket(heightKey[:])
+}
+
+// Wipe deletes all the channel's forwarding packages, if any.
+func (p *ChannelPackager) Wipe(tx kvdb.RwTx) error {
+ // If the root bucket doesn't exist, there's no need to delete.
+ fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
+ if fwdPkgBkt == nil {
+ return nil
+ }
+
+ sourceBytes := forwardingLogKey(p.source.ToUint64())
+
+ // If the nested bucket doesn't exist, there's no need to delete.
+ if fwdPkgBkt.NestedReadWriteBucket(sourceBytes[:]) == nil {
+ return nil
+ }
+
+ return fwdPkgBkt.DeleteNestedBucket(sourceBytes[:])
+}
+
+// uint16Key writes the provided 16-bit unsigned integer to a 2-byte slice.
+func uint16Key(i uint16) []byte {
+ key := make([]byte, 2)
+ byteOrder.PutUint16(key, i)
+ return key
+}
+
+// forwardingLogKey converts a uint64 into an 8 byte forwarding package key.
+func forwardingLogKey(updateNum uint64) [8]byte {
+ var key [8]byte
+ byteOrder.PutUint64(key[:], updateNum)
+ return key
+}
+
+// Compile-time constraint to ensure that ChannelPackager implements the public
+// FwdPackager interface.
+var _ FwdPackager = (*ChannelPackager)(nil)
+
+// Compile-time constraint to ensure that SwitchPackager implements the public
+// FwdOperator interface.
+var _ FwdOperator = (*SwitchPackager)(nil)
### chanstate/kv_forwarding_package_test.go
@@ -1,4 +1,4 @@
-package channeldb_test
+package chanstate_test
import (
"bytes"
@@ -7,7 +7,7 @@ import (
"testing"
"github.com/btcsuite/btcd/wire/v2"
- "github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
@@ -27,7 +27,7 @@ func TestPkgFilterBruteForce(t *testing.T) {
// properly for all relevant sizes of `high`.
func checkPkgFilterRange(t *testing.T, high int) {
for i := uint16(0); i < uint16(high); i++ {
- f := channeldb.NewPkgFilter(i)
+ f := chanstate.NewPkgFilter(i)
if f.Count() != i {
t.Fatalf("pkg filter count=%d is actually %d",
@@ -74,7 +74,7 @@ func TestPkgFilterRand(t *testing.T) {
// is parameterized by a base `b` coprime to `p`, and using modular
// exponentiation to generate all elements in [1,p).
func checkPkgFilterRand(t *testing.T, b, p uint16) {
- f := channeldb.NewPkgFilter(p)
+ f := chanstate.NewPkgFilter(p)
var j = b
for i := uint16(1); i < p; i++ {
if f.Contains(j) {
@@ -113,7 +113,9 @@ func checkPkgFilterRand(t *testing.T, b, p uint16) {
// 2. verifying the number of bytes written matches the filter's Size()
// 3. reconstructing the filter decoding the bytes
// 4. checking that the two filters are the same according to Equal
-func checkPkgFilterEncodeDecode(t *testing.T, i uint16, f *channeldb.PkgFilter) {
+func checkPkgFilterEncodeDecode(t *testing.T, i uint16,
+ f *chanstate.PkgFilter) {
+
var b bytes.Buffer
if err := f.Encode(&b); err != nil {
t.Fatalf("unable to serialize pkg filter: %v", err)
@@ -128,7 +130,7 @@ func checkPkgFilterEncodeDecode(t *testing.T, i uint16, f *channeldb.PkgFilter)
reader := bytes.NewReader(b.Bytes())
- f2 := &channeldb.PkgFilter{}
+ f2 := &chanstate.PkgFilter{}
if err := f2.Decode(reader); err != nil {
t.Fatalf("unable to deserialize pkg filter: %v", err)
}
@@ -144,8 +146,8 @@ var (
chanID = lnwire.NewChanIDFromOutPoint(wire.OutPoint{})
)
-func testSettleFails() []channeldb.LogUpdate {
- return []channeldb.LogUpdate{
+func testSettleFails() []chanstate.LogUpdate {
+ return []chanstate.LogUpdate{
{
LogIndex: 2,
UpdateMsg: &lnwire.UpdateFulfillHTLC{
@@ -165,8 +167,8 @@ func testSettleFails() []channeldb.LogUpdate {
}
}
-func testAdds() []channeldb.LogUpdate {
- return []channeldb.LogUpdate{
+func testAdds() []chanstate.LogUpdate {
+ return []chanstate.LogUpdate{
{
LogIndex: 0,
UpdateMsg: &lnwire.UpdateAddHTLC{
@@ -200,7 +202,7 @@ func TestPackagerEmptyFwdPkg(t *testing.T) {
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
- packager := channeldb.NewChannelPackager(shortChanID)
+ packager := chanstate.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
@@ -209,7 +211,7 @@ func TestPackagerEmptyFwdPkg(t *testing.T) {
}
// Next, create and write a new forwarding package with no htlcs.
- fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, nil, nil)
+ fwdPkg := chanstate.NewFwdPkg(shortChanID, 0, nil, nil)
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AddFwdPkg(tx, fwdPkg)
@@ -224,7 +226,7 @@ func TestPackagerEmptyFwdPkg(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, 0)
assertAckFilterIsFull(t, fwdPkgs[0], true)
@@ -243,7 +245,7 @@ func TestPackagerEmptyFwdPkg(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, 0)
assertAckFilterIsFull(t, fwdPkgs[0], true)
@@ -269,7 +271,7 @@ func TestPackagerOnlyAdds(t *testing.T) {
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
- packager := channeldb.NewChannelPackager(shortChanID)
+ packager := chanstate.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
@@ -281,7 +283,7 @@ func TestPackagerOnlyAdds(t *testing.T) {
// Next, create and write a new forwarding package that only has add
// htlcs.
- fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, nil)
+ fwdPkg := chanstate.NewFwdPkg(shortChanID, 0, adds, nil)
nAdds := len(adds)
@@ -298,7 +300,7 @@ func TestPackagerOnlyAdds(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, 0)
assertAckFilterIsFull(t, fwdPkgs[0], false)
@@ -321,11 +323,11 @@ func TestPackagerOnlyAdds(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, 0)
assertAckFilterIsFull(t, fwdPkgs[0], false)
- addRef := channeldb.AddRef{
+ addRef := chanstate.AddRef{
Height: fwdPkg.Height,
Index: uint16(i),
}
@@ -344,7 +346,7 @@ func TestPackagerOnlyAdds(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, 0)
assertAckFilterIsFull(t, fwdPkgs[0], true)
@@ -373,7 +375,7 @@ func TestPackagerOnlySettleFails(t *testing.T) {
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
- packager := channeldb.NewChannelPackager(shortChanID)
+ packager := chanstate.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
@@ -384,7 +386,7 @@ func TestPackagerOnlySettleFails(t *testing.T) {
// Next, create and write a new forwarding package that only has add
// htlcs.
settleFails := testSettleFails()
- fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, nil, settleFails)
+ fwdPkg := chanstate.NewFwdPkg(shortChanID, 0, nil, settleFails)
nSettleFails := len(settleFails)
@@ -401,7 +403,7 @@ func TestPackagerOnlySettleFails(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, nSettleFails)
assertAckFilterIsFull(t, fwdPkgs[0], true)
@@ -424,12 +426,12 @@ func TestPackagerOnlySettleFails(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], false)
assertAckFilterIsFull(t, fwdPkgs[0], true)
- failSettleRef := channeldb.SettleFailRef{
+ failSettleRef := chanstate.SettleFailRef{
Source: shortChanID,
Height: fwdPkg.Height,
Index: uint16(i),
@@ -449,7 +451,7 @@ func TestPackagerOnlySettleFails(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], true)
assertAckFilterIsFull(t, fwdPkgs[0], true)
@@ -478,7 +480,7 @@ func TestPackagerAddsThenSettleFails(t *testing.T) {
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
- packager := channeldb.NewChannelPackager(shortChanID)
+ packager := chanstate.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
@@ -491,7 +493,7 @@ func TestPackagerAddsThenSettleFails(t *testing.T) {
// Next, create and write a new forwarding package that only has add
// htlcs.
settleFails := testSettleFails()
- fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, settleFails)
+ fwdPkg := chanstate.NewFwdPkg(shortChanID, 0, adds, settleFails)
nAdds := len(adds)
nSettleFails := len(settleFails)
@@ -509,7 +511,7 @@ func TestPackagerAddsThenSettleFails(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertAckFilterIsFull(t, fwdPkgs[0], false)
@@ -532,12 +534,12 @@ func TestPackagerAddsThenSettleFails(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], false)
assertAckFilterIsFull(t, fwdPkgs[0], false)
- addRef := channeldb.AddRef{
+ addRef := chanstate.AddRef{
Height: fwdPkg.Height,
Index: uint16(i),
}
@@ -558,12 +560,12 @@ func TestPackagerAddsThenSettleFails(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], false)
assertAckFilterIsFull(t, fwdPkgs[0], true)
- failSettleRef := channeldb.SettleFailRef{
+ failSettleRef := chanstate.SettleFailRef{
Source: shortChanID,
Height: fwdPkg.Height,
Index: uint16(i),
@@ -583,7 +585,7 @@ func TestPackagerAddsThenSettleFails(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], true)
assertAckFilterIsFull(t, fwdPkgs[0], true)
@@ -614,7 +616,7 @@ func TestPackagerSettleFailsThenAdds(t *testing.T) {
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
- packager := channeldb.NewChannelPackager(shortChanID)
+ packager := chanstate.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
@@ -627,7 +629,7 @@ func TestPackagerSettleFailsThenAdds(t *testing.T) {
// Next, create and write a new forwarding package that has both add
// and settle/fail htlcs.
settleFails := testSettleFails()
- fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, settleFails)
+ fwdPkg := chanstate.NewFwdPkg(shortChanID, 0, adds, settleFails)
nAdds := len(adds)
nSettleFails := len(settleFails)
@@ -645,7 +647,7 @@ func TestPackagerSettleFailsThenAdds(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertAckFilterIsFull(t, fwdPkgs[0], false)
@@ -671,12 +673,12 @@ func TestPackagerSettleFailsThenAdds(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], false)
assertAckFilterIsFull(t, fwdPkgs[0], false)
- failSettleRef := channeldb.SettleFailRef{
+ failSettleRef := chanstate.SettleFailRef{
Source: shortChanID,
Height: fwdPkg.Height,
Index: uint16(i),
@@ -699,12 +701,12 @@ func TestPackagerSettleFailsThenAdds(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], true)
assertAckFilterIsFull(t, fwdPkgs[0], false)
- addRef := channeldb.AddRef{
+ addRef := chanstate.AddRef{
Height: fwdPkg.Height,
Index: uint16(i),
}
@@ -723,7 +725,7 @@ func TestPackagerSettleFailsThenAdds(t *testing.T) {
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
- assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
+ assertFwdPkgState(t, fwdPkgs[0], chanstate.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], true)
assertAckFilterIsFull(t, fwdPkgs[0], true)
@@ -750,7 +752,7 @@ func TestPackagerWipeAll(t *testing.T) {
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
- packager := channeldb.NewChannelPackager(shortChanID)
+ packager := chanstate.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
@@ -761,8 +763,8 @@ func TestPackagerWipeAll(t *testing.T) {
require.NoError(t, err, "unable to wipe fwdpkg")
// Next, create and write two forwarding packages with no htlcs.
- fwdPkg1 := channeldb.NewFwdPkg(shortChanID, 0, nil, nil)
- fwdPkg2 := channeldb.NewFwdPkg(shortChanID, 1, nil, nil)
+ fwdPkg1 := chanstate.NewFwdPkg(shortChanID, 0, nil, nil)
+ fwdPkg2 := chanstate.NewFwdPkg(shortChanID, 1, nil, nil)
err = kvdb.Update(db, func(tx kvdb.RwTx) error {
if err := packager.AddFwdPkg(tx, fwdPkg2); err != nil {
@@ -787,8 +789,9 @@ func TestPackagerWipeAll(t *testing.T) {
// assertFwdPkgState checks the current state of a fwdpkg meets our
// expectations.
-func assertFwdPkgState(t *testing.T, fwdPkg *channeldb.FwdPkg,
- state channeldb.FwdState) {
+func assertFwdPkgState(t *testing.T, fwdPkg *chanstate.FwdPkg,
+ state chanstate.FwdState) {
+
_, _, line, _ := runtime.Caller(1)
if fwdPkg.State != state {
t.Fatalf("line %d: expected fwdpkg in state %v, found %v",
@@ -798,7 +801,7 @@ func assertFwdPkgState(t *testing.T, fwdPkg *channeldb.FwdPkg,
// assertFwdPkgNumAddsSettleFails checks that the number of adds and
// settle/fail log updates are correct.
-func assertFwdPkgNumAddsSettleFails(t *testing.T, fwdPkg *channeldb.FwdPkg,
+func assertFwdPkgNumAddsSettleFails(t *testing.T, fwdPkg *chanstate.FwdPkg,
expectedNumAdds, expectedNumSettleFails int) {
_, _, line, _ := runtime.Caller(1)
if len(fwdPkg.Adds) != expectedNumAdds {
@@ -814,7 +817,9 @@ func assertFwdPkgNumAddsSettleFails(t *testing.T, fwdPkg *channeldb.FwdPkg,
// assertAckFilterIsFull checks whether or not a fwdpkg's ack filter matches our
// expected full-ness.
-func assertAckFilterIsFull(t *testing.T, fwdPkg *channeldb.FwdPkg, expected bool) {
+func assertAckFilterIsFull(t *testing.T, fwdPkg *chanstate.FwdPkg,
+ expected bool) {
+
_, _, line, _ := runtime.Caller(1)
if fwdPkg.AckFilter.IsFull() != expected {
t.Fatalf("line %d: expected fwdpkg ack filter IsFull to be %v, "+
@@ -824,7 +829,9 @@ func assertAckFilterIsFull(t *testing.T, fwdPkg *channeldb.FwdPkg, expected bool
// assertSettleFailFilterIsFull checks whether or not a fwdpkg's settle fail
// filter matches our expected full-ness.
-func assertSettleFailFilterIsFull(t *testing.T, fwdPkg *channeldb.FwdPkg, expected bool) {
+func assertSettleFailFilterIsFull(t *testing.T, fwdPkg *chanstate.FwdPkg,
+ expected bool) {
+
_, _, line, _ := runtime.Caller(1)
if fwdPkg.SettleFailFilter.IsFull() != expected {
t.Fatalf("line %d: expected fwdpkg settle/fail filter IsFull to be %v, "+
@@ -835,9 +842,9 @@ func assertSettleFailFilterIsFull(t *testing.T, fwdPkg *channeldb.FwdPkg, expect
// loadFwdPkgs is a helper method that reads all forwarding packages for a
// particular packager.
func loadFwdPkgs(t *testing.T, db kvdb.Backend,
- packager channeldb.FwdPackager) []*channeldb.FwdPkg {
+ packager chanstate.FwdPackager) []*chanstate.FwdPkg {
- var fwdPkgs []*channeldb.FwdPkg
+ var fwdPkgs []*chanstate.FwdPkg
if err := kvdb.View(db, func(tx kvdb.RTx) error {
var err error
fwdPkgs, err = packager.LoadFwdPkgs(tx)
### chanstate/kv_open_channel.go
@@ -0,0 +1,796 @@
+package chanstate
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/btcsuite/btcd/chainhash/v2"
+ "github.com/btcsuite/btcd/wire/v2"
+ "github.com/lightningnetwork/lnd/fn/v2"
+ graphdb "github.com/lightningnetwork/lnd/graph/db"
+ "github.com/lightningnetwork/lnd/keychain"
+ "github.com/lightningnetwork/lnd/kvdb"
+ "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+var (
+ // dataLossCommitPointKey stores the commitment point received from the
+ // remote peer during a channel sync in case we have lost channel state.
+ dataLossCommitPointKey = []byte("data-loss-commit-point-key")
+)
+
+// PutChannelDataLossCommitPoint stores the data-loss commit point in the
+// target channel bucket.
+func PutChannelDataLossCommitPoint(chanBucket kvdb.RwBucket,
+ commitPoint *btcec.PublicKey) error {
+
+ return chanBucket.Put(
+ dataLossCommitPointKey, commitPoint.SerializeCompressed(),
+ )
+}
+
+// FetchChannelDataLossCommitPoint retrieves the data-loss commit point from the
+// target channel bucket.
+func FetchChannelDataLossCommitPoint(
+ chanBucket kvdb.RBucket) (*btcec.PublicKey, error) {
+
+ bs := chanBucket.Get(dataLossCommitPointKey)
+ if bs == nil {
+ return nil, ErrNoCommitPoint
+ }
+
+ var b [btcec.PubKeyBytesLenCompressed]byte
+ r := bytes.NewReader(bs)
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return nil, err
+ }
+
+ return btcec.ParsePubKey(b[:])
+}
+
+const (
+ // A tlv type definition used to serialize an outpoint's IndexStatus
+ // for use in the outpoint index.
+ IndexStatusType tlv.Type = 0
+)
+
+// IndexStatus is an enum-like type that describes what state the outpoint is
+// in. Currently only two possible values.
+type IndexStatus uint8
+
+const (
+ // OutpointOpen represents an outpoint that is open in the outpoint
+ // index.
+ OutpointOpen IndexStatus = 0
+
+ // OutpointClosed represents an outpoint that is closed in the outpoint
+ // index.
+ OutpointClosed IndexStatus = 1
+)
+
+func putOutpointIndexStatus(opBucket kvdb.RwBucket, chanKey []byte,
+ status IndexStatus) error {
+
+ statusByte := uint8(status)
+ statusRecord := tlv.MakePrimitiveRecord(IndexStatusType, &statusByte)
+ opStream, err := tlv.NewStream(statusRecord)
+ if err != nil {
+ return err
+ }
+
+ var b bytes.Buffer
+ if err := opStream.Encode(&b); err != nil {
+ return err
+ }
+
+ return opBucket.Put(chanKey, b.Bytes())
+}
+
+// PutOpenOutpointIndex stores chanKey in the outpoint index as an open
+// outpoint.
+func PutOpenOutpointIndex(opBucket kvdb.RwBucket, chanKey []byte) error {
+ return putOutpointIndexStatus(opBucket, chanKey, OutpointOpen)
+}
+
+// UpdateClosedOutpointIndex flips the outpoint index entry for chanKey from
+// open to closed. The index entry must already exist; it was placed there when
+// the channel was opened.
+func UpdateClosedOutpointIndex(tx kvdb.RwTx, chanKey []byte) error {
+ opBucket := tx.ReadWriteBucket(outpointBucket)
+ if opBucket == nil {
+ return ErrNoChanDBExists
+ }
+ if opBucket.Get(chanKey) == nil {
+ return ErrMissingIndexEntry
+ }
+
+ return putOutpointIndexStatus(opBucket, chanKey, OutpointClosed)
+}
+
+// IsOutpointClosed reports whether the supplied chanKey has been flipped to
+// OutpointClosed in the supplied outpointBucket. The flip is performed in the
+// same transaction as the rest of CloseChannel (sync and tombstone paths
+// alike), so a true result is the authoritative "this channel went through
+// CloseChannel" signal. On tombstone-enabled backends the chanBucket may still
+// exist on disk; readers consult this helper to skip those entries. Callers
+// fetch outpointBucket once and pass it in, which lets loop-style readers
+// hoist the bucket lookup out of the inner loop.
+func IsOutpointClosed(opBucket kvdb.RBucket, chanKey []byte) (bool, error) {
+ if opBucket == nil {
+ return false, nil
+ }
+ raw := opBucket.Get(chanKey)
+ if raw == nil {
+ return false, nil
+ }
+
+ var status uint8
+ statusRecord := tlv.MakePrimitiveRecord(IndexStatusType, &status)
+ stream, err := tlv.NewStream(statusRecord)
+ if err != nil {
+ return false, err
+ }
+ if err := stream.Decode(bytes.NewReader(raw)); err != nil {
+ return false, fmt.Errorf("decode outpoint status for "+
+ "chan_key=%x: %w", chanKey, err)
+ }
+
+ return IndexStatus(status) == OutpointClosed, 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.RTx, nodeKey *btcec.PublicKey,
+ outPoint *wire.OutPoint, chainHash chainhash.Hash) (
+ kvdb.RBucket, error) {
+
+ // First fetch the top level bucket which stores all data related to
+ // current, active channels.
+ openChanBucket := tx.ReadBucket(openChannelBucket)
+ if openChanBucket == nil {
+ return nil, ErrNoChanDBExists
+ }
+
+ // TODO(roasbeef): CreateTopLevelBucket on the interface isn't like
+ // CreateIfNotExists, will return error.
+
+ // Within this top level bucket, fetch the bucket dedicated to storing
+ // open channel data specific to the remote node.
+ nodePub := nodeKey.SerializeCompressed()
+ nodeChanBucket := openChanBucket.NestedReadBucket(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.NestedReadBucket(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
+ if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
+ return nil, err
+ }
+ chanKey := chanPointBuf.Bytes()
+
+ // Treat already-closed channels as gone. The chanBucket may still
+ // exist on tombstone-enabled backends; the outpoint flip is the
+ // source of truth.
+ closed, err := IsOutpointClosed(tx.ReadBucket(outpointBucket), chanKey)
+ if err != nil {
+ return nil, err
+ }
+ if closed {
+ return nil, ErrChannelNotFound
+ }
+
+ chanBucket := chainBucket.NestedReadBucket(chanKey)
+ if chanBucket == nil {
+ return nil, ErrChannelNotFound
+ }
+
+ return chanBucket, nil
+}
+
+// FetchChanBucketRw 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. This differs from
+// FetchChanBucket in that it returns a writeable bucket.
+func FetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey,
+ outPoint *wire.OutPoint, chainHash chainhash.Hash) (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
+ }
+
+ // TODO(roasbeef): CreateTopLevelBucket on the interface isn't like
+ // CreateIfNotExists, will return error.
+
+ // Within this top level bucket, fetch the bucket dedicated to storing
+ // open channel data specific to the remote node.
+ nodePub := nodeKey.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(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
+ if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
+ return nil, err
+ }
+ chanKey := chanPointBuf.Bytes()
+
+ // Treat already-closed channels as gone. The chanBucket may still
+ // exist on tombstone-enabled backends; the outpoint flip is the
+ // source of truth.
+ closed, err := IsOutpointClosed(tx.ReadBucket(outpointBucket), chanKey)
+ if err != nil {
+ return nil, err
+ }
+ if closed {
+ return nil, ErrChannelNotFound
+ }
+
+ chanBucket := chainBucket.NestedReadWriteBucket(chanKey)
+ if chanBucket == nil {
+ return nil, ErrChannelNotFound
+ }
+
+ return chanBucket, nil
+}
+
+// keyLocRecord is a wrapper struct around keychain.KeyLocator to implement the
+// tlv.RecordProducer interface.
+type keyLocRecord struct {
+ keychain.KeyLocator
+}
+
+// Record 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.
+//
+// NOTE: This is part of the tlv.RecordProducer interface.
+func (k *keyLocRecord) Record() tlv.Record {
+ // Note that we set the type here as zero, as when used with a
+ // tlv.RecordT, the type param will be used as the type.
+ return tlv.MakeStaticRecord(
+ 0, &k.KeyLocator, 8, EKeyLocator, DKeyLocator,
+ )
+}
+
+// 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)
+}
+
+// WriteChanConfig serializes a channel config.
+func WriteChanConfig(b io.Writer, c *ChannelConfig) error {
+ return WriteElements(b,
+ c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC,
+ c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey,
+ c.RevocationBasePoint, c.PaymentBasePoint, c.DelayBasePoint,
+ c.HtlcBasePoint,
+ )
+}
+
+// ReadChanConfig deserializes a channel config.
+func ReadChanConfig(b io.Reader, c *ChannelConfig) error {
+ return ReadElements(b,
+ &c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve,
+ &c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay,
+ &c.MultiSigKey, &c.RevocationBasePoint,
+ &c.PaymentBasePoint, &c.DelayBasePoint,
+ &c.HtlcBasePoint,
+ )
+}
+
+// PutChanInfo serializes the static channel info into the channel bucket.
+func PutChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
+ var w bytes.Buffer
+ if err := WriteElements(&w,
+ channel.ChanType, channel.ChainHash, channel.FundingOutpoint,
+ channel.ShortChannelID, channel.IsPending, channel.IsInitiator,
+ channel.ChannelStatusForStore(), channel.FundingBroadcastHeight,
+ channel.NumConfsRequired, channel.ChannelFlags,
+ channel.IdentityPub, channel.Capacity, channel.TotalMSatSent,
+ channel.TotalMSatReceived,
+ ); err != nil {
+ return err
+ }
+
+ // For single funder channels that we initiated, and we have the
+ // funding transaction, then write the funding txn.
+ if channel.fundingTxPresent() {
+ if err := WriteElement(&w, channel.FundingTxn); err != nil {
+ return err
+ }
+ }
+
+ if err := WriteChanConfig(&w, &channel.LocalChanCfg); err != nil {
+ return err
+ }
+ if err := WriteChanConfig(&w, &channel.RemoteChanCfg); err != nil {
+ return err
+ }
+
+ if err := EncodeOpenChannelTlvData(&w, channel); err != nil {
+ return fmt.Errorf("unable to encode aux data: %w", 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,
+ channel.LocalShutdownScript,
+ ); err != nil {
+ return err
+ }
+
+ return putOptionalUpfrontShutdownScript(
+ chanBucket, remoteUpfrontShutdownKey,
+ channel.RemoteShutdownScript,
+ )
+}
+
+// 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 := WriteElement(&w, script); err != nil {
+ return err
+ }
+
+ return chanBucket.Put(key, w.Bytes())
+}
+
+// FetchChanInfo deserializes the static channel info from the channel bucket.
+func FetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
+ infoBytes := chanBucket.Get(chanInfoKey)
+ if infoBytes == nil {
+ return ErrNoChanInfoFound
+ }
+ r := bytes.NewReader(infoBytes)
+
+ var chanStatus ChannelStatus
+ if err := ReadElements(r,
+ &channel.ChanType, &channel.ChainHash, &channel.FundingOutpoint,
+ &channel.ShortChannelID, &channel.IsPending,
+ &channel.IsInitiator,
+ &chanStatus, &channel.FundingBroadcastHeight,
+ &channel.NumConfsRequired, &channel.ChannelFlags,
+ &channel.IdentityPub, &channel.Capacity, &channel.TotalMSatSent,
+ &channel.TotalMSatReceived,
+ ); err != nil {
+ return err
+ }
+ channel.SetChannelStatusForStore(chanStatus)
+
+ // For single funder channels that we initiated and have the funding
+ // transaction to, read the funding txn.
+ if channel.fundingTxPresent() {
+ if err := ReadElement(r, &channel.FundingTxn); err != nil {
+ return err
+ }
+ }
+
+ if err := ReadChanConfig(r, &channel.LocalChanCfg); err != nil {
+ return err
+ }
+ if err := ReadChanConfig(r, &channel.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.
+ channel.LastWasRevoke = false
+ } else {
+ // Otherwise, read the value into the LastWasRevoke field.
+ revokeReader := bytes.NewReader(lastWasRevokeBytes)
+ err := ReadElements(revokeReader, &channel.LastWasRevoke)
+ if err != nil {
+ return err
+ }
+ }
+
+ if err := DecodeOpenChannelTlvData(r, channel); err != nil {
+ return fmt.Errorf("unable to decode aux data: %w", err)
+ }
+
+ // Finally, read the optional shutdown scripts.
+ if err := getOptionalUpfrontShutdownScript(
+ chanBucket, localUpfrontShutdownKey,
+ &channel.LocalShutdownScript,
+ ); err != nil {
+ return err
+ }
+
+ return getOptionalUpfrontShutdownScript(
+ chanBucket, remoteUpfrontShutdownKey,
+ &channel.RemoteShutdownScript,
+ )
+}
+
+// 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 := ReadElement(r, &tempScript); err != nil {
+ return err
+ }
+ *script = tempScript
+
+ return nil
+}
+
+// PutOpenChannel serializes, and stores the current state of the channel in
+// its entirety.
+func PutOpenChannel(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
+ // First, we'll write out all the relatively static fields, that are
+ // decided upon initial channel creation.
+ if err := PutChanInfo(chanBucket, channel); err != nil {
+ return fmt.Errorf("unable to store chan info: %w", err)
+ }
+
+ // With the static channel info written out, we'll now write out the
+ // current commitment state for both parties.
+ if err := PutChanCommitments(chanBucket, channel); err != nil {
+ return fmt.Errorf("unable to store chan commitments: %w", err)
+ }
+
+ // Next, if this is a frozen channel, we'll add in the axillary
+ // information we need to store.
+ if channel.ChanType.IsFrozen() ||
+ channel.ChanType.HasLeaseExpiration() {
+
+ err := StoreThawHeight(
+ chanBucket, channel.ThawHeight,
+ )
+ if err != nil {
+ return fmt.Errorf("unable to store thaw height: %w",
+ err)
+ }
+ }
+
+ // Finally, we'll write out the revocation state for both parties
+ // within a distinct key space.
+ if err := PutChanRevocationState(chanBucket, channel); err != nil {
+ return fmt.Errorf("unable to store chan revocations: %w", err)
+ }
+
+ return nil
+}
+
+// FetchOpenChannel retrieves, and deserializes (including decrypting
+// sensitive) the complete channel currently active with the passed nodeID.
+func FetchOpenChannel(chanBucket kvdb.RBucket,
+ chanPoint *wire.OutPoint) (*OpenChannel, error) {
+
+ channel := &OpenChannel{
+ FundingOutpoint: *chanPoint,
+ }
+
+ // First, we'll read all the static information that changes less
+ // frequently from disk.
+ if err := FetchChanInfo(chanBucket, channel); err != nil {
+ return nil, fmt.Errorf("unable to fetch chan info: %w", err)
+ }
+
+ // With the static information read, we'll now read the current
+ // commitment state for both sides of the channel.
+ if err := FetchChanCommitments(chanBucket, channel); err != nil {
+ return nil, fmt.Errorf("unable to fetch chan commitments: %w",
+ err)
+ }
+
+ // Next, if this is a frozen channel, we'll add in the axillary
+ // information we need to store.
+ if channel.ChanType.IsFrozen() ||
+ channel.ChanType.HasLeaseExpiration() {
+
+ thawHeight, err := FetchThawHeight(chanBucket)
+ if err != nil {
+ return nil, fmt.Errorf("unable to store thaw "+
+ "height: %v", err)
+ }
+
+ channel.ThawHeight = thawHeight
+ }
+
+ // Finally, we'll retrieve the current revocation state so we can
+ // properly
+ if err := FetchChanRevocationState(chanBucket, channel); err != nil {
+ return nil, fmt.Errorf("unable to fetch chan revocations: %w",
+ err)
+ }
+
+ return channel, nil
+}
+
+// openChannelTlvData houses the new data fields that are stored for each
+// channel in a TLV stream within the root bucket. This is stored as a TLV
+// stream appended to the existing hard-coded fields in the channel's root
+// bucket. New fields being added to the channel state should be added here.
+//
+// NOTE: This struct is used for serialization purposes only and its fields
+// should be accessed via the OpenChannel struct while in memory.
+type openChannelTlvData struct {
+ // revokeKeyLoc is the key locator for the revocation key.
+ revokeKeyLoc tlv.RecordT[tlv.TlvType1, keyLocRecord]
+
+ // initialLocalBalance is the initial local balance of the channel.
+ initialLocalBalance tlv.RecordT[tlv.TlvType2, uint64]
+
+ // initialRemoteBalance is the initial remote balance of the channel.
+ initialRemoteBalance tlv.RecordT[tlv.TlvType3, uint64]
+
+ // realScid is the real short channel ID of the channel corresponding to
+ // the on-chain outpoint.
+ realScid tlv.RecordT[tlv.TlvType4, lnwire.ShortChannelID]
+
+ // memo is an optional text field that gives context to the user about
+ // the channel.
+ memo tlv.OptionalRecordT[tlv.TlvType5, []byte]
+
+ // tapscriptRoot is the optional Tapscript root the channel funding
+ // output commits to.
+ tapscriptRoot tlv.OptionalRecordT[tlv.TlvType6, [32]byte]
+
+ // customBlob is an optional TLV encoded blob of data representing
+ // custom channel funding information.
+ customBlob tlv.OptionalRecordT[tlv.TlvType7, tlv.Blob]
+
+ // confirmationHeight records the block height at which the funding
+ // transaction was first confirmed.
+ confirmationHeight tlv.RecordT[tlv.TlvType8, uint32]
+
+ // closeConfirmationHeight records the block height at which the closing
+ // transaction was first confirmed. This is used to calculate the
+ // remaining confirmations until the channel is considered fully closed.
+ // Note: if not set, it means either the channel has not been
+ // closed yet, or it was closed before this field was introduced.
+ closeConfirmationHeight tlv.OptionalRecordT[tlv.TlvType9, uint32]
+}
+
+// encode serializes the openChannelTlvData to the given io.Writer.
+func (c *openChannelTlvData) encode(w io.Writer) error {
+ tlvRecords := []tlv.Record{
+ c.revokeKeyLoc.Record(),
+ c.initialLocalBalance.Record(),
+ c.initialRemoteBalance.Record(),
+ c.realScid.Record(),
+ c.confirmationHeight.Record(),
+ }
+ c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
+ tlvRecords = append(tlvRecords, memo.Record())
+ })
+ c.tapscriptRoot.WhenSome(
+ func(root tlv.RecordT[tlv.TlvType6, [32]byte]) {
+ tlvRecords = append(tlvRecords, root.Record())
+ },
+ )
+ c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType7, tlv.Blob]) {
+ tlvRecords = append(tlvRecords, blob.Record())
+ })
+ c.closeConfirmationHeight.WhenSome(
+ func(h tlv.RecordT[tlv.TlvType9, uint32]) {
+ tlvRecords = append(tlvRecords, h.Record())
+ },
+ )
+
+ tlv.SortRecords(tlvRecords)
+
+ // Create the tlv stream.
+ tlvStream, err := tlv.NewStream(tlvRecords...)
+ if err != nil {
+ return err
+ }
+
+ return tlvStream.Encode(w)
+}
+
+// decode deserializes the openChannelTlvData from the given io.Reader.
+func (c *openChannelTlvData) decode(r io.Reader) error {
+ memo := c.memo.Zero()
+ tapscriptRoot := c.tapscriptRoot.Zero()
+ blob := c.customBlob.Zero()
+ closeConfHeight := c.closeConfirmationHeight.Zero()
+
+ // Create the tlv stream.
+ tlvStream, err := tlv.NewStream(
+ c.revokeKeyLoc.Record(),
+ c.initialLocalBalance.Record(),
+ c.initialRemoteBalance.Record(),
+ c.realScid.Record(),
+ memo.Record(),
+ tapscriptRoot.Record(),
+ blob.Record(),
+ c.confirmationHeight.Record(),
+ closeConfHeight.Record(),
+ )
+ if err != nil {
+ return err
+ }
+
+ tlvs, err := tlvStream.DecodeWithParsedTypes(r)
+ if err != nil {
+ return err
+ }
+
+ if _, ok := tlvs[memo.TlvType()]; ok {
+ c.memo = tlv.SomeRecordT(memo)
+ }
+ if _, ok := tlvs[tapscriptRoot.TlvType()]; ok {
+ c.tapscriptRoot = tlv.SomeRecordT(tapscriptRoot)
+ }
+ if _, ok := tlvs[c.customBlob.TlvType()]; ok {
+ c.customBlob = tlv.SomeRecordT(blob)
+ }
+ if _, ok := tlvs[closeConfHeight.TlvType()]; ok {
+ c.closeConfirmationHeight = tlv.SomeRecordT(closeConfHeight)
+ }
+
+ return nil
+}
+
+// DecodeOpenChannelTlvData decodes and applies auxiliary TLV data to an open
+// channel.
+func DecodeOpenChannelTlvData(r io.Reader, channel *OpenChannel) error {
+ var auxData openChannelTlvData
+ if err := auxData.decode(r); err != nil {
+ return err
+ }
+
+ amendOpenChannelTlvData(channel, auxData)
+
+ return nil
+}
+
+// EncodeOpenChannelTlvData extracts and encodes auxiliary TLV data from an open
+// channel.
+func EncodeOpenChannelTlvData(w io.Writer, channel *OpenChannel) error {
+ auxData := extractOpenChannelTlvData(channel)
+ return auxData.encode(w)
+}
+
+// amendOpenChannelTlvData updates the channel with the given auxiliary TLV
+// data.
+func amendOpenChannelTlvData(channel *OpenChannel, auxData openChannelTlvData) {
+ channel.RevocationKeyLocator = auxData.revokeKeyLoc.Val.KeyLocator
+ channel.InitialLocalBalance = lnwire.MilliSatoshi(
+ auxData.initialLocalBalance.Val,
+ )
+ channel.InitialRemoteBalance = lnwire.MilliSatoshi(
+ auxData.initialRemoteBalance.Val,
+ )
+ channel.SetConfirmedScidForStore(auxData.realScid.Val)
+ channel.ConfirmationHeight = auxData.confirmationHeight.Val
+
+ auxData.memo.WhenSomeV(func(memo []byte) {
+ channel.Memo = memo
+ })
+ auxData.tapscriptRoot.WhenSomeV(func(h [32]byte) {
+ channel.TapscriptRoot = fn.Some[chainhash.Hash](h)
+ })
+ auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
+ channel.CustomBlob = fn.Some(blob)
+ })
+ auxData.closeConfirmationHeight.WhenSomeV(func(h uint32) {
+ channel.CloseConfirmationHeight = fn.Some(h)
+ })
+}
+
+// extractOpenChannelTlvData creates a new openChannelTlvData from the given
+// channel.
+func extractOpenChannelTlvData(channel *OpenChannel) openChannelTlvData {
+ auxData := openChannelTlvData{
+ revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1](
+ keyLocRecord{channel.RevocationKeyLocator},
+ ),
+ initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2](
+ uint64(channel.InitialLocalBalance),
+ ),
+ initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3](
+ uint64(channel.InitialRemoteBalance),
+ ),
+ realScid: tlv.NewRecordT[tlv.TlvType4](
+ channel.ConfirmedScidForStore(),
+ ),
+ confirmationHeight: tlv.NewPrimitiveRecord[tlv.TlvType8](
+ channel.ConfirmationHeight,
+ ),
+ }
+
+ if len(channel.Memo) != 0 {
+ auxData.memo = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType5](channel.Memo),
+ )
+ }
+ channel.TapscriptRoot.WhenSome(func(h chainhash.Hash) {
+ auxData.tapscriptRoot = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h),
+ )
+ })
+ channel.CustomBlob.WhenSome(func(blob tlv.Blob) {
+ auxData.customBlob = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType7](blob),
+ )
+ })
+ channel.CloseConfirmationHeight.WhenSome(func(h uint32) {
+ auxData.closeConfirmationHeight = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType9](h),
+ )
+ })
+
+ return auxData
+}
### chanstate/kv_revocation_log.go
@@ -5,15 +5,186 @@ import (
"encoding/binary"
"errors"
"io"
+ "math"
+ "github.com/lightningnetwork/lnd/kvdb"
+ "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/tlv"
)
-// This file contains the KV/TLV serialization helpers for revocation logs.
-// The domain types remain in revocation_log.go.
+var (
+ // revocationLogBucket is a sub-bucket under openChannelBucket. This
+ // sub-bucket is dedicated for storing the minimal info required to
+ // re-construct a past state in order to punish a counterparty
+ // attempting a non-cooperative channel closure.
+ revocationLogBucket = []byte("revocation-log")
-// htlcEntryToTlvStream converts an HTLCEntry record into a tlv representation.
-func htlcEntryToTlvStream(h *HTLCEntry) (*tlv.Stream, error) {
+ // ErrLogEntryNotFound is returned when we cannot find a log entry at
+ // the height requested in the revocation log.
+ ErrLogEntryNotFound = errors.New("log entry not found")
+
+ // ErrOutputIndexTooBig is returned when the output index is greater
+ // than uint16.
+ ErrOutputIndexTooBig = errors.New("output index is over uint16")
+)
+
+// RevocationLogBucketKey returns the sub-bucket key that stores the current
+// revocation log format.
+func RevocationLogBucketKey() []byte {
+ return revocationLogBucket
+}
+
+// PutRevocationLog uses the fields `CommitTx` and `Htlcs` from a
+// ChannelCommitment to construct a revocation log entry and saves them to
+// disk. It also saves our output index and their output index, which are
+// useful when creating breach retribution.
+func PutRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
+ ourOutputIndex, theirOutputIndex uint32, noAmtData bool) error {
+
+ // Sanity check that the output indexes can be safely converted.
+ if ourOutputIndex > math.MaxUint16 {
+ return ErrOutputIndexTooBig
+ }
+ if theirOutputIndex > math.MaxUint16 {
+ return ErrOutputIndexTooBig
+ }
+
+ rl := &RevocationLog{
+ OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
+ uint16(ourOutputIndex),
+ ),
+ TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
+ uint16(theirOutputIndex),
+ ),
+ CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2, [32]byte](
+ commit.CommitTx.TxHash(),
+ ),
+ HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
+ }
+
+ commit.CustomBlob.WhenSome(func(blob tlv.Blob) {
+ rl.CustomBlob = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
+ )
+ })
+
+ if !noAmtData {
+ rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
+ tlv.NewBigSizeT(commit.LocalBalance),
+ ))
+
+ rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
+ tlv.NewBigSizeT(commit.RemoteBalance),
+ ))
+ }
+
+ for _, htlc := range commit.Htlcs {
+ // Skip dust HTLCs.
+ if htlc.OutputIndex < 0 {
+ continue
+ }
+
+ // Sanity check that the output indexes can be safely
+ // converted.
+ if htlc.OutputIndex > math.MaxUint16 {
+ return ErrOutputIndexTooBig
+ }
+
+ entry, err := NewHTLCEntryFromHTLC(htlc)
+ if err != nil {
+ return err
+ }
+ rl.HTLCEntries = append(rl.HTLCEntries, entry)
+ }
+
+ var b bytes.Buffer
+ err := SerializeRevocationLog(&b, rl)
+ if err != nil {
+ return err
+ }
+
+ logEntrykey := revocationLogKey(commit.CommitHeight)
+
+ return bucket.Put(logEntrykey[:], b.Bytes())
+}
+
+// FetchRevocationLog queries the revocation log bucket to find an log entry.
+// Return an error if not found.
+func FetchRevocationLog(log kvdb.RBucket,
+ updateNum uint64) (RevocationLog, error) {
+
+ logEntrykey := revocationLogKey(updateNum)
+ commitBytes := log.Get(logEntrykey[:])
+ if commitBytes == nil {
+ return RevocationLog{}, ErrLogEntryNotFound
+ }
+
+ commitReader := bytes.NewReader(commitBytes)
+
+ return DeserializeRevocationLog(commitReader)
+}
+
+// Record returns a tlv record for the SparsePayHash.
+func (s *SparsePayHash) Record() tlv.Record {
+ // We use a zero for the type here, as this'll be used along with the
+ // RecordT type.
+ return tlv.MakeDynamicRecord(
+ 0, s, s.hashLen,
+ sparseHashEncoder, sparseHashDecoder,
+ )
+}
+
+// hashLen is used by MakeDynamicRecord to return the size of the RHash.
+//
+// NOTE: for zero hash, we return a length 0.
+func (s *SparsePayHash) hashLen() uint64 {
+ if bytes.Equal(s[:], lntypes.ZeroHash[:]) {
+ return 0
+ }
+
+ return 32
+}
+
+// sparseHashEncoder is the customized encoder which skips encoding the empty
+// hash.
+func sparseHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
+ v, ok := val.(*SparsePayHash)
+ if !ok {
+ return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
+ }
+
+ // If the value is an empty hash, we will skip encoding it.
+ if bytes.Equal(v[:], lntypes.ZeroHash[:]) {
+ return nil
+ }
+
+ vArray := (*[32]byte)(v)
+
+ return tlv.EBytes32(w, vArray, buf)
+}
+
+// sparseHashDecoder is the customized decoder which skips decoding the empty
+// hash.
+func sparseHashDecoder(r io.Reader, val interface{}, buf *[8]byte,
+ l uint64) error {
+
+ v, ok := val.(*SparsePayHash)
+ if !ok {
+ return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
+ }
+
+ // If the length is zero, we will skip encoding the empty hash.
+ if l == 0 {
+ return nil
+ }
+
+ vArray := (*[32]byte)(v)
+
+ return tlv.DBytes32(r, vArray, buf, 32)
+}
+
+// toTlvStream converts an HTLCEntry record into a tlv representation.
+func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
records := []tlv.Record{
h.RHash.Record(),
h.RefundTimeout.Record(),
@@ -84,7 +255,7 @@ func SerializeRevocationLog(w io.Writer, rl *RevocationLog) error {
func SerializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
for _, htlc := range htlcs {
// Create the tlv stream.
- tlvStream, err := htlcEntryToTlvStream(htlc)
+ tlvStream, err := htlc.toTlvStream()
if err != nil {
return err
}
@@ -305,3 +476,10 @@ func ReadTlvStream(r io.Reader, s *tlv.Stream) (tlv.TypeMap, error) {
return s.DecodeWithParsedTypes(lr)
}
+
+// revocationLogKey converts a uint64 into an 8 byte revocation log key.
+func revocationLogKey(updateNum uint64) [8]byte {
+ var key [8]byte
+ byteOrder.PutUint64(key[:], updateNum)
+ return key
+}
### chanstate/kv_shutdown.go
@@ -0,0 +1,76 @@
+package chanstate
+
+import (
+ "bytes"
+ "io"
+
+ "github.com/lightningnetwork/lnd/kvdb"
+ "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+var (
+ // shutdownInfoKey points to the serialised shutdown info that has been
+ // persisted for a channel. The existence of this info means that we
+ // have sent the Shutdown message before and so should re-initiate the
+ // shutdown on re-establish.
+ shutdownInfoKey = []byte("shutdown-info-key")
+)
+
+// PutChannelShutdownInfo persists the ShutdownInfo in the target channel
+// bucket.
+func PutChannelShutdownInfo(chanBucket kvdb.RwBucket,
+ info *ShutdownInfo) error {
+
+ var b bytes.Buffer
+ err := EncodeShutdownInfo(info, &b)
+ if err != nil {
+ return err
+ }
+
+ return chanBucket.Put(shutdownInfoKey, b.Bytes())
+}
+
+// FetchChannelShutdownInfo fetches the persisted ShutdownInfo from the target
+// channel bucket.
+func FetchChannelShutdownInfo(chanBucket kvdb.RBucket) (
+ *ShutdownInfo, error) {
+
+ shutdownInfoBytes := chanBucket.Get(shutdownInfoKey)
+ if shutdownInfoBytes == nil {
+ return nil, ErrNoShutdownInfo
+ }
+
+ return DecodeShutdownInfo(shutdownInfoBytes)
+}
+
+// EncodeShutdownInfo serialises the ShutdownInfo to the given io.Writer.
+func EncodeShutdownInfo(s *ShutdownInfo, w io.Writer) error {
+ records := []tlv.Record{
+ s.DeliveryScript.Record(),
+ s.LocalInitiator.Record(),
+ }
+
+ stream, err := tlv.NewStream(records...)
+ if err != nil {
+ return err
+ }
+
+ return stream.Encode(w)
+}
+
+// DecodeShutdownInfo constructs a ShutdownInfo struct by decoding the given
+// byte slice.
+func DecodeShutdownInfo(b []byte) (*ShutdownInfo, error) {
+ tlvStream := lnwire.ExtraOpaqueData(b)
+
+ var info ShutdownInfo
+ records := []tlv.RecordProducer{
+ &info.DeliveryScript,
+ &info.LocalInitiator,
+ }
+
+ _, err := tlvStream.ExtractRecords(records...)
+
+ return &info, err
+}
### chanstate/kv_thaw_height.go
@@ -0,0 +1,44 @@
+package chanstate
+
+import (
+ "bytes"
+ "encoding/binary"
+
+ "github.com/lightningnetwork/lnd/kvdb"
+)
+
+var (
+ // frozenChanKey is the key where we store the information for any
+ // active "frozen" channels. This key is present only in the leaf
+ // bucket for a given channel.
+ frozenChanKey = []byte("frozen-chans")
+)
+
+// FetchThawHeight fetches a channel's thaw height from the channel bucket.
+func FetchThawHeight(chanBucket kvdb.RBucket) (uint32, error) {
+ var height uint32
+
+ heightBytes := chanBucket.Get(frozenChanKey)
+ heightReader := bytes.NewReader(heightBytes)
+
+ if err := binary.Read(heightReader, byteOrder, &height); err != nil {
+ return 0, err
+ }
+
+ return height, nil
+}
+
+// StoreThawHeight stores a channel's thaw height in the channel bucket.
+func StoreThawHeight(chanBucket kvdb.RwBucket, height uint32) error {
+ var heightBuf bytes.Buffer
+ if err := binary.Write(&heightBuf, byteOrder, height); err != nil {
+ return err
+ }
+
+ return chanBucket.Put(frozenChanKey, heightBuf.Bytes())
+}
+
+// DeleteThawHeight deletes a channel's thaw height from the channel bucket.
+func DeleteThawHeight(chanBucket kvdb.RwBucket) error {
+ return chanBucket.Delete(frozenChanKey)
+}
### chanstate/open_channel.go
@@ -347,6 +347,8 @@ func (c *OpenChannel) ChannelStatusForStore() ChannelStatus {
// SetChannelStatusForStore updates the in-memory channel status without taking
// the channel mutex.
//
+// TODO(chanstate): remove together with the other ForStore accessors.
+//
// NOTE: This is a preliminary migration hook for KV-backed store code that
// still lives in channeldb during this refactor. Callers are responsible for
// synchronization. Normal callers should use ApplyChanStatus or
@@ -396,6 +398,8 @@ func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool {
// HasChanStatusForStore returns true if the internal bitfield channel status
// has the specified status bit set, without taking the channel mutex.
//
+// TODO(chanstate): remove together with the other ForStore accessors.
+//
// NOTE: This is a preliminary migration hook for KV-backed store code that
// still lives in channeldb during this refactor. Callers are responsible for
// synchronization. Normal callers should use HasChanStatus.
@@ -406,6 +410,8 @@ func (c *OpenChannel) HasChanStatusForStore(status ChannelStatus) bool {
// ConfirmedScidForStore returns the in-memory confirmed SCID without taking
// the channel mutex.
//
+// TODO(chanstate): remove together with the other ForStore accessors.
+//
// NOTE: This is a preliminary migration hook for KV-backed store code that
// still lives in channeldb during this refactor. Callers are responsible for
// synchronization. Normal callers should use ZeroConfRealScid.
@@ -416,6 +422,8 @@ func (c *OpenChannel) ConfirmedScidForStore() lnwire.ShortChannelID {
// SetConfirmedScidForStore updates the in-memory confirmed SCID without taking
// the channel mutex.
//
+// TODO(chanstate): remove together with the other ForStore accessors.
+//
// NOTE: This is a preliminary migration hook for KV-backed store code that
// still lives in channeldb during this refactor. Callers are responsible for
// synchronization.
@@ -431,6 +439,20 @@ func (c *OpenChannel) BroadcastHeight() uint32 {
return c.FundingBroadcastHeight
}
+// fundingTxPresent returns true if we expect the funding transaction to be
+// found on disk or already populated within the channel.
+//
+// NOTE: this reads channel state without holding the lock, matching the
+// other ForStore accessors it calls. Callers are responsible for
+// synchronization.
+func (c *OpenChannel) fundingTxPresent() bool {
+ chanType := c.ChanType
+
+ return chanType.IsSingleFunder() && chanType.HasFundingTx() &&
+ c.IsInitiator &&
+ !c.HasChanStatusForStore(ChanStatusRestored)
+}
+
// SetBroadcastHeight sets the FundingBroadcastHeight.
func (c *OpenChannel) SetBroadcastHeight(height uint32) {
c.Lock()
@@ -1066,7 +1088,7 @@ func (c *OpenChannel) FindPreviousState(
// channel entails persisting a record of the close while either purging the
// nested per-channel state inline (synchronous backends like bbolt and etcd)
// or skipping the cascading delete on tombstone-enabled backends, where the
-// outpoint-index flip to outpointClosed is the authoritative marker. The
+// outpoint-index flip to OutpointClosed is the authoritative marker. The
// compact summary written to closedChannelBucket and the historical record
// under historicalChannelBucket are populated identically across both paths,
// so historical reads remain uniform regardless of backend. The optional set
### chanstate/revocation_log.go
@@ -1,13 +1,10 @@
package chanstate
import (
- "bytes"
- "io"
"math"
"github.com/btcsuite/btcd/btcutil/v2"
"github.com/lightningnetwork/lnd/fn/v2"
- "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
@@ -35,65 +32,6 @@ func NewSparsePayHash(rHash [32]byte) SparsePayHash {
return SparsePayHash(rHash)
}
-// Record returns a tlv record for the SparsePayHash.
-func (s *SparsePayHash) Record() tlv.Record {
- // We use a zero for the type here, as this'll be used along with the
- // RecordT type.
- return tlv.MakeDynamicRecord(
- 0, s, s.hashLen,
- sparseHashEncoder, sparseHashDecoder,
- )
-}
-
-// hashLen is used by MakeDynamicRecord to return the size of the RHash.
-//
-// NOTE: for zero hash, we return a length 0.
-func (s *SparsePayHash) hashLen() uint64 {
- if bytes.Equal(s[:], lntypes.ZeroHash[:]) {
- return 0
- }
-
- return 32
-}
-
-// sparseHashEncoder is the customized encoder which skips encoding the empty
-// hash.
-func sparseHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
- v, ok := val.(*SparsePayHash)
- if !ok {
- return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
- }
-
- // If the value is an empty hash, we will skip encoding it.
- if bytes.Equal(v[:], lntypes.ZeroHash[:]) {
- return nil
- }
-
- vArray := (*[32]byte)(v)
-
- return tlv.EBytes32(w, vArray, buf)
-}
-
-// sparseHashDecoder is the customized decoder which skips decoding the empty
-// hash.
-func sparseHashDecoder(r io.Reader, val interface{}, buf *[8]byte,
- l uint64) error {
-
- v, ok := val.(*SparsePayHash)
- if !ok {
- return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
- }
-
- // If the length is zero, we will skip encoding the empty hash.
- if l == 0 {
- return nil
- }
-
- vArray := (*[32]byte)(v)
-
- return tlv.DBytes32(r, vArray, buf, 32)
-}
-
// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the
// historical HTLCs, which is useful for constructing RevocationLog when a
// breach is detected.Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.