Merge pull request #9072 from lightningnetwork/extract-part3-from-staging-branch
[custom channels 3/5]: Extract PART3 from mega staging branch
This commit is contained in:
40 files changed
+1926
-162
No files matched your search
@@ -0,0 +1,250 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"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"
|
||||
)
|
||||
|
||||
// htlcCustomSigType is the TLV type that is used to encode the custom HTLC
|
||||
// signatures within the custom data for an existing HTLC.
|
||||
var htlcCustomSigType tlv.TlvType65543
|
||||
|
||||
// AuxHtlcDescriptor is a struct that contains the information needed to sign or
|
||||
// verify an HTLC for custom channels.
|
||||
type AuxHtlcDescriptor struct {
|
||||
// ChanID is the ChannelID of the LightningChannel that this
|
||||
// paymentDescriptor belongs to. We track this here so we can
|
||||
// reconstruct the Messages that this paymentDescriptor is built from.
|
||||
ChanID lnwire.ChannelID
|
||||
|
||||
// RHash is the payment hash for this HTLC. The HTLC can be settled iff
|
||||
// the preimage to this hash is presented.
|
||||
RHash PaymentHash
|
||||
|
||||
// Timeout is the absolute timeout in blocks, after which this HTLC
|
||||
// expires.
|
||||
Timeout uint32
|
||||
|
||||
// Amount is the HTLC amount in milli-satoshis.
|
||||
Amount lnwire.MilliSatoshi
|
||||
|
||||
// HtlcIndex is the index within the main update log for this HTLC.
|
||||
// Entries within the log of type Add will have this field populated,
|
||||
// as other entries will point to the entry via this counter.
|
||||
//
|
||||
// NOTE: This field will only be populated if EntryType is Add.
|
||||
HtlcIndex uint64
|
||||
|
||||
// ParentIndex is the HTLC index of the entry that this update settles
|
||||
// or times out.
|
||||
//
|
||||
// NOTE: This field will only be populated if EntryType is Fail or
|
||||
// Settle.
|
||||
ParentIndex uint64
|
||||
|
||||
// EntryType denotes the exact type of the paymentDescriptor. In the
|
||||
// case of a Timeout, or Settle type, then the Parent field will point
|
||||
// into the log to the HTLC being modified.
|
||||
EntryType updateType
|
||||
|
||||
// CustomRecords also stores the set of optional custom records that
|
||||
// may have been attached to a sent HTLC.
|
||||
CustomRecords lnwire.CustomRecords
|
||||
|
||||
// addCommitHeight[Remote|Local] encodes the height of the commitment
|
||||
// which included this HTLC on either the remote or local commitment
|
||||
// chain. This value is used to determine when an HTLC is fully
|
||||
// "locked-in".
|
||||
addCommitHeightRemote uint64
|
||||
addCommitHeightLocal uint64
|
||||
|
||||
// removeCommitHeight[Remote|Local] encodes the height of the
|
||||
// commitment which removed the parent pointer of this
|
||||
// paymentDescriptor either due to a timeout or a settle. Once both
|
||||
// these heights are below the tail of both chains, the log entries can
|
||||
// safely be removed.
|
||||
removeCommitHeightRemote uint64
|
||||
removeCommitHeightLocal uint64
|
||||
}
|
||||
|
||||
// AddHeight returns the height at which the HTLC was added to the commitment
|
||||
// chain. The height is returned based on the chain the HTLC is being added to
|
||||
// (local or remote chain).
|
||||
func (a *AuxHtlcDescriptor) AddHeight(
|
||||
whoseCommitChain lntypes.ChannelParty) uint64 {
|
||||
|
||||
if whoseCommitChain.IsRemote() {
|
||||
return a.addCommitHeightRemote
|
||||
}
|
||||
|
||||
return a.addCommitHeightLocal
|
||||
}
|
||||
|
||||
// RemoveHeight returns the height at which the HTLC was removed from the
|
||||
// commitment chain. The height is returned based on the chain the HTLC is being
|
||||
// removed from (local or remote chain).
|
||||
func (a *AuxHtlcDescriptor) RemoveHeight(
|
||||
whoseCommitChain lntypes.ChannelParty) uint64 {
|
||||
|
||||
if whoseCommitChain.IsRemote() {
|
||||
return a.removeCommitHeightRemote
|
||||
}
|
||||
|
||||
return a.removeCommitHeightLocal
|
||||
}
|
||||
|
||||
// newAuxHtlcDescriptor creates a new AuxHtlcDescriptor from a payment
|
||||
// descriptor.
|
||||
func newAuxHtlcDescriptor(p *paymentDescriptor) AuxHtlcDescriptor {
|
||||
return AuxHtlcDescriptor{
|
||||
ChanID: p.ChanID,
|
||||
RHash: p.RHash,
|
||||
Timeout: p.Timeout,
|
||||
Amount: p.Amount,
|
||||
HtlcIndex: p.HtlcIndex,
|
||||
ParentIndex: p.ParentIndex,
|
||||
EntryType: p.EntryType,
|
||||
CustomRecords: p.CustomRecords.Copy(),
|
||||
addCommitHeightRemote: p.addCommitHeightRemote,
|
||||
addCommitHeightLocal: p.addCommitHeightLocal,
|
||||
removeCommitHeightRemote: p.removeCommitHeightRemote,
|
||||
removeCommitHeightLocal: p.removeCommitHeightLocal,
|
||||
}
|
||||
}
|
||||
|
||||
// BaseAuxJob is a struct that contains the common fields that are shared among
|
||||
// the aux sign/verify jobs.
|
||||
type BaseAuxJob struct {
|
||||
// OutputIndex is the output index of the HTLC on the commitment
|
||||
// transaction being signed.
|
||||
//
|
||||
// NOTE: If the output is dust from the PoV of the commitment chain,
|
||||
// then this value will be -1.
|
||||
OutputIndex int32
|
||||
|
||||
// KeyRing is the commitment key ring that contains the keys needed to
|
||||
// generate the second level HTLC signatures.
|
||||
KeyRing CommitmentKeyRing
|
||||
|
||||
// HTLC is the HTLC that is being signed or verified.
|
||||
HTLC AuxHtlcDescriptor
|
||||
|
||||
// Incoming is a boolean that indicates if the HTLC is incoming or
|
||||
// outgoing.
|
||||
Incoming bool
|
||||
|
||||
// CommitBlob is the commitment transaction blob that contains the aux
|
||||
// information for this channel.
|
||||
CommitBlob fn.Option[tlv.Blob]
|
||||
|
||||
// HtlcLeaf is the aux tap leaf that corresponds to the HTLC being
|
||||
// signed/verified.
|
||||
HtlcLeaf input.AuxTapLeaf
|
||||
}
|
||||
|
||||
// AuxSigJob is a struct that contains all the information needed to sign an
|
||||
// HTLC for custom channels.
|
||||
type AuxSigJob struct {
|
||||
// SignDesc is the sign desc for this HTLC.
|
||||
SignDesc input.SignDescriptor
|
||||
|
||||
BaseAuxJob
|
||||
|
||||
// Resp is a channel that will be used to send the result of the sign
|
||||
// job. This channel MUST be buffered.
|
||||
Resp chan AuxSigJobResp
|
||||
|
||||
// Cancel is a channel that is closed by the caller if they wish to
|
||||
// abandon all pending sign jobs part of a single batch. This should
|
||||
// never be closed by the validator.
|
||||
Cancel <-chan struct{}
|
||||
}
|
||||
|
||||
// NewAuxSigJob creates a new AuxSigJob.
|
||||
func NewAuxSigJob(sigJob SignJob, keyRing CommitmentKeyRing, incoming bool,
|
||||
htlc AuxHtlcDescriptor, commitBlob fn.Option[tlv.Blob],
|
||||
htlcLeaf input.AuxTapLeaf, cancelChan <-chan struct{}) AuxSigJob {
|
||||
|
||||
return AuxSigJob{
|
||||
SignDesc: sigJob.SignDesc,
|
||||
BaseAuxJob: BaseAuxJob{
|
||||
OutputIndex: sigJob.OutputIndex,
|
||||
KeyRing: keyRing,
|
||||
HTLC: htlc,
|
||||
Incoming: incoming,
|
||||
CommitBlob: commitBlob,
|
||||
HtlcLeaf: htlcLeaf,
|
||||
},
|
||||
Resp: make(chan AuxSigJobResp, 1),
|
||||
Cancel: cancelChan,
|
||||
}
|
||||
}
|
||||
|
||||
// AuxSigJobResp is a struct that contains the result of a sign job.
|
||||
type AuxSigJobResp struct {
|
||||
// SigBlob is the signature blob that was generated for the HTLC. This
|
||||
// is an opaque TLV field that may contain the signature and other data.
|
||||
SigBlob fn.Option[tlv.Blob]
|
||||
|
||||
// HtlcIndex is the index of the HTLC that was signed.
|
||||
HtlcIndex uint64
|
||||
|
||||
// Err is the error that occurred when executing the specified
|
||||
// signature job. In the case that no error occurred, this value will
|
||||
// be nil.
|
||||
Err error
|
||||
}
|
||||
|
||||
// AuxVerifyJob is a struct that contains all the information needed to verify
|
||||
// an HTLC for custom channels.
|
||||
type AuxVerifyJob struct {
|
||||
// SigBlob is the signature blob that was generated for the HTLC. This
|
||||
// is an opaque TLV field that may contain the signature and other data.
|
||||
SigBlob fn.Option[tlv.Blob]
|
||||
|
||||
BaseAuxJob
|
||||
}
|
||||
|
||||
// NewAuxVerifyJob creates a new AuxVerifyJob.
|
||||
func NewAuxVerifyJob(sig fn.Option[tlv.Blob], keyRing CommitmentKeyRing,
|
||||
incoming bool, htlc AuxHtlcDescriptor, commitBlob fn.Option[tlv.Blob],
|
||||
htlcLeaf input.AuxTapLeaf) AuxVerifyJob {
|
||||
|
||||
return AuxVerifyJob{
|
||||
SigBlob: sig,
|
||||
BaseAuxJob: BaseAuxJob{
|
||||
KeyRing: keyRing,
|
||||
HTLC: htlc,
|
||||
Incoming: incoming,
|
||||
CommitBlob: commitBlob,
|
||||
HtlcLeaf: htlcLeaf,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AuxSigner is an interface that is used to sign and verify HTLCs for custom
|
||||
// channels. It is similar to the existing SigPool, but uses opaque blobs to
|
||||
// shuffle around signature information and other metadata.
|
||||
type AuxSigner interface {
|
||||
// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and
|
||||
// processes them asynchronously.
|
||||
SubmitSecondLevelSigBatch(chanState AuxChanState, commitTx *wire.MsgTx,
|
||||
sigJob []AuxSigJob) error
|
||||
|
||||
// PackSigs takes a series of aux signatures and packs them into a
|
||||
// single blob that can be sent alongside the CommitSig messages.
|
||||
PackSigs([]fn.Option[tlv.Blob]) fn.Result[fn.Option[tlv.Blob]]
|
||||
|
||||
// UnpackSigs takes a packed blob of signatures and returns the
|
||||
// original signatures for each HTLC, keyed by HTLC index.
|
||||
UnpackSigs(fn.Option[tlv.Blob]) fn.Result[[]fn.Option[tlv.Blob]]
|
||||
|
||||
// VerifySecondLevelSigs attempts to synchronously verify a batch of aux
|
||||
// sig jobs.
|
||||
VerifySecondLevelSigs(chanState AuxChanState, commitTx *wire.MsgTx,
|
||||
verifyJob []AuxVerifyJob) error
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
@@ -98,6 +99,26 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) {
|
||||
)
|
||||
}
|
||||
|
||||
// TaprootInternalKey may return the internal key for a MuSig2 funding output,
|
||||
// but only if this is actually a MuSig2 channel.
|
||||
func (s *ShimIntent) TaprootInternalKey() fn.Option[*btcec.PublicKey] {
|
||||
if !s.musig2 {
|
||||
return fn.None[*btcec.PublicKey]()
|
||||
}
|
||||
|
||||
// Similar to the existing p2wsh script, we'll always ensure the keys
|
||||
// are sorted before use. Since we're only interested in the internal
|
||||
// key, we don't need to take into account any tapscript root.
|
||||
//
|
||||
// We ignore the error here as this is only called after FundingOutput
|
||||
// is called.
|
||||
combinedKey, _, _, _ := musig2.AggregateKeys(
|
||||
[]*btcec.PublicKey{s.localKey.PubKey, s.remoteKey}, true,
|
||||
)
|
||||
|
||||
return fn.Some(combinedKey.PreTweakedKey)
|
||||
}
|
||||
|
||||
// Cancel allows the caller to cancel a funding Intent at any time. This will
|
||||
// return any resources such as coins back to the eligible pool to be used in
|
||||
// order channel fundings.
|
||||
|
||||
@@ -6,11 +6,14 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"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"
|
||||
)
|
||||
@@ -162,6 +165,13 @@ func (i *PsbtIntent) BindKeys(localKey *keychain.KeyDescriptor,
|
||||
i.State = PsbtOutputKnown
|
||||
}
|
||||
|
||||
// BindTapscriptRoot takes an optional tapscript root and binds it to the
|
||||
// underlying funding intent. This only applies to musig2 channels, and will be
|
||||
// used to make the musig2 funding output.
|
||||
func (i *PsbtIntent) BindTapscriptRoot(root fn.Option[chainhash.Hash]) {
|
||||
i.tapscriptRoot = root
|
||||
}
|
||||
|
||||
// FundingParams returns the parameters that are necessary to start funding the
|
||||
// channel output this intent was created for. It returns the P2WSH funding
|
||||
// address, the exact funding amount and a PSBT packet that contains exactly one
|
||||
@@ -208,7 +218,18 @@ func (i *PsbtIntent) FundingParams() (btcutil.Address, int64, *psbt.Packet,
|
||||
}
|
||||
}
|
||||
packet.UnsignedTx.TxOut = append(packet.UnsignedTx.TxOut, out)
|
||||
packet.Outputs = append(packet.Outputs, psbt.POutput{})
|
||||
|
||||
var pOut psbt.POutput
|
||||
|
||||
// If this is a MuSig2 channel, we also need to communicate the internal
|
||||
// key to the caller. Otherwise, they cannot verify the construction of
|
||||
// the P2TR output script.
|
||||
pOut.TaprootInternalKey = fn.MapOptionZ(
|
||||
i.TaprootInternalKey(), schnorr.SerializePubKey,
|
||||
)
|
||||
|
||||
packet.Outputs = append(packet.Outputs, pOut)
|
||||
|
||||
return addr, out.Value, packet, nil
|
||||
}
|
||||
|
||||
|
||||
+264
-52
@@ -6,7 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
@@ -760,6 +760,10 @@ type LightningChannel struct {
|
||||
// signatures, of which there may be hundreds.
|
||||
sigPool *SigPool
|
||||
|
||||
// auxSigner is a special signer used to obtain opaque signatures for
|
||||
// custom channel variants.
|
||||
auxSigner fn.Option[AuxSigner]
|
||||
|
||||
// Capacity is the total capacity of this channel.
|
||||
Capacity btcutil.Amount
|
||||
|
||||
@@ -821,6 +825,7 @@ type channelOpts struct {
|
||||
remoteNonce *musig2.Nonces
|
||||
|
||||
leafStore fn.Option[AuxLeafStore]
|
||||
auxSigner fn.Option[AuxSigner]
|
||||
|
||||
skipNonceInit bool
|
||||
}
|
||||
@@ -859,6 +864,13 @@ func WithLeafStore(store AuxLeafStore) ChannelOpt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuxSigner is used to specify a custom aux signer for the channel.
|
||||
func WithAuxSigner(signer AuxSigner) ChannelOpt {
|
||||
return func(o *channelOpts) {
|
||||
o.auxSigner = fn.Some[AuxSigner](signer)
|
||||
}
|
||||
}
|
||||
|
||||
// defaultChannelOpts returns the set of default options for a new channel.
|
||||
func defaultChannelOpts() *channelOpts {
|
||||
return &channelOpts{}
|
||||
@@ -911,6 +923,7 @@ func NewLightningChannel(signer input.Signer,
|
||||
lc := &LightningChannel{
|
||||
Signer: signer,
|
||||
leafStore: opts.leafStore,
|
||||
auxSigner: opts.auxSigner,
|
||||
sigPool: sigPool,
|
||||
currentHeight: localCommit.CommitHeight,
|
||||
commitChains: commitChains,
|
||||
@@ -2545,6 +2558,18 @@ type HtlcView struct {
|
||||
FeePerKw chainfee.SatPerKWeight
|
||||
}
|
||||
|
||||
// AuxOurUpdates returns the outgoing HTLCs as a read-only copy of
|
||||
// AuxHtlcDescriptors.
|
||||
func (v *HtlcView) AuxOurUpdates() []AuxHtlcDescriptor {
|
||||
return fn.Map(newAuxHtlcDescriptor, v.OurUpdates)
|
||||
}
|
||||
|
||||
// AuxTheirUpdates returns the incoming HTLCs as a read-only copy of
|
||||
// AuxHtlcDescriptors.
|
||||
func (v *HtlcView) AuxTheirUpdates() []AuxHtlcDescriptor {
|
||||
return fn.Map(newAuxHtlcDescriptor, v.TheirUpdates)
|
||||
}
|
||||
|
||||
// fetchHTLCView returns all the candidate HTLC updates which should be
|
||||
// considered for inclusion within a commitment based on the passed HTLC log
|
||||
// indexes.
|
||||
@@ -3064,7 +3089,8 @@ func processFeeUpdate(feeUpdate *paymentDescriptor, nextHeight uint64,
|
||||
func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
chanState *channeldb.OpenChannel, leaseExpiry uint32,
|
||||
remoteCommitView *commitment,
|
||||
leafStore fn.Option[AuxLeafStore]) ([]SignJob, chan struct{}, error) {
|
||||
leafStore fn.Option[AuxLeafStore]) ([]SignJob, []AuxSigJob,
|
||||
chan struct{}, error) {
|
||||
|
||||
var (
|
||||
isRemoteInitiator = !chanState.IsInitiator
|
||||
@@ -3084,6 +3110,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
numSigs := len(remoteCommitView.incomingHTLCs) +
|
||||
len(remoteCommitView.outgoingHTLCs)
|
||||
sigBatch := make([]SignJob, 0, numSigs)
|
||||
auxSigBatch := make([]AuxSigJob, 0, numSigs)
|
||||
|
||||
var err error
|
||||
cancelChan := make(chan struct{})
|
||||
@@ -3098,8 +3125,8 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
},
|
||||
).Unpack()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to fetch aux leaves: %w",
|
||||
err)
|
||||
return nil, nil, nil, fmt.Errorf("unable to fetch aux leaves: "+
|
||||
"%w", err)
|
||||
}
|
||||
|
||||
// For each outgoing and incoming HTLC, if the HTLC isn't considered a
|
||||
@@ -3148,12 +3175,9 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// TODO(roasbeef): hook up signer interface here (later commit
|
||||
// in this PR).
|
||||
|
||||
// Construct a full hash cache as we may be signing a segwit v1
|
||||
// sighash.
|
||||
txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex]
|
||||
@@ -3185,6 +3209,11 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
}
|
||||
|
||||
sigBatch = append(sigBatch, sigJob)
|
||||
|
||||
auxSigBatch = append(auxSigBatch, NewAuxSigJob(
|
||||
sigJob, *keyRing, true, newAuxHtlcDescriptor(&htlc),
|
||||
remoteCommitView.customBlob, auxLeaf, cancelChan,
|
||||
))
|
||||
}
|
||||
for _, htlc := range remoteCommitView.outgoingHTLCs {
|
||||
if HtlcIsDust(
|
||||
@@ -3228,7 +3257,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Construct a full hash cache as we may be signing a segwit v1
|
||||
@@ -3257,13 +3286,19 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
// If this is a taproot channel, then we'll need to set the
|
||||
// method type to ensure we generate a valid signature.
|
||||
if chanType.IsTaproot() {
|
||||
sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod //nolint:lll
|
||||
//nolint:lll
|
||||
sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod
|
||||
}
|
||||
|
||||
sigBatch = append(sigBatch, sigJob)
|
||||
|
||||
auxSigBatch = append(auxSigBatch, NewAuxSigJob(
|
||||
sigJob, *keyRing, false, newAuxHtlcDescriptor(&htlc),
|
||||
remoteCommitView.customBlob, auxLeaf, cancelChan,
|
||||
))
|
||||
}
|
||||
|
||||
return sigBatch, cancelChan, nil
|
||||
return sigBatch, auxSigBatch, cancelChan, nil
|
||||
}
|
||||
|
||||
// createCommitDiff will create a commit diff given a new pending commitment
|
||||
@@ -3272,7 +3307,8 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
// new commitment to the remote party. The commit diff returned contains all
|
||||
// information necessary for retransmission.
|
||||
func (lc *LightningChannel) createCommitDiff(newCommit *commitment,
|
||||
commitSig lnwire.Sig, htlcSigs []lnwire.Sig) *channeldb.CommitDiff {
|
||||
commitSig lnwire.Sig, htlcSigs []lnwire.Sig,
|
||||
auxSigs []fn.Option[tlv.Blob]) (*channeldb.CommitDiff, error) {
|
||||
|
||||
var (
|
||||
logUpdates []channeldb.LogUpdate
|
||||
@@ -3341,21 +3377,71 @@ func (lc *LightningChannel) createCommitDiff(newCommit *commitment,
|
||||
// disk.
|
||||
diskCommit := newCommit.toDiskCommit(lntypes.Remote)
|
||||
|
||||
return &channeldb.CommitDiff{
|
||||
Commitment: *diskCommit,
|
||||
CommitSig: &lnwire.CommitSig{
|
||||
ChanID: lnwire.NewChanIDFromOutPoint(
|
||||
lc.channelState.FundingOutpoint,
|
||||
),
|
||||
CommitSig: commitSig,
|
||||
HtlcSigs: htlcSigs,
|
||||
// We prepare the commit sig message to be sent to the remote party.
|
||||
commitSigMsg := &lnwire.CommitSig{
|
||||
ChanID: lnwire.NewChanIDFromOutPoint(
|
||||
lc.channelState.FundingOutpoint,
|
||||
),
|
||||
CommitSig: commitSig,
|
||||
HtlcSigs: htlcSigs,
|
||||
}
|
||||
|
||||
// Encode and check the size of the custom records now.
|
||||
auxCustomRecords, err := fn.MapOptionZ(
|
||||
lc.auxSigner,
|
||||
func(s AuxSigner) fn.Result[lnwire.CustomRecords] {
|
||||
blobOption, err := s.PackSigs(auxSigs).Unpack()
|
||||
if err != nil {
|
||||
return fn.Err[lnwire.CustomRecords](err)
|
||||
}
|
||||
|
||||
// We now serialize the commit sig message without the
|
||||
// custom records to make sure we have space for them.
|
||||
var buf bytes.Buffer
|
||||
err = commitSigMsg.Encode(&buf, 0)
|
||||
if err != nil {
|
||||
return fn.Err[lnwire.CustomRecords](err)
|
||||
}
|
||||
|
||||
// The number of available bytes is the max message size
|
||||
// minus the size of the message without the custom
|
||||
// records. We also subtract 8 bytes for encoding
|
||||
// overhead of the custom records (just some safety
|
||||
// padding).
|
||||
available := lnwire.MaxMsgBody - buf.Len() - 8
|
||||
|
||||
blob := blobOption.UnwrapOr(nil)
|
||||
if len(blob) > available {
|
||||
err = fmt.Errorf("aux sigs size %d exceeds "+
|
||||
"max allowed size of %d", len(blob),
|
||||
available)
|
||||
|
||||
return fn.Err[lnwire.CustomRecords](err)
|
||||
}
|
||||
|
||||
records, err := lnwire.ParseCustomRecords(blob)
|
||||
if err != nil {
|
||||
return fn.Err[lnwire.CustomRecords](err)
|
||||
}
|
||||
|
||||
return fn.Ok(records)
|
||||
},
|
||||
).Unpack()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error packing aux sigs: %w", err)
|
||||
}
|
||||
|
||||
commitSigMsg.CustomRecords = auxCustomRecords
|
||||
|
||||
return &channeldb.CommitDiff{
|
||||
Commitment: *diskCommit,
|
||||
CommitSig: commitSigMsg,
|
||||
LogUpdates: logUpdates,
|
||||
OpenedCircuitKeys: openCircuitKeys,
|
||||
ClosedCircuitKeys: closedCircuitKeys,
|
||||
AddAcks: ackAddRefs,
|
||||
SettleFailAcks: settleFailRefs,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getUnsignedAckedUpdates returns all remote log updates that we haven't
|
||||
@@ -3748,6 +3834,10 @@ type CommitSigs struct {
|
||||
// PartialSig is the musig2 partial signature for taproot commitment
|
||||
// transactions.
|
||||
PartialSig lnwire.OptPartialSigWithNonceTLV
|
||||
|
||||
// AuxSigBlob is the blob containing all the auxiliary signatures for
|
||||
// this new commitment state.
|
||||
AuxSigBlob tlv.Blob
|
||||
}
|
||||
|
||||
// NewCommitState wraps the various signatures needed to properly
|
||||
@@ -3772,6 +3862,8 @@ type NewCommitState struct {
|
||||
// any). The HTLC signatures are sorted according to the BIP 69 order of the
|
||||
// HTLC's on the commitment transaction. Finally, the new set of pending HTLCs
|
||||
// for the remote party's commitment are also returned.
|
||||
//
|
||||
//nolint:funlen
|
||||
func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
@@ -3864,15 +3956,37 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
if lc.channelState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = lc.channelState.ThawHeight
|
||||
}
|
||||
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(
|
||||
sigBatch, auxSigBatch, cancelChan, err := genRemoteHtlcSigJobs(
|
||||
keyRing, lc.channelState, leaseExpiry, newCommitView,
|
||||
lc.leafStore,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We'll need to send over the signatures to the remote party in the
|
||||
// order as they appear on the commitment transaction after BIP 69
|
||||
// sorting.
|
||||
slices.SortFunc(sigBatch, func(i, j SignJob) int {
|
||||
return int(i.OutputIndex - j.OutputIndex)
|
||||
})
|
||||
slices.SortFunc(auxSigBatch, func(i, j AuxSigJob) int {
|
||||
return int(i.OutputIndex - j.OutputIndex)
|
||||
})
|
||||
|
||||
lc.sigPool.SubmitSignBatch(sigBatch)
|
||||
|
||||
err = fn.MapOptionZ(lc.auxSigner, func(a AuxSigner) error {
|
||||
return a.SubmitSecondLevelSigBatch(
|
||||
NewAuxChanState(lc.channelState), newCommitView.txn,
|
||||
auxSigBatch,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error submitting second level sig "+
|
||||
"batch: %w", err)
|
||||
}
|
||||
|
||||
// While the jobs are being carried out, we'll Sign their version of
|
||||
// the new commitment transaction while we're waiting for the rest of
|
||||
// the HTLC signatures to be processed.
|
||||
@@ -3910,17 +4024,12 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// We'll need to send over the signatures to the remote party in the
|
||||
// order as they appear on the commitment transaction after BIP 69
|
||||
// sorting.
|
||||
sort.Slice(sigBatch, func(i, j int) bool {
|
||||
return sigBatch[i].OutputIndex < sigBatch[j].OutputIndex
|
||||
})
|
||||
|
||||
// With the jobs sorted, we'll now iterate through all the responses to
|
||||
// gather each of the signatures in order.
|
||||
// Iterate through all the responses to gather each of the signatures
|
||||
// in the order they were submitted.
|
||||
htlcSigs = make([]lnwire.Sig, 0, len(sigBatch))
|
||||
for _, htlcSigJob := range sigBatch {
|
||||
auxSigs := make([]fn.Option[tlv.Blob], 0, len(auxSigBatch))
|
||||
for i := range sigBatch {
|
||||
htlcSigJob := sigBatch[i]
|
||||
jobResp := <-htlcSigJob.Resp
|
||||
|
||||
// If an error occurred, then we'll cancel any other active
|
||||
@@ -3931,12 +4040,34 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
}
|
||||
|
||||
htlcSigs = append(htlcSigs, jobResp.Sig)
|
||||
|
||||
if lc.auxSigner.IsNone() {
|
||||
continue
|
||||
}
|
||||
|
||||
auxHtlcSigJob := auxSigBatch[i]
|
||||
auxJobResp := <-auxHtlcSigJob.Resp
|
||||
|
||||
// If an error occurred, then we'll cancel any other active
|
||||
// jobs.
|
||||
if auxJobResp.Err != nil {
|
||||
close(cancelChan)
|
||||
return nil, auxJobResp.Err
|
||||
}
|
||||
|
||||
auxSigs = append(auxSigs, auxJobResp.SigBlob)
|
||||
}
|
||||
|
||||
// As we're about to proposer a new commitment state for the remote
|
||||
// party, we'll write this pending state to disk before we exit, so we
|
||||
// can retransmit it if necessary.
|
||||
commitDiff := lc.createCommitDiff(newCommitView, sig, htlcSigs)
|
||||
commitDiff, err := lc.createCommitDiff(
|
||||
newCommitView, sig, htlcSigs, auxSigs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = lc.channelState.AppendRemoteCommitChain(commitDiff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -3950,11 +4081,18 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
// latest commitment update.
|
||||
lc.commitChains.Remote.addCommitment(newCommitView)
|
||||
|
||||
auxSigBlob, err := commitDiff.CommitSig.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to serialize aux sig blob: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
return &NewCommitState{
|
||||
CommitSigs: &CommitSigs{
|
||||
CommitSig: sig,
|
||||
HtlcSigs: htlcSigs,
|
||||
PartialSig: lnwire.MaybePartialSigWithNonce(partialSig),
|
||||
AuxSigBlob: auxSigBlob,
|
||||
},
|
||||
PendingHTLCs: commitDiff.Commitment.Htlcs,
|
||||
}, nil
|
||||
@@ -3966,8 +4104,8 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
// each time. After we receive the channel reestablish message, we learn the
|
||||
// nonce we need to use for the remote party. As a result, we need to generate
|
||||
// the partial signature again with the new nonce.
|
||||
func (lc *LightningChannel) resignMusigCommit(commitTx *wire.MsgTx,
|
||||
) (lnwire.OptPartialSigWithNonceTLV, error) {
|
||||
func (lc *LightningChannel) resignMusigCommit(
|
||||
commitTx *wire.MsgTx) (lnwire.OptPartialSigWithNonceTLV, error) {
|
||||
|
||||
remoteSession := lc.musigSessions.RemoteSession
|
||||
musig, err := remoteSession.SignCommit(commitTx)
|
||||
@@ -4172,13 +4310,23 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
||||
// If we signed this state, then we'll accumulate
|
||||
// another update to send over.
|
||||
case err == nil:
|
||||
customRecords, err := lnwire.ParseCustomRecords(
|
||||
newCommit.AuxSigBlob,
|
||||
)
|
||||
if err != nil {
|
||||
sErr := fmt.Errorf("error parsing aux "+
|
||||
"sigs: %w", err)
|
||||
return nil, nil, nil, sErr
|
||||
}
|
||||
|
||||
commitSig := &lnwire.CommitSig{
|
||||
ChanID: lnwire.NewChanIDFromOutPoint(
|
||||
lc.channelState.FundingOutpoint,
|
||||
),
|
||||
CommitSig: newCommit.CommitSig,
|
||||
HtlcSigs: newCommit.HtlcSigs,
|
||||
PartialSig: newCommit.PartialSig,
|
||||
CommitSig: newCommit.CommitSig,
|
||||
HtlcSigs: newCommit.HtlcSigs,
|
||||
PartialSig: newCommit.PartialSig,
|
||||
CustomRecords: customRecords,
|
||||
}
|
||||
|
||||
updates = append(updates, commitSig)
|
||||
@@ -4396,6 +4544,7 @@ func (lc *LightningChannel) computeView(view *HtlcView,
|
||||
// need this to determine which HTLCs are dust, and also the final fee
|
||||
// rate.
|
||||
view.FeePerKw = commitChain.tip().feePerKw
|
||||
view.NextHeight = nextHeight
|
||||
|
||||
// We evaluate the view at this stage, meaning settled and failed HTLCs
|
||||
// will remove their corresponding added HTLCs. The resulting filtered
|
||||
@@ -4471,7 +4620,8 @@ func (lc *LightningChannel) computeView(view *HtlcView,
|
||||
func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
localCommitmentView *commitment, keyRing *CommitmentKeyRing,
|
||||
htlcSigs []lnwire.Sig, leaseExpiry uint32,
|
||||
leafStore fn.Option[AuxLeafStore]) ([]VerifyJob, error) {
|
||||
leafStore fn.Option[AuxLeafStore], auxSigner fn.Option[AuxSigner],
|
||||
sigBlob fn.Option[tlv.Blob]) ([]VerifyJob, []AuxVerifyJob, error) {
|
||||
|
||||
var (
|
||||
isLocalInitiator = chanState.IsInitiator
|
||||
@@ -4490,6 +4640,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
numHtlcs := len(localCommitmentView.incomingHTLCs) +
|
||||
len(localCommitmentView.outgoingHTLCs)
|
||||
verifyJobs := make([]VerifyJob, 0, numHtlcs)
|
||||
auxVerifyJobs := make([]AuxVerifyJob, 0, numHtlcs)
|
||||
|
||||
diskCommit := localCommitmentView.toDiskCommit(lntypes.Local)
|
||||
auxResult, err := fn.MapOptionZ(
|
||||
@@ -4501,7 +4652,20 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
},
|
||||
).Unpack()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
|
||||
return nil, nil, fmt.Errorf("unable to fetch aux leaves: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
// If we have a sig blob, then we'll attempt to map that to individual
|
||||
// blobs for each HTLC we might need a signature for.
|
||||
auxHtlcSigs, err := fn.MapOptionZ(
|
||||
auxSigner, func(a AuxSigner) fn.Result[[]fn.Option[tlv.Blob]] {
|
||||
return a.UnpackSigs(sigBlob)
|
||||
},
|
||||
).Unpack()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error unpacking aux sigs: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
// We'll iterate through each output in the commitment transaction,
|
||||
@@ -4514,6 +4678,9 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
htlcIndex uint64
|
||||
sigHash func() ([]byte, error)
|
||||
sig input.Signature
|
||||
htlc *paymentDescriptor
|
||||
incoming bool
|
||||
auxLeaf input.AuxTapLeaf
|
||||
err error
|
||||
)
|
||||
|
||||
@@ -4523,10 +4690,12 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
// If this output index is found within the incoming HTLC
|
||||
// index, then this means that we need to generate an HTLC
|
||||
// success transaction in order to validate the signature.
|
||||
//nolint:lll
|
||||
case localCommitmentView.incomingHTLCIndex[outputIndex] != nil:
|
||||
htlc := localCommitmentView.incomingHTLCIndex[outputIndex]
|
||||
htlc = localCommitmentView.incomingHTLCIndex[outputIndex]
|
||||
|
||||
htlcIndex = htlc.HtlcIndex
|
||||
incoming = true
|
||||
|
||||
sigHash = func() ([]byte, error) {
|
||||
op := wire.OutPoint{
|
||||
@@ -4592,7 +4761,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
|
||||
// Make sure there are more signatures left.
|
||||
if i >= len(htlcSigs) {
|
||||
return nil, fmt.Errorf("not enough HTLC " +
|
||||
return nil, nil, fmt.Errorf("not enough HTLC " +
|
||||
"signatures")
|
||||
}
|
||||
|
||||
@@ -4608,15 +4777,16 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
// is valid.
|
||||
sig, err = htlcSigs[i].ToSignature()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
htlc.sig = sig
|
||||
|
||||
// Otherwise, if this is an outgoing HTLC, then we'll need to
|
||||
// generate a timeout transaction so we can verify the
|
||||
// signature presented.
|
||||
//nolint:lll
|
||||
case localCommitmentView.outgoingHTLCIndex[outputIndex] != nil:
|
||||
htlc := localCommitmentView.outgoingHTLCIndex[outputIndex]
|
||||
htlc = localCommitmentView.outgoingHTLCIndex[outputIndex]
|
||||
|
||||
htlcIndex = htlc.HtlcIndex
|
||||
|
||||
@@ -4687,7 +4857,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
|
||||
// Make sure there are more signatures left.
|
||||
if i >= len(htlcSigs) {
|
||||
return nil, fmt.Errorf("not enough HTLC " +
|
||||
return nil, nil, fmt.Errorf("not enough HTLC " +
|
||||
"signatures")
|
||||
}
|
||||
|
||||
@@ -4703,7 +4873,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
// is valid.
|
||||
sig, err = htlcSigs[i].ToSignature()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
htlc.sig = sig
|
||||
@@ -4719,17 +4889,40 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
SigHash: sigHash,
|
||||
})
|
||||
|
||||
if len(auxHtlcSigs) > i {
|
||||
auxSig := auxHtlcSigs[i]
|
||||
auxVerifyJob := NewAuxVerifyJob(
|
||||
auxSig, *keyRing, incoming,
|
||||
newAuxHtlcDescriptor(htlc),
|
||||
localCommitmentView.customBlob, auxLeaf,
|
||||
)
|
||||
|
||||
if htlc.CustomRecords == nil {
|
||||
htlc.CustomRecords = make(lnwire.CustomRecords)
|
||||
}
|
||||
|
||||
// As this HTLC has a custom signature associated with
|
||||
// it, store it in the custom records map so we can
|
||||
// write to disk later.
|
||||
sigType := htlcCustomSigType.TypeVal()
|
||||
htlc.CustomRecords[uint64(sigType)] = auxSig.UnwrapOr(
|
||||
nil,
|
||||
)
|
||||
|
||||
auxVerifyJobs = append(auxVerifyJobs, auxVerifyJob)
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// If we received a number of HTLC signatures that doesn't match our
|
||||
// commitment, we'll return an error now.
|
||||
if len(htlcSigs) != i {
|
||||
return nil, fmt.Errorf("number of htlc sig mismatch. "+
|
||||
return nil, nil, fmt.Errorf("number of htlc sig mismatch. "+
|
||||
"Expected %v sigs, got %v", i, len(htlcSigs))
|
||||
}
|
||||
|
||||
return verifyJobs, nil
|
||||
return verifyJobs, auxVerifyJobs, nil
|
||||
}
|
||||
|
||||
// InvalidCommitSigError is a struct that implements the error interface to
|
||||
@@ -4888,6 +5081,11 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
|
||||
localCommitmentView.ourBalance, localCommitmentView.theirBalance,
|
||||
lnutils.SpewLogClosure(localCommitmentView.txn))
|
||||
|
||||
var auxSigBlob fn.Option[tlv.Blob]
|
||||
if commitSigs.AuxSigBlob != nil {
|
||||
auxSigBlob = fn.Some(commitSigs.AuxSigBlob)
|
||||
}
|
||||
|
||||
// As an optimization, we'll generate a series of jobs for the worker
|
||||
// pool to verify each of the HTLC signatures presented. Once
|
||||
// generated, we'll submit these jobs to the worker pool.
|
||||
@@ -4895,9 +5093,10 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
|
||||
if lc.channelState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = lc.channelState.ThawHeight
|
||||
}
|
||||
verifyJobs, err := genHtlcSigValidationJobs(
|
||||
verifyJobs, auxVerifyJobs, err := genHtlcSigValidationJobs(
|
||||
lc.channelState, localCommitmentView, keyRing,
|
||||
commitSigs.HtlcSigs, leaseExpiry, lc.leafStore,
|
||||
commitSigs.HtlcSigs, leaseExpiry, lc.leafStore, lc.auxSigner,
|
||||
auxSigBlob,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -5050,6 +5249,18 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know all the normal sigs are valid, we'll also verify
|
||||
// the aux jobs, if any exist.
|
||||
err = fn.MapOptionZ(lc.auxSigner, func(a AuxSigner) error {
|
||||
return a.VerifySecondLevelSigs(
|
||||
NewAuxChanState(lc.channelState), localCommitTx,
|
||||
auxVerifyJobs,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to validate aux sigs: %w", err)
|
||||
}
|
||||
|
||||
// The signature checks out, so we can now add the new commitment to
|
||||
// our local commitment chain. For regular channels, we can just
|
||||
// serialize the ECDSA sig. For taproot channels, we'll serialize the
|
||||
@@ -5782,8 +5993,9 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
|
||||
defer lc.Unlock()
|
||||
|
||||
if htlc.ID != lc.updateLogs.Remote.htlcCounter {
|
||||
return 0, fmt.Errorf("ID %d on HTLC add does not match expected next "+
|
||||
"ID %d", htlc.ID, lc.updateLogs.Remote.htlcCounter)
|
||||
return 0, fmt.Errorf("ID %d on HTLC add does not match "+
|
||||
"expected next ID %d", htlc.ID,
|
||||
lc.updateLogs.Remote.htlcCounter)
|
||||
}
|
||||
|
||||
pd := &paymentDescriptor{
|
||||
|
||||
+173
-5
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -702,6 +703,68 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) {
|
||||
require.NoError(t, err, "unable to receive bob's commitment")
|
||||
}
|
||||
|
||||
// TestCommitHTLCSigCustomRecordSize asserts that custom records produced for
|
||||
// a commitment_signed message are properly limited in size.
|
||||
func TestCommitHTLCSigCustomRecordSize(t *testing.T) {
|
||||
aliceChannel, bobChannel, err := CreateTestChannels(
|
||||
t, channeldb.SimpleTaprootFeatureBit|
|
||||
channeldb.TapscriptRootBit,
|
||||
)
|
||||
require.NoError(t, err, "unable to create test channels")
|
||||
|
||||
const (
|
||||
htlcAmt = lnwire.MilliSatoshi(20000000)
|
||||
numHtlcs = 2
|
||||
)
|
||||
|
||||
largeRecords := lnwire.CustomRecords{
|
||||
lnwire.MinCustomRecordsTlvType: bytes.Repeat([]byte{0}, 65_500),
|
||||
}
|
||||
largeBlob, err := largeRecords.Serialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
aliceChannel.auxSigner.WhenSome(func(a AuxSigner) {
|
||||
mockSigner, ok := a.(*MockAuxSigner)
|
||||
require.True(t, ok, "expected MockAuxSigner")
|
||||
|
||||
// Replace the default PackSigs implementation to return a
|
||||
// large custom records blob.
|
||||
mockSigner.ExpectedCalls = fn.Filter(func(c *mock.Call) bool {
|
||||
return c.Method != "PackSigs"
|
||||
}, mockSigner.ExpectedCalls)
|
||||
mockSigner.On("PackSigs", mock.Anything).
|
||||
Return(fn.Ok(fn.Some(largeBlob)))
|
||||
})
|
||||
|
||||
// Add HTLCs with identical payment hashes and amounts, but descending
|
||||
// CLTV values. We will expect the signatures to appear in the reverse
|
||||
// order that the HTLCs are added due to the commitment sorting.
|
||||
for i := 0; i < numHtlcs; i++ {
|
||||
var (
|
||||
preimage lntypes.Preimage
|
||||
hash = preimage.Hash()
|
||||
)
|
||||
|
||||
htlc := &lnwire.UpdateAddHTLC{
|
||||
ID: uint64(i),
|
||||
PaymentHash: hash,
|
||||
Amount: htlcAmt,
|
||||
Expiry: uint32(numHtlcs - i),
|
||||
}
|
||||
|
||||
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
||||
t.Fatalf("alice unable to add htlc: %v", err)
|
||||
}
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("bob unable to receive htlc: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We expect an error because of the large custom records blob.
|
||||
_, err = aliceChannel.SignNextCommitment()
|
||||
require.ErrorContains(t, err, "exceeds max allowed size")
|
||||
}
|
||||
|
||||
// TestCooperativeChannelClosure checks that the coop close process finishes
|
||||
// with an agreement from both parties, and that the final balances of the
|
||||
// close tx check out.
|
||||
@@ -3046,6 +3109,10 @@ func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
|
||||
return channelNew, nil
|
||||
}
|
||||
|
||||
// testChanSyncOweCommitment tests that if Bob restarts (and then Alice) before
|
||||
// he receives Alice's CommitSig message, then Alice concludes that she needs
|
||||
// to re-send the CommitDiff. After the diff has been sent, both nodes should
|
||||
// resynchronize and be able to complete the dangling commit.
|
||||
func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
@@ -3210,8 +3277,10 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
|
||||
len(commitSigMsg.HtlcSigs))
|
||||
}
|
||||
for i, htlcSig := range commitSigMsg.HtlcSigs {
|
||||
if !bytes.Equal(htlcSig.RawBytes(),
|
||||
aliceNewCommit.HtlcSigs[i].RawBytes()) {
|
||||
if !bytes.Equal(
|
||||
htlcSig.RawBytes(),
|
||||
aliceNewCommit.HtlcSigs[i].RawBytes(),
|
||||
) {
|
||||
|
||||
t.Fatalf("htlc sig msgs don't match: "+
|
||||
"expected %v got %v",
|
||||
@@ -3389,6 +3458,100 @@ func TestChanSyncOweCommitment(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type testSigBlob struct {
|
||||
BlobInt tlv.RecordT[tlv.TlvType65634, uint16]
|
||||
}
|
||||
|
||||
// TestChanSyncOweCommitmentAuxSigner tests that when one party owes a
|
||||
// signature after a channel reest, if an aux signer is present, then the
|
||||
// signature message sent includes the additional aux sigs as extra data.
|
||||
func TestChanSyncOweCommitmentAuxSigner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
chanType := channeldb.SingleFunderTweaklessBit |
|
||||
channeldb.AnchorOutputsBit | channeldb.SimpleTaprootFeatureBit |
|
||||
channeldb.TapscriptRootBit
|
||||
|
||||
aliceChannel, bobChannel, err := CreateTestChannels(t, chanType)
|
||||
require.NoError(t, err, "unable to create test channels")
|
||||
|
||||
// We'll now manually attach an aux signer to Alice's channel.
|
||||
auxSigner := &MockAuxSigner{}
|
||||
aliceChannel.auxSigner = fn.Some[AuxSigner](auxSigner)
|
||||
|
||||
var fakeOnionBlob [lnwire.OnionPacketSize]byte
|
||||
copy(
|
||||
fakeOnionBlob[:],
|
||||
bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize),
|
||||
)
|
||||
|
||||
// To kick things off, we'll have Alice send a single HTLC to Bob.
|
||||
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
|
||||
var bobPreimage [32]byte
|
||||
copy(bobPreimage[:], bytes.Repeat([]byte{0}, 32))
|
||||
rHash := sha256.Sum256(bobPreimage[:])
|
||||
h := &lnwire.UpdateAddHTLC{
|
||||
PaymentHash: rHash,
|
||||
Amount: htlcAmt,
|
||||
Expiry: uint32(10),
|
||||
OnionBlob: fakeOnionBlob,
|
||||
}
|
||||
|
||||
_, err = aliceChannel.AddHTLC(h, nil)
|
||||
require.NoError(t, err, "unable to recv bob's htlc: %v", err)
|
||||
|
||||
// We'll set up the mock to expect calls to PackSigs and also
|
||||
// SubmitSubmitSecondLevelSigBatch.
|
||||
var sigBlobBuf bytes.Buffer
|
||||
sigBlob := testSigBlob{
|
||||
BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5),
|
||||
}
|
||||
tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record())
|
||||
require.NoError(t, err, "unable to create tlv stream")
|
||||
require.NoError(t, tlvStream.Encode(&sigBlobBuf))
|
||||
|
||||
auxSigner.On(
|
||||
"SubmitSecondLevelSigBatch", mock.Anything, mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(nil).Twice()
|
||||
auxSigner.On(
|
||||
"PackSigs", mock.Anything,
|
||||
).Return(
|
||||
fn.Ok(fn.Some(sigBlobBuf.Bytes())), nil,
|
||||
)
|
||||
|
||||
_, err = aliceChannel.SignNextCommitment()
|
||||
require.NoError(t, err, "unable to sign commitment")
|
||||
|
||||
_, err = aliceChannel.GenMusigNonces()
|
||||
require.NoError(t, err, "unable to generate musig nonces")
|
||||
|
||||
// Next we'll simulate a restart, by having Bob send over a chan sync
|
||||
// message to Alice.
|
||||
bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg()
|
||||
require.NoError(t, err, "unable to produce chan sync msg")
|
||||
|
||||
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(
|
||||
bobSyncMsg,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, aliceMsgsToSend, 2)
|
||||
|
||||
// The first message should be an update add HTLC.
|
||||
require.IsType(t, &lnwire.UpdateAddHTLC{}, aliceMsgsToSend[0])
|
||||
|
||||
// The second should be a commit sig message.
|
||||
sigMsg, ok := aliceMsgsToSend[1].(*lnwire.CommitSig)
|
||||
require.True(t, ok)
|
||||
require.True(t, sigMsg.PartialSig.IsSome())
|
||||
|
||||
// The signature should have the CustomRecords field set.
|
||||
require.NotEmpty(t, sigMsg.CustomRecords)
|
||||
}
|
||||
|
||||
func testChanSyncOweCommitmentPendingRemote(t *testing.T,
|
||||
chanType channeldb.ChannelType) {
|
||||
|
||||
@@ -3398,7 +3561,10 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T,
|
||||
require.NoError(t, err, "unable to create test channels")
|
||||
|
||||
var fakeOnionBlob [lnwire.OnionPacketSize]byte
|
||||
copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize))
|
||||
copy(
|
||||
fakeOnionBlob[:],
|
||||
bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize),
|
||||
)
|
||||
|
||||
// We'll start off the scenario where Bob send two htlcs to Alice in a
|
||||
// single state update.
|
||||
@@ -3437,7 +3603,9 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T,
|
||||
|
||||
// Next, Alice settles the HTLCs from Bob in distinct state updates.
|
||||
for i := 0; i < numHtlcs; i++ {
|
||||
err = aliceChannel.SettleHTLC(preimages[i], uint64(i), nil, nil, nil)
|
||||
err = aliceChannel.SettleHTLC(
|
||||
preimages[i], uint64(i), nil, nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle htlc: %v", err)
|
||||
}
|
||||
@@ -3727,7 +3895,7 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) {
|
||||
}
|
||||
|
||||
// TestChanSyncOweRevocation tests that if Bob restarts (and then Alice) before
|
||||
// he receiver's Alice's RevokeAndAck message, then Alice concludes that she
|
||||
// he received Alice's RevokeAndAck message, then Alice concludes that she
|
||||
// needs to re-send the RevokeAndAck. After the revocation has been sent, both
|
||||
// nodes should be able to successfully complete another state transition.
|
||||
func TestChanSyncOweRevocation(t *testing.T) {
|
||||
|
||||
@@ -67,4 +67,8 @@ type Config struct {
|
||||
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxLeafStore fn.Option[AuxLeafStore]
|
||||
|
||||
// AuxSigner is an optional signer that can be used to sign auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxSigner fn.Option[AuxSigner]
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -441,3 +442,54 @@ func (*MockAuxLeafStore) ApplyHtlcView(
|
||||
|
||||
return fn.Ok(fn.None[tlv.Blob]())
|
||||
}
|
||||
|
||||
// MockAuxSigner is a mock implementation of the AuxSigner interface.
|
||||
type MockAuxSigner struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and
|
||||
// processes them asynchronously.
|
||||
func (a *MockAuxSigner) SubmitSecondLevelSigBatch(chanState AuxChanState,
|
||||
tx *wire.MsgTx, jobs []AuxSigJob) error {
|
||||
|
||||
args := a.Called(chanState, tx, jobs)
|
||||
|
||||
// While we return, we'll also send back an instant response for the
|
||||
// set of jobs.
|
||||
for _, sigJob := range jobs {
|
||||
sigJob.Resp <- AuxSigJobResp{}
|
||||
}
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// PackSigs takes a series of aux signatures and packs them into a
|
||||
// single blob that can be sent alongside the CommitSig messages.
|
||||
func (a *MockAuxSigner) PackSigs(
|
||||
sigs []fn.Option[tlv.Blob]) fn.Result[fn.Option[tlv.Blob]] {
|
||||
|
||||
args := a.Called(sigs)
|
||||
|
||||
return args.Get(0).(fn.Result[fn.Option[tlv.Blob]])
|
||||
}
|
||||
|
||||
// UnpackSigs takes a packed blob of signatures and returns the
|
||||
// original signatures for each HTLC, keyed by HTLC index.
|
||||
func (a *MockAuxSigner) UnpackSigs(
|
||||
sigs fn.Option[tlv.Blob]) fn.Result[[]fn.Option[tlv.Blob]] {
|
||||
|
||||
args := a.Called(sigs)
|
||||
|
||||
return args.Get(0).(fn.Result[[]fn.Option[tlv.Blob]])
|
||||
}
|
||||
|
||||
// VerifySecondLevelSigs attempts to synchronously verify a batch of aux
|
||||
// sig jobs.
|
||||
func (a *MockAuxSigner) VerifySecondLevelSigs(chanState AuxChanState,
|
||||
tx *wire.MsgTx, jobs []AuxVerifyJob) error {
|
||||
|
||||
args := a.Called(chanState, tx, jobs)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
+90
-6
@@ -11,6 +11,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"
|
||||
@@ -25,7 +26,7 @@ type CommitmentType int
|
||||
const (
|
||||
// CommitmentTypeLegacy is the legacy commitment format with a tweaked
|
||||
// to_remote key.
|
||||
CommitmentTypeLegacy = iota
|
||||
CommitmentTypeLegacy CommitmentType = iota
|
||||
|
||||
// CommitmentTypeTweakless is a newer commitment format where the
|
||||
// to_remote key is static.
|
||||
@@ -100,6 +101,28 @@ func (c CommitmentType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// ReservationState is a type that represents the current state of a channel
|
||||
// reservation within the funding workflow.
|
||||
type ReservationState int
|
||||
|
||||
const (
|
||||
// WaitingToSend is the state either the funder/fundee is in after
|
||||
// creating a reservation, but hasn't sent a message yet.
|
||||
WaitingToSend ReservationState = iota
|
||||
|
||||
// SentOpenChannel is the state the funder is in after sending the
|
||||
// OpenChannel message.
|
||||
SentOpenChannel
|
||||
|
||||
// SentAcceptChannel is the state the fundee is in after sending the
|
||||
// AcceptChannel message.
|
||||
SentAcceptChannel
|
||||
|
||||
// SentFundingCreated is the state the funder is in after sending the
|
||||
// FundingCreated message.
|
||||
SentFundingCreated
|
||||
)
|
||||
|
||||
// ChannelContribution is the primary constituent of the funding workflow
|
||||
// within lnwallet. Each side first exchanges their respective contributions
|
||||
// along with channel specific parameters like the min fee/KB. Once
|
||||
@@ -223,6 +246,8 @@ type ChannelReservation struct {
|
||||
nextRevocationKeyLoc keychain.KeyLocator
|
||||
|
||||
musigSessions *MusigPairSession
|
||||
|
||||
state ReservationState
|
||||
}
|
||||
|
||||
// NewChannelReservation creates a new channel reservation. This function is
|
||||
@@ -459,6 +484,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
reservationID: id,
|
||||
wallet: wallet,
|
||||
chanFunder: req.ChanFunder,
|
||||
state: WaitingToSend,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -470,6 +496,22 @@ func (r *ChannelReservation) AddAlias(scid lnwire.ShortChannelID) {
|
||||
r.partialState.ShortChannelID = scid
|
||||
}
|
||||
|
||||
// SetState sets the ReservationState.
|
||||
func (r *ChannelReservation) SetState(state ReservationState) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
r.state = state
|
||||
}
|
||||
|
||||
// State returns the current ReservationState.
|
||||
func (r *ChannelReservation) State() ReservationState {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
|
||||
return r.state
|
||||
}
|
||||
|
||||
// SetNumConfsRequired sets the number of confirmations that are required for
|
||||
// the ultimate funding transaction before the channel can be considered open.
|
||||
// This is distinct from the main reservation workflow as it allows
|
||||
@@ -610,12 +652,15 @@ func (r *ChannelReservation) IsCannedShim() bool {
|
||||
}
|
||||
|
||||
// ProcessPsbt continues a previously paused funding flow that involves PSBT to
|
||||
// construct the funding transaction. This method can be called once the PSBT is
|
||||
// finalized and the signed transaction is available.
|
||||
func (r *ChannelReservation) ProcessPsbt() error {
|
||||
// construct the funding transaction. This method can be called once the PSBT
|
||||
// is finalized and the signed transaction is available.
|
||||
func (r *ChannelReservation) ProcessPsbt(
|
||||
auxFundingDesc fn.Option[AuxFundingDesc]) error {
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
r.wallet.msgChan <- &continueContributionMsg{
|
||||
auxFundingDesc: auxFundingDesc,
|
||||
pendingFundingID: r.reservationID,
|
||||
err: errChan,
|
||||
}
|
||||
@@ -717,8 +762,10 @@ func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Sc
|
||||
// available via the .OurSignatures() method. As this method should only be
|
||||
// called as a response to a single funder channel, only a commitment signature
|
||||
// will be populated.
|
||||
func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint,
|
||||
commitSig input.Signature) (*channeldb.OpenChannel, error) {
|
||||
func (r *ChannelReservation) CompleteReservationSingle(
|
||||
fundingPoint *wire.OutPoint, commitSig input.Signature,
|
||||
auxFundingDesc fn.Option[AuxFundingDesc]) (*channeldb.OpenChannel,
|
||||
error) {
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
completeChan := make(chan *channeldb.OpenChannel, 1)
|
||||
@@ -728,6 +775,7 @@ func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoi
|
||||
fundingOutpoint: fundingPoint,
|
||||
theirCommitmentSig: commitSig,
|
||||
completeChan: completeChan,
|
||||
auxFundingDesc: auxFundingDesc,
|
||||
err: errChan,
|
||||
}
|
||||
|
||||
@@ -813,6 +861,42 @@ func (r *ChannelReservation) Cancel() error {
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// ChanState the current open channel state.
|
||||
func (r *ChannelReservation) ChanState() *channeldb.OpenChannel {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
|
||||
return r.partialState
|
||||
}
|
||||
|
||||
// CommitmentKeyRings returns the local+remote key ring used for the very first
|
||||
// commitment transaction both parties.
|
||||
//
|
||||
//nolint:lll
|
||||
func (r *ChannelReservation) CommitmentKeyRings() lntypes.Dual[CommitmentKeyRing] {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
|
||||
chanType := r.partialState.ChanType
|
||||
ourChanCfg := r.ourContribution.ChannelConfig
|
||||
theirChanCfg := r.theirContribution.ChannelConfig
|
||||
|
||||
localKeys := DeriveCommitmentKeys(
|
||||
r.ourContribution.FirstCommitmentPoint, lntypes.Local, chanType,
|
||||
ourChanCfg, theirChanCfg,
|
||||
)
|
||||
|
||||
remoteKeys := DeriveCommitmentKeys(
|
||||
r.theirContribution.FirstCommitmentPoint, lntypes.Remote,
|
||||
chanType, ourChanCfg, theirChanCfg,
|
||||
)
|
||||
|
||||
return lntypes.Dual[CommitmentKeyRing]{
|
||||
Local: *localKeys,
|
||||
Remote: *remoteKeys,
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyConstraints is a helper function that can be used to check the sanity
|
||||
// of various channel constraints.
|
||||
func VerifyConstraints(bounds *channeldb.ChannelStateBounds,
|
||||
|
||||
+11
-10
@@ -45,17 +45,17 @@ type VerifyJob struct {
|
||||
// party's update log.
|
||||
HtlcIndex uint64
|
||||
|
||||
// Cancel is a channel that should be closed if the caller wishes to
|
||||
// Cancel is a channel that is closed by the caller if they wish to
|
||||
// cancel all pending verification jobs part of a single batch. This
|
||||
// channel is to be closed in the case that a single signature in a
|
||||
// batch has been returned as invalid, as there is no need to verify
|
||||
// the remainder of the signatures.
|
||||
Cancel chan struct{}
|
||||
// channel is closed in the case that a single signature in a batch has
|
||||
// been returned as invalid, as there is no need to verify the remainder
|
||||
// of the signatures.
|
||||
Cancel <-chan struct{}
|
||||
|
||||
// ErrResp is the channel that the result of the signature verification
|
||||
// is to be sent over. In the see that the signature is valid, a nil
|
||||
// error will be passed. Otherwise, a concrete error detailing the
|
||||
// issue will be passed.
|
||||
// issue will be passed. This channel MUST be buffered.
|
||||
ErrResp chan *HtlcIndexErr
|
||||
}
|
||||
|
||||
@@ -86,12 +86,13 @@ type SignJob struct {
|
||||
// transaction being signed.
|
||||
OutputIndex int32
|
||||
|
||||
// Cancel is a channel that should be closed if the caller wishes to
|
||||
// abandon all pending sign jobs part of a single batch.
|
||||
Cancel chan struct{}
|
||||
// Cancel is a channel that is closed by the caller if they wish to
|
||||
// abandon all pending sign jobs part of a single batch. This should
|
||||
// never be closed by the validator.
|
||||
Cancel <-chan struct{}
|
||||
|
||||
// Resp is the channel that the response to this particular SignJob
|
||||
// will be sent over.
|
||||
// will be sent over. This channel MUST be buffered.
|
||||
//
|
||||
// TODO(roasbeef): actually need to allow caller to set, need to retain
|
||||
// order mark commit sig as special
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
@@ -940,6 +941,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
fundingPoint := aliceChanReservation.FundingOutpoint()
|
||||
_, err = bobChanReservation.CompleteReservationSingle(
|
||||
fundingPoint, aliceCommitSig,
|
||||
fn.None[lnwallet.AuxFundingDesc](),
|
||||
)
|
||||
require.NoError(t, err, "bob unable to consume single reservation")
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
@@ -21,6 +22,8 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/shachain"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -369,9 +372,13 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
|
||||
|
||||
// TODO(roasbeef): make mock version of pre-image store
|
||||
|
||||
auxSigner := NewDefaultAuxSignerMock(t)
|
||||
|
||||
alicePool := NewSigPool(1, aliceSigner)
|
||||
channelAlice, err := NewLightningChannel(
|
||||
aliceSigner, aliceChannelState, alicePool,
|
||||
WithLeafStore(&MockAuxLeafStore{}),
|
||||
WithAuxSigner(auxSigner),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -386,6 +393,8 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
|
||||
bobPool := NewSigPool(1, bobSigner)
|
||||
channelBob, err := NewLightningChannel(
|
||||
bobSigner, bobChannelState, bobPool,
|
||||
WithLeafStore(&MockAuxLeafStore{}),
|
||||
WithAuxSigner(auxSigner),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -586,3 +595,38 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDefaultAuxSignerMock(t *testing.T) *MockAuxSigner {
|
||||
auxSigner := &MockAuxSigner{}
|
||||
|
||||
type testSigBlob struct {
|
||||
BlobInt tlv.RecordT[tlv.TlvType65634, uint16]
|
||||
}
|
||||
|
||||
var sigBlobBuf bytes.Buffer
|
||||
sigBlob := testSigBlob{
|
||||
BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5),
|
||||
}
|
||||
tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record())
|
||||
require.NoError(t, err, "unable to create tlv stream")
|
||||
require.NoError(t, tlvStream.Encode(&sigBlobBuf))
|
||||
|
||||
auxSigner.On(
|
||||
"SubmitSecondLevelSigBatch", mock.Anything, mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(nil)
|
||||
auxSigner.On(
|
||||
"PackSigs", mock.Anything,
|
||||
).Return(fn.Ok(fn.Some(sigBlobBuf.Bytes())))
|
||||
auxSigner.On(
|
||||
"UnpackSigs", mock.Anything,
|
||||
).Return(fn.Ok([]fn.Option[tlv.Blob]{
|
||||
fn.Some(sigBlobBuf.Bytes()),
|
||||
}))
|
||||
auxSigner.On(
|
||||
"VerifySecondLevelSigs", mock.Anything, mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(nil)
|
||||
|
||||
return auxSigner
|
||||
}
|
||||
@@ -1018,9 +1018,12 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp
|
||||
tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2,
|
||||
}, nil)
|
||||
|
||||
auxSigner := NewDefaultAuxSignerMock(t)
|
||||
remotePool := NewSigPool(1, remoteSigner)
|
||||
channelRemote, err := NewLightningChannel(
|
||||
remoteSigner, remoteChannelState, remotePool,
|
||||
WithLeafStore(&MockAuxLeafStore{}),
|
||||
WithAuxSigner(auxSigner),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, remotePool.Start())
|
||||
@@ -1028,6 +1031,8 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp
|
||||
localPool := NewSigPool(1, localSigner)
|
||||
channelLocal, err := NewLightningChannel(
|
||||
localSigner, localChannelState, localPool,
|
||||
WithLeafStore(&MockAuxLeafStore{}),
|
||||
WithAuxSigner(auxSigner),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, localPool.Start())
|
||||
|
||||
+138
-28
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/shachain"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -90,6 +91,33 @@ func (p *PsbtFundingRequired) Error() string {
|
||||
return ErrPsbtFundingRequired.Error()
|
||||
}
|
||||
|
||||
// AuxFundingDesc stores a series of attributes that may be used to modify the
|
||||
// way the channel funding occurs. This struct contains information that can
|
||||
// only be derived once both sides have received and sent their contributions
|
||||
// to the channel (keys, etc.).
|
||||
type AuxFundingDesc struct {
|
||||
// CustomFundingBlob is a custom blob that'll be stored in the database
|
||||
// within the OpenChannel struct. This should represent information
|
||||
// static to the channel lifetime.
|
||||
CustomFundingBlob tlv.Blob
|
||||
|
||||
// CustomLocalCommitBlob is a custom blob that'll be stored in the
|
||||
// first commitment entry for the local party.
|
||||
CustomLocalCommitBlob tlv.Blob
|
||||
|
||||
// CustomRemoteCommitBlob is a custom blob that'll be stored in the
|
||||
// first commitment entry for the remote party.
|
||||
CustomRemoteCommitBlob tlv.Blob
|
||||
|
||||
// LocalInitAuxLeaves is the set of aux leaves that'll be used for our
|
||||
// very first commitment state.
|
||||
LocalInitAuxLeaves CommitAuxLeaves
|
||||
|
||||
// RemoteInitAuxLeaves is the set of aux leaves that'll be used for the
|
||||
// very first commitment state for the remote party.
|
||||
RemoteInitAuxLeaves CommitAuxLeaves
|
||||
}
|
||||
|
||||
// InitFundingReserveMsg is the first message sent to initiate the workflow
|
||||
// required to open a payment channel with a remote peer. The initial required
|
||||
// parameters are configurable across channels. These parameters are to be
|
||||
@@ -211,9 +239,8 @@ 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 is an optional tapscript root that if provided, will
|
||||
// be used to create the combined key for musig2 based channels.
|
||||
TapscriptRoot fn.Option[chainhash.Hash]
|
||||
|
||||
// err is a channel in which all errors will be sent across. Will be
|
||||
@@ -251,7 +278,6 @@ type fundingReserveCancelMsg struct {
|
||||
type addContributionMsg struct {
|
||||
pendingFundingID uint64
|
||||
|
||||
// TODO(roasbeef): Should also carry SPV proofs in we're in SPV mode
|
||||
contribution *ChannelContribution
|
||||
|
||||
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
|
||||
@@ -264,6 +290,10 @@ type addContributionMsg struct {
|
||||
type continueContributionMsg struct {
|
||||
pendingFundingID uint64
|
||||
|
||||
// auxFundingDesc is an optional descriptor that contains information
|
||||
// about the custom channel funding flow.
|
||||
auxFundingDesc fn.Option[AuxFundingDesc]
|
||||
|
||||
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
|
||||
err chan error
|
||||
}
|
||||
@@ -319,6 +349,10 @@ type addCounterPartySigsMsg struct {
|
||||
type addSingleFunderSigsMsg struct {
|
||||
pendingFundingID uint64
|
||||
|
||||
// auxFundingDesc is an optional descriptor that contains information
|
||||
// about the custom channel funding flow.
|
||||
auxFundingDesc fn.Option[AuxFundingDesc]
|
||||
|
||||
// fundingOutpoint is the outpoint of the completed funding
|
||||
// transaction as assembled by the workflow initiator.
|
||||
fundingOutpoint *wire.OutPoint
|
||||
@@ -422,8 +456,6 @@ type LightningWallet struct {
|
||||
quit chan struct{}
|
||||
|
||||
wg sync.WaitGroup
|
||||
|
||||
// TODO(roasbeef): handle wallet lock/unlock
|
||||
}
|
||||
|
||||
// NewLightningWallet creates/opens and initializes a LightningWallet instance.
|
||||
@@ -468,7 +500,6 @@ func (l *LightningWallet) Startup() error {
|
||||
}
|
||||
|
||||
l.wg.Add(1)
|
||||
// TODO(roasbeef): multiple request handlers?
|
||||
go l.requestHandler()
|
||||
|
||||
return nil
|
||||
@@ -1424,7 +1455,6 @@ func (l *LightningWallet) initOurContribution(reservation *ChannelReservation,
|
||||
// transaction via coin selection are freed allowing future reservations to
|
||||
// include them.
|
||||
func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMsg) {
|
||||
// TODO(roasbeef): holding lock too long
|
||||
l.limboMtx.Lock()
|
||||
defer l.limboMtx.Unlock()
|
||||
|
||||
@@ -1449,11 +1479,6 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
|
||||
)
|
||||
}
|
||||
|
||||
// TODO(roasbeef): is it even worth it to keep track of unused keys?
|
||||
|
||||
// TODO(roasbeef): Is it possible to mark the unused change also as
|
||||
// available?
|
||||
|
||||
delete(l.fundingLimbo, req.pendingFundingID)
|
||||
|
||||
pid := pendingReservation.pendingChanID
|
||||
@@ -1473,7 +1498,8 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
|
||||
// createCommitOpts is a struct that holds the options for creating a new
|
||||
// commitment transaction.
|
||||
type createCommitOpts struct {
|
||||
auxLeaves fn.Option[CommitAuxLeaves]
|
||||
localAuxLeaves fn.Option[CommitAuxLeaves]
|
||||
remoteAuxLeaves fn.Option[CommitAuxLeaves]
|
||||
}
|
||||
|
||||
// defaultCommitOpts returns a new createCommitOpts with default values.
|
||||
@@ -1481,6 +1507,17 @@ func defaultCommitOpts() createCommitOpts {
|
||||
return createCommitOpts{}
|
||||
}
|
||||
|
||||
// WithAuxLeaves is a functional option that can be used to set the aux leaves
|
||||
// for a new commitment transaction.
|
||||
func WithAuxLeaves(localLeaves,
|
||||
remoteLeaves fn.Option[CommitAuxLeaves]) CreateCommitOpt {
|
||||
|
||||
return func(o *createCommitOpts) {
|
||||
o.localAuxLeaves = localLeaves
|
||||
o.remoteAuxLeaves = remoteLeaves
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCommitOpt is a functional option that can be used to modify the way a
|
||||
// new commitment transaction is created.
|
||||
type CreateCommitOpt func(*createCommitOpts)
|
||||
@@ -1514,7 +1551,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
||||
ourCommitTx, err := CreateCommitTx(
|
||||
chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
|
||||
theirChanCfg, localBalance, remoteBalance, 0, initiator,
|
||||
leaseExpiry, options.auxLeaves,
|
||||
leaseExpiry, options.localAuxLeaves,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -1528,7 +1565,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
||||
theirCommitTx, err := CreateCommitTx(
|
||||
chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
|
||||
ourChanCfg, remoteBalance, localBalance, 0, !initiator,
|
||||
leaseExpiry, options.auxLeaves,
|
||||
leaseExpiry, options.remoteAuxLeaves,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -1621,16 +1658,24 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||
// and remote key which will be needed to calculate the multisig
|
||||
// funding output in a next step.
|
||||
pendingChanID := pendingReservation.pendingChanID
|
||||
|
||||
walletLog.Debugf("Advancing PSBT funding flow for "+
|
||||
"pending_id(%x), binding keys local_key=%v, "+
|
||||
"remote_key=%x", pendingChanID,
|
||||
&ourContribution.MultiSigKey,
|
||||
theirContribution.MultiSigKey.PubKey.SerializeCompressed())
|
||||
|
||||
fundingIntent.BindKeys(
|
||||
&ourContribution.MultiSigKey,
|
||||
theirContribution.MultiSigKey.PubKey,
|
||||
)
|
||||
|
||||
// We might have a tapscript root, so we'll bind that now to
|
||||
// ensure we make the proper funding output.
|
||||
fundingIntent.BindTapscriptRoot(
|
||||
pendingReservation.partialState.TapscriptRoot,
|
||||
)
|
||||
|
||||
// Exit early because we can't continue the funding flow yet.
|
||||
req.err <- &PsbtFundingRequired{
|
||||
Intent: fundingIntent,
|
||||
@@ -1703,16 +1748,17 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||
// the commitment transaction for the remote party, and verify their incoming
|
||||
// partial signature.
|
||||
func genMusigSession(ourContribution, theirContribution *ChannelContribution,
|
||||
signer input.MuSig2Signer,
|
||||
fundingOutput *wire.TxOut) *MusigPairSession {
|
||||
signer input.MuSig2Signer, fundingOutput *wire.TxOut,
|
||||
tapscriptRoot fn.Option[chainhash.Hash]) *MusigPairSession {
|
||||
|
||||
return NewMusigPairSession(&MusigSessionCfg{
|
||||
LocalKey: ourContribution.MultiSigKey,
|
||||
RemoteKey: theirContribution.MultiSigKey,
|
||||
LocalNonce: *ourContribution.LocalNonce,
|
||||
RemoteNonce: *theirContribution.LocalNonce,
|
||||
Signer: signer,
|
||||
InputTxOut: fundingOutput,
|
||||
LocalKey: ourContribution.MultiSigKey,
|
||||
RemoteKey: theirContribution.MultiSigKey,
|
||||
LocalNonce: *ourContribution.LocalNonce,
|
||||
RemoteNonce: *theirContribution.LocalNonce,
|
||||
Signer: signer,
|
||||
InputTxOut: fundingOutput,
|
||||
TapscriptTweak: tapscriptRoot,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1762,6 +1808,7 @@ func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation,
|
||||
musigSessions := genMusigSession(
|
||||
ourContribution, theirContribution,
|
||||
l.Cfg.Signer, fundingOutput,
|
||||
pendingReservation.partialState.TapscriptRoot,
|
||||
)
|
||||
pendingReservation.musigSessions = musigSessions
|
||||
}
|
||||
@@ -1797,6 +1844,26 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) {
|
||||
return
|
||||
}
|
||||
|
||||
chanState := pendingReservation.partialState
|
||||
|
||||
// If we have an aux funding desc, then we can use it to populate some
|
||||
// of the optional, but opaque TLV blobs we'll carry for the channel.
|
||||
chanState.CustomBlob = fn.MapOption(func(desc AuxFundingDesc) tlv.Blob {
|
||||
return desc.CustomFundingBlob
|
||||
})(req.auxFundingDesc)
|
||||
|
||||
chanState.LocalCommitment.CustomBlob = fn.MapOption(
|
||||
func(desc AuxFundingDesc) tlv.Blob {
|
||||
return desc.CustomLocalCommitBlob
|
||||
},
|
||||
)(req.auxFundingDesc)
|
||||
|
||||
chanState.RemoteCommitment.CustomBlob = fn.MapOption(
|
||||
func(desc AuxFundingDesc) tlv.Blob {
|
||||
return desc.CustomRemoteCommitBlob
|
||||
},
|
||||
)(req.auxFundingDesc)
|
||||
|
||||
ourContribution := pendingReservation.ourContribution
|
||||
theirContribution := pendingReservation.theirContribution
|
||||
chanPoint := pendingReservation.partialState.FundingOutpoint
|
||||
@@ -1855,7 +1922,6 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) {
|
||||
// Store their current commitment point. We'll need this after the
|
||||
// first state transition in order to verify the authenticity of the
|
||||
// revocation.
|
||||
chanState := pendingReservation.partialState
|
||||
chanState.RemoteCurrentRevocation = theirContribution.FirstCommitmentPoint
|
||||
|
||||
// Create the txin to our commitment transaction; required to construct
|
||||
@@ -1871,6 +1937,18 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) {
|
||||
if pendingReservation.partialState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = pendingReservation.partialState.ThawHeight
|
||||
}
|
||||
|
||||
localAuxLeaves := fn.MapOption(
|
||||
func(desc AuxFundingDesc) CommitAuxLeaves {
|
||||
return desc.LocalInitAuxLeaves
|
||||
},
|
||||
)(req.auxFundingDesc)
|
||||
remoteAuxLeaves := fn.MapOption(
|
||||
func(desc AuxFundingDesc) CommitAuxLeaves {
|
||||
return desc.RemoteInitAuxLeaves
|
||||
},
|
||||
)(req.auxFundingDesc)
|
||||
|
||||
ourCommitTx, theirCommitTx, err := CreateCommitmentTxns(
|
||||
localBalance, remoteBalance, ourContribution.ChannelConfig,
|
||||
theirContribution.ChannelConfig,
|
||||
@@ -1878,6 +1956,7 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) {
|
||||
theirContribution.FirstCommitmentPoint, fundingTxIn,
|
||||
pendingReservation.partialState.ChanType,
|
||||
pendingReservation.partialState.IsInitiator, leaseExpiry,
|
||||
WithAuxLeaves(localAuxLeaves, remoteAuxLeaves),
|
||||
)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
@@ -2138,6 +2217,7 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation,
|
||||
res.musigSessions = genMusigSession(
|
||||
res.ourContribution, res.theirContribution,
|
||||
l.Cfg.Signer, fundingOutput,
|
||||
res.partialState.TapscriptRoot,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2228,9 +2308,6 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
|
||||
|
||||
// As we're about to broadcast the funding transaction, we'll take note
|
||||
// of the current height for record keeping purposes.
|
||||
//
|
||||
// TODO(roasbeef): this info can also be piped into light client's
|
||||
// basic fee estimation?
|
||||
_, bestHeight, err := l.Cfg.ChainIO.GetBestBlock()
|
||||
if err != nil {
|
||||
msg.err <- err
|
||||
@@ -2291,6 +2368,23 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
||||
defer pendingReservation.Unlock()
|
||||
|
||||
chanState := pendingReservation.partialState
|
||||
|
||||
// If we have an aux funding desc, then we can use it to populate some
|
||||
// of the optional, but opaque TLV blobs we'll carry for the channel.
|
||||
chanState.CustomBlob = fn.MapOption(func(desc AuxFundingDesc) tlv.Blob {
|
||||
return desc.CustomFundingBlob
|
||||
})(req.auxFundingDesc)
|
||||
chanState.LocalCommitment.CustomBlob = fn.MapOption(
|
||||
func(desc AuxFundingDesc) tlv.Blob {
|
||||
return desc.CustomLocalCommitBlob
|
||||
},
|
||||
)(req.auxFundingDesc)
|
||||
chanState.RemoteCommitment.CustomBlob = fn.MapOption(
|
||||
func(desc AuxFundingDesc) tlv.Blob {
|
||||
return desc.CustomRemoteCommitBlob
|
||||
},
|
||||
)(req.auxFundingDesc)
|
||||
|
||||
chanType := pendingReservation.partialState.ChanType
|
||||
chanState.FundingOutpoint = *req.fundingOutpoint
|
||||
fundingTxIn := wire.NewTxIn(req.fundingOutpoint, nil, nil)
|
||||
@@ -2304,6 +2398,18 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
||||
if pendingReservation.partialState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = pendingReservation.partialState.ThawHeight
|
||||
}
|
||||
|
||||
localAuxLeaves := fn.MapOption(
|
||||
func(desc AuxFundingDesc) CommitAuxLeaves {
|
||||
return desc.LocalInitAuxLeaves
|
||||
},
|
||||
)(req.auxFundingDesc)
|
||||
remoteAuxLeaves := fn.MapOption(
|
||||
func(desc AuxFundingDesc) CommitAuxLeaves {
|
||||
return desc.RemoteInitAuxLeaves
|
||||
},
|
||||
)(req.auxFundingDesc)
|
||||
|
||||
ourCommitTx, theirCommitTx, err := CreateCommitmentTxns(
|
||||
localBalance, remoteBalance,
|
||||
pendingReservation.ourContribution.ChannelConfig,
|
||||
@@ -2312,6 +2418,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
||||
pendingReservation.theirContribution.FirstCommitmentPoint,
|
||||
*fundingTxIn, chanType,
|
||||
pendingReservation.partialState.IsInitiator, leaseExpiry,
|
||||
WithAuxLeaves(localAuxLeaves, remoteAuxLeaves),
|
||||
)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
@@ -2500,6 +2607,9 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
|
||||
l.Cfg.AuxLeafStore.WhenSome(func(s AuxLeafStore) {
|
||||
chanOpts = append(chanOpts, WithLeafStore(s))
|
||||
})
|
||||
l.Cfg.AuxSigner.WhenSome(func(s AuxSigner) {
|
||||
chanOpts = append(chanOpts, WithAuxSigner(s))
|
||||
})
|
||||
|
||||
// First, we'll obtain a fully signed commitment transaction so we can
|
||||
// pass into it on the chanvalidate package for verification.
|
||||
|
||||
Reference in new issue
Block a user