mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-25 15:09:44 +01:00
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:
commit
5c3a8e949c
43 changed files with 2457 additions and 785 deletions
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
|
@ -63,6 +64,10 @@ type Config struct {
|
|||
// state.
|
||||
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 *blockcache.BlockCache
|
||||
|
||||
|
|
|
@ -226,28 +226,109 @@ const (
|
|||
// A tlv type definition used to serialize an outpoint's indexStatus
|
||||
// for use in the outpoint index.
|
||||
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
|
||||
// outpoint is in. Currently only two possible values.
|
||||
type indexStatus uint8
|
||||
|
@ -325,6 +406,11 @@ const (
|
|||
// SimpleTaprootFeatureBit indicates that the simple-taproot-chans
|
||||
// feature bit was negotiated during the lifetime of the channel.
|
||||
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
|
||||
|
@ -395,6 +481,12 @@ func (c ChannelType) IsTaproot() bool {
|
|||
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
|
||||
// that are responsible for providing bounds on the state space of the abstract
|
||||
// channel state. These values must be remembered for normal channel operation
|
||||
|
@ -496,6 +588,53 @@ type ChannelConfig struct {
|
|||
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
|
||||
// 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
|
||||
|
@ -562,6 +701,11 @@ type ChannelCommitment struct {
|
|||
// able by us.
|
||||
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
|
||||
// the script for the commitment transaction above. This is 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
|
||||
// commitment height.
|
||||
Htlcs []HTLC
|
||||
}
|
||||
|
||||
// TODO(roasbeef): pending commit pointer?
|
||||
// * lets just walk through
|
||||
// amendTlvData updates the channel with the given auxiliary TLV data.
|
||||
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
|
||||
|
@ -867,6 +1028,16 @@ type OpenChannel struct {
|
|||
// channel that will be useful to our future selves.
|
||||
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
|
||||
Db *ChannelStateDB
|
||||
|
||||
|
@ -1025,6 +1196,64 @@ func (c *OpenChannel) SetBroadcastHeight(height uint32) {
|
|||
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
|
||||
// on disk.
|
||||
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
|
||||
// a TLV stream of additional information associated with the HTLC.
|
||||
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
|
||||
|
@ -2369,6 +2604,11 @@ func (h *HTLC) serializeExtraData() error {
|
|||
records = append(records, &b)
|
||||
})
|
||||
|
||||
records, err := h.CustomRecords.ExtendRecordProducers(records)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.ExtraData.PackRecords(records...)
|
||||
}
|
||||
|
||||
|
@ -2390,8 +2630,19 @@ func (h *HTLC) deserializeExtraData() error {
|
|||
|
||||
if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil {
|
||||
h.BlindingPoint = tlv.SomeRecordT(blindingPoint)
|
||||
|
||||
// Remove the entry from the TLV map. Anything left in the map
|
||||
// will be included in the custom records field.
|
||||
delete(tlvMap, h.BlindingPoint.TlvType())
|
||||
}
|
||||
|
||||
// Set the custom records field to the remaining TLV records.
|
||||
customRecords, err := lnwire.NewCustomRecords(tlvMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.CustomRecords = customRecords
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2529,6 +2780,8 @@ func (h *HTLC) Copy() HTLC {
|
|||
copy(clone.Signature[:], h.Signature)
|
||||
copy(clone.RHash[:], h.RHash[:])
|
||||
copy(clone.ExtraData, h.ExtraData)
|
||||
clone.BlindingPoint = h.BlindingPoint
|
||||
clone.CustomRecords = h.CustomRecords.Copy()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// our internal state.
|
||||
snapshot.Htlcs = make([]HTLC, len(localCommit.Htlcs))
|
||||
|
@ -4030,32 +4309,9 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Convert balance fields into uint64.
|
||||
localBalance := uint64(channel.InitialLocalBalance)
|
||||
remoteBalance := uint64(channel.InitialRemoteBalance)
|
||||
|
||||
// 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
|
||||
auxData := channel.extractTlvData()
|
||||
if err := auxData.encode(&w); err != nil {
|
||||
return fmt.Errorf("unable to encode aux data: %w", err)
|
||||
}
|
||||
|
||||
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
|
||||
|
@ -4142,6 +4398,12 @@ func putChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment,
|
|||
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())
|
||||
}
|
||||
|
||||
|
@ -4244,45 +4506,14 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Create balance fields in uint64, and Memo field as byte slice.
|
||||
var (
|
||||
localBalance uint64
|
||||
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
|
||||
var auxData openChannelTlvData
|
||||
if err := auxData.decode(r); err != nil {
|
||||
return fmt.Errorf("unable to decode aux data: %w", err)
|
||||
}
|
||||
|
||||
if err := tlvStream.Decode(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// Assign all the relevant fields from the aux data into the actual
|
||||
// open channel.
|
||||
channel.amendTlvData(auxData)
|
||||
|
||||
channel.Packager = NewChannelPackager(channel.ShortChannelID)
|
||||
|
||||
|
@ -4318,7 +4549,9 @@ func deserializeChanCommit(r io.Reader) (ChannelCommitment, error) {
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func fetchChanCommitment(chanBucket kvdb.RBucket, local bool) (ChannelCommitment, error) {
|
||||
func fetchChanCommitment(chanBucket kvdb.RBucket,
|
||||
local bool) (ChannelCommitment, error) {
|
||||
|
||||
var commitKey []byte
|
||||
if local {
|
||||
commitKey = append(chanCommitmentKey, byte(0x00))
|
||||
|
@ -4332,7 +4565,23 @@ func fetchChanCommitment(chanBucket kvdb.RBucket, local bool) (ChannelCommitment
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -4440,6 +4689,25 @@ func deleteThawHeight(chanBucket kvdb.RwBucket) error {
|
|||
return chanBucket.Delete(frozenChanKey)
|
||||
}
|
||||
|
||||
// keyLocRecord is a wrapper struct around keychain.KeyLocator to implement the
|
||||
// tlv.RecordProducer interface.
|
||||
type keyLocRecord struct {
|
||||
keychain.KeyLocator
|
||||
}
|
||||
|
||||
// Record creates a Record out of a KeyLocator using the passed Type and the
|
||||
// EKeyLocator and DKeyLocator functions. The size will always be 8 as
|
||||
// KeyFamily is uint32 and the Index is uint32.
|
||||
//
|
||||
// NOTE: This is part of the tlv.RecordProducer interface.
|
||||
func (k *keyLocRecord) Record() tlv.Record {
|
||||
// Note that we set the type here as zero, as when used with a
|
||||
// tlv.RecordT, the type param will be used as the type.
|
||||
return tlv.MakeStaticRecord(
|
||||
0, &k.KeyLocator, 8, EKeyLocator, DKeyLocator,
|
||||
)
|
||||
}
|
||||
|
||||
// EKeyLocator is an encoder for keychain.KeyLocator.
|
||||
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
if v, ok := val.(*keychain.KeyLocator); ok {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// channel.
|
||||
type ShutdownInfo struct {
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"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.
|
||||
var channelIDOption = func(chanID lnwire.ShortChannelID) testChannelOption {
|
||||
func channelIDOption(chanID lnwire.ShortChannelID) testChannelOption {
|
||||
return func(params *testChannelParams) {
|
||||
params.channel.ShortChannelID = chanID
|
||||
}
|
||||
|
@ -326,6 +327,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
|||
uniqueOutputIndex.Add(1)
|
||||
op := wire.OutPoint{Hash: key, Index: uniqueOutputIndex.Load()}
|
||||
|
||||
var tapscriptRoot chainhash.Hash
|
||||
copy(tapscriptRoot[:], bytes.Repeat([]byte{1}, 32))
|
||||
|
||||
return &OpenChannel{
|
||||
ChanType: SingleFunderBit | FrozenBit,
|
||||
ChainHash: key,
|
||||
|
@ -347,6 +351,7 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
|||
FeePerKw: btcutil.Amount(5000),
|
||||
CommitTx: channels.TestFundingTx,
|
||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||
CustomBlob: fn.Some([]byte{1, 2, 3}),
|
||||
},
|
||||
RemoteCommitment: ChannelCommitment{
|
||||
CommitHeight: 0,
|
||||
|
@ -356,6 +361,7 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
|||
FeePerKw: btcutil.Amount(5000),
|
||||
CommitTx: channels.TestFundingTx,
|
||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||
CustomBlob: fn.Some([]byte{4, 5, 6}),
|
||||
},
|
||||
NumConfsRequired: 4,
|
||||
RemoteCurrentRevocation: privKey.PubKey(),
|
||||
|
@ -368,6 +374,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
|||
ThawHeight: uint32(defaultPendingHeight),
|
||||
InitialLocalBalance: lnwire.MilliSatoshi(9000),
|
||||
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,
|
||||
r *RevocationLog) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
// Check the common fields.
|
||||
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.
|
||||
require.Equal(t, len(r.HTLCEntries), len(c.Htlcs), "HTLCs len mismatch")
|
||||
for i, rHtlc := range r.HTLCEntries {
|
||||
cHtlc := c.Htlcs[i]
|
||||
require.Equal(t, rHtlc.RHash, cHtlc.RHash, "RHash mismatch")
|
||||
require.Equal(t, rHtlc.Amt, cHtlc.Amt.ToSatoshis(),
|
||||
"Amt mismatch")
|
||||
require.Equal(t, rHtlc.RefundTimeout, cHtlc.RefundTimeout,
|
||||
"RefundTimeout mismatch")
|
||||
require.EqualValues(t, rHtlc.OutputIndex, cHtlc.OutputIndex,
|
||||
"OutputIndex mismatch")
|
||||
require.Equal(t, rHtlc.Incoming, cHtlc.Incoming,
|
||||
"Incoming mismatch")
|
||||
require.Equal(t, rHtlc.RHash.Val[:], cHtlc.RHash[:], "RHash")
|
||||
require.Equal(
|
||||
t, rHtlc.Amt.Val.Int(), cHtlc.Amt.ToSatoshis(), "Amt",
|
||||
)
|
||||
require.Equal(
|
||||
t, rHtlc.RefundTimeout.Val, cHtlc.RefundTimeout,
|
||||
"RefundTimeout",
|
||||
)
|
||||
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,
|
||||
CommitSig: newSig,
|
||||
Htlcs: htlcs,
|
||||
CustomBlob: fn.Some([]byte{4, 5, 6}),
|
||||
}
|
||||
|
||||
// First update the local node's broadcastable state and also add a
|
||||
|
@ -694,9 +712,14 @@ func TestChannelStateTransition(t *testing.T) {
|
|||
// have been updated.
|
||||
updatedChannel, err := cdb.FetchOpenChannels(channel.IdentityPub)
|
||||
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()
|
||||
require.NoError(t, err, "unable to read commitment height from disk")
|
||||
|
||||
if numDiskUpdates != uint64(commitment.CommitHeight) {
|
||||
t.Fatalf("num disk updates doesn't match: %v vs %v",
|
||||
numDiskUpdates, commitment.CommitHeight)
|
||||
|
@ -799,10 +822,10 @@ func TestChannelStateTransition(t *testing.T) {
|
|||
|
||||
// Check the output indexes are saved as expected.
|
||||
require.EqualValues(
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex,
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex.Val,
|
||||
)
|
||||
require.EqualValues(
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex,
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex.Val,
|
||||
)
|
||||
|
||||
// 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.
|
||||
require.EqualValues(
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex,
|
||||
t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex.Val,
|
||||
)
|
||||
require.EqualValues(
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex,
|
||||
t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex.Val,
|
||||
)
|
||||
|
||||
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 {
|
||||
name string
|
||||
htlcs []HTLC
|
||||
|
@ -1663,6 +1704,7 @@ func TestHTLCsExtraData(t *testing.T) {
|
|||
mockHtlc,
|
||||
blindingPointHTLC,
|
||||
mockHtlc,
|
||||
customDataHTLC,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
@ -16,16 +17,15 @@ import (
|
|||
const (
|
||||
// OutputIndexEmpty is used when the output index doesn't exist.
|
||||
OutputIndexEmpty = math.MaxUint16
|
||||
)
|
||||
|
||||
// A set of tlv type definitions used to serialize the body of
|
||||
// revocation logs to the database.
|
||||
//
|
||||
// NOTE: A migration should be added whenever this list changes.
|
||||
revLogOurOutputIndexType tlv.Type = 0
|
||||
revLogTheirOutputIndexType tlv.Type = 1
|
||||
revLogCommitTxHashType tlv.Type = 2
|
||||
revLogOurBalanceType tlv.Type = 3
|
||||
revLogTheirBalanceType tlv.Type = 4
|
||||
type (
|
||||
// BigSizeAmount is a type alias for a TLV record of a btcutil.Amount.
|
||||
BigSizeAmount = tlv.BigSizeT[btcutil.Amount]
|
||||
|
||||
// BigSizeMilliSatoshi is a type alias for a TLV record of a
|
||||
// lnwire.MilliSatoshi.
|
||||
BigSizeMilliSatoshi = tlv.BigSizeT[lnwire.MilliSatoshi]
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -54,6 +54,74 @@ var (
|
|||
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
|
||||
// historical HTLCs, which is useful for constructing RevocationLog when a
|
||||
// breach is detected.
|
||||
|
@ -72,118 +140,92 @@ var (
|
|||
// made into tlv records without further conversion.
|
||||
type HTLCEntry struct {
|
||||
// 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
|
||||
// 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
|
||||
// within the commitment transaction.
|
||||
//
|
||||
// NOTE: we use uint16 instead of int32 here to save us 2 bytes, which
|
||||
// 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
|
||||
// HTLC.
|
||||
//
|
||||
// NOTE: this field is the memory representation of the field
|
||||
// incomingUint.
|
||||
Incoming bool
|
||||
Incoming tlv.RecordT[tlv.TlvType3, bool]
|
||||
|
||||
// Amt is the amount of satoshis this HTLC escrows.
|
||||
//
|
||||
// NOTE: this field is the memory representation of the field amtUint.
|
||||
Amt btcutil.Amount
|
||||
Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]]
|
||||
|
||||
// amtTlv is the uint64 format of Amt. This field is created so we can
|
||||
// easily make it into a tlv record and save it to disk.
|
||||
//
|
||||
// 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
|
||||
// CustomBlob is an optional blob that can be used to store information
|
||||
// specific to revocation handling for a custom channel type.
|
||||
CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
|
||||
|
||||
// incomingTlv is the uint8 format of Incoming. This field is created
|
||||
// so we can easily make it into a tlv record and save it to disk.
|
||||
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)
|
||||
// HtlcIndex is the index of the HTLC in the channel.
|
||||
HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, uint16]
|
||||
}
|
||||
|
||||
// toTlvStream converts an HTLCEntry record into a tlv representation.
|
||||
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
||||
const (
|
||||
// A set of tlv type definitions used to serialize htlc entries
|
||||
// to the database. We define it here instead of the head of
|
||||
// the file to avoid naming conflicts.
|
||||
//
|
||||
// NOTE: A migration should be added whenever this list
|
||||
// changes.
|
||||
rHashType tlv.Type = 0
|
||||
refundTimeoutType tlv.Type = 1
|
||||
outputIndexType tlv.Type = 2
|
||||
incomingType tlv.Type = 3
|
||||
amtType tlv.Type = 4
|
||||
)
|
||||
records := []tlv.Record{
|
||||
h.RHash.Record(),
|
||||
h.RefundTimeout.Record(),
|
||||
h.OutputIndex.Record(),
|
||||
h.Incoming.Record(),
|
||||
h.Amt.Record(),
|
||||
}
|
||||
|
||||
return tlv.NewStream(
|
||||
tlv.MakeDynamicRecord(
|
||||
rHashType, &h.RHash, h.RHashLen,
|
||||
RHashEncoder, RHashDecoder,
|
||||
h.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
|
||||
records = append(records, r.Record())
|
||||
})
|
||||
|
||||
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(
|
||||
refundTimeoutType, &h.RefundTimeout,
|
||||
RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||
htlc.RefundTimeout,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
outputIndexType, &h.OutputIndex,
|
||||
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2](
|
||||
uint16(htlc.OutputIndex),
|
||||
),
|
||||
tlv.MakePrimitiveRecord(incomingType, &h.incomingTlv),
|
||||
// We will save 3 bytes if the amount is less or equal to
|
||||
// 4,294,967,295 msat, or roughly 0.043 bitcoin.
|
||||
tlv.MakeBigSizeRecord(amtType, &h.amtTlv),
|
||||
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](htlc.Incoming),
|
||||
Amt: tlv.NewRecordT[tlv.TlvType4](
|
||||
tlv.NewBigSizeT(htlc.Amt.ToSatoshis()),
|
||||
),
|
||||
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
|
||||
// fields can be viewed as a subset of a ChannelCommitment's. In the database,
|
||||
// all historical versions of the RevocationLog are saved using the
|
||||
|
@ -191,15 +233,15 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
|||
type RevocationLog struct {
|
||||
// OurOutputIndex specifies our output index in this commitment. In a
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// particular commitment height.
|
||||
|
@ -209,21 +251,65 @@ type RevocationLog struct {
|
|||
// directly spendable by us. In other words, it is the value of the
|
||||
// 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
|
||||
// this field, it could be the case that the field is not present for
|
||||
// all revocation logs.
|
||||
OurBalance *lnwire.MilliSatoshi
|
||||
OurBalance tlv.OptionalRecordT[tlv.TlvType3, BigSizeMilliSatoshi]
|
||||
|
||||
// TheirBalance is the current available balance within the channel
|
||||
// directly spendable by the remote node. In other words, it is the
|
||||
// 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
|
||||
// this field, it could be the case that the field is not present for
|
||||
// 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
|
||||
|
@ -242,15 +328,32 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
|
|||
}
|
||||
|
||||
rl := &RevocationLog{
|
||||
OurOutputIndex: uint16(ourOutputIndex),
|
||||
TheirOutputIndex: uint16(theirOutputIndex),
|
||||
CommitTxHash: commit.CommitTx.TxHash(),
|
||||
OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
|
||||
uint16(ourOutputIndex),
|
||||
),
|
||||
TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||
uint16(theirOutputIndex),
|
||||
),
|
||||
CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2, [32]byte](
|
||||
commit.CommitTx.TxHash(),
|
||||
),
|
||||
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
|
||||
}
|
||||
|
||||
commit.CustomBlob.WhenSome(func(blob tlv.Blob) {
|
||||
rl.CustomBlob = tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
|
||||
)
|
||||
})
|
||||
|
||||
if !noAmtData {
|
||||
rl.OurBalance = &commit.LocalBalance
|
||||
rl.TheirBalance = &commit.RemoteBalance
|
||||
rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
|
||||
tlv.NewBigSizeT(commit.LocalBalance),
|
||||
))
|
||||
|
||||
rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
|
||||
tlv.NewBigSizeT(commit.RemoteBalance),
|
||||
))
|
||||
}
|
||||
|
||||
for _, htlc := range commit.Htlcs {
|
||||
|
@ -265,12 +368,9 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
|
|||
return ErrOutputIndexTooBig
|
||||
}
|
||||
|
||||
entry := &HTLCEntry{
|
||||
RHash: htlc.RHash,
|
||||
RefundTimeout: htlc.RefundTimeout,
|
||||
Incoming: htlc.Incoming,
|
||||
OutputIndex: uint16(htlc.OutputIndex),
|
||||
Amt: htlc.Amt.ToSatoshis(),
|
||||
entry, err := NewHTLCEntryFromHTLC(htlc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rl.HTLCEntries = append(rl.HTLCEntries, entry)
|
||||
}
|
||||
|
@ -306,31 +406,27 @@ func fetchRevocationLog(log kvdb.RBucket,
|
|||
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
|
||||
// Add the tlv records for all non-optional fields.
|
||||
records := []tlv.Record{
|
||||
tlv.MakePrimitiveRecord(
|
||||
revLogOurOutputIndexType, &rl.OurOutputIndex,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
revLogCommitTxHashType, &rl.CommitTxHash,
|
||||
),
|
||||
rl.OurOutputIndex.Record(),
|
||||
rl.TheirOutputIndex.Record(),
|
||||
rl.CommitTxHash.Record(),
|
||||
}
|
||||
|
||||
// Now we add any optional fields that are non-nil.
|
||||
if rl.OurBalance != nil {
|
||||
lb := uint64(*rl.OurBalance)
|
||||
records = append(records, tlv.MakeBigSizeRecord(
|
||||
revLogOurBalanceType, &lb,
|
||||
))
|
||||
}
|
||||
rl.OurBalance.WhenSome(
|
||||
func(r tlv.RecordT[tlv.TlvType3, BigSizeMilliSatoshi]) {
|
||||
records = append(records, r.Record())
|
||||
},
|
||||
)
|
||||
|
||||
if rl.TheirBalance != nil {
|
||||
rb := uint64(*rl.TheirBalance)
|
||||
records = append(records, tlv.MakeBigSizeRecord(
|
||||
revLogTheirBalanceType, &rb,
|
||||
))
|
||||
}
|
||||
rl.TheirBalance.WhenSome(
|
||||
func(r tlv.RecordT[tlv.TlvType4, BigSizeMilliSatoshi]) {
|
||||
records = append(records, r.Record())
|
||||
},
|
||||
)
|
||||
|
||||
rl.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
|
||||
records = append(records, r.Record())
|
||||
})
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := tlv.NewStream(records...)
|
||||
|
@ -351,14 +447,6 @@ func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
|
|||
// format.
|
||||
func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
|
||||
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.
|
||||
tlvStream, err := htlc.toTlvStream()
|
||||
if err != nil {
|
||||
|
@ -376,27 +464,20 @@ func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
|
|||
|
||||
// deserializeRevocationLog deserializes a RevocationLog based on tlv format.
|
||||
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
|
||||
var (
|
||||
rl RevocationLog
|
||||
ourBalance uint64
|
||||
theirBalance uint64
|
||||
)
|
||||
var rl RevocationLog
|
||||
|
||||
ourBalance := rl.OurBalance.Zero()
|
||||
theirBalance := rl.TheirBalance.Zero()
|
||||
customBlob := rl.CustomBlob.Zero()
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := tlv.NewStream(
|
||||
tlv.MakePrimitiveRecord(
|
||||
revLogOurOutputIndexType, &rl.OurOutputIndex,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
revLogCommitTxHashType, &rl.CommitTxHash,
|
||||
),
|
||||
tlv.MakeBigSizeRecord(revLogOurBalanceType, &ourBalance),
|
||||
tlv.MakeBigSizeRecord(
|
||||
revLogTheirBalanceType, &theirBalance,
|
||||
),
|
||||
rl.OurOutputIndex.Record(),
|
||||
rl.TheirOutputIndex.Record(),
|
||||
rl.CommitTxHash.Record(),
|
||||
ourBalance.Record(),
|
||||
theirBalance.Record(),
|
||||
customBlob.Record(),
|
||||
)
|
||||
if err != nil {
|
||||
return rl, err
|
||||
|
@ -408,14 +489,16 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
|
|||
return rl, err
|
||||
}
|
||||
|
||||
if t, ok := parsedTypes[revLogOurBalanceType]; ok && t == nil {
|
||||
lb := lnwire.MilliSatoshi(ourBalance)
|
||||
rl.OurBalance = &lb
|
||||
if t, ok := parsedTypes[ourBalance.TlvType()]; ok && t == nil {
|
||||
rl.OurBalance = tlv.SomeRecordT(ourBalance)
|
||||
}
|
||||
|
||||
if t, ok := parsedTypes[revLogTheirBalanceType]; ok && t == nil {
|
||||
rb := lnwire.MilliSatoshi(theirBalance)
|
||||
rl.TheirBalance = &rb
|
||||
if t, ok := parsedTypes[theirBalance.TlvType()]; ok && t == nil {
|
||||
rl.TheirBalance = tlv.SomeRecordT(theirBalance)
|
||||
}
|
||||
|
||||
if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
|
||||
rl.CustomBlob = tlv.SomeRecordT(customBlob)
|
||||
}
|
||||
|
||||
// Read the HTLC entries.
|
||||
|
@ -432,14 +515,28 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
|
|||
for {
|
||||
var htlc HTLCEntry
|
||||
|
||||
customBlob := htlc.CustomBlob.Zero()
|
||||
htlcIndex := htlc.HtlcIndex.Zero()
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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.
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
|
@ -447,13 +544,13 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Patch the Incoming field.
|
||||
if htlc.incomingTlv == 1 {
|
||||
htlc.Incoming = true
|
||||
if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
|
||||
htlc.CustomBlob = tlv.SomeRecordT(customBlob)
|
||||
}
|
||||
|
||||
// Patch the Amt field.
|
||||
htlc.Amt = btcutil.Amount(htlc.amtTlv)
|
||||
if t, ok := parsedTypes[htlcIndex.TlvType()]; ok && t == nil {
|
||||
htlc.HtlcIndex = tlv.SomeRecordT(htlcIndex)
|
||||
}
|
||||
|
||||
// Append the entry.
|
||||
htlcs = append(htlcs, &htlc)
|
||||
|
@ -469,6 +566,7 @@ func writeTlvStream(w io.Writer, s *tlv.Stream) error {
|
|||
if err := s.Encode(&b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the stream's length as a varint.
|
||||
err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{})
|
||||
if err != nil {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntest/channels"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
@ -33,17 +34,38 @@ var (
|
|||
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{
|
||||
RefundTimeout: 740_000,
|
||||
OutputIndex: 10,
|
||||
Incoming: true,
|
||||
Amt: 1000_000,
|
||||
amtTlv: 1000_000,
|
||||
incomingTlv: 1,
|
||||
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)),
|
||||
),
|
||||
CustomBlob: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType5](blobBytes),
|
||||
),
|
||||
HtlcIndex: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType6, uint16](0x33),
|
||||
),
|
||||
}
|
||||
testHTLCEntryBytes = []byte{
|
||||
// Body length 23.
|
||||
0x16,
|
||||
// Body length 45.
|
||||
0x2d,
|
||||
// Rhash tlv.
|
||||
0x0, 0x0,
|
||||
// RefundTimeout tlv.
|
||||
|
@ -54,6 +76,45 @@ var (
|
|||
0x3, 0x1, 0x1,
|
||||
// Amt tlv.
|
||||
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)
|
||||
|
@ -68,24 +129,29 @@ var (
|
|||
CommitTx: channels.TestFundingTx,
|
||||
CommitSig: bytes.Repeat([]byte{1}, 71),
|
||||
Htlcs: []HTLC{{
|
||||
RefundTimeout: testHTLCEntry.RefundTimeout,
|
||||
OutputIndex: int32(testHTLCEntry.OutputIndex),
|
||||
Incoming: testHTLCEntry.Incoming,
|
||||
Amt: lnwire.NewMSatFromSatoshis(
|
||||
testHTLCEntry.Amt,
|
||||
RefundTimeout: testHTLCEntry.RefundTimeout.Val,
|
||||
OutputIndex: int32(testHTLCEntry.OutputIndex.Val),
|
||||
HtlcIndex: uint64(
|
||||
testHTLCEntry.HtlcIndex.ValOpt().
|
||||
UnsafeFromSome(),
|
||||
),
|
||||
Incoming: testHTLCEntry.Incoming.Val,
|
||||
Amt: lnwire.NewMSatFromSatoshis(
|
||||
testHTLCEntry.Amt.Val.Int(),
|
||||
),
|
||||
CustomRecords: customRecords,
|
||||
}},
|
||||
CustomBlob: fn.Some(blobBytes),
|
||||
}
|
||||
|
||||
testRevocationLogNoAmts = RevocationLog{
|
||||
OurOutputIndex: 0,
|
||||
TheirOutputIndex: 1,
|
||||
CommitTxHash: testChannelCommit.CommitTx.TxHash(),
|
||||
HTLCEntries: []*HTLCEntry{&testHTLCEntry},
|
||||
}
|
||||
testRevocationLogNoAmts = NewRevocationLog(
|
||||
0, 1, testChannelCommit.CommitTx.TxHash(),
|
||||
fn.None[lnwire.MilliSatoshi](), fn.None[lnwire.MilliSatoshi](),
|
||||
[]*HTLCEntry{&testHTLCEntry}, fn.Some(blobBytes),
|
||||
)
|
||||
testRevocationLogNoAmtsBytes = []byte{
|
||||
// Body length 42.
|
||||
0x2a,
|
||||
// Body length 61.
|
||||
0x3d,
|
||||
// OurOutputIndex tlv.
|
||||
0x0, 0x2, 0x0, 0x0,
|
||||
// TheirOutputIndex tlv.
|
||||
|
@ -96,19 +162,19 @@ var (
|
|||
0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6,
|
||||
0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff,
|
||||
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{
|
||||
OurOutputIndex: 0,
|
||||
TheirOutputIndex: 1,
|
||||
CommitTxHash: testChannelCommit.CommitTx.TxHash(),
|
||||
HTLCEntries: []*HTLCEntry{&testHTLCEntry},
|
||||
OurBalance: &localBalance,
|
||||
TheirBalance: &remoteBalance,
|
||||
}
|
||||
testRevocationLogWithAmts = NewRevocationLog(
|
||||
0, 1, testChannelCommit.CommitTx.TxHash(),
|
||||
fn.Some(localBalance), fn.Some(remoteBalance),
|
||||
[]*HTLCEntry{&testHTLCEntry}, fn.Some(blobBytes),
|
||||
)
|
||||
testRevocationLogWithAmtsBytes = []byte{
|
||||
// Body length 52.
|
||||
0x34,
|
||||
// Body length 71.
|
||||
0x47,
|
||||
// OurOutputIndex tlv.
|
||||
0x0, 0x2, 0x0, 0x0,
|
||||
// TheirOutputIndex tlv.
|
||||
|
@ -123,6 +189,9 @@ var (
|
|||
0x3, 0x3, 0xfd, 0x23, 0x28,
|
||||
// Remote Balance.
|
||||
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.
|
||||
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.
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := serializeHTLCEntries(buf, []*HTLCEntry{&entry})
|
||||
|
@ -207,6 +271,21 @@ func TestSerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
|||
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) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -215,7 +294,7 @@ func TestSerializeHTLCEntries(t *testing.T) {
|
|||
|
||||
// Create a fake rHash.
|
||||
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
||||
copy(entry.RHash[:], rHashBytes)
|
||||
copy(entry.RHash.Val[:], rHashBytes)
|
||||
|
||||
// Construct the serialized bytes.
|
||||
//
|
||||
|
@ -224,7 +303,7 @@ func TestSerializeHTLCEntries(t *testing.T) {
|
|||
partialBytes := testHTLCEntryBytes[3:]
|
||||
|
||||
// Write the total length and RHash tlv.
|
||||
expectedBytes := []byte{0x36, 0x0, 0x20}
|
||||
expectedBytes := []byte{0x4d, 0x0, 0x20}
|
||||
expectedBytes = append(expectedBytes, rHashBytes...)
|
||||
|
||||
// Append the rest.
|
||||
|
@ -269,7 +348,7 @@ func TestSerializeAndDeserializeRevLog(t *testing.T) {
|
|||
t, &test.revLog, test.revLogBytes,
|
||||
)
|
||||
|
||||
testDerializeRevocationLog(
|
||||
testDeserializeRevocationLog(
|
||||
t, &test.revLog, test.revLogBytes,
|
||||
)
|
||||
})
|
||||
|
@ -293,7 +372,7 @@ func testSerializeRevocationLog(t *testing.T, rl *RevocationLog,
|
|||
require.Equal(t, revLogBytes, buf.Bytes()[:bodyIndex])
|
||||
}
|
||||
|
||||
func testDerializeRevocationLog(t *testing.T, revLog *RevocationLog,
|
||||
func testDeserializeRevocationLog(t *testing.T, revLog *RevocationLog,
|
||||
revLogBytes []byte) {
|
||||
|
||||
// Construct the full bytes.
|
||||
|
@ -309,7 +388,7 @@ func testDerializeRevocationLog(t *testing.T, revLog *RevocationLog,
|
|||
require.Equal(t, *revLog, rl)
|
||||
}
|
||||
|
||||
func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
||||
func TestDeserializeHTLCEntriesEmptyRHash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Read the tlv stream.
|
||||
|
@ -322,7 +401,7 @@ func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
|||
require.Equal(t, &testHTLCEntry, htlcs[0])
|
||||
}
|
||||
|
||||
func TestDerializeHTLCEntries(t *testing.T) {
|
||||
func TestDeserializeHTLCEntries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Copy the testHTLCEntry.
|
||||
|
@ -330,7 +409,7 @@ func TestDerializeHTLCEntries(t *testing.T) {
|
|||
|
||||
// Create a fake rHash.
|
||||
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
||||
copy(entry.RHash[:], rHashBytes)
|
||||
copy(entry.RHash.Val[:], rHashBytes)
|
||||
|
||||
// Construct the serialized bytes.
|
||||
//
|
||||
|
@ -339,7 +418,7 @@ func TestDerializeHTLCEntries(t *testing.T) {
|
|||
partialBytes := testHTLCEntryBytes[3:]
|
||||
|
||||
// Write the total length and RHash tlv.
|
||||
testBytes := append([]byte{0x36, 0x0, 0x20}, rHashBytes...)
|
||||
testBytes := append([]byte{0x4d, 0x0, 0x20}, rHashBytes...)
|
||||
|
||||
// Append the rest.
|
||||
testBytes = append(testBytes, partialBytes...)
|
||||
|
@ -398,11 +477,11 @@ func TestDeleteLogBucket(t *testing.T) {
|
|||
|
||||
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||
// Create the buckets.
|
||||
chanBucket, _, err := createTestRevocatoinLogBuckets(tx)
|
||||
chanBucket, _, err := createTestRevocationLogBuckets(tx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create the buckets again should give us an error.
|
||||
_, _, err = createTestRevocatoinLogBuckets(tx)
|
||||
_, _, err = createTestRevocationLogBuckets(tx)
|
||||
require.ErrorIs(t, err, kvdb.ErrBucketExists)
|
||||
|
||||
// Delete both buckets.
|
||||
|
@ -410,7 +489,7 @@ func TestDeleteLogBucket(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Create the buckets again should give us NO error.
|
||||
_, _, err = createTestRevocatoinLogBuckets(tx)
|
||||
_, _, err = createTestRevocationLogBuckets(tx)
|
||||
return err
|
||||
}, func() {})
|
||||
require.NoError(t, err)
|
||||
|
@ -516,7 +595,7 @@ func TestPutRevocationLog(t *testing.T) {
|
|||
// Construct the testing db transaction.
|
||||
dbTx := func(tx kvdb.RwTx) (RevocationLog, error) {
|
||||
// Create the buckets.
|
||||
_, bucket, err := createTestRevocatoinLogBuckets(tx)
|
||||
_, bucket, err := createTestRevocationLogBuckets(tx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 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) {
|
||||
|
||||
chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
|
||||
|
|
|
@ -105,7 +105,7 @@ type DatabaseBuilder interface {
|
|||
type WalletConfigBuilder interface {
|
||||
// BuildWalletConfig is responsible for creating or unlocking and then
|
||||
// fully initializing a wallet.
|
||||
BuildWalletConfig(context.Context, *DatabaseInstances,
|
||||
BuildWalletConfig(context.Context, *DatabaseInstances, *AuxComponents,
|
||||
*rpcperms.InterceptorChain,
|
||||
[]*ListenerWithSignal) (*chainreg.PartialChainControl,
|
||||
*btcwallet.Config, func(), error)
|
||||
|
@ -120,14 +120,6 @@ type ChainControlBuilder interface {
|
|||
*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
|
||||
// components that can be implemented outside lnd itself.
|
||||
type ImplementationCfg struct {
|
||||
|
@ -160,6 +152,18 @@ type ImplementationCfg struct {
|
|||
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
|
||||
// backed configuration.
|
||||
type DefaultWalletImpl struct {
|
||||
|
@ -242,7 +246,8 @@ func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
|
|||
//
|
||||
// NOTE: This is part of the WalletConfigBuilder interface.
|
||||
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
|
||||
dbs *DatabaseInstances, interceptorChain *rpcperms.InterceptorChain,
|
||||
dbs *DatabaseInstances, aux *AuxComponents,
|
||||
interceptorChain *rpcperms.InterceptorChain,
|
||||
grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
|
||||
*btcwallet.Config, func(), error) {
|
||||
|
||||
|
@ -562,6 +567,7 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
|
|||
HeightHintDB: dbs.HeightHintDB,
|
||||
ChanStateDB: dbs.ChanStateDB.ChannelStateDB(),
|
||||
NeutrinoCS: neutrinoCS,
|
||||
AuxLeafStore: aux.AuxLeafStore,
|
||||
ActiveNetParams: d.cfg.ActiveNetParams,
|
||||
FeeURL: d.cfg.FeeURL,
|
||||
Fee: &lncfg.Fee{
|
||||
|
@ -625,8 +631,9 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
|
|||
|
||||
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
|
||||
// rebroadcaster client.
|
||||
func proxyBlockEpoch(notifier chainntnfs.ChainNotifier,
|
||||
) func() (*blockntfns.Subscription, error) {
|
||||
func proxyBlockEpoch(
|
||||
notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription,
|
||||
error) {
|
||||
|
||||
return func() (*blockntfns.Subscription, error) {
|
||||
blockEpoch, err := notifier.RegisterBlockEpochNtfn(
|
||||
|
@ -717,6 +724,7 @@ func (d *DefaultWalletImpl) BuildChainControl(
|
|||
ChainIO: walletController,
|
||||
NetParams: *walletConfig.NetParams,
|
||||
CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
|
||||
AuxLeafStore: partialChainControl.Cfg.AuxLeafStore,
|
||||
}
|
||||
|
||||
// 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
|
||||
// be nil if the use-native-sql flag was not set.
|
||||
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
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lntest/channels"
|
||||
|
@ -1590,6 +1591,7 @@ func testBreachSpends(t *testing.T, test breachTest) {
|
|||
// Notify the breach arbiter about the breach.
|
||||
retribution, err := lnwallet.NewBreachRetribution(
|
||||
alice.State(), height, 1, forceCloseTx,
|
||||
fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}),
|
||||
)
|
||||
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.
|
||||
retribution, err := lnwallet.NewBreachRetribution(
|
||||
alice.State(), height, uint32(blockHeight), forceCloseTx,
|
||||
fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}),
|
||||
)
|
||||
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
|
||||
// lower package.
|
||||
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
|
||||
|
@ -299,8 +303,13 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions,
|
|||
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(
|
||||
a.c.cfg.Signer, channel, nil,
|
||||
a.c.cfg.Signer, channel, nil, chanOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -344,10 +353,15 @@ func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error)
|
|||
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
|
||||
// the force close workflow.
|
||||
chanMachine, err := lnwallet.NewLightningChannel(
|
||||
a.c.cfg.Signer, channel, nil,
|
||||
a.c.cfg.Signer, channel, nil, chanOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -193,6 +193,9 @@ type chainWatcherConfig struct {
|
|||
// obfuscater. This is used by the chain watcher to identify which
|
||||
// state was broadcast and confirmed on-chain.
|
||||
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
|
||||
|
@ -308,7 +311,7 @@ func (c *chainWatcher) Start() error {
|
|||
)
|
||||
if chanState.ChanType.IsTaproot() {
|
||||
c.fundingPkScript, _, err = input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, 0,
|
||||
localKey, remoteKey, 0, chanState.TapscriptRoot,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -423,15 +426,36 @@ func (c *chainWatcher) handleUnknownLocalState(
|
|||
&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
|
||||
// present if they have a non-dust balance on the commitment.
|
||||
var leaseExpiry uint32
|
||||
if c.cfg.chanState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = c.cfg.chanState.ThawHeight
|
||||
}
|
||||
|
||||
remoteAuxLeaf := fn.ChainOption(
|
||||
func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.RemoteAuxLeaf
|
||||
},
|
||||
)(auxResult.AuxLeaves)
|
||||
remoteScript, _, err := lnwallet.CommitScriptToRemote(
|
||||
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
|
||||
commitKeyRing.ToRemoteKey, leaseExpiry,
|
||||
remoteAuxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -440,10 +464,16 @@ func (c *chainWatcher) handleUnknownLocalState(
|
|||
// Next, we'll derive our script that includes the revocation base for
|
||||
// the remote party allowing them to claim this output before the CSV
|
||||
// delay if we breach.
|
||||
localAuxLeaf := fn.ChainOption(
|
||||
func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.LocalAuxLeaf
|
||||
},
|
||||
)(auxResult.AuxLeaves)
|
||||
localScript, err := lnwallet.CommitScriptToSelf(
|
||||
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
|
||||
commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
|
||||
uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
|
||||
localAuxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -866,7 +896,7 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
|
|||
spendHeight := uint32(commitSpend.SpendingHeight)
|
||||
retribution, err := lnwallet.NewBreachRetribution(
|
||||
c.cfg.chanState, broadcastStateNum, spendHeight,
|
||||
commitSpend.SpendingTx,
|
||||
commitSpend.SpendingTx, c.cfg.auxLeafStore,
|
||||
)
|
||||
|
||||
switch {
|
||||
|
@ -1116,8 +1146,8 @@ func (c *chainWatcher) dispatchLocalForceClose(
|
|||
"detected", c.cfg.chanState.FundingOutpoint)
|
||||
|
||||
forceClose, err := lnwallet.NewLocalForceCloseSummary(
|
||||
c.cfg.chanState, c.cfg.signer,
|
||||
commitSpend.SpendingTx, stateNum,
|
||||
c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
|
||||
c.cfg.auxLeafStore,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1210,7 +1240,7 @@ func (c *chainWatcher) dispatchRemoteForceClose(
|
|||
// channel on-chain.
|
||||
uniClose, err := lnwallet.NewUnilateralCloseSummary(
|
||||
c.cfg.chanState, c.cfg.signer, commitSpend,
|
||||
remoteCommit, commitPoint,
|
||||
remoteCommit, commitPoint, c.cfg.auxLeafStore,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/graph"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"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
|
||||
// subsystem.
|
||||
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
|
||||
|
@ -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.
|
||||
lnChannel, err := lnwallet.NewLightningChannel(
|
||||
nil, channel, nil,
|
||||
nil, channel, nil, chanOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to create LightningChannel(%v): %v",
|
||||
|
@ -2899,6 +2909,7 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) {
|
|||
if channel.ChanType.IsTaproot() {
|
||||
pkScript, _, err := input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, int64(channel.Capacity),
|
||||
channel.TapscriptRoot,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
|
@ -563,6 +564,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||
IsSweeperOutpoint: func(wire.OutPoint) bool {
|
||||
return false
|
||||
},
|
||||
AuxLeafStore: fn.Some[lnwallet.AuxLeafStore](
|
||||
&lnwallet.MockAuxLeafStore{},
|
||||
),
|
||||
}
|
||||
|
||||
for _, op := range options {
|
||||
|
@ -672,6 +676,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
|||
OpenChannelPredicate: chainedAcceptor,
|
||||
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
|
||||
AliasManager: oldCfg.AliasManager,
|
||||
AuxLeafStore: oldCfg.AuxLeafStore,
|
||||
})
|
||||
require.NoError(t, err, "failed recreating aliceFundingManager")
|
||||
|
||||
|
|
|
@ -11,12 +11,14 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/batch"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
|
@ -1138,12 +1140,14 @@ func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte,
|
|||
}
|
||||
|
||||
fundingScript, _, err := input.GenTaprootFundingScript(
|
||||
pubKey1, pubKey2, 0,
|
||||
pubKey1, pubKey2, 0, fn.None[chainhash.Hash](),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(roasbeef): add tapscript root to gossip v1.5
|
||||
|
||||
return fundingScript, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,10 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
"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
|
||||
// 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,
|
||||
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
|
||||
// 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
|
||||
// 86 tweak here during aggregation.
|
||||
//
|
||||
// TODO(roasbeef): revisit if BIP 86 is needed here?
|
||||
// 86 tweak here during aggregation, unless the user has explicitly
|
||||
// specified a tapscript root.
|
||||
combinedKey, _, _, err := musig2.AggregateKeys(
|
||||
[]*btcec.PublicKey{aPub, bPub}, true,
|
||||
musig2.WithBIP86KeyTweak(),
|
||||
[]*btcec.PublicKey{aPub, bPub}, true, muSig2Opt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to combine keys: %w", err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to make taproot "+
|
||||
|
@ -228,7 +234,7 @@ func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
|
|||
txOut := wire.NewTxOut(amt, pkScript)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -640,6 +646,13 @@ type HtlcScriptTree struct {
|
|||
// TimeoutTapLeaf is the tapleaf for the timeout path.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -720,8 +733,8 @@ var _ TapscriptDescriptor = (*HtlcScriptTree)(nil)
|
|||
// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor
|
||||
// the HTLC key for HTLCs on the sender's commitment.
|
||||
func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
||||
revokeKey *btcec.PublicKey, payHash []byte,
|
||||
hType htlcType) (*HtlcScriptTree, error) {
|
||||
revokeKey *btcec.PublicKey, payHash []byte, hType htlcType,
|
||||
auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
|
||||
|
||||
// First, we'll obtain the tap leaves for both the success and timeout
|
||||
// path.
|
||||
|
@ -738,11 +751,14 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||
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,
|
||||
// then obtain the root from that
|
||||
tapscriptTree := txscript.AssembleTaprootScriptTree(
|
||||
successTapLeaf, timeoutTapLeaf,
|
||||
)
|
||||
tapscriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
|
||||
|
||||
tapScriptRoot := tapscriptTree.RootNode.TapHash()
|
||||
|
||||
|
@ -761,6 +777,7 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||
},
|
||||
SuccessTapLeaf: successTapLeaf,
|
||||
TimeoutTapLeaf: timeoutTapLeaf,
|
||||
AuxLeaf: auxLeaf,
|
||||
htlcType: hType,
|
||||
}, nil
|
||||
}
|
||||
|
@ -795,7 +812,8 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||
// unilaterally spend the created output.
|
||||
func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey,
|
||||
revokeKey *btcec.PublicKey, payHash []byte,
|
||||
whoseCommit lntypes.ChannelParty) (*HtlcScriptTree, error) {
|
||||
whoseCommit lntypes.ChannelParty, auxLeaf AuxTapLeaf) (*HtlcScriptTree,
|
||||
error) {
|
||||
|
||||
var hType htlcType
|
||||
if whoseCommit.IsLocal() {
|
||||
|
@ -808,8 +826,8 @@ func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey,
|
|||
// tree that includes the top level output script, as well as the two
|
||||
// tap leaf paths.
|
||||
return senderHtlcTapScriptTree(
|
||||
senderHtlcKey, receiverHtlcKey, revokeKey, payHash,
|
||||
hType,
|
||||
senderHtlcKey, receiverHtlcKey, revokeKey, payHash, hType,
|
||||
auxLeaf,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1279,8 +1297,8 @@ func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
|
|||
// receiverHtlcTapScriptTree builds the tapscript tree which is used to anchor
|
||||
// the HTLC key for HTLCs on the receiver's commitment.
|
||||
func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
||||
revokeKey *btcec.PublicKey, payHash []byte,
|
||||
cltvExpiry uint32, hType htlcType) (*HtlcScriptTree, error) {
|
||||
revokeKey *btcec.PublicKey, payHash []byte, cltvExpiry uint32,
|
||||
hType htlcType, auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
|
||||
|
||||
// First, we'll obtain the tap leaves for both the success and timeout
|
||||
// path.
|
||||
|
@ -1297,11 +1315,14 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||
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,
|
||||
// then obtain the root from that
|
||||
tapscriptTree := txscript.AssembleTaprootScriptTree(
|
||||
timeoutTapLeaf, successTapLeaf,
|
||||
)
|
||||
tapscriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
|
||||
|
||||
tapScriptRoot := tapscriptTree.RootNode.TapHash()
|
||||
|
||||
|
@ -1320,6 +1341,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||
},
|
||||
SuccessTapLeaf: successTapLeaf,
|
||||
TimeoutTapLeaf: timeoutTapLeaf,
|
||||
AuxLeaf: auxLeaf,
|
||||
htlcType: hType,
|
||||
}, nil
|
||||
}
|
||||
|
@ -1355,7 +1377,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||
func ReceiverHTLCScriptTaproot(cltvExpiry uint32,
|
||||
senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey,
|
||||
payHash []byte, whoseCommit lntypes.ChannelParty,
|
||||
) (*HtlcScriptTree, error) {
|
||||
auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
|
||||
|
||||
var hType htlcType
|
||||
if whoseCommit.IsLocal() {
|
||||
|
@ -1369,7 +1391,7 @@ func ReceiverHTLCScriptTaproot(cltvExpiry uint32,
|
|||
// tap leaf paths.
|
||||
return receiverHtlcTapScriptTree(
|
||||
senderHtlcKey, receiverHtlcKey, revocationKey, payHash,
|
||||
cltvExpiry, hType,
|
||||
cltvExpiry, hType, auxLeaf,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1599,8 +1621,8 @@ func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey,
|
|||
|
||||
// SecondLevelHtlcTapscriptTree construct the indexed tapscript tree needed to
|
||||
// generate the tap tweak to create the final output and also control block.
|
||||
func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey,
|
||||
csvDelay uint32) (*txscript.IndexedTapScriptTree, error) {
|
||||
func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, csvDelay uint32,
|
||||
auxLeaf AuxTapLeaf) (*txscript.IndexedTapScriptTree, error) {
|
||||
|
||||
// First grab the second level leaf script we need to create the top
|
||||
// level output.
|
||||
|
@ -1609,9 +1631,14 @@ func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey,
|
|||
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
|
||||
// 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
|
||||
|
@ -1631,12 +1658,12 @@ func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey,
|
|||
//
|
||||
// The keyspend path require knowledge of the top level revocation private key.
|
||||
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
|
||||
// path.
|
||||
tapScriptTree, err := SecondLevelHtlcTapscriptTree(
|
||||
delayKey, csvDelay,
|
||||
delayKey, csvDelay, auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1661,17 +1688,21 @@ type SecondLevelScriptTree struct {
|
|||
|
||||
// SuccessTapLeaf is the tapleaf for the redemption path.
|
||||
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
|
||||
// second level HTLC output.
|
||||
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
|
||||
// path.
|
||||
tapScriptTree, err := SecondLevelHtlcTapscriptTree(
|
||||
delayKey, csvDelay,
|
||||
delayKey, csvDelay, auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1692,6 +1723,7 @@ func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey,
|
|||
InternalKey: revokeKey,
|
||||
},
|
||||
SuccessTapLeaf: tapScriptTree.LeafMerkleProofs[0].TapLeaf,
|
||||
AuxLeaf: auxLeaf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -2073,6 +2105,12 @@ type CommitScriptTree struct {
|
|||
// RevocationLeaf is the leaf used to spend the output with the
|
||||
// revocation key signature.
|
||||
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
|
||||
|
@ -2137,8 +2175,9 @@ func (c *CommitScriptTree) Tree() ScriptTree {
|
|||
|
||||
// NewLocalCommitScriptTree returns a new CommitScript tree that can be used to
|
||||
// create and spend the commitment output for the local party.
|
||||
func NewLocalCommitScriptTree(csvTimeout uint32,
|
||||
selfKey, revokeKey *btcec.PublicKey) (*CommitScriptTree, error) {
|
||||
func NewLocalCommitScriptTree(csvTimeout uint32, selfKey,
|
||||
revokeKey *btcec.PublicKey, auxLeaf AuxTapLeaf) (*CommitScriptTree,
|
||||
error) {
|
||||
|
||||
// First, we'll need to construct the tapLeaf that'll be our delay CSV
|
||||
// clause.
|
||||
|
@ -2158,9 +2197,13 @@ func NewLocalCommitScriptTree(csvTimeout uint32,
|
|||
// the two leaves, and then obtain a root from that.
|
||||
delayTapLeaf := txscript.NewBaseTapLeaf(delayScript)
|
||||
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()
|
||||
|
||||
// Now that we have our root, we can arrive at the final output script
|
||||
|
@ -2178,6 +2221,7 @@ func NewLocalCommitScriptTree(csvTimeout uint32,
|
|||
},
|
||||
SettleLeaf: delayTapLeaf,
|
||||
RevocationLeaf: revokeTapLeaf,
|
||||
AuxLeaf: auxLeaf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -2247,7 +2291,7 @@ func TaprootCommitScriptToSelf(csvTimeout uint32,
|
|||
selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) {
|
||||
|
||||
commitScriptTree, err := NewLocalCommitScriptTree(
|
||||
csvTimeout, selfKey, revokeKey,
|
||||
csvTimeout, selfKey, revokeKey, NoneTapLeaf(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -2573,7 +2617,7 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
|
|||
// NewRemoteCommitScriptTree constructs a new script tree for the remote party
|
||||
// to sweep their funds after a hard coded 1 block delay.
|
||||
func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
|
||||
) (*CommitScriptTree, error) {
|
||||
auxLeaf AuxTapLeaf) (*CommitScriptTree, error) {
|
||||
|
||||
// First, construct the remote party's tapscript they'll use to sweep
|
||||
// their outputs.
|
||||
|
@ -2589,10 +2633,16 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
|
|||
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
|
||||
// make a new tapscript root from that.
|
||||
tapLeaf := txscript.NewBaseTapLeaf(remoteScript)
|
||||
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf)
|
||||
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
|
||||
tapScriptRoot := tapScriptTree.RootNode.TapHash()
|
||||
|
||||
// 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,
|
||||
},
|
||||
SettleLeaf: tapLeaf,
|
||||
AuxLeaf: auxLeaf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -2625,9 +2676,9 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
|
|||
// <remotepubkey> OP_CHECKSIG
|
||||
// 1 OP_CHECKSEQUENCEVERIFY OP_DROP
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -853,7 +853,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
signer := &dummySigner{}
|
||||
commitScriptTree, err := input.NewLocalCommitScriptTree(
|
||||
testCSVDelay, testKey.PubKey(),
|
||||
testKey.PubKey(),
|
||||
testKey.PubKey(), input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -887,7 +887,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
signer := &dummySigner{}
|
||||
commitScriptTree, err := input.NewLocalCommitScriptTree(
|
||||
testCSVDelay, testKey.PubKey(),
|
||||
testKey.PubKey(),
|
||||
testKey.PubKey(), input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -921,7 +921,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
signer := &dummySigner{}
|
||||
//nolint:lll
|
||||
commitScriptTree, err := input.NewRemoteCommitScriptTree(
|
||||
testKey.PubKey(),
|
||||
testKey.PubKey(), input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -988,6 +988,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
|
||||
scriptTree, err := input.SecondLevelHtlcTapscriptTree(
|
||||
testKey.PubKey(), testCSVDelay,
|
||||
input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1027,6 +1028,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
|
||||
scriptTree, err := input.SecondLevelHtlcTapscriptTree(
|
||||
testKey.PubKey(), testCSVDelay,
|
||||
input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1075,6 +1077,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
||||
senderKey.PubKey(), receiverKey.PubKey(),
|
||||
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
||||
input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1116,7 +1119,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
||||
testCLTVExpiry, senderKey.PubKey(),
|
||||
receiverKey.PubKey(), revokeKey.PubKey(),
|
||||
payHash[:], lntypes.Remote,
|
||||
payHash[:], lntypes.Remote, input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1158,7 +1161,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
||||
testCLTVExpiry, senderKey.PubKey(),
|
||||
receiverKey.PubKey(), revokeKey.PubKey(),
|
||||
payHash[:], lntypes.Remote,
|
||||
payHash[:], lntypes.Remote, input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1205,6 +1208,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
||||
senderKey.PubKey(), receiverKey.PubKey(),
|
||||
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
||||
input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1265,6 +1269,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
|
||||
senderKey.PubKey(), receiverKey.PubKey(),
|
||||
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
||||
input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1310,7 +1315,7 @@ var witnessSizeTests = []witnessSizeTest{
|
|||
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
|
||||
testCLTVExpiry, senderKey.PubKey(),
|
||||
receiverKey.PubKey(), revokeKey.PubKey(),
|
||||
payHash[:], lntypes.Remote,
|
||||
payHash[:], lntypes.Remote, input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1383,7 +1388,7 @@ func genTimeoutTx(t *testing.T,
|
|||
// Create the unsigned timeout tx.
|
||||
timeoutTx, err := lnwallet.CreateHtlcTimeoutTx(
|
||||
chanType, false, testOutPoint, testAmt, testCLTVExpiry,
|
||||
testCSVDelay, 0, testPubkey, testPubkey,
|
||||
testCSVDelay, 0, testPubkey, testPubkey, input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1396,7 +1401,7 @@ func genTimeoutTx(t *testing.T,
|
|||
if chanType.IsTaproot() {
|
||||
tapscriptTree, err = input.SenderHTLCScriptTaproot(
|
||||
testPubkey, testPubkey, testPubkey, testHash160,
|
||||
lntypes.Remote,
|
||||
lntypes.Remote, input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1452,7 +1457,7 @@ func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx {
|
|||
// Create the unsigned success tx.
|
||||
successTx, err := lnwallet.CreateHtlcSuccessTx(
|
||||
chanType, false, testOutPoint, testAmt, testCSVDelay, 0,
|
||||
testPubkey, testPubkey,
|
||||
testPubkey, testPubkey, input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -1465,7 +1470,7 @@ func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx {
|
|||
if chanType.IsTaproot() {
|
||||
tapscriptTree, err = input.ReceiverHTLCScriptTaproot(
|
||||
testCLTVExpiry, testPubkey, testPubkey, testPubkey,
|
||||
testHash160, lntypes.Remote,
|
||||
testHash160, lntypes.Remote, input.NoneTapLeaf(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -21,6 +22,33 @@ const (
|
|||
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
|
||||
// only calculate the sighash midstate values for segwit v0 inputs and can
|
||||
// therefore never be used for transactions that want to spend segwit v1
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -31,7 +34,9 @@ type testSenderHtlcScriptTree struct {
|
|||
htlcAmt int64
|
||||
}
|
||||
|
||||
func newTestSenderHtlcScriptTree(t *testing.T) *testSenderHtlcScriptTree {
|
||||
func newTestSenderHtlcScriptTree(t *testing.T,
|
||||
auxLeaf AuxTapLeaf) *testSenderHtlcScriptTree {
|
||||
|
||||
var preImage lntypes.Preimage
|
||||
_, err := rand.Read(preImage[:])
|
||||
require.NoError(t, err)
|
||||
|
@ -48,7 +53,7 @@ func newTestSenderHtlcScriptTree(t *testing.T) *testSenderHtlcScriptTree {
|
|||
payHash := preImage.Hash()
|
||||
htlcScriptTree, err := SenderHTLCScriptTaproot(
|
||||
senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(),
|
||||
payHash[:], lntypes.Remote,
|
||||
payHash[:], lntypes.Remote, auxLeaf,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -207,13 +212,9 @@ func htlcSenderTimeoutWitnessGen(sigHash txscript.SigHashType,
|
|||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||
// First, create a new test script tree.
|
||||
htlcScriptTree := newTestSenderHtlcScriptTree(t)
|
||||
htlcScriptTree := newTestSenderHtlcScriptTree(t, auxLeaf)
|
||||
|
||||
spendTx := wire.NewMsgTx(2)
|
||||
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 {
|
||||
preImage lntypes.Preimage
|
||||
|
||||
|
@ -452,7 +473,9 @@ type testReceiverHtlcScriptTree struct {
|
|||
lockTime int32
|
||||
}
|
||||
|
||||
func newTestReceiverHtlcScriptTree(t *testing.T) *testReceiverHtlcScriptTree {
|
||||
func newTestReceiverHtlcScriptTree(t *testing.T,
|
||||
auxLeaf AuxTapLeaf) *testReceiverHtlcScriptTree {
|
||||
|
||||
var preImage lntypes.Preimage
|
||||
_, err := rand.Read(preImage[:])
|
||||
require.NoError(t, err)
|
||||
|
@ -471,7 +494,7 @@ func newTestReceiverHtlcScriptTree(t *testing.T) *testReceiverHtlcScriptTree {
|
|||
payHash := preImage.Hash()
|
||||
htlcScriptTree, err := ReceiverHTLCScriptTaproot(
|
||||
cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(),
|
||||
revokeKey.PubKey(), payHash[:], lntypes.Remote,
|
||||
revokeKey.PubKey(), payHash[:], lntypes.Remote, auxLeaf,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -629,15 +652,11 @@ func htlcReceiverSuccessWitnessGen(sigHash txscript.SigHashType,
|
|||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||
// 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
|
||||
// signing below.
|
||||
htlcScriptTree := newTestReceiverHtlcScriptTree(t)
|
||||
htlcScriptTree := newTestReceiverHtlcScriptTree(t, auxLeaf)
|
||||
|
||||
// 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 {
|
||||
csvDelay uint32
|
||||
|
||||
|
@ -905,7 +946,9 @@ type testCommitScriptTree struct {
|
|||
*CommitScriptTree
|
||||
}
|
||||
|
||||
func newTestCommitScriptTree(local bool) (*testCommitScriptTree, error) {
|
||||
func newTestCommitScriptTree(local bool,
|
||||
auxLeaf AuxTapLeaf) (*testCommitScriptTree, error) {
|
||||
|
||||
selfKey, err := btcec.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -925,10 +968,11 @@ func newTestCommitScriptTree(local bool) (*testCommitScriptTree, error) {
|
|||
if local {
|
||||
commitScriptTree, err = NewLocalCommitScriptTree(
|
||||
csvDelay, selfKey.PubKey(), revokeKey.PubKey(),
|
||||
auxLeaf,
|
||||
)
|
||||
} else {
|
||||
commitScriptTree, err = NewRemoteCommitScriptTree(
|
||||
selfKey.PubKey(),
|
||||
selfKey.PubKey(), auxLeaf,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -1020,12 +1064,8 @@ func localCommitRevokeWitGen(sigHash txscript.SigHashType,
|
|||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
commitScriptTree, err := newTestCommitScriptTree(true)
|
||||
func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||
commitScriptTree, err := newTestCommitScriptTree(true, auxLeaf)
|
||||
require.NoError(t, err)
|
||||
|
||||
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,
|
||||
commitScriptTree *testCommitScriptTree) witnessGen {
|
||||
|
||||
|
@ -1220,12 +1280,8 @@ func remoteCommitSweepWitGen(sigHash txscript.SigHashType,
|
|||
}
|
||||
}
|
||||
|
||||
// TestTaprootCommitScriptRemote tests that the remote party can properly sweep
|
||||
// their output after force close.
|
||||
func TestTaprootCommitScriptRemote(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
commitScriptTree, err := newTestCommitScriptTree(false)
|
||||
func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||
commitScriptTree, err := newTestCommitScriptTree(false, auxLeaf)
|
||||
require.NoError(t, err)
|
||||
|
||||
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 {
|
||||
sweepKey *btcec.PrivateKey
|
||||
|
||||
|
@ -1599,25 +1675,21 @@ type testSecondLevelHtlcTree struct {
|
|||
tapScriptRoot []byte
|
||||
}
|
||||
|
||||
func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) {
|
||||
func newTestSecondLevelHtlcTree(t *testing.T,
|
||||
auxLeaf AuxTapLeaf) *testSecondLevelHtlcTree {
|
||||
|
||||
delayKey, err := btcec.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
revokeKey, err := btcec.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
const csvDelay = 6
|
||||
|
||||
scriptTree, err := SecondLevelHtlcTapscriptTree(
|
||||
delayKey.PubKey(), csvDelay,
|
||||
delayKey.PubKey(), csvDelay, auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
tapScriptRoot := scriptTree.RootNode.TapHash()
|
||||
|
||||
|
@ -1626,9 +1698,7 @@ func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) {
|
|||
)
|
||||
|
||||
pkScript, err := PayToTaprootScript(htlcKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
const amt = 100
|
||||
|
||||
|
@ -1643,7 +1713,7 @@ func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) {
|
|||
amt: amt,
|
||||
scriptTree: scriptTree,
|
||||
tapScriptRoot: tapScriptRoot[:],
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func secondLevelHtlcSuccessWitGen(sigHash txscript.SigHashType,
|
||||
|
@ -1713,13 +1783,8 @@ func secondLevelHtlcRevokeWitnessgen(sigHash txscript.SigHashType,
|
|||
}
|
||||
}
|
||||
|
||||
// TestTaprootSecondLevelHtlcScript tests that a channel peer can properly
|
||||
// spend the second level HTLC script to resolve HTLCs.
|
||||
func TestTaprootSecondLevelHtlcScript(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
htlcScriptTree, err := newTestSecondLevelHtlcTree()
|
||||
require.NoError(t, err)
|
||||
func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf) {
|
||||
htlcScriptTree := newTestSecondLevelHtlcTree(t, auxLeaf)
|
||||
|
||||
spendTx := wire.NewMsgTx(2)
|
||||
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/wire"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/funding"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/labels"
|
||||
|
@ -1192,6 +1193,7 @@ func deriveFundingShim(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
|
|||
|
||||
_, fundingOutput, err = input.GenTaprootFundingScript(
|
||||
carolKey, daveKey, int64(chanSize),
|
||||
fn.None[chainhash.Hash](),
|
||||
)
|
||||
require.NoError(ht, err)
|
||||
|
||||
|
|
3
lnd.go
3
lnd.go
|
@ -456,7 +456,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
|
|||
defer cleanUp()
|
||||
|
||||
partialChainControl, walletConfig, cleanUp, err := implCfg.BuildWalletConfig(
|
||||
ctx, dbs, interceptorChain, grpcListeners,
|
||||
ctx, dbs, &implCfg.AuxComponents, interceptorChain,
|
||||
grpcListeners,
|
||||
)
|
||||
if err != nil {
|
||||
return mkErr("error creating wallet config: %v", err)
|
||||
|
|
239
lnwallet/aux_leaf_store.go
Normal file
239
lnwallet/aux_leaf_store.go
Normal file
|
@ -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/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
|
@ -178,8 +179,9 @@ func (m *mockChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress {
|
|||
}
|
||||
|
||||
func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
|
||||
localScript, remoteScript []byte, _ ...lnwallet.ChanCloseOpt,
|
||||
) (input.Signature, *chainhash.Hash, btcutil.Amount, error) {
|
||||
localScript, remoteScript []byte,
|
||||
_ ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash,
|
||||
btcutil.Amount, error) {
|
||||
|
||||
if m.chanType.IsTaproot() {
|
||||
return lnwallet.NewMusigPartialSig(
|
||||
|
@ -188,6 +190,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
|
|||
R: new(btcec.PublicKey),
|
||||
},
|
||||
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
|
||||
fn.None[chainhash.Hash](),
|
||||
), nil, 0, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,9 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
@ -56,6 +58,14 @@ type ShimIntent struct {
|
|||
// generate an aggregate key to use as the taproot-native multi-sig
|
||||
// output.
|
||||
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
|
||||
|
@ -76,9 +86,8 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) {
|
|||
// Similar to the existing p2wsh script, we'll always ensure
|
||||
// the keys are sorted before use.
|
||||
return input.GenTaprootFundingScript(
|
||||
s.localKey.PubKey,
|
||||
s.remoteKey,
|
||||
int64(totalAmt),
|
||||
s.localKey.PubKey, s.remoteKey, int64(totalAmt),
|
||||
s.tapscriptRoot,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -534,6 +534,7 @@ func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) {
|
|||
ShimIntent: ShimIntent{
|
||||
localFundingAmt: p.fundingAmt,
|
||||
musig2: req.Musig2,
|
||||
tapscriptRoot: req.TapscriptRoot,
|
||||
},
|
||||
State: PsbtShimRegistered,
|
||||
BasePsbt: p.basePsbt,
|
||||
|
|
|
@ -394,7 +394,6 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
|||
// we will call the specialized coin selection function for
|
||||
// that.
|
||||
case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
|
||||
|
||||
// We need to ensure that manually selected coins, which
|
||||
// are spent entirely on the channel funding, leave
|
||||
// enough funds in the wallet to cover for a reserve.
|
||||
|
@ -539,6 +538,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
|||
localFundingAmt: localContributionAmt,
|
||||
remoteFundingAmt: r.RemoteAmt,
|
||||
musig2: r.Musig2,
|
||||
tapscriptRoot: r.TapscriptRoot,
|
||||
},
|
||||
InputCoins: selectedCoins,
|
||||
coinLeaser: w.cfg.CoinLeaser,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,6 +2,7 @@ package lnwallet
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"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) {
|
||||
testAddSettleWorkflow(t, false, 0, true)
|
||||
})
|
||||
|
@ -828,6 +835,16 @@ func TestForceClose(t *testing.T) {
|
|||
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 {
|
||||
|
@ -5678,6 +5695,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
|||
spendDetail,
|
||||
aliceChannel.channelState.RemoteCommitment,
|
||||
aliceChannel.channelState.RemoteCurrentRevocation,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
require.NoError(t, err, "unable to create alice close summary")
|
||||
|
||||
|
@ -5827,6 +5845,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
|||
spendDetail,
|
||||
aliceChannel.channelState.RemoteCommitment,
|
||||
aliceChannel.channelState.RemoteCurrentRevocation,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
require.NoError(t, err, "unable to create alice close summary")
|
||||
|
||||
|
@ -5844,6 +5863,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
|||
spendDetail,
|
||||
aliceRemoteChainTip.Commitment,
|
||||
aliceChannel.channelState.RemoteNextRevocation,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
require.NoError(t, err, "unable to create alice close summary")
|
||||
|
||||
|
@ -6724,6 +6744,7 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) {
|
|||
breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx
|
||||
breachRet, err := NewBreachRetribution(
|
||||
aliceChannel.channelState, revokedStateNum, 100, breachTx,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
require.NoError(t, err, "unable to create breach retribution")
|
||||
|
||||
|
@ -8525,10 +8546,10 @@ func TestEvaluateView(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
view := &htlcView{
|
||||
ourUpdates: test.ourHtlcs,
|
||||
theirUpdates: test.theirHtlcs,
|
||||
feePerKw: feePerKw,
|
||||
view := &HtlcView{
|
||||
OurUpdates: test.ourHtlcs,
|
||||
TheirUpdates: test.theirHtlcs,
|
||||
FeePerKw: feePerKw,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -8549,17 +8570,17 @@ func TestEvaluateView(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result.feePerKw != test.expectedFee {
|
||||
if result.FeePerKw != test.expectedFee {
|
||||
t.Fatalf("expected fee: %v, got: %v",
|
||||
test.expectedFee, result.feePerKw)
|
||||
test.expectedFee, result.FeePerKw)
|
||||
}
|
||||
|
||||
checkExpectedHtlcs(
|
||||
t, result.ourUpdates, test.ourExpectedHtlcs,
|
||||
t, result.OurUpdates, test.ourExpectedHtlcs,
|
||||
)
|
||||
|
||||
checkExpectedHtlcs(
|
||||
t, result.theirUpdates, test.theirExpectedHtlcs,
|
||||
t, result.TheirUpdates, test.theirExpectedHtlcs,
|
||||
)
|
||||
|
||||
if lc.channelState.TotalMSatSent != test.expectSent {
|
||||
|
@ -8782,15 +8803,15 @@ func TestProcessFeeUpdate(t *testing.T) {
|
|||
EntryType: FeeUpdate,
|
||||
}
|
||||
|
||||
view := &htlcView{
|
||||
feePerKw: chainfee.SatPerKWeight(feePerKw),
|
||||
view := &HtlcView{
|
||||
FeePerKw: chainfee.SatPerKWeight(feePerKw),
|
||||
}
|
||||
processFeeUpdate(
|
||||
update, nextHeight, test.whoseCommitChain,
|
||||
test.mutate, view,
|
||||
)
|
||||
|
||||
if view.feePerKw != test.expectedFee {
|
||||
if view.FeePerKw != test.expectedFee {
|
||||
t.Fatalf("expected fee: %v, got: %v",
|
||||
test.expectedFee, feePerKw)
|
||||
}
|
||||
|
@ -9940,15 +9961,17 @@ func TestCreateHtlcRetribution(t *testing.T) {
|
|||
aliceChannel.channelState,
|
||||
)
|
||||
htlc := &channeldb.HTLCEntry{
|
||||
Amt: testAmt,
|
||||
Incoming: true,
|
||||
OutputIndex: 1,
|
||||
Amt: tlv.NewRecordT[tlv.TlvType4](
|
||||
tlv.NewBigSizeT(testAmt),
|
||||
),
|
||||
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true),
|
||||
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2, uint16](1),
|
||||
}
|
||||
|
||||
// Create the htlc retribution.
|
||||
hr, err := createHtlcRetribution(
|
||||
aliceChannel.channelState, keyRing, commitHash,
|
||||
dummyPrivate, leaseExpiry, htlc,
|
||||
dummyPrivate, leaseExpiry, htlc, fn.None[CommitAuxLeaves](),
|
||||
)
|
||||
// Expect no error.
|
||||
require.NoError(t, err)
|
||||
|
@ -9956,8 +9979,8 @@ func TestCreateHtlcRetribution(t *testing.T) {
|
|||
// Check the fields have expected values.
|
||||
require.EqualValues(t, testAmt, hr.SignDesc.Output.Value)
|
||||
require.Equal(t, commitHash, hr.OutPoint.Hash)
|
||||
require.EqualValues(t, htlc.OutputIndex, hr.OutPoint.Index)
|
||||
require.Equal(t, htlc.Incoming, hr.IsIncoming)
|
||||
require.EqualValues(t, htlc.OutputIndex.Val, hr.OutPoint.Index)
|
||||
require.Equal(t, htlc.Incoming.Val, hr.IsIncoming)
|
||||
}
|
||||
|
||||
// TestCreateBreachRetribution checks that `createBreachRetribution` behaves as
|
||||
|
@ -9997,30 +10020,31 @@ func TestCreateBreachRetribution(t *testing.T) {
|
|||
aliceChannel.channelState,
|
||||
)
|
||||
htlc := &channeldb.HTLCEntry{
|
||||
Amt: btcutil.Amount(testAmt),
|
||||
Incoming: true,
|
||||
OutputIndex: uint16(htlcIndex),
|
||||
Amt: tlv.NewRecordT[tlv.TlvType4](
|
||||
tlv.NewBigSizeT(btcutil.Amount(testAmt)),
|
||||
),
|
||||
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true),
|
||||
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2](
|
||||
uint16(htlcIndex),
|
||||
),
|
||||
}
|
||||
|
||||
// Create a dummy revocation log.
|
||||
ourAmtMsat := lnwire.MilliSatoshi(ourAmt * 1000)
|
||||
theirAmtMsat := lnwire.MilliSatoshi(theirAmt * 1000)
|
||||
revokedLog := channeldb.RevocationLog{
|
||||
CommitTxHash: commitHash,
|
||||
OurOutputIndex: uint16(localIndex),
|
||||
TheirOutputIndex: uint16(remoteIndex),
|
||||
HTLCEntries: []*channeldb.HTLCEntry{htlc},
|
||||
TheirBalance: &theirAmtMsat,
|
||||
OurBalance: &ourAmtMsat,
|
||||
}
|
||||
revokedLog := channeldb.NewRevocationLog(
|
||||
uint16(localIndex), uint16(remoteIndex), commitHash,
|
||||
fn.Some(ourAmtMsat), fn.Some(theirAmtMsat),
|
||||
[]*channeldb.HTLCEntry{htlc}, fn.None[tlv.Blob](),
|
||||
)
|
||||
|
||||
// Create a log with an empty local output index.
|
||||
revokedLogNoLocal := revokedLog
|
||||
revokedLogNoLocal.OurOutputIndex = channeldb.OutputIndexEmpty
|
||||
revokedLogNoLocal.OurOutputIndex.Val = channeldb.OutputIndexEmpty
|
||||
|
||||
// Create a log with an empty remote output index.
|
||||
revokedLogNoRemote := revokedLog
|
||||
revokedLogNoRemote.TheirOutputIndex = channeldb.OutputIndexEmpty
|
||||
revokedLogNoRemote.TheirOutputIndex.Val = channeldb.OutputIndexEmpty
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
@ -10050,14 +10074,20 @@ func TestCreateBreachRetribution(t *testing.T) {
|
|||
{
|
||||
name: "fail due to our index too big",
|
||||
revocationLog: &channeldb.RevocationLog{
|
||||
OurOutputIndex: uint16(htlcIndex + 1),
|
||||
//nolint:lll
|
||||
OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
|
||||
uint16(htlcIndex + 1),
|
||||
),
|
||||
},
|
||||
expectedErr: ErrOutputIndexOutOfRange,
|
||||
},
|
||||
{
|
||||
name: "fail due to their index too big",
|
||||
revocationLog: &channeldb.RevocationLog{
|
||||
TheirOutputIndex: uint16(htlcIndex + 1),
|
||||
//nolint:lll
|
||||
TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||
uint16(htlcIndex + 1),
|
||||
),
|
||||
},
|
||||
expectedErr: ErrOutputIndexOutOfRange,
|
||||
},
|
||||
|
@ -10126,11 +10156,12 @@ func TestCreateBreachRetribution(t *testing.T) {
|
|||
require.Equal(t, remote, br.RemoteOutpoint)
|
||||
|
||||
for _, hr := range br.HtlcRetributions {
|
||||
require.EqualValues(t, testAmt,
|
||||
hr.SignDesc.Output.Value)
|
||||
require.EqualValues(
|
||||
t, testAmt, hr.SignDesc.Output.Value,
|
||||
)
|
||||
require.Equal(t, commitHash, hr.OutPoint.Hash)
|
||||
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,
|
||||
aliceChannel.channelState, keyRing,
|
||||
dummyPrivate, leaseExpiry,
|
||||
fn.None[CommitAuxLeaves](),
|
||||
)
|
||||
|
||||
// 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.
|
||||
_, err = NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
||||
|
||||
|
@ -10271,6 +10304,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||
// provided.
|
||||
_, err = NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum, breachHeight, nil,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
||||
|
||||
|
@ -10316,6 +10350,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||
// successfully.
|
||||
br, err := NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
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.
|
||||
br, err = NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum, breachHeight, nil,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assertRetribution(br, 1, 0)
|
||||
|
@ -10335,6 +10371,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||
// error.
|
||||
_, err = NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum+1, breachHeight, breachTx,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
||||
|
||||
|
@ -10342,6 +10379,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||
// provided.
|
||||
_, err = NewBreachRetribution(
|
||||
aliceChannel.channelState, stateNum+1, breachHeight, nil,
|
||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||
)
|
||||
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
|
||||
// scripts(`genHtlcScript`) as it should be tested independently.
|
||||
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)
|
||||
|
||||
|
@ -10415,19 +10454,26 @@ func assertPayDescMatchHTLC(t *testing.T, pd PaymentDescriptor,
|
|||
// the `Incoming`.
|
||||
func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC {
|
||||
var onionBlob [lnwire.OnionPacketSize]byte
|
||||
_, err := rand.Read(onionBlob[:])
|
||||
_, err := crand.Read(onionBlob[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
var rHash [lntypes.HashSize]byte
|
||||
_, err = rand.Read(rHash[:])
|
||||
_, err = crand.Read(rHash[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
sig := make([]byte, 64)
|
||||
_, err = rand.Read(sig)
|
||||
_, err = crand.Read(sig)
|
||||
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(
|
||||
"0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d48236c39", //nolint:lll
|
||||
"0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d48" +
|
||||
"236c39",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -10442,9 +10488,13 @@ func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC {
|
|||
HtlcIndex: rand.Uint64(),
|
||||
LogIndex: rand.Uint64(),
|
||||
BlindingPoint: tlv.SomeRecordT(
|
||||
//nolint:lll
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](blinding),
|
||||
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
|
||||
blinding,
|
||||
),
|
||||
),
|
||||
CustomRecords: map[uint64][]byte{
|
||||
uint64(randCustomType): randCustomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/btcsuite/btcd/txscript"
|
||||
"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/lnwallet/chainfee"
|
||||
|
@ -226,8 +227,7 @@ func (w *WitnessScriptDesc) WitnessScriptForPath(
|
|||
// the settled funds in the channel, plus the unsettled funds.
|
||||
func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
|
||||
selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
|
||||
) (
|
||||
input.ScriptDescriptor, error) {
|
||||
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
|
||||
|
||||
switch {
|
||||
// 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.
|
||||
case chanType.IsTaproot():
|
||||
return input.NewLocalCommitScriptTree(
|
||||
csvDelay, selfKey, revokeKey,
|
||||
csvDelay, selfKey, revokeKey, auxLeaf,
|
||||
)
|
||||
|
||||
// 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,
|
||||
// what must be satisfied in order to spend the output.
|
||||
func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool,
|
||||
remoteKey *btcec.PublicKey,
|
||||
leaseExpiry uint32) (input.ScriptDescriptor, uint32, error) {
|
||||
remoteKey *btcec.PublicKey, leaseExpiry uint32,
|
||||
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, uint32, error) {
|
||||
|
||||
switch {
|
||||
// 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.
|
||||
case chanType.IsTaproot():
|
||||
toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(
|
||||
remoteKey,
|
||||
remoteKey, auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
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
|
||||
// we are generating the to_local script for.
|
||||
func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool,
|
||||
revocationKey, delayKey *btcec.PublicKey,
|
||||
csvDelay, leaseExpiry uint32) (input.ScriptDescriptor, error) {
|
||||
revocationKey, delayKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
|
||||
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
|
||||
|
||||
switch {
|
||||
// For taproot channels, the pkScript is a segwit v1 p2tr output.
|
||||
case chanType.IsTaproot():
|
||||
return input.TaprootSecondLevelScriptTree(
|
||||
revocationKey, delayKey, csvDelay,
|
||||
revocationKey, delayKey, csvDelay, auxLeaf,
|
||||
)
|
||||
|
||||
// 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
|
||||
// transactions.
|
||||
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
|
||||
// parameters.
|
||||
chanState *channeldb.OpenChannel
|
||||
|
@ -621,10 +621,16 @@ type CommitmentBuilder struct {
|
|||
// obfuscator is a 48-bit state hint that's used to obfuscate the
|
||||
// current state number on the commitment transactions.
|
||||
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.
|
||||
func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder {
|
||||
func NewCommitmentBuilder(chanState *channeldb.OpenChannel,
|
||||
leafStore fn.Option[AuxLeafStore]) *CommitmentBuilder {
|
||||
|
||||
// The anchor channel type MUST be tweakless.
|
||||
if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() {
|
||||
panic("invalid channel type combination")
|
||||
|
@ -633,6 +639,7 @@ func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder {
|
|||
return &CommitmentBuilder{
|
||||
chanState: chanState,
|
||||
obfuscator: createStateHintObfuscator(chanState),
|
||||
auxLeafStore: leafStore,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -685,9 +692,9 @@ type unsignedCommitmentTx struct {
|
|||
// fees, but after anchor outputs.
|
||||
func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||
theirBalance lnwire.MilliSatoshi, whoseCommit lntypes.ChannelParty,
|
||||
feePerKw chainfee.SatPerKWeight, height uint64,
|
||||
filteredHTLCView *htlcView,
|
||||
keyRing *CommitmentKeyRing) (*unsignedCommitmentTx, error) {
|
||||
feePerKw chainfee.SatPerKWeight, height uint64, originalHtlcView,
|
||||
filteredHTLCView *HtlcView, keyRing *CommitmentKeyRing,
|
||||
prevCommit *commitment) (*unsignedCommitmentTx, error) {
|
||||
|
||||
dustLimit := cb.chanState.LocalChanCfg.DustLimit
|
||||
if whoseCommit.IsRemote() {
|
||||
|
@ -695,7 +702,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||
}
|
||||
|
||||
numHTLCs := int64(0)
|
||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
||||
for _, htlc := range filteredHTLCView.OurUpdates {
|
||||
if HtlcIsDust(
|
||||
cb.chanState.ChanType, false, whoseCommit, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
|
@ -706,7 +713,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||
|
||||
numHTLCs++
|
||||
}
|
||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
||||
for _, htlc := range filteredHTLCView.TheirUpdates {
|
||||
if HtlcIsDust(
|
||||
cb.chanState.ChanType, true, whoseCommit, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
|
@ -748,10 +755,24 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||
theirBalance -= commitFeeMSat
|
||||
}
|
||||
|
||||
var (
|
||||
commitTx *wire.MsgTx
|
||||
err error
|
||||
var commitTx *wire.MsgTx
|
||||
|
||||
// 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
|
||||
// CreateCommitTx with parameters matching the perspective, to generate
|
||||
|
@ -767,6 +788,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
|
||||
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
|
||||
numHTLCs, cb.chanState.IsInitiator, leaseExpiry,
|
||||
auxResult.AuxLeaves,
|
||||
)
|
||||
} else {
|
||||
commitTx, err = CreateCommitTx(
|
||||
|
@ -774,12 +796,26 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
|
||||
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
|
||||
numHTLCs, !cb.chanState.IsInitiator, leaseExpiry,
|
||||
auxResult.AuxLeaves,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
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.
|
||||
// Each output includes an off-chain 2-of-2 covenant clause, so we'll
|
||||
// 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
|
||||
// purposes of sorting.
|
||||
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(
|
||||
cb.chanState.ChanType, false, whoseCommit, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
|
@ -799,16 +836,26 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||
continue
|
||||
}
|
||||
|
||||
auxLeaf := fn.ChainOption(
|
||||
func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf {
|
||||
return leaves[htlc.HtlcIndex].AuxTapLeaf
|
||||
},
|
||||
)(outgoingAuxLeaves)
|
||||
|
||||
err := addHTLC(
|
||||
commitTx, whoseCommit, false, htlc, keyRing,
|
||||
cb.chanState.ChanType,
|
||||
cb.chanState.ChanType, auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
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(
|
||||
cb.chanState.ChanType, true, whoseCommit, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
|
@ -817,14 +864,24 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||
continue
|
||||
}
|
||||
|
||||
auxLeaf := fn.ChainOption(
|
||||
func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf {
|
||||
return leaves[htlc.HtlcIndex].AuxTapLeaf
|
||||
},
|
||||
)(incomingAuxLeaves)
|
||||
|
||||
err := addHTLC(
|
||||
commitTx, whoseCommit, true, htlc, keyRing,
|
||||
cb.chanState.ChanType,
|
||||
cb.chanState.ChanType, auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
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
|
||||
|
@ -836,9 +893,16 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
|||
}
|
||||
|
||||
// Sort the transactions according to the agreed upon canonical
|
||||
// ordering. This lets us skip sending the entire transaction over,
|
||||
// instead we'll just send signatures.
|
||||
InPlaceCommitSort(commitTx, cltvs)
|
||||
// ordering (which might be customized for custom channel types, but
|
||||
// deterministic and both parties will arrive at the same result). This
|
||||
// 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
|
||||
// transaction which would be invalid by consensus.
|
||||
|
@ -880,24 +944,33 @@ func CreateCommitTx(chanType channeldb.ChannelType,
|
|||
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
|
||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
||||
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.
|
||||
// This output has 2 main redemption clauses: either we can redeem the
|
||||
// output after a relative block delay, or the remote node can claim
|
||||
// the funds with the revocation key if we broadcast a revoked
|
||||
// commitment transaction.
|
||||
localAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.LocalAuxLeaf
|
||||
})(auxLeaves)
|
||||
toLocalScript, err := CommitScriptToSelf(
|
||||
chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey,
|
||||
uint32(localChanCfg.CsvDelay), leaseExpiry,
|
||||
fn.FlattenOption(localAuxLeaf),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next, we create the script paying to the remote.
|
||||
remoteAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.RemoteAuxLeaf
|
||||
})(auxLeaves)
|
||||
toRemoteScript, _, err := CommitScriptToRemote(
|
||||
chanType, initiator, keyRing.ToRemoteKey, leaseExpiry,
|
||||
fn.FlattenOption(remoteAuxLeaf),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1077,7 +1150,7 @@ func genSegwitV0HtlcScript(chanType channeldb.ChannelType,
|
|||
// channel.
|
||||
func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
||||
timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing,
|
||||
) (*input.HtlcScriptTree, error) {
|
||||
auxLeaf input.AuxTapLeaf) (*input.HtlcScriptTree, error) {
|
||||
|
||||
var (
|
||||
htlcScriptTree *input.HtlcScriptTree
|
||||
|
@ -1094,7 +1167,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||
case isIncoming && whoseCommit.IsLocal():
|
||||
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
|
||||
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
|
||||
|
@ -1103,7 +1176,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||
case isIncoming && whoseCommit.IsRemote():
|
||||
htlcScriptTree, err = input.SenderHTLCScriptTaproot(
|
||||
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
|
||||
|
@ -1112,7 +1185,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||
case !isIncoming && whoseCommit.IsLocal():
|
||||
htlcScriptTree, err = input.SenderHTLCScriptTaproot(
|
||||
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
|
||||
|
@ -1121,7 +1194,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
|
|||
case !isIncoming && whoseCommit.IsRemote():
|
||||
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
|
||||
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.
|
||||
func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
|
||||
whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte,
|
||||
keyRing *CommitmentKeyRing) (input.ScriptDescriptor, error) {
|
||||
keyRing *CommitmentKeyRing,
|
||||
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
|
||||
|
||||
if !chanType.IsTaproot() {
|
||||
return genSegwitV0HtlcScript(
|
||||
|
@ -1146,7 +1220,7 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
|
|||
}
|
||||
|
||||
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.
|
||||
func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty,
|
||||
isIncoming bool, paymentDesc *PaymentDescriptor,
|
||||
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error {
|
||||
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType,
|
||||
auxLeaf input.AuxTapLeaf) error {
|
||||
|
||||
timeout := paymentDesc.Timeout
|
||||
rHash := paymentDesc.RHash
|
||||
|
||||
scriptInfo, err := genHtlcScript(
|
||||
chanType, isIncoming, whoseCommit, timeout, rHash, keyRing,
|
||||
auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
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
|
||||
// to find the match.
|
||||
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.
|
||||
ourIndex := uint32(channeldb.OutputIndexEmpty)
|
||||
|
@ -1228,26 +1305,51 @@ func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
|
|||
leaseExpiry = chanState.ThawHeight
|
||||
}
|
||||
|
||||
// Map the scripts from our PoV. When facing a local commitment, the 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.
|
||||
// If we have a custom blob, then we'll attempt to fetch the aux leaves
|
||||
// for this state.
|
||||
auxResult, err := fn.MapOptionZ(
|
||||
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
|
||||
// commitment, the to local output belongs to them.
|
||||
// Map the scripts from our PoV. When facing a local commitment, the
|
||||
// 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(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
|
||||
keyRing.RevocationKey, theirDelay, leaseExpiry,
|
||||
keyRing.RevocationKey, theirDelay, leaseExpiry, localAuxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return ourIndex, theirIndex, err
|
||||
}
|
||||
|
||||
// Compute the to remote script. From our PoV, when facing a remote
|
||||
// commitment, the to remote output belongs to us.
|
||||
// Compute the to_remote script. From our PoV, when facing a remote
|
||||
// commitment, the to_remote output belongs to us.
|
||||
remoteAuxLeaf := fn.ChainOption(
|
||||
func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.RemoteAuxLeaf
|
||||
},
|
||||
)(auxResult.AuxLeaves)
|
||||
ourScript, _, err := CommitScriptToRemote(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
|
||||
leaseExpiry,
|
||||
leaseExpiry, remoteAuxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return ourIndex, theirIndex, err
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
|
@ -62,4 +63,8 @@ type Config struct {
|
|||
// CoinSelectionStrategy is the strategy that is used for selecting
|
||||
// coins when funding a transaction.
|
||||
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/wtxmgr"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -397,3 +399,45 @@ func (*mockChainIO) GetBlockHeader(
|
|||
|
||||
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]())
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
@ -37,6 +39,20 @@ var (
|
|||
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
|
||||
// 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
|
||||
|
@ -54,25 +70,30 @@ type MusigPartialSig struct {
|
|||
|
||||
// signerKeys is the set of public keys of all signers.
|
||||
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.
|
||||
func NewMusigPartialSig(sig *musig2.PartialSignature,
|
||||
signerNonce, combinedNonce lnwire.Musig2Nonce,
|
||||
signerKeys []*btcec.PublicKey) *MusigPartialSig {
|
||||
// NewMusigPartialSig creates a new MuSig2 partial signature.
|
||||
func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce,
|
||||
combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey,
|
||||
tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig {
|
||||
|
||||
return &MusigPartialSig{
|
||||
sig: sig,
|
||||
signerNonce: signerNonce,
|
||||
combinedNonce: combinedNonce,
|
||||
signerKeys: signerKeys,
|
||||
tapscriptTweak: tapscriptTweak,
|
||||
}
|
||||
}
|
||||
|
||||
// FromWireSig maps a wire partial sig to this internal type that we'll use to
|
||||
// perform signature validation.
|
||||
func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce,
|
||||
) *MusigPartialSig {
|
||||
func (p *MusigPartialSig) FromWireSig(
|
||||
sig *lnwire.PartialSigWithNonce) *MusigPartialSig {
|
||||
|
||||
p.sig = &musig2.PartialSignature{
|
||||
S: &sig.Sig,
|
||||
|
@ -135,9 +156,15 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
|
|||
var m [32]byte
|
||||
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(
|
||||
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[:])
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
@ -197,15 +232,20 @@ type MusigSession struct {
|
|||
// commitType tracks if this is the session for the local or remote
|
||||
// commitment.
|
||||
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
|
||||
// verification nonce (local nonce), and the other information that has already
|
||||
// been bound to the session.
|
||||
func NewPartialMusigSession(verificationNonce musig2.Nonces,
|
||||
localKey, remoteKey keychain.KeyDescriptor,
|
||||
signer input.MuSig2Signer, inputTxOut *wire.TxOut,
|
||||
commitType MusigCommitType) *MusigSession {
|
||||
localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer,
|
||||
inputTxOut *wire.TxOut, commitType MusigCommitType,
|
||||
tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
|
||||
|
||||
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
|
||||
|
||||
|
@ -221,6 +261,7 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces,
|
|||
signerKeys: signerKeys,
|
||||
signer: signer,
|
||||
commitType: commitType,
|
||||
tapscriptTweak: tapscriptTweak,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,9 +295,9 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
|
|||
remoteNonce = m.nonces.SigningNonce
|
||||
}
|
||||
|
||||
tweakDesc := input.MuSig2Tweaks{
|
||||
tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
|
||||
TaprootBIP0086Tweak: true,
|
||||
}
|
||||
})
|
||||
m.session, err = m.signer.MuSig2CreateSession(
|
||||
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
|
||||
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
|
||||
|
@ -351,8 +392,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
|
||||
|
||||
return NewMusigPartialSig(
|
||||
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
|
||||
tapscriptRoot,
|
||||
), nil
|
||||
}
|
||||
|
||||
|
@ -364,7 +408,7 @@ func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
|
|||
|
||||
return NewPartialMusigSession(
|
||||
*verificationNonce, m.localKey, m.remoteKey, m.signer,
|
||||
m.inputTxOut, m.commitType,
|
||||
m.inputTxOut, m.commitType, m.tapscriptTweak,
|
||||
), nil
|
||||
}
|
||||
|
||||
|
@ -451,9 +495,11 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
|
|||
// When we verify a commitment signature, we always assume that we're
|
||||
// verifying a signature on our local commitment. Therefore, we'll use:
|
||||
// their remote nonce, and also public key.
|
||||
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
|
||||
partialSig := NewMusigPartialSig(
|
||||
&musig2.PartialSignature{S: &sig.Sig},
|
||||
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
|
||||
tapscriptRoot,
|
||||
)
|
||||
|
||||
// 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
|
||||
// funding input.
|
||||
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
|
||||
|
@ -561,13 +611,14 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
|
|||
//
|
||||
// Both sessions will be created using only the verification nonce for
|
||||
// the local+remote party.
|
||||
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak)
|
||||
localSession := NewPartialMusigSession(
|
||||
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey,
|
||||
cfg.Signer, cfg.InputTxOut, LocalMusigCommit,
|
||||
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
|
||||
cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
|
||||
)
|
||||
remoteSession := NewPartialMusigSession(
|
||||
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey,
|
||||
cfg.Signer, cfg.InputTxOut, RemoteMusigCommit,
|
||||
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
|
||||
cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
|
||||
)
|
||||
|
||||
return &MusigPairSession{
|
||||
|
|
|
@ -221,4 +221,8 @@ type PaymentDescriptor struct {
|
|||
// blinded route (ie, not the introduction node) from update_add_htlc's
|
||||
// TLVs.
|
||||
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
|
||||
}
|
||||
|
||||
if req.TapscriptRoot.IsSome() {
|
||||
chanType |= channeldb.TapscriptRootBit
|
||||
}
|
||||
|
||||
return &ChannelReservation{
|
||||
ourContribution: &ChannelContribution{
|
||||
FundingAmount: ourBalance.ToSatoshis(),
|
||||
|
@ -448,6 +452,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
|||
InitialLocalBalance: ourBalance,
|
||||
InitialRemoteBalance: theirBalance,
|
||||
Memo: req.Memo,
|
||||
TapscriptRoot: req.TapscriptRoot,
|
||||
},
|
||||
pushMSat: req.PushMSat,
|
||||
pendingChanID: req.PendingChanID,
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"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/keychain"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
|
@ -348,6 +349,21 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
|
|||
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)
|
||||
bobSigner := input.NewMockSigner(bobKeys, nil)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -50,8 +51,8 @@ var (
|
|||
// - <sender sig> <receiver sig> <preimage> <success_script> <control_block>
|
||||
func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
|
||||
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay,
|
||||
leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) (
|
||||
*wire.MsgTx, error) {
|
||||
leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey,
|
||||
auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a version two transaction (as the success version of this
|
||||
// spends an output with a CSV timeout).
|
||||
|
@ -71,7 +72,7 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
|
|||
// HTLC outputs.
|
||||
scriptInfo, err := SecondLevelHtlcScript(
|
||||
chanType, initiator, revocationKey, delayKey, csvDelay,
|
||||
leaseExpiry,
|
||||
leaseExpiry, auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -110,7 +111,8 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
|
|||
func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool,
|
||||
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
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
|
||||
// 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.
|
||||
scriptInfo, err := SecondLevelHtlcScript(
|
||||
chanType, initiator, revocationKey, delayKey, csvDelay,
|
||||
leaseExpiry,
|
||||
leaseExpiry, auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
|
@ -631,6 +632,7 @@ func testSpendValidation(t *testing.T, tweakless bool) {
|
|||
commitmentTx, err := CreateCommitTx(
|
||||
channelType, *fakeFundingTxIn, keyRing, aliceChanCfg,
|
||||
bobChanCfg, channelBalance, channelBalance, 0, true, 0,
|
||||
fn.None[CommitAuxLeaves](),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create commitment transaction: %v", nil)
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
|
@ -210,6 +211,11 @@ type InitFundingReserveMsg struct {
|
|||
// channel that will be useful to our future selves.
|
||||
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
|
||||
// nil if this initial set is successful.
|
||||
//
|
||||
|
@ -1464,6 +1470,21 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
|
|||
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
|
||||
// commitment transaction for both parties. This function is used during 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,
|
||||
localCommitPoint, remoteCommitPoint *btcec.PublicKey,
|
||||
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(
|
||||
localCommitPoint, lntypes.Local, chanType, ourChanCfg,
|
||||
|
@ -1487,7 +1514,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
|||
ourCommitTx, err := CreateCommitTx(
|
||||
chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
|
||||
theirChanCfg, localBalance, remoteBalance, 0, initiator,
|
||||
leaseExpiry,
|
||||
leaseExpiry, options.auxLeaves,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -1501,7 +1528,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
|||
theirCommitTx, err := CreateCommitTx(
|
||||
chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
|
||||
ourChanCfg, remoteBalance, localBalance, 0, !initiator,
|
||||
leaseExpiry,
|
||||
leaseExpiry, options.auxLeaves,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -2102,6 +2129,7 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation,
|
|||
if res.musigSessions == nil {
|
||||
_, fundingOutput, err := input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, channelValue,
|
||||
res.partialState.TapscriptRoot,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2341,11 +2369,14 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
|||
fundingTxOut *wire.TxOut
|
||||
)
|
||||
if chanType.IsTaproot() {
|
||||
fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript( //nolint:lll
|
||||
//nolint:lll
|
||||
fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript(
|
||||
ourKey.PubKey, theirKey.PubKey, channelValue,
|
||||
pendingReservation.partialState.TapscriptRoot,
|
||||
)
|
||||
} else {
|
||||
fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript( //nolint:lll
|
||||
//nolint:lll
|
||||
fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript(
|
||||
ourKey.PubKey.SerializeCompressed(),
|
||||
theirKey.PubKey.SerializeCompressed(), channelValue,
|
||||
)
|
||||
|
@ -2465,9 +2496,16 @@ func initStateHints(commit1, commit2 *wire.MsgTx,
|
|||
func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
|
||||
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
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
@ -2482,6 +2520,7 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
|
|||
if channelState.ChanType.IsTaproot() {
|
||||
fundingScript, _, err = input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, int64(channel.Capacity),
|
||||
channelState.TapscriptRoot,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -372,6 +372,10 @@ type Config struct {
|
|||
AddLocalAlias func(alias, base lnwire.ShortChannelID,
|
||||
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
|
||||
// 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
|
||||
|
@ -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(
|
||||
p.cfg.Signer, dbChan, p.cfg.SigPool,
|
||||
p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
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())
|
||||
}
|
||||
|
||||
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
|
||||
// channels, so we can look it up later easily according to its channel
|
||||
// ID.
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
|
||||
|
@ -43,10 +44,15 @@ func (m *MusigChanCloser) ProposalClosingOpts() (
|
|||
}
|
||||
|
||||
localKey, remoteKey := m.channel.MultiSigKeys()
|
||||
|
||||
tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)(
|
||||
m.channel.State().TapscriptRoot,
|
||||
)
|
||||
|
||||
m.musigSession = lnwallet.NewPartialMusigSession(
|
||||
*m.remoteNonce, localKey, remoteKey,
|
||||
m.channel.Signer, m.channel.FundingTxOut(),
|
||||
lnwallet.RemoteMusigCommit,
|
||||
lnwallet.RemoteMusigCommit, tapscriptTweak,
|
||||
)
|
||||
|
||||
err := m.musigSession.FinalizeSession(*m.localNonce)
|
||||
|
|
|
@ -1273,6 +1273,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
|
||||
return &pc.Incoming
|
||||
},
|
||||
AuxLeafStore: implCfg.AuxLeafStore,
|
||||
}, dbs.ChanStateDB)
|
||||
|
||||
// Select the configuration and funding parameters for Bitcoin.
|
||||
|
@ -1607,6 +1608,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
|
||||
br, err := lnwallet.NewBreachRetribution(
|
||||
channel, commitHeight, 0, nil,
|
||||
implCfg.AuxLeafStore,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
@ -4073,6 +4075,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||
DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(),
|
||||
MaxFeeExposure: thresholdMSats,
|
||||
Quit: s.quit,
|
||||
AuxLeafStore: s.implCfg.AuxLeafStore,
|
||||
MsgRouter: s.implCfg.MsgRouter,
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
@ -307,9 +308,11 @@ func newTaprootJusticeKit(sweepScript []byte,
|
|||
|
||||
keyRing := breachInfo.KeyRing
|
||||
|
||||
// TODO(roasbeef): aux leaf tower updates needed
|
||||
|
||||
tree, err := input.NewLocalCommitScriptTree(
|
||||
breachInfo.RemoteDelay, keyRing.ToLocalKey,
|
||||
keyRing.RevocationKey,
|
||||
keyRing.RevocationKey, fn.None[txscript.TapLeaf](),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -416,7 +419,9 @@ func (t *taprootJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
|
|||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
scriptTree, err := input.NewRemoteCommitScriptTree(toRemotePk)
|
||||
scriptTree, err := input.NewRemoteCommitScriptTree(
|
||||
toRemotePk, fn.None[txscript.TapLeaf](),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
@ -304,7 +305,9 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
|||
name: "taproot commitment",
|
||||
blobType: TypeAltruistTaprootCommit,
|
||||
expWitnessScript: func(pk *btcec.PublicKey) []byte {
|
||||
tree, _ := input.NewRemoteCommitScriptTree(pk)
|
||||
tree, _ := input.NewRemoteCommitScriptTree(
|
||||
pk, fn.None[txscript.TapLeaf](),
|
||||
)
|
||||
|
||||
return tree.SettleLeaf.Script
|
||||
},
|
||||
|
@ -461,6 +464,7 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
|
|||
|
||||
script, _ := input.NewLocalCommitScriptTree(
|
||||
csvDelay, delay, rev,
|
||||
fn.None[txscript.TapLeaf](),
|
||||
)
|
||||
|
||||
return script.RevocationLeaf.Script
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
|
@ -123,7 +124,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
|
||||
if isTaprootChannel {
|
||||
toLocalCommitTree, err = input.NewLocalCommitScriptTree(
|
||||
csvDelay, toLocalPK, revPK,
|
||||
csvDelay, toLocalPK, revPK, fn.None[txscript.TapLeaf](),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -174,7 +175,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
toRemoteSequence = 1
|
||||
|
||||
commitScriptTree, err := input.NewRemoteCommitScriptTree(
|
||||
toRemotePK,
|
||||
toRemotePK, fn.None[txscript.TapLeaf](),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
|
@ -136,6 +137,7 @@ func genTaskTest(
|
|||
if chanType.IsTaproot() {
|
||||
scriptTree, _ := input.NewLocalCommitScriptTree(
|
||||
csvDelay, toLocalPK, revPK,
|
||||
fn.None[txscript.TapLeaf](),
|
||||
)
|
||||
|
||||
pkScript, _ := input.PayToTaprootScript(
|
||||
|
@ -189,7 +191,7 @@ func genTaskTest(
|
|||
|
||||
if chanType.IsTaproot() {
|
||||
scriptTree, _ := input.NewRemoteCommitScriptTree(
|
||||
toRemotePK,
|
||||
toRemotePK, fn.None[txscript.TapLeaf](),
|
||||
)
|
||||
|
||||
pkScript, _ := input.PayToTaprootScript(
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
|
@ -230,12 +231,14 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) {
|
|||
|
||||
// Construct the to-local witness script.
|
||||
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")
|
||||
|
||||
// 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")
|
||||
|
||||
// Compute the to-local witness script hash.
|
||||
|
|
Loading…
Add table
Reference in a new issue