lnwallet: add new aux resolver interface
This will be used by external callers to modify the way we resolve contracts on chain. For a given contract, we'll store an extra "blob", that will later be presented during the sweeping phase.
This commit is contained in:
8 files changed
+303
-23
No files matched your search
@@ -187,6 +187,10 @@ type AuxComponents struct {
|
|||||||
// AuxChanCloser is an optional channel closer that can be used to
|
// AuxChanCloser is an optional channel closer that can be used to
|
||||||
// modify the way a coop-close transaction is constructed.
|
// modify the way a coop-close transaction is constructed.
|
||||||
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
|
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
|
||||||
|
|
||||||
|
// AuxContractResolver is an optional interface that can be used to
|
||||||
|
// modify the way contracts are resolved.
|
||||||
|
AuxContractResolver fn.Option[lnwallet.AuxContractResolver]
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultWalletImpl is the default implementation of our normal, btcwallet
|
// DefaultWalletImpl is the default implementation of our normal, btcwallet
|
||||||
|
|||||||
@@ -1592,6 +1592,9 @@ func testBreachSpends(t *testing.T, test breachTest) {
|
|||||||
retribution, err := lnwallet.NewBreachRetribution(
|
retribution, err := lnwallet.NewBreachRetribution(
|
||||||
alice.State(), height, 1, forceCloseTx,
|
alice.State(), height, 1, forceCloseTx,
|
||||||
fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}),
|
fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}),
|
||||||
|
fn.Some[lnwallet.AuxContractResolver](
|
||||||
|
&lnwallet.MockAuxContractResolver{},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create breach retribution")
|
require.NoError(t, err, "unable to create breach retribution")
|
||||||
|
|
||||||
@@ -1802,6 +1805,9 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) {
|
|||||||
retribution, err := lnwallet.NewBreachRetribution(
|
retribution, err := lnwallet.NewBreachRetribution(
|
||||||
alice.State(), height, uint32(blockHeight), forceCloseTx,
|
alice.State(), height, uint32(blockHeight), forceCloseTx,
|
||||||
fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}),
|
fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}),
|
||||||
|
fn.Some[lnwallet.AuxContractResolver](
|
||||||
|
&lnwallet.MockAuxContractResolver{},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create breach retribution")
|
require.NoError(t, err, "unable to create breach retribution")
|
||||||
|
|
||||||
|
|||||||
@@ -196,6 +196,9 @@ type chainWatcherConfig struct {
|
|||||||
|
|
||||||
// auxLeafStore can be used to fetch information for custom channels.
|
// auxLeafStore can be used to fetch information for custom channels.
|
||||||
auxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
auxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||||
|
|
||||||
|
// auxResolver is used to supplement contract resolution.
|
||||||
|
auxResolver fn.Option[lnwallet.AuxContractResolver]
|
||||||
}
|
}
|
||||||
|
|
||||||
// chainWatcher is a system that's assigned to every active channel. The duty
|
// chainWatcher is a system that's assigned to every active channel. The duty
|
||||||
@@ -896,7 +899,7 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
|
|||||||
spendHeight := uint32(commitSpend.SpendingHeight)
|
spendHeight := uint32(commitSpend.SpendingHeight)
|
||||||
retribution, err := lnwallet.NewBreachRetribution(
|
retribution, err := lnwallet.NewBreachRetribution(
|
||||||
c.cfg.chanState, broadcastStateNum, spendHeight,
|
c.cfg.chanState, broadcastStateNum, spendHeight,
|
||||||
commitSpend.SpendingTx, c.cfg.auxLeafStore,
|
commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver,
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -1147,7 +1150,7 @@ func (c *chainWatcher) dispatchLocalForceClose(
|
|||||||
|
|
||||||
forceClose, err := lnwallet.NewLocalForceCloseSummary(
|
forceClose, err := lnwallet.NewLocalForceCloseSummary(
|
||||||
c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
|
c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
|
||||||
c.cfg.auxLeafStore,
|
c.cfg.auxLeafStore, c.cfg.auxResolver,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1239,8 +1242,8 @@ func (c *chainWatcher) dispatchRemoteForceClose(
|
|||||||
// materials required to let each subscriber sweep the funds in the
|
// materials required to let each subscriber sweep the funds in the
|
||||||
// channel on-chain.
|
// channel on-chain.
|
||||||
uniClose, err := lnwallet.NewUnilateralCloseSummary(
|
uniClose, err := lnwallet.NewUnilateralCloseSummary(
|
||||||
c.cfg.chanState, c.cfg.signer, commitSpend,
|
c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit,
|
||||||
remoteCommit, commitPoint, c.cfg.auxLeafStore,
|
commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package lnwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/tlv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CloseType is an enum that represents the type of close that we are trying to
|
||||||
|
// resolve.
|
||||||
|
type CloseType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LocalForceClose represents a local force close.
|
||||||
|
LocalForceClose CloseType = iota
|
||||||
|
|
||||||
|
// RemoteForceClose represents a remote force close.
|
||||||
|
RemoteForceClose
|
||||||
|
|
||||||
|
// Breach represents a breach by the remote party.
|
||||||
|
Breach
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResolutionReq is used to ask an outside sub-system for additional
|
||||||
|
// information needed to resolve a contract.
|
||||||
|
type ResolutionReq struct {
|
||||||
|
// ChanPoint is the channel point of the channel that we are trying to
|
||||||
|
// resolve.
|
||||||
|
ChanPoint wire.OutPoint
|
||||||
|
|
||||||
|
// ShortChanID is the short channel ID of the channel that we are
|
||||||
|
// trying to resolve.
|
||||||
|
ShortChanID lnwire.ShortChannelID
|
||||||
|
|
||||||
|
// Initiator is a bool if we're the initiator of the channel.
|
||||||
|
Initiator bool
|
||||||
|
|
||||||
|
// CommitBlob is an optional commit blob for the channel.
|
||||||
|
CommitBlob fn.Option[tlv.Blob]
|
||||||
|
|
||||||
|
// FundingBlob is an optional funding blob for the channel.
|
||||||
|
FundingBlob fn.Option[tlv.Blob]
|
||||||
|
|
||||||
|
// Type is the type of the witness that we are trying to resolve.
|
||||||
|
Type input.WitnessType
|
||||||
|
|
||||||
|
// CloseType is the type of close that we are trying to resolve.
|
||||||
|
CloseType CloseType
|
||||||
|
|
||||||
|
// CommitTx is the force close commitment transaction.
|
||||||
|
CommitTx *wire.MsgTx
|
||||||
|
|
||||||
|
// CommitFee is the fee that was paid for the commitment transaction.
|
||||||
|
CommitFee btcutil.Amount
|
||||||
|
|
||||||
|
// ContractPoint is the outpoint of the contract we're trying to
|
||||||
|
// resolve.
|
||||||
|
ContractPoint wire.OutPoint
|
||||||
|
|
||||||
|
// SignDesc is the sign descriptor for the contract.
|
||||||
|
SignDesc input.SignDescriptor
|
||||||
|
|
||||||
|
// KeyRing is the key ring for the channel.
|
||||||
|
KeyRing *CommitmentKeyRing
|
||||||
|
|
||||||
|
// CsvDelay is the CSV delay for the local output for this commitment.
|
||||||
|
CsvDelay uint32
|
||||||
|
|
||||||
|
// BreachCsvDelay is the CSV delay for the remote output. This is only
|
||||||
|
// set when the CloseType is Breach. This indicates the CSV delay to
|
||||||
|
// use for the remote party's to_local delayed output, that is now
|
||||||
|
// rightfully ours in a breach situation.
|
||||||
|
BreachCsvDelay fn.Option[uint32]
|
||||||
|
|
||||||
|
// CltvDelay is the CLTV delay for the outpoint.
|
||||||
|
CltvDelay fn.Option[uint32]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuxContractResolver is an interface that is used to resolve contracts that
|
||||||
|
// may need additional outside information to resolve correctly.
|
||||||
|
type AuxContractResolver interface {
|
||||||
|
// ResolveContract is called to resolve a contract that needs
|
||||||
|
// additional information to resolve properly. If no extra information
|
||||||
|
// is required, a nil Result error is returned.
|
||||||
|
ResolveContract(ResolutionReq) fn.Result[tlv.Blob]
|
||||||
|
}
|
||||||
+175
-19
@@ -764,6 +764,10 @@ type LightningChannel struct {
|
|||||||
// custom channel variants.
|
// custom channel variants.
|
||||||
auxSigner fn.Option[AuxSigner]
|
auxSigner fn.Option[AuxSigner]
|
||||||
|
|
||||||
|
// auxResolver is an optional component that can be used to modify the
|
||||||
|
// way contracts are resolved.
|
||||||
|
auxResolver fn.Option[AuxContractResolver]
|
||||||
|
|
||||||
// Capacity is the total capacity of this channel.
|
// Capacity is the total capacity of this channel.
|
||||||
Capacity btcutil.Amount
|
Capacity btcutil.Amount
|
||||||
|
|
||||||
@@ -824,8 +828,9 @@ type channelOpts struct {
|
|||||||
localNonce *musig2.Nonces
|
localNonce *musig2.Nonces
|
||||||
remoteNonce *musig2.Nonces
|
remoteNonce *musig2.Nonces
|
||||||
|
|
||||||
leafStore fn.Option[AuxLeafStore]
|
leafStore fn.Option[AuxLeafStore]
|
||||||
auxSigner fn.Option[AuxSigner]
|
auxSigner fn.Option[AuxSigner]
|
||||||
|
auxResolver fn.Option[AuxContractResolver]
|
||||||
|
|
||||||
skipNonceInit bool
|
skipNonceInit bool
|
||||||
}
|
}
|
||||||
@@ -871,6 +876,14 @@ func WithAuxSigner(signer AuxSigner) ChannelOpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAuxResolver is used to specify a custom aux contract resolver for the
|
||||||
|
// channel.
|
||||||
|
func WithAuxResolver(resolver AuxContractResolver) ChannelOpt {
|
||||||
|
return func(o *channelOpts) {
|
||||||
|
o.auxResolver = fn.Some[AuxContractResolver](resolver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// defaultChannelOpts returns the set of default options for a new channel.
|
// defaultChannelOpts returns the set of default options for a new channel.
|
||||||
func defaultChannelOpts() *channelOpts {
|
func defaultChannelOpts() *channelOpts {
|
||||||
return &channelOpts{}
|
return &channelOpts{}
|
||||||
@@ -924,6 +937,7 @@ func NewLightningChannel(signer input.Signer,
|
|||||||
Signer: signer,
|
Signer: signer,
|
||||||
leafStore: opts.leafStore,
|
leafStore: opts.leafStore,
|
||||||
auxSigner: opts.auxSigner,
|
auxSigner: opts.auxSigner,
|
||||||
|
auxResolver: opts.auxResolver,
|
||||||
sigPool: sigPool,
|
sigPool: sigPool,
|
||||||
currentHeight: localCommit.CommitHeight,
|
currentHeight: localCommit.CommitHeight,
|
||||||
commitChains: commitChains,
|
commitChains: commitChains,
|
||||||
@@ -1884,6 +1898,10 @@ type HtlcRetribution struct {
|
|||||||
// this HTLC was offered by us. This flag is used determine the exact
|
// this HTLC was offered by us. This flag is used determine the exact
|
||||||
// witness type should be used to sweep the output.
|
// witness type should be used to sweep the output.
|
||||||
IsIncoming bool
|
IsIncoming bool
|
||||||
|
|
||||||
|
// ResolutionBlob is a blob used for aux channels that permits a
|
||||||
|
// spender of this output to claim all funds.
|
||||||
|
ResolutionBlob fn.Option[tlv.Blob]
|
||||||
}
|
}
|
||||||
|
|
||||||
// BreachRetribution contains all the data necessary to bring a channel
|
// BreachRetribution contains all the data necessary to bring a channel
|
||||||
@@ -1953,6 +1971,14 @@ type BreachRetribution struct {
|
|||||||
// breaching commitment transaction. This allows downstream clients to
|
// breaching commitment transaction. This allows downstream clients to
|
||||||
// have access to the public keys used in the scripts.
|
// have access to the public keys used in the scripts.
|
||||||
KeyRing *CommitmentKeyRing
|
KeyRing *CommitmentKeyRing
|
||||||
|
|
||||||
|
// LocalResolutionBlob is a blob used for aux channels that permits an
|
||||||
|
// honest party to sweep the local commitment output.
|
||||||
|
LocalResolutionBlob fn.Option[tlv.Blob]
|
||||||
|
|
||||||
|
// RemoteResolutionBlob is a blob used for aux channels that permits an
|
||||||
|
// honest party to sweep the remote commitment output.
|
||||||
|
RemoteResolutionBlob fn.Option[tlv.Blob]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBreachRetribution creates a new fully populated BreachRetribution for the
|
// NewBreachRetribution creates a new fully populated BreachRetribution for the
|
||||||
@@ -1964,7 +1990,9 @@ type BreachRetribution struct {
|
|||||||
// the required fields then ErrRevLogDataMissing will be returned.
|
// the required fields then ErrRevLogDataMissing will be returned.
|
||||||
func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||||
breachHeight uint32, spendTx *wire.MsgTx,
|
breachHeight uint32, spendTx *wire.MsgTx,
|
||||||
leafStore fn.Option[AuxLeafStore]) (*BreachRetribution, error) {
|
leafStore fn.Option[AuxLeafStore],
|
||||||
|
auxResolver fn.Option[AuxContractResolver]) (*BreachRetribution,
|
||||||
|
error) {
|
||||||
|
|
||||||
// Query the on-disk revocation log for the snapshot which was recorded
|
// Query the on-disk revocation log for the snapshot which was recorded
|
||||||
// at this particular state num. Based on whether a legacy revocation
|
// at this particular state num. Based on whether a legacy revocation
|
||||||
@@ -2127,6 +2155,38 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At this point, we'll check to see if we need any extra
|
||||||
|
// resolution data for this output.
|
||||||
|
resolveReq := ResolutionReq{
|
||||||
|
ChanPoint: chanState.FundingOutpoint,
|
||||||
|
ShortChanID: chanState.ShortChanID(),
|
||||||
|
Initiator: chanState.IsInitiator,
|
||||||
|
FundingBlob: chanState.CustomBlob,
|
||||||
|
Type: input.TaprootRemoteCommitSpend,
|
||||||
|
CloseType: Breach,
|
||||||
|
CommitTx: spendTx,
|
||||||
|
SignDesc: *br.LocalOutputSignDesc,
|
||||||
|
KeyRing: keyRing,
|
||||||
|
CsvDelay: ourDelay,
|
||||||
|
BreachCsvDelay: fn.Some(theirDelay),
|
||||||
|
CommitFee: chanState.RemoteCommitment.CommitFee,
|
||||||
|
}
|
||||||
|
if revokedLog != nil {
|
||||||
|
resolveReq.CommitBlob = revokedLog.CustomBlob.ValOpt()
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveBlob := fn.MapOptionZ(
|
||||||
|
auxResolver,
|
||||||
|
func(a AuxContractResolver) fn.Result[tlv.Blob] {
|
||||||
|
return a.ResolveContract(resolveReq)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err := resolveBlob.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to aux resolve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
br.LocalResolutionBlob = resolveBlob.Option()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similarly, if their balance exceeds the remote party's dust limit,
|
// Similarly, if their balance exceeds the remote party's dust limit,
|
||||||
@@ -2174,6 +2234,37 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At this point, we'll check to see if we need any extra
|
||||||
|
// resolution data for this output.
|
||||||
|
resolveReq := ResolutionReq{
|
||||||
|
ChanPoint: chanState.FundingOutpoint,
|
||||||
|
ShortChanID: chanState.ShortChanID(),
|
||||||
|
Initiator: chanState.IsInitiator,
|
||||||
|
FundingBlob: chanState.CustomBlob,
|
||||||
|
Type: input.TaprootCommitmentRevoke,
|
||||||
|
CloseType: Breach,
|
||||||
|
CommitTx: spendTx,
|
||||||
|
SignDesc: *br.RemoteOutputSignDesc,
|
||||||
|
KeyRing: keyRing,
|
||||||
|
CsvDelay: theirDelay,
|
||||||
|
BreachCsvDelay: fn.Some(theirDelay),
|
||||||
|
CommitFee: chanState.RemoteCommitment.CommitFee,
|
||||||
|
}
|
||||||
|
if revokedLog != nil {
|
||||||
|
resolveReq.CommitBlob = revokedLog.CustomBlob.ValOpt()
|
||||||
|
}
|
||||||
|
resolveBlob := fn.MapOptionZ(
|
||||||
|
auxResolver,
|
||||||
|
func(a AuxContractResolver) fn.Result[tlv.Blob] {
|
||||||
|
return a.ResolveContract(resolveReq)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err := resolveBlob.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to aux resolve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
br.RemoteResolutionBlob = resolveBlob.Option()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, with all the necessary data constructed, we can pad the
|
// Finally, with all the necessary data constructed, we can pad the
|
||||||
@@ -6473,6 +6564,11 @@ type CommitOutputResolution struct {
|
|||||||
// that pay to the local party within the broadcast commitment
|
// that pay to the local party within the broadcast commitment
|
||||||
// transaction.
|
// transaction.
|
||||||
MaturityDelay uint32
|
MaturityDelay uint32
|
||||||
|
|
||||||
|
// ResolutionBlob is a blob used for aux channels that permits a
|
||||||
|
// spender of the output to properly resolve it in the case of a force
|
||||||
|
// close.
|
||||||
|
ResolutionBlob fn.Option[tlv.Blob]
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnilateralCloseSummary describes the details of a detected unilateral
|
// UnilateralCloseSummary describes the details of a detected unilateral
|
||||||
@@ -6530,7 +6626,9 @@ type UnilateralCloseSummary struct {
|
|||||||
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
|
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
|
||||||
signer input.Signer, commitSpend *chainntnfs.SpendDetail,
|
signer input.Signer, commitSpend *chainntnfs.SpendDetail,
|
||||||
remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey,
|
remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey,
|
||||||
leafStore fn.Option[AuxLeafStore]) (*UnilateralCloseSummary, error) {
|
leafStore fn.Option[AuxLeafStore],
|
||||||
|
auxResolver fn.Option[AuxContractResolver]) (*UnilateralCloseSummary,
|
||||||
|
error) {
|
||||||
|
|
||||||
// First, we'll generate the commitment point and the revocation point
|
// First, we'll generate the commitment point and the revocation point
|
||||||
// so we can re-construct the HTLC state and also our payment key.
|
// so we can re-construct the HTLC state and also our payment key.
|
||||||
@@ -6554,11 +6652,17 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
|
|||||||
|
|
||||||
// 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
|
||||||
|
selfPoint *wire.OutPoint
|
||||||
|
localBalance int64
|
||||||
|
isRemoteInitiator = !chanState.IsInitiator
|
||||||
|
commitTxBroadcast = commitSpend.SpendingTx
|
||||||
|
)
|
||||||
|
|
||||||
if chanState.ChanType.HasLeaseExpiration() {
|
if chanState.ChanType.HasLeaseExpiration() {
|
||||||
leaseExpiry = chanState.ThawHeight
|
leaseExpiry = chanState.ThawHeight
|
||||||
}
|
}
|
||||||
isRemoteInitiator := !chanState.IsInitiator
|
|
||||||
htlcResolutions, err := extractHtlcResolutions(
|
htlcResolutions, err := extractHtlcResolutions(
|
||||||
chainfee.SatPerKWeight(remoteCommit.FeePerKw), commitType,
|
chainfee.SatPerKWeight(remoteCommit.FeePerKw), commitType,
|
||||||
signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
|
signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
|
||||||
@@ -6567,12 +6671,10 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
|
|||||||
auxResult.AuxLeaves,
|
auxResult.AuxLeaves,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create htlc "+
|
return nil, fmt.Errorf("unable to create htlc resolutions: %w",
|
||||||
"resolutions: %v", err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commitTxBroadcast := commitSpend.SpendingTx
|
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -6587,14 +6689,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create self commit "+
|
return nil, fmt.Errorf("unable to create self commit "+
|
||||||
"script: %v", err)
|
"script: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
selfPoint *wire.OutPoint
|
|
||||||
localBalance int64
|
|
||||||
)
|
|
||||||
|
|
||||||
for outputIndex, txOut := range commitTxBroadcast.TxOut {
|
for outputIndex, txOut := range commitTxBroadcast.TxOut {
|
||||||
if bytes.Equal(txOut.PkScript, selfScript.PkScript()) {
|
if bytes.Equal(txOut.PkScript, selfScript.PkScript()) {
|
||||||
selfPoint = &wire.OutPoint{
|
selfPoint = &wire.OutPoint{
|
||||||
@@ -6657,6 +6753,35 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At this point, we'll check to see if we need any extra
|
||||||
|
// resolution data for this output.
|
||||||
|
resolveReq := ResolutionReq{
|
||||||
|
ChanPoint: chanState.FundingOutpoint,
|
||||||
|
ShortChanID: chanState.ShortChanID(),
|
||||||
|
Initiator: chanState.IsInitiator,
|
||||||
|
CommitBlob: chanState.RemoteCommitment.CustomBlob,
|
||||||
|
FundingBlob: chanState.CustomBlob,
|
||||||
|
Type: input.TaprootRemoteCommitSpend,
|
||||||
|
CloseType: RemoteForceClose,
|
||||||
|
CommitTx: commitTxBroadcast,
|
||||||
|
ContractPoint: *selfPoint,
|
||||||
|
SignDesc: commitResolution.SelfOutputSignDesc,
|
||||||
|
KeyRing: keyRing,
|
||||||
|
CsvDelay: maturityDelay,
|
||||||
|
CommitFee: chanState.RemoteCommitment.CommitFee,
|
||||||
|
}
|
||||||
|
resolveBlob := fn.MapOptionZ(
|
||||||
|
auxResolver,
|
||||||
|
func(a AuxContractResolver) fn.Result[tlv.Blob] {
|
||||||
|
return a.ResolveContract(resolveReq)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err := resolveBlob.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to aux resolve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commitResolution.ResolutionBlob = resolveBlob.Option()
|
||||||
}
|
}
|
||||||
|
|
||||||
closeSummary := channeldb.ChannelCloseSummary{
|
closeSummary := channeldb.ChannelCloseSummary{
|
||||||
@@ -7513,7 +7638,7 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
|
|||||||
localCommitment := lc.channelState.LocalCommitment
|
localCommitment := lc.channelState.LocalCommitment
|
||||||
summary, err := NewLocalForceCloseSummary(
|
summary, err := NewLocalForceCloseSummary(
|
||||||
lc.channelState, lc.Signer, commitTx,
|
lc.channelState, lc.Signer, commitTx,
|
||||||
localCommitment.CommitHeight, lc.leafStore,
|
localCommitment.CommitHeight, lc.leafStore, lc.auxResolver,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to gen force close "+
|
return nil, fmt.Errorf("unable to gen force close "+
|
||||||
@@ -7531,7 +7656,9 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
|
|||||||
// transaction corresponding to localCommit.
|
// transaction corresponding to localCommit.
|
||||||
func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
|
func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
|
||||||
signer input.Signer, commitTx *wire.MsgTx, stateNum uint64,
|
signer input.Signer, commitTx *wire.MsgTx, stateNum uint64,
|
||||||
leafStore fn.Option[AuxLeafStore]) (*LocalForceCloseSummary, error) {
|
leafStore fn.Option[AuxLeafStore],
|
||||||
|
auxResolver fn.Option[AuxContractResolver]) (*LocalForceCloseSummary,
|
||||||
|
error) {
|
||||||
|
|
||||||
// Re-derive the original pkScript for to-self output within the
|
// Re-derive the original pkScript for to-self output within the
|
||||||
// commitment transaction. We'll need this to find the corresponding
|
// commitment transaction. We'll need this to find the corresponding
|
||||||
@@ -7655,6 +7782,35 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At this point, we'll check to see if we need any extra
|
||||||
|
// resolution data for this output.
|
||||||
|
resolveBlob := fn.MapOptionZ(
|
||||||
|
auxResolver,
|
||||||
|
func(a AuxContractResolver) fn.Result[tlv.Blob] {
|
||||||
|
//nolint:lll
|
||||||
|
return a.ResolveContract(ResolutionReq{
|
||||||
|
ChanPoint: chanState.FundingOutpoint,
|
||||||
|
ShortChanID: chanState.ShortChanID(),
|
||||||
|
Initiator: chanState.IsInitiator,
|
||||||
|
CommitBlob: chanState.LocalCommitment.CustomBlob,
|
||||||
|
FundingBlob: chanState.CustomBlob,
|
||||||
|
Type: input.TaprootLocalCommitSpend,
|
||||||
|
CloseType: LocalForceClose,
|
||||||
|
CommitTx: commitTx,
|
||||||
|
ContractPoint: commitResolution.SelfOutPoint,
|
||||||
|
SignDesc: commitResolution.SelfOutputSignDesc,
|
||||||
|
KeyRing: keyRing,
|
||||||
|
CsvDelay: csvTimeout,
|
||||||
|
CommitFee: chanState.LocalCommitment.CommitFee,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err := resolveBlob.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to aux resolve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commitResolution.ResolutionBlob = resolveBlob.Option()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once the delay output has been found (if it exists), then we'll also
|
// Once the delay output has been found (if it exists), then we'll also
|
||||||
|
|||||||
@@ -6043,6 +6043,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
|||||||
aliceChannel.channelState.RemoteCommitment,
|
aliceChannel.channelState.RemoteCommitment,
|
||||||
aliceChannel.channelState.RemoteCurrentRevocation,
|
aliceChannel.channelState.RemoteCurrentRevocation,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create alice close summary")
|
require.NoError(t, err, "unable to create alice close summary")
|
||||||
|
|
||||||
@@ -6193,6 +6194,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
|||||||
aliceChannel.channelState.RemoteCommitment,
|
aliceChannel.channelState.RemoteCommitment,
|
||||||
aliceChannel.channelState.RemoteCurrentRevocation,
|
aliceChannel.channelState.RemoteCurrentRevocation,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create alice close summary")
|
require.NoError(t, err, "unable to create alice close summary")
|
||||||
|
|
||||||
@@ -6211,6 +6213,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
|||||||
aliceRemoteChainTip.Commitment,
|
aliceRemoteChainTip.Commitment,
|
||||||
aliceChannel.channelState.RemoteNextRevocation,
|
aliceChannel.channelState.RemoteNextRevocation,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create alice close summary")
|
require.NoError(t, err, "unable to create alice close summary")
|
||||||
|
|
||||||
@@ -7092,6 +7095,7 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) {
|
|||||||
breachRet, err := NewBreachRetribution(
|
breachRet, err := NewBreachRetribution(
|
||||||
aliceChannel.channelState, revokedStateNum, 100, breachTx,
|
aliceChannel.channelState, revokedStateNum, 100, breachTx,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create breach retribution")
|
require.NoError(t, err, "unable to create breach retribution")
|
||||||
|
|
||||||
@@ -10653,6 +10657,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
_, err = NewBreachRetribution(
|
_, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
||||||
|
|
||||||
@@ -10661,6 +10666,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
_, err = NewBreachRetribution(
|
_, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum, breachHeight, nil,
|
aliceChannel.channelState, stateNum, breachHeight, nil,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
|
||||||
|
|
||||||
@@ -10707,6 +10713,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
br, err := NewBreachRetribution(
|
br, err := NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
aliceChannel.channelState, stateNum, breachHeight, breachTx,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -10719,6 +10726,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
br, err = NewBreachRetribution(
|
br, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum, breachHeight, nil,
|
aliceChannel.channelState, stateNum, breachHeight, nil,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assertRetribution(br, 1, 0)
|
assertRetribution(br, 1, 0)
|
||||||
@@ -10728,6 +10736,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
_, err = NewBreachRetribution(
|
_, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum+1, breachHeight, breachTx,
|
aliceChannel.channelState, stateNum+1, breachHeight, breachTx,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
||||||
|
|
||||||
@@ -10736,6 +10745,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
|
|||||||
_, err = NewBreachRetribution(
|
_, err = NewBreachRetribution(
|
||||||
aliceChannel.channelState, stateNum+1, breachHeight, nil,
|
aliceChannel.channelState, stateNum+1, breachHeight, nil,
|
||||||
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
fn.Some[AuxLeafStore](&MockAuxLeafStore{}),
|
||||||
|
fn.Some[AuxContractResolver](&MockAuxContractResolver{}),
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,3 +493,14 @@ func (a *MockAuxSigner) VerifySecondLevelSigs(chanState AuxChanState,
|
|||||||
|
|
||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MockAuxContractResolver struct{}
|
||||||
|
|
||||||
|
// ResolveContract is called to resolve a contract that needs
|
||||||
|
// additional information to resolve properly. If no extra information
|
||||||
|
// is required, a nil Result error is returned.
|
||||||
|
func (*MockAuxContractResolver) ResolveContract(
|
||||||
|
ResolutionReq) fn.Result[tlv.Blob] {
|
||||||
|
|
||||||
|
return fn.Ok[tlv.Blob](nil)
|
||||||
|
}
|
||||||
@@ -1631,6 +1631,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
br, err := lnwallet.NewBreachRetribution(
|
br, err := lnwallet.NewBreachRetribution(
|
||||||
channel, commitHeight, 0, nil,
|
channel, commitHeight, 0, nil,
|
||||||
implCfg.AuxLeafStore,
|
implCfg.AuxLeafStore,
|
||||||
|
implCfg.AuxContractResolver,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
|||||||
Reference in new issue
Block a user