Merge pull request #9030 from lightningnetwork/extract-part1-from-staging-branch
[custom channels 1/5]: extract PART1 from mega staging branch
This commit is contained in:
43 files changed
+2457
-785
No files matched your search
@@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
|
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
@@ -63,6 +64,10 @@ type Config struct {
|
|||||||
// state.
|
// state.
|
||||||
ChanStateDB *channeldb.ChannelStateDB
|
ChanStateDB *channeldb.ChannelStateDB
|
||||||
|
|
||||||
|
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||||
|
// leaves for certain custom channel types.
|
||||||
|
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||||
|
|
||||||
// BlockCache is the main cache for storing block information.
|
// BlockCache is the main cache for storing block information.
|
||||||
BlockCache *blockcache.BlockCache
|
BlockCache *blockcache.BlockCache
|
||||||
|
|
||||||
|
|||||||
+355
-103
@@ -226,28 +226,109 @@ const (
|
|||||||
// A tlv type definition used to serialize an outpoint's indexStatus
|
// A tlv type definition used to serialize an outpoint's indexStatus
|
||||||
// for use in the outpoint index.
|
// for use in the outpoint index.
|
||||||
indexStatusType tlv.Type = 0
|
indexStatusType tlv.Type = 0
|
||||||
|
|
||||||
// A tlv type definition used to serialize and deserialize a KeyLocator
|
|
||||||
// from the database.
|
|
||||||
keyLocType tlv.Type = 1
|
|
||||||
|
|
||||||
// A tlv type used to serialize and deserialize the
|
|
||||||
// `InitialLocalBalance` field.
|
|
||||||
initialLocalBalanceType tlv.Type = 2
|
|
||||||
|
|
||||||
// A tlv type used to serialize and deserialize the
|
|
||||||
// `InitialRemoteBalance` field.
|
|
||||||
initialRemoteBalanceType tlv.Type = 3
|
|
||||||
|
|
||||||
// A tlv type definition used to serialize and deserialize the
|
|
||||||
// confirmed ShortChannelID for a zero-conf channel.
|
|
||||||
realScidType tlv.Type = 4
|
|
||||||
|
|
||||||
// A tlv type definition used to serialize and deserialize the
|
|
||||||
// Memo for the channel channel.
|
|
||||||
channelMemoType tlv.Type = 5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.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())
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
// 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(),
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// indexStatus is an enum-like type that describes what state the
|
// indexStatus is an enum-like type that describes what state the
|
||||||
// outpoint is in. Currently only two possible values.
|
// outpoint is in. Currently only two possible values.
|
||||||
type indexStatus uint8
|
type indexStatus uint8
|
||||||
@@ -325,6 +406,11 @@ const (
|
|||||||
// SimpleTaprootFeatureBit indicates that the simple-taproot-chans
|
// SimpleTaprootFeatureBit indicates that the simple-taproot-chans
|
||||||
// feature bit was negotiated during the lifetime of the channel.
|
// feature bit was negotiated during the lifetime of the channel.
|
||||||
SimpleTaprootFeatureBit ChannelType = 1 << 10
|
SimpleTaprootFeatureBit ChannelType = 1 << 10
|
||||||
|
|
||||||
|
// TapscriptRootBit indicates that this is a MuSig2 channel with a top
|
||||||
|
// level tapscript commitment. This MUST be set along with the
|
||||||
|
// SimpleTaprootFeatureBit.
|
||||||
|
TapscriptRootBit ChannelType = 1 << 11
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsSingleFunder returns true if the channel type if one of the known single
|
// IsSingleFunder returns true if the channel type if one of the known single
|
||||||
@@ -395,6 +481,12 @@ func (c ChannelType) IsTaproot() bool {
|
|||||||
return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit
|
return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasTapscriptRoot returns true if the channel is using a top level tapscript
|
||||||
|
// root commitment.
|
||||||
|
func (c ChannelType) HasTapscriptRoot() bool {
|
||||||
|
return c&TapscriptRootBit == TapscriptRootBit
|
||||||
|
}
|
||||||
|
|
||||||
// ChannelStateBounds are the parameters from OpenChannel and AcceptChannel
|
// ChannelStateBounds are the parameters from OpenChannel and AcceptChannel
|
||||||
// that are responsible for providing bounds on the state space of the abstract
|
// that are responsible for providing bounds on the state space of the abstract
|
||||||
// channel state. These values must be remembered for normal channel operation
|
// channel state. These values must be remembered for normal channel operation
|
||||||
@@ -496,6 +588,53 @@ type ChannelConfig struct {
|
|||||||
HtlcBasePoint keychain.KeyDescriptor
|
HtlcBasePoint keychain.KeyDescriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
// ChannelCommitment is a snapshot of the commitment state at a particular
|
// ChannelCommitment is a snapshot of the commitment state at a particular
|
||||||
// point in the commitment chain. With each state transition, a snapshot of the
|
// point in the commitment chain. With each state transition, a snapshot of the
|
||||||
// current state along with all non-settled HTLCs are recorded. These snapshots
|
// current state along with all non-settled HTLCs are recorded. These snapshots
|
||||||
@@ -562,6 +701,11 @@ type ChannelCommitment struct {
|
|||||||
// able by us.
|
// able by us.
|
||||||
CommitTx *wire.MsgTx
|
CommitTx *wire.MsgTx
|
||||||
|
|
||||||
|
// CustomBlob is an optional blob that can be used to store information
|
||||||
|
// specific to a custom channel type. This may track some custom
|
||||||
|
// specific state for this given commitment.
|
||||||
|
CustomBlob fn.Option[tlv.Blob]
|
||||||
|
|
||||||
// CommitSig is one half of the signature required to fully complete
|
// CommitSig is one half of the signature required to fully complete
|
||||||
// the script for the commitment transaction above. This is the
|
// the script for the commitment transaction above. This is the
|
||||||
// signature signed by the remote party for our version of the
|
// signature signed by the remote party for our version of the
|
||||||
@@ -571,9 +715,26 @@ type ChannelCommitment struct {
|
|||||||
// Htlcs is the set of HTLC's that are pending at this particular
|
// Htlcs is the set of HTLC's that are pending at this particular
|
||||||
// commitment height.
|
// commitment height.
|
||||||
Htlcs []HTLC
|
Htlcs []HTLC
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): pending commit pointer?
|
// amendTlvData updates the channel with the given auxiliary TLV data.
|
||||||
// * lets just walk through
|
func (c *ChannelCommitment) amendTlvData(auxData commitTlvData) {
|
||||||
|
auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
|
||||||
|
c.CustomBlob = fn.Some(blob)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTlvData creates a new commitTlvData from the given commitment.
|
||||||
|
func (c *ChannelCommitment) extractTlvData() 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
|
// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in
|
||||||
@@ -867,6 +1028,16 @@ type OpenChannel struct {
|
|||||||
// channel that will be useful to our future selves.
|
// channel that will be useful to our future selves.
|
||||||
Memo []byte
|
Memo []byte
|
||||||
|
|
||||||
|
// TapscriptRoot is an optional tapscript root used to derive the MuSig2
|
||||||
|
// funding output.
|
||||||
|
TapscriptRoot fn.Option[chainhash.Hash]
|
||||||
|
|
||||||
|
// CustomBlob is an optional blob that can be used to store information
|
||||||
|
// specific to a custom channel type. This information is only created
|
||||||
|
// at channel funding time, and after wards is to be considered
|
||||||
|
// immutable.
|
||||||
|
CustomBlob fn.Option[tlv.Blob]
|
||||||
|
|
||||||
// TODO(roasbeef): eww
|
// TODO(roasbeef): eww
|
||||||
Db *ChannelStateDB
|
Db *ChannelStateDB
|
||||||
|
|
||||||
@@ -1025,6 +1196,64 @@ func (c *OpenChannel) SetBroadcastHeight(height uint32) {
|
|||||||
c.FundingBroadcastHeight = height
|
c.FundingBroadcastHeight = height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// amendTlvData updates the channel with the given auxiliary TLV data.
|
||||||
|
func (c *OpenChannel) amendTlvData(auxData openChannelTlvData) {
|
||||||
|
c.RevocationKeyLocator = auxData.revokeKeyLoc.Val.KeyLocator
|
||||||
|
c.InitialLocalBalance = lnwire.MilliSatoshi(
|
||||||
|
auxData.initialLocalBalance.Val,
|
||||||
|
)
|
||||||
|
c.InitialRemoteBalance = lnwire.MilliSatoshi(
|
||||||
|
auxData.initialRemoteBalance.Val,
|
||||||
|
)
|
||||||
|
c.confirmedScid = auxData.realScid.Val
|
||||||
|
|
||||||
|
auxData.memo.WhenSomeV(func(memo []byte) {
|
||||||
|
c.Memo = memo
|
||||||
|
})
|
||||||
|
auxData.tapscriptRoot.WhenSomeV(func(h [32]byte) {
|
||||||
|
c.TapscriptRoot = fn.Some[chainhash.Hash](h)
|
||||||
|
})
|
||||||
|
auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
|
||||||
|
c.CustomBlob = fn.Some(blob)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTlvData creates a new openChannelTlvData from the given channel.
|
||||||
|
func (c *OpenChannel) extractTlvData() openChannelTlvData {
|
||||||
|
auxData := openChannelTlvData{
|
||||||
|
revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1](
|
||||||
|
keyLocRecord{c.RevocationKeyLocator},
|
||||||
|
),
|
||||||
|
initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2](
|
||||||
|
uint64(c.InitialLocalBalance),
|
||||||
|
),
|
||||||
|
initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3](
|
||||||
|
uint64(c.InitialRemoteBalance),
|
||||||
|
),
|
||||||
|
realScid: tlv.NewRecordT[tlv.TlvType4](
|
||||||
|
c.confirmedScid,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Memo) != 0 {
|
||||||
|
auxData.memo = tlv.SomeRecordT(
|
||||||
|
tlv.NewPrimitiveRecord[tlv.TlvType5](c.Memo),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
c.TapscriptRoot.WhenSome(func(h chainhash.Hash) {
|
||||||
|
auxData.tapscriptRoot = tlv.SomeRecordT(
|
||||||
|
tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
c.CustomBlob.WhenSome(func(blob tlv.Blob) {
|
||||||
|
auxData.customBlob = tlv.SomeRecordT(
|
||||||
|
tlv.NewPrimitiveRecord[tlv.TlvType7](blob),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return auxData
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh updates the in-memory channel state using the latest state observed
|
// Refresh updates the in-memory channel state using the latest state observed
|
||||||
// on disk.
|
// on disk.
|
||||||
func (c *OpenChannel) Refresh() error {
|
func (c *OpenChannel) Refresh() error {
|
||||||
@@ -2351,6 +2580,12 @@ type HTLC struct {
|
|||||||
// HTLC. It is stored in the ExtraData field, which is used to store
|
// HTLC. It is stored in the ExtraData field, which is used to store
|
||||||
// a TLV stream of additional information associated with the HTLC.
|
// a TLV stream of additional information associated with the HTLC.
|
||||||
BlindingPoint lnwire.BlindingPointRecord
|
BlindingPoint lnwire.BlindingPointRecord
|
||||||
|
|
||||||
|
// CustomRecords is a set of custom TLV records that are associated with
|
||||||
|
// this HTLC. These records are used to store additional information
|
||||||
|
// about the HTLC that is not part of the standard HTLC fields. This
|
||||||
|
// field is encoded within the ExtraData field.
|
||||||
|
CustomRecords lnwire.CustomRecords
|
||||||
}
|
}
|
||||||
|
|
||||||
// serializeExtraData encodes a TLV stream of extra data to be stored with a
|
// serializeExtraData encodes a TLV stream of extra data to be stored with a
|
||||||
@@ -2369,6 +2604,11 @@ func (h *HTLC) serializeExtraData() error {
|
|||||||
records = append(records, &b)
|
records = append(records, &b)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
records, err := h.CustomRecords.ExtendRecordProducers(records)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return h.ExtraData.PackRecords(records...)
|
return h.ExtraData.PackRecords(records...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2390,8 +2630,19 @@ func (h *HTLC) deserializeExtraData() error {
|
|||||||
|
|
||||||
if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil {
|
if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil {
|
||||||
h.BlindingPoint = tlv.SomeRecordT(blindingPoint)
|
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 nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2529,6 +2780,8 @@ func (h *HTLC) Copy() HTLC {
|
|||||||
copy(clone.Signature[:], h.Signature)
|
copy(clone.Signature[:], h.Signature)
|
||||||
copy(clone.RHash[:], h.RHash[:])
|
copy(clone.RHash[:], h.RHash[:])
|
||||||
copy(clone.ExtraData, h.ExtraData)
|
copy(clone.ExtraData, h.ExtraData)
|
||||||
|
clone.BlindingPoint = h.BlindingPoint
|
||||||
|
clone.CustomRecords = h.CustomRecords.Copy()
|
||||||
|
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
@@ -2690,6 +2943,14 @@ func serializeCommitDiff(w io.Writer, diff *CommitDiff) error { // nolint: dupl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 := diff.Commitment.extractTlvData()
|
||||||
|
if err := auxData.encode(w); err != nil {
|
||||||
|
return fmt.Errorf("unable to write aux data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2750,6 +3011,17 @@ func deserializeCommitDiff(r io.Reader) (*CommitDiff, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Commitment.amendTlvData(auxData)
|
||||||
|
|
||||||
return &d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3728,6 +4000,13 @@ func (c *OpenChannel) Snapshot() *ChannelSnapshot {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localCommit.CustomBlob.WhenSome(func(blob tlv.Blob) {
|
||||||
|
blobCopy := make([]byte, len(blob))
|
||||||
|
copy(blobCopy, blob)
|
||||||
|
|
||||||
|
snapshot.ChannelCommitment.CustomBlob = fn.Some(blobCopy)
|
||||||
|
})
|
||||||
|
|
||||||
// Copy over the current set of HTLCs to ensure the caller can't mutate
|
// Copy over the current set of HTLCs to ensure the caller can't mutate
|
||||||
// our internal state.
|
// our internal state.
|
||||||
snapshot.Htlcs = make([]HTLC, len(localCommit.Htlcs))
|
snapshot.Htlcs = make([]HTLC, len(localCommit.Htlcs))
|
||||||
@@ -4030,32 +4309,9 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert balance fields into uint64.
|
auxData := channel.extractTlvData()
|
||||||
localBalance := uint64(channel.InitialLocalBalance)
|
if err := auxData.encode(&w); err != nil {
|
||||||
remoteBalance := uint64(channel.InitialRemoteBalance)
|
return fmt.Errorf("unable to encode aux data: %w", err)
|
||||||
|
|
||||||
// Create the tlv stream.
|
|
||||||
tlvStream, err := tlv.NewStream(
|
|
||||||
// Write the RevocationKeyLocator as the first entry in a tlv
|
|
||||||
// stream.
|
|
||||||
MakeKeyLocRecord(
|
|
||||||
keyLocType, &channel.RevocationKeyLocator,
|
|
||||||
),
|
|
||||||
tlv.MakePrimitiveRecord(
|
|
||||||
initialLocalBalanceType, &localBalance,
|
|
||||||
),
|
|
||||||
tlv.MakePrimitiveRecord(
|
|
||||||
initialRemoteBalanceType, &remoteBalance,
|
|
||||||
),
|
|
||||||
MakeScidRecord(realScidType, &channel.confirmedScid),
|
|
||||||
tlv.MakePrimitiveRecord(channelMemoType, &channel.Memo),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tlvStream.Encode(&w); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
|
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
|
||||||
@@ -4142,6 +4398,12 @@ func putChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Before we write to disk, we'll also write our aux data as well.
|
||||||
|
auxData := c.extractTlvData()
|
||||||
|
if err := auxData.encode(&b); err != nil {
|
||||||
|
return fmt.Errorf("unable to write aux data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return chanBucket.Put(commitKey, b.Bytes())
|
return chanBucket.Put(commitKey, b.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4244,45 +4506,14 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create balance fields in uint64, and Memo field as byte slice.
|
var auxData openChannelTlvData
|
||||||
var (
|
if err := auxData.decode(r); err != nil {
|
||||||
localBalance uint64
|
return fmt.Errorf("unable to decode aux data: %w", err)
|
||||||
remoteBalance uint64
|
|
||||||
memo []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create the tlv stream.
|
|
||||||
tlvStream, err := tlv.NewStream(
|
|
||||||
// Write the RevocationKeyLocator as the first entry in a tlv
|
|
||||||
// stream.
|
|
||||||
MakeKeyLocRecord(
|
|
||||||
keyLocType, &channel.RevocationKeyLocator,
|
|
||||||
),
|
|
||||||
tlv.MakePrimitiveRecord(
|
|
||||||
initialLocalBalanceType, &localBalance,
|
|
||||||
),
|
|
||||||
tlv.MakePrimitiveRecord(
|
|
||||||
initialRemoteBalanceType, &remoteBalance,
|
|
||||||
),
|
|
||||||
MakeScidRecord(realScidType, &channel.confirmedScid),
|
|
||||||
tlv.MakePrimitiveRecord(channelMemoType, &memo),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tlvStream.Decode(r); err != nil {
|
// Assign all the relevant fields from the aux data into the actual
|
||||||
return err
|
// open channel.
|
||||||
}
|
channel.amendTlvData(auxData)
|
||||||
|
|
||||||
// Attach the balance fields.
|
|
||||||
channel.InitialLocalBalance = lnwire.MilliSatoshi(localBalance)
|
|
||||||
channel.InitialRemoteBalance = lnwire.MilliSatoshi(remoteBalance)
|
|
||||||
|
|
||||||
// Attach the memo field if non-empty.
|
|
||||||
if len(memo) > 0 {
|
|
||||||
channel.Memo = memo
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.Packager = NewChannelPackager(channel.ShortChannelID)
|
channel.Packager = NewChannelPackager(channel.ShortChannelID)
|
||||||
|
|
||||||
@@ -4318,7 +4549,9 @@ func deserializeChanCommit(r io.Reader) (ChannelCommitment, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchChanCommitment(chanBucket kvdb.RBucket, local bool) (ChannelCommitment, error) {
|
func fetchChanCommitment(chanBucket kvdb.RBucket,
|
||||||
|
local bool) (ChannelCommitment, error) {
|
||||||
|
|
||||||
var commitKey []byte
|
var commitKey []byte
|
||||||
if local {
|
if local {
|
||||||
commitKey = append(chanCommitmentKey, byte(0x00))
|
commitKey = append(chanCommitmentKey, byte(0x00))
|
||||||
@@ -4332,7 +4565,23 @@ func fetchChanCommitment(chanBucket kvdb.RBucket, local bool) (ChannelCommitment
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := bytes.NewReader(commitBytes)
|
r := bytes.NewReader(commitBytes)
|
||||||
return deserializeChanCommit(r)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
chanCommit.amendTlvData(auxData)
|
||||||
|
|
||||||
|
return chanCommit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
func fetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
||||||
@@ -4440,6 +4689,25 @@ func deleteThawHeight(chanBucket kvdb.RwBucket) error {
|
|||||||
return chanBucket.Delete(frozenChanKey)
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// EKeyLocator is an encoder for keychain.KeyLocator.
|
// EKeyLocator is an encoder for keychain.KeyLocator.
|
||||||
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
|
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||||
if v, ok := val.(*keychain.KeyLocator); ok {
|
if v, ok := val.(*keychain.KeyLocator); ok {
|
||||||
@@ -4468,22 +4736,6 @@ func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
|||||||
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
|
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed
|
|
||||||
// Type and the EKeyLocator and DKeyLocator functions. The size will always be
|
|
||||||
// 8 as KeyFamily is uint32 and the Index is uint32.
|
|
||||||
func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
|
|
||||||
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeScidRecord creates a Record out of a ShortChannelID using the passed
|
|
||||||
// Type and the EShortChannelID and DShortChannelID functions. The size will
|
|
||||||
// always be 8 for the ShortChannelID.
|
|
||||||
func MakeScidRecord(typ tlv.Type, scid *lnwire.ShortChannelID) tlv.Record {
|
|
||||||
return tlv.MakeStaticRecord(
|
|
||||||
typ, scid, 8, lnwire.EShortChannelID, lnwire.DShortChannelID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShutdownInfo contains various info about the shutdown initiation of a
|
// ShutdownInfo contains various info about the shutdown initiation of a
|
||||||
// channel.
|
// channel.
|
||||||
type ShutdownInfo struct {
|
type ShutdownInfo struct {
|
||||||
|
|||||||
+58
-16
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
"github.com/lightningnetwork/lnd/clock"
|
"github.com/lightningnetwork/lnd/clock"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lnmock"
|
"github.com/lightningnetwork/lnd/lnmock"
|
||||||
@@ -173,7 +174,7 @@ func fundingPointOption(chanPoint wire.OutPoint) testChannelOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// channelIDOption is an option which sets the short channel ID of the channel.
|
// channelIDOption is an option which sets the short channel ID of the channel.
|
||||||
var channelIDOption = func(chanID lnwire.ShortChannelID) testChannelOption {
|
func channelIDOption(chanID lnwire.ShortChannelID) testChannelOption {
|
||||||
return func(params *testChannelParams) {
|
return func(params *testChannelParams) {
|
||||||
params.channel.ShortChannelID = chanID
|
params.channel.ShortChannelID = chanID
|
||||||
}
|
}
|
||||||
@@ -326,6 +327,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
|||||||
uniqueOutputIndex.Add(1)
|
uniqueOutputIndex.Add(1)
|
||||||
op := wire.OutPoint{Hash: key, Index: uniqueOutputIndex.Load()}
|
op := wire.OutPoint{Hash: key, Index: uniqueOutputIndex.Load()}
|
||||||
|
|
||||||
|
var tapscriptRoot chainhash.Hash
|
||||||
|
copy(tapscriptRoot[:], bytes.Repeat([]byte{1}, 32))
|
||||||
|
|
||||||
return &OpenChannel{
|
return &OpenChannel{
|
||||||
ChanType: SingleFunderBit | FrozenBit,
|
ChanType: SingleFunderBit | FrozenBit,
|
||||||
ChainHash: key,
|
ChainHash: key,
|
||||||
@@ -347,6 +351,7 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
|||||||
FeePerKw: btcutil.Amount(5000),
|
FeePerKw: btcutil.Amount(5000),
|
||||||
CommitTx: channels.TestFundingTx,
|
CommitTx: channels.TestFundingTx,
|
||||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||||
|
CustomBlob: fn.Some([]byte{1, 2, 3}),
|
||||||
},
|
},
|
||||||
RemoteCommitment: ChannelCommitment{
|
RemoteCommitment: ChannelCommitment{
|
||||||
CommitHeight: 0,
|
CommitHeight: 0,
|
||||||
@@ -356,6 +361,7 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
|||||||
FeePerKw: btcutil.Amount(5000),
|
FeePerKw: btcutil.Amount(5000),
|
||||||
CommitTx: channels.TestFundingTx,
|
CommitTx: channels.TestFundingTx,
|
||||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||||
|
CustomBlob: fn.Some([]byte{4, 5, 6}),
|
||||||
},
|
},
|
||||||
NumConfsRequired: 4,
|
NumConfsRequired: 4,
|
||||||
RemoteCurrentRevocation: privKey.PubKey(),
|
RemoteCurrentRevocation: privKey.PubKey(),
|
||||||
@@ -368,6 +374,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
|||||||
ThawHeight: uint32(defaultPendingHeight),
|
ThawHeight: uint32(defaultPendingHeight),
|
||||||
InitialLocalBalance: lnwire.MilliSatoshi(9000),
|
InitialLocalBalance: lnwire.MilliSatoshi(9000),
|
||||||
InitialRemoteBalance: lnwire.MilliSatoshi(3000),
|
InitialRemoteBalance: lnwire.MilliSatoshi(3000),
|
||||||
|
Memo: []byte("test"),
|
||||||
|
TapscriptRoot: fn.Some(tapscriptRoot),
|
||||||
|
CustomBlob: fn.Some([]byte{1, 2, 3}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,24 +584,32 @@ func assertCommitmentEqual(t *testing.T, a, b *ChannelCommitment) {
|
|||||||
func assertRevocationLogEntryEqual(t *testing.T, c *ChannelCommitment,
|
func assertRevocationLogEntryEqual(t *testing.T, c *ChannelCommitment,
|
||||||
r *RevocationLog) {
|
r *RevocationLog) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
// Check the common fields.
|
// Check the common fields.
|
||||||
require.EqualValues(
|
require.EqualValues(
|
||||||
t, r.CommitTxHash, c.CommitTx.TxHash(), "CommitTx mismatch",
|
t, r.CommitTxHash.Val, c.CommitTx.TxHash(), "CommitTx mismatch",
|
||||||
)
|
)
|
||||||
|
|
||||||
// Now check the common fields from the HTLCs.
|
// Now check the common fields from the HTLCs.
|
||||||
require.Equal(t, len(r.HTLCEntries), len(c.Htlcs), "HTLCs len mismatch")
|
require.Equal(t, len(r.HTLCEntries), len(c.Htlcs), "HTLCs len mismatch")
|
||||||
for i, rHtlc := range r.HTLCEntries {
|
for i, rHtlc := range r.HTLCEntries {
|
||||||
cHtlc := c.Htlcs[i]
|
cHtlc := c.Htlcs[i]
|
||||||
require.Equal(t, rHtlc.RHash, cHtlc.RHash, "RHash mismatch")
|
require.Equal(t, rHtlc.RHash.Val[:], cHtlc.RHash[:], "RHash")
|
||||||
require.Equal(t, rHtlc.Amt, cHtlc.Amt.ToSatoshis(),
|
require.Equal(
|
||||||
"Amt mismatch")
|
t, rHtlc.Amt.Val.Int(), cHtlc.Amt.ToSatoshis(), "Amt",
|
||||||
require.Equal(t, rHtlc.RefundTimeout, cHtlc.RefundTimeout,
|
)
|
||||||
"RefundTimeout mismatch")
|
require.Equal(
|
||||||
require.EqualValues(t, rHtlc.OutputIndex, cHtlc.OutputIndex,
|
t, rHtlc.RefundTimeout.Val, cHtlc.RefundTimeout,
|
||||||
"OutputIndex mismatch")
|
"RefundTimeout",
|
||||||
require.Equal(t, rHtlc.Incoming, cHtlc.Incoming,
|
)
|
||||||
"Incoming mismatch")
|
require.EqualValues(
|
||||||
|
t, rHtlc.OutputIndex.Val, cHtlc.OutputIndex,
|
||||||
|
"OutputIndex",
|
||||||
|
)
|
||||||
|
require.Equal(
|
||||||
|
t, rHtlc.Incoming.Val, cHtlc.Incoming, "Incoming",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,6 +674,7 @@ func TestChannelStateTransition(t *testing.T) {
|
|||||||
CommitTx: newTx,
|
CommitTx: newTx,
|
||||||
CommitSig: newSig,
|
CommitSig: newSig,
|
||||||
Htlcs: htlcs,
|
Htlcs: htlcs,
|
||||||
|
CustomBlob: fn.Some([]byte{4, 5, 6}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// First update the local node's broadcastable state and also add a
|
// First update the local node's broadcastable state and also add a
|
||||||
@@ -694,9 +712,14 @@ func TestChannelStateTransition(t *testing.T) {
|
|||||||
// have been updated.
|
// have been updated.
|
||||||
updatedChannel, err := cdb.FetchOpenChannels(channel.IdentityPub)
|
updatedChannel, err := cdb.FetchOpenChannels(channel.IdentityPub)
|
||||||
require.NoError(t, err, "unable to fetch updated channel")
|
require.NoError(t, err, "unable to fetch updated channel")
|
||||||
assertCommitmentEqual(t, &commitment, &updatedChannel[0].LocalCommitment)
|
|
||||||
|
assertCommitmentEqual(
|
||||||
|
t, &commitment, &updatedChannel[0].LocalCommitment,
|
||||||
|
)
|
||||||
|
|
||||||
numDiskUpdates, err := updatedChannel[0].CommitmentHeight()
|
numDiskUpdates, err := updatedChannel[0].CommitmentHeight()
|
||||||
require.NoError(t, err, "unable to read commitment height from disk")
|
require.NoError(t, err, "unable to read commitment height from disk")
|
||||||
|
|
||||||
if numDiskUpdates != uint64(commitment.CommitHeight) {
|
if numDiskUpdates != uint64(commitment.CommitHeight) {
|
||||||
t.Fatalf("num disk updates doesn't match: %v vs %v",
|
t.Fatalf("num disk updates doesn't match: %v vs %v",
|
||||||
numDiskUpdates, commitment.CommitHeight)
|
numDiskUpdates, commitment.CommitHeight)
|
||||||
@@ -799,10 +822,10 @@ func TestChannelStateTransition(t *testing.T) {
|
|||||||
|
|
||||||
// Check the output indexes are saved as expected.
|
// Check the output indexes are saved as expected.
|
||||||
require.EqualValues(
|
require.EqualValues(
|
||||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex,
|
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex.Val,
|
||||||
)
|
)
|
||||||
require.EqualValues(
|
require.EqualValues(
|
||||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex,
|
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex.Val,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The two deltas (the original vs the on-disk version) should
|
// The two deltas (the original vs the on-disk version) should
|
||||||
@@ -844,10 +867,10 @@ func TestChannelStateTransition(t *testing.T) {
|
|||||||
|
|
||||||
// Check the output indexes are saved as expected.
|
// Check the output indexes are saved as expected.
|
||||||
require.EqualValues(
|
require.EqualValues(
|
||||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex,
|
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex.Val,
|
||||||
)
|
)
|
||||||
require.EqualValues(
|
require.EqualValues(
|
||||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex,
|
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex.Val,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertRevocationLogEntryEqual(t, &oldRemoteCommit, prevCommit)
|
assertRevocationLogEntryEqual(t, &oldRemoteCommit, prevCommit)
|
||||||
@@ -1642,6 +1665,24 @@ func TestHTLCsExtraData(t *testing.T) {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom channel data htlc with a blinding point.
|
||||||
|
customDataHTLC := HTLC{
|
||||||
|
Signature: testSig.Serialize(),
|
||||||
|
Incoming: false,
|
||||||
|
Amt: 10,
|
||||||
|
RHash: key,
|
||||||
|
RefundTimeout: 1,
|
||||||
|
OnionBlob: lnmock.MockOnion(),
|
||||||
|
BlindingPoint: tlv.SomeRecordT(
|
||||||
|
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
|
||||||
|
pubKey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomRecords: map[uint64][]byte{
|
||||||
|
uint64(lnwire.MinCustomRecordsTlvType + 3): {1, 2, 3},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
htlcs []HTLC
|
htlcs []HTLC
|
||||||
@@ -1663,6 +1704,7 @@ func TestHTLCsExtraData(t *testing.T) {
|
|||||||
mockHtlc,
|
mockHtlc,
|
||||||
blindingPointHTLC,
|
blindingPointHTLC,
|
||||||
mockHtlc,
|
mockHtlc,
|
||||||
|
customDataHTLC,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+272
-174
@@ -7,6 +7,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@@ -16,16 +17,15 @@ import (
|
|||||||
const (
|
const (
|
||||||
// OutputIndexEmpty is used when the output index doesn't exist.
|
// OutputIndexEmpty is used when the output index doesn't exist.
|
||||||
OutputIndexEmpty = math.MaxUint16
|
OutputIndexEmpty = math.MaxUint16
|
||||||
|
)
|
||||||
|
|
||||||
// A set of tlv type definitions used to serialize the body of
|
type (
|
||||||
// revocation logs to the database.
|
// BigSizeAmount is a type alias for a TLV record of a btcutil.Amount.
|
||||||
//
|
BigSizeAmount = tlv.BigSizeT[btcutil.Amount]
|
||||||
// NOTE: A migration should be added whenever this list changes.
|
|
||||||
revLogOurOutputIndexType tlv.Type = 0
|
// BigSizeMilliSatoshi is a type alias for a TLV record of a
|
||||||
revLogTheirOutputIndexType tlv.Type = 1
|
// lnwire.MilliSatoshi.
|
||||||
revLogCommitTxHashType tlv.Type = 2
|
BigSizeMilliSatoshi = tlv.BigSizeT[lnwire.MilliSatoshi]
|
||||||
revLogOurBalanceType tlv.Type = 3
|
|
||||||
revLogTheirBalanceType tlv.Type = 4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -54,6 +54,74 @@ var (
|
|||||||
ErrOutputIndexTooBig = errors.New("output index is over uint16")
|
ErrOutputIndexTooBig = errors.New("output index is over uint16")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SparsePayHash is a type alias for a 32 byte array, which when serialized is
|
||||||
|
// able to save some space by not including an empty payment hash on disk.
|
||||||
|
type SparsePayHash [32]byte
|
||||||
|
|
||||||
|
// NewSparsePayHash creates a new SparsePayHash from a 32 byte array.
|
||||||
|
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
|
// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the
|
||||||
// historical HTLCs, which is useful for constructing RevocationLog when a
|
// historical HTLCs, which is useful for constructing RevocationLog when a
|
||||||
// breach is detected.
|
// breach is detected.
|
||||||
@@ -72,116 +140,90 @@ var (
|
|||||||
// made into tlv records without further conversion.
|
// made into tlv records without further conversion.
|
||||||
type HTLCEntry struct {
|
type HTLCEntry struct {
|
||||||
// RHash is the payment hash of the HTLC.
|
// RHash is the payment hash of the HTLC.
|
||||||
RHash [32]byte
|
RHash tlv.RecordT[tlv.TlvType0, SparsePayHash]
|
||||||
|
|
||||||
// RefundTimeout is the absolute timeout on the HTLC that the sender
|
// RefundTimeout is the absolute timeout on the HTLC that the sender
|
||||||
// must wait before reclaiming the funds in limbo.
|
// must wait before reclaiming the funds in limbo.
|
||||||
RefundTimeout uint32
|
RefundTimeout tlv.RecordT[tlv.TlvType1, uint32]
|
||||||
|
|
||||||
// OutputIndex is the output index for this particular HTLC output
|
// OutputIndex is the output index for this particular HTLC output
|
||||||
// within the commitment transaction.
|
// within the commitment transaction.
|
||||||
//
|
//
|
||||||
// NOTE: we use uint16 instead of int32 here to save us 2 bytes, which
|
// NOTE: we use uint16 instead of int32 here to save us 2 bytes, which
|
||||||
// gives us a max number of HTLCs of 65K.
|
// gives us a max number of HTLCs of 65K.
|
||||||
OutputIndex uint16
|
OutputIndex tlv.RecordT[tlv.TlvType2, uint16]
|
||||||
|
|
||||||
// Incoming denotes whether we're the receiver or the sender of this
|
// Incoming denotes whether we're the receiver or the sender of this
|
||||||
// HTLC.
|
// HTLC.
|
||||||
//
|
Incoming tlv.RecordT[tlv.TlvType3, bool]
|
||||||
// NOTE: this field is the memory representation of the field
|
|
||||||
// incomingUint.
|
|
||||||
Incoming bool
|
|
||||||
|
|
||||||
// Amt is the amount of satoshis this HTLC escrows.
|
// Amt is the amount of satoshis this HTLC escrows.
|
||||||
//
|
Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]]
|
||||||
// NOTE: this field is the memory representation of the field amtUint.
|
|
||||||
Amt btcutil.Amount
|
|
||||||
|
|
||||||
// amtTlv is the uint64 format of Amt. This field is created so we can
|
// CustomBlob is an optional blob that can be used to store information
|
||||||
// easily make it into a tlv record and save it to disk.
|
// specific to revocation handling for a custom channel type.
|
||||||
//
|
CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
|
||||||
// NOTE: we keep this field for accounting purpose only. If the disk
|
|
||||||
// space becomes an issue, we could delete this field to save us extra
|
|
||||||
// 8 bytes.
|
|
||||||
amtTlv uint64
|
|
||||||
|
|
||||||
// incomingTlv is the uint8 format of Incoming. This field is created
|
// HtlcIndex is the index of the HTLC in the channel.
|
||||||
// so we can easily make it into a tlv record and save it to disk.
|
HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, uint16]
|
||||||
incomingTlv uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// RHashLen is used by MakeDynamicRecord to return the size of the RHash.
|
|
||||||
//
|
|
||||||
// NOTE: for zero hash, we return a length 0.
|
|
||||||
func (h *HTLCEntry) RHashLen() uint64 {
|
|
||||||
if h.RHash == lntypes.ZeroHash {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 32
|
|
||||||
}
|
|
||||||
|
|
||||||
// RHashEncoder is the customized encoder which skips encoding the empty hash.
|
|
||||||
func RHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
|
|
||||||
v, ok := val.(*[32]byte)
|
|
||||||
if !ok {
|
|
||||||
return tlv.NewTypeForEncodingErr(val, "RHash")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the value is an empty hash, we will skip encoding it.
|
|
||||||
if *v == lntypes.ZeroHash {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlv.EBytes32(w, v, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RHashDecoder is the customized decoder which skips decoding the empty hash.
|
|
||||||
func RHashDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
|
||||||
v, ok := val.(*[32]byte)
|
|
||||||
if !ok {
|
|
||||||
return tlv.NewTypeForEncodingErr(val, "RHash")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the length is zero, we will skip encoding the empty hash.
|
|
||||||
if l == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlv.DBytes32(r, v, buf, 32)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// toTlvStream converts an HTLCEntry record into a tlv representation.
|
// toTlvStream converts an HTLCEntry record into a tlv representation.
|
||||||
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
||||||
const (
|
records := []tlv.Record{
|
||||||
// A set of tlv type definitions used to serialize htlc entries
|
h.RHash.Record(),
|
||||||
// to the database. We define it here instead of the head of
|
h.RefundTimeout.Record(),
|
||||||
// the file to avoid naming conflicts.
|
h.OutputIndex.Record(),
|
||||||
//
|
h.Incoming.Record(),
|
||||||
// NOTE: A migration should be added whenever this list
|
h.Amt.Record(),
|
||||||
// changes.
|
}
|
||||||
rHashType tlv.Type = 0
|
|
||||||
refundTimeoutType tlv.Type = 1
|
|
||||||
outputIndexType tlv.Type = 2
|
|
||||||
incomingType tlv.Type = 3
|
|
||||||
amtType tlv.Type = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
return tlv.NewStream(
|
h.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
|
||||||
tlv.MakeDynamicRecord(
|
records = append(records, r.Record())
|
||||||
rHashType, &h.RHash, h.RHashLen,
|
})
|
||||||
RHashEncoder, RHashDecoder,
|
|
||||||
|
h.HtlcIndex.WhenSome(func(r tlv.RecordT[tlv.TlvType6, uint16]) {
|
||||||
|
records = append(records, r.Record())
|
||||||
|
})
|
||||||
|
|
||||||
|
tlv.SortRecords(records)
|
||||||
|
|
||||||
|
return tlv.NewStream(records...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC.
|
||||||
|
func NewHTLCEntryFromHTLC(htlc HTLC) (*HTLCEntry, error) {
|
||||||
|
h := &HTLCEntry{
|
||||||
|
RHash: tlv.NewRecordT[tlv.TlvType0](
|
||||||
|
NewSparsePayHash(htlc.RHash),
|
||||||
),
|
),
|
||||||
tlv.MakePrimitiveRecord(
|
RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||||
refundTimeoutType, &h.RefundTimeout,
|
htlc.RefundTimeout,
|
||||||
),
|
),
|
||||||
tlv.MakePrimitiveRecord(
|
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2](
|
||||||
outputIndexType, &h.OutputIndex,
|
uint16(htlc.OutputIndex),
|
||||||
),
|
),
|
||||||
tlv.MakePrimitiveRecord(incomingType, &h.incomingTlv),
|
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](htlc.Incoming),
|
||||||
// We will save 3 bytes if the amount is less or equal to
|
Amt: tlv.NewRecordT[tlv.TlvType4](
|
||||||
// 4,294,967,295 msat, or roughly 0.043 bitcoin.
|
tlv.NewBigSizeT(htlc.Amt.ToSatoshis()),
|
||||||
tlv.MakeBigSizeRecord(amtType, &h.amtTlv),
|
),
|
||||||
)
|
HtlcIndex: tlv.SomeRecordT(tlv.NewPrimitiveRecord[tlv.TlvType6](
|
||||||
|
uint16(htlc.HtlcIndex),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(htlc.CustomRecords) != 0 {
|
||||||
|
blob, err := htlc.CustomRecords.Serialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.CustomBlob = tlv.SomeRecordT(
|
||||||
|
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevocationLog stores the info needed to construct a breach retribution. Its
|
// RevocationLog stores the info needed to construct a breach retribution. Its
|
||||||
@@ -191,15 +233,15 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
|||||||
type RevocationLog struct {
|
type RevocationLog struct {
|
||||||
// OurOutputIndex specifies our output index in this commitment. In a
|
// OurOutputIndex specifies our output index in this commitment. In a
|
||||||
// remote commitment transaction, this is the to remote output index.
|
// remote commitment transaction, this is the to remote output index.
|
||||||
OurOutputIndex uint16
|
OurOutputIndex tlv.RecordT[tlv.TlvType0, uint16]
|
||||||
|
|
||||||
// TheirOutputIndex specifies their output index in this commitment. In
|
// TheirOutputIndex specifies their output index in this commitment. In
|
||||||
// a remote commitment transaction, this is the to local output index.
|
// a remote commitment transaction, this is the to local output index.
|
||||||
TheirOutputIndex uint16
|
TheirOutputIndex tlv.RecordT[tlv.TlvType1, uint16]
|
||||||
|
|
||||||
// CommitTxHash is the hash of the latest version of the commitment
|
// CommitTxHash is the hash of the latest version of the commitment
|
||||||
// state, broadcast able by us.
|
// state, broadcast able by us.
|
||||||
CommitTxHash [32]byte
|
CommitTxHash tlv.RecordT[tlv.TlvType2, [32]byte]
|
||||||
|
|
||||||
// HTLCEntries is the set of HTLCEntry's that are pending at this
|
// HTLCEntries is the set of HTLCEntry's that are pending at this
|
||||||
// particular commitment height.
|
// particular commitment height.
|
||||||
@@ -209,21 +251,65 @@ type RevocationLog struct {
|
|||||||
// directly spendable by us. In other words, it is the value of the
|
// directly spendable by us. In other words, it is the value of the
|
||||||
// to_remote output on the remote parties' commitment transaction.
|
// to_remote output on the remote parties' commitment transaction.
|
||||||
//
|
//
|
||||||
// NOTE: this is a pointer so that it is clear if the value is zero or
|
// NOTE: this is an option so that it is clear if the value is zero or
|
||||||
// nil. Since migration 30 of the channeldb initially did not include
|
// nil. Since migration 30 of the channeldb initially did not include
|
||||||
// this field, it could be the case that the field is not present for
|
// this field, it could be the case that the field is not present for
|
||||||
// all revocation logs.
|
// all revocation logs.
|
||||||
OurBalance *lnwire.MilliSatoshi
|
OurBalance tlv.OptionalRecordT[tlv.TlvType3, BigSizeMilliSatoshi]
|
||||||
|
|
||||||
// TheirBalance is the current available balance within the channel
|
// TheirBalance is the current available balance within the channel
|
||||||
// directly spendable by the remote node. In other words, it is the
|
// directly spendable by the remote node. In other words, it is the
|
||||||
// value of the to_local output on the remote parties' commitment.
|
// value of the to_local output on the remote parties' commitment.
|
||||||
//
|
//
|
||||||
// NOTE: this is a pointer so that it is clear if the value is zero or
|
// NOTE: this is an option so that it is clear if the value is zero or
|
||||||
// nil. Since migration 30 of the channeldb initially did not include
|
// nil. Since migration 30 of the channeldb initially did not include
|
||||||
// this field, it could be the case that the field is not present for
|
// this field, it could be the case that the field is not present for
|
||||||
// all revocation logs.
|
// all revocation logs.
|
||||||
TheirBalance *lnwire.MilliSatoshi
|
TheirBalance tlv.OptionalRecordT[tlv.TlvType4, BigSizeMilliSatoshi]
|
||||||
|
|
||||||
|
// CustomBlob is an optional blob that can be used to store information
|
||||||
|
// specific to a custom channel type. This information is only created
|
||||||
|
// at channel funding time, and after wards is to be considered
|
||||||
|
// immutable.
|
||||||
|
CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRevocationLog creates a new RevocationLog from the given parameters.
|
||||||
|
func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16,
|
||||||
|
commitHash [32]byte, ourBalance,
|
||||||
|
theirBalance fn.Option[lnwire.MilliSatoshi], htlcs []*HTLCEntry,
|
||||||
|
customBlob fn.Option[tlv.Blob]) RevocationLog {
|
||||||
|
|
||||||
|
rl := RevocationLog{
|
||||||
|
OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
|
||||||
|
ourOutputIndex,
|
||||||
|
),
|
||||||
|
TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||||
|
theirOutputIndex,
|
||||||
|
),
|
||||||
|
CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2](commitHash),
|
||||||
|
HTLCEntries: htlcs,
|
||||||
|
}
|
||||||
|
|
||||||
|
ourBalance.WhenSome(func(balance lnwire.MilliSatoshi) {
|
||||||
|
rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
|
||||||
|
tlv.NewBigSizeT(balance),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
theirBalance.WhenSome(func(balance lnwire.MilliSatoshi) {
|
||||||
|
rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
|
||||||
|
tlv.NewBigSizeT(balance),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
customBlob.WhenSome(func(blob tlv.Blob) {
|
||||||
|
rl.CustomBlob = tlv.SomeRecordT(
|
||||||
|
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return rl
|
||||||
}
|
}
|
||||||
|
|
||||||
// putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
|
// putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
|
||||||
@@ -242,15 +328,32 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
|
|||||||
}
|
}
|
||||||
|
|
||||||
rl := &RevocationLog{
|
rl := &RevocationLog{
|
||||||
OurOutputIndex: uint16(ourOutputIndex),
|
OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
|
||||||
TheirOutputIndex: uint16(theirOutputIndex),
|
uint16(ourOutputIndex),
|
||||||
CommitTxHash: commit.CommitTx.TxHash(),
|
),
|
||||||
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
|
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 {
|
if !noAmtData {
|
||||||
rl.OurBalance = &commit.LocalBalance
|
rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
|
||||||
rl.TheirBalance = &commit.RemoteBalance
|
tlv.NewBigSizeT(commit.LocalBalance),
|
||||||
|
))
|
||||||
|
|
||||||
|
rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
|
||||||
|
tlv.NewBigSizeT(commit.RemoteBalance),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, htlc := range commit.Htlcs {
|
for _, htlc := range commit.Htlcs {
|
||||||
@@ -265,12 +368,9 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
|
|||||||
return ErrOutputIndexTooBig
|
return ErrOutputIndexTooBig
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := &HTLCEntry{
|
entry, err := NewHTLCEntryFromHTLC(htlc)
|
||||||
RHash: htlc.RHash,
|
if err != nil {
|
||||||
RefundTimeout: htlc.RefundTimeout,
|
return err
|
||||||
Incoming: htlc.Incoming,
|
|
||||||
OutputIndex: uint16(htlc.OutputIndex),
|
|
||||||
Amt: htlc.Amt.ToSatoshis(),
|
|
||||||
}
|
}
|
||||||
rl.HTLCEntries = append(rl.HTLCEntries, entry)
|
rl.HTLCEntries = append(rl.HTLCEntries, entry)
|
||||||
}
|
}
|
||||||
@@ -306,31 +406,27 @@ func fetchRevocationLog(log kvdb.RBucket,
|
|||||||
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
|
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
|
||||||
// Add the tlv records for all non-optional fields.
|
// Add the tlv records for all non-optional fields.
|
||||||
records := []tlv.Record{
|
records := []tlv.Record{
|
||||||
tlv.MakePrimitiveRecord(
|
rl.OurOutputIndex.Record(),
|
||||||
revLogOurOutputIndexType, &rl.OurOutputIndex,
|
rl.TheirOutputIndex.Record(),
|
||||||
),
|
rl.CommitTxHash.Record(),
|
||||||
tlv.MakePrimitiveRecord(
|
|
||||||
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
|
|
||||||
),
|
|
||||||
tlv.MakePrimitiveRecord(
|
|
||||||
revLogCommitTxHashType, &rl.CommitTxHash,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we add any optional fields that are non-nil.
|
// Now we add any optional fields that are non-nil.
|
||||||
if rl.OurBalance != nil {
|
rl.OurBalance.WhenSome(
|
||||||
lb := uint64(*rl.OurBalance)
|
func(r tlv.RecordT[tlv.TlvType3, BigSizeMilliSatoshi]) {
|
||||||
records = append(records, tlv.MakeBigSizeRecord(
|
records = append(records, r.Record())
|
||||||
revLogOurBalanceType, &lb,
|
},
|
||||||
))
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if rl.TheirBalance != nil {
|
rl.TheirBalance.WhenSome(
|
||||||
rb := uint64(*rl.TheirBalance)
|
func(r tlv.RecordT[tlv.TlvType4, BigSizeMilliSatoshi]) {
|
||||||
records = append(records, tlv.MakeBigSizeRecord(
|
records = append(records, r.Record())
|
||||||
revLogTheirBalanceType, &rb,
|
},
|
||||||
))
|
)
|
||||||
}
|
|
||||||
|
rl.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
|
||||||
|
records = append(records, r.Record())
|
||||||
|
})
|
||||||
|
|
||||||
// Create the tlv stream.
|
// Create the tlv stream.
|
||||||
tlvStream, err := tlv.NewStream(records...)
|
tlvStream, err := tlv.NewStream(records...)
|
||||||
@@ -351,14 +447,6 @@ func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
|
|||||||
// format.
|
// format.
|
||||||
func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
|
func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
|
||||||
for _, htlc := range htlcs {
|
for _, htlc := range htlcs {
|
||||||
// Patch the incomingTlv field.
|
|
||||||
if htlc.Incoming {
|
|
||||||
htlc.incomingTlv = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patch the amtTlv field.
|
|
||||||
htlc.amtTlv = uint64(htlc.Amt)
|
|
||||||
|
|
||||||
// Create the tlv stream.
|
// Create the tlv stream.
|
||||||
tlvStream, err := htlc.toTlvStream()
|
tlvStream, err := htlc.toTlvStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -376,27 +464,20 @@ func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
|
|||||||
|
|
||||||
// deserializeRevocationLog deserializes a RevocationLog based on tlv format.
|
// deserializeRevocationLog deserializes a RevocationLog based on tlv format.
|
||||||
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
|
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
|
||||||
var (
|
var rl RevocationLog
|
||||||
rl RevocationLog
|
|
||||||
ourBalance uint64
|
ourBalance := rl.OurBalance.Zero()
|
||||||
theirBalance uint64
|
theirBalance := rl.TheirBalance.Zero()
|
||||||
)
|
customBlob := rl.CustomBlob.Zero()
|
||||||
|
|
||||||
// Create the tlv stream.
|
// Create the tlv stream.
|
||||||
tlvStream, err := tlv.NewStream(
|
tlvStream, err := tlv.NewStream(
|
||||||
tlv.MakePrimitiveRecord(
|
rl.OurOutputIndex.Record(),
|
||||||
revLogOurOutputIndexType, &rl.OurOutputIndex,
|
rl.TheirOutputIndex.Record(),
|
||||||
),
|
rl.CommitTxHash.Record(),
|
||||||
tlv.MakePrimitiveRecord(
|
ourBalance.Record(),
|
||||||
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
|
theirBalance.Record(),
|
||||||
),
|
customBlob.Record(),
|
||||||
tlv.MakePrimitiveRecord(
|
|
||||||
revLogCommitTxHashType, &rl.CommitTxHash,
|
|
||||||
),
|
|
||||||
tlv.MakeBigSizeRecord(revLogOurBalanceType, &ourBalance),
|
|
||||||
tlv.MakeBigSizeRecord(
|
|
||||||
revLogTheirBalanceType, &theirBalance,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rl, err
|
return rl, err
|
||||||
@@ -408,14 +489,16 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
|
|||||||
return rl, err
|
return rl, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t, ok := parsedTypes[revLogOurBalanceType]; ok && t == nil {
|
if t, ok := parsedTypes[ourBalance.TlvType()]; ok && t == nil {
|
||||||
lb := lnwire.MilliSatoshi(ourBalance)
|
rl.OurBalance = tlv.SomeRecordT(ourBalance)
|
||||||
rl.OurBalance = &lb
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if t, ok := parsedTypes[revLogTheirBalanceType]; ok && t == nil {
|
if t, ok := parsedTypes[theirBalance.TlvType()]; ok && t == nil {
|
||||||
rb := lnwire.MilliSatoshi(theirBalance)
|
rl.TheirBalance = tlv.SomeRecordT(theirBalance)
|
||||||
rl.TheirBalance = &rb
|
}
|
||||||
|
|
||||||
|
if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
|
||||||
|
rl.CustomBlob = tlv.SomeRecordT(customBlob)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the HTLC entries.
|
// Read the HTLC entries.
|
||||||
@@ -432,14 +515,28 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
|
|||||||
for {
|
for {
|
||||||
var htlc HTLCEntry
|
var htlc HTLCEntry
|
||||||
|
|
||||||
|
customBlob := htlc.CustomBlob.Zero()
|
||||||
|
htlcIndex := htlc.HtlcIndex.Zero()
|
||||||
|
|
||||||
// Create the tlv stream.
|
// Create the tlv stream.
|
||||||
tlvStream, err := htlc.toTlvStream()
|
records := []tlv.Record{
|
||||||
|
htlc.RHash.Record(),
|
||||||
|
htlc.RefundTimeout.Record(),
|
||||||
|
htlc.OutputIndex.Record(),
|
||||||
|
htlc.Incoming.Record(),
|
||||||
|
htlc.Amt.Record(),
|
||||||
|
customBlob.Record(),
|
||||||
|
htlcIndex.Record(),
|
||||||
|
}
|
||||||
|
|
||||||
|
tlvStream, err := tlv.NewStream(records...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the HTLC entry.
|
// Read the HTLC entry.
|
||||||
if _, err := readTlvStream(r, tlvStream); err != nil {
|
parsedTypes, err := readTlvStream(r, tlvStream)
|
||||||
|
if err != nil {
|
||||||
// We've reached the end when hitting an EOF.
|
// We've reached the end when hitting an EOF.
|
||||||
if err == io.ErrUnexpectedEOF {
|
if err == io.ErrUnexpectedEOF {
|
||||||
break
|
break
|
||||||
@@ -447,13 +544,13 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch the Incoming field.
|
if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
|
||||||
if htlc.incomingTlv == 1 {
|
htlc.CustomBlob = tlv.SomeRecordT(customBlob)
|
||||||
htlc.Incoming = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch the Amt field.
|
if t, ok := parsedTypes[htlcIndex.TlvType()]; ok && t == nil {
|
||||||
htlc.Amt = btcutil.Amount(htlc.amtTlv)
|
htlc.HtlcIndex = tlv.SomeRecordT(htlcIndex)
|
||||||
|
}
|
||||||
|
|
||||||
// Append the entry.
|
// Append the entry.
|
||||||
htlcs = append(htlcs, &htlc)
|
htlcs = append(htlcs, &htlc)
|
||||||
@@ -469,6 +566,7 @@ func writeTlvStream(w io.Writer, s *tlv.Stream) error {
|
|||||||
if err := s.Encode(&b); err != nil {
|
if err := s.Encode(&b); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the stream's length as a varint.
|
// Write the stream's length as a varint.
|
||||||
err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{})
|
err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lntest/channels"
|
"github.com/lightningnetwork/lnd/lntest/channels"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@@ -33,17 +34,38 @@ var (
|
|||||||
0xff, // value = 255
|
0xff, // value = 255
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customRecords = lnwire.CustomRecords{
|
||||||
|
lnwire.MinCustomRecordsTlvType + 1: []byte("custom data"),
|
||||||
|
}
|
||||||
|
|
||||||
|
blobBytes = []byte{
|
||||||
|
// Corresponds to the encoded version of the above custom
|
||||||
|
// records.
|
||||||
|
0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73, 0x74,
|
||||||
|
0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61,
|
||||||
|
}
|
||||||
|
|
||||||
testHTLCEntry = HTLCEntry{
|
testHTLCEntry = HTLCEntry{
|
||||||
RefundTimeout: 740_000,
|
RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1, uint32](
|
||||||
OutputIndex: 10,
|
740_000,
|
||||||
Incoming: true,
|
),
|
||||||
Amt: 1000_000,
|
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2, uint16](
|
||||||
amtTlv: 1000_000,
|
10,
|
||||||
incomingTlv: 1,
|
),
|
||||||
|
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true),
|
||||||
|
Amt: tlv.NewRecordT[tlv.TlvType4](
|
||||||
|
tlv.NewBigSizeT(btcutil.Amount(1_000_000)),
|
||||||
|
),
|
||||||
|
CustomBlob: tlv.SomeRecordT(
|
||||||
|
tlv.NewPrimitiveRecord[tlv.TlvType5](blobBytes),
|
||||||
|
),
|
||||||
|
HtlcIndex: tlv.SomeRecordT(
|
||||||
|
tlv.NewPrimitiveRecord[tlv.TlvType6, uint16](0x33),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
testHTLCEntryBytes = []byte{
|
testHTLCEntryBytes = []byte{
|
||||||
// Body length 23.
|
// Body length 45.
|
||||||
0x16,
|
0x2d,
|
||||||
// Rhash tlv.
|
// Rhash tlv.
|
||||||
0x0, 0x0,
|
0x0, 0x0,
|
||||||
// RefundTimeout tlv.
|
// RefundTimeout tlv.
|
||||||
@@ -54,6 +76,45 @@ var (
|
|||||||
0x3, 0x1, 0x1,
|
0x3, 0x1, 0x1,
|
||||||
// Amt tlv.
|
// Amt tlv.
|
||||||
0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40,
|
0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40,
|
||||||
|
// Custom blob tlv.
|
||||||
|
0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73,
|
||||||
|
0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61,
|
||||||
|
// HLTC index tlv.
|
||||||
|
0x6, 0x2, 0x0, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
testHTLCEntryHash = HTLCEntry{
|
||||||
|
RHash: tlv.NewPrimitiveRecord[tlv.TlvType0](NewSparsePayHash(
|
||||||
|
[32]byte{0x33, 0x44, 0x55},
|
||||||
|
)),
|
||||||
|
RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1, uint32](
|
||||||
|
740_000,
|
||||||
|
),
|
||||||
|
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2, uint16](
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true),
|
||||||
|
Amt: tlv.NewRecordT[tlv.TlvType4](
|
||||||
|
tlv.NewBigSizeT(btcutil.Amount(1_000_000)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
testHTLCEntryHashBytes = []byte{
|
||||||
|
// Body length 54.
|
||||||
|
0x36,
|
||||||
|
// Rhash tlv.
|
||||||
|
0x0, 0x20,
|
||||||
|
0x33, 0x44, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
// RefundTimeout tlv.
|
||||||
|
0x1, 0x4, 0x0, 0xb, 0x4a, 0xa0,
|
||||||
|
// OutputIndex tlv.
|
||||||
|
0x2, 0x2, 0x0, 0xa,
|
||||||
|
// Incoming tlv.
|
||||||
|
0x3, 0x1, 0x1,
|
||||||
|
// Amt tlv.
|
||||||
|
0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40,
|
||||||
}
|
}
|
||||||
|
|
||||||
localBalance = lnwire.MilliSatoshi(9000)
|
localBalance = lnwire.MilliSatoshi(9000)
|
||||||
@@ -68,24 +129,29 @@ var (
|
|||||||
CommitTx: channels.TestFundingTx,
|
CommitTx: channels.TestFundingTx,
|
||||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||||
Htlcs: []HTLC{{
|
Htlcs: []HTLC{{
|
||||||
RefundTimeout: testHTLCEntry.RefundTimeout,
|
RefundTimeout: testHTLCEntry.RefundTimeout.Val,
|
||||||
OutputIndex: int32(testHTLCEntry.OutputIndex),
|
OutputIndex: int32(testHTLCEntry.OutputIndex.Val),
|
||||||
Incoming: testHTLCEntry.Incoming,
|
HtlcIndex: uint64(
|
||||||
Amt: lnwire.NewMSatFromSatoshis(
|
testHTLCEntry.HtlcIndex.ValOpt().
|
||||||
testHTLCEntry.Amt,
|
UnsafeFromSome(),
|
||||||
),
|
),
|
||||||
|
Incoming: testHTLCEntry.Incoming.Val,
|
||||||
|
Amt: lnwire.NewMSatFromSatoshis(
|
||||||
|
testHTLCEntry.Amt.Val.Int(),
|
||||||
|
),
|
||||||
|
CustomRecords: customRecords,
|
||||||
}},
|
}},
|
||||||
|
CustomBlob: fn.Some(blobBytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
testRevocationLogNoAmts = RevocationLog{
|
testRevocationLogNoAmts = NewRevocationLog(
|
||||||
OurOutputIndex: 0,
|
0, 1, testChannelCommit.CommitTx.TxHash(),
|
||||||
TheirOutputIndex: 1,
|
fn.None[lnwire.MilliSatoshi](), fn.None[lnwire.MilliSatoshi](),
|
||||||
CommitTxHash: testChannelCommit.CommitTx.TxHash(),
|
[]*HTLCEntry{&testHTLCEntry}, fn.Some(blobBytes),
|
||||||
HTLCEntries: []*HTLCEntry{&testHTLCEntry},
|
)
|
||||||
}
|
|
||||||
testRevocationLogNoAmtsBytes = []byte{
|
testRevocationLogNoAmtsBytes = []byte{
|
||||||
// Body length 42.
|
// Body length 61.
|
||||||
0x2a,
|
0x3d,
|
||||||
// OurOutputIndex tlv.
|
// OurOutputIndex tlv.
|
||||||
0x0, 0x2, 0x0, 0x0,
|
0x0, 0x2, 0x0, 0x0,
|
||||||
// TheirOutputIndex tlv.
|
// TheirOutputIndex tlv.
|
||||||
@@ -96,19 +162,19 @@ var (
|
|||||||
0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6,
|
0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6,
|
||||||
0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff,
|
0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff,
|
||||||
0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd,
|
0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd,
|
||||||
|
// Custom blob tlv.
|
||||||
|
0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73,
|
||||||
|
0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61,
|
||||||
}
|
}
|
||||||
|
|
||||||
testRevocationLogWithAmts = RevocationLog{
|
testRevocationLogWithAmts = NewRevocationLog(
|
||||||
OurOutputIndex: 0,
|
0, 1, testChannelCommit.CommitTx.TxHash(),
|
||||||
TheirOutputIndex: 1,
|
fn.Some(localBalance), fn.Some(remoteBalance),
|
||||||
CommitTxHash: testChannelCommit.CommitTx.TxHash(),
|
[]*HTLCEntry{&testHTLCEntry}, fn.Some(blobBytes),
|
||||||
HTLCEntries: []*HTLCEntry{&testHTLCEntry},
|
)
|
||||||
OurBalance: &localBalance,
|
|
||||||
TheirBalance: &remoteBalance,
|
|
||||||
}
|
|
||||||
testRevocationLogWithAmtsBytes = []byte{
|
testRevocationLogWithAmtsBytes = []byte{
|
||||||
// Body length 52.
|
// Body length 71.
|
||||||
0x34,
|
0x47,
|
||||||
// OurOutputIndex tlv.
|
// OurOutputIndex tlv.
|
||||||
0x0, 0x2, 0x0, 0x0,
|
0x0, 0x2, 0x0, 0x0,
|
||||||
// TheirOutputIndex tlv.
|
// TheirOutputIndex tlv.
|
||||||
@@ -123,6 +189,9 @@ var (
|
|||||||
0x3, 0x3, 0xfd, 0x23, 0x28,
|
0x3, 0x3, 0xfd, 0x23, 0x28,
|
||||||
// Remote Balance.
|
// Remote Balance.
|
||||||
0x4, 0x3, 0xfd, 0x0b, 0xb8,
|
0x4, 0x3, 0xfd, 0x0b, 0xb8,
|
||||||
|
// Custom blob tlv.
|
||||||
|
0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73,
|
||||||
|
0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -193,11 +262,6 @@ func TestSerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
|||||||
// Copy the testHTLCEntry.
|
// Copy the testHTLCEntry.
|
||||||
entry := testHTLCEntry
|
entry := testHTLCEntry
|
||||||
|
|
||||||
// Set the internal fields to empty values so we can test the bytes are
|
|
||||||
// padded.
|
|
||||||
entry.incomingTlv = 0
|
|
||||||
entry.amtTlv = 0
|
|
||||||
|
|
||||||
// Write the tlv stream.
|
// Write the tlv stream.
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
err := serializeHTLCEntries(buf, []*HTLCEntry{&entry})
|
err := serializeHTLCEntries(buf, []*HTLCEntry{&entry})
|
||||||
@@ -207,6 +271,21 @@ func TestSerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
|||||||
require.Equal(t, testHTLCEntryBytes, buf.Bytes())
|
require.Equal(t, testHTLCEntryBytes, buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSerializeHTLCEntriesWithRHash(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Copy the testHTLCEntry.
|
||||||
|
entry := testHTLCEntryHash
|
||||||
|
|
||||||
|
// Write the tlv stream.
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := serializeHTLCEntries(buf, []*HTLCEntry{&entry})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check the bytes are read as expected.
|
||||||
|
require.Equal(t, testHTLCEntryHashBytes, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
func TestSerializeHTLCEntries(t *testing.T) {
|
func TestSerializeHTLCEntries(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -215,7 +294,7 @@ func TestSerializeHTLCEntries(t *testing.T) {
|
|||||||
|
|
||||||
// Create a fake rHash.
|
// Create a fake rHash.
|
||||||
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
||||||
copy(entry.RHash[:], rHashBytes)
|
copy(entry.RHash.Val[:], rHashBytes)
|
||||||
|
|
||||||
// Construct the serialized bytes.
|
// Construct the serialized bytes.
|
||||||
//
|
//
|
||||||
@@ -224,7 +303,7 @@ func TestSerializeHTLCEntries(t *testing.T) {
|
|||||||
partialBytes := testHTLCEntryBytes[3:]
|
partialBytes := testHTLCEntryBytes[3:]
|
||||||
|
|
||||||
// Write the total length and RHash tlv.
|
// Write the total length and RHash tlv.
|
||||||
expectedBytes := []byte{0x36, 0x0, 0x20}
|
expectedBytes := []byte{0x4d, 0x0, 0x20}
|
||||||
expectedBytes = append(expectedBytes, rHashBytes...)
|
expectedBytes = append(expectedBytes, rHashBytes...)
|
||||||
|
|
||||||
// Append the rest.
|
// Append the rest.
|
||||||
@@ -269,7 +348,7 @@ func TestSerializeAndDeserializeRevLog(t *testing.T) {
|
|||||||
t, &test.revLog, test.revLogBytes,
|
t, &test.revLog, test.revLogBytes,
|
||||||
)
|
)
|
||||||
|
|
||||||
testDerializeRevocationLog(
|
testDeserializeRevocationLog(
|
||||||
t, &test.revLog, test.revLogBytes,
|
t, &test.revLog, test.revLogBytes,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -293,7 +372,7 @@ func testSerializeRevocationLog(t *testing.T, rl *RevocationLog,
|
|||||||
require.Equal(t, revLogBytes, buf.Bytes()[:bodyIndex])
|
require.Equal(t, revLogBytes, buf.Bytes()[:bodyIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDerializeRevocationLog(t *testing.T, revLog *RevocationLog,
|
func testDeserializeRevocationLog(t *testing.T, revLog *RevocationLog,
|
||||||
revLogBytes []byte) {
|
revLogBytes []byte) {
|
||||||
|
|
||||||
// Construct the full bytes.
|
// Construct the full bytes.
|
||||||
@@ -309,7 +388,7 @@ func testDerializeRevocationLog(t *testing.T, revLog *RevocationLog,
|
|||||||
require.Equal(t, *revLog, rl)
|
require.Equal(t, *revLog, rl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
func TestDeserializeHTLCEntriesEmptyRHash(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Read the tlv stream.
|
// Read the tlv stream.
|
||||||
@@ -322,7 +401,7 @@ func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
|||||||
require.Equal(t, &testHTLCEntry, htlcs[0])
|
require.Equal(t, &testHTLCEntry, htlcs[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDerializeHTLCEntries(t *testing.T) {
|
func TestDeserializeHTLCEntries(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Copy the testHTLCEntry.
|
// Copy the testHTLCEntry.
|
||||||
@@ -330,7 +409,7 @@ func TestDerializeHTLCEntries(t *testing.T) {
|
|||||||
|
|
||||||
// Create a fake rHash.
|
// Create a fake rHash.
|
||||||
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
||||||
copy(entry.RHash[:], rHashBytes)
|
copy(entry.RHash.Val[:], rHashBytes)
|
||||||
|
|
||||||
// Construct the serialized bytes.
|
// Construct the serialized bytes.
|
||||||
//
|
//
|
||||||
@@ -339,7 +418,7 @@ func TestDerializeHTLCEntries(t *testing.T) {
|
|||||||
partialBytes := testHTLCEntryBytes[3:]
|
partialBytes := testHTLCEntryBytes[3:]
|
||||||
|
|
||||||
// Write the total length and RHash tlv.
|
// Write the total length and RHash tlv.
|
||||||
testBytes := append([]byte{0x36, 0x0, 0x20}, rHashBytes...)
|
testBytes := append([]byte{0x4d, 0x0, 0x20}, rHashBytes...)
|
||||||
|
|
||||||
// Append the rest.
|
// Append the rest.
|
||||||
testBytes = append(testBytes, partialBytes...)
|
testBytes = append(testBytes, partialBytes...)
|
||||||
@@ -398,11 +477,11 @@ func TestDeleteLogBucket(t *testing.T) {
|
|||||||
|
|
||||||
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||||
// Create the buckets.
|
// Create the buckets.
|
||||||
chanBucket, _, err := createTestRevocatoinLogBuckets(tx)
|
chanBucket, _, err := createTestRevocationLogBuckets(tx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create the buckets again should give us an error.
|
// Create the buckets again should give us an error.
|
||||||
_, _, err = createTestRevocatoinLogBuckets(tx)
|
_, _, err = createTestRevocationLogBuckets(tx)
|
||||||
require.ErrorIs(t, err, kvdb.ErrBucketExists)
|
require.ErrorIs(t, err, kvdb.ErrBucketExists)
|
||||||
|
|
||||||
// Delete both buckets.
|
// Delete both buckets.
|
||||||
@@ -410,7 +489,7 @@ func TestDeleteLogBucket(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create the buckets again should give us NO error.
|
// Create the buckets again should give us NO error.
|
||||||
_, _, err = createTestRevocatoinLogBuckets(tx)
|
_, _, err = createTestRevocationLogBuckets(tx)
|
||||||
return err
|
return err
|
||||||
}, func() {})
|
}, func() {})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -516,7 +595,7 @@ func TestPutRevocationLog(t *testing.T) {
|
|||||||
// Construct the testing db transaction.
|
// Construct the testing db transaction.
|
||||||
dbTx := func(tx kvdb.RwTx) (RevocationLog, error) {
|
dbTx := func(tx kvdb.RwTx) (RevocationLog, error) {
|
||||||
// Create the buckets.
|
// Create the buckets.
|
||||||
_, bucket, err := createTestRevocatoinLogBuckets(tx)
|
_, bucket, err := createTestRevocationLogBuckets(tx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Save the log.
|
// Save the log.
|
||||||
@@ -686,7 +765,7 @@ func TestFetchRevocationLogCompatible(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestRevocatoinLogBuckets(tx kvdb.RwTx) (kvdb.RwBucket,
|
func createTestRevocationLogBuckets(tx kvdb.RwTx) (kvdb.RwBucket,
|
||||||
kvdb.RwBucket, error) {
|
kvdb.RwBucket, error) {
|
||||||
|
|
||||||
chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
|
chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
|
||||||
|
|||||||
+24
-12
@@ -105,7 +105,7 @@ type DatabaseBuilder interface {
|
|||||||
type WalletConfigBuilder interface {
|
type WalletConfigBuilder interface {
|
||||||
// BuildWalletConfig is responsible for creating or unlocking and then
|
// BuildWalletConfig is responsible for creating or unlocking and then
|
||||||
// fully initializing a wallet.
|
// fully initializing a wallet.
|
||||||
BuildWalletConfig(context.Context, *DatabaseInstances,
|
BuildWalletConfig(context.Context, *DatabaseInstances, *AuxComponents,
|
||||||
*rpcperms.InterceptorChain,
|
*rpcperms.InterceptorChain,
|
||||||
[]*ListenerWithSignal) (*chainreg.PartialChainControl,
|
[]*ListenerWithSignal) (*chainreg.PartialChainControl,
|
||||||
*btcwallet.Config, func(), error)
|
*btcwallet.Config, func(), error)
|
||||||
@@ -120,14 +120,6 @@ type ChainControlBuilder interface {
|
|||||||
*btcwallet.Config) (*chainreg.ChainControl, func(), error)
|
*btcwallet.Config) (*chainreg.ChainControl, func(), error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuxComponents is a set of auxiliary components that can be used by lnd for
|
|
||||||
// certain custom channel types.
|
|
||||||
type AuxComponents struct {
|
|
||||||
// MsgRouter is an optional message router that if set will be used in
|
|
||||||
// place of a new blank default message router.
|
|
||||||
MsgRouter fn.Option[msgmux.Router]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImplementationCfg is a struct that holds all configuration items for
|
// ImplementationCfg is a struct that holds all configuration items for
|
||||||
// components that can be implemented outside lnd itself.
|
// components that can be implemented outside lnd itself.
|
||||||
type ImplementationCfg struct {
|
type ImplementationCfg struct {
|
||||||
@@ -160,6 +152,18 @@ type ImplementationCfg struct {
|
|||||||
AuxComponents
|
AuxComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AuxComponents is a set of auxiliary components that can be used by lnd for
|
||||||
|
// certain custom channel types.
|
||||||
|
type AuxComponents struct {
|
||||||
|
// AuxLeafStore is an optional data source that can be used by custom
|
||||||
|
// channels to fetch+store various data.
|
||||||
|
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||||
|
|
||||||
|
// MsgRouter is an optional message router that if set will be used in
|
||||||
|
// place of a new blank default message router.
|
||||||
|
MsgRouter fn.Option[msgmux.Router]
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultWalletImpl is the default implementation of our normal, btcwallet
|
// DefaultWalletImpl is the default implementation of our normal, btcwallet
|
||||||
// backed configuration.
|
// backed configuration.
|
||||||
type DefaultWalletImpl struct {
|
type DefaultWalletImpl struct {
|
||||||
@@ -242,7 +246,8 @@ func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
|
|||||||
//
|
//
|
||||||
// NOTE: This is part of the WalletConfigBuilder interface.
|
// NOTE: This is part of the WalletConfigBuilder interface.
|
||||||
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
|
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
|
||||||
dbs *DatabaseInstances, interceptorChain *rpcperms.InterceptorChain,
|
dbs *DatabaseInstances, aux *AuxComponents,
|
||||||
|
interceptorChain *rpcperms.InterceptorChain,
|
||||||
grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
|
grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
|
||||||
*btcwallet.Config, func(), error) {
|
*btcwallet.Config, func(), error) {
|
||||||
|
|
||||||
@@ -562,6 +567,7 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
|
|||||||
HeightHintDB: dbs.HeightHintDB,
|
HeightHintDB: dbs.HeightHintDB,
|
||||||
ChanStateDB: dbs.ChanStateDB.ChannelStateDB(),
|
ChanStateDB: dbs.ChanStateDB.ChannelStateDB(),
|
||||||
NeutrinoCS: neutrinoCS,
|
NeutrinoCS: neutrinoCS,
|
||||||
|
AuxLeafStore: aux.AuxLeafStore,
|
||||||
ActiveNetParams: d.cfg.ActiveNetParams,
|
ActiveNetParams: d.cfg.ActiveNetParams,
|
||||||
FeeURL: d.cfg.FeeURL,
|
FeeURL: d.cfg.FeeURL,
|
||||||
Fee: &lncfg.Fee{
|
Fee: &lncfg.Fee{
|
||||||
@@ -625,8 +631,9 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
|
|||||||
|
|
||||||
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
|
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
|
||||||
// rebroadcaster client.
|
// rebroadcaster client.
|
||||||
func proxyBlockEpoch(notifier chainntnfs.ChainNotifier,
|
func proxyBlockEpoch(
|
||||||
) func() (*blockntfns.Subscription, error) {
|
notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription,
|
||||||
|
error) {
|
||||||
|
|
||||||
return func() (*blockntfns.Subscription, error) {
|
return func() (*blockntfns.Subscription, error) {
|
||||||
blockEpoch, err := notifier.RegisterBlockEpochNtfn(
|
blockEpoch, err := notifier.RegisterBlockEpochNtfn(
|
||||||
@@ -717,6 +724,7 @@ func (d *DefaultWalletImpl) BuildChainControl(
|
|||||||
ChainIO: walletController,
|
ChainIO: walletController,
|
||||||
NetParams: *walletConfig.NetParams,
|
NetParams: *walletConfig.NetParams,
|
||||||
CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
|
CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
|
||||||
|
AuxLeafStore: partialChainControl.Cfg.AuxLeafStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
// The broadcast is already always active for neutrino nodes, so we
|
// The broadcast is already always active for neutrino nodes, so we
|
||||||
@@ -899,6 +907,10 @@ type DatabaseInstances struct {
|
|||||||
// for native SQL queries for tables that already support it. This may
|
// for native SQL queries for tables that already support it. This may
|
||||||
// be nil if the use-native-sql flag was not set.
|
// be nil if the use-native-sql flag was not set.
|
||||||
NativeSQLStore *sqldb.BaseDB
|
NativeSQLStore *sqldb.BaseDB
|
||||||
|
|
||||||
|
// AuxLeafStore is an optional data source that can be used by custom
|
||||||
|
// channels to fetch+store various data.
|
||||||
|
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultDatabaseBuilder is a type that builds the default database backends
|
// DefaultDatabaseBuilder is a type that builds the default database backends
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lntest/channels"
|
"github.com/lightningnetwork/lnd/lntest/channels"
|
||||||
@@ -1590,6 +1591,7 @@ func testBreachSpends(t *testing.T, test breachTest) {
|
|||||||
// Notify the breach arbiter about the breach.
|
// Notify the breach arbiter about the breach.
|
||||||
retribution, err := lnwallet.NewBreachRetribution(
|
retribution, err := lnwallet.NewBreachRetribution(
|
||||||
alice.State(), height, 1, forceCloseTx,
|
alice.State(), height, 1, forceCloseTx,
|
||||||
|
fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create breach retribution")
|
require.NoError(t, err, "unable to create breach retribution")
|
||||||
|
|
||||||
@@ -1799,6 +1801,7 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) {
|
|||||||
// Notify the breach arbiter about the breach.
|
// Notify the breach arbiter about the breach.
|
||||||
retribution, err := lnwallet.NewBreachRetribution(
|
retribution, err := lnwallet.NewBreachRetribution(
|
||||||
alice.State(), height, uint32(blockHeight), forceCloseTx,
|
alice.State(), height, uint32(blockHeight), forceCloseTx,
|
||||||
|
fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create breach retribution")
|
require.NoError(t, err, "unable to create breach retribution")
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,10 @@ type ChainArbitratorConfig struct {
|
|||||||
// meanwhile, turn `PaymentCircuit` into an interface or bring it to a
|
// meanwhile, turn `PaymentCircuit` into an interface or bring it to a
|
||||||
// lower package.
|
// lower package.
|
||||||
QueryIncomingCircuit func(circuit models.CircuitKey) *models.CircuitKey
|
QueryIncomingCircuit func(circuit models.CircuitKey) *models.CircuitKey
|
||||||
|
|
||||||
|
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||||
|
// leaves for certain custom channel types.
|
||||||
|
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
||||||
@@ -299,8 +303,13 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chanOpts []lnwallet.ChannelOpt
|
||||||
|
a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||||
|
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||||
|
})
|
||||||
|
|
||||||
chanMachine, err := lnwallet.NewLightningChannel(
|
chanMachine, err := lnwallet.NewLightningChannel(
|
||||||
a.c.cfg.Signer, channel, nil,
|
a.c.cfg.Signer, channel, nil, chanOpts...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -344,10 +353,15 @@ func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chanOpts []lnwallet.ChannelOpt
|
||||||
|
a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||||
|
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||||
|
})
|
||||||
|
|
||||||
// Finally, we'll force close the channel completing
|
// Finally, we'll force close the channel completing
|
||||||
// the force close workflow.
|
// the force close workflow.
|
||||||
chanMachine, err := lnwallet.NewLightningChannel(
|
chanMachine, err := lnwallet.NewLightningChannel(
|
||||||
a.c.cfg.Signer, channel, nil,
|
a.c.cfg.Signer, channel, nil, chanOpts...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -193,6 +193,9 @@ type chainWatcherConfig struct {
|
|||||||
// obfuscater. This is used by the chain watcher to identify which
|
// obfuscater. This is used by the chain watcher to identify which
|
||||||
// state was broadcast and confirmed on-chain.
|
// state was broadcast and confirmed on-chain.
|
||||||
extractStateNumHint func(*wire.MsgTx, [lnwallet.StateHintSize]byte) uint64
|
extractStateNumHint func(*wire.MsgTx, [lnwallet.StateHintSize]byte) uint64
|
||||||
|
|
||||||
|
// auxLeafStore can be used to fetch information for custom channels.
|
||||||
|
auxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||||
}
|
}
|
||||||
|
|
||||||
// chainWatcher is a system that's assigned to every active channel. The duty
|
// chainWatcher is a system that's assigned to every active channel. The duty
|
||||||
@@ -308,7 +311,7 @@ func (c *chainWatcher) Start() error {
|
|||||||
)
|
)
|
||||||
if chanState.ChanType.IsTaproot() {
|
if chanState.ChanType.IsTaproot() {
|
||||||
c.fundingPkScript, _, err = input.GenTaprootFundingScript(
|
c.fundingPkScript, _, err = input.GenTaprootFundingScript(
|
||||||
localKey, remoteKey, 0,
|
localKey, remoteKey, 0, chanState.TapscriptRoot,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -423,15 +426,36 @@ func (c *chainWatcher) handleUnknownLocalState(
|
|||||||
&c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg,
|
&c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
auxResult, err := fn.MapOptionZ(
|
||||||
|
c.cfg.auxLeafStore,
|
||||||
|
//nolint:lll
|
||||||
|
func(s lnwallet.AuxLeafStore) fn.Result[lnwallet.CommitDiffAuxResult] {
|
||||||
|
return s.FetchLeavesFromCommit(
|
||||||
|
lnwallet.NewAuxChanState(c.cfg.chanState),
|
||||||
|
c.cfg.chanState.LocalCommitment, *commitKeyRing,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
).Unpack()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to fetch aux leaves: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// With the keys derived, we'll construct the remote script that'll be
|
// With the keys derived, we'll construct the remote script that'll be
|
||||||
// present if they have a non-dust balance on the commitment.
|
// present if they have a non-dust balance on the commitment.
|
||||||
var leaseExpiry uint32
|
var leaseExpiry uint32
|
||||||
if c.cfg.chanState.ChanType.HasLeaseExpiration() {
|
if c.cfg.chanState.ChanType.HasLeaseExpiration() {
|
||||||
leaseExpiry = c.cfg.chanState.ThawHeight
|
leaseExpiry = c.cfg.chanState.ThawHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remoteAuxLeaf := fn.ChainOption(
|
||||||
|
func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
|
||||||
|
return l.RemoteAuxLeaf
|
||||||
|
},
|
||||||
|
)(auxResult.AuxLeaves)
|
||||||
remoteScript, _, err := lnwallet.CommitScriptToRemote(
|
remoteScript, _, err := lnwallet.CommitScriptToRemote(
|
||||||
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
|
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
|
||||||
commitKeyRing.ToRemoteKey, leaseExpiry,
|
commitKeyRing.ToRemoteKey, leaseExpiry,
|
||||||
|
remoteAuxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -440,10 +464,16 @@ func (c *chainWatcher) handleUnknownLocalState(
|
|||||||
// Next, we'll derive our script that includes the revocation base for
|
// Next, we'll derive our script that includes the revocation base for
|
||||||
// the remote party allowing them to claim this output before the CSV
|
// the remote party allowing them to claim this output before the CSV
|
||||||
// delay if we breach.
|
// delay if we breach.
|
||||||
|
localAuxLeaf := fn.ChainOption(
|
||||||
|
func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
|
||||||
|
return l.LocalAuxLeaf
|
||||||
|
},
|
||||||
|
)(auxResult.AuxLeaves)
|
||||||
localScript, err := lnwallet.CommitScriptToSelf(
|
localScript, err := lnwallet.CommitScriptToSelf(
|
||||||
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
|
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
|
||||||
commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
|
commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
|
||||||
uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
|
uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
|
||||||
|
localAuxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -866,7 +896,7 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
|
|||||||
spendHeight := uint32(commitSpend.SpendingHeight)
|
spendHeight := uint32(commitSpend.SpendingHeight)
|
||||||
retribution, err := lnwallet.NewBreachRetribution(
|
retribution, err := lnwallet.NewBreachRetribution(
|
||||||
c.cfg.chanState, broadcastStateNum, spendHeight,
|
c.cfg.chanState, broadcastStateNum, spendHeight,
|
||||||
commitSpend.SpendingTx,
|
commitSpend.SpendingTx, c.cfg.auxLeafStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -1116,8 +1146,8 @@ func (c *chainWatcher) dispatchLocalForceClose(
|
|||||||
"detected", c.cfg.chanState.FundingOutpoint)
|
"detected", c.cfg.chanState.FundingOutpoint)
|
||||||
|
|
||||||
forceClose, err := lnwallet.NewLocalForceCloseSummary(
|
forceClose, err := lnwallet.NewLocalForceCloseSummary(
|
||||||
c.cfg.chanState, c.cfg.signer,
|
c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
|
||||||
commitSpend.SpendingTx, stateNum,
|
c.cfg.auxLeafStore,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1210,7 +1240,7 @@ func (c *chainWatcher) dispatchRemoteForceClose(
|
|||||||
// channel on-chain.
|
// channel on-chain.
|
||||||
uniClose, err := lnwallet.NewUnilateralCloseSummary(
|
uniClose, err := lnwallet.NewUnilateralCloseSummary(
|
||||||
c.cfg.chanState, c.cfg.signer, commitSpend,
|
c.cfg.chanState, c.cfg.signer, commitSpend,
|
||||||
remoteCommit, commitPoint,
|
remoteCommit, commitPoint, c.cfg.auxLeafStore,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+12
-1
@@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
"github.com/lightningnetwork/lnd/discovery"
|
"github.com/lightningnetwork/lnd/discovery"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/graph"
|
"github.com/lightningnetwork/lnd/graph"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
@@ -544,6 +545,10 @@ type Config struct {
|
|||||||
// backed funding flow to not use utxos still being swept by the sweeper
|
// backed funding flow to not use utxos still being swept by the sweeper
|
||||||
// subsystem.
|
// subsystem.
|
||||||
IsSweeperOutpoint func(wire.OutPoint) bool
|
IsSweeperOutpoint func(wire.OutPoint) bool
|
||||||
|
|
||||||
|
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||||
|
// leaves for certain custom channel types.
|
||||||
|
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager acts as an orchestrator/bridge between the wallet's
|
// Manager acts as an orchestrator/bridge between the wallet's
|
||||||
@@ -1069,9 +1074,14 @@ func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chanOpts []lnwallet.ChannelOpt
|
||||||
|
f.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||||
|
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||||
|
})
|
||||||
|
|
||||||
// We create the state-machine object which wraps the database state.
|
// We create the state-machine object which wraps the database state.
|
||||||
lnChannel, err := lnwallet.NewLightningChannel(
|
lnChannel, err := lnwallet.NewLightningChannel(
|
||||||
nil, channel, nil,
|
nil, channel, nil, chanOpts...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unable to create LightningChannel(%v): %v",
|
log.Errorf("Unable to create LightningChannel(%v): %v",
|
||||||
@@ -2899,6 +2909,7 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) {
|
|||||||
if channel.ChanType.IsTaproot() {
|
if channel.ChanType.IsTaproot() {
|
||||||
pkScript, _, err := input.GenTaprootFundingScript(
|
pkScript, _, err := input.GenTaprootFundingScript(
|
||||||
localKey, remoteKey, int64(channel.Capacity),
|
localKey, remoteKey, int64(channel.Capacity),
|
||||||
|
channel.TapscriptRoot,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
"github.com/lightningnetwork/lnd/channelnotifier"
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||||
"github.com/lightningnetwork/lnd/discovery"
|
"github.com/lightningnetwork/lnd/discovery"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lncfg"
|
"github.com/lightningnetwork/lnd/lncfg"
|
||||||
@@ -563,6 +564,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
IsSweeperOutpoint: func(wire.OutPoint) bool {
|
IsSweeperOutpoint: func(wire.OutPoint) bool {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
AuxLeafStore: fn.Some[lnwallet.AuxLeafStore](
|
||||||
|
&lnwallet.MockAuxLeafStore{},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, op := range options {
|
for _, op := range options {
|
||||||
@@ -672,6 +676,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
|||||||
OpenChannelPredicate: chainedAcceptor,
|
OpenChannelPredicate: chainedAcceptor,
|
||||||
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
|
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
|
||||||
AliasManager: oldCfg.AliasManager,
|
AliasManager: oldCfg.AliasManager,
|
||||||
|
AuxLeafStore: oldCfg.AuxLeafStore,
|
||||||
})
|
})
|
||||||
require.NoError(t, err, "failed recreating aliceFundingManager")
|
require.NoError(t, err, "failed recreating aliceFundingManager")
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -11,12 +11,14 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/batch"
|
"github.com/lightningnetwork/lnd/batch"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lnutils"
|
"github.com/lightningnetwork/lnd/lnutils"
|
||||||
@@ -1138,12 +1140,14 @@ func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fundingScript, _, err := input.GenTaprootFundingScript(
|
fundingScript, _, err := input.GenTaprootFundingScript(
|
||||||
pubKey1, pubKey2, 0,
|
pubKey1, pubKey2, 0, fn.None[chainhash.Hash](),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(roasbeef): add tapscript root to gossip v1.5
|
||||||
|
|
||||||
return fundingScript, nil
|
return fundingScript, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+94
-43
@@ -11,8 +11,10 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnutils"
|
"github.com/lightningnetwork/lnd/lnutils"
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
@@ -199,26 +201,30 @@ func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenTaprootFundingScript constructs the taproot-native funding output that
|
// GenTaprootFundingScript constructs the taproot-native funding output that
|
||||||
// uses musig2 to create a single aggregated key to anchor the channel.
|
// uses MuSig2 to create a single aggregated key to anchor the channel.
|
||||||
func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
|
func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
|
||||||
amt int64) ([]byte, *wire.TxOut, error) {
|
amt int64, tapscriptRoot fn.Option[chainhash.Hash]) ([]byte,
|
||||||
|
*wire.TxOut, error) {
|
||||||
|
|
||||||
|
muSig2Opt := musig2.WithBIP86KeyTweak()
|
||||||
|
tapscriptRoot.WhenSome(func(scriptRoot chainhash.Hash) {
|
||||||
|
muSig2Opt = musig2.WithTaprootKeyTweak(scriptRoot[:])
|
||||||
|
})
|
||||||
|
|
||||||
// Similar to the existing p2wsh funding script, we'll always make sure
|
// Similar to the existing p2wsh funding script, we'll always make sure
|
||||||
// we sort the keys before any major operations. In order to ensure
|
// we sort the keys before any major operations. In order to ensure
|
||||||
// that there's no other way this output can be spent, we'll use a BIP
|
// that there's no other way this output can be spent, we'll use a BIP
|
||||||
// 86 tweak here during aggregation.
|
// 86 tweak here during aggregation, unless the user has explicitly
|
||||||
//
|
// specified a tapscript root.
|
||||||
// TODO(roasbeef): revisit if BIP 86 is needed here?
|
|
||||||
combinedKey, _, _, err := musig2.AggregateKeys(
|
combinedKey, _, _, err := musig2.AggregateKeys(
|
||||||
[]*btcec.PublicKey{aPub, bPub}, true,
|
[]*btcec.PublicKey{aPub, bPub}, true, muSig2Opt,
|
||||||
musig2.WithBIP86KeyTweak(),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to combine keys: %w", err)
|
return nil, nil, fmt.Errorf("unable to combine keys: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we have the combined key, we can create a taproot pkScript
|
// Now that we have the combined key, we can create a taproot pkScript
|
||||||
// from this, and then make the txout given the amount.
|
// from this, and then make the txOut given the amount.
|
||||||
pkScript, err := PayToTaprootScript(combinedKey.FinalKey)
|
pkScript, err := PayToTaprootScript(combinedKey.FinalKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to make taproot "+
|
return nil, nil, fmt.Errorf("unable to make taproot "+
|
||||||
@@ -228,7 +234,7 @@ func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
|
|||||||
txOut := wire.NewTxOut(amt, pkScript)
|
txOut := wire.NewTxOut(amt, pkScript)
|
||||||
|
|
||||||
// For the "witness program" we just return the raw pkScript since the
|
// For the "witness program" we just return the raw pkScript since the
|
||||||
// output we create can _only_ be spent with a musig2 signature.
|
// output we create can _only_ be spent with a MuSig2 signature.
|
||||||
return pkScript, txOut, nil
|
return pkScript, txOut, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,6 +646,13 @@ type HtlcScriptTree struct {
|
|||||||
// TimeoutTapLeaf is the tapleaf for the timeout path.
|
// TimeoutTapLeaf is the tapleaf for the timeout path.
|
||||||
TimeoutTapLeaf txscript.TapLeaf
|
TimeoutTapLeaf txscript.TapLeaf
|
||||||
|
|
||||||
|
// AuxLeaf is an auxiliary leaf that can be used to extend the base
|
||||||
|
// HTLC script tree with new spend paths, or just as extra commitment
|
||||||
|
// space. When present, this leaf will always be in the right-most area
|
||||||
|
// of the tapscript tree.
|
||||||
|
AuxLeaf AuxTapLeaf
|
||||||
|
|
||||||
|
// htlcType is the type of HTLC script this is.
|
||||||
htlcType htlcType
|
htlcType htlcType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,8 +733,8 @@ var _ TapscriptDescriptor = (*HtlcScriptTree)(nil)
|
|||||||
// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor
|
// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor
|
||||||
// the HTLC key for HTLCs on the sender's commitment.
|
// the HTLC key for HTLCs on the sender's commitment.
|
||||||
func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
||||||
revokeKey *btcec.PublicKey, payHash []byte,
|
revokeKey *btcec.PublicKey, payHash []byte, hType htlcType,
|
||||||
hType htlcType) (*HtlcScriptTree, error) {
|
auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
|
||||||
|
|
||||||
// First, we'll obtain the tap leaves for both the success and timeout
|
// First, we'll obtain the tap leaves for both the success and timeout
|
||||||
// path.
|
// path.
|
||||||
@@ -738,11 +751,14 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tapLeaves := []txscript.TapLeaf{successTapLeaf, timeoutTapLeaf}
|
||||||
|
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
|
||||||
|
tapLeaves = append(tapLeaves, l)
|
||||||
|
})
|
||||||
|
|
||||||
// With the two leaves obtained, we'll now make the tapscript tree,
|
// With the two leaves obtained, we'll now make the tapscript tree,
|
||||||
// then obtain the root from that
|
// then obtain the root from that
|
||||||
tapscriptTree := txscript.AssembleTaprootScriptTree(
|
tapscriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
|
||||||
successTapLeaf, timeoutTapLeaf,
|
|
||||||
)
|
|
||||||
|
|
||||||
tapScriptRoot := tapscriptTree.RootNode.TapHash()
|
tapScriptRoot := tapscriptTree.RootNode.TapHash()
|
||||||
|
|
||||||
@@ -761,6 +777,7 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
},
|
},
|
||||||
SuccessTapLeaf: successTapLeaf,
|
SuccessTapLeaf: successTapLeaf,
|
||||||
TimeoutTapLeaf: timeoutTapLeaf,
|
TimeoutTapLeaf: timeoutTapLeaf,
|
||||||
|
AuxLeaf: auxLeaf,
|
||||||
htlcType: hType,
|
htlcType: hType,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -795,7 +812,8 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
// unilaterally spend the created output.
|
// unilaterally spend the created output.
|
||||||
func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey,
|
func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey,
|
||||||
revokeKey *btcec.PublicKey, payHash []byte,
|
revokeKey *btcec.PublicKey, payHash []byte,
|
||||||
whoseCommit lntypes.ChannelParty) (*HtlcScriptTree, error) {
|
whoseCommit lntypes.ChannelParty, auxLeaf AuxTapLeaf) (*HtlcScriptTree,
|
||||||
|
error) {
|
||||||
|
|
||||||
var hType htlcType
|
var hType htlcType
|
||||||
if whoseCommit.IsLocal() {
|
if whoseCommit.IsLocal() {
|
||||||
@@ -808,8 +826,8 @@ func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey,
|
|||||||
// tree that includes the top level output script, as well as the two
|
// tree that includes the top level output script, as well as the two
|
||||||
// tap leaf paths.
|
// tap leaf paths.
|
||||||
return senderHtlcTapScriptTree(
|
return senderHtlcTapScriptTree(
|
||||||
senderHtlcKey, receiverHtlcKey, revokeKey, payHash,
|
senderHtlcKey, receiverHtlcKey, revokeKey, payHash, hType,
|
||||||
hType,
|
auxLeaf,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1279,8 +1297,8 @@ func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
|
|||||||
// receiverHtlcTapScriptTree builds the tapscript tree which is used to anchor
|
// receiverHtlcTapScriptTree builds the tapscript tree which is used to anchor
|
||||||
// the HTLC key for HTLCs on the receiver's commitment.
|
// the HTLC key for HTLCs on the receiver's commitment.
|
||||||
func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
||||||
revokeKey *btcec.PublicKey, payHash []byte,
|
revokeKey *btcec.PublicKey, payHash []byte, cltvExpiry uint32,
|
||||||
cltvExpiry uint32, hType htlcType) (*HtlcScriptTree, error) {
|
hType htlcType, auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
|
||||||
|
|
||||||
// First, we'll obtain the tap leaves for both the success and timeout
|
// First, we'll obtain the tap leaves for both the success and timeout
|
||||||
// path.
|
// path.
|
||||||
@@ -1297,11 +1315,14 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tapLeaves := []txscript.TapLeaf{timeoutTapLeaf, successTapLeaf}
|
||||||
|
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
|
||||||
|
tapLeaves = append(tapLeaves, l)
|
||||||
|
})
|
||||||
|
|
||||||
// With the two leaves obtained, we'll now make the tapscript tree,
|
// With the two leaves obtained, we'll now make the tapscript tree,
|
||||||
// then obtain the root from that
|
// then obtain the root from that
|
||||||
tapscriptTree := txscript.AssembleTaprootScriptTree(
|
tapscriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
|
||||||
timeoutTapLeaf, successTapLeaf,
|
|
||||||
)
|
|
||||||
|
|
||||||
tapScriptRoot := tapscriptTree.RootNode.TapHash()
|
tapScriptRoot := tapscriptTree.RootNode.TapHash()
|
||||||
|
|
||||||
@@ -1320,6 +1341,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
},
|
},
|
||||||
SuccessTapLeaf: successTapLeaf,
|
SuccessTapLeaf: successTapLeaf,
|
||||||
TimeoutTapLeaf: timeoutTapLeaf,
|
TimeoutTapLeaf: timeoutTapLeaf,
|
||||||
|
AuxLeaf: auxLeaf,
|
||||||
htlcType: hType,
|
htlcType: hType,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -1355,7 +1377,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
func ReceiverHTLCScriptTaproot(cltvExpiry uint32,
|
func ReceiverHTLCScriptTaproot(cltvExpiry uint32,
|
||||||
senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey,
|
senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey,
|
||||||
payHash []byte, whoseCommit lntypes.ChannelParty,
|
payHash []byte, whoseCommit lntypes.ChannelParty,
|
||||||
) (*HtlcScriptTree, error) {
|
auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
|
||||||
|
|
||||||
var hType htlcType
|
var hType htlcType
|
||||||
if whoseCommit.IsLocal() {
|
if whoseCommit.IsLocal() {
|
||||||
@@ -1369,7 +1391,7 @@ func ReceiverHTLCScriptTaproot(cltvExpiry uint32,
|
|||||||
// tap leaf paths.
|
// tap leaf paths.
|
||||||
return receiverHtlcTapScriptTree(
|
return receiverHtlcTapScriptTree(
|
||||||
senderHtlcKey, receiverHtlcKey, revocationKey, payHash,
|
senderHtlcKey, receiverHtlcKey, revocationKey, payHash,
|
||||||
cltvExpiry, hType,
|
cltvExpiry, hType, auxLeaf,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1598,9 +1620,9 @@ func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SecondLevelHtlcTapscriptTree construct the indexed tapscript tree needed to
|
// SecondLevelHtlcTapscriptTree construct the indexed tapscript tree needed to
|
||||||
// generate the taptweak to create the final output and also control block.
|
// generate the tap tweak to create the final output and also control block.
|
||||||
func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey,
|
func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, csvDelay uint32,
|
||||||
csvDelay uint32) (*txscript.IndexedTapScriptTree, error) {
|
auxLeaf AuxTapLeaf) (*txscript.IndexedTapScriptTree, error) {
|
||||||
|
|
||||||
// First grab the second level leaf script we need to create the top
|
// First grab the second level leaf script we need to create the top
|
||||||
// level output.
|
// level output.
|
||||||
@@ -1609,9 +1631,14 @@ func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tapLeaves := []txscript.TapLeaf{secondLevelTapLeaf}
|
||||||
|
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
|
||||||
|
tapLeaves = append(tapLeaves, l)
|
||||||
|
})
|
||||||
|
|
||||||
// Now that we have the sole second level script, we can create the
|
// Now that we have the sole second level script, we can create the
|
||||||
// tapscript tree that commits to both the leaves.
|
// tapscript tree that commits to both the leaves.
|
||||||
return txscript.AssembleTaprootScriptTree(secondLevelTapLeaf), nil
|
return txscript.AssembleTaprootScriptTree(tapLeaves...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaprootSecondLevelHtlcScript is the uniform script that's used as the output
|
// TaprootSecondLevelHtlcScript is the uniform script that's used as the output
|
||||||
@@ -1631,12 +1658,12 @@ func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey,
|
|||||||
//
|
//
|
||||||
// The keyspend path require knowledge of the top level revocation private key.
|
// The keyspend path require knowledge of the top level revocation private key.
|
||||||
func TaprootSecondLevelHtlcScript(revokeKey, delayKey *btcec.PublicKey,
|
func TaprootSecondLevelHtlcScript(revokeKey, delayKey *btcec.PublicKey,
|
||||||
csvDelay uint32) (*btcec.PublicKey, error) {
|
csvDelay uint32, auxLeaf AuxTapLeaf) (*btcec.PublicKey, error) {
|
||||||
|
|
||||||
// First, we'll make the tapscript tree that commits to the redemption
|
// First, we'll make the tapscript tree that commits to the redemption
|
||||||
// path.
|
// path.
|
||||||
tapScriptTree, err := SecondLevelHtlcTapscriptTree(
|
tapScriptTree, err := SecondLevelHtlcTapscriptTree(
|
||||||
delayKey, csvDelay,
|
delayKey, csvDelay, auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1661,17 +1688,21 @@ type SecondLevelScriptTree struct {
|
|||||||
|
|
||||||
// SuccessTapLeaf is the tapleaf for the redemption path.
|
// SuccessTapLeaf is the tapleaf for the redemption path.
|
||||||
SuccessTapLeaf txscript.TapLeaf
|
SuccessTapLeaf txscript.TapLeaf
|
||||||
|
|
||||||
|
// AuxLeaf is an optional leaf that can be used to extend the script
|
||||||
|
// tree.
|
||||||
|
AuxLeaf AuxTapLeaf
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaprootSecondLevelScriptTree constructs the tapscript tree used to spend the
|
// TaprootSecondLevelScriptTree constructs the tapscript tree used to spend the
|
||||||
// second level HTLC output.
|
// second level HTLC output.
|
||||||
func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey,
|
func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey,
|
||||||
csvDelay uint32) (*SecondLevelScriptTree, error) {
|
csvDelay uint32, auxLeaf AuxTapLeaf) (*SecondLevelScriptTree, error) {
|
||||||
|
|
||||||
// First, we'll make the tapscript tree that commits to the redemption
|
// First, we'll make the tapscript tree that commits to the redemption
|
||||||
// path.
|
// path.
|
||||||
tapScriptTree, err := SecondLevelHtlcTapscriptTree(
|
tapScriptTree, err := SecondLevelHtlcTapscriptTree(
|
||||||
delayKey, csvDelay,
|
delayKey, csvDelay, auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1692,6 +1723,7 @@ func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey,
|
|||||||
InternalKey: revokeKey,
|
InternalKey: revokeKey,
|
||||||
},
|
},
|
||||||
SuccessTapLeaf: tapScriptTree.LeafMerkleProofs[0].TapLeaf,
|
SuccessTapLeaf: tapScriptTree.LeafMerkleProofs[0].TapLeaf,
|
||||||
|
AuxLeaf: auxLeaf,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2073,6 +2105,12 @@ type CommitScriptTree struct {
|
|||||||
// RevocationLeaf is the leaf used to spend the output with the
|
// RevocationLeaf is the leaf used to spend the output with the
|
||||||
// revocation key signature.
|
// revocation key signature.
|
||||||
RevocationLeaf txscript.TapLeaf
|
RevocationLeaf txscript.TapLeaf
|
||||||
|
|
||||||
|
// AuxLeaf is an auxiliary leaf that can be used to extend the base
|
||||||
|
// commitment script tree with new spend paths, or just as extra
|
||||||
|
// commitment space. When present, this leaf will always be in the
|
||||||
|
// left-most or right-most area of the tapscript tree.
|
||||||
|
AuxLeaf AuxTapLeaf
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile time check to ensure CommitScriptTree implements the
|
// A compile time check to ensure CommitScriptTree implements the
|
||||||
@@ -2137,8 +2175,9 @@ func (c *CommitScriptTree) Tree() ScriptTree {
|
|||||||
|
|
||||||
// NewLocalCommitScriptTree returns a new CommitScript tree that can be used to
|
// NewLocalCommitScriptTree returns a new CommitScript tree that can be used to
|
||||||
// create and spend the commitment output for the local party.
|
// create and spend the commitment output for the local party.
|
||||||
func NewLocalCommitScriptTree(csvTimeout uint32,
|
func NewLocalCommitScriptTree(csvTimeout uint32, selfKey,
|
||||||
selfKey, revokeKey *btcec.PublicKey) (*CommitScriptTree, error) {
|
revokeKey *btcec.PublicKey, auxLeaf AuxTapLeaf) (*CommitScriptTree,
|
||||||
|
error) {
|
||||||
|
|
||||||
// First, we'll need to construct the tapLeaf that'll be our delay CSV
|
// First, we'll need to construct the tapLeaf that'll be our delay CSV
|
||||||
// clause.
|
// clause.
|
||||||
@@ -2158,9 +2197,13 @@ func NewLocalCommitScriptTree(csvTimeout uint32,
|
|||||||
// the two leaves, and then obtain a root from that.
|
// the two leaves, and then obtain a root from that.
|
||||||
delayTapLeaf := txscript.NewBaseTapLeaf(delayScript)
|
delayTapLeaf := txscript.NewBaseTapLeaf(delayScript)
|
||||||
revokeTapLeaf := txscript.NewBaseTapLeaf(revokeScript)
|
revokeTapLeaf := txscript.NewBaseTapLeaf(revokeScript)
|
||||||
tapScriptTree := txscript.AssembleTaprootScriptTree(
|
|
||||||
delayTapLeaf, revokeTapLeaf,
|
tapLeaves := []txscript.TapLeaf{delayTapLeaf, revokeTapLeaf}
|
||||||
)
|
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
|
||||||
|
tapLeaves = append(tapLeaves, l)
|
||||||
|
})
|
||||||
|
|
||||||
|
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
|
||||||
tapScriptRoot := tapScriptTree.RootNode.TapHash()
|
tapScriptRoot := tapScriptTree.RootNode.TapHash()
|
||||||
|
|
||||||
// Now that we have our root, we can arrive at the final output script
|
// Now that we have our root, we can arrive at the final output script
|
||||||
@@ -2178,6 +2221,7 @@ func NewLocalCommitScriptTree(csvTimeout uint32,
|
|||||||
},
|
},
|
||||||
SettleLeaf: delayTapLeaf,
|
SettleLeaf: delayTapLeaf,
|
||||||
RevocationLeaf: revokeTapLeaf,
|
RevocationLeaf: revokeTapLeaf,
|
||||||
|
AuxLeaf: auxLeaf,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2247,7 +2291,7 @@ func TaprootCommitScriptToSelf(csvTimeout uint32,
|
|||||||
selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) {
|
selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) {
|
||||||
|
|
||||||
commitScriptTree, err := NewLocalCommitScriptTree(
|
commitScriptTree, err := NewLocalCommitScriptTree(
|
||||||
csvTimeout, selfKey, revokeKey,
|
csvTimeout, selfKey, revokeKey, NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -2573,7 +2617,7 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
|
|||||||
// NewRemoteCommitScriptTree constructs a new script tree for the remote party
|
// NewRemoteCommitScriptTree constructs a new script tree for the remote party
|
||||||
// to sweep their funds after a hard coded 1 block delay.
|
// to sweep their funds after a hard coded 1 block delay.
|
||||||
func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
|
func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
|
||||||
) (*CommitScriptTree, error) {
|
auxLeaf AuxTapLeaf) (*CommitScriptTree, error) {
|
||||||
|
|
||||||
// First, construct the remote party's tapscript they'll use to sweep
|
// First, construct the remote party's tapscript they'll use to sweep
|
||||||
// their outputs.
|
// their outputs.
|
||||||
@@ -2589,10 +2633,16 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tapLeaf := txscript.NewBaseTapLeaf(remoteScript)
|
||||||
|
|
||||||
|
tapLeaves := []txscript.TapLeaf{tapLeaf}
|
||||||
|
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
|
||||||
|
tapLeaves = append(tapLeaves, l)
|
||||||
|
})
|
||||||
|
|
||||||
// With this script constructed, we'll map that into a tapLeaf, then
|
// With this script constructed, we'll map that into a tapLeaf, then
|
||||||
// make a new tapscript root from that.
|
// make a new tapscript root from that.
|
||||||
tapLeaf := txscript.NewBaseTapLeaf(remoteScript)
|
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
|
||||||
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf)
|
|
||||||
tapScriptRoot := tapScriptTree.RootNode.TapHash()
|
tapScriptRoot := tapScriptTree.RootNode.TapHash()
|
||||||
|
|
||||||
// Now that we have our root, we can arrive at the final output script
|
// Now that we have our root, we can arrive at the final output script
|
||||||
@@ -2609,6 +2659,7 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
|
|||||||
InternalKey: &TaprootNUMSKey,
|
InternalKey: &TaprootNUMSKey,
|
||||||
},
|
},
|
||||||
SettleLeaf: tapLeaf,
|
SettleLeaf: tapLeaf,
|
||||||
|
AuxLeaf: auxLeaf,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2625,9 +2676,9 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
|
|||||||
// <remotepubkey> OP_CHECKSIG
|
// <remotepubkey> OP_CHECKSIG
|
||||||
// 1 OP_CHECKSEQUENCEVERIFY OP_DROP
|
// 1 OP_CHECKSEQUENCEVERIFY OP_DROP
|
||||||
func TaprootCommitScriptToRemote(remoteKey *btcec.PublicKey,
|
func TaprootCommitScriptToRemote(remoteKey *btcec.PublicKey,
|
||||||
) (*btcec.PublicKey, error) {
|
auxLeaf AuxTapLeaf) (*btcec.PublicKey, error) {
|
||||||
|
|
||||||
commitScriptTree, err := NewRemoteCommitScriptTree(remoteKey)
|
commitScriptTree, err := NewRemoteCommitScriptTree(remoteKey, auxLeaf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-10
@@ -853,7 +853,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
signer := &dummySigner{}
|
signer := &dummySigner{}
|
||||||
commitScriptTree, err := input.NewLocalCommitScriptTree(
|
commitScriptTree, err := input.NewLocalCommitScriptTree(
|
||||||
testCSVDelay, testKey.PubKey(),
|
testCSVDelay, testKey.PubKey(),
|
||||||
testKey.PubKey(),
|
testKey.PubKey(), input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -887,7 +887,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
signer := &dummySigner{}
|
signer := &dummySigner{}
|
||||||
commitScriptTree, err := input.NewLocalCommitScriptTree(
|
commitScriptTree, err := input.NewLocalCommitScriptTree(
|
||||||
testCSVDelay, testKey.PubKey(),
|
testCSVDelay, testKey.PubKey(),
|
||||||
testKey.PubKey(),
|
testKey.PubKey(), input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -921,7 +921,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
signer := &dummySigner{}
|
signer := &dummySigner{}
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
commitScriptTree, err := input.NewRemoteCommitScriptTree(
|
commitScriptTree, err := input.NewRemoteCommitScriptTree(
|
||||||
testKey.PubKey(),
|
testKey.PubKey(), input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -988,6 +988,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
|
|
||||||
scriptTree, err := input.SecondLevelHtlcTapscriptTree(
|
scriptTree, err := input.SecondLevelHtlcTapscriptTree(
|
||||||
testKey.PubKey(), testCSVDelay,
|
testKey.PubKey(), testCSVDelay,
|
||||||
|
input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1027,6 +1028,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
|
|
||||||
scriptTree, err := input.SecondLevelHtlcTapscriptTree(
|
scriptTree, err := input.SecondLevelHtlcTapscriptTree(
|
||||||
testKey.PubKey(), testCSVDelay,
|
testKey.PubKey(), testCSVDelay,
|
||||||
|
input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1075,6 +1077,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
||||||
senderKey.PubKey(), receiverKey.PubKey(),
|
senderKey.PubKey(), receiverKey.PubKey(),
|
||||||
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
||||||
|
input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1116,7 +1119,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
||||||
testCLTVExpiry, senderKey.PubKey(),
|
testCLTVExpiry, senderKey.PubKey(),
|
||||||
receiverKey.PubKey(), revokeKey.PubKey(),
|
receiverKey.PubKey(), revokeKey.PubKey(),
|
||||||
payHash[:], lntypes.Remote,
|
payHash[:], lntypes.Remote, input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1158,7 +1161,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
||||||
testCLTVExpiry, senderKey.PubKey(),
|
testCLTVExpiry, senderKey.PubKey(),
|
||||||
receiverKey.PubKey(), revokeKey.PubKey(),
|
receiverKey.PubKey(), revokeKey.PubKey(),
|
||||||
payHash[:], lntypes.Remote,
|
payHash[:], lntypes.Remote, input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1205,6 +1208,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
||||||
senderKey.PubKey(), receiverKey.PubKey(),
|
senderKey.PubKey(), receiverKey.PubKey(),
|
||||||
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
||||||
|
input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1265,6 +1269,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
||||||
senderKey.PubKey(), receiverKey.PubKey(),
|
senderKey.PubKey(), receiverKey.PubKey(),
|
||||||
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
||||||
|
input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1310,7 +1315,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||||||
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
||||||
testCLTVExpiry, senderKey.PubKey(),
|
testCLTVExpiry, senderKey.PubKey(),
|
||||||
receiverKey.PubKey(), revokeKey.PubKey(),
|
receiverKey.PubKey(), revokeKey.PubKey(),
|
||||||
payHash[:], lntypes.Remote,
|
payHash[:], lntypes.Remote, input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1383,7 +1388,7 @@ func genTimeoutTx(t *testing.T,
|
|||||||
// Create the unsigned timeout tx.
|
// Create the unsigned timeout tx.
|
||||||
timeoutTx, err := lnwallet.CreateHtlcTimeoutTx(
|
timeoutTx, err := lnwallet.CreateHtlcTimeoutTx(
|
||||||
chanType, false, testOutPoint, testAmt, testCLTVExpiry,
|
chanType, false, testOutPoint, testAmt, testCLTVExpiry,
|
||||||
testCSVDelay, 0, testPubkey, testPubkey,
|
testCSVDelay, 0, testPubkey, testPubkey, input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1396,7 +1401,7 @@ func genTimeoutTx(t *testing.T,
|
|||||||
if chanType.IsTaproot() {
|
if chanType.IsTaproot() {
|
||||||
tapscriptTree, err = input.SenderHTLCScriptTaproot(
|
tapscriptTree, err = input.SenderHTLCScriptTaproot(
|
||||||
testPubkey, testPubkey, testPubkey, testHash160,
|
testPubkey, testPubkey, testPubkey, testHash160,
|
||||||
lntypes.Remote,
|
lntypes.Remote, input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1452,7 +1457,7 @@ func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx {
|
|||||||
// Create the unsigned success tx.
|
// Create the unsigned success tx.
|
||||||
successTx, err := lnwallet.CreateHtlcSuccessTx(
|
successTx, err := lnwallet.CreateHtlcSuccessTx(
|
||||||
chanType, false, testOutPoint, testAmt, testCSVDelay, 0,
|
chanType, false, testOutPoint, testAmt, testCSVDelay, 0,
|
||||||
testPubkey, testPubkey,
|
testPubkey, testPubkey, input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1465,7 +1470,7 @@ func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx {
|
|||||||
if chanType.IsTaproot() {
|
if chanType.IsTaproot() {
|
||||||
tapscriptTree, err = input.ReceiverHTLCScriptTaproot(
|
tapscriptTree, err = input.ReceiverHTLCScriptTaproot(
|
||||||
testCLTVExpiry, testPubkey, testPubkey, testPubkey,
|
testCLTVExpiry, testPubkey, testPubkey, testPubkey,
|
||||||
testHash160, lntypes.Remote,
|
testHash160, lntypes.Remote, input.NoneTapLeaf(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,6 +22,33 @@ const (
|
|||||||
PubKeyFormatCompressedOdd byte = 0x03
|
PubKeyFormatCompressedOdd byte = 0x03
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AuxTapLeaf is a type alias for an optional tapscript leaf that may be added
|
||||||
|
// to the tapscript tree of HTLC and commitment outputs.
|
||||||
|
type AuxTapLeaf = fn.Option[txscript.TapLeaf]
|
||||||
|
|
||||||
|
// NoneTapLeaf returns an empty optional tapscript leaf.
|
||||||
|
func NoneTapLeaf() AuxTapLeaf {
|
||||||
|
return fn.None[txscript.TapLeaf]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HtlcIndex represents the monotonically increasing counter that is used to
|
||||||
|
// identify HTLCs created by a peer.
|
||||||
|
type HtlcIndex = uint64
|
||||||
|
|
||||||
|
// HtlcAuxLeaf is a type that represents an auxiliary leaf for an HTLC output.
|
||||||
|
// An HTLC may have up to two aux leaves: one for the output on the commitment
|
||||||
|
// transaction, and one for the second level HTLC.
|
||||||
|
type HtlcAuxLeaf struct {
|
||||||
|
AuxTapLeaf
|
||||||
|
|
||||||
|
// SecondLevelLeaf is the auxiliary leaf for the second level HTLC
|
||||||
|
// success or timeout transaction.
|
||||||
|
SecondLevelLeaf AuxTapLeaf
|
||||||
|
}
|
||||||
|
|
||||||
|
// HtlcAuxLeaves is a type alias for a map of optional tapscript leaves.
|
||||||
|
type HtlcAuxLeaves = map[HtlcIndex]HtlcAuxLeaf
|
||||||
|
|
||||||
// NewTxSigHashesV0Only returns a new txscript.TxSigHashes instance that will
|
// NewTxSigHashesV0Only returns a new txscript.TxSigHashes instance that will
|
||||||
// only calculate the sighash midstate values for segwit v0 inputs and can
|
// only calculate the sighash midstate values for segwit v0 inputs and can
|
||||||
// therefore never be used for transactions that want to spend segwit v1
|
// therefore never be used for transactions that want to spend segwit v1
|
||||||
|
|||||||
+137
-52
@@ -1,13 +1,16 @@
|
|||||||
package input
|
package input
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -31,7 +34,9 @@ type testSenderHtlcScriptTree struct {
|
|||||||
htlcAmt int64
|
htlcAmt int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestSenderHtlcScriptTree(t *testing.T) *testSenderHtlcScriptTree {
|
func newTestSenderHtlcScriptTree(t *testing.T,
|
||||||
|
auxLeaf AuxTapLeaf) *testSenderHtlcScriptTree {
|
||||||
|
|
||||||
var preImage lntypes.Preimage
|
var preImage lntypes.Preimage
|
||||||
_, err := rand.Read(preImage[:])
|
_, err := rand.Read(preImage[:])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -48,7 +53,7 @@ func newTestSenderHtlcScriptTree(t *testing.T) *testSenderHtlcScriptTree {
|
|||||||
payHash := preImage.Hash()
|
payHash := preImage.Hash()
|
||||||
htlcScriptTree, err := SenderHTLCScriptTaproot(
|
htlcScriptTree, err := SenderHTLCScriptTaproot(
|
||||||
senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(),
|
senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(),
|
||||||
payHash[:], lntypes.Remote,
|
payHash[:], lntypes.Remote, auxLeaf,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -207,13 +212,9 @@ func htlcSenderTimeoutWitnessGen(sigHash txscript.SigHashType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTaprootSenderHtlcSpend tests that all the positive and negative paths
|
func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||||
// for the sender HTLC tapscript tree work as expected.
|
|
||||||
func TestTaprootSenderHtlcSpend(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// First, create a new test script tree.
|
// First, create a new test script tree.
|
||||||
htlcScriptTree := newTestSenderHtlcScriptTree(t)
|
htlcScriptTree := newTestSenderHtlcScriptTree(t, auxLeaf)
|
||||||
|
|
||||||
spendTx := wire.NewMsgTx(2)
|
spendTx := wire.NewMsgTx(2)
|
||||||
spendTx.AddTxIn(&wire.TxIn{})
|
spendTx.AddTxIn(&wire.TxIn{})
|
||||||
@@ -432,6 +433,26 @@ func TestTaprootSenderHtlcSpend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTaprootSenderHtlcSpend tests that all the positive and negative paths
|
||||||
|
// for the sender HTLC tapscript tree work as expected.
|
||||||
|
func TestTaprootSenderHtlcSpend(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, hasAuxLeaf := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var auxLeaf AuxTapLeaf
|
||||||
|
if hasAuxLeaf {
|
||||||
|
auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
|
||||||
|
bytes.Repeat([]byte{0x01}, 32),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
testTaprootSenderHtlcSpend(t, auxLeaf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testReceiverHtlcScriptTree struct {
|
type testReceiverHtlcScriptTree struct {
|
||||||
preImage lntypes.Preimage
|
preImage lntypes.Preimage
|
||||||
|
|
||||||
@@ -452,7 +473,9 @@ type testReceiverHtlcScriptTree struct {
|
|||||||
lockTime int32
|
lockTime int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestReceiverHtlcScriptTree(t *testing.T) *testReceiverHtlcScriptTree {
|
func newTestReceiverHtlcScriptTree(t *testing.T,
|
||||||
|
auxLeaf AuxTapLeaf) *testReceiverHtlcScriptTree {
|
||||||
|
|
||||||
var preImage lntypes.Preimage
|
var preImage lntypes.Preimage
|
||||||
_, err := rand.Read(preImage[:])
|
_, err := rand.Read(preImage[:])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -471,7 +494,7 @@ func newTestReceiverHtlcScriptTree(t *testing.T) *testReceiverHtlcScriptTree {
|
|||||||
payHash := preImage.Hash()
|
payHash := preImage.Hash()
|
||||||
htlcScriptTree, err := ReceiverHTLCScriptTaproot(
|
htlcScriptTree, err := ReceiverHTLCScriptTaproot(
|
||||||
cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(),
|
cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(),
|
||||||
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
revokeKey.PubKey(), payHash[:], lntypes.Remote, auxLeaf,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -629,15 +652,11 @@ func htlcReceiverSuccessWitnessGen(sigHash txscript.SigHashType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTaprootReceiverHtlcSpend tests that all possible paths for redeeming an
|
func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||||
// accepted HTLC (on the commitment transaction) of the receiver work properly.
|
|
||||||
func TestTaprootReceiverHtlcSpend(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// We'll start by creating the HTLC script tree (contains all 3 valid
|
// We'll start by creating the HTLC script tree (contains all 3 valid
|
||||||
// spend paths), and also a mock spend transaction that we'll be
|
// spend paths), and also a mock spend transaction that we'll be
|
||||||
// signing below.
|
// signing below.
|
||||||
htlcScriptTree := newTestReceiverHtlcScriptTree(t)
|
htlcScriptTree := newTestReceiverHtlcScriptTree(t, auxLeaf)
|
||||||
|
|
||||||
// TODO(roasbeef): issue with revoke key??? ctrl block even/odd
|
// TODO(roasbeef): issue with revoke key??? ctrl block even/odd
|
||||||
|
|
||||||
@@ -891,6 +910,28 @@ func TestTaprootReceiverHtlcSpend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTaprootReceiverHtlcSpend tests that all possible paths for redeeming an
|
||||||
|
// accepted HTLC (on the commitment transaction) of the receiver work properly.
|
||||||
|
func TestTaprootReceiverHtlcSpend(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, hasAuxLeaf := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var auxLeaf AuxTapLeaf
|
||||||
|
if hasAuxLeaf {
|
||||||
|
auxLeaf = fn.Some(
|
||||||
|
txscript.NewBaseTapLeaf(
|
||||||
|
bytes.Repeat([]byte{0x01}, 32),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
testTaprootReceiverHtlcSpend(t, auxLeaf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testCommitScriptTree struct {
|
type testCommitScriptTree struct {
|
||||||
csvDelay uint32
|
csvDelay uint32
|
||||||
|
|
||||||
@@ -905,7 +946,9 @@ type testCommitScriptTree struct {
|
|||||||
*CommitScriptTree
|
*CommitScriptTree
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestCommitScriptTree(local bool) (*testCommitScriptTree, error) {
|
func newTestCommitScriptTree(local bool,
|
||||||
|
auxLeaf AuxTapLeaf) (*testCommitScriptTree, error) {
|
||||||
|
|
||||||
selfKey, err := btcec.NewPrivateKey()
|
selfKey, err := btcec.NewPrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -925,10 +968,11 @@ func newTestCommitScriptTree(local bool) (*testCommitScriptTree, error) {
|
|||||||
if local {
|
if local {
|
||||||
commitScriptTree, err = NewLocalCommitScriptTree(
|
commitScriptTree, err = NewLocalCommitScriptTree(
|
||||||
csvDelay, selfKey.PubKey(), revokeKey.PubKey(),
|
csvDelay, selfKey.PubKey(), revokeKey.PubKey(),
|
||||||
|
auxLeaf,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
commitScriptTree, err = NewRemoteCommitScriptTree(
|
commitScriptTree, err = NewRemoteCommitScriptTree(
|
||||||
selfKey.PubKey(),
|
selfKey.PubKey(), auxLeaf,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1020,12 +1064,8 @@ func localCommitRevokeWitGen(sigHash txscript.SigHashType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTaprootCommitScriptToSelf tests that the taproot script for redeeming
|
func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||||
// one's output after a force close behaves as expected.
|
commitScriptTree, err := newTestCommitScriptTree(true, auxLeaf)
|
||||||
func TestTaprootCommitScriptToSelf(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
commitScriptTree, err := newTestCommitScriptTree(true)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
spendTx := wire.NewMsgTx(2)
|
spendTx := wire.NewMsgTx(2)
|
||||||
@@ -1187,6 +1227,26 @@ func TestTaprootCommitScriptToSelf(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTaprootCommitScriptToSelf tests that the taproot script for redeeming
|
||||||
|
// one's output after a force close behaves as expected.
|
||||||
|
func TestTaprootCommitScriptToSelf(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, hasAuxLeaf := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var auxLeaf AuxTapLeaf
|
||||||
|
if hasAuxLeaf {
|
||||||
|
auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
|
||||||
|
bytes.Repeat([]byte{0x01}, 32),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
testTaprootCommitScriptToSelf(t, auxLeaf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func remoteCommitSweepWitGen(sigHash txscript.SigHashType,
|
func remoteCommitSweepWitGen(sigHash txscript.SigHashType,
|
||||||
commitScriptTree *testCommitScriptTree) witnessGen {
|
commitScriptTree *testCommitScriptTree) witnessGen {
|
||||||
|
|
||||||
@@ -1220,12 +1280,8 @@ func remoteCommitSweepWitGen(sigHash txscript.SigHashType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTaprootCommitScriptRemote tests that the remote party can properly sweep
|
func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||||
// their output after force close.
|
commitScriptTree, err := newTestCommitScriptTree(false, auxLeaf)
|
||||||
func TestTaprootCommitScriptRemote(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
commitScriptTree, err := newTestCommitScriptTree(false)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
spendTx := wire.NewMsgTx(2)
|
spendTx := wire.NewMsgTx(2)
|
||||||
@@ -1364,6 +1420,26 @@ func TestTaprootCommitScriptRemote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTaprootCommitScriptRemote tests that the remote party can properly sweep
|
||||||
|
// their output after force close.
|
||||||
|
func TestTaprootCommitScriptRemote(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, hasAuxLeaf := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var auxLeaf AuxTapLeaf
|
||||||
|
if hasAuxLeaf {
|
||||||
|
auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
|
||||||
|
bytes.Repeat([]byte{0x01}, 32),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
testTaprootCommitScriptRemote(t, auxLeaf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testAnchorScriptTree struct {
|
type testAnchorScriptTree struct {
|
||||||
sweepKey *btcec.PrivateKey
|
sweepKey *btcec.PrivateKey
|
||||||
|
|
||||||
@@ -1599,25 +1675,21 @@ type testSecondLevelHtlcTree struct {
|
|||||||
tapScriptRoot []byte
|
tapScriptRoot []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) {
|
func newTestSecondLevelHtlcTree(t *testing.T,
|
||||||
|
auxLeaf AuxTapLeaf) *testSecondLevelHtlcTree {
|
||||||
|
|
||||||
delayKey, err := btcec.NewPrivateKey()
|
delayKey, err := btcec.NewPrivateKey()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
revokeKey, err := btcec.NewPrivateKey()
|
revokeKey, err := btcec.NewPrivateKey()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const csvDelay = 6
|
const csvDelay = 6
|
||||||
|
|
||||||
scriptTree, err := SecondLevelHtlcTapscriptTree(
|
scriptTree, err := SecondLevelHtlcTapscriptTree(
|
||||||
delayKey.PubKey(), csvDelay,
|
delayKey.PubKey(), csvDelay, auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tapScriptRoot := scriptTree.RootNode.TapHash()
|
tapScriptRoot := scriptTree.RootNode.TapHash()
|
||||||
|
|
||||||
@@ -1626,9 +1698,7 @@ func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
pkScript, err := PayToTaprootScript(htlcKey)
|
pkScript, err := PayToTaprootScript(htlcKey)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const amt = 100
|
const amt = 100
|
||||||
|
|
||||||
@@ -1643,7 +1713,7 @@ func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) {
|
|||||||
amt: amt,
|
amt: amt,
|
||||||
scriptTree: scriptTree,
|
scriptTree: scriptTree,
|
||||||
tapScriptRoot: tapScriptRoot[:],
|
tapScriptRoot: tapScriptRoot[:],
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func secondLevelHtlcSuccessWitGen(sigHash txscript.SigHashType,
|
func secondLevelHtlcSuccessWitGen(sigHash txscript.SigHashType,
|
||||||
@@ -1713,13 +1783,8 @@ func secondLevelHtlcRevokeWitnessgen(sigHash txscript.SigHashType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTaprootSecondLevelHtlcScript tests that a channel peer can properly
|
func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||||
// spend the second level HTLC script to resolve HTLCs.
|
htlcScriptTree := newTestSecondLevelHtlcTree(t, auxLeaf)
|
||||||
func TestTaprootSecondLevelHtlcScript(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
htlcScriptTree, err := newTestSecondLevelHtlcTree()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
spendTx := wire.NewMsgTx(2)
|
spendTx := wire.NewMsgTx(2)
|
||||||
spendTx.AddTxIn(&wire.TxIn{})
|
spendTx.AddTxIn(&wire.TxIn{})
|
||||||
@@ -1879,3 +1944,23 @@ func TestTaprootSecondLevelHtlcScript(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTaprootSecondLevelHtlcScript tests that a channel peer can properly
|
||||||
|
// spend the second level HTLC script to resolve HTLCs.
|
||||||
|
func TestTaprootSecondLevelHtlcScript(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, hasAuxLeaf := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var auxLeaf AuxTapLeaf
|
||||||
|
if hasAuxLeaf {
|
||||||
|
auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
|
||||||
|
bytes.Repeat([]byte{0x01}, 32),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
testTaprootSecondLevelHtlcScript(t, auxLeaf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/chainreg"
|
"github.com/lightningnetwork/lnd/chainreg"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/funding"
|
"github.com/lightningnetwork/lnd/funding"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/labels"
|
"github.com/lightningnetwork/lnd/labels"
|
||||||
@@ -1192,6 +1193,7 @@ func deriveFundingShim(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
|
|||||||
|
|
||||||
_, fundingOutput, err = input.GenTaprootFundingScript(
|
_, fundingOutput, err = input.GenTaprootFundingScript(
|
||||||
carolKey, daveKey, int64(chanSize),
|
carolKey, daveKey, int64(chanSize),
|
||||||
|
fn.None[chainhash.Hash](),
|
||||||
)
|
)
|
||||||
require.NoError(ht, err)
|
require.NoError(ht, err)
|
||||||
|
|
||||||
|
|||||||
@@ -456,7 +456,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
|
|||||||
defer cleanUp()
|
defer cleanUp()
|
||||||
|
|
||||||
partialChainControl, walletConfig, cleanUp, err := implCfg.BuildWalletConfig(
|
partialChainControl, walletConfig, cleanUp, err := implCfg.BuildWalletConfig(
|
||||||
ctx, dbs, interceptorChain, grpcListeners,
|
ctx, dbs, &implCfg.AuxComponents, interceptorChain,
|
||||||
|
grpcListeners,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mkErr("error creating wallet config: %v", err)
|
return mkErr("error creating wallet config: %v", err)
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package lnwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/tlv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommitSortFunc is a function type alias for a function that sorts the
|
||||||
|
// commitment transaction outputs. The second parameter is a list of CLTV
|
||||||
|
// timeouts that must correspond to the number of transaction outputs, with the
|
||||||
|
// value of 0 for non-HTLC outputs. The HTLC indexes are needed to have a
|
||||||
|
// deterministic sort value for HTLCs that have the identical amount, CLTV
|
||||||
|
// timeout and payment hash (e.g. multiple MPP shards of the same payment, where
|
||||||
|
// the on-chain script would be identical).
|
||||||
|
type CommitSortFunc func(tx *wire.MsgTx, cltvs []uint32,
|
||||||
|
indexes []input.HtlcIndex) error
|
||||||
|
|
||||||
|
// DefaultCommitSort is the default commitment sort function that sorts the
|
||||||
|
// commitment transaction inputs and outputs according to BIP69. The second
|
||||||
|
// parameter is a list of CLTV timeouts that must correspond to the number of
|
||||||
|
// transaction outputs, with the value of 0 for non-HTLC outputs. The third
|
||||||
|
// parameter is unused for the default sort function.
|
||||||
|
func DefaultCommitSort(tx *wire.MsgTx, cltvs []uint32,
|
||||||
|
_ []input.HtlcIndex) error {
|
||||||
|
|
||||||
|
InPlaceCommitSort(tx, cltvs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitAuxLeaves stores two potential auxiliary leaves for the remote and
|
||||||
|
// local output that may be used to augment the final tapscript trees of the
|
||||||
|
// commitment transaction.
|
||||||
|
type CommitAuxLeaves struct {
|
||||||
|
// LocalAuxLeaf is the local party's auxiliary leaf.
|
||||||
|
LocalAuxLeaf input.AuxTapLeaf
|
||||||
|
|
||||||
|
// RemoteAuxLeaf is the remote party's auxiliary leaf.
|
||||||
|
RemoteAuxLeaf input.AuxTapLeaf
|
||||||
|
|
||||||
|
// OutgoingHTLCLeaves is the set of aux leaves for the outgoing HTLCs
|
||||||
|
// on this commitment transaction.
|
||||||
|
OutgoingHtlcLeaves input.HtlcAuxLeaves
|
||||||
|
|
||||||
|
// IncomingHTLCLeaves is the set of aux leaves for the incoming HTLCs
|
||||||
|
// on this commitment transaction.
|
||||||
|
IncomingHtlcLeaves input.HtlcAuxLeaves
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuxChanState is a struct that holds certain fields of the
|
||||||
|
// channeldb.OpenChannel struct that are used by the aux components. The data
|
||||||
|
// is copied over to prevent accidental mutation of the original channel state.
|
||||||
|
type AuxChanState struct {
|
||||||
|
// ChanType denotes which type of channel this is.
|
||||||
|
ChanType channeldb.ChannelType
|
||||||
|
|
||||||
|
// FundingOutpoint is the outpoint of the final funding transaction.
|
||||||
|
// This value uniquely and globally identifies the channel within the
|
||||||
|
// target blockchain as specified by the chain hash parameter.
|
||||||
|
FundingOutpoint wire.OutPoint
|
||||||
|
|
||||||
|
// ShortChannelID encodes the exact location in the chain in which the
|
||||||
|
// channel was initially confirmed. This includes: the block height,
|
||||||
|
// transaction index, and the output within the target transaction.
|
||||||
|
//
|
||||||
|
// If IsZeroConf(), then this will the "base" (very first) ALIAS scid
|
||||||
|
// and the confirmed SCID will be stored in ConfirmedScid.
|
||||||
|
ShortChannelID lnwire.ShortChannelID
|
||||||
|
|
||||||
|
// IsInitiator is a bool which indicates if we were the original
|
||||||
|
// initiator for the channel. This value may affect how higher levels
|
||||||
|
// negotiate fees, or close the channel.
|
||||||
|
IsInitiator bool
|
||||||
|
|
||||||
|
// Capacity is the total capacity of this channel.
|
||||||
|
Capacity btcutil.Amount
|
||||||
|
|
||||||
|
// LocalChanCfg is the channel configuration for the local node.
|
||||||
|
LocalChanCfg channeldb.ChannelConfig
|
||||||
|
|
||||||
|
// RemoteChanCfg is the channel configuration for the remote node.
|
||||||
|
RemoteChanCfg channeldb.ChannelConfig
|
||||||
|
|
||||||
|
// ThawHeight is the height when a frozen channel once again becomes a
|
||||||
|
// normal channel. If this is zero, then there're no restrictions on
|
||||||
|
// this channel. If the value is lower than 500,000, then it's
|
||||||
|
// interpreted as a relative height, or an absolute height otherwise.
|
||||||
|
ThawHeight uint32
|
||||||
|
|
||||||
|
// TapscriptRoot is an optional tapscript root used to derive the MuSig2
|
||||||
|
// funding output.
|
||||||
|
TapscriptRoot fn.Option[chainhash.Hash]
|
||||||
|
|
||||||
|
// CustomBlob is an optional blob that can be used to store information
|
||||||
|
// specific to a custom channel type. This information is only created
|
||||||
|
// at channel funding time, and after wards is to be considered
|
||||||
|
// immutable.
|
||||||
|
CustomBlob fn.Option[tlv.Blob]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuxChanState creates a new AuxChanState from the given channel state.
|
||||||
|
func NewAuxChanState(chanState *channeldb.OpenChannel) AuxChanState {
|
||||||
|
return AuxChanState{
|
||||||
|
ChanType: chanState.ChanType,
|
||||||
|
FundingOutpoint: chanState.FundingOutpoint,
|
||||||
|
ShortChannelID: chanState.ShortChannelID,
|
||||||
|
IsInitiator: chanState.IsInitiator,
|
||||||
|
Capacity: chanState.Capacity,
|
||||||
|
LocalChanCfg: chanState.LocalChanCfg,
|
||||||
|
RemoteChanCfg: chanState.RemoteChanCfg,
|
||||||
|
ThawHeight: chanState.ThawHeight,
|
||||||
|
TapscriptRoot: chanState.TapscriptRoot,
|
||||||
|
CustomBlob: chanState.CustomBlob,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitDiffAuxInput is the input required to compute the diff of the auxiliary
|
||||||
|
// leaves for a commitment transaction.
|
||||||
|
type CommitDiffAuxInput struct {
|
||||||
|
// ChannelState is the static channel information of the channel this
|
||||||
|
// commitment transaction relates to.
|
||||||
|
ChannelState AuxChanState
|
||||||
|
|
||||||
|
// PrevBlob is the blob of the previous commitment transaction.
|
||||||
|
PrevBlob tlv.Blob
|
||||||
|
|
||||||
|
// UnfilteredView is the unfiltered, original HTLC view of the channel.
|
||||||
|
// Unfiltered in this context means that the view contains all HTLCs,
|
||||||
|
// including the canceled ones.
|
||||||
|
UnfilteredView *HtlcView
|
||||||
|
|
||||||
|
// WhoseCommit denotes whose commitment transaction we are computing the
|
||||||
|
// diff for.
|
||||||
|
WhoseCommit lntypes.ChannelParty
|
||||||
|
|
||||||
|
// OurBalance is the balance of the local party.
|
||||||
|
OurBalance lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// TheirBalance is the balance of the remote party.
|
||||||
|
TheirBalance lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// KeyRing is the key ring that can be used to derive keys for the
|
||||||
|
// commitment transaction.
|
||||||
|
KeyRing CommitmentKeyRing
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitDiffAuxResult is the result of computing the diff of the auxiliary
|
||||||
|
// leaves for a commitment transaction.
|
||||||
|
type CommitDiffAuxResult struct {
|
||||||
|
// AuxLeaves are the auxiliary leaves for the new commitment
|
||||||
|
// transaction.
|
||||||
|
AuxLeaves fn.Option[CommitAuxLeaves]
|
||||||
|
|
||||||
|
// CommitSortFunc is an optional function that sorts the commitment
|
||||||
|
// transaction inputs and outputs.
|
||||||
|
CommitSortFunc fn.Option[CommitSortFunc]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuxLeafStore is used to optionally fetch auxiliary tapscript leaves for the
|
||||||
|
// commitment transaction given an opaque blob. This is also used to implement
|
||||||
|
// a state transition function for the blobs to allow them to be refreshed with
|
||||||
|
// each state.
|
||||||
|
type AuxLeafStore interface {
|
||||||
|
// FetchLeavesFromView attempts to fetch the auxiliary leaves that
|
||||||
|
// correspond to the passed aux blob, and pending original (unfiltered)
|
||||||
|
// HTLC view.
|
||||||
|
FetchLeavesFromView(
|
||||||
|
in CommitDiffAuxInput) fn.Result[CommitDiffAuxResult]
|
||||||
|
|
||||||
|
// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that
|
||||||
|
// correspond to the passed aux blob, and an existing channel
|
||||||
|
// commitment.
|
||||||
|
FetchLeavesFromCommit(chanState AuxChanState,
|
||||||
|
commit channeldb.ChannelCommitment,
|
||||||
|
keyRing CommitmentKeyRing) fn.Result[CommitDiffAuxResult]
|
||||||
|
|
||||||
|
// FetchLeavesFromRevocation attempts to fetch the auxiliary leaves
|
||||||
|
// from a channel revocation that stores balance + blob information.
|
||||||
|
FetchLeavesFromRevocation(
|
||||||
|
r *channeldb.RevocationLog) fn.Result[CommitDiffAuxResult]
|
||||||
|
|
||||||
|
// ApplyHtlcView serves as the state transition function for the custom
|
||||||
|
// channel's blob. Given the old blob, and an HTLC view, then a new
|
||||||
|
// blob should be returned that reflects the pending updates.
|
||||||
|
ApplyHtlcView(in CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// auxLeavesFromView is used to derive the set of commit aux leaves (if any),
|
||||||
|
// that are needed to create a new commitment transaction using the original
|
||||||
|
// (unfiltered) htlc view.
|
||||||
|
func auxLeavesFromView(leafStore AuxLeafStore, chanState *channeldb.OpenChannel,
|
||||||
|
prevBlob fn.Option[tlv.Blob], originalView *HtlcView,
|
||||||
|
whoseCommit lntypes.ChannelParty, ourBalance,
|
||||||
|
theirBalance lnwire.MilliSatoshi,
|
||||||
|
keyRing CommitmentKeyRing) fn.Result[CommitDiffAuxResult] {
|
||||||
|
|
||||||
|
return fn.MapOptionZ(
|
||||||
|
prevBlob, func(blob tlv.Blob) fn.Result[CommitDiffAuxResult] {
|
||||||
|
return leafStore.FetchLeavesFromView(CommitDiffAuxInput{
|
||||||
|
ChannelState: NewAuxChanState(chanState),
|
||||||
|
PrevBlob: blob,
|
||||||
|
UnfilteredView: originalView,
|
||||||
|
WhoseCommit: whoseCommit,
|
||||||
|
OurBalance: ourBalance,
|
||||||
|
TheirBalance: theirBalance,
|
||||||
|
KeyRing: keyRing,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateAuxBlob is a helper function that attempts to update the aux blob
|
||||||
|
// given the prior and current state information.
|
||||||
|
func updateAuxBlob(leafStore AuxLeafStore, chanState *channeldb.OpenChannel,
|
||||||
|
prevBlob fn.Option[tlv.Blob], nextViewUnfiltered *HtlcView,
|
||||||
|
whoseCommit lntypes.ChannelParty, ourBalance,
|
||||||
|
theirBalance lnwire.MilliSatoshi,
|
||||||
|
keyRing CommitmentKeyRing) fn.Result[fn.Option[tlv.Blob]] {
|
||||||
|
|
||||||
|
return fn.MapOptionZ(
|
||||||
|
prevBlob, func(blob tlv.Blob) fn.Result[fn.Option[tlv.Blob]] {
|
||||||
|
return leafStore.ApplyHtlcView(CommitDiffAuxInput{
|
||||||
|
ChannelState: NewAuxChanState(chanState),
|
||||||
|
PrevBlob: blob,
|
||||||
|
UnfilteredView: nextViewUnfiltered,
|
||||||
|
WhoseCommit: whoseCommit,
|
||||||
|
OurBalance: ourBalance,
|
||||||
|
TheirBalance: theirBalance,
|
||||||
|
KeyRing: keyRing,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -178,8 +179,9 @@ func (m *mockChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
|
func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
|
||||||
localScript, remoteScript []byte, _ ...lnwallet.ChanCloseOpt,
|
localScript, remoteScript []byte,
|
||||||
) (input.Signature, *chainhash.Hash, btcutil.Amount, error) {
|
_ ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash,
|
||||||
|
btcutil.Amount, error) {
|
||||||
|
|
||||||
if m.chanType.IsTaproot() {
|
if m.chanType.IsTaproot() {
|
||||||
return lnwallet.NewMusigPartialSig(
|
return lnwallet.NewMusigPartialSig(
|
||||||
@@ -188,6 +190,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
|
|||||||
R: new(btcec.PublicKey),
|
R: new(btcec.PublicKey),
|
||||||
},
|
},
|
||||||
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
|
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
|
||||||
|
fn.None[chainhash.Hash](),
|
||||||
), nil, 0, nil
|
), nil, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
)
|
)
|
||||||
@@ -56,6 +58,14 @@ type ShimIntent struct {
|
|||||||
// generate an aggregate key to use as the taproot-native multi-sig
|
// generate an aggregate key to use as the taproot-native multi-sig
|
||||||
// output.
|
// output.
|
||||||
musig2 bool
|
musig2 bool
|
||||||
|
|
||||||
|
// tapscriptRoot is the root of the tapscript tree that will be used to
|
||||||
|
// create the funding output. This field will only be utilized if the
|
||||||
|
// MuSig2 flag above is set to true.
|
||||||
|
//
|
||||||
|
// TODO(roasbeef): fold above into new chan type? sum type like thing,
|
||||||
|
// includes the tapscript root, etc
|
||||||
|
tapscriptRoot fn.Option[chainhash.Hash]
|
||||||
}
|
}
|
||||||
|
|
||||||
// FundingOutput returns the witness script, and the output that creates the
|
// FundingOutput returns the witness script, and the output that creates the
|
||||||
@@ -76,9 +86,8 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) {
|
|||||||
// Similar to the existing p2wsh script, we'll always ensure
|
// Similar to the existing p2wsh script, we'll always ensure
|
||||||
// the keys are sorted before use.
|
// the keys are sorted before use.
|
||||||
return input.GenTaprootFundingScript(
|
return input.GenTaprootFundingScript(
|
||||||
s.localKey.PubKey,
|
s.localKey.PubKey, s.remoteKey, int64(totalAmt),
|
||||||
s.remoteKey,
|
s.tapscriptRoot,
|
||||||
int64(totalAmt),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -534,6 +534,7 @@ func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) {
|
|||||||
ShimIntent: ShimIntent{
|
ShimIntent: ShimIntent{
|
||||||
localFundingAmt: p.fundingAmt,
|
localFundingAmt: p.fundingAmt,
|
||||||
musig2: req.Musig2,
|
musig2: req.Musig2,
|
||||||
|
tapscriptRoot: req.TapscriptRoot,
|
||||||
},
|
},
|
||||||
State: PsbtShimRegistered,
|
State: PsbtShimRegistered,
|
||||||
BasePsbt: p.basePsbt,
|
BasePsbt: p.basePsbt,
|
||||||
|
|||||||
@@ -394,7 +394,6 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
|||||||
// we will call the specialized coin selection function for
|
// we will call the specialized coin selection function for
|
||||||
// that.
|
// that.
|
||||||
case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
|
case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
|
||||||
|
|
||||||
// We need to ensure that manually selected coins, which
|
// We need to ensure that manually selected coins, which
|
||||||
// are spent entirely on the channel funding, leave
|
// are spent entirely on the channel funding, leave
|
||||||
// enough funds in the wallet to cover for a reserve.
|
// enough funds in the wallet to cover for a reserve.
|
||||||
@@ -539,6 +538,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
|||||||
localFundingAmt: localContributionAmt,
|
localFundingAmt: localContributionAmt,
|
||||||
remoteFundingAmt: r.RemoteAmt,
|
remoteFundingAmt: r.RemoteAmt,
|
||||||
musig2: r.Musig2,
|
musig2: r.Musig2,
|
||||||
|
tapscriptRoot: r.TapscriptRoot,
|
||||||
},
|
},
|
||||||
InputCoins: selectedCoins,
|
InputCoins: selectedCoins,
|
||||||
coinLeaser: w.cfg.CoinLeaser,
|
coinLeaser: w.cfg.CoinLeaser,
|
||||||
|
|||||||
+505
-168
File diff suppressed because it is too large.
Load diff
+92
-42
@@ -2,6 +2,7 @@ package lnwallet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
crand "crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@@ -386,6 +387,12 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("taproot with tapscript root", func(t *testing.T) {
|
||||||
|
flags := channeldb.SimpleTaprootFeatureBit |
|
||||||
|
channeldb.TapscriptRootBit
|
||||||
|
testAddSettleWorkflow(t, true, flags, false)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("storeFinalHtlcResolutions=true", func(t *testing.T) {
|
t.Run("storeFinalHtlcResolutions=true", func(t *testing.T) {
|
||||||
testAddSettleWorkflow(t, false, 0, true)
|
testAddSettleWorkflow(t, false, 0, true)
|
||||||
})
|
})
|
||||||
@@ -828,6 +835,16 @@ func TestForceClose(t *testing.T) {
|
|||||||
anchorAmt: AnchorSize * 2,
|
anchorAmt: AnchorSize * 2,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
t.Run("taproot with tapscript root", func(t *testing.T) {
|
||||||
|
testForceClose(t, &forceCloseTestCase{
|
||||||
|
chanType: channeldb.SingleFunderTweaklessBit |
|
||||||
|
channeldb.AnchorOutputsBit |
|
||||||
|
channeldb.SimpleTaprootFeatureBit |
|
||||||
|
channeldb.TapscriptRootBit,
|
||||||
|
expectedCommitWeight: input.TaprootCommitWeight,
|
||||||
|
anchorAmt: AnchorSize * 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type forceCloseTestCase struct {
|
type forceCloseTestCase struct {
|
||||||
@@ -5678,6 +5695,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
|||||||
spendDetail,
|
spendDetail,
|
||||||
aliceChannel.channelState.RemoteCommitment,
|
aliceChannel.channelState.RemoteCommitment,
|
||||||
aliceChannel.channelState.RemoteCurrentRevocation,
|
aliceChannel.channelState.RemoteCurrentRevocation,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create alice close summary")
|
require.NoError(t, err, "unable to create alice close summary")
|
||||||
|
|
||||||
@@ -5827,6 +5845,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
|||||||
spendDetail,
|
spendDetail,
|
||||||
aliceChannel.channelState.RemoteCommitment,
|
aliceChannel.channelState.RemoteCommitment,
|
||||||
aliceChannel.channelState.RemoteCurrentRevocation,
|
aliceChannel.channelState.RemoteCurrentRevocation,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create alice close summary")
|
require.NoError(t, err, "unable to create alice close summary")
|
||||||
|
|
||||||
@@ -5844,6 +5863,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
|||||||
spendDetail,
|
spendDetail,
|
||||||
aliceRemoteChainTip.Commitment,
|
aliceRemoteChainTip.Commitment,
|
||||||
aliceChannel.channelState.RemoteNextRevocation,
|
aliceChannel.channelState.RemoteNextRevocation,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create alice close summary")
|
require.NoError(t, err, "unable to create alice close summary")
|
||||||
|
|
||||||
@@ -6724,6 +6744,7 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) {
|
|||||||
breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx
|
breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx
|
||||||
breachRet, err := NewBreachRetribution(
|
breachRet, err := NewBreachRetribution(
|
||||||
aliceChannel.channelState, revokedStateNum, 100, breachTx,
|
aliceChannel.channelState, revokedStateNum, 100, breachTx,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create breach retribution")
|
require.NoError(t, err, "unable to create breach retribution")
|
||||||
|
|
||||||
@@ -8525,10 +8546,10 @@ func TestEvaluateView(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
view := &htlcView{
|
view := &HtlcView{
|
||||||
ourUpdates: test.ourHtlcs,
|
OurUpdates: test.ourHtlcs,
|
||||||
theirUpdates: test.theirHtlcs,
|
TheirUpdates: test.theirHtlcs,
|
||||||
feePerKw: feePerKw,
|
FeePerKw: feePerKw,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -8549,17 +8570,17 @@ func TestEvaluateView(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.feePerKw != test.expectedFee {
|
if result.FeePerKw != test.expectedFee {
|
||||||
t.Fatalf("expected fee: %v, got: %v",
|
t.Fatalf("expected fee: %v, got: %v",
|
||||||
test.expectedFee, result.feePerKw)
|
test.expectedFee, result.FeePerKw)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkExpectedHtlcs(
|
checkExpectedHtlcs(
|
||||||
t, result.ourUpdates, test.ourExpectedHtlcs,
|
t, result.OurUpdates, test.ourExpectedHtlcs,
|
||||||
)
|
)
|
||||||
|
|
||||||
checkExpectedHtlcs(
|
checkExpectedHtlcs(
|
||||||
t, result.theirUpdates, test.theirExpectedHtlcs,
|
t, result.TheirUpdates, test.theirExpectedHtlcs,
|
||||||
)
|
)
|
||||||
|
|
||||||
if lc.channelState.TotalMSatSent != test.expectSent {
|
if lc.channelState.TotalMSatSent != test.expectSent {
|
||||||
@@ -8782,15 +8803,15 @@ func TestProcessFeeUpdate(t *testing.T) {
|
|||||||
EntryType: FeeUpdate,
|
EntryType: FeeUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
view := &htlcView{
|
view := &HtlcView{
|
||||||
feePerKw: chainfee.SatPerKWeight(feePerKw),
|
FeePerKw: chainfee.SatPerKWeight(feePerKw),
|
||||||
}
|
}
|
||||||
processFeeUpdate(
|
processFeeUpdate(
|
||||||
update, nextHeight, test.whoseCommitChain,
|
update, nextHeight, test.whoseCommitChain,
|
||||||
test.mutate, view,
|
test.mutate, view,
|
||||||
)
|
)
|
||||||
|
|
||||||
if view.feePerKw != test.expectedFee {
|
if view.FeePerKw != test.expectedFee {
|
||||||
t.Fatalf("expected fee: %v, got: %v",
|
t.Fatalf("expected fee: %v, got: %v",
|
||||||
test.expectedFee, feePerKw)
|
test.expectedFee, feePerKw)
|
||||||
}
|
}
|
||||||
@@ -9940,15 +9961,17 @@ func TestCreateHtlcRetribution(t *testing.T) {
|
|||||||
aliceChannel.channelState,
|
aliceChannel.channelState,
|
||||||
)
|
)
|
||||||
htlc := &channeldb.HTLCEntry{
|
htlc := &channeldb.HTLCEntry{
|
||||||
Amt: testAmt,
|
Amt: tlv.NewRecordT[tlv.TlvType4](
|
||||||
Incoming: true,
|
tlv.NewBigSizeT(testAmt),
|
||||||
OutputIndex: 1,
|
),
|
||||||
|
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true),
|
||||||
|
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2, uint16](1),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the htlc retribution.
|
// Create the htlc retribution.
|
||||||
hr, err := createHtlcRetribution(
|
hr, err := createHtlcRetribution(
|
||||||
aliceChannel.channelState, keyRing, commitHash,
|
aliceChannel.channelState, keyRing, commitHash,
|
||||||
dummyPrivate, leaseExpiry, htlc,
|
dummyPrivate, leaseExpiry, htlc, fn.None[CommitAuxLeaves](),
|
||||||
)
|
)
|
||||||
// Expect no error.
|
// Expect no error.
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -9956,8 +9979,8 @@ func TestCreateHtlcRetribution(t *testing.T) {
|
|||||||
// Check the fields have expected values.
|
// Check the fields have expected values.
|
||||||
require.EqualValues(t, testAmt, hr.SignDesc.Output.Value)
|
require.EqualValues(t, testAmt, hr.SignDesc.Output.Value)
|
||||||
require.Equal(t, commitHash, hr.OutPoint.Hash)
|
require.Equal(t, commitHash, hr.OutPoint.Hash)
|
||||||
require.EqualValues(t, htlc.OutputIndex, hr.OutPoint.Index)
|
require.EqualValues(t, htlc.OutputIndex.Val, hr.OutPoint.Index)
|
||||||
require.Equal(t, htlc.Incoming, hr.IsIncoming)
|
require.Equal(t, htlc.Incoming.Val, hr.IsIncoming)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCreateBreachRetribution checks that `createBreachRetribution` behaves as
|
// TestCreateBreachRetribution checks that `createBreachRetribution` behaves as
|
||||||
@@ -9997,30 +10020,31 @@ func TestCreateBreachRetribution(t *testing.T) {
|
|||||||
aliceChannel.channelState,
|
aliceChannel.channelState,
|
||||||
)
|
)
|
||||||
htlc := &channeldb.HTLCEntry{
|
htlc := &channeldb.HTLCEntry{
|
||||||
Amt: btcutil.Amount(testAmt),
|
Amt: tlv.NewRecordT[tlv.TlvType4](
|
||||||
Incoming: true,
|
tlv.NewBigSizeT(btcutil.Amount(testAmt)),
|
||||||
OutputIndex: uint16(htlcIndex),
|
),
|
||||||
|
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true),
|
||||||
|
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2](
|
||||||
|
uint16(htlcIndex),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a dummy revocation log.
|
// Create a dummy revocation log.
|
||||||
ourAmtMsat := lnwire.MilliSatoshi(ourAmt * 1000)
|
ourAmtMsat := lnwire.MilliSatoshi(ourAmt * 1000)
|
||||||
theirAmtMsat := lnwire.MilliSatoshi(theirAmt * 1000)
|
theirAmtMsat := lnwire.MilliSatoshi(theirAmt * 1000)
|
||||||
revokedLog := channeldb.RevocationLog{
|
revokedLog := channeldb.NewRevocationLog(
|
||||||
CommitTxHash: commitHash,
|
uint16(localIndex), uint16(remoteIndex), commitHash,
|
||||||
OurOutputIndex: uint16(localIndex),
|
fn.Some(ourAmtMsat), fn.Some(theirAmtMsat),
|
||||||
TheirOutputIndex: uint16(remoteIndex),
|
[]*channeldb.HTLCEntry{htlc}, fn.None[tlv.Blob](),
|
||||||
HTLCEntries: []*channeldb.HTLCEntry{htlc},
|
)
|
||||||
TheirBalance: &theirAmtMsat,
|
|
||||||
OurBalance: &ourAmtMsat,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a log with an empty local output index.
|
// Create a log with an empty local output index.
|
||||||
revokedLogNoLocal := revokedLog
|
revokedLogNoLocal := revokedLog
|
||||||
revokedLogNoLocal.OurOutputIndex = channeldb.OutputIndexEmpty
|
revokedLogNoLocal.OurOutputIndex.Val = channeldb.OutputIndexEmpty
|
||||||
|
|
||||||
// Create a log with an empty remote output index.
|
// Create a log with an empty remote output index.
|
||||||
revokedLogNoRemote := revokedLog
|
revokedLogNoRemote := revokedLog
|
||||||
revokedLogNoRemote.TheirOutputIndex = channeldb.OutputIndexEmpty
|
revokedLogNoRemote.TheirOutputIndex.Val = channeldb.OutputIndexEmpty
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -10050,14 +10074,20 @@ func TestCreateBreachRetribution(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "fail due to our index too big",
|
name: "fail due to our index too big",
|
||||||
revocationLog: &channeldb.RevocationLog{
|
revocationLog: &channeldb.RevocationLog{
|
||||||
OurOutputIndex: uint16(htlcIndex + 1),
|
//nolint:lll
|
||||||
|
OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
|
||||||
|
uint16(htlcIndex + 1),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
expectedErr: ErrOutputIndexOutOfRange,
|
expectedErr: ErrOutputIndexOutOfRange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail due to their index too big",
|
name: "fail due to their index too big",
|
||||||
revocationLog: &channeldb.RevocationLog{
|
revocationLog: &channeldb.RevocationLog{
|
||||||
TheirOutputIndex: uint16(htlcIndex + 1),
|
//nolint:lll
|
||||||
|
TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||||
|
uint16(htlcIndex + 1),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
expectedErr: ErrOutputIndexOutOfRange,
|
expectedErr: ErrOutputIndexOutOfRange,
|
||||||
},
|
},
|
||||||
@@ -10126,11 +10156,12 @@ func TestCreateBreachRetribution(t *testing.T) {
|
|||||||
require.Equal(t, remote, br.RemoteOutpoint)
|
require.Equal(t, remote, br.RemoteOutpoint)
|
||||||
|
|
||||||
for _, hr := range br.HtlcRetributions {
|
for _, hr := range br.HtlcRetributions {
|
||||||
require.EqualValues(t, testAmt,
|
require.EqualValues(
|
||||||
hr.SignDesc.Output.Value)
|
t, testAmt, hr.SignDesc.Output.Value,
|
||||||
|
)
|
||||||
require.Equal(t, commitHash, hr.OutPoint.Hash)
|
require.Equal(t, commitHash, hr.OutPoint.Hash)
|
||||||
require.EqualValues(t, htlcIndex, hr.OutPoint.Index)
|
require.EqualValues(t, htlcIndex, hr.OutPoint.Index)
|
||||||
require.Equal(t, htlc.Incoming, hr.IsIncoming)
|
require.Equal(t, htlc.Incoming.Val, hr.IsIncoming)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10146,6 +10177,7 @@ func TestCreateBreachRetribution(t *testing.T) {
|
|||||||
tc.revocationLog, tx,
|
tc.revocationLog, tx,
|
||||||
aliceChannel.channelState, keyRing,
|
aliceChannel.channelState, keyRing,
|
||||||
dummyPrivate, leaseExpiry,
|
dummyPrivate, leaseExpiry,
|
||||||
|
fn.None[CommitAuxLeaves](),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check the error if expected.
|
// Check the error if expected.
|
||||||
@@ -10264,6 +10296,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
// error as there are no past delta state saved as revocation logs yet.
|
// error as there are no past delta state saved as revocation logs yet.
|
||||||
_, err = NewBreachRetribution(
|
_, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
||||||
|
|
||||||
@@ -10271,6 +10304,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
// provided.
|
// provided.
|
||||||
_, err = NewBreachRetribution(
|
_, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum, breachHeight, nil,
|
aliceChannel.channelState, stateNum, breachHeight, nil,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
||||||
|
|
||||||
@@ -10316,6 +10350,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
// successfully.
|
// successfully.
|
||||||
br, err := NewBreachRetribution(
|
br, err := NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -10327,6 +10362,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
// since the necessary info should now be found in the revocation log.
|
// since the necessary info should now be found in the revocation log.
|
||||||
br, err = NewBreachRetribution(
|
br, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum, breachHeight, nil,
|
aliceChannel.channelState, stateNum, breachHeight, nil,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assertRetribution(br, 1, 0)
|
assertRetribution(br, 1, 0)
|
||||||
@@ -10335,6 +10371,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
// error.
|
// error.
|
||||||
_, err = NewBreachRetribution(
|
_, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum+1, breachHeight, breachTx,
|
aliceChannel.channelState, stateNum+1, breachHeight, breachTx,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
||||||
|
|
||||||
@@ -10342,6 +10379,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
// provided.
|
// provided.
|
||||||
_, err = NewBreachRetribution(
|
_, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum+1, breachHeight, nil,
|
aliceChannel.channelState, stateNum+1, breachHeight, nil,
|
||||||
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
||||||
}
|
}
|
||||||
@@ -10379,7 +10417,8 @@ func TestExtractPayDescs(t *testing.T) {
|
|||||||
// NOTE: we use nil commitment key rings to avoid checking the htlc
|
// NOTE: we use nil commitment key rings to avoid checking the htlc
|
||||||
// scripts(`genHtlcScript`) as it should be tested independently.
|
// scripts(`genHtlcScript`) as it should be tested independently.
|
||||||
incomingPDs, outgoingPDs, err := lnChan.extractPayDescs(
|
incomingPDs, outgoingPDs, err := lnChan.extractPayDescs(
|
||||||
0, htlcs, nil, nil, lntypes.Local,
|
0, htlcs, lntypes.Dual[*CommitmentKeyRing]{}, lntypes.Local,
|
||||||
|
fn.None[CommitAuxLeaves](),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -10415,19 +10454,26 @@ func assertPayDescMatchHTLC(t *testing.T, pd PaymentDescriptor,
|
|||||||
// the `Incoming`.
|
// the `Incoming`.
|
||||||
func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC {
|
func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC {
|
||||||
var onionBlob [lnwire.OnionPacketSize]byte
|
var onionBlob [lnwire.OnionPacketSize]byte
|
||||||
_, err := rand.Read(onionBlob[:])
|
_, err := crand.Read(onionBlob[:])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var rHash [lntypes.HashSize]byte
|
var rHash [lntypes.HashSize]byte
|
||||||
_, err = rand.Read(rHash[:])
|
_, err = crand.Read(rHash[:])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
sig := make([]byte, 64)
|
sig := make([]byte, 64)
|
||||||
_, err = rand.Read(sig)
|
_, err = crand.Read(sig)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
randCustomData := make([]byte, 32)
|
||||||
|
_, err = crand.Read(randCustomData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
randCustomType := rand.Intn(255) + lnwire.MinCustomRecordsTlvType
|
||||||
|
|
||||||
blinding, err := pubkeyFromHex(
|
blinding, err := pubkeyFromHex(
|
||||||
"0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d48236c39", //nolint:lll
|
"0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d48" +
|
||||||
|
"236c39",
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -10442,9 +10488,13 @@ func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC {
|
|||||||
HtlcIndex: rand.Uint64(),
|
HtlcIndex: rand.Uint64(),
|
||||||
LogIndex: rand.Uint64(),
|
LogIndex: rand.Uint64(),
|
||||||
BlindingPoint: tlv.SomeRecordT(
|
BlindingPoint: tlv.SomeRecordT(
|
||||||
//nolint:lll
|
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
|
||||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](blinding),
|
blinding,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
CustomRecords: map[uint64][]byte{
|
||||||
|
uint64(randCustomType): randCustomData,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+153
-51
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
@@ -226,8 +227,7 @@ func (w *WitnessScriptDesc) WitnessScriptForPath(
|
|||||||
// the settled funds in the channel, plus the unsettled funds.
|
// the settled funds in the channel, plus the unsettled funds.
|
||||||
func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
|
func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
|
||||||
selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
|
selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
|
||||||
) (
|
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
|
||||||
input.ScriptDescriptor, error) {
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// For taproot scripts, we'll need to make a slightly modified script
|
// For taproot scripts, we'll need to make a slightly modified script
|
||||||
@@ -237,7 +237,7 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
|
|||||||
// Our "redeem" script here is just the taproot witness program.
|
// Our "redeem" script here is just the taproot witness program.
|
||||||
case chanType.IsTaproot():
|
case chanType.IsTaproot():
|
||||||
return input.NewLocalCommitScriptTree(
|
return input.NewLocalCommitScriptTree(
|
||||||
csvDelay, selfKey, revokeKey,
|
csvDelay, selfKey, revokeKey, auxLeaf,
|
||||||
)
|
)
|
||||||
|
|
||||||
// If we are the initiator of a leased channel, then we have an
|
// If we are the initiator of a leased channel, then we have an
|
||||||
@@ -291,8 +291,8 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
|
|||||||
// script for. The second return value is the CSV delay of the output script,
|
// script for. The second return value is the CSV delay of the output script,
|
||||||
// what must be satisfied in order to spend the output.
|
// what must be satisfied in order to spend the output.
|
||||||
func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool,
|
func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool,
|
||||||
remoteKey *btcec.PublicKey,
|
remoteKey *btcec.PublicKey, leaseExpiry uint32,
|
||||||
leaseExpiry uint32) (input.ScriptDescriptor, uint32, error) {
|
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, uint32, error) {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// If we are not the initiator of a leased channel, then the remote
|
// If we are not the initiator of a leased channel, then the remote
|
||||||
@@ -321,7 +321,7 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool,
|
|||||||
// with the sole tap leaf enforcing the 1 CSV delay.
|
// with the sole tap leaf enforcing the 1 CSV delay.
|
||||||
case chanType.IsTaproot():
|
case chanType.IsTaproot():
|
||||||
toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(
|
toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(
|
||||||
remoteKey,
|
remoteKey, auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
@@ -420,14 +420,14 @@ func sweepSigHash(chanType channeldb.ChannelType) txscript.SigHashType {
|
|||||||
// argument should correspond to the owner of the commitment transaction which
|
// argument should correspond to the owner of the commitment transaction which
|
||||||
// we are generating the to_local script for.
|
// we are generating the to_local script for.
|
||||||
func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool,
|
func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool,
|
||||||
revocationKey, delayKey *btcec.PublicKey,
|
revocationKey, delayKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
|
||||||
csvDelay, leaseExpiry uint32) (input.ScriptDescriptor, error) {
|
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// For taproot channels, the pkScript is a segwit v1 p2tr output.
|
// For taproot channels, the pkScript is a segwit v1 p2tr output.
|
||||||
case chanType.IsTaproot():
|
case chanType.IsTaproot():
|
||||||
return input.TaprootSecondLevelScriptTree(
|
return input.TaprootSecondLevelScriptTree(
|
||||||
revocationKey, delayKey, csvDelay,
|
revocationKey, delayKey, csvDelay, auxLeaf,
|
||||||
)
|
)
|
||||||
|
|
||||||
// If we are the initiator of a leased channel, then we have an
|
// If we are the initiator of a leased channel, then we have an
|
||||||
@@ -613,7 +613,7 @@ func CommitScriptAnchors(chanType channeldb.ChannelType,
|
|||||||
// with, and abstracts the various ways of constructing commitment
|
// with, and abstracts the various ways of constructing commitment
|
||||||
// transactions.
|
// transactions.
|
||||||
type CommitmentBuilder struct {
|
type CommitmentBuilder struct {
|
||||||
// chanState is the underlying channels's state struct, used to
|
// chanState is the underlying channel's state struct, used to
|
||||||
// determine the type of channel we are dealing with, and relevant
|
// determine the type of channel we are dealing with, and relevant
|
||||||
// parameters.
|
// parameters.
|
||||||
chanState *channeldb.OpenChannel
|
chanState *channeldb.OpenChannel
|
||||||
@@ -621,18 +621,25 @@ type CommitmentBuilder struct {
|
|||||||
// obfuscator is a 48-bit state hint that's used to obfuscate the
|
// obfuscator is a 48-bit state hint that's used to obfuscate the
|
||||||
// current state number on the commitment transactions.
|
// current state number on the commitment transactions.
|
||||||
obfuscator [StateHintSize]byte
|
obfuscator [StateHintSize]byte
|
||||||
|
|
||||||
|
// auxLeafStore is an interface that allows us to fetch auxiliary
|
||||||
|
// tapscript leaves for the commitment output.
|
||||||
|
auxLeafStore fn.Option[AuxLeafStore]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommitmentBuilder creates a new CommitmentBuilder from chanState.
|
// NewCommitmentBuilder creates a new CommitmentBuilder from chanState.
|
||||||
func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder {
|
func NewCommitmentBuilder(chanState *channeldb.OpenChannel,
|
||||||
|
leafStore fn.Option[AuxLeafStore]) *CommitmentBuilder {
|
||||||
|
|
||||||
// The anchor channel type MUST be tweakless.
|
// The anchor channel type MUST be tweakless.
|
||||||
if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() {
|
if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() {
|
||||||
panic("invalid channel type combination")
|
panic("invalid channel type combination")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CommitmentBuilder{
|
return &CommitmentBuilder{
|
||||||
chanState: chanState,
|
chanState: chanState,
|
||||||
obfuscator: createStateHintObfuscator(chanState),
|
obfuscator: createStateHintObfuscator(chanState),
|
||||||
|
auxLeafStore: leafStore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,9 +692,9 @@ type unsignedCommitmentTx struct {
|
|||||||
// fees, but after anchor outputs.
|
// fees, but after anchor outputs.
|
||||||
func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||||
theirBalance lnwire.MilliSatoshi, whoseCommit lntypes.ChannelParty,
|
theirBalance lnwire.MilliSatoshi, whoseCommit lntypes.ChannelParty,
|
||||||
feePerKw chainfee.SatPerKWeight, height uint64,
|
feePerKw chainfee.SatPerKWeight, height uint64, originalHtlcView,
|
||||||
filteredHTLCView *htlcView,
|
filteredHTLCView *HtlcView, keyRing *CommitmentKeyRing,
|
||||||
keyRing *CommitmentKeyRing) (*unsignedCommitmentTx, error) {
|
prevCommit *commitment) (*unsignedCommitmentTx, error) {
|
||||||
|
|
||||||
dustLimit := cb.chanState.LocalChanCfg.DustLimit
|
dustLimit := cb.chanState.LocalChanCfg.DustLimit
|
||||||
if whoseCommit.IsRemote() {
|
if whoseCommit.IsRemote() {
|
||||||
@@ -695,7 +702,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
}
|
}
|
||||||
|
|
||||||
numHTLCs := int64(0)
|
numHTLCs := int64(0)
|
||||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
for _, htlc := range filteredHTLCView.OurUpdates {
|
||||||
if HtlcIsDust(
|
if HtlcIsDust(
|
||||||
cb.chanState.ChanType, false, whoseCommit, feePerKw,
|
cb.chanState.ChanType, false, whoseCommit, feePerKw,
|
||||||
htlc.Amount.ToSatoshis(), dustLimit,
|
htlc.Amount.ToSatoshis(), dustLimit,
|
||||||
@@ -706,7 +713,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
|
|
||||||
numHTLCs++
|
numHTLCs++
|
||||||
}
|
}
|
||||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
for _, htlc := range filteredHTLCView.TheirUpdates {
|
||||||
if HtlcIsDust(
|
if HtlcIsDust(
|
||||||
cb.chanState.ChanType, true, whoseCommit, feePerKw,
|
cb.chanState.ChanType, true, whoseCommit, feePerKw,
|
||||||
htlc.Amount.ToSatoshis(), dustLimit,
|
htlc.Amount.ToSatoshis(), dustLimit,
|
||||||
@@ -748,10 +755,24 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
theirBalance -= commitFeeMSat
|
theirBalance -= commitFeeMSat
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var commitTx *wire.MsgTx
|
||||||
commitTx *wire.MsgTx
|
|
||||||
err error
|
// Before we create the commitment transaction below, we'll try to see
|
||||||
)
|
// if there're any aux leaves that need to be a part of the tapscript
|
||||||
|
// tree. We'll only do this if we have a custom blob defined though.
|
||||||
|
auxResult, err := fn.MapOptionZ(
|
||||||
|
cb.auxLeafStore,
|
||||||
|
func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
|
||||||
|
return auxLeavesFromView(
|
||||||
|
s, cb.chanState, prevCommit.customBlob,
|
||||||
|
originalHtlcView, whoseCommit, ourBalance,
|
||||||
|
theirBalance, *keyRing,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
).Unpack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Depending on whether the transaction is ours or not, we call
|
// Depending on whether the transaction is ours or not, we call
|
||||||
// CreateCommitTx with parameters matching the perspective, to generate
|
// CreateCommitTx with parameters matching the perspective, to generate
|
||||||
@@ -767,6 +788,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
|
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
|
||||||
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
|
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
|
||||||
numHTLCs, cb.chanState.IsInitiator, leaseExpiry,
|
numHTLCs, cb.chanState.IsInitiator, leaseExpiry,
|
||||||
|
auxResult.AuxLeaves,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
commitTx, err = CreateCommitTx(
|
commitTx, err = CreateCommitTx(
|
||||||
@@ -774,12 +796,26 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
|
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
|
||||||
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
|
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
|
||||||
numHTLCs, !cb.chanState.IsInitiator, leaseExpiry,
|
numHTLCs, !cb.chanState.IsInitiator, leaseExpiry,
|
||||||
|
auxResult.AuxLeaves,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Similarly, we'll now attempt to extract the set of aux leaves for
|
||||||
|
// the set of incoming and outgoing HTLCs.
|
||||||
|
incomingAuxLeaves := fn.MapOption(
|
||||||
|
func(leaves CommitAuxLeaves) input.HtlcAuxLeaves {
|
||||||
|
return leaves.IncomingHtlcLeaves
|
||||||
|
},
|
||||||
|
)(auxResult.AuxLeaves)
|
||||||
|
outgoingAuxLeaves := fn.MapOption(
|
||||||
|
func(leaves CommitAuxLeaves) input.HtlcAuxLeaves {
|
||||||
|
return leaves.OutgoingHtlcLeaves
|
||||||
|
},
|
||||||
|
)(auxResult.AuxLeaves)
|
||||||
|
|
||||||
// We'll now add all the HTLC outputs to the commitment transaction.
|
// We'll now add all the HTLC outputs to the commitment transaction.
|
||||||
// Each output includes an off-chain 2-of-2 covenant clause, so we'll
|
// Each output includes an off-chain 2-of-2 covenant clause, so we'll
|
||||||
// need the objective local/remote keys for this particular commitment
|
// need the objective local/remote keys for this particular commitment
|
||||||
@@ -790,7 +826,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
// commitment outputs and should correspond to zero values for the
|
// commitment outputs and should correspond to zero values for the
|
||||||
// purposes of sorting.
|
// purposes of sorting.
|
||||||
cltvs := make([]uint32, len(commitTx.TxOut))
|
cltvs := make([]uint32, len(commitTx.TxOut))
|
||||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
htlcIndexes := make([]input.HtlcIndex, len(commitTx.TxOut))
|
||||||
|
for _, htlc := range filteredHTLCView.OurUpdates {
|
||||||
if HtlcIsDust(
|
if HtlcIsDust(
|
||||||
cb.chanState.ChanType, false, whoseCommit, feePerKw,
|
cb.chanState.ChanType, false, whoseCommit, feePerKw,
|
||||||
htlc.Amount.ToSatoshis(), dustLimit,
|
htlc.Amount.ToSatoshis(), dustLimit,
|
||||||
@@ -799,16 +836,26 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auxLeaf := fn.ChainOption(
|
||||||
|
func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf {
|
||||||
|
return leaves[htlc.HtlcIndex].AuxTapLeaf
|
||||||
|
},
|
||||||
|
)(outgoingAuxLeaves)
|
||||||
|
|
||||||
err := addHTLC(
|
err := addHTLC(
|
||||||
commitTx, whoseCommit, false, htlc, keyRing,
|
commitTx, whoseCommit, false, htlc, keyRing,
|
||||||
cb.chanState.ChanType,
|
cb.chanState.ChanType, auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cltvs = append(cltvs, htlc.Timeout) // nolint:makezero
|
|
||||||
|
// We want to add the CLTV and HTLC index to their respective
|
||||||
|
// slices, even if we already pre-allocated them.
|
||||||
|
cltvs = append(cltvs, htlc.Timeout) //nolint
|
||||||
|
htlcIndexes = append(htlcIndexes, htlc.HtlcIndex) //nolint
|
||||||
}
|
}
|
||||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
for _, htlc := range filteredHTLCView.TheirUpdates {
|
||||||
if HtlcIsDust(
|
if HtlcIsDust(
|
||||||
cb.chanState.ChanType, true, whoseCommit, feePerKw,
|
cb.chanState.ChanType, true, whoseCommit, feePerKw,
|
||||||
htlc.Amount.ToSatoshis(), dustLimit,
|
htlc.Amount.ToSatoshis(), dustLimit,
|
||||||
@@ -817,14 +864,24 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auxLeaf := fn.ChainOption(
|
||||||
|
func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf {
|
||||||
|
return leaves[htlc.HtlcIndex].AuxTapLeaf
|
||||||
|
},
|
||||||
|
)(incomingAuxLeaves)
|
||||||
|
|
||||||
err := addHTLC(
|
err := addHTLC(
|
||||||
commitTx, whoseCommit, true, htlc, keyRing,
|
commitTx, whoseCommit, true, htlc, keyRing,
|
||||||
cb.chanState.ChanType,
|
cb.chanState.ChanType, auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cltvs = append(cltvs, htlc.Timeout) // nolint:makezero
|
|
||||||
|
// We want to add the CLTV and HTLC index to their respective
|
||||||
|
// slices, even if we already pre-allocated them.
|
||||||
|
cltvs = append(cltvs, htlc.Timeout) //nolint
|
||||||
|
htlcIndexes = append(htlcIndexes, htlc.HtlcIndex) //nolint
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the state hint of the commitment transaction to facilitate
|
// Set the state hint of the commitment transaction to facilitate
|
||||||
@@ -836,9 +893,16 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort the transactions according to the agreed upon canonical
|
// Sort the transactions according to the agreed upon canonical
|
||||||
// ordering. This lets us skip sending the entire transaction over,
|
// ordering (which might be customized for custom channel types, but
|
||||||
// instead we'll just send signatures.
|
// deterministic and both parties will arrive at the same result). This
|
||||||
InPlaceCommitSort(commitTx, cltvs)
|
// lets us skip sending the entire transaction over, instead we'll just
|
||||||
|
// send signatures.
|
||||||
|
commitSort := auxResult.CommitSortFunc.UnwrapOr(DefaultCommitSort)
|
||||||
|
err = commitSort(commitTx, cltvs, htlcIndexes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to sort commitment "+
|
||||||
|
"transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Next, we'll ensure that we don't accidentally create a commitment
|
// Next, we'll ensure that we don't accidentally create a commitment
|
||||||
// transaction which would be invalid by consensus.
|
// transaction which would be invalid by consensus.
|
||||||
@@ -880,24 +944,33 @@ func CreateCommitTx(chanType channeldb.ChannelType,
|
|||||||
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
|
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
|
||||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
||||||
amountToLocal, amountToRemote btcutil.Amount,
|
amountToLocal, amountToRemote btcutil.Amount,
|
||||||
numHTLCs int64, initiator bool, leaseExpiry uint32) (*wire.MsgTx, error) {
|
numHTLCs int64, initiator bool, leaseExpiry uint32,
|
||||||
|
auxLeaves fn.Option[CommitAuxLeaves]) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
// First, we create the script for the delayed "pay-to-self" output.
|
// First, we create the script for the delayed "pay-to-self" output.
|
||||||
// This output has 2 main redemption clauses: either we can redeem the
|
// This output has 2 main redemption clauses: either we can redeem the
|
||||||
// output after a relative block delay, or the remote node can claim
|
// output after a relative block delay, or the remote node can claim
|
||||||
// the funds with the revocation key if we broadcast a revoked
|
// the funds with the revocation key if we broadcast a revoked
|
||||||
// commitment transaction.
|
// commitment transaction.
|
||||||
|
localAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||||
|
return l.LocalAuxLeaf
|
||||||
|
})(auxLeaves)
|
||||||
toLocalScript, err := CommitScriptToSelf(
|
toLocalScript, err := CommitScriptToSelf(
|
||||||
chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey,
|
chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey,
|
||||||
uint32(localChanCfg.CsvDelay), leaseExpiry,
|
uint32(localChanCfg.CsvDelay), leaseExpiry,
|
||||||
|
fn.FlattenOption(localAuxLeaf),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, we create the script paying to the remote.
|
// Next, we create the script paying to the remote.
|
||||||
|
remoteAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||||
|
return l.RemoteAuxLeaf
|
||||||
|
})(auxLeaves)
|
||||||
toRemoteScript, _, err := CommitScriptToRemote(
|
toRemoteScript, _, err := CommitScriptToRemote(
|
||||||
chanType, initiator, keyRing.ToRemoteKey, leaseExpiry,
|
chanType, initiator, keyRing.ToRemoteKey, leaseExpiry,
|
||||||
|
fn.FlattenOption(remoteAuxLeaf),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1077,7 +1150,7 @@ func genSegwitV0HtlcScript(chanType channeldb.ChannelType,
|
|||||||
// channel.
|
// channel.
|
||||||
func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
||||||
timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing,
|
timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing,
|
||||||
) (*input.HtlcScriptTree, error) {
|
auxLeaf input.AuxTapLeaf) (*input.HtlcScriptTree, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
htlcScriptTree *input.HtlcScriptTree
|
htlcScriptTree *input.HtlcScriptTree
|
||||||
@@ -1094,7 +1167,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||||||
case isIncoming && whoseCommit.IsLocal():
|
case isIncoming && whoseCommit.IsLocal():
|
||||||
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
|
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
|
||||||
timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||||
keyRing.RevocationKey, rHash[:], whoseCommit,
|
keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
|
||||||
)
|
)
|
||||||
|
|
||||||
// We're being paid via an HTLC by the remote party, and the HTLC is
|
// We're being paid via an HTLC by the remote party, and the HTLC is
|
||||||
@@ -1103,7 +1176,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||||||
case isIncoming && whoseCommit.IsRemote():
|
case isIncoming && whoseCommit.IsRemote():
|
||||||
htlcScriptTree, err = input.SenderHTLCScriptTaproot(
|
htlcScriptTree, err = input.SenderHTLCScriptTaproot(
|
||||||
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||||
keyRing.RevocationKey, rHash[:], whoseCommit,
|
keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
|
||||||
)
|
)
|
||||||
|
|
||||||
// We're sending an HTLC which is being added to our commitment
|
// We're sending an HTLC which is being added to our commitment
|
||||||
@@ -1112,7 +1185,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||||||
case !isIncoming && whoseCommit.IsLocal():
|
case !isIncoming && whoseCommit.IsLocal():
|
||||||
htlcScriptTree, err = input.SenderHTLCScriptTaproot(
|
htlcScriptTree, err = input.SenderHTLCScriptTaproot(
|
||||||
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
||||||
keyRing.RevocationKey, rHash[:], whoseCommit,
|
keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Finally, we're paying the remote party via an HTLC, which is being
|
// Finally, we're paying the remote party via an HTLC, which is being
|
||||||
@@ -1121,7 +1194,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||||||
case !isIncoming && whoseCommit.IsRemote():
|
case !isIncoming && whoseCommit.IsRemote():
|
||||||
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
|
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
|
||||||
timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
||||||
keyRing.RevocationKey, rHash[:], whoseCommit,
|
keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1136,7 +1209,8 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||||||
// along side the multiplexer.
|
// along side the multiplexer.
|
||||||
func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
|
func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
|
||||||
whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte,
|
whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte,
|
||||||
keyRing *CommitmentKeyRing) (input.ScriptDescriptor, error) {
|
keyRing *CommitmentKeyRing,
|
||||||
|
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
|
||||||
|
|
||||||
if !chanType.IsTaproot() {
|
if !chanType.IsTaproot() {
|
||||||
return genSegwitV0HtlcScript(
|
return genSegwitV0HtlcScript(
|
||||||
@@ -1146,7 +1220,7 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return GenTaprootHtlcScript(
|
return GenTaprootHtlcScript(
|
||||||
isIncoming, whoseCommit, timeout, rHash, keyRing,
|
isIncoming, whoseCommit, timeout, rHash, keyRing, auxLeaf,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1159,13 +1233,15 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
|
|||||||
// the descriptor itself.
|
// the descriptor itself.
|
||||||
func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty,
|
func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty,
|
||||||
isIncoming bool, paymentDesc *PaymentDescriptor,
|
isIncoming bool, paymentDesc *PaymentDescriptor,
|
||||||
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error {
|
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType,
|
||||||
|
auxLeaf input.AuxTapLeaf) error {
|
||||||
|
|
||||||
timeout := paymentDesc.Timeout
|
timeout := paymentDesc.Timeout
|
||||||
rHash := paymentDesc.RHash
|
rHash := paymentDesc.RHash
|
||||||
|
|
||||||
scriptInfo, err := genHtlcScript(
|
scriptInfo, err := genHtlcScript(
|
||||||
chanType, isIncoming, whoseCommit, timeout, rHash, keyRing,
|
chanType, isIncoming, whoseCommit, timeout, rHash, keyRing,
|
||||||
|
auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1198,7 +1274,8 @@ func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty,
|
|||||||
// output scripts and compares them against the outputs inside the commitment
|
// output scripts and compares them against the outputs inside the commitment
|
||||||
// to find the match.
|
// to find the match.
|
||||||
func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
|
func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
|
||||||
chanState *channeldb.OpenChannel) (uint32, uint32, error) {
|
chanState *channeldb.OpenChannel,
|
||||||
|
leafStore fn.Option[AuxLeafStore]) (uint32, uint32, error) {
|
||||||
|
|
||||||
// Init the output indexes as empty.
|
// Init the output indexes as empty.
|
||||||
ourIndex := uint32(channeldb.OutputIndexEmpty)
|
ourIndex := uint32(channeldb.OutputIndexEmpty)
|
||||||
@@ -1228,26 +1305,51 @@ func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
|
|||||||
leaseExpiry = chanState.ThawHeight
|
leaseExpiry = chanState.ThawHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the scripts from our PoV. When facing a local commitment, the to
|
// If we have a custom blob, then we'll attempt to fetch the aux leaves
|
||||||
// local output belongs to us and the to remote output belongs to them.
|
// for this state.
|
||||||
// When facing a remote commitment, the to local output belongs to them
|
auxResult, err := fn.MapOptionZ(
|
||||||
// and the to remote output belongs to us.
|
leafStore, func(a AuxLeafStore) fn.Result[CommitDiffAuxResult] {
|
||||||
|
return a.FetchLeavesFromCommit(
|
||||||
|
NewAuxChanState(chanState), chanCommit,
|
||||||
|
*keyRing,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
).Unpack()
|
||||||
|
if err != nil {
|
||||||
|
return ourIndex, theirIndex, fmt.Errorf("unable to fetch aux "+
|
||||||
|
"leaves: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the to local script. From our PoV, when facing a remote
|
// Map the scripts from our PoV. When facing a local commitment, the
|
||||||
// commitment, the to local output belongs to them.
|
// to_local output belongs to us and the to_remote output belongs to
|
||||||
|
// them. When facing a remote commitment, the to_local output belongs to
|
||||||
|
// them and the to_remote output belongs to us.
|
||||||
|
|
||||||
|
// Compute the to_local script. From our PoV, when facing a remote
|
||||||
|
// commitment, the to_local output belongs to them.
|
||||||
|
localAuxLeaf := fn.ChainOption(
|
||||||
|
func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||||
|
return l.LocalAuxLeaf
|
||||||
|
},
|
||||||
|
)(auxResult.AuxLeaves)
|
||||||
theirScript, err := CommitScriptToSelf(
|
theirScript, err := CommitScriptToSelf(
|
||||||
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
|
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
|
||||||
keyRing.RevocationKey, theirDelay, leaseExpiry,
|
keyRing.RevocationKey, theirDelay, leaseExpiry, localAuxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ourIndex, theirIndex, err
|
return ourIndex, theirIndex, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the to remote script. From our PoV, when facing a remote
|
// Compute the to_remote script. From our PoV, when facing a remote
|
||||||
// commitment, the to remote output belongs to us.
|
// commitment, the to_remote output belongs to us.
|
||||||
|
remoteAuxLeaf := fn.ChainOption(
|
||||||
|
func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||||
|
return l.RemoteAuxLeaf
|
||||||
|
},
|
||||||
|
)(auxResult.AuxLeaves)
|
||||||
ourScript, _, err := CommitScriptToRemote(
|
ourScript, _, err := CommitScriptToRemote(
|
||||||
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
|
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
|
||||||
leaseExpiry,
|
leaseExpiry, remoteAuxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ourIndex, theirIndex, err
|
return ourIndex, theirIndex, err
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/btcsuite/btcwallet/wallet"
|
"github.com/btcsuite/btcwallet/wallet"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
@@ -62,4 +63,8 @@ type Config struct {
|
|||||||
// CoinSelectionStrategy is the strategy that is used for selecting
|
// CoinSelectionStrategy is the strategy that is used for selecting
|
||||||
// coins when funding a transaction.
|
// coins when funding a transaction.
|
||||||
CoinSelectionStrategy wallet.CoinSelectionStrategy
|
CoinSelectionStrategy wallet.CoinSelectionStrategy
|
||||||
|
|
||||||
|
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||||
|
// leaves for certain custom channel types.
|
||||||
|
AuxLeafStore fn.Option[AuxLeafStore]
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,10 @@ import (
|
|||||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/fn"
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
|
"github.com/lightningnetwork/lnd/tlv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -397,3 +399,45 @@ func (*mockChainIO) GetBlockHeader(
|
|||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MockAuxLeafStore struct{}
|
||||||
|
|
||||||
|
// A compile time check to ensure that MockAuxLeafStore implements the
|
||||||
|
// AuxLeafStore interface.
|
||||||
|
var _ AuxLeafStore = (*MockAuxLeafStore)(nil)
|
||||||
|
|
||||||
|
// FetchLeavesFromView attempts to fetch the auxiliary leaves that
|
||||||
|
// correspond to the passed aux blob, and pending original (unfiltered)
|
||||||
|
// HTLC view.
|
||||||
|
func (*MockAuxLeafStore) FetchLeavesFromView(
|
||||||
|
_ CommitDiffAuxInput) fn.Result[CommitDiffAuxResult] {
|
||||||
|
|
||||||
|
return fn.Ok(CommitDiffAuxResult{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that
|
||||||
|
// correspond to the passed aux blob, and an existing channel
|
||||||
|
// commitment.
|
||||||
|
func (*MockAuxLeafStore) FetchLeavesFromCommit(_ AuxChanState,
|
||||||
|
_ channeldb.ChannelCommitment,
|
||||||
|
_ CommitmentKeyRing) fn.Result[CommitDiffAuxResult] {
|
||||||
|
|
||||||
|
return fn.Ok(CommitDiffAuxResult{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchLeavesFromRevocation attempts to fetch the auxiliary leaves
|
||||||
|
// from a channel revocation that stores balance + blob information.
|
||||||
|
func (*MockAuxLeafStore) FetchLeavesFromRevocation(
|
||||||
|
_ *channeldb.RevocationLog) fn.Result[CommitDiffAuxResult] {
|
||||||
|
|
||||||
|
return fn.Ok(CommitDiffAuxResult{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyHtlcView serves as the state transition function for the custom
|
||||||
|
// channel's blob. Given the old blob, and an HTLC view, then a new
|
||||||
|
// blob should be returned that reflects the pending updates.
|
||||||
|
func (*MockAuxLeafStore) ApplyHtlcView(
|
||||||
|
_ CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]] {
|
||||||
|
|
||||||
|
return fn.Ok(fn.None[tlv.Blob]())
|
||||||
|
}
|
||||||
+79
-28
@@ -8,8 +8,10 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@@ -37,6 +39,20 @@ var (
|
|||||||
ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
|
ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// tapscriptRootToSignOpt is a function that takes a tapscript root and returns
|
||||||
|
// a MuSig2 sign opt that'll apply the tweak when signing+verifying.
|
||||||
|
func tapscriptRootToSignOpt(root chainhash.Hash) musig2.SignOption {
|
||||||
|
return musig2.WithTaprootSignTweak(root[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TapscriptRootToTweak is a helper function that converts a tapscript root
|
||||||
|
// into a tweak that can be used with the MuSig2 API.
|
||||||
|
func TapscriptRootToTweak(root chainhash.Hash) input.MuSig2Tweaks {
|
||||||
|
return input.MuSig2Tweaks{
|
||||||
|
TaprootTweak: root[:],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MusigPartialSig is a wrapper around the base musig2.PartialSignature type
|
// MusigPartialSig is a wrapper around the base musig2.PartialSignature type
|
||||||
// that also includes information about the set of nonces used, and also the
|
// that also includes information about the set of nonces used, and also the
|
||||||
// signer. This allows us to implement the input.Signature interface, as that
|
// signer. This allows us to implement the input.Signature interface, as that
|
||||||
@@ -54,25 +70,30 @@ type MusigPartialSig struct {
|
|||||||
|
|
||||||
// signerKeys is the set of public keys of all signers.
|
// signerKeys is the set of public keys of all signers.
|
||||||
signerKeys []*btcec.PublicKey
|
signerKeys []*btcec.PublicKey
|
||||||
|
|
||||||
|
// tapscriptTweak is an optional tweak, that if specified, will be used
|
||||||
|
// instead of the normal BIP 86 tweak when validating the signature.
|
||||||
|
tapscriptTweak fn.Option[chainhash.Hash]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMusigPartialSig creates a new musig partial signature.
|
// NewMusigPartialSig creates a new MuSig2 partial signature.
|
||||||
func NewMusigPartialSig(sig *musig2.PartialSignature,
|
func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce,
|
||||||
signerNonce, combinedNonce lnwire.Musig2Nonce,
|
combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey,
|
||||||
signerKeys []*btcec.PublicKey) *MusigPartialSig {
|
tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig {
|
||||||
|
|
||||||
return &MusigPartialSig{
|
return &MusigPartialSig{
|
||||||
sig: sig,
|
sig: sig,
|
||||||
signerNonce: signerNonce,
|
signerNonce: signerNonce,
|
||||||
combinedNonce: combinedNonce,
|
combinedNonce: combinedNonce,
|
||||||
signerKeys: signerKeys,
|
signerKeys: signerKeys,
|
||||||
|
tapscriptTweak: tapscriptTweak,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromWireSig maps a wire partial sig to this internal type that we'll use to
|
// FromWireSig maps a wire partial sig to this internal type that we'll use to
|
||||||
// perform signature validation.
|
// perform signature validation.
|
||||||
func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce,
|
func (p *MusigPartialSig) FromWireSig(
|
||||||
) *MusigPartialSig {
|
sig *lnwire.PartialSigWithNonce) *MusigPartialSig {
|
||||||
|
|
||||||
p.sig = &musig2.PartialSignature{
|
p.sig = &musig2.PartialSignature{
|
||||||
S: &sig.Sig,
|
S: &sig.Sig,
|
||||||
@@ -135,9 +156,15 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
|
|||||||
var m [32]byte
|
var m [32]byte
|
||||||
copy(m[:], msg)
|
copy(m[:], msg)
|
||||||
|
|
||||||
|
// If we have a tapscript tweak, then we'll use that as a tweak
|
||||||
|
// otherwise, we'll fall back to the normal BIP 86 sign tweak.
|
||||||
|
signOpts := fn.MapOption(tapscriptRootToSignOpt)(
|
||||||
|
p.tapscriptTweak,
|
||||||
|
).UnwrapOr(musig2.WithBip86SignTweak())
|
||||||
|
|
||||||
return p.sig.Verify(
|
return p.sig.Verify(
|
||||||
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
|
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
|
||||||
musig2.WithSortedKeys(), musig2.WithBip86SignTweak(),
|
musig2.WithSortedKeys(), signOpts,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +187,14 @@ func (n *MusigNoncePair) String() string {
|
|||||||
n.SigningNonce.PubNonce[:])
|
n.SigningNonce.PubNonce[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TapscriptRootToTweak is a function that takes a MuSig2 taproot tweak and
|
||||||
|
// returns the root hash of the tapscript tree.
|
||||||
|
func muSig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash {
|
||||||
|
var root chainhash.Hash
|
||||||
|
copy(root[:], tweak.TaprootTweak)
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
// MusigSession abstracts over the details of a logical musig session. A single
|
// MusigSession abstracts over the details of a logical musig session. A single
|
||||||
// session is used for each commitment transactions. The sessions use a JIT
|
// session is used for each commitment transactions. The sessions use a JIT
|
||||||
// nonce style, wherein part of the session can be created using only the
|
// nonce style, wherein part of the session can be created using only the
|
||||||
@@ -197,15 +232,20 @@ type MusigSession struct {
|
|||||||
// commitType tracks if this is the session for the local or remote
|
// commitType tracks if this is the session for the local or remote
|
||||||
// commitment.
|
// commitment.
|
||||||
commitType MusigCommitType
|
commitType MusigCommitType
|
||||||
|
|
||||||
|
// tapscriptTweak is an optional tweak, that if specified, will be used
|
||||||
|
// instead of the normal BIP 86 tweak when creating the MuSig2
|
||||||
|
// aggregate key and session.
|
||||||
|
tapscriptTweak fn.Option[input.MuSig2Tweaks]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPartialMusigSession creates a new musig2 session given only the
|
// NewPartialMusigSession creates a new musig2 session given only the
|
||||||
// verification nonce (local nonce), and the other information that has already
|
// verification nonce (local nonce), and the other information that has already
|
||||||
// been bound to the session.
|
// been bound to the session.
|
||||||
func NewPartialMusigSession(verificationNonce musig2.Nonces,
|
func NewPartialMusigSession(verificationNonce musig2.Nonces,
|
||||||
localKey, remoteKey keychain.KeyDescriptor,
|
localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer,
|
||||||
signer input.MuSig2Signer, inputTxOut *wire.TxOut,
|
inputTxOut *wire.TxOut, commitType MusigCommitType,
|
||||||
commitType MusigCommitType) *MusigSession {
|
tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
|
||||||
|
|
||||||
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
|
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
|
||||||
|
|
||||||
@@ -214,13 +254,14 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &MusigSession{
|
return &MusigSession{
|
||||||
nonces: nonces,
|
nonces: nonces,
|
||||||
remoteKey: remoteKey,
|
remoteKey: remoteKey,
|
||||||
localKey: localKey,
|
localKey: localKey,
|
||||||
inputTxOut: inputTxOut,
|
inputTxOut: inputTxOut,
|
||||||
signerKeys: signerKeys,
|
signerKeys: signerKeys,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
commitType: commitType,
|
commitType: commitType,
|
||||||
|
tapscriptTweak: tapscriptTweak,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,9 +295,9 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
|
|||||||
remoteNonce = m.nonces.SigningNonce
|
remoteNonce = m.nonces.SigningNonce
|
||||||
}
|
}
|
||||||
|
|
||||||
tweakDesc := input.MuSig2Tweaks{
|
tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
|
||||||
TaprootBIP0086Tweak: true,
|
TaprootBIP0086Tweak: true,
|
||||||
}
|
})
|
||||||
m.session, err = m.signer.MuSig2CreateSession(
|
m.session, err = m.signer.MuSig2CreateSession(
|
||||||
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
|
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
|
||||||
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
|
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
|
||||||
@@ -351,8 +392,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
|
||||||
|
|
||||||
return NewMusigPartialSig(
|
return NewMusigPartialSig(
|
||||||
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
|
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
|
||||||
|
tapscriptRoot,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,7 +408,7 @@ func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
|
|||||||
|
|
||||||
return NewPartialMusigSession(
|
return NewPartialMusigSession(
|
||||||
*verificationNonce, m.localKey, m.remoteKey, m.signer,
|
*verificationNonce, m.localKey, m.remoteKey, m.signer,
|
||||||
m.inputTxOut, m.commitType,
|
m.inputTxOut, m.commitType, m.tapscriptTweak,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,9 +495,11 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
|
|||||||
// When we verify a commitment signature, we always assume that we're
|
// When we verify a commitment signature, we always assume that we're
|
||||||
// verifying a signature on our local commitment. Therefore, we'll use:
|
// verifying a signature on our local commitment. Therefore, we'll use:
|
||||||
// their remote nonce, and also public key.
|
// their remote nonce, and also public key.
|
||||||
|
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
|
||||||
partialSig := NewMusigPartialSig(
|
partialSig := NewMusigPartialSig(
|
||||||
&musig2.PartialSignature{S: &sig.Sig},
|
&musig2.PartialSignature{S: &sig.Sig},
|
||||||
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
|
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
|
||||||
|
tapscriptRoot,
|
||||||
)
|
)
|
||||||
|
|
||||||
// With the partial sig loaded with the proper context, we'll now
|
// With the partial sig loaded with the proper context, we'll now
|
||||||
@@ -537,6 +583,10 @@ type MusigSessionCfg struct {
|
|||||||
// InputTxOut is the output that we're signing for. This will be the
|
// InputTxOut is the output that we're signing for. This will be the
|
||||||
// funding input.
|
// funding input.
|
||||||
InputTxOut *wire.TxOut
|
InputTxOut *wire.TxOut
|
||||||
|
|
||||||
|
// TapscriptRoot is an optional tweak that can be used to modify the
|
||||||
|
// MuSig2 public key used in the session.
|
||||||
|
TapscriptTweak fn.Option[chainhash.Hash]
|
||||||
}
|
}
|
||||||
|
|
||||||
// MusigPairSession houses the two musig2 sessions needed to do funding and
|
// MusigPairSession houses the two musig2 sessions needed to do funding and
|
||||||
@@ -561,13 +611,14 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
|
|||||||
//
|
//
|
||||||
// Both sessions will be created using only the verification nonce for
|
// Both sessions will be created using only the verification nonce for
|
||||||
// the local+remote party.
|
// the local+remote party.
|
||||||
|
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak)
|
||||||
localSession := NewPartialMusigSession(
|
localSession := NewPartialMusigSession(
|
||||||
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey,
|
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
|
||||||
cfg.Signer, cfg.InputTxOut, LocalMusigCommit,
|
cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
|
||||||
)
|
)
|
||||||
remoteSession := NewPartialMusigSession(
|
remoteSession := NewPartialMusigSession(
|
||||||
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey,
|
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
|
||||||
cfg.Signer, cfg.InputTxOut, RemoteMusigCommit,
|
cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
|
||||||
)
|
)
|
||||||
|
|
||||||
return &MusigPairSession{
|
return &MusigPairSession{
|
||||||
|
|||||||
@@ -221,4 +221,8 @@ type PaymentDescriptor struct {
|
|||||||
// blinded route (ie, not the introduction node) from update_add_htlc's
|
// blinded route (ie, not the introduction node) from update_add_htlc's
|
||||||
// TLVs.
|
// TLVs.
|
||||||
BlindingPoint lnwire.BlindingPointRecord
|
BlindingPoint lnwire.BlindingPointRecord
|
||||||
|
|
||||||
|
// CustomRecords also stores the set of optional custom records that
|
||||||
|
// may have been attached to a sent HTLC.
|
||||||
|
CustomRecords lnwire.CustomRecords
|
||||||
}
|
}
|
||||||
@@ -415,6 +415,10 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
|||||||
chanType |= channeldb.ScidAliasFeatureBit
|
chanType |= channeldb.ScidAliasFeatureBit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.TapscriptRoot.IsSome() {
|
||||||
|
chanType |= channeldb.TapscriptRootBit
|
||||||
|
}
|
||||||
|
|
||||||
return &ChannelReservation{
|
return &ChannelReservation{
|
||||||
ourContribution: &ChannelContribution{
|
ourContribution: &ChannelContribution{
|
||||||
FundingAmount: ourBalance.ToSatoshis(),
|
FundingAmount: ourBalance.ToSatoshis(),
|
||||||
@@ -448,6 +452,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
|||||||
InitialLocalBalance: ourBalance,
|
InitialLocalBalance: ourBalance,
|
||||||
InitialRemoteBalance: theirBalance,
|
InitialRemoteBalance: theirBalance,
|
||||||
Memo: req.Memo,
|
Memo: req.Memo,
|
||||||
|
TapscriptRoot: req.TapscriptRoot,
|
||||||
},
|
},
|
||||||
pushMSat: req.PushMSat,
|
pushMSat: req.PushMSat,
|
||||||
pendingChanID: req.PendingChanID,
|
pendingChanID: req.PendingChanID,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -348,6 +349,21 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
|
|||||||
Packager: channeldb.NewChannelPackager(shortChanID),
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the channel type has a tapscript root, then we'll also specify
|
||||||
|
// one here to apply to both the channels.
|
||||||
|
if chanType.HasTapscriptRoot() {
|
||||||
|
var tapscriptRoot chainhash.Hash
|
||||||
|
_, err := io.ReadFull(rand.Reader, tapscriptRoot[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
someRoot := fn.Some(tapscriptRoot)
|
||||||
|
|
||||||
|
aliceChannelState.TapscriptRoot = someRoot
|
||||||
|
bobChannelState.TapscriptRoot = someRoot
|
||||||
|
}
|
||||||
|
|
||||||
aliceSigner := input.NewMockSigner(aliceKeys, nil)
|
aliceSigner := input.NewMockSigner(aliceKeys, nil)
|
||||||
bobSigner := input.NewMockSigner(bobKeys, nil)
|
bobSigner := input.NewMockSigner(bobKeys, nil)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -50,8 +51,8 @@ var (
|
|||||||
// - <sender sig> <receiver sig> <preimage> <success_script> <control_block>
|
// - <sender sig> <receiver sig> <preimage> <success_script> <control_block>
|
||||||
func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
|
func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
|
||||||
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay,
|
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay,
|
||||||
leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) (
|
leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey,
|
||||||
*wire.MsgTx, error) {
|
auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
// Create a version two transaction (as the success version of this
|
// Create a version two transaction (as the success version of this
|
||||||
// spends an output with a CSV timeout).
|
// spends an output with a CSV timeout).
|
||||||
@@ -71,7 +72,7 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
|
|||||||
// HTLC outputs.
|
// HTLC outputs.
|
||||||
scriptInfo, err := SecondLevelHtlcScript(
|
scriptInfo, err := SecondLevelHtlcScript(
|
||||||
chanType, initiator, revocationKey, delayKey, csvDelay,
|
chanType, initiator, revocationKey, delayKey, csvDelay,
|
||||||
leaseExpiry,
|
leaseExpiry, auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -110,7 +111,8 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
|
|||||||
func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool,
|
func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool,
|
||||||
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||||
cltvExpiry, csvDelay, leaseExpiry uint32,
|
cltvExpiry, csvDelay, leaseExpiry uint32,
|
||||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
revocationKey, delayKey *btcec.PublicKey,
|
||||||
|
auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
// Create a version two transaction (as the success version of this
|
// Create a version two transaction (as the success version of this
|
||||||
// spends an output with a CSV timeout), and set the lock-time to the
|
// spends an output with a CSV timeout), and set the lock-time to the
|
||||||
@@ -134,7 +136,7 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool,
|
|||||||
// HTLC outputs.
|
// HTLC outputs.
|
||||||
scriptInfo, err := SecondLevelHtlcScript(
|
scriptInfo, err := SecondLevelHtlcScript(
|
||||||
chanType, initiator, revocationKey, delayKey, csvDelay,
|
chanType, initiator, revocationKey, delayKey, csvDelay,
|
||||||
leaseExpiry,
|
leaseExpiry, auxLeaf,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -631,6 +632,7 @@ func testSpendValidation(t *testing.T, tweakless bool) {
|
|||||||
commitmentTx, err := CreateCommitTx(
|
commitmentTx, err := CreateCommitTx(
|
||||||
channelType, *fakeFundingTxIn, keyRing, aliceChanCfg,
|
channelType, *fakeFundingTxIn, keyRing, aliceChanCfg,
|
||||||
bobChanCfg, channelBalance, channelBalance, 0, true, 0,
|
bobChanCfg, channelBalance, channelBalance, 0, true, 0,
|
||||||
|
fn.None[CommitAuxLeaves](),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create commitment transaction: %v", nil)
|
t.Fatalf("unable to create commitment transaction: %v", nil)
|
||||||
|
|||||||
+45
-6
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/btcsuite/btcwallet/wallet"
|
"github.com/btcsuite/btcwallet/wallet"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -210,6 +211,11 @@ type InitFundingReserveMsg struct {
|
|||||||
// channel that will be useful to our future selves.
|
// channel that will be useful to our future selves.
|
||||||
Memo []byte
|
Memo []byte
|
||||||
|
|
||||||
|
// TapscriptRoot is the root of the tapscript tree that will be used to
|
||||||
|
// create the funding output. This is an optional field that should
|
||||||
|
// only be set for taproot channels.
|
||||||
|
TapscriptRoot fn.Option[chainhash.Hash]
|
||||||
|
|
||||||
// err is a channel in which all errors will be sent across. Will be
|
// err is a channel in which all errors will be sent across. Will be
|
||||||
// nil if this initial set is successful.
|
// nil if this initial set is successful.
|
||||||
//
|
//
|
||||||
@@ -1464,6 +1470,21 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
|
|||||||
req.err <- nil
|
req.err <- nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createCommitOpts is a struct that holds the options for creating a new
|
||||||
|
// commitment transaction.
|
||||||
|
type createCommitOpts struct {
|
||||||
|
auxLeaves fn.Option[CommitAuxLeaves]
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultCommitOpts returns a new createCommitOpts with default values.
|
||||||
|
func defaultCommitOpts() createCommitOpts {
|
||||||
|
return createCommitOpts{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCommitOpt is a functional option that can be used to modify the way a
|
||||||
|
// new commitment transaction is created.
|
||||||
|
type CreateCommitOpt func(*createCommitOpts)
|
||||||
|
|
||||||
// CreateCommitmentTxns is a helper function that creates the initial
|
// CreateCommitmentTxns is a helper function that creates the initial
|
||||||
// commitment transaction for both parties. This function is used during the
|
// commitment transaction for both parties. This function is used during the
|
||||||
// initial funding workflow as both sides must generate a signature for the
|
// initial funding workflow as both sides must generate a signature for the
|
||||||
@@ -1473,7 +1494,13 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
|||||||
ourChanCfg, theirChanCfg *channeldb.ChannelConfig,
|
ourChanCfg, theirChanCfg *channeldb.ChannelConfig,
|
||||||
localCommitPoint, remoteCommitPoint *btcec.PublicKey,
|
localCommitPoint, remoteCommitPoint *btcec.PublicKey,
|
||||||
fundingTxIn wire.TxIn, chanType channeldb.ChannelType, initiator bool,
|
fundingTxIn wire.TxIn, chanType channeldb.ChannelType, initiator bool,
|
||||||
leaseExpiry uint32) (*wire.MsgTx, *wire.MsgTx, error) {
|
leaseExpiry uint32, opts ...CreateCommitOpt) (*wire.MsgTx, *wire.MsgTx,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
options := defaultCommitOpts()
|
||||||
|
for _, optFunc := range opts {
|
||||||
|
optFunc(&options)
|
||||||
|
}
|
||||||
|
|
||||||
localCommitmentKeys := DeriveCommitmentKeys(
|
localCommitmentKeys := DeriveCommitmentKeys(
|
||||||
localCommitPoint, lntypes.Local, chanType, ourChanCfg,
|
localCommitPoint, lntypes.Local, chanType, ourChanCfg,
|
||||||
@@ -1487,7 +1514,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
|||||||
ourCommitTx, err := CreateCommitTx(
|
ourCommitTx, err := CreateCommitTx(
|
||||||
chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
|
chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
|
||||||
theirChanCfg, localBalance, remoteBalance, 0, initiator,
|
theirChanCfg, localBalance, remoteBalance, 0, initiator,
|
||||||
leaseExpiry,
|
leaseExpiry, options.auxLeaves,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -1501,7 +1528,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
|||||||
theirCommitTx, err := CreateCommitTx(
|
theirCommitTx, err := CreateCommitTx(
|
||||||
chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
|
chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
|
||||||
ourChanCfg, remoteBalance, localBalance, 0, !initiator,
|
ourChanCfg, remoteBalance, localBalance, 0, !initiator,
|
||||||
leaseExpiry,
|
leaseExpiry, options.auxLeaves,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -2102,6 +2129,7 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation,
|
|||||||
if res.musigSessions == nil {
|
if res.musigSessions == nil {
|
||||||
_, fundingOutput, err := input.GenTaprootFundingScript(
|
_, fundingOutput, err := input.GenTaprootFundingScript(
|
||||||
localKey, remoteKey, channelValue,
|
localKey, remoteKey, channelValue,
|
||||||
|
res.partialState.TapscriptRoot,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -2341,11 +2369,14 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
|||||||
fundingTxOut *wire.TxOut
|
fundingTxOut *wire.TxOut
|
||||||
)
|
)
|
||||||
if chanType.IsTaproot() {
|
if chanType.IsTaproot() {
|
||||||
fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript( //nolint:lll
|
//nolint:lll
|
||||||
|
fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript(
|
||||||
ourKey.PubKey, theirKey.PubKey, channelValue,
|
ourKey.PubKey, theirKey.PubKey, channelValue,
|
||||||
|
pendingReservation.partialState.TapscriptRoot,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript( //nolint:lll
|
//nolint:lll
|
||||||
|
fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript(
|
||||||
ourKey.PubKey.SerializeCompressed(),
|
ourKey.PubKey.SerializeCompressed(),
|
||||||
theirKey.PubKey.SerializeCompressed(), channelValue,
|
theirKey.PubKey.SerializeCompressed(), channelValue,
|
||||||
)
|
)
|
||||||
@@ -2465,9 +2496,16 @@ func initStateHints(commit1, commit2 *wire.MsgTx,
|
|||||||
func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
|
func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
|
||||||
fundingTx *wire.MsgTx) error {
|
fundingTx *wire.MsgTx) error {
|
||||||
|
|
||||||
|
var chanOpts []ChannelOpt
|
||||||
|
l.Cfg.AuxLeafStore.WhenSome(func(s AuxLeafStore) {
|
||||||
|
chanOpts = append(chanOpts, WithLeafStore(s))
|
||||||
|
})
|
||||||
|
|
||||||
// First, we'll obtain a fully signed commitment transaction so we can
|
// First, we'll obtain a fully signed commitment transaction so we can
|
||||||
// pass into it on the chanvalidate package for verification.
|
// pass into it on the chanvalidate package for verification.
|
||||||
channel, err := NewLightningChannel(l.Cfg.Signer, channelState, nil)
|
channel, err := NewLightningChannel(
|
||||||
|
l.Cfg.Signer, channelState, nil, chanOpts...,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -2482,6 +2520,7 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
|
|||||||
if channelState.ChanType.IsTaproot() {
|
if channelState.ChanType.IsTaproot() {
|
||||||
fundingScript, _, err = input.GenTaprootFundingScript(
|
fundingScript, _, err = input.GenTaprootFundingScript(
|
||||||
localKey, remoteKey, int64(channel.Capacity),
|
localKey, remoteKey, int64(channel.Capacity),
|
||||||
|
channelState.TapscriptRoot,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+13
-1
@@ -372,6 +372,10 @@ type Config struct {
|
|||||||
AddLocalAlias func(alias, base lnwire.ShortChannelID,
|
AddLocalAlias func(alias, base lnwire.ShortChannelID,
|
||||||
gossip bool) error
|
gossip bool) error
|
||||||
|
|
||||||
|
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||||
|
// leaves for certain custom channel types.
|
||||||
|
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||||
|
|
||||||
// PongBuf is a slice we'll reuse instead of allocating memory on the
|
// PongBuf is a slice we'll reuse instead of allocating memory on the
|
||||||
// heap. Since only reads will occur and no writes, there is no need
|
// heap. Since only reads will occur and no writes, there is no need
|
||||||
// for any synchronization primitives. As a result, it's safe to share
|
// for any synchronization primitives. As a result, it's safe to share
|
||||||
@@ -943,8 +947,12 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chanOpts []lnwallet.ChannelOpt
|
||||||
|
p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||||
|
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||||
|
})
|
||||||
lnChan, err := lnwallet.NewLightningChannel(
|
lnChan, err := lnwallet.NewLightningChannel(
|
||||||
p.cfg.Signer, dbChan, p.cfg.SigPool,
|
p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create channel "+
|
return nil, fmt.Errorf("unable to create channel "+
|
||||||
@@ -4151,6 +4159,10 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error {
|
|||||||
chanOpts = append(chanOpts, lnwallet.WithSkipNonceInit())
|
chanOpts = append(chanOpts, lnwallet.WithSkipNonceInit())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||||
|
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||||
|
})
|
||||||
|
|
||||||
// If not already active, we'll add this channel to the set of active
|
// If not already active, we'll add this channel to the set of active
|
||||||
// channels, so we can look it up later easily according to its channel
|
// channels, so we can look it up later easily according to its channel
|
||||||
// ID.
|
// ID.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
|
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
|
||||||
@@ -43,10 +44,15 @@ func (m *MusigChanCloser) ProposalClosingOpts() (
|
|||||||
}
|
}
|
||||||
|
|
||||||
localKey, remoteKey := m.channel.MultiSigKeys()
|
localKey, remoteKey := m.channel.MultiSigKeys()
|
||||||
|
|
||||||
|
tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)(
|
||||||
|
m.channel.State().TapscriptRoot,
|
||||||
|
)
|
||||||
|
|
||||||
m.musigSession = lnwallet.NewPartialMusigSession(
|
m.musigSession = lnwallet.NewPartialMusigSession(
|
||||||
*m.remoteNonce, localKey, remoteKey,
|
*m.remoteNonce, localKey, remoteKey,
|
||||||
m.channel.Signer, m.channel.FundingTxOut(),
|
m.channel.Signer, m.channel.FundingTxOut(),
|
||||||
lnwallet.RemoteMusigCommit,
|
lnwallet.RemoteMusigCommit, tapscriptTweak,
|
||||||
)
|
)
|
||||||
|
|
||||||
err := m.musigSession.FinalizeSession(*m.localNonce)
|
err := m.musigSession.FinalizeSession(*m.localNonce)
|
||||||
|
|||||||
@@ -1273,6 +1273,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
|
|
||||||
return &pc.Incoming
|
return &pc.Incoming
|
||||||
},
|
},
|
||||||
|
AuxLeafStore: implCfg.AuxLeafStore,
|
||||||
}, dbs.ChanStateDB)
|
}, dbs.ChanStateDB)
|
||||||
|
|
||||||
// Select the configuration and funding parameters for Bitcoin.
|
// Select the configuration and funding parameters for Bitcoin.
|
||||||
@@ -1607,6 +1608,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
|
|
||||||
br, err := lnwallet.NewBreachRetribution(
|
br, err := lnwallet.NewBreachRetribution(
|
||||||
channel, commitHeight, 0, nil,
|
channel, commitHeight, 0, nil,
|
||||||
|
implCfg.AuxLeafStore,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
@@ -4073,6 +4075,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||||||
DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(),
|
DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(),
|
||||||
MaxFeeExposure: thresholdMSats,
|
MaxFeeExposure: thresholdMSats,
|
||||||
Quit: s.quit,
|
Quit: s.quit,
|
||||||
|
AuxLeafStore: s.implCfg.AuxLeafStore,
|
||||||
MsgRouter: s.implCfg.MsgRouter,
|
MsgRouter: s.implCfg.MsgRouter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@@ -307,9 +308,11 @@ func newTaprootJusticeKit(sweepScript []byte,
|
|||||||
|
|
||||||
keyRing := breachInfo.KeyRing
|
keyRing := breachInfo.KeyRing
|
||||||
|
|
||||||
|
// TODO(roasbeef): aux leaf tower updates needed
|
||||||
|
|
||||||
tree, err := input.NewLocalCommitScriptTree(
|
tree, err := input.NewLocalCommitScriptTree(
|
||||||
breachInfo.RemoteDelay, keyRing.ToLocalKey,
|
breachInfo.RemoteDelay, keyRing.ToLocalKey,
|
||||||
keyRing.RevocationKey,
|
keyRing.RevocationKey, fn.None[txscript.TapLeaf](),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -416,7 +419,9 @@ func (t *taprootJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
|
|||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptTree, err := input.NewRemoteCommitScriptTree(toRemotePk)
|
scriptTree, err := input.NewRemoteCommitScriptTree(
|
||||||
|
toRemotePk, fn.None[txscript.TapLeaf](),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@@ -304,7 +305,9 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
|||||||
name: "taproot commitment",
|
name: "taproot commitment",
|
||||||
blobType: TypeAltruistTaprootCommit,
|
blobType: TypeAltruistTaprootCommit,
|
||||||
expWitnessScript: func(pk *btcec.PublicKey) []byte {
|
expWitnessScript: func(pk *btcec.PublicKey) []byte {
|
||||||
tree, _ := input.NewRemoteCommitScriptTree(pk)
|
tree, _ := input.NewRemoteCommitScriptTree(
|
||||||
|
pk, fn.None[txscript.TapLeaf](),
|
||||||
|
)
|
||||||
|
|
||||||
return tree.SettleLeaf.Script
|
return tree.SettleLeaf.Script
|
||||||
},
|
},
|
||||||
@@ -461,6 +464,7 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
|
|||||||
|
|
||||||
script, _ := input.NewLocalCommitScriptTree(
|
script, _ := input.NewLocalCommitScriptTree(
|
||||||
csvDelay, delay, rev,
|
csvDelay, delay, rev,
|
||||||
|
fn.None[txscript.TapLeaf](),
|
||||||
)
|
)
|
||||||
|
|
||||||
return script.RevocationLeaf.Script
|
return script.RevocationLeaf.Script
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
@@ -123,7 +124,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
|
|
||||||
if isTaprootChannel {
|
if isTaprootChannel {
|
||||||
toLocalCommitTree, err = input.NewLocalCommitScriptTree(
|
toLocalCommitTree, err = input.NewLocalCommitScriptTree(
|
||||||
csvDelay, toLocalPK, revPK,
|
csvDelay, toLocalPK, revPK, fn.None[txscript.TapLeaf](),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -174,7 +175,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
toRemoteSequence = 1
|
toRemoteSequence = 1
|
||||||
|
|
||||||
commitScriptTree, err := input.NewRemoteCommitScriptTree(
|
commitScriptTree, err := input.NewRemoteCommitScriptTree(
|
||||||
toRemotePK,
|
toRemotePK, fn.None[txscript.TapLeaf](),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
@@ -136,6 +137,7 @@ func genTaskTest(
|
|||||||
if chanType.IsTaproot() {
|
if chanType.IsTaproot() {
|
||||||
scriptTree, _ := input.NewLocalCommitScriptTree(
|
scriptTree, _ := input.NewLocalCommitScriptTree(
|
||||||
csvDelay, toLocalPK, revPK,
|
csvDelay, toLocalPK, revPK,
|
||||||
|
fn.None[txscript.TapLeaf](),
|
||||||
)
|
)
|
||||||
|
|
||||||
pkScript, _ := input.PayToTaprootScript(
|
pkScript, _ := input.PayToTaprootScript(
|
||||||
@@ -189,7 +191,7 @@ func genTaskTest(
|
|||||||
|
|
||||||
if chanType.IsTaproot() {
|
if chanType.IsTaproot() {
|
||||||
scriptTree, _ := input.NewRemoteCommitScriptTree(
|
scriptTree, _ := input.NewRemoteCommitScriptTree(
|
||||||
toRemotePK,
|
toRemotePK, fn.None[txscript.TapLeaf](),
|
||||||
)
|
)
|
||||||
|
|
||||||
pkScript, _ := input.PayToTaprootScript(
|
pkScript, _ := input.PayToTaprootScript(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channelnotifier"
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
@@ -230,12 +231,14 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) {
|
|||||||
|
|
||||||
// Construct the to-local witness script.
|
// Construct the to-local witness script.
|
||||||
toLocalScriptTree, err := input.NewLocalCommitScriptTree(
|
toLocalScriptTree, err := input.NewLocalCommitScriptTree(
|
||||||
c.csvDelay, c.toLocalPK, c.revPK,
|
c.csvDelay, c.toLocalPK, c.revPK, fn.None[txscript.TapLeaf](),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create to-local script")
|
require.NoError(t, err, "unable to create to-local script")
|
||||||
|
|
||||||
// Construct the to-remote witness script.
|
// Construct the to-remote witness script.
|
||||||
toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(c.toRemotePK)
|
toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(
|
||||||
|
c.toRemotePK, fn.None[txscript.TapLeaf](),
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable to create to-remote script")
|
require.NoError(t, err, "unable to create to-remote script")
|
||||||
|
|
||||||
// Compute the to-local witness script hash.
|
// Compute the to-local witness script hash.
|
||||||
|
|||||||
Reference in new issue
Block a user