mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-22 22:25:24 +01:00
Merge pull request #8861 from lightningnetwork/custom-channels-breach
multi: hook up breach arb with new aux sweeper sub-system
This commit is contained in:
commit
4e968d9b52
9 changed files with 249 additions and 94 deletions
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
|
@ -149,7 +150,7 @@ type BreachConfig struct {
|
|||
Estimator chainfee.Estimator
|
||||
|
||||
// GenSweepScript generates the receiving scripts for swept outputs.
|
||||
GenSweepScript func() ([]byte, error)
|
||||
GenSweepScript func() fn.Result[lnwallet.AddrWithKey]
|
||||
|
||||
// Notifier provides a publish/subscribe interface for event driven
|
||||
// notifications regarding the confirmation of txids.
|
||||
|
@ -174,6 +175,10 @@ type BreachConfig struct {
|
|||
// breached channels. This is used in conjunction with DB to recover
|
||||
// from crashes, restarts, or other failures.
|
||||
Store RetributionStorer
|
||||
|
||||
// AuxSweeper is an optional interface that can be used to modify the
|
||||
// way sweep transaction are generated.
|
||||
AuxSweeper fn.Option[sweep.AuxSweeper]
|
||||
}
|
||||
|
||||
// BreachArbitrator is a special subsystem which is responsible for watching and
|
||||
|
@ -738,10 +743,28 @@ justiceTxBroadcast:
|
|||
return spew.Sdump(finalTx)
|
||||
}))
|
||||
|
||||
// As we're about to broadcast our breach transaction, we'll notify the
|
||||
// aux sweeper of our broadcast attempt first.
|
||||
err = fn.MapOptionZ(b.cfg.AuxSweeper, func(aux sweep.AuxSweeper) error {
|
||||
bumpReq := sweep.BumpRequest{
|
||||
Inputs: finalTx.inputs,
|
||||
DeliveryAddress: finalTx.sweepAddr,
|
||||
ExtraTxOut: finalTx.extraTxOut,
|
||||
}
|
||||
|
||||
return aux.NotifyBroadcast(
|
||||
&bumpReq, finalTx.justiceTx, finalTx.fee,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
brarLog.Errorf("unable to notify broadcast: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// We'll now attempt to broadcast the transaction which finalized the
|
||||
// channel's retribution against the cheating counter party.
|
||||
label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil)
|
||||
err = b.cfg.PublishTransaction(finalTx, label)
|
||||
err = b.cfg.PublishTransaction(finalTx.justiceTx, label)
|
||||
if err != nil {
|
||||
brarLog.Errorf("Unable to broadcast justice tx: %v", err)
|
||||
}
|
||||
|
@ -863,7 +886,9 @@ Loop:
|
|||
return spew.Sdump(tx)
|
||||
}))
|
||||
|
||||
err = b.cfg.PublishTransaction(tx, label)
|
||||
err = b.cfg.PublishTransaction(
|
||||
tx.justiceTx, label,
|
||||
)
|
||||
if err != nil {
|
||||
brarLog.Warnf("Unable to broadcast "+
|
||||
"commit out spending justice "+
|
||||
|
@ -880,7 +905,9 @@ Loop:
|
|||
return spew.Sdump(tx)
|
||||
}))
|
||||
|
||||
err = b.cfg.PublishTransaction(tx, label)
|
||||
err = b.cfg.PublishTransaction(
|
||||
tx.justiceTx, label,
|
||||
)
|
||||
if err != nil {
|
||||
brarLog.Warnf("Unable to broadcast "+
|
||||
"HTLC out spending justice "+
|
||||
|
@ -897,7 +924,9 @@ Loop:
|
|||
return spew.Sdump(tx)
|
||||
}))
|
||||
|
||||
err = b.cfg.PublishTransaction(tx, label)
|
||||
err = b.cfg.PublishTransaction(
|
||||
tx.justiceTx, label,
|
||||
)
|
||||
if err != nil {
|
||||
brarLog.Warnf("Unable to broadcast "+
|
||||
"second-level HTLC out "+
|
||||
|
@ -1085,10 +1114,9 @@ type breachedOutput struct {
|
|||
// makeBreachedOutput assembles a new breachedOutput that can be used by the
|
||||
// breach arbiter to construct a justice or sweep transaction.
|
||||
func makeBreachedOutput(outpoint *wire.OutPoint,
|
||||
witnessType input.StandardWitnessType,
|
||||
secondLevelScript []byte,
|
||||
signDescriptor *input.SignDescriptor,
|
||||
confHeight uint32) breachedOutput {
|
||||
witnessType input.StandardWitnessType, secondLevelScript []byte,
|
||||
signDescriptor *input.SignDescriptor, confHeight uint32,
|
||||
resolutionBlob fn.Option[tlv.Blob]) breachedOutput {
|
||||
|
||||
amount := signDescriptor.Output.Value
|
||||
|
||||
|
@ -1099,6 +1127,7 @@ func makeBreachedOutput(outpoint *wire.OutPoint,
|
|||
witnessType: witnessType,
|
||||
signDesc: *signDescriptor,
|
||||
confHeight: confHeight,
|
||||
resolutionBlob: resolutionBlob,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1276,6 +1305,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||
nil,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
breachInfo.BreachHeight,
|
||||
breachInfo.LocalResolutionBlob,
|
||||
)
|
||||
|
||||
breachedOutputs = append(breachedOutputs, localOutput)
|
||||
|
@ -1302,6 +1332,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||
nil,
|
||||
breachInfo.RemoteOutputSignDesc,
|
||||
breachInfo.BreachHeight,
|
||||
breachInfo.RemoteResolutionBlob,
|
||||
)
|
||||
|
||||
breachedOutputs = append(breachedOutputs, remoteOutput)
|
||||
|
@ -1336,6 +1367,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||
breachInfo.HtlcRetributions[i].SecondLevelWitnessScript,
|
||||
&breachInfo.HtlcRetributions[i].SignDesc,
|
||||
breachInfo.BreachHeight,
|
||||
breachInfo.HtlcRetributions[i].ResolutionBlob,
|
||||
)
|
||||
|
||||
// For taproot outputs, we also need to hold onto the second
|
||||
|
@ -1375,10 +1407,10 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||
// spend the to_local output and commitment level HTLC outputs separately,
|
||||
// before the CSV locks expire.
|
||||
type justiceTxVariants struct {
|
||||
spendAll *wire.MsgTx
|
||||
spendCommitOuts *wire.MsgTx
|
||||
spendHTLCs *wire.MsgTx
|
||||
spendSecondLevelHTLCs []*wire.MsgTx
|
||||
spendAll *justiceTxCtx
|
||||
spendCommitOuts *justiceTxCtx
|
||||
spendHTLCs *justiceTxCtx
|
||||
spendSecondLevelHTLCs []*justiceTxCtx
|
||||
}
|
||||
|
||||
// createJusticeTx creates transactions which exacts "justice" by sweeping ALL
|
||||
|
@ -1442,7 +1474,9 @@ func (b *BreachArbitrator) createJusticeTx(
|
|||
err)
|
||||
}
|
||||
|
||||
secondLevelSweeps := make([]*wire.MsgTx, 0, len(secondLevelInputs))
|
||||
// TODO(roasbeef): only register one of them?
|
||||
|
||||
secondLevelSweeps := make([]*justiceTxCtx, 0, len(secondLevelInputs))
|
||||
for _, input := range secondLevelInputs {
|
||||
sweepTx, err := b.createSweepTx(input)
|
||||
if err != nil {
|
||||
|
@ -1459,9 +1493,23 @@ func (b *BreachArbitrator) createJusticeTx(
|
|||
return txs, nil
|
||||
}
|
||||
|
||||
// justiceTxCtx contains the justice transaction along with other related meta
|
||||
// data.
|
||||
type justiceTxCtx struct {
|
||||
justiceTx *wire.MsgTx
|
||||
|
||||
sweepAddr lnwallet.AddrWithKey
|
||||
|
||||
extraTxOut fn.Option[sweep.SweepOutput]
|
||||
|
||||
fee btcutil.Amount
|
||||
|
||||
inputs []input.Input
|
||||
}
|
||||
|
||||
// createSweepTx creates a tx that sweeps the passed inputs back to our wallet.
|
||||
func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx,
|
||||
error) {
|
||||
func (b *BreachArbitrator) createSweepTx(
|
||||
inputs ...input.Input) (*justiceTxCtx, error) {
|
||||
|
||||
if len(inputs) == 0 {
|
||||
return nil, nil
|
||||
|
@ -1484,6 +1532,15 @@ func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx,
|
|||
// nLockTime, and output are already included in the TxWeightEstimator.
|
||||
weightEstimate.AddP2TROutput()
|
||||
|
||||
// If any of our inputs has a resolution blob, then we'll add another
|
||||
// P2TR output.
|
||||
hasBlobs := fn.Any(func(i input.Input) bool {
|
||||
return i.ResolutionBlob().IsSome()
|
||||
}, inputs)
|
||||
if hasBlobs {
|
||||
weightEstimate.AddP2TROutput()
|
||||
}
|
||||
|
||||
// Next, we iterate over the breached outputs contained in the
|
||||
// retribution info. For each, we switch over the witness type such
|
||||
// that we contribute the appropriate weight for each input and
|
||||
|
@ -1517,13 +1574,13 @@ func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx,
|
|||
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
|
||||
// spendable outputs by sweeping the funds into a single p2wkh output.
|
||||
func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
|
||||
inputs ...input.Input) (*wire.MsgTx, error) {
|
||||
inputs ...input.Input) (*justiceTxCtx, error) {
|
||||
|
||||
// First, we obtain a new public key script from the wallet which we'll
|
||||
// sweep the funds to.
|
||||
// TODO(roasbeef): possibly create many outputs to minimize change in
|
||||
// the future?
|
||||
pkScript, err := b.cfg.GenSweepScript()
|
||||
pkScript, err := b.cfg.GenSweepScript().Unpack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1542,6 +1599,18 @@ func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
|
|||
}
|
||||
txFee := feePerKw.FeeForWeight(txWeight)
|
||||
|
||||
// At this point, we'll check to see if we have any extra outputs to
|
||||
// add from the aux sweeper.
|
||||
extraChangeOut := fn.MapOptionZ(
|
||||
b.cfg.AuxSweeper,
|
||||
func(aux sweep.AuxSweeper) fn.Result[sweep.SweepOutput] {
|
||||
return aux.DeriveSweepAddr(inputs, pkScript)
|
||||
},
|
||||
)
|
||||
if err := extraChangeOut.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(roasbeef): already start to siphon their funds into fees
|
||||
sweepAmt := int64(totalAmt - txFee)
|
||||
|
||||
|
@ -1549,12 +1618,22 @@ func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
|
|||
// information gathered above and the provided retribution information.
|
||||
txn := wire.NewMsgTx(2)
|
||||
|
||||
// We begin by adding the output to which our funds will be deposited.
|
||||
// First, we'll add the extra swep output if it exists, subtracting the
|
||||
// amount from the sweep amt.
|
||||
extraChangeOut.WhenResult(func(o sweep.SweepOutput) {
|
||||
sweepAmt -= o.Value
|
||||
|
||||
txn.AddTxOut(&o.TxOut)
|
||||
})
|
||||
|
||||
// Next, we'll add the output to which our funds will be deposited.
|
||||
txn.AddTxOut(&wire.TxOut{
|
||||
PkScript: pkScript,
|
||||
PkScript: pkScript.DeliveryAddress,
|
||||
Value: sweepAmt,
|
||||
})
|
||||
|
||||
// TODO(roasbeef): add other output change modify sweep amt
|
||||
|
||||
// Next, we add all of the spendable outputs as inputs to the
|
||||
// transaction.
|
||||
for _, inp := range inputs {
|
||||
|
@ -1610,7 +1689,13 @@ func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
|
|||
}
|
||||
}
|
||||
|
||||
return txn, nil
|
||||
return &justiceTxCtx{
|
||||
justiceTx: txn,
|
||||
sweepAddr: pkScript,
|
||||
extraTxOut: extraChangeOut.Option(),
|
||||
fee: txFee,
|
||||
inputs: inputs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RetributionStore handles persistence of retribution states to disk and is
|
||||
|
@ -1642,12 +1727,28 @@ func taprootBriefcaseFromRetInfo(retInfo *retributionInfo) *taprootBriefcase {
|
|||
//nolint:lll
|
||||
tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock = bo.signDesc.ControlBlock
|
||||
|
||||
bo.resolutionBlob.WhenSome(func(blob tlv.Blob) {
|
||||
tapCase.SettledCommitBlob = tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType2](
|
||||
blob,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
// To spend the revoked output again, we'll store the same
|
||||
// control block value as above, but in a different place.
|
||||
case input.TaprootCommitmentRevoke:
|
||||
//nolint:lll
|
||||
tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock = bo.signDesc.ControlBlock
|
||||
|
||||
bo.resolutionBlob.WhenSome(func(blob tlv.Blob) {
|
||||
tapCase.BreachedCommitBlob = tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType3](
|
||||
blob,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
// For spending the HTLC outputs, we'll store the first and
|
||||
// second level tweak values.
|
||||
case input.TaprootHtlcAcceptedRevoke:
|
||||
|
@ -1685,12 +1786,22 @@ func applyTaprootRetInfo(tapCase *taprootBriefcase,
|
|||
//nolint:lll
|
||||
bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock
|
||||
|
||||
tapCase.SettledCommitBlob.WhenSomeV(func(blob tlv.Blob) { //nolint:lll
|
||||
bo.resolutionBlob = fn.Some(blob)
|
||||
})
|
||||
|
||||
// To spend the revoked output again, we'll apply the same
|
||||
// control block value as above, but to a different place.
|
||||
case input.TaprootCommitmentRevoke:
|
||||
//nolint:lll
|
||||
bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock
|
||||
|
||||
tapCase.BreachedCommitBlob.WhenSomeV(
|
||||
func(blob tlv.Blob) {
|
||||
bo.resolutionBlob = fn.Some(blob)
|
||||
},
|
||||
)
|
||||
|
||||
// For spending the HTLC outputs, we'll apply the first and
|
||||
// second level tweak values.
|
||||
case input.TaprootHtlcAcceptedRevoke:
|
||||
|
|
|
@ -1199,6 +1199,8 @@ func TestBreachCreateJusticeTx(t *testing.T) {
|
|||
input.HtlcSecondLevelRevoke,
|
||||
}
|
||||
|
||||
rBlob := fn.Some([]byte{0x01})
|
||||
|
||||
breachedOutputs := make([]breachedOutput, len(outputTypes))
|
||||
for i, wt := range outputTypes {
|
||||
// Create a fake breached output for each type, ensuring they
|
||||
|
@ -1217,6 +1219,7 @@ func TestBreachCreateJusticeTx(t *testing.T) {
|
|||
nil,
|
||||
signDesc,
|
||||
1,
|
||||
rBlob,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1227,16 +1230,16 @@ func TestBreachCreateJusticeTx(t *testing.T) {
|
|||
|
||||
// The spendAll tx should be spending all the outputs. This is the
|
||||
// "regular" justice transaction type.
|
||||
require.Len(t, justiceTxs.spendAll.TxIn, len(breachedOutputs))
|
||||
require.Len(t, justiceTxs.spendAll.justiceTx.TxIn, len(breachedOutputs))
|
||||
|
||||
// The spendCommitOuts tx should be spending the 4 types of commit outs
|
||||
// (note that in practice there will be at most two commit outputs per
|
||||
// commit, but we test all 4 types here).
|
||||
require.Len(t, justiceTxs.spendCommitOuts.TxIn, 4)
|
||||
require.Len(t, justiceTxs.spendCommitOuts.justiceTx.TxIn, 4)
|
||||
|
||||
// Check that the spendHTLCs tx is spending the two revoked commitment
|
||||
// level HTLC output types.
|
||||
require.Len(t, justiceTxs.spendHTLCs.TxIn, 2)
|
||||
require.Len(t, justiceTxs.spendHTLCs.justiceTx.TxIn, 2)
|
||||
|
||||
// Finally, check that the spendSecondLevelHTLCs txs are spending the
|
||||
// second level type.
|
||||
|
@ -2131,15 +2134,19 @@ func createTestArbiter(t *testing.T, contractBreaches chan *ContractBreachEvent,
|
|||
// Assemble our test arbiter.
|
||||
notifier := mock.MakeMockSpendNotifier()
|
||||
ba := NewBreachArbitrator(&BreachConfig{
|
||||
CloseLink: func(_ *wire.OutPoint, _ ChannelCloseType) {},
|
||||
DB: db.ChannelStateDB(),
|
||||
Estimator: chainfee.NewStaticEstimator(12500, 0),
|
||||
GenSweepScript: func() ([]byte, error) { return nil, nil },
|
||||
ContractBreaches: contractBreaches,
|
||||
Signer: signer,
|
||||
Notifier: notifier,
|
||||
PublishTransaction: func(_ *wire.MsgTx, _ string) error { return nil },
|
||||
Store: store,
|
||||
CloseLink: func(_ *wire.OutPoint, _ ChannelCloseType) {},
|
||||
DB: db.ChannelStateDB(),
|
||||
Estimator: chainfee.NewStaticEstimator(12500, 0),
|
||||
GenSweepScript: func() fn.Result[lnwallet.AddrWithKey] {
|
||||
return fn.Ok(lnwallet.AddrWithKey{})
|
||||
},
|
||||
ContractBreaches: contractBreaches,
|
||||
Signer: signer,
|
||||
Notifier: notifier,
|
||||
PublishTransaction: func(_ *wire.MsgTx, _ string) error {
|
||||
return nil
|
||||
},
|
||||
Store: store,
|
||||
})
|
||||
|
||||
if err := ba.Start(); err != nil {
|
||||
|
|
|
@ -1558,7 +1558,7 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error {
|
|||
tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock = commitSignDesc.ControlBlock
|
||||
|
||||
c.CommitResolution.ResolutionBlob.WhenSome(func(b []byte) {
|
||||
tapCase.CommitBlob = tlv.SomeRecordT(
|
||||
tapCase.SettledCommitBlob = tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType2](b),
|
||||
)
|
||||
})
|
||||
|
@ -1649,7 +1649,7 @@ func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error {
|
|||
c.CommitResolution.SelfOutputSignDesc.ControlBlock =
|
||||
tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock
|
||||
|
||||
tapCase.CommitBlob.WhenSomeV(func(b []byte) {
|
||||
tapCase.SettledCommitBlob.WhenSomeV(func(b []byte) {
|
||||
c.CommitResolution.ResolutionBlob = fn.Some(b)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -30,10 +30,16 @@ type taprootBriefcase struct {
|
|||
// revocation paths.
|
||||
TapTweaks tlv.RecordT[tlv.TlvType1, tapTweaks]
|
||||
|
||||
// CommitBlob is an optional record that contains an opaque blob that
|
||||
// may be used to properly sweep commitment outputs on a force close
|
||||
// transaction.
|
||||
CommitBlob tlv.OptionalRecordT[tlv.TlvType2, tlv.Blob]
|
||||
// SettledCommitBlob is an optional record that contains an opaque blob
|
||||
// that may be used to properly sweep commitment outputs on a force
|
||||
// close transaction.
|
||||
SettledCommitBlob tlv.OptionalRecordT[tlv.TlvType2, tlv.Blob]
|
||||
|
||||
// BreachCommitBlob is an optional record that contains an opaque blob
|
||||
// used to sweep a remote party's breached output.
|
||||
BreachedCommitBlob tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob]
|
||||
|
||||
// TODO(roasbeef): htlc blobs
|
||||
}
|
||||
|
||||
// TODO(roasbeef): morph into new tlv record
|
||||
|
@ -53,9 +59,16 @@ func (t *taprootBriefcase) EncodeRecords() []tlv.Record {
|
|||
t.CtrlBlocks.Record(), t.TapTweaks.Record(),
|
||||
}
|
||||
|
||||
t.CommitBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType2, tlv.Blob]) {
|
||||
records = append(records, r.Record())
|
||||
})
|
||||
t.SettledCommitBlob.WhenSome(
|
||||
func(r tlv.RecordT[tlv.TlvType2, tlv.Blob]) {
|
||||
records = append(records, r.Record())
|
||||
},
|
||||
)
|
||||
t.BreachedCommitBlob.WhenSome(
|
||||
func(r tlv.RecordT[tlv.TlvType3, tlv.Blob]) {
|
||||
records = append(records, r.Record())
|
||||
},
|
||||
)
|
||||
|
||||
return records
|
||||
}
|
||||
|
@ -79,8 +92,12 @@ func (t *taprootBriefcase) Encode(w io.Writer) error {
|
|||
|
||||
// Decode decodes the given reader into the target struct.
|
||||
func (t *taprootBriefcase) Decode(r io.Reader) error {
|
||||
commitBlob := t.CommitBlob.Zero()
|
||||
records := append(t.DecodeRecords(), commitBlob.Record())
|
||||
settledCommitBlob := t.SettledCommitBlob.Zero()
|
||||
breachedCommitBlob := t.BreachedCommitBlob.Zero()
|
||||
records := append(
|
||||
t.DecodeRecords(), settledCommitBlob.Record(),
|
||||
breachedCommitBlob.Record(),
|
||||
)
|
||||
stream, err := tlv.NewStream(records...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -91,8 +108,11 @@ func (t *taprootBriefcase) Decode(r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if val, ok := typeMap[t.CommitBlob.TlvType()]; ok && val == nil {
|
||||
t.CommitBlob = tlv.SomeRecordT(commitBlob)
|
||||
if val, ok := typeMap[t.SettledCommitBlob.TlvType()]; ok && val == nil {
|
||||
t.SettledCommitBlob = tlv.SomeRecordT(settledCommitBlob)
|
||||
}
|
||||
if v, ok := typeMap[t.BreachedCommitBlob.TlvType()]; ok && v == nil {
|
||||
t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -87,9 +87,12 @@ func TestTaprootBriefcase(t *testing.T) {
|
|||
BreachedHtlcTweaks: randHtlcTweaks(t),
|
||||
BreachedSecondLevelHltcTweaks: randHtlcTweaks(t),
|
||||
}),
|
||||
CommitBlob: tlv.SomeRecordT(
|
||||
SettledCommitBlob: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType2](commitBlob[:]),
|
||||
),
|
||||
BreachedCommitBlob: tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType3](commitBlob[:]),
|
||||
),
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/labels"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// SUMMARY OF OUTPUT STATES
|
||||
|
@ -1425,6 +1426,7 @@ func makeKidOutput(outpoint, originChanPoint *wire.OutPoint,
|
|||
return kidOutput{
|
||||
breachedOutput: makeBreachedOutput(
|
||||
outpoint, witnessType, nil, signDescriptor, heightHint,
|
||||
fn.None[tlv.Blob](),
|
||||
),
|
||||
isHtlc: isHtlc,
|
||||
originChanPoint: *originChanPoint,
|
||||
|
|
|
@ -69,6 +69,12 @@ type ResolutionReq struct {
|
|||
// 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]
|
||||
}
|
||||
|
|
|
@ -2466,6 +2466,10 @@ type HtlcRetribution struct {
|
|||
// this HTLC was offered by us. This flag is used determine the exact
|
||||
// witness type should be used to sweep the output.
|
||||
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
|
||||
|
@ -2536,10 +2540,13 @@ type BreachRetribution struct {
|
|||
// have access to the public keys used in the scripts.
|
||||
KeyRing *CommitmentKeyRing
|
||||
|
||||
// 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]
|
||||
// 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
|
||||
|
@ -2552,7 +2559,8 @@ type BreachRetribution struct {
|
|||
func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
breachHeight uint32, spendTx *wire.MsgTx,
|
||||
leafStore fn.Option[AuxLeafStore],
|
||||
auxResolver fn.Option[AuxContractResolver]) (*BreachRetribution, error) { //nolint:lll
|
||||
auxResolver fn.Option[AuxContractResolver]) (*BreachRetribution,
|
||||
error) {
|
||||
|
||||
// Query the on-disk revocation log for the snapshot which was recorded
|
||||
// at this particular state num. Based on whether a legacy revocation
|
||||
|
@ -2604,26 +2612,26 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||
|
||||
// Since it is the remote breach we are reconstructing, the output
|
||||
// going to us will be a to-remote script with our local params.
|
||||
localAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.LocalAuxLeaf
|
||||
remoteAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.RemoteAuxLeaf
|
||||
})(auxLeaves)
|
||||
isRemoteInitiator := !chanState.IsInitiator
|
||||
ourScript, ourDelay, err := CommitScriptToRemote(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
|
||||
leaseExpiry, fn.FlattenOption(localAuxLeaf),
|
||||
leaseExpiry, fn.FlattenOption(remoteAuxLeaf),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.RemoteAuxLeaf
|
||||
localAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
|
||||
return l.LocalAuxLeaf
|
||||
})(auxLeaves)
|
||||
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
|
||||
theirScript, err := CommitScriptToSelf(
|
||||
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
|
||||
keyRing.RevocationKey, theirDelay, leaseExpiry,
|
||||
fn.FlattenOption(remoteAuxLeaf),
|
||||
fn.FlattenOption(localAuxLeaf),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -2716,19 +2724,21 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||
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.RemoteCommitment.CustomBlob, //nolint:lll
|
||||
FundingBlob: chanState.CustomBlob,
|
||||
Type: input.TaprootRemoteCommitSpend, //nolint:lll
|
||||
CloseType: Breach,
|
||||
CommitTx: spendTx,
|
||||
SignDesc: *br.LocalOutputSignDesc,
|
||||
KeyRing: keyRing,
|
||||
CsvDelay: theirDelay,
|
||||
CommitFee: chanState.RemoteCommitment.CommitFee, //nolint:lll
|
||||
ChanPoint: chanState.FundingOutpoint,
|
||||
ShortChanID: chanState.ShortChanID(),
|
||||
Initiator: chanState.IsInitiator,
|
||||
CommitBlob: revokedLog.CustomBlob.ValOpt(),
|
||||
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,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
@ -2736,7 +2746,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||
return nil, fmt.Errorf("unable to aux resolve: %w", err)
|
||||
}
|
||||
|
||||
br.ResolutionBlob = resolveBlob.Option()
|
||||
br.LocalResolutionBlob = resolveBlob.Option()
|
||||
}
|
||||
|
||||
// Similarly, if their balance exceeds the remote party's dust limit,
|
||||
|
@ -2790,19 +2800,21 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||
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.RemoteCommitment.CustomBlob, //nolint:lll
|
||||
FundingBlob: chanState.CustomBlob,
|
||||
Type: input.TaprootCommitmentRevoke, //nolint:lll
|
||||
CloseType: Breach,
|
||||
CommitTx: spendTx,
|
||||
SignDesc: *br.RemoteOutputSignDesc,
|
||||
KeyRing: keyRing,
|
||||
CsvDelay: theirDelay,
|
||||
CommitFee: chanState.RemoteCommitment.CommitFee, //nolint:lll
|
||||
ChanPoint: chanState.FundingOutpoint,
|
||||
ShortChanID: chanState.ShortChanID(),
|
||||
Initiator: chanState.IsInitiator,
|
||||
CommitBlob: revokedLog.CustomBlob.ValOpt(),
|
||||
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,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
@ -2810,7 +2822,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|||
return nil, fmt.Errorf("unable to aux resolve: %w", err)
|
||||
}
|
||||
|
||||
br.ResolutionBlob = resolveBlob.Option()
|
||||
br.RemoteResolutionBlob = resolveBlob.Option()
|
||||
}
|
||||
|
||||
// Finally, with all the necessary data constructed, we can pad the
|
||||
|
|
14
server.go
14
server.go
|
@ -1152,16 +1152,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
CloseLink: closeLink,
|
||||
DB: s.chanStateDB,
|
||||
Estimator: s.cc.FeeEstimator,
|
||||
GenSweepScript: func() ([]byte, error) {
|
||||
addr, err := newSweepPkScriptGen(
|
||||
cc.Wallet, netParams,
|
||||
)().Unpack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return addr.DeliveryAddress, nil
|
||||
},
|
||||
GenSweepScript: newSweepPkScriptGen(
|
||||
cc.Wallet, s.cfg.ActiveNetParams.Params,
|
||||
),
|
||||
Notifier: cc.ChainNotifier,
|
||||
PublishTransaction: cc.Wallet.PublishTransaction,
|
||||
ContractBreaches: contractBreaches,
|
||||
|
@ -1169,6 +1162,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
Store: contractcourt.NewRetributionStore(
|
||||
dbs.ChanStateDB,
|
||||
),
|
||||
AuxSweeper: s.implCfg.AuxSweeper,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue