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:
1 parent
c36cd9298f
commit
d95c1f93f3
6 files changed
+430
-45
No files matched your search
+108
-30
@@ -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
|
||||
|
||||
Reference in new issue
Block a user