breacharbiter: create split variants of justice tx

We define a new struct justiceTxVariants, which holds three different
justice transactions:

1. The "normal" justice tx that spends all breached outputs
2. A tx that spends only the breached to_local output and to_remote output
   (can be nil if none of these exist)
3. A tx that spends all the breached HTLC outputs (can be nil if no HTLC
   outputs exist)

This will later be used to sweep the time sensitive outputs separately,
in case the normal justice tx doesn't confirm in time.
This commit is contained in:
Johan T. Halseth 2021-02-12 12:36:45 +01:00
parent 783d1f9578
commit 2d710154c4
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
2 changed files with 153 additions and 11 deletions

View File

@ -568,11 +568,12 @@ justiceTxBroadcast:
// With the breach transaction confirmed, we now create the
// justice tx which will claim ALL the funds within the
// channel.
finalTx, err := b.createJusticeTx(breachInfo)
justiceTxs, err := b.createJusticeTx(breachInfo.breachedOutputs)
if err != nil {
brarLog.Errorf("Unable to create justice tx: %v", err)
return
}
finalTx := justiceTxs.spendAll
brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string {
return spew.Sdump(finalTx)
@ -1073,12 +1074,87 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
}
}
// createJusticeTx creates a transaction which exacts "justice" by sweeping ALL
// justiceTxVariants is a struct that holds transactions which exacts "justice"
// by sweeping ALL the funds within the channel which we are now entitled to
// due to a breach of the channel's contract by the counterparty. There are
// three variants of the justice transaction:
//
// 1. The "normal" justice tx that spends all breached outputs
// 2. A tx that spends only the breached to_local output and to_remote output
// (can be nil if none of these exist)
// 3. A tx that spends all the breached HTLC outputs, and second-level HTLC
// outputs (can be nil if no HTLC outputs exist).
//
// The reason we create these three variants, is that in certain cases (like
// with the anchor output HTLC malleability), the channel counter party can pin
// the HTLC outputs with low fee children, hindering our normal justice tx that
// attempts to spend these outputs from propagating. In this case we want to
// spend the to_local output separately, before the CSV lock expires.
type justiceTxVariants struct {
spendAll *wire.MsgTx
spendCommitOuts *wire.MsgTx
spendHTLCs *wire.MsgTx
}
// createJusticeTx creates transactions which exacts "justice" by sweeping ALL
// the funds within the channel which we are now entitled to due to a breach of
// the channel's contract by the counterparty. This function returns a *fully*
// signed transaction with the witness for each input fully in place.
func (b *breachArbiter) createJusticeTx(
r *retributionInfo) (*wire.MsgTx, error) {
breachedOutputs []breachedOutput) (*justiceTxVariants, error) {
var (
allInputs []input.Input
commitInputs []input.Input
htlcInputs []input.Input
)
for i := range breachedOutputs {
// Grab locally scoped reference to breached output.
inp := &breachedOutputs[i]
allInputs = append(allInputs, inp)
// Check if the input is from an HTLC or a commitment output.
if inp.WitnessType() == input.HtlcAcceptedRevoke ||
inp.WitnessType() == input.HtlcOfferedRevoke ||
inp.WitnessType() == input.HtlcSecondLevelRevoke {
htlcInputs = append(htlcInputs, inp)
} else {
commitInputs = append(commitInputs, inp)
}
}
var (
txs = &justiceTxVariants{}
err error
)
// For each group of inputs, create a tx that spends them.
txs.spendAll, err = b.createSweepTx(allInputs)
if err != nil {
return nil, err
}
txs.spendCommitOuts, err = b.createSweepTx(commitInputs)
if err != nil {
return nil, err
}
txs.spendHTLCs, err = b.createSweepTx(htlcInputs)
if err != nil {
return nil, err
}
return txs, nil
}
// createSweepTx creates a tx that sweeps the passed inputs back to our wallet.
func (b *breachArbiter) createSweepTx(inputs []input.Input) (*wire.MsgTx,
error) {
if len(inputs) == 0 {
return nil, nil
}
// We will assemble the breached outputs into a slice of spendable
// outputs, while simultaneously computing the estimated weight of the
@ -1090,7 +1166,7 @@ func (b *breachArbiter) createJusticeTx(
// Allocate enough space to potentially hold each of the breached
// outputs in the retribution info.
spendableOutputs = make([]input.Input, 0, len(r.breachedOutputs))
spendableOutputs = make([]input.Input, 0, len(inputs))
// The justice transaction we construct will be a segwit transaction
// that pays to a p2wkh output. Components such as the version,
@ -1099,15 +1175,15 @@ func (b *breachArbiter) createJusticeTx(
// 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 witness,
// finally adding to our list of spendable outputs.
for i := range r.breachedOutputs {
// that we contribute the appropriate weight for each input and
// witness, finally adding to our list of spendable outputs.
for i := range inputs {
// Grab locally scoped reference to breached output.
inp := &r.breachedOutputs[i]
inp := inputs[i]
// First, determine the appropriate estimated witness weight for
// the give witness type of this breached output. If the witness
// weight cannot be estimated, we will omit it from the
// First, determine the appropriate estimated witness weight
// for the give witness type of this breached output. If the
// witness weight cannot be estimated, we will omit it from the
// transaction.
witnessWeight, _, err := inp.WitnessType().SizeUpperBound()
if err != nil {

View File

@ -1203,6 +1203,72 @@ func TestBreachHandoffFail(t *testing.T) {
assertArbiterBreach(t, brar, chanPoint)
}
// TestBreachCreateJusticeTx tests that we create three different variants of
// the justice tx.
func TestBreachCreateJusticeTx(t *testing.T) {
brar, _, _, _, _, cleanUpChans, cleanUpArb := initBreachedState(t)
defer cleanUpChans()
defer cleanUpArb()
// In this test we just want to check that the correct inputs are added
// to the justice tx, not that we create a valid spend, so we just set
// some params making the script generation succeed.
aliceKeyPriv, _ := btcec.PrivKeyFromBytes(
btcec.S256(), channels.AlicesPrivKey,
)
alicePubKey := aliceKeyPriv.PubKey()
signDesc := &breachedOutputs[0].signDesc
signDesc.KeyDesc.PubKey = alicePubKey
signDesc.DoubleTweak = aliceKeyPriv
// We'll test all the different types of outputs we'll sweep with the
// justice tx.
outputTypes := []input.StandardWitnessType{
input.CommitmentNoDelay,
input.CommitSpendNoDelayTweakless,
input.CommitmentToRemoteConfirmed,
input.CommitmentRevoke,
input.HtlcAcceptedRevoke,
input.HtlcOfferedRevoke,
input.HtlcSecondLevelRevoke,
}
breachedOutputs := make([]breachedOutput, len(outputTypes))
for i, wt := range outputTypes {
// Create a fake breached output for each type, ensuring they
// have different outpoints for our logic to accept them.
op := breachedOutputs[0].outpoint
op.Index = uint32(i)
breachedOutputs[i] = makeBreachedOutput(
&op,
wt,
// Second level scripts doesn't matter in this test.
nil,
signDesc,
1,
)
}
// Create the justice transactions.
justiceTxs, err := brar.createJusticeTx(breachedOutputs)
require.NoError(t, err)
require.NotNil(t, justiceTxs)
// The spendAll tx should be spending all the outputs. This is the
// "regular" justice transaction type.
require.Len(t, justiceTxs.spendAll.TxIn, len(breachedOutputs))
// The spendCommitOuts tx should be spending the 4 typed of commit outs
// (note that in practice there will be at most two commit outputs per
// commmit, but we test all 4 types here).
require.Len(t, justiceTxs.spendCommitOuts.TxIn, 4)
// Finally check that the spendHTLCs tx are spending the two revoked
// HTLC types, and the second level type.
require.Len(t, justiceTxs.spendHTLCs.TxIn, 3)
}
type publAssertion func(*testing.T, map[wire.OutPoint]struct{},
chan *wire.MsgTx, chainhash.Hash) *wire.MsgTx