lnwallet: thread thru input.AuxTapleaf to all relevant areas

In this commit, we start to thread thru the new aux tap leaf structures to all relevant areas. This includes: commitment outputs, resolution creation, breach handling, and also HTLC scripts.
This commit is contained in:
Oliver Gugger 2024-04-25 19:01:37 +02:00
parent 2ab22b0f0b
commit 9dfbde7013
No known key found for this signature in database
GPG key ID: 8E4256593F177720
6 changed files with 345 additions and 76 deletions

View file

@ -426,15 +426,36 @@ func (c *chainWatcher) handleUnknownLocalState(
&c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg, &c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg,
) )
auxResult, err := fn.MapOptionZ(
c.cfg.auxLeafStore,
//nolint:lll
func(s lnwallet.AuxLeafStore) fn.Result[lnwallet.CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
lnwallet.NewAuxChanState(c.cfg.chanState),
c.cfg.chanState.LocalCommitment, *commitKeyRing,
)
},
).Unpack()
if err != nil {
return false, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// With the keys derived, we'll construct the remote script that'll be // With the keys derived, we'll construct the remote script that'll be
// present if they have a non-dust balance on the commitment. // present if they have a non-dust balance on the commitment.
var leaseExpiry uint32 var leaseExpiry uint32
if c.cfg.chanState.ChanType.HasLeaseExpiration() { if c.cfg.chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = c.cfg.chanState.ThawHeight leaseExpiry = c.cfg.chanState.ThawHeight
} }
remoteAuxLeaf := fn.ChainOption(
func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
},
)(auxResult.AuxLeaves)
remoteScript, _, err := lnwallet.CommitScriptToRemote( remoteScript, _, err := lnwallet.CommitScriptToRemote(
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator, c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
commitKeyRing.ToRemoteKey, leaseExpiry, input.NoneTapLeaf(), commitKeyRing.ToRemoteKey, leaseExpiry,
remoteAuxLeaf,
) )
if err != nil { if err != nil {
return false, err return false, err
@ -443,11 +464,16 @@ func (c *chainWatcher) handleUnknownLocalState(
// Next, we'll derive our script that includes the revocation base for // Next, we'll derive our script that includes the revocation base for
// the remote party allowing them to claim this output before the CSV // the remote party allowing them to claim this output before the CSV
// delay if we breach. // delay if we breach.
localAuxLeaf := fn.ChainOption(
func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
},
)(auxResult.AuxLeaves)
localScript, err := lnwallet.CommitScriptToSelf( localScript, err := lnwallet.CommitScriptToSelf(
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator, c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey, commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry, uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
input.NoneTapLeaf(), localAuxLeaf,
) )
if err != nil { if err != nil {
return false, err return false, err

View file

@ -1388,7 +1388,7 @@ func genTimeoutTx(t *testing.T,
// Create the unsigned timeout tx. // Create the unsigned timeout tx.
timeoutTx, err := lnwallet.CreateHtlcTimeoutTx( timeoutTx, err := lnwallet.CreateHtlcTimeoutTx(
chanType, false, testOutPoint, testAmt, testCLTVExpiry, chanType, false, testOutPoint, testAmt, testCLTVExpiry,
testCSVDelay, 0, testPubkey, testPubkey, testCSVDelay, 0, testPubkey, testPubkey, input.NoneTapLeaf(),
) )
require.NoError(t, err) require.NoError(t, err)
@ -1457,7 +1457,7 @@ func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx {
// Create the unsigned success tx. // Create the unsigned success tx.
successTx, err := lnwallet.CreateHtlcSuccessTx( successTx, err := lnwallet.CreateHtlcSuccessTx(
chanType, false, testOutPoint, testAmt, testCSVDelay, 0, chanType, false, testOutPoint, testAmt, testCSVDelay, 0,
testPubkey, testPubkey, testPubkey, testPubkey, input.NoneTapLeaf(),
) )
require.NoError(t, err) require.NoError(t, err)

View file

@ -576,7 +576,8 @@ func (c *commitment) toDiskCommit(
// restart a channel session. // restart a channel session.
func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
htlc *channeldb.HTLC, commitKeys lntypes.Dual[*CommitmentKeyRing], htlc *channeldb.HTLC, commitKeys lntypes.Dual[*CommitmentKeyRing],
whoseCommit lntypes.ChannelParty) (PaymentDescriptor, error) { whoseCommit lntypes.ChannelParty,
auxLeaf input.AuxTapLeaf) (PaymentDescriptor, error) {
// The proper pkScripts for this PaymentDescriptor must be // The proper pkScripts for this PaymentDescriptor must be
// generated so we can easily locate them within the commitment // generated so we can easily locate them within the commitment
@ -602,6 +603,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
scriptInfo, err := genHtlcScript( scriptInfo, err := genHtlcScript(
chanType, htlc.Incoming, lntypes.Local, chanType, htlc.Incoming, lntypes.Local,
htlc.RefundTimeout, htlc.RHash, localCommitKeys, htlc.RefundTimeout, htlc.RHash, localCommitKeys,
auxLeaf,
) )
if err != nil { if err != nil {
return pd, err return pd, err
@ -618,6 +620,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
scriptInfo, err := genHtlcScript( scriptInfo, err := genHtlcScript(
chanType, htlc.Incoming, lntypes.Remote, chanType, htlc.Incoming, lntypes.Remote,
htlc.RefundTimeout, htlc.RHash, remoteCommitKeys, htlc.RefundTimeout, htlc.RHash, remoteCommitKeys,
auxLeaf,
) )
if err != nil { if err != nil {
return pd, err return pd, err
@ -667,7 +670,8 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
// for each side. // for each side.
func (lc *LightningChannel) extractPayDescs(feeRate chainfee.SatPerKWeight, func (lc *LightningChannel) extractPayDescs(feeRate chainfee.SatPerKWeight,
htlcs []channeldb.HTLC, commitKeys lntypes.Dual[*CommitmentKeyRing], htlcs []channeldb.HTLC, commitKeys lntypes.Dual[*CommitmentKeyRing],
whoseCommit lntypes.ChannelParty) ([]PaymentDescriptor, whoseCommit lntypes.ChannelParty,
auxLeaves fn.Option[CommitAuxLeaves]) ([]PaymentDescriptor,
[]PaymentDescriptor, error) { []PaymentDescriptor, error) {
var ( var (
@ -685,8 +689,19 @@ func (lc *LightningChannel) extractPayDescs(feeRate chainfee.SatPerKWeight,
htlc := htlc htlc := htlc
auxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
if htlc.Incoming {
leaves = l.IncomingHtlcLeaves
}
return leaves[htlc.HtlcIndex].AuxTapLeaf
},
)(auxLeaves)
payDesc, err := lc.diskHtlcToPayDesc( payDesc, err := lc.diskHtlcToPayDesc(
feeRate, &htlc, commitKeys, whoseCommit, feeRate, &htlc, commitKeys, whoseCommit, auxLeaf,
) )
if err != nil { if err != nil {
return incomingHtlcs, outgoingHtlcs, err return incomingHtlcs, outgoingHtlcs, err
@ -733,12 +748,25 @@ func (lc *LightningChannel) diskCommitToMemCommit(
)) ))
} }
auxResult, err := fn.MapOptionZ(
lc.leafStore,
func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(lc.channelState), *diskCommit,
*commitKeys.GetForParty(whoseCommit),
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// With the key rings re-created, we'll now convert all the on-disk // With the key rings re-created, we'll now convert all the on-disk
// HTLC"s into PaymentDescriptor's so we can re-insert them into our // HTLC"s into PaymentDescriptor's so we can re-insert them into our
// update log. // update log.
incomingHtlcs, outgoingHtlcs, err := lc.extractPayDescs( incomingHtlcs, outgoingHtlcs, err := lc.extractPayDescs(
chainfee.SatPerKWeight(diskCommit.FeePerKw), chainfee.SatPerKWeight(diskCommit.FeePerKw),
diskCommit.Htlcs, commitKeys, whoseCommit, diskCommit.Htlcs, commitKeys, whoseCommit, auxResult.AuxLeaves,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1091,7 +1119,8 @@ func (lc *LightningChannel) ResetState() {
func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
remoteUpdateLog *updateLog, commitHeight uint64, remoteUpdateLog *updateLog, commitHeight uint64,
feeRate chainfee.SatPerKWeight, remoteCommitKeys *CommitmentKeyRing, feeRate chainfee.SatPerKWeight, remoteCommitKeys *CommitmentKeyRing,
remoteDustLimit btcutil.Amount) (*PaymentDescriptor, error) { remoteDustLimit btcutil.Amount,
auxLeaves fn.Option[CommitAuxLeaves]) (*PaymentDescriptor, error) {
// Depending on the type of update message we'll map that to a distinct // Depending on the type of update message we'll map that to a distinct
// PaymentDescriptor instance. // PaymentDescriptor instance.
@ -1127,10 +1156,17 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
feeRate, wireMsg.Amount.ToSatoshis(), remoteDustLimit, feeRate, wireMsg.Amount.ToSatoshis(), remoteDustLimit,
) )
if !isDustRemote { if !isDustRemote {
auxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
return leaves[pd.HtlcIndex].AuxTapLeaf
},
)(auxLeaves)
scriptInfo, err := genHtlcScript( scriptInfo, err := genHtlcScript(
lc.channelState.ChanType, false, lntypes.Remote, lc.channelState.ChanType, false, lntypes.Remote,
wireMsg.Expiry, wireMsg.PaymentHash, wireMsg.Expiry, wireMsg.PaymentHash,
remoteCommitKeys, remoteCommitKeys, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1791,6 +1827,19 @@ func (lc *LightningChannel) restorePendingLocalUpdates(
pendingCommit := pendingRemoteCommitDiff.Commitment pendingCommit := pendingRemoteCommitDiff.Commitment
pendingHeight := pendingCommit.CommitHeight pendingHeight := pendingCommit.CommitHeight
auxResult, err := fn.MapOptionZ(
lc.leafStore,
func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(lc.channelState), pendingCommit,
*pendingRemoteKeys,
)
},
).Unpack()
if err != nil {
return fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// If we did have a dangling commit, then we'll examine which updates // If we did have a dangling commit, then we'll examine which updates
// we included in that state and re-insert them into our update log. // we included in that state and re-insert them into our update log.
for _, logUpdate := range pendingRemoteCommitDiff.LogUpdates { for _, logUpdate := range pendingRemoteCommitDiff.LogUpdates {
@ -1801,6 +1850,7 @@ func (lc *LightningChannel) restorePendingLocalUpdates(
chainfee.SatPerKWeight(pendingCommit.FeePerKw), chainfee.SatPerKWeight(pendingCommit.FeePerKw),
pendingRemoteKeys, pendingRemoteKeys,
lc.channelState.RemoteChanCfg.DustLimit, lc.channelState.RemoteChanCfg.DustLimit,
auxResult.AuxLeaves,
) )
if err != nil { if err != nil {
return err return err
@ -2007,24 +2057,40 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
leaseExpiry = chanState.ThawHeight leaseExpiry = chanState.ThawHeight
} }
// TODO(roasbeef): Actually fetch aux leaves (later commits in this PR). auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromRevocation(revokedLog)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// Since it is the remote breach we are reconstructing, the output // Since it is the remote breach we are reconstructing, the output
// going to us will be a to-remote script with our local params. // going to us will be a to-remote script with our local params.
remoteAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
},
)(auxResult.AuxLeaves)
isRemoteInitiator := !chanState.IsInitiator isRemoteInitiator := !chanState.IsInitiator
ourScript, ourDelay, err := CommitScriptToRemote( ourScript, ourDelay, err := CommitScriptToRemote(
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry, input.NoneTapLeaf(), leaseExpiry, remoteAuxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
localAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
},
)(auxResult.AuxLeaves)
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
theirScript, err := CommitScriptToSelf( theirScript, err := CommitScriptToSelf(
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey, chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, theirDelay, leaseExpiry, keyRing.RevocationKey, theirDelay, leaseExpiry, localAuxLeaf,
input.NoneTapLeaf(),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -2042,7 +2108,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
if revokedLog != nil { if revokedLog != nil {
br, ourAmt, theirAmt, err = createBreachRetribution( br, ourAmt, theirAmt, err = createBreachRetribution(
revokedLog, spendTx, chanState, keyRing, revokedLog, spendTx, chanState, keyRing,
commitmentSecret, leaseExpiry, commitmentSecret, leaseExpiry, auxResult.AuxLeaves,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -2176,7 +2242,8 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
func createHtlcRetribution(chanState *channeldb.OpenChannel, func createHtlcRetribution(chanState *channeldb.OpenChannel,
keyRing *CommitmentKeyRing, commitHash chainhash.Hash, keyRing *CommitmentKeyRing, commitHash chainhash.Hash,
commitmentSecret *btcec.PrivateKey, leaseExpiry uint32, commitmentSecret *btcec.PrivateKey, leaseExpiry uint32,
htlc *channeldb.HTLCEntry) (HtlcRetribution, error) { htlc *channeldb.HTLCEntry,
auxLeaves fn.Option[CommitAuxLeaves]) (HtlcRetribution, error) {
var emptyRetribution HtlcRetribution var emptyRetribution HtlcRetribution
@ -2186,10 +2253,24 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel,
// We'll generate the original second level witness script now, as // We'll generate the original second level witness script now, as
// we'll need it if we're revoking an HTLC output on the remote // we'll need it if we're revoking an HTLC output on the remote
// commitment transaction, and *they* go to the second level. // commitment transaction, and *they* go to the second level.
secondLevelAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) fn.Option[input.AuxTapLeaf] {
return fn.MapOption(func(val uint16) input.AuxTapLeaf {
idx := input.HtlcIndex(val)
if htlc.Incoming.Val {
leaves := l.IncomingHtlcLeaves[idx]
return leaves.SecondLevelLeaf
}
return l.OutgoingHtlcLeaves[idx].SecondLevelLeaf
})(htlc.HtlcIndex.ValOpt())
},
)(auxLeaves)
secondLevelScript, err := SecondLevelHtlcScript( secondLevelScript, err := SecondLevelHtlcScript(
chanState.ChanType, isRemoteInitiator, chanState.ChanType, isRemoteInitiator,
keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay, keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay,
leaseExpiry, leaseExpiry, fn.FlattenOption(secondLevelAuxLeaf),
) )
if err != nil { if err != nil {
return emptyRetribution, err return emptyRetribution, err
@ -2200,9 +2281,24 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel,
// HTLC script. Otherwise, is this was an outgoing HTLC that we sent, // HTLC script. Otherwise, is this was an outgoing HTLC that we sent,
// then from the PoV of the remote commitment state, they're the // then from the PoV of the remote commitment state, they're the
// receiver of this HTLC. // receiver of this HTLC.
htlcLeaf := fn.ChainOption(
func(l CommitAuxLeaves) fn.Option[input.AuxTapLeaf] {
return fn.MapOption(func(val uint16) input.AuxTapLeaf {
idx := input.HtlcIndex(val)
if htlc.Incoming.Val {
leaves := l.IncomingHtlcLeaves[idx]
return leaves.AuxTapLeaf
}
return l.OutgoingHtlcLeaves[idx].AuxTapLeaf
})(htlc.HtlcIndex.ValOpt())
},
)(auxLeaves)
scriptInfo, err := genHtlcScript( scriptInfo, err := genHtlcScript(
chanState.ChanType, htlc.Incoming.Val, lntypes.Remote, chanState.ChanType, htlc.Incoming.Val, lntypes.Remote,
htlc.RefundTimeout.Val, htlc.RHash.Val, keyRing, htlc.RefundTimeout.Val, htlc.RHash.Val, keyRing,
fn.FlattenOption(htlcLeaf),
) )
if err != nil { if err != nil {
return emptyRetribution, err return emptyRetribution, err
@ -2266,7 +2362,9 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel,
func createBreachRetribution(revokedLog *channeldb.RevocationLog, func createBreachRetribution(revokedLog *channeldb.RevocationLog,
spendTx *wire.MsgTx, chanState *channeldb.OpenChannel, spendTx *wire.MsgTx, chanState *channeldb.OpenChannel,
keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey, keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey,
leaseExpiry uint32) (*BreachRetribution, int64, int64, error) { leaseExpiry uint32,
auxLeaves fn.Option[CommitAuxLeaves]) (*BreachRetribution, int64, int64,
error) {
commitHash := revokedLog.CommitTxHash commitHash := revokedLog.CommitTxHash
@ -2275,7 +2373,7 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog,
for i, htlc := range revokedLog.HTLCEntries { for i, htlc := range revokedLog.HTLCEntries {
hr, err := createHtlcRetribution( hr, err := createHtlcRetribution(
chanState, keyRing, commitHash.Val, chanState, keyRing, commitHash.Val,
commitmentSecret, leaseExpiry, htlc, commitmentSecret, leaseExpiry, htlc, auxLeaves,
) )
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
@ -2427,6 +2525,7 @@ func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment,
hr, err := createHtlcRetribution( hr, err := createHtlcRetribution(
chanState, keyRing, commitHash, chanState, keyRing, commitHash,
commitmentSecret, leaseExpiry, entry, commitmentSecret, leaseExpiry, entry,
fn.None[CommitAuxLeaves](),
) )
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
@ -3041,13 +3140,27 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
// With the keys generated, we'll make a slice with enough capacity to // With the keys generated, we'll make a slice with enough capacity to
// hold potentially all the HTLCs. The actual slice may be a bit // hold potentially all the HTLCs. The actual slice may be a bit
// smaller (than its total capacity) and some HTLCs may be dust. // smaller (than its total capacity) and some HTLCs may be dust.
numSigs := (len(remoteCommitView.incomingHTLCs) + numSigs := len(remoteCommitView.incomingHTLCs) +
len(remoteCommitView.outgoingHTLCs)) len(remoteCommitView.outgoingHTLCs)
sigBatch := make([]SignJob, 0, numSigs) sigBatch := make([]SignJob, 0, numSigs)
var err error var err error
cancelChan := make(chan struct{}) cancelChan := make(chan struct{})
auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState),
*remoteCommitView.toDiskCommit(lntypes.Remote),
*keyRing,
)
},
).Unpack()
if err != nil {
return nil, nil, fmt.Errorf("unable to fetch aux leaves: %w",
err)
}
// For each outgoing and incoming HTLC, if the HTLC isn't considered a // For each outgoing and incoming HTLC, if the HTLC isn't considered a
// dust output after taking into account second-level HTLC fees, then a // dust output after taking into account second-level HTLC fees, then a
// sigJob will be generated and appended to the current batch. // sigJob will be generated and appended to the current batch.
@ -3074,6 +3187,13 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
htlcFee := HtlcTimeoutFee(chanType, feePerKw) htlcFee := HtlcTimeoutFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee outputAmt := htlc.Amount.ToSatoshis() - htlcFee
auxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.IncomingHtlcLeaves
return leaves[htlc.HtlcIndex].SecondLevelLeaf
},
)(auxResult.AuxLeaves)
// With the fee calculate, we can properly create the HTLC // With the fee calculate, we can properly create the HTLC
// timeout transaction using the HTLC amount minus the fee. // timeout transaction using the HTLC amount minus the fee.
op := wire.OutPoint{ op := wire.OutPoint{
@ -3084,11 +3204,15 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
chanType, isRemoteInitiator, op, outputAmt, chanType, isRemoteInitiator, op, outputAmt,
htlc.Timeout, uint32(remoteChanCfg.CsvDelay), htlc.Timeout, uint32(remoteChanCfg.CsvDelay),
leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey, leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey,
auxLeaf,
) )
if err != nil { if err != nil {
return nil, nil, err return 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 // Construct a full hash cache as we may be signing a segwit v1
// sighash. // sighash.
txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex]
@ -3115,7 +3239,8 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
// If this is a taproot channel, then we'll need to set the // If this is a taproot channel, then we'll need to set the
// method type to ensure we generate a valid signature. // method type to ensure we generate a valid signature.
if chanType.IsTaproot() { if chanType.IsTaproot() {
sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod //nolint:lll //nolint:lll
sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod
} }
sigBatch = append(sigBatch, sigJob) sigBatch = append(sigBatch, sigJob)
@ -3141,6 +3266,13 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
htlcFee := HtlcSuccessFee(chanType, feePerKw) htlcFee := HtlcSuccessFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee outputAmt := htlc.Amount.ToSatoshis() - htlcFee
auxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
return leaves[htlc.HtlcIndex].SecondLevelLeaf
},
)(auxResult.AuxLeaves)
// With the proper output amount calculated, we can now // With the proper output amount calculated, we can now
// generate the success transaction using the remote party's // generate the success transaction using the remote party's
// CSV delay. // CSV delay.
@ -3152,6 +3284,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
chanType, isRemoteInitiator, op, outputAmt, chanType, isRemoteInitiator, op, outputAmt,
uint32(remoteChanCfg.CsvDelay), leaseExpiry, uint32(remoteChanCfg.CsvDelay), leaseExpiry,
keyRing.RevocationKey, keyRing.ToLocalKey, keyRing.RevocationKey, keyRing.ToLocalKey,
auxLeaf,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -4491,10 +4624,24 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
// enough capacity to hold verification jobs for all HTLC's in this // enough capacity to hold verification jobs for all HTLC's in this
// view. In the case that we have some dust outputs, then the actual // view. In the case that we have some dust outputs, then the actual
// length will be smaller than the total capacity. // length will be smaller than the total capacity.
numHtlcs := (len(localCommitmentView.incomingHTLCs) + numHtlcs := len(localCommitmentView.incomingHTLCs) +
len(localCommitmentView.outgoingHTLCs)) len(localCommitmentView.outgoingHTLCs)
verifyJobs := make([]VerifyJob, 0, numHtlcs) verifyJobs := make([]VerifyJob, 0, numHtlcs)
auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState),
*localCommitmentView.toDiskCommit(
lntypes.Local,
), *keyRing,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// We'll iterate through each output in the commitment transaction, // We'll iterate through each output in the commitment transaction,
// populating the sigHash closure function if it's detected to be an // populating the sigHash closure function if it's detected to be an
// HLTC output. Given the sighash, and the signing key, we'll be able // HLTC output. Given the sighash, and the signing key, we'll be able
@ -4528,11 +4675,19 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
htlcFee := HtlcSuccessFee(chanType, feePerKw) htlcFee := HtlcSuccessFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee outputAmt := htlc.Amount.ToSatoshis() - htlcFee
auxLeaf := fn.ChainOption(func(
l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.IncomingHtlcLeaves
idx := htlc.HtlcIndex
return leaves[idx].SecondLevelLeaf
})(auxResult.AuxLeaves)
successTx, err := CreateHtlcSuccessTx( successTx, err := CreateHtlcSuccessTx(
chanType, isLocalInitiator, op, chanType, isLocalInitiator, op,
outputAmt, uint32(localChanCfg.CsvDelay), outputAmt, uint32(localChanCfg.CsvDelay),
leaseExpiry, keyRing.RevocationKey, leaseExpiry, keyRing.RevocationKey,
keyRing.ToLocalKey, keyRing.ToLocalKey, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -4612,12 +4767,20 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
htlcFee := HtlcTimeoutFee(chanType, feePerKw) htlcFee := HtlcTimeoutFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee outputAmt := htlc.Amount.ToSatoshis() - htlcFee
auxLeaf := fn.ChainOption(func(
l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
idx := htlc.HtlcIndex
return leaves[idx].SecondLevelLeaf
})(auxResult.AuxLeaves)
timeoutTx, err := CreateHtlcTimeoutTx( timeoutTx, err := CreateHtlcTimeoutTx(
chanType, isLocalInitiator, op, chanType, isLocalInitiator, op,
outputAmt, htlc.Timeout, outputAmt, htlc.Timeout,
uint32(localChanCfg.CsvDelay), uint32(localChanCfg.CsvDelay),
leaseExpiry, keyRing.RevocationKey, leaseExpiry, keyRing.RevocationKey,
keyRing.ToLocalKey, keyRing.ToLocalKey, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -6332,6 +6495,18 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg, &chanState.LocalChanCfg, &chanState.RemoteChanCfg,
) )
auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState), remoteCommit,
*keyRing,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we // Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we
// had on their commitment transaction. // had on their commitment transaction.
var leaseExpiry uint32 var leaseExpiry uint32
@ -6344,6 +6519,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg, signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitSpend.SpendingTx, &chanState.RemoteChanCfg, commitSpend.SpendingTx,
chanState.ChanType, isRemoteInitiator, leaseExpiry, chanState.ChanType, isRemoteInitiator, leaseExpiry,
auxResult.AuxLeaves,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create htlc "+ return nil, fmt.Errorf("unable to create htlc "+
@ -6352,14 +6528,17 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
commitTxBroadcast := commitSpend.SpendingTx commitTxBroadcast := commitSpend.SpendingTx
// TODO(roasbeef): Actually fetch aux leaves (later commits in this PR).
// Before we can generate the proper sign descriptor, we'll need to // Before we can generate the proper sign descriptor, we'll need to
// locate the output index of our non-delayed output on the commitment // locate the output index of our non-delayed output on the commitment
// transaction. // transaction.
remoteAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
},
)(auxResult.AuxLeaves)
selfScript, maturityDelay, err := CommitScriptToRemote( selfScript, maturityDelay, err := CommitScriptToRemote(
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry, input.NoneTapLeaf(), leaseExpiry, remoteAuxLeaf,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create self commit "+ return nil, fmt.Errorf("unable to create self commit "+
@ -6599,7 +6778,8 @@ func newOutgoingHtlcResolution(signer input.Signer,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool, whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool,
chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) { chanType channeldb.ChannelType,
auxLeaves fn.Option[CommitAuxLeaves]) (*OutgoingHtlcResolution, error) {
op := wire.OutPoint{ op := wire.OutPoint{
Hash: commitTx.TxHash(), Hash: commitTx.TxHash(),
@ -6608,9 +6788,12 @@ func newOutgoingHtlcResolution(signer input.Signer,
// First, we'll re-generate the script used to send the HTLC to the // First, we'll re-generate the script used to send the HTLC to the
// remote party within their commitment transaction. // remote party within their commitment transaction.
auxLeaf := fn.ChainOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.OutgoingHtlcLeaves[htlc.HtlcIndex].AuxTapLeaf
})(auxLeaves)
htlcScriptInfo, err := genHtlcScript( htlcScriptInfo, err := genHtlcScript(
chanType, false, whoseCommit, htlc.RefundTimeout, htlc.RHash, chanType, false, whoseCommit, htlc.RefundTimeout, htlc.RHash,
keyRing, keyRing, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -6683,10 +6866,16 @@ func newOutgoingHtlcResolution(signer input.Signer,
// With the fee calculated, re-construct the second level timeout // With the fee calculated, re-construct the second level timeout
// transaction. // transaction.
secondLevelAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
return leaves[htlc.HtlcIndex].SecondLevelLeaf
},
)(auxLeaves)
timeoutTx, err := CreateHtlcTimeoutTx( timeoutTx, err := CreateHtlcTimeoutTx(
chanType, isCommitFromInitiator, op, secondLevelOutputAmt, chanType, isCommitFromInitiator, op, secondLevelOutputAmt,
htlc.RefundTimeout, csvDelay, leaseExpiry, keyRing.RevocationKey, htlc.RefundTimeout, csvDelay, leaseExpiry,
keyRing.ToLocalKey, keyRing.RevocationKey, keyRing.ToLocalKey, secondLevelAuxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -6769,6 +6958,7 @@ func newOutgoingHtlcResolution(signer input.Signer,
htlcSweepScript, err = SecondLevelHtlcScript( htlcSweepScript, err = SecondLevelHtlcScript(
chanType, isCommitFromInitiator, keyRing.RevocationKey, chanType, isCommitFromInitiator, keyRing.RevocationKey,
keyRing.ToLocalKey, csvDelay, leaseExpiry, keyRing.ToLocalKey, csvDelay, leaseExpiry,
secondLevelAuxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -6777,7 +6967,7 @@ func newOutgoingHtlcResolution(signer input.Signer,
//nolint:lll //nolint:lll
secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree( secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree(
keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay,
fn.None[txscript.TapLeaf](), secondLevelAuxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -6852,8 +7042,8 @@ func newIncomingHtlcResolution(signer input.Signer,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool, whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool,
chanType channeldb.ChannelType) ( chanType channeldb.ChannelType,
*IncomingHtlcResolution, error) { auxLeaves fn.Option[CommitAuxLeaves]) (*IncomingHtlcResolution, error) {
op := wire.OutPoint{ op := wire.OutPoint{
Hash: commitTx.TxHash(), Hash: commitTx.TxHash(),
@ -6862,9 +7052,12 @@ func newIncomingHtlcResolution(signer input.Signer,
// First, we'll re-generate the script the remote party used to // First, we'll re-generate the script the remote party used to
// send the HTLC to us in their commitment transaction. // send the HTLC to us in their commitment transaction.
auxLeaf := fn.ChainOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.IncomingHtlcLeaves[htlc.HtlcIndex].AuxTapLeaf
})(auxLeaves)
scriptInfo, err := genHtlcScript( scriptInfo, err := genHtlcScript(
chanType, true, whoseCommit, htlc.RefundTimeout, htlc.RHash, chanType, true, whoseCommit, htlc.RefundTimeout, htlc.RHash,
keyRing, keyRing, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -6924,6 +7117,13 @@ func newIncomingHtlcResolution(signer input.Signer,
}, nil }, nil
} }
secondLevelAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.IncomingHtlcLeaves
return leaves[htlc.HtlcIndex].SecondLevelLeaf
},
)(auxLeaves)
// Otherwise, we'll need to go to the second level to sweep this HTLC. // Otherwise, we'll need to go to the second level to sweep this HTLC.
// //
// First, we'll reconstruct the original HTLC success transaction, // First, we'll reconstruct the original HTLC success transaction,
@ -6933,7 +7133,7 @@ func newIncomingHtlcResolution(signer input.Signer,
successTx, err := CreateHtlcSuccessTx( successTx, err := CreateHtlcSuccessTx(
chanType, isCommitFromInitiator, op, secondLevelOutputAmt, chanType, isCommitFromInitiator, op, secondLevelOutputAmt,
csvDelay, leaseExpiry, keyRing.RevocationKey, csvDelay, leaseExpiry, keyRing.RevocationKey,
keyRing.ToLocalKey, keyRing.ToLocalKey, secondLevelAuxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -7016,6 +7216,7 @@ func newIncomingHtlcResolution(signer input.Signer,
htlcSweepScript, err = SecondLevelHtlcScript( htlcSweepScript, err = SecondLevelHtlcScript(
chanType, isCommitFromInitiator, keyRing.RevocationKey, chanType, isCommitFromInitiator, keyRing.RevocationKey,
keyRing.ToLocalKey, csvDelay, leaseExpiry, keyRing.ToLocalKey, csvDelay, leaseExpiry,
secondLevelAuxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -7024,7 +7225,7 @@ func newIncomingHtlcResolution(signer input.Signer,
//nolint:lll //nolint:lll
secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree( secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree(
keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay,
fn.None[txscript.TapLeaf](), secondLevelAuxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -7117,7 +7318,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig, localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
commitTx *wire.MsgTx, chanType channeldb.ChannelType, commitTx *wire.MsgTx, chanType channeldb.ChannelType,
isCommitFromInitiator bool, leaseExpiry uint32) (*HtlcResolutions, error) { isCommitFromInitiator bool, leaseExpiry uint32,
auxLeaves fn.Option[CommitAuxLeaves]) (*HtlcResolutions, error) {
// TODO(roasbeef): don't need to swap csv delay? // TODO(roasbeef): don't need to swap csv delay?
dustLimit := remoteChanCfg.DustLimit dustLimit := remoteChanCfg.DustLimit
@ -7150,8 +7352,9 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
// as we can satisfy the contract. // as we can satisfy the contract.
ihr, err := newIncomingHtlcResolution( ihr, err := newIncomingHtlcResolution(
signer, localChanCfg, commitTx, &htlc, signer, localChanCfg, commitTx, &htlc,
keyRing, feePerKw, uint32(csvDelay), leaseExpiry, keyRing, feePerKw, uint32(csvDelay),
whoseCommit, isCommitFromInitiator, chanType, leaseExpiry, whoseCommit, isCommitFromInitiator,
chanType, auxLeaves,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("incoming resolution "+ return nil, fmt.Errorf("incoming resolution "+
@ -7165,7 +7368,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
ohr, err := newOutgoingHtlcResolution( ohr, err := newOutgoingHtlcResolution(
signer, localChanCfg, commitTx, &htlc, keyRing, signer, localChanCfg, commitTx, &htlc, keyRing,
feePerKw, uint32(csvDelay), leaseExpiry, whoseCommit, feePerKw, uint32(csvDelay), leaseExpiry, whoseCommit,
isCommitFromInitiator, chanType, isCommitFromInitiator, chanType, auxLeaves,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("outgoing resolution "+ return nil, fmt.Errorf("outgoing resolution "+
@ -7304,16 +7507,31 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg, &chanState.LocalChanCfg, &chanState.RemoteChanCfg,
) )
// TODO(roasbeef): Actually fetch aux leaves (later commits in this PR). auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState),
chanState.LocalCommitment, *keyRing,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
var leaseExpiry uint32 var leaseExpiry uint32
if chanState.ChanType.HasLeaseExpiration() { if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight leaseExpiry = chanState.ThawHeight
} }
localAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
},
)(auxResult.AuxLeaves)
toLocalScript, err := CommitScriptToSelf( toLocalScript, err := CommitScriptToSelf(
chanState.ChanType, chanState.IsInitiator, keyRing.ToLocalKey, chanState.ChanType, chanState.IsInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, csvTimeout, leaseExpiry, keyRing.RevocationKey, csvTimeout, leaseExpiry, localAuxLeaf,
input.NoneTapLeaf(),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -7404,7 +7622,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
chainfee.SatPerKWeight(localCommit.FeePerKw), lntypes.Local, chainfee.SatPerKWeight(localCommit.FeePerKw), lntypes.Local,
signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg, signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitTx, chanState.ChanType, &chanState.RemoteChanCfg, commitTx, chanState.ChanType,
chanState.IsInitiator, leaseExpiry, chanState.IsInitiator, leaseExpiry, auxResult.AuxLeaves,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to gen htlc resolution: %w", err) return nil, fmt.Errorf("unable to gen htlc resolution: %w", err)

View file

@ -9971,7 +9971,7 @@ func TestCreateHtlcRetribution(t *testing.T) {
// Create the htlc retribution. // Create the htlc retribution.
hr, err := createHtlcRetribution( hr, err := createHtlcRetribution(
aliceChannel.channelState, keyRing, commitHash, aliceChannel.channelState, keyRing, commitHash,
dummyPrivate, leaseExpiry, htlc, dummyPrivate, leaseExpiry, htlc, fn.None[CommitAuxLeaves](),
) )
// Expect no error. // Expect no error.
require.NoError(t, err) require.NoError(t, err)
@ -10177,6 +10177,7 @@ func TestCreateBreachRetribution(t *testing.T) {
tc.revocationLog, tx, tc.revocationLog, tx,
aliceChannel.channelState, keyRing, aliceChannel.channelState, keyRing,
dummyPrivate, leaseExpiry, dummyPrivate, leaseExpiry,
fn.None[CommitAuxLeaves](),
) )
// Check the error if expected. // Check the error if expected.
@ -10295,7 +10296,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
// error as there are no past delta state saved as revocation logs yet. // error as there are no past delta state saved as revocation logs yet.
_, err = NewBreachRetribution( _, err = NewBreachRetribution(
aliceChannel.channelState, stateNum, breachHeight, breachTx, aliceChannel.channelState, stateNum, breachHeight, breachTx,
fn.None[AuxLeafStore](), fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
) )
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
@ -10303,7 +10304,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
// provided. // provided.
_, err = NewBreachRetribution( _, err = NewBreachRetribution(
aliceChannel.channelState, stateNum, breachHeight, nil, aliceChannel.channelState, stateNum, breachHeight, nil,
fn.None[AuxLeafStore](), fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
) )
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
@ -10417,6 +10418,7 @@ func TestExtractPayDescs(t *testing.T) {
// scripts(`genHtlcScript`) as it should be tested independently. // scripts(`genHtlcScript`) as it should be tested independently.
incomingPDs, outgoingPDs, err := lnChan.extractPayDescs( incomingPDs, outgoingPDs, err := lnChan.extractPayDescs(
0, htlcs, lntypes.Dual[*CommitmentKeyRing]{}, lntypes.Local, 0, htlcs, lntypes.Dual[*CommitmentKeyRing]{}, lntypes.Local,
fn.None[CommitAuxLeaves](),
) )
require.NoError(t, err) require.NoError(t, err)

View file

@ -420,14 +420,14 @@ func sweepSigHash(chanType channeldb.ChannelType) txscript.SigHashType {
// argument should correspond to the owner of the commitment transaction which // argument should correspond to the owner of the commitment transaction which
// we are generating the to_local script for. // we are generating the to_local script for.
func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool, func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool,
revocationKey, delayKey *btcec.PublicKey, revocationKey, delayKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
csvDelay, leaseExpiry uint32) (input.ScriptDescriptor, error) { auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
switch { switch {
// For taproot channels, the pkScript is a segwit v1 p2tr output. // For taproot channels, the pkScript is a segwit v1 p2tr output.
case chanType.IsTaproot(): case chanType.IsTaproot():
return input.TaprootSecondLevelScriptTree( return input.TaprootSecondLevelScriptTree(
revocationKey, delayKey, csvDelay, input.NoneTapLeaf(), revocationKey, delayKey, csvDelay, auxLeaf,
) )
// If we are the initiator of a leased channel, then we have an // If we are the initiator of a leased channel, then we have an
@ -755,10 +755,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
theirBalance -= commitFeeMSat theirBalance -= commitFeeMSat
} }
var ( var commitTx *wire.MsgTx
commitTx *wire.MsgTx
err error
)
// Before we create the commitment transaction below, we'll try to see // Before we create the commitment transaction below, we'll try to see
// if there're any aux leaves that need to be a part of the tapscript // if there're any aux leaves that need to be a part of the tapscript
@ -806,6 +803,19 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
return nil, err return nil, err
} }
// Similarly, we'll now attempt to extract the set of aux leaves for
// the set of incoming and outgoing HTLCs.
incomingAuxLeaves := fn.MapOption(
func(leaves CommitAuxLeaves) input.HtlcAuxLeaves {
return leaves.IncomingHtlcLeaves
},
)(auxResult.AuxLeaves)
outgoingAuxLeaves := fn.MapOption(
func(leaves CommitAuxLeaves) input.HtlcAuxLeaves {
return leaves.OutgoingHtlcLeaves
},
)(auxResult.AuxLeaves)
// We'll now add all the HTLC outputs to the commitment transaction. // We'll now add all the HTLC outputs to the commitment transaction.
// Each output includes an off-chain 2-of-2 covenant clause, so we'll // Each output includes an off-chain 2-of-2 covenant clause, so we'll
// need the objective local/remote keys for this particular commitment // need the objective local/remote keys for this particular commitment
@ -826,9 +836,15 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
continue continue
} }
auxLeaf := fn.ChainOption(
func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf {
return leaves[htlc.HtlcIndex].AuxTapLeaf
},
)(outgoingAuxLeaves)
err := addHTLC( err := addHTLC(
commitTx, whoseCommit, false, htlc, keyRing, commitTx, whoseCommit, false, htlc, keyRing,
cb.chanState.ChanType, cb.chanState.ChanType, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -848,9 +864,15 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
continue continue
} }
auxLeaf := fn.ChainOption(
func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf {
return leaves[htlc.HtlcIndex].AuxTapLeaf
},
)(incomingAuxLeaves)
err := addHTLC( err := addHTLC(
commitTx, whoseCommit, true, htlc, keyRing, commitTx, whoseCommit, true, htlc, keyRing,
cb.chanState.ChanType, cb.chanState.ChanType, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1128,7 +1150,7 @@ func genSegwitV0HtlcScript(chanType channeldb.ChannelType,
// channel. // channel.
func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing, timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing,
) (*input.HtlcScriptTree, error) { auxLeaf input.AuxTapLeaf) (*input.HtlcScriptTree, error) {
var ( var (
htlcScriptTree *input.HtlcScriptTree htlcScriptTree *input.HtlcScriptTree
@ -1145,8 +1167,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
case isIncoming && whoseCommit.IsLocal(): case isIncoming && whoseCommit.IsLocal():
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:], whoseCommit, keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
input.NoneTapLeaf(),
) )
// We're being paid via an HTLC by the remote party, and the HTLC is // We're being paid via an HTLC by the remote party, and the HTLC is
@ -1155,8 +1176,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
case isIncoming && whoseCommit.IsRemote(): case isIncoming && whoseCommit.IsRemote():
htlcScriptTree, err = input.SenderHTLCScriptTaproot( htlcScriptTree, err = input.SenderHTLCScriptTaproot(
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:], whoseCommit, keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
input.NoneTapLeaf(),
) )
// We're sending an HTLC which is being added to our commitment // We're sending an HTLC which is being added to our commitment
@ -1165,8 +1185,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
case !isIncoming && whoseCommit.IsLocal(): case !isIncoming && whoseCommit.IsLocal():
htlcScriptTree, err = input.SenderHTLCScriptTaproot( htlcScriptTree, err = input.SenderHTLCScriptTaproot(
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, rHash[:], whoseCommit, keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
input.NoneTapLeaf(),
) )
// Finally, we're paying the remote party via an HTLC, which is being // Finally, we're paying the remote party via an HTLC, which is being
@ -1175,8 +1194,7 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
case !isIncoming && whoseCommit.IsRemote(): case !isIncoming && whoseCommit.IsRemote():
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, rHash[:], whoseCommit, keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
input.NoneTapLeaf(),
) )
} }
@ -1191,7 +1209,8 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
// along side the multiplexer. // along side the multiplexer.
func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool, func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte, whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte,
keyRing *CommitmentKeyRing) (input.ScriptDescriptor, error) { keyRing *CommitmentKeyRing,
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
if !chanType.IsTaproot() { if !chanType.IsTaproot() {
return genSegwitV0HtlcScript( return genSegwitV0HtlcScript(
@ -1201,7 +1220,7 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
} }
return GenTaprootHtlcScript( return GenTaprootHtlcScript(
isIncoming, whoseCommit, timeout, rHash, keyRing, isIncoming, whoseCommit, timeout, rHash, keyRing, auxLeaf,
) )
} }
@ -1214,13 +1233,15 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
// the descriptor itself. // the descriptor itself.
func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty, func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty,
isIncoming bool, paymentDesc *PaymentDescriptor, isIncoming bool, paymentDesc *PaymentDescriptor,
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error { keyRing *CommitmentKeyRing, chanType channeldb.ChannelType,
auxLeaf input.AuxTapLeaf) error {
timeout := paymentDesc.Timeout timeout := paymentDesc.Timeout
rHash := paymentDesc.RHash rHash := paymentDesc.RHash
scriptInfo, err := genHtlcScript( scriptInfo, err := genHtlcScript(
chanType, isIncoming, whoseCommit, timeout, rHash, keyRing, chanType, isIncoming, whoseCommit, timeout, rHash, keyRing,
auxLeaf,
) )
if err != nil { if err != nil {
return err return err

View file

@ -8,6 +8,7 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
) )
const ( const (
@ -50,8 +51,8 @@ var (
// - <sender sig> <receiver sig> <preimage> <success_script> <control_block> // - <sender sig> <receiver sig> <preimage> <success_script> <control_block>
func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay, htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay,
leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) ( leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey,
*wire.MsgTx, error) { auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) {
// Create a version two transaction (as the success version of this // Create a version two transaction (as the success version of this
// spends an output with a CSV timeout). // spends an output with a CSV timeout).
@ -71,7 +72,7 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
// HTLC outputs. // HTLC outputs.
scriptInfo, err := SecondLevelHtlcScript( scriptInfo, err := SecondLevelHtlcScript(
chanType, initiator, revocationKey, delayKey, csvDelay, chanType, initiator, revocationKey, delayKey, csvDelay,
leaseExpiry, leaseExpiry, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -110,7 +111,8 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool, func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
cltvExpiry, csvDelay, leaseExpiry uint32, cltvExpiry, csvDelay, leaseExpiry uint32,
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) { revocationKey, delayKey *btcec.PublicKey,
auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) {
// Create a version two transaction (as the success version of this // Create a version two transaction (as the success version of this
// spends an output with a CSV timeout), and set the lock-time to the // spends an output with a CSV timeout), and set the lock-time to the
@ -134,7 +136,7 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool,
// HTLC outputs. // HTLC outputs.
scriptInfo, err := SecondLevelHtlcScript( scriptInfo, err := SecondLevelHtlcScript(
chanType, initiator, revocationKey, delayKey, csvDelay, chanType, initiator, revocationKey, delayKey, csvDelay,
leaseExpiry, leaseExpiry, auxLeaf,
) )
if err != nil { if err != nil {
return nil, err return nil, err