lnwallet+channeldb: add new AuxLeafStore for dynamic aux leaves

In this commit, we add a new AuxLeafStore which can be used to dynamically fetch the latest aux leaves for a given state. This is useful for custom channel types that will store some extra information in the form of a custom blob, then will use that information to derive the new leaf tapscript leaves that may be attached to reach state.
This commit is contained in:
Olaoluwa Osuntokun authored and Oliver Gugger committed 2024-08-28 13:21:08 +02:00
1 parent c36cd9298f
commit d95c1f93f3
6 files changed
+430 -45

No files matched your search

+108 -30
View File
@@ -11,6 +11,7 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -225,8 +226,8 @@ func (w *WitnessScriptDesc) WitnessScriptForPath(
// party learns of the preimage to the revocation hash, then they can claim all
// the settled funds in the channel, plus the unsettled funds.
func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
selfKey, revokeKey *btcec.PublicKey, csvDelay,
leaseExpiry uint32) (input.ScriptDescriptor, error) {
selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
switch {
// For taproot scripts, we'll need to make a slightly modified script
@@ -236,7 +237,7 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
// Our "redeem" script here is just the taproot witness program.
case chanType.IsTaproot():
return input.NewLocalCommitScriptTree(
csvDelay, selfKey, revokeKey, input.NoneTapLeaf(),
csvDelay, selfKey, revokeKey, auxLeaf,
)
// If we are the initiator of a leased channel, then we have an
@@ -290,8 +291,8 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
// script for. The second return value is the CSV delay of the output script,
// what must be satisfied in order to spend the output.
func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool,
remoteKey *btcec.PublicKey,
leaseExpiry uint32) (input.ScriptDescriptor, uint32, error) {
remoteKey *btcec.PublicKey, leaseExpiry uint32,
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, uint32, error) {
switch {
// If we are not the initiator of a leased channel, then the remote
@@ -320,7 +321,7 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool,
// with the sole tap leaf enforcing the 1 CSV delay.
case chanType.IsTaproot():
toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(
remoteKey, input.NoneTapLeaf(),
remoteKey, auxLeaf,
)
if err != nil {
return nil, 0, err
@@ -612,7 +613,7 @@ func CommitScriptAnchors(chanType channeldb.ChannelType,
// with, and abstracts the various ways of constructing commitment
// transactions.
type CommitmentBuilder struct {
// chanState is the underlying channels's state struct, used to
// chanState is the underlying channel's state struct, used to
// determine the type of channel we are dealing with, and relevant
// parameters.
chanState *channeldb.OpenChannel
@@ -620,18 +621,25 @@ type CommitmentBuilder struct {
// obfuscator is a 48-bit state hint that's used to obfuscate the
// current state number on the commitment transactions.
obfuscator [StateHintSize]byte
// auxLeafStore is an interface that allows us to fetch auxiliary
// tapscript leaves for the commitment output.
auxLeafStore fn.Option[AuxLeafStore]
}
// NewCommitmentBuilder creates a new CommitmentBuilder from chanState.
func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder {
func NewCommitmentBuilder(chanState *channeldb.OpenChannel,
leafStore fn.Option[AuxLeafStore]) *CommitmentBuilder {
// The anchor channel type MUST be tweakless.
if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() {
panic("invalid channel type combination")
}
return &CommitmentBuilder{
chanState: chanState,
obfuscator: createStateHintObfuscator(chanState),
chanState: chanState,
obfuscator: createStateHintObfuscator(chanState),
auxLeafStore: leafStore,
}
}
@@ -684,9 +692,9 @@ type unsignedCommitmentTx struct {
// fees, but after anchor outputs.
func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
theirBalance lnwire.MilliSatoshi, whoseCommit lntypes.ChannelParty,
feePerKw chainfee.SatPerKWeight, height uint64,
filteredHTLCView *HtlcView,
keyRing *CommitmentKeyRing) (*unsignedCommitmentTx, error) {
feePerKw chainfee.SatPerKWeight, height uint64, originalHtlcView,
filteredHTLCView *HtlcView, keyRing *CommitmentKeyRing,
prevCommit *commitment) (*unsignedCommitmentTx, error) {
dustLimit := cb.chanState.LocalChanCfg.DustLimit
if whoseCommit.IsRemote() {
@@ -752,6 +760,23 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
err error
)
// Before we create the commitment transaction below, we'll try to see
// if there're any aux leaves that need to be a part of the tapscript
// tree. We'll only do this if we have a custom blob defined though.
auxResult, err := fn.MapOptionZ(
cb.auxLeafStore,
func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return auxLeavesFromView(
s, cb.chanState, prevCommit.customBlob,
originalHtlcView, whoseCommit, ourBalance,
theirBalance, *keyRing,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// Depending on whether the transaction is ours or not, we call
// CreateCommitTx with parameters matching the perspective, to generate
// a new commitment transaction with all the latest unsettled/un-timed
@@ -766,6 +791,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
numHTLCs, cb.chanState.IsInitiator, leaseExpiry,
auxResult.AuxLeaves,
)
} else {
commitTx, err = CreateCommitTx(
@@ -773,6 +799,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
numHTLCs, !cb.chanState.IsInitiator, leaseExpiry,
auxResult.AuxLeaves,
)
}
if err != nil {
@@ -789,6 +816,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
// commitment outputs and should correspond to zero values for the
// purposes of sorting.
cltvs := make([]uint32, len(commitTx.TxOut))
htlcIndexes := make([]input.HtlcIndex, len(commitTx.TxOut))
for _, htlc := range filteredHTLCView.OurUpdates {
if HtlcIsDust(
cb.chanState.ChanType, false, whoseCommit, feePerKw,
@@ -805,7 +833,11 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
if err != nil {
return nil, err
}
cltvs = append(cltvs, htlc.Timeout) // nolint:makezero
// We want to add the CLTV and HTLC index to their respective
// slices, even if we already pre-allocated them.
cltvs = append(cltvs, htlc.Timeout) //nolint
htlcIndexes = append(htlcIndexes, htlc.HtlcIndex) //nolint
}
for _, htlc := range filteredHTLCView.TheirUpdates {
if HtlcIsDust(
@@ -823,7 +855,11 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
if err != nil {
return nil, err
}
cltvs = append(cltvs, htlc.Timeout) // nolint:makezero
// We want to add the CLTV and HTLC index to their respective
// slices, even if we already pre-allocated them.
cltvs = append(cltvs, htlc.Timeout) //nolint
htlcIndexes = append(htlcIndexes, htlc.HtlcIndex) //nolint
}
// Set the state hint of the commitment transaction to facilitate
@@ -835,9 +871,16 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
}
// Sort the transactions according to the agreed upon canonical
// ordering. This lets us skip sending the entire transaction over,
// instead we'll just send signatures.
InPlaceCommitSort(commitTx, cltvs)
// ordering (which might be customized for custom channel types, but
// deterministic and both parties will arrive at the same result). This
// lets us skip sending the entire transaction over, instead we'll just
// send signatures.
commitSort := auxResult.CommitSortFunc.UnwrapOr(DefaultCommitSort)
err = commitSort(commitTx, cltvs, htlcIndexes)
if err != nil {
return nil, fmt.Errorf("unable to sort commitment "+
"transaction: %w", err)
}
// Next, we'll ensure that we don't accidentally create a commitment
// transaction which would be invalid by consensus.
@@ -879,24 +922,33 @@ func CreateCommitTx(chanType channeldb.ChannelType,
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
amountToLocal, amountToRemote btcutil.Amount,
numHTLCs int64, initiator bool, leaseExpiry uint32) (*wire.MsgTx, error) {
numHTLCs int64, initiator bool, leaseExpiry uint32,
auxLeaves fn.Option[CommitAuxLeaves]) (*wire.MsgTx, error) {
// First, we create the script for the delayed "pay-to-self" output.
// This output has 2 main redemption clauses: either we can redeem the
// output after a relative block delay, or the remote node can claim
// the funds with the revocation key if we broadcast a revoked
// commitment transaction.
localAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
})(auxLeaves)
toLocalScript, err := CommitScriptToSelf(
chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey,
uint32(localChanCfg.CsvDelay), leaseExpiry,
fn.FlattenOption(localAuxLeaf),
)
if err != nil {
return nil, err
}
// Next, we create the script paying to the remote.
remoteAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
})(auxLeaves)
toRemoteScript, _, err := CommitScriptToRemote(
chanType, initiator, keyRing.ToRemoteKey, leaseExpiry,
fn.FlattenOption(remoteAuxLeaf),
)
if err != nil {
return nil, err
@@ -1201,7 +1253,8 @@ func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty,
// output scripts and compares them against the outputs inside the commitment
// to find the match.
func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
chanState *channeldb.OpenChannel) (uint32, uint32, error) {
chanState *channeldb.OpenChannel,
leafStore fn.Option[AuxLeafStore]) (uint32, uint32, error) {
// Init the output indexes as empty.
ourIndex := uint32(channeldb.OutputIndexEmpty)
@@ -1231,26 +1284,51 @@ func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
leaseExpiry = chanState.ThawHeight
}
// Map the scripts from our PoV. When facing a local commitment, the to
// local output belongs to us and the to remote output belongs to them.
// When facing a remote commitment, the to local output belongs to them
// and the to remote output belongs to us.
// If we have a custom blob, then we'll attempt to fetch the aux leaves
// for this state.
auxResult, err := fn.MapOptionZ(
leafStore, func(a AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return a.FetchLeavesFromCommit(
NewAuxChanState(chanState), chanCommit,
*keyRing,
)
},
).Unpack()
if err != nil {
return ourIndex, theirIndex, fmt.Errorf("unable to fetch aux "+
"leaves: %w", err)
}
// Compute the to local script. From our PoV, when facing a remote
// commitment, the to local output belongs to them.
// Map the scripts from our PoV. When facing a local commitment, the
// to_local output belongs to us and the to_remote output belongs to
// them. When facing a remote commitment, the to_local output belongs to
// them and the to_remote output belongs to us.
// Compute the to_local script. From our PoV, when facing a remote
// commitment, the to_local output belongs to them.
localAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
},
)(auxResult.AuxLeaves)
theirScript, err := CommitScriptToSelf(
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, theirDelay, leaseExpiry,
keyRing.RevocationKey, theirDelay, leaseExpiry, localAuxLeaf,
)
if err != nil {
return ourIndex, theirIndex, err
}
// Compute the to remote script. From our PoV, when facing a remote
// commitment, the to remote output belongs to us.
// Compute the to_remote script. From our PoV, when facing a remote
// commitment, the to_remote output belongs to us.
remoteAuxLeaf := fn.ChainOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
},
)(auxResult.AuxLeaves)
ourScript, _, err := CommitScriptToRemote(
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry,
leaseExpiry, remoteAuxLeaf,
)
if err != nil {
return ourIndex, theirIndex, err