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:
Oliver Gugger authored and GitHub committed 2024-09-19 01:20:55 -06:00
commit cdad5d988d
40 files changed
+1926 -162

No files matched your search

+250
View File
@@ -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
}
+21
View File
@@ -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.
+22 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+4
View File
@@ -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]
}
+52
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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")
+44
View File
@@ -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
}
+5
View File
@@ -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
View File
@@ -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.