mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
contractcourt: integration aux sweeper to breach arb
Similar to the sweeper, when we're about to make a new breach transaction, we ask the sweeper for a new change address, if it has one. Then when we go to publish, we notify broadcast.
This commit is contained in:
parent
4c16e55aca
commit
3d7d9d2612
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnutils"
|
"github.com/lightningnetwork/lnd/lnutils"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
"github.com/lightningnetwork/lnd/tlv"
|
"github.com/lightningnetwork/lnd/tlv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -174,6 +175,10 @@ type BreachConfig struct {
|
|||||||
// breached channels. This is used in conjunction with DB to recover
|
// breached channels. This is used in conjunction with DB to recover
|
||||||
// from crashes, restarts, or other failures.
|
// from crashes, restarts, or other failures.
|
||||||
Store RetributionStorer
|
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
|
// BreachArbitrator is a special subsystem which is responsible for watching and
|
||||||
@ -737,10 +742,28 @@ justiceTxBroadcast:
|
|||||||
brarLog.Debugf("Broadcasting justice tx: %v", lnutils.SpewLogClosure(
|
brarLog.Debugf("Broadcasting justice tx: %v", lnutils.SpewLogClosure(
|
||||||
finalTx))
|
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
|
// We'll now attempt to broadcast the transaction which finalized the
|
||||||
// channel's retribution against the cheating counter party.
|
// channel's retribution against the cheating counter party.
|
||||||
label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil)
|
label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil)
|
||||||
err = b.cfg.PublishTransaction(finalTx, label)
|
err = b.cfg.PublishTransaction(finalTx.justiceTx, label)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
brarLog.Errorf("Unable to broadcast justice tx: %v", err)
|
brarLog.Errorf("Unable to broadcast justice tx: %v", err)
|
||||||
}
|
}
|
||||||
@ -860,7 +883,9 @@ Loop:
|
|||||||
"spending commitment outs: %v",
|
"spending commitment outs: %v",
|
||||||
lnutils.SpewLogClosure(tx))
|
lnutils.SpewLogClosure(tx))
|
||||||
|
|
||||||
err = b.cfg.PublishTransaction(tx, label)
|
err = b.cfg.PublishTransaction(
|
||||||
|
tx.justiceTx, label,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
brarLog.Warnf("Unable to broadcast "+
|
brarLog.Warnf("Unable to broadcast "+
|
||||||
"commit out spending justice "+
|
"commit out spending justice "+
|
||||||
@ -875,7 +900,9 @@ Loop:
|
|||||||
"spending HTLC outs: %v",
|
"spending HTLC outs: %v",
|
||||||
lnutils.SpewLogClosure(tx))
|
lnutils.SpewLogClosure(tx))
|
||||||
|
|
||||||
err = b.cfg.PublishTransaction(tx, label)
|
err = b.cfg.PublishTransaction(
|
||||||
|
tx.justiceTx, label,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
brarLog.Warnf("Unable to broadcast "+
|
brarLog.Warnf("Unable to broadcast "+
|
||||||
"HTLC out spending justice "+
|
"HTLC out spending justice "+
|
||||||
@ -890,7 +917,9 @@ Loop:
|
|||||||
"spending second-level HTLC output: %v",
|
"spending second-level HTLC output: %v",
|
||||||
lnutils.SpewLogClosure(tx))
|
lnutils.SpewLogClosure(tx))
|
||||||
|
|
||||||
err = b.cfg.PublishTransaction(tx, label)
|
err = b.cfg.PublishTransaction(
|
||||||
|
tx.justiceTx, label,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
brarLog.Warnf("Unable to broadcast "+
|
brarLog.Warnf("Unable to broadcast "+
|
||||||
"second-level HTLC out "+
|
"second-level HTLC out "+
|
||||||
@ -1372,10 +1401,10 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
|||||||
// spend the to_local output and commitment level HTLC outputs separately,
|
// spend the to_local output and commitment level HTLC outputs separately,
|
||||||
// before the CSV locks expire.
|
// before the CSV locks expire.
|
||||||
type justiceTxVariants struct {
|
type justiceTxVariants struct {
|
||||||
spendAll *wire.MsgTx
|
spendAll *justiceTxCtx
|
||||||
spendCommitOuts *wire.MsgTx
|
spendCommitOuts *justiceTxCtx
|
||||||
spendHTLCs *wire.MsgTx
|
spendHTLCs *justiceTxCtx
|
||||||
spendSecondLevelHTLCs []*wire.MsgTx
|
spendSecondLevelHTLCs []*justiceTxCtx
|
||||||
}
|
}
|
||||||
|
|
||||||
// createJusticeTx creates transactions which exacts "justice" by sweeping ALL
|
// createJusticeTx creates transactions which exacts "justice" by sweeping ALL
|
||||||
@ -1439,7 +1468,9 @@ func (b *BreachArbitrator) createJusticeTx(
|
|||||||
err)
|
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 {
|
for _, input := range secondLevelInputs {
|
||||||
sweepTx, err := b.createSweepTx(input)
|
sweepTx, err := b.createSweepTx(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1456,9 +1487,23 @@ func (b *BreachArbitrator) createJusticeTx(
|
|||||||
return txs, nil
|
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.
|
// createSweepTx creates a tx that sweeps the passed inputs back to our wallet.
|
||||||
func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx,
|
func (b *BreachArbitrator) createSweepTx(
|
||||||
error) {
|
inputs ...input.Input) (*justiceTxCtx, error) {
|
||||||
|
|
||||||
if len(inputs) == 0 {
|
if len(inputs) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -1481,6 +1526,18 @@ func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx,
|
|||||||
// nLockTime, and output are already included in the TxWeightEstimator.
|
// nLockTime, and output are already included in the TxWeightEstimator.
|
||||||
weightEstimate.AddP2TROutput()
|
weightEstimate.AddP2TROutput()
|
||||||
|
|
||||||
|
// If any of our inputs has a resolution blob, then we'll add another
|
||||||
|
// P2TR _output_, since we'll want to separate the custom channel
|
||||||
|
// outputs from the regular, BTC only outputs. So we only need one such
|
||||||
|
// output, which'll carry the custom channel "valuables" from both the
|
||||||
|
// breached commitment and HTLC outputs.
|
||||||
|
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
|
// Next, we iterate over the breached outputs contained in the
|
||||||
// retribution info. For each, we switch over the witness type such
|
// retribution info. For each, we switch over the witness type such
|
||||||
// that we contribute the appropriate weight for each input and
|
// that we contribute the appropriate weight for each input and
|
||||||
@ -1514,7 +1571,7 @@ func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx,
|
|||||||
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
|
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
|
||||||
// spendable outputs by sweeping the funds into a single p2wkh output.
|
// spendable outputs by sweeping the funds into a single p2wkh output.
|
||||||
func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
|
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
|
// First, we obtain a new public key script from the wallet which we'll
|
||||||
// sweep the funds to.
|
// sweep the funds to.
|
||||||
@ -1539,6 +1596,18 @@ func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
|
|||||||
}
|
}
|
||||||
txFee := feePerKw.FeeForWeight(txWeight)
|
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
|
// TODO(roasbeef): already start to siphon their funds into fees
|
||||||
sweepAmt := int64(totalAmt - txFee)
|
sweepAmt := int64(totalAmt - txFee)
|
||||||
|
|
||||||
@ -1546,12 +1615,24 @@ func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
|
|||||||
// information gathered above and the provided retribution information.
|
// information gathered above and the provided retribution information.
|
||||||
txn := wire.NewMsgTx(2)
|
txn := wire.NewMsgTx(2)
|
||||||
|
|
||||||
// We begin by adding the output to which our funds will be deposited.
|
// First, we'll add the extra sweep output if it exists, subtracting the
|
||||||
|
// amount from the sweep amt.
|
||||||
|
if b.cfg.AuxSweeper.IsSome() {
|
||||||
|
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{
|
txn.AddTxOut(&wire.TxOut{
|
||||||
PkScript: pkScript.DeliveryAddress,
|
PkScript: pkScript.DeliveryAddress,
|
||||||
Value: sweepAmt,
|
Value: sweepAmt,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO(roasbeef): add other output change modify sweep amt
|
||||||
|
|
||||||
// Next, we add all of the spendable outputs as inputs to the
|
// Next, we add all of the spendable outputs as inputs to the
|
||||||
// transaction.
|
// transaction.
|
||||||
for _, inp := range inputs {
|
for _, inp := range inputs {
|
||||||
@ -1607,7 +1688,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
|
// RetributionStore handles persistence of retribution states to disk and is
|
||||||
|
@ -1230,16 +1230,16 @@ func TestBreachCreateJusticeTx(t *testing.T) {
|
|||||||
|
|
||||||
// The spendAll tx should be spending all the outputs. This is the
|
// The spendAll tx should be spending all the outputs. This is the
|
||||||
// "regular" justice transaction type.
|
// "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
|
// 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
|
// (note that in practice there will be at most two commit outputs per
|
||||||
// commit, but we test all 4 types here).
|
// 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
|
// Check that the spendHTLCs tx is spending the two revoked commitment
|
||||||
// level HTLC output types.
|
// 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
|
// Finally, check that the spendSecondLevelHTLCs txs are spending the
|
||||||
// second level type.
|
// second level type.
|
||||||
|
Loading…
Reference in New Issue
Block a user