From d88804178ed65e9766621ca7f1930d1fb734f83e Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 21 Aug 2017 16:56:58 -0700 Subject: [PATCH 01/15] breacharbiter: unifies ba sweep txn creation --- breacharbiter.go | 528 ++++++++++++++++++++++++++++------------------- 1 file changed, 318 insertions(+), 210 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 16d4282e9..84eabb083 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "errors" - "fmt" "io" "sync" "sync/atomic" @@ -40,6 +39,7 @@ var retributionBucket = []byte("retribution") // TODO(roasbeef): closures in config for subsystem pointers to decouple? type breachArbiter struct { wallet *lnwallet.LightningWallet + signer lnwallet.Signer db *channeldb.DB notifier chainntnfs.ChainNotifier chainIO lnwallet.BlockChainIO @@ -87,6 +87,7 @@ func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, return &breachArbiter{ wallet: wallet, + signer: wallet.Cfg.Signer, db: db, notifier: notifier, chainIO: chain, @@ -129,7 +130,7 @@ func (b *breachArbiter) Start() error { closeSummary := channeldb.ChannelCloseSummary{ ChanPoint: ret.chanPoint, ClosingTXID: ret.commitHash, - RemotePub: &ret.remoteIdentity, + RemotePub: ret.remoteIdentity, Capacity: ret.capacity, SettledBalance: ret.settledBalance, CloseType: channeldb.BreachClose, @@ -234,6 +235,13 @@ func (b *breachArbiter) Start() error { return err } + // Additionally, we'll also want to watch any pending close or force + // close transactions to we can properly mark them as resolved in the + // database. + if err := b.watchForPendingCloseConfs(currentHeight); err != nil { + return err + } + // Spawn the exactRetribution tasks to monitor and resolve any breaches // that were loaded from the retribution store. for chanPoint, closeSummary := range closeSummaries { @@ -259,10 +267,13 @@ func (b *breachArbiter) Start() error { b.wg.Add(1) go b.contractObserver(channelsToWatch) - // Additionally, we'll also want to retrieve any pending close or force - // close transactions to we can properly mark them as resolved in the - // database. - pendingCloseChans, err := b.db.FetchClosedChannels(true) + return nil +} + +// watchForPendingCloseConfs dispatches confirmation notification subscribers +// that mark any pending channels as fully closed when signaled. +func (b *breachArbiter) watchForPendingCloseConfs(currentHeight int32) error { + pendingCloseChans, err := b.cfg.DB.FetchClosedChannels(true) if err != nil { brarLog.Errorf("unable to fetch closing channels: %v", err) return err @@ -311,7 +322,7 @@ func (b *breachArbiter) Start() error { err := b.db.MarkChanFullyClosed(&chanPoint) if err != nil { - brarLog.Errorf("unable to mark chan "+ + brarLog.Errorf("unable to mark channel "+ "as closed: %v", err) } @@ -375,8 +386,8 @@ out: case breachInfo := <-b.breachedContracts: _, currentHeight, err := b.chainIO.GetBestBlock() if err != nil { - brarLog.Errorf( - "unable to get best height: %v", err) + brarLog.Errorf("unable to get best height: %v", + err) } // A new channel contract has just been breached! We @@ -572,8 +583,6 @@ func (b *breachArbiter) exactRetribution( // TODO(roasbeef): close other active channels with offending // peer - close(breachInfo.doneChan) - return case <-b.quit: return @@ -621,10 +630,11 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, // Next, we'll launch a goroutine to wait until the closing // transaction has been confirmed so we can mark the contract - // as resolved in the database. This go routine is _not_ - // tracked by the breach aribter's wait group since the callback - // may not be executed before shutdown, potentially leading to - // a deadlock. + // as resolved in the database. This go routine is _not_ tracked + // by the breach arbiter's wait group since the callback may not + // be executed before shutdown, potentially leading to a + // deadlocks as the arbiter may not be able to finish shutting + // down. // // TODO(roasbeef): also notify utxoNursery, might've had // outbound HTLC's in flight @@ -650,6 +660,10 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, goto close } + brarLog.Infof("Sweeping %v breached "+ + "outputs with: %v", + spew.Sdump(sweepTx)) + err = b.wallet.PublishTransaction( sweepTx, ) @@ -685,82 +699,45 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, // multi-hop HTLCs aren't sent over this link, nor any other // links associated with this peer. b.htlcSwitch.CloseLink(chanPoint, htlcswitch.CloseBreach) - chanInfo := contract.StateSnapshot() // TODO(roasbeef): need to handle case of remote broadcast // mid-local initiated state-transition, possible // false-positive? - // First we generate the witness generation function which will - // be used to sweep the output only we can satisfy on the - // commitment transaction. This output is just a regular p2wkh - // output. - localSignDesc := breachInfo.LocalOutputSignDesc - localWitness := func(tx *wire.MsgTx, hc *txscript.TxSigHashes, - inputIndex int) ([][]byte, error) { + // Obtain a snapshot of the final channel state, which can be + // used to reclose a breached channel in the event of a failure. + chanInfo := contract.StateSnapshot() - desc := localSignDesc - desc.SigHashes = hc - desc.InputIndex = inputIndex - - return lnwallet.CommitSpendNoDelay( - b.wallet.Cfg.Signer, &desc, tx) - } - - // Next we create the witness generation function that will be - // used to sweep the cheating counterparty's output by taking - // advantage of the revocation clause within the output's - // witness script. - remoteSignDesc := breachInfo.RemoteOutputSignDesc - remoteWitness := func(tx *wire.MsgTx, hc *txscript.TxSigHashes, - inputIndex int) ([][]byte, error) { - - desc := breachInfo.RemoteOutputSignDesc - desc.SigHashes = hc - desc.InputIndex = inputIndex - - return lnwallet.CommitSpendRevoke( - b.wallet.Cfg.Signer, &desc, tx) - } - - // Assemble the retribution information that parameterizes the - // construction of transactions required to correct the breach. - // TODO(roasbeef): populate htlc breaches - retInfo := &retributionInfo{ - commitHash: breachInfo.BreachTransaction.TxHash(), - chanPoint: *chanPoint, - - remoteIdentity: chanInfo.RemoteIdentity, - capacity: chanInfo.Capacity, - settledBalance: chanInfo.LocalBalance.ToSatoshis(), - - selfOutput: &breachedOutput{ - amt: btcutil.Amount(localSignDesc.Output.Value), - outpoint: breachInfo.LocalOutpoint, - signDescriptor: localSignDesc, - witnessType: lnwallet.CommitmentNoDelay, - witnessFunc: localWitness, - }, - - revokedOutput: &breachedOutput{ - amt: btcutil.Amount(remoteSignDesc.Output.Value), - outpoint: breachInfo.RemoteOutpoint, - signDescriptor: remoteSignDesc, - witnessType: lnwallet.CommitmentRevoke, - witnessFunc: remoteWitness, - }, - - htlcOutputs: []*breachedOutput{}, - - doneChan: make(chan struct{}), - } + // Using the breach information provided by the wallet and the + // channel snapshot, construct the retribution information that + // will be persisted to disk. + retInfo := newRetributionInfo(chanPoint, breachInfo, chanInfo) // Persist the pending retribution state to disk. if err := b.retributionStore.Add(retInfo); err != nil { - brarLog.Errorf("unable to persist "+ - "retribution info to db: %v", err) + brarLog.Errorf("unable to persist retribution info "+ + "to db: %v", err) } + // TODO(conner): move responsibility of channel closure into + // lnwallet. Have breach arbiter ACK after writing to disk, then + // have wallet mark channel as closed. This allows the wallet to + // attempt to retransmit the breach info if the either arbiter + // or the wallet goes down before completing the hand off. + + // Now that the breach arbiter has persisted the information, + // we can go ahead and mark the channel as closed in the + // channeldb. This step is done after persisting the + // retribution information so that a failure between these steps + // will cause an attempt to monitor the still-open channel. + // However, since the retribution information was persisted + // before, the arbiter will recognize that the channel should be + // closed, and proceed to mark it as such after a restart, and + // forgo monitoring it for breaches. + + // Construct the breached channel's close summary marking the + // channel using the snapshot from before, and marking this as a + // BreachClose. closeInfo := &channeldb.ChannelCloseSummary{ ChanPoint: *chanPoint, ClosingTXID: breachInfo.BreachTransaction.TxHash(), @@ -770,6 +747,10 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, CloseType: channeldb.BreachClose, IsPending: true, } + + // Next, persist the channel close to disk. Upon restart, the + // arbiter will recognize that this channel has been breached + // and marked close, and fast track its path to justice. if err := contract.DeleteState(closeInfo); err != nil { brarLog.Errorf("unable to delete channel state: %v", err) @@ -787,20 +768,93 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, } } +// SpendableOutput an interface which can be used by the breach arbiter to +// construct a transaction spending from outputs we control. +type SpendableOutput interface { + // Amount returns the number of satoshis contained within the output. + Amount() btcutil.Amount + + // Outpoint returns the reference to the output being spent, used to + // construct the corresponding transaction input. + OutPoint() *wire.OutPoint + + // BuildWitness returns a valid witness allowing this output to be + // spent, the witness should be attached to the transaction at the + // location determined by the given `txinIdx`. + BuildWitness(signer lnwallet.Signer, + txn *wire.MsgTx, + hashCache *txscript.TxSigHashes, + txinIdx int) ([][]byte, error) +} + // breachedOutput contains all the information needed to sweep a breached // output. A breached output is an output that we are now entitled to due to a // revoked commitment transaction being broadcast. type breachedOutput struct { - amt btcutil.Amount - outpoint wire.OutPoint + amt btcutil.Amount + outpoint wire.OutPoint + witnessType lnwallet.WitnessType + signDesc *lnwallet.SignDescriptor - signDescriptor lnwallet.SignDescriptor - witnessType lnwallet.WitnessType - witnessFunc lnwallet.WitnessGenerator - - twoStageClaim bool + witnessFunc lnwallet.WitnessGenerator } +// newBreachedOutput assembles new breachedOutput that can be used by the breach +// arbiter to construct a justice or sweep transaction. +func newBreachedOutput(outpoint *wire.OutPoint, + witnessType lnwallet.WitnessType, + signDescriptor *lnwallet.SignDescriptor) *breachedOutput { + + amount := signDescriptor.Output.Value + + return &breachedOutput{ + amt: btcutil.Amount(amount), + outpoint: *outpoint, + witnessType: witnessType, + signDesc: signDescriptor, + } +} + +// Amount returns the number of satoshis contained in the breached output. +func (bo *breachedOutput) Amount() btcutil.Amount { + return bo.amt +} + +// OutPoint returns the breached outputs identifier that is to be included as a +// transaction input. +func (bo *breachedOutput) OutPoint() *wire.OutPoint { + return &bo.outpoint +} + +// BuildWitness computes a valid witness that allows us to spend from the +// breached output. It does so by first generating and memoizing the witness +// generation function, which parameterized primarily by the witness type and +// sign descriptor. The method then returns the witness computed by invoking +// this function on the first and subsequent calls. +func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer, + txn *wire.MsgTx, + hashCache *txscript.TxSigHashes, + txinIdx int) ([][]byte, error) { + + // First, we ensure that the witness generation function has + // been initialized for this breached output. + if bo.witnessFunc == nil { + bo.witnessFunc = bo.witnessType.GenWitnessFunc( + signer, + bo.signDesc, + ) + } + + // Now that we have ensured that the witness generation function has + // been initialized, we can proceed to execute it and generate the + // witness for this particular breached output. + return bo.witnessFunc(txn, hashCache, txinIdx) +} + +// Add compile-time constraint ensuring breachedOutput implements +// SpendableOutput. +var _ SpendableOutput = (*breachedOutput)(nil) + // retributionInfo encapsulates all the data needed to sweep all the contested // funds within a channel whose contract has been breached by the prior // counterparty. This struct is used to create the justice transaction which @@ -810,11 +864,14 @@ type retributionInfo struct { commitHash chainhash.Hash chanPoint wire.OutPoint + // TODO(conner) remove the following group of fields after decoupling + // the breach arbiter from the wallet. + // Fields copied from channel snapshot when a breach is detected. This // is necessary for deterministically constructing the channel close // summary in the event that the breach arbiter crashes before closing // the channel. - remoteIdentity btcec.PublicKey + remoteIdentity *btcec.PublicKey capacity btcutil.Amount settledBalance btcutil.Amount @@ -827,6 +884,68 @@ type retributionInfo struct { doneChan chan struct{} } +// newRetributionInfo constructs a retributionInfo containing all the +// information required by the breach arbiter to recover funds from breached +// channels. The information is primarily populated using the BreachRetribution +// delivered by the wallet when it detects a channel breach. +func newRetributionInfo(chanPoint *wire.OutPoint, + breachInfo *lnwallet.BreachRetribution, + chanInfo *channeldb.ChannelSnapshot) *retributionInfo { + + // First, record the breach information and witness type for the local + // channel point. This will allow us to completely generate a valid + // witness in the event of failures, as it will be persisted in the + // retribution store. Here we use CommitmentNoDelay since this output + // belongs to us and has no time-based constraints on spending. + selfOutput := newBreachedOutput( + &breachInfo.LocalOutpoint, + lnwallet.CommitmentNoDelay, + &breachInfo.LocalOutputSignDesc, + ) + + // Second, record the same information and witness type regarding the + // remote outpoint, which belongs to the party who tried to steal our + // money! Here we set witnessType of the breachedOutput to + // CommitmentRevoke, since we will be using a revoke key, withdrawing + // the funds from the commitment transaction immediately. + revokedOutput := newBreachedOutput( + &breachInfo.RemoteOutpoint, + lnwallet.CommitmentRevoke, + &breachInfo.RemoteOutputSignDesc, + ) + + // Determine the number of second layer HTLCs we will attempt to sweep. + nHtlcs := len(breachInfo.HtlcRetributions) + + // Lastly, for each of the breached HTLC outputs, assemble the + // information we will persist to disk, such that we will be able to + // deterministically generate a valid witness for each output. This will + // allow the breach arbiter to recover from failures, in the event that + // it must sign and broadcast the justice transaction. + var htlcOutputs = make([]*breachedOutput, nHtlcs) + for i, breachedHtlc := range breachInfo.HtlcRetributions { + htlcOutputs[i] = newBreachedOutput( + &breachedHtlc.OutPoint, + lnwallet.CommitmentRevoke, + &breachedHtlc.SignDesc, + ) + } + + // TODO(conner) remove dependency on channel snapshot after decoupling + // channel closure from the breach arbiter. + + return &retributionInfo{ + commitHash: breachInfo.BreachTransaction.TxHash(), + chanPoint: *chanPoint, + remoteIdentity: &chanInfo.RemoteIdentity, + capacity: chanInfo.Capacity, + settledBalance: chanInfo.LocalBalance.ToSatoshis(), + selfOutput: selfOutput, + revokedOutput: revokedOutput, + htlcOutputs: htlcOutputs, + } +} + // createJusticeTx creates a transaction 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* @@ -834,66 +953,33 @@ type retributionInfo struct { func (b *breachArbiter) createJusticeTx( r *retributionInfo) (*wire.MsgTx, 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? - pkScriptOfJustice, err := newSweepPkScript(b.wallet) - if err != nil { - return nil, err + // Determine the number of HTLCs to be swept by the justice txn. + nHtlcs := len(r.htlcOutputs) + + // Assemble the breached outputs into a slice of spendable outputs, + // starting with the self and revoked outputs, then adding any htlc + // outputs. + var breachedOutputs = make([]SpendableOutput, 2+nHtlcs) + breachedOutputs[0] = r.selfOutput + breachedOutputs[1] = r.revokedOutput + for i, htlcOutput := range r.htlcOutputs { + breachedOutputs[2+i] = htlcOutput } - r.selfOutput.witnessFunc = r.selfOutput.witnessType.GenWitnessFunc( - &b.wallet.Cfg.Signer, &r.selfOutput.signDescriptor) - - r.revokedOutput.witnessFunc = r.revokedOutput.witnessType.GenWitnessFunc( - &b.wallet.Cfg.Signer, &r.revokedOutput.signDescriptor) - - for i := range r.htlcOutputs { - r.htlcOutputs[i].witnessFunc = r.htlcOutputs[i].witnessType.GenWitnessFunc( - &b.wallet.Cfg.Signer, &r.htlcOutputs[i].signDescriptor) + var txWeight uint64 + // Begin with a base txn weight of 4 * tx_non_wit_data + + // witness_header_size. + txWeight += 4*53 + 2 + // Add to_local revoke script and tx input. + txWeight += 154 + 4*41 + // Add to_remote p2wpkh witness and tx input. + txWeight += 108 + 4*41 + for range r.htlcOutputs { + // Add revoke offered htlc witness and tx input. + txWeight += 243 + 4*41 } - // Before creating the actual TxOut, we'll need to calculate the proper - // fee to attach to the transaction to ensure a timely confirmation. - // TODO(roasbeef): remove hard-coded fee - totalAmt := r.selfOutput.amt + r.revokedOutput.amt - sweepedAmt := int64(totalAmt - 5000) - - // With the fee calculated, we can now create the justice transaction - // using the information gathered above. - justiceTx := wire.NewMsgTx(2) - justiceTx.AddTxOut(&wire.TxOut{ - PkScript: pkScriptOfJustice, - Value: sweepedAmt, - }) - justiceTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: r.selfOutput.outpoint, - }) - justiceTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: r.revokedOutput.outpoint, - }) - - hashCache := txscript.NewTxSigHashes(justiceTx) - - // Finally, using the witness generation functions attached to the - // retribution information, we'll populate the inputs with fully valid - // witnesses for both commitment outputs, and all the pending HTLCs at - // this state in the channel's history. - // TODO(roasbeef): handle the 2-layer HTLCs - localWitness, err := r.selfOutput.witnessFunc(justiceTx, hashCache, 0) - if err != nil { - return nil, err - } - justiceTx.TxIn[0].Witness = localWitness - - remoteWitness, err := r.revokedOutput.witnessFunc(justiceTx, hashCache, 1) - if err != nil { - return nil, err - } - justiceTx.TxIn[1].Witness = remoteWitness - - return justiceTx, nil + return b.sweepSpendableOutputsTxn(txWeight, breachedOutputs...) } // craftCommitmentSweepTx creates a transaction to sweep the non-delayed output @@ -907,61 +993,102 @@ func (b *breachArbiter) createJusticeTx( func (b *breachArbiter) craftCommitSweepTx( closeInfo *lnwallet.UnilateralCloseSummary) (*wire.MsgTx, error) { - // First, we'll fetch a fresh script that we can use to sweep the funds - // under the control of the wallet. - sweepPkScript, err := newSweepPkScript(b.wallet) + selfOutput := newBreachedOutput( + closeInfo.SelfOutPoint, + lnwallet.CommitmentNoDelay, + closeInfo.SelfOutputSignDesc, + ) + + var txWeight uint64 + // Begin with a base txn weight of 4 * tx_non_wit_data + + // witness_header_size. + txWeight += 4*53 + 2 + // Add receiver script witness and tx input + txWeight += 325 + 4*41 + + return b.sweepSpendableOutputsTxn(txWeight, selfOutput) +} + +// sweepSpendableOutputsTxn creates a signed transaction from a sequence of +// spendable outputs by sweeping the funds into a single p2wkh output. +func (b *breachArbiter) sweepSpendableOutputsTxn( + txWeight uint64, + inputs ...SpendableOutput) (*wire.MsgTx, 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 := newSweepPkScript(b.wallet) if err != nil { return nil, err } - // TODO(roasbeef): use proper fees - outputAmt := closeInfo.SelfOutputSignDesc.Output.Value - sweepAmt := int64(outputAmt - 5000) - - if sweepAmt <= 0 { - // TODO(roasbeef): add output to special pool, can be swept - // when: funding a channel, sweeping time locked outputs, or - // delivering - // justice after a channel breach - return nil, fmt.Errorf("output to small to sweep in isolation") + // Compute the total amount contained in the inputs. + var totalAmt btcutil.Amount + for _, input := range inputs { + totalAmt += input.Amount() } - // With the amount we're sweeping computed, we can now creating the - // sweep transaction itself. - sweepTx := wire.NewMsgTx(1) - sweepTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *closeInfo.SelfOutPoint, - }) - sweepTx.AddTxOut(&wire.TxOut{ - PkScript: sweepPkScript, - Value: int64(sweepAmt), + feePerWeight := b.estimator.EstimateFeePerWeight(1) + txFee := btcutil.Amount(txWeight * feePerWeight) + + sweepAmt := int64(totalAmt - txFee) + + // With the fee calculated, we can now create the transaction using the + // information gathered above and the provided retribution information. + var txn = wire.NewMsgTx(2) + + // We begin by adding the output to which our funds will be deposited. + txn.AddTxOut(&wire.TxOut{ + PkScript: pkScript, + Value: sweepAmt, }) - // Next, we'll generate the signature required to satisfy the p2wkh - // witness program. - signDesc := closeInfo.SelfOutputSignDesc - signDesc.SigHashes = txscript.NewTxSigHashes(sweepTx) - signDesc.InputIndex = 0 - sweepSig, err := b.wallet.Cfg.Signer.SignOutputRaw(sweepTx, signDesc) - if err != nil { - return nil, err + // Next, we add all of the spendable outputs as inputs to the + // transaction. + for _, input := range inputs { + txn.AddTxIn(&wire.TxIn{ + PreviousOutPoint: *input.OutPoint(), + }) } - // Finally, we'll manually craft the witness. The witness here is the - // exact same as a regular p2wkh witness, but we'll need to ensure that - // we use the tweaked public key as the last item in the witness stack - // which was originally used to created the pkScript we're spending. - witness := make([][]byte, 2) - witness[0] = append(sweepSig, byte(txscript.SigHashAll)) - witness[1] = lnwallet.TweakPubKeyWithTweak( - signDesc.PubKey, signDesc.SingleTweak, - ).SerializeCompressed() + // Create a sighash cache to improve the performance of hashing and + // signing SigHashAll inputs. + hashCache := txscript.NewTxSigHashes(txn) - sweepTx.TxIn[0].Witness = witness + // Create a closure that encapsulates the process of initializing a + // particular output's witness generation function, computing the + // witness, and attaching it to the transaction. This function accepts + // an integer index representing the intended txin index, and the + // breached output from which it will spend. + addWitness := func(idx int, so SpendableOutput) error { + // First, we construct a valid witness for this outpoint and + // transaction using the SpendableOutput's witness generation + // function. + witness, err := so.BuildWitness( + b.wallet.Cfg.Signer, txn, hashCache, idx, + ) + if err != nil { + return err + } - brarLog.Infof("Sweeping commitment output with: %v", spew.Sdump(sweepTx)) + // Then, we add the witness to the transaction at the + // appropriate txin index. + txn.TxIn[idx].Witness = witness - return sweepTx, nil + return nil + } + + // Finally, generate a witness for each output and attach it to the + // transaction. + for i, input := range inputs { + if err := addWitness(i, input); err != nil { + return nil, err + } + } + + return txn, nil } // RetributionStore provides an interface for managing a persistent map from @@ -1154,7 +1281,7 @@ func (ret *retributionInfo) Decode(r io.Reader) error { if err != nil { return err } - ret.remoteIdentity = *remoteIdentity + ret.remoteIdentity = remoteIdentity if _, err := io.ReadFull(r, scratch[:8]); err != nil { return err @@ -1184,7 +1311,7 @@ func (ret *retributionInfo) Decode(r io.Reader) error { numHtlcOutputs := int(numHtlcOutputsU64) ret.htlcOutputs = make([]*breachedOutput, numHtlcOutputs) - for i := 0; i < numHtlcOutputs; i++ { + for i := range ret.htlcOutputs { ret.htlcOutputs[i] = &breachedOutput{} if err := ret.htlcOutputs[i].Decode(r); err != nil { return err @@ -1207,8 +1334,7 @@ func (bo *breachedOutput) Encode(w io.Writer) error { return err } - if err := lnwallet.WriteSignDescriptor( - w, &bo.signDescriptor); err != nil { + if err := lnwallet.WriteSignDescriptor(w, bo.signDesc); err != nil { return err } @@ -1217,15 +1343,6 @@ func (bo *breachedOutput) Encode(w io.Writer) error { return err } - if bo.twoStageClaim { - scratch[0] = 1 - } else { - scratch[0] = 0 - } - if _, err := w.Write(scratch[:1]); err != nil { - return err - } - return nil } @@ -1242,8 +1359,8 @@ func (bo *breachedOutput) Decode(r io.Reader) error { return err } - if err := lnwallet.ReadSignDescriptor( - r, &bo.signDescriptor); err != nil { + bo.signDesc = &lnwallet.SignDescriptor{} + if err := lnwallet.ReadSignDescriptor(r, bo.signDesc); err != nil { return err } @@ -1253,14 +1370,5 @@ func (bo *breachedOutput) Decode(r io.Reader) error { bo.witnessType = lnwallet.WitnessType( binary.BigEndian.Uint16(scratch[:2])) - if _, err := io.ReadFull(r, scratch[:1]); err != nil { - return err - } - if scratch[0] == 1 { - bo.twoStageClaim = true - } else { - bo.twoStageClaim = false - } - return nil } From 9cc9977f956860be84e7cffd281884ff570e14ce Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 21 Aug 2017 16:58:21 -0700 Subject: [PATCH 02/15] breacharbiter_test: modify test vectors to support new structs --- breacharbiter_test.go | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 6affbe144..c82ac2d17 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -173,24 +173,21 @@ var ( breachedOutputs = []breachedOutput{ { - amt: btcutil.Amount(1e7), - outpoint: breachOutPoints[0], - witnessType: lnwallet.CommitmentNoDelay, - twoStageClaim: true, + amt: btcutil.Amount(1e7), + outpoint: breachOutPoints[0], + witnessType: lnwallet.CommitmentNoDelay, }, { - amt: btcutil.Amount(2e9), - outpoint: breachOutPoints[1], - witnessType: lnwallet.CommitmentRevoke, - twoStageClaim: false, + amt: btcutil.Amount(2e9), + outpoint: breachOutPoints[1], + witnessType: lnwallet.CommitmentRevoke, }, { - amt: btcutil.Amount(3e4), - outpoint: breachOutPoints[2], - witnessType: lnwallet.CommitmentDelayOutput, - twoStageClaim: false, + amt: btcutil.Amount(3e4), + outpoint: breachOutPoints[2], + witnessType: lnwallet.CommitmentDelayOutput, }, } @@ -240,7 +237,7 @@ func init() { // channel point. for i := range retributions { retInfo := &retributions[i] - retInfo.remoteIdentity = *breachedOutputs[i].signDescriptor.PubKey + retInfo.remoteIdentity = breachedOutputs[i].signDesc.PubKey retributionMap[retInfo.chanPoint] = *retInfo } } @@ -320,7 +317,7 @@ func initBreachedOutputs() error { breachKeys[i]) } sd.PubKey = pubkey - bo.signDescriptor = *sd + bo.signDesc = sd } return nil @@ -395,7 +392,6 @@ func copyRetInfo(retInfo *retributionInfo) *retributionInfo { selfOutput: retInfo.selfOutput, revokedOutput: retInfo.revokedOutput, htlcOutputs: make([]*breachedOutput, nHtlcs), - doneChan: retInfo.doneChan, } for i, htlco := range retInfo.htlcOutputs { @@ -776,8 +772,8 @@ restartCheck: foundSet[ret.chanPoint] = struct{}{} } else { - return fmt.Errorf("unkwown retribution "+ - "retrieved from db: %v", ret) + return fmt.Errorf("unkwown retribution retrieved "+ + "from db: %v", ret) } return nil From da250cd79b8dc1eb8e0a4c551c44aab0c804b76f Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 21 Aug 2017 17:03:16 -0700 Subject: [PATCH 03/15] lnwallet/witnessgen: remove use of pointer to Signer iface --- lnwallet/witnessgen.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lnwallet/witnessgen.go b/lnwallet/witnessgen.go index 42e92fc37..ad5e139ce 100644 --- a/lnwallet/witnessgen.go +++ b/lnwallet/witnessgen.go @@ -35,7 +35,7 @@ type WitnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes, // GenWitnessFunc will return a WitnessGenerator function that an output // uses to generate the witness for a sweep transaction. -func (wt WitnessType) GenWitnessFunc(signer *Signer, +func (wt WitnessType) GenWitnessFunc(signer Signer, descriptor *SignDescriptor) WitnessGenerator { return func(tx *wire.MsgTx, hc *txscript.TxSigHashes, @@ -47,11 +47,11 @@ func (wt WitnessType) GenWitnessFunc(signer *Signer, switch wt { case CommitmentTimeLock: - return CommitSpendTimeout(*signer, desc, tx) + return CommitSpendTimeout(signer, desc, tx) case CommitmentNoDelay: - return CommitSpendNoDelay(*signer, desc, tx) + return CommitSpendNoDelay(signer, desc, tx) case CommitmentRevoke: - return CommitSpendRevoke(*signer, desc, tx) + return CommitSpendRevoke(signer, desc, tx) default: return nil, fmt.Errorf("unknown witness type: %v", wt) } From 7f93aec31c4f4f31b76720b89655202e77ab7473 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 21 Aug 2017 17:03:59 -0700 Subject: [PATCH 04/15] utxonursery: remove use of pointer to lnwallet.Signer iface --- utxonursery.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utxonursery.go b/utxonursery.go index 8ccd9aca1..f022a0896 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -786,7 +786,7 @@ func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet, // output or not. for _, kgtnOutput := range kgtnOutputs { kgtnOutput.witnessFunc = kgtnOutput.witnessType.GenWitnessFunc( - &wallet.Cfg.Signer, kgtnOutput.signDescriptor, + wallet.Cfg.Signer, kgtnOutput.signDescriptor, ) } From f0ac99197984dc3330403d258bbb4276a521da74 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 29 Aug 2017 19:07:52 -0700 Subject: [PATCH 05/15] breacharbiter: use BOLT 5 constants --- breacharbiter.go | 55 ++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 84eabb083..30ff4f910 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -14,6 +14,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/roasbeef/btcd/blockchain" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/txscript" @@ -168,11 +169,11 @@ func (b *breachArbiter) Start() error { // channels can be discarded, as their fate will be placed in the hands // of an exactRetribution task spawned later. // - // NOTE Spawning of the exactRetribution task is intentionally postponed - // until after this step in order to ensure that the all breached - // channels are reflected as closed in channeldb and consistent with - // what is checkpointed by the breach arbiter. Instead of treating the - // breached-and-closed and breached-but-still-active channels as + // NOTE: Spawning of the exactRetribution task is intentionally + // postponed until after this step in order to ensure that the all + // breached channels are reflected as closed in channeldb and consistent + // with what is checkpointed by the breach arbiter. Instead of treating + // the breached-and-closed and breached-but-still-active channels as // separate sets of channels, we first ensure that all // breached-but-still-active channels are promoted to // breached-and-closed during restart, allowing us to treat them as a @@ -799,8 +800,8 @@ type breachedOutput struct { witnessFunc lnwallet.WitnessGenerator } -// newBreachedOutput assembles new breachedOutput that can be used by the breach -// arbiter to construct a justice or sweep transaction. +// newBreachedOutput assembles a new breachedOutput that can be used by the +// breach arbiter to construct a justice or sweep transaction. func newBreachedOutput(outpoint *wire.OutPoint, witnessType lnwallet.WitnessType, signDescriptor *lnwallet.SignDescriptor) *breachedOutput { @@ -864,7 +865,7 @@ type retributionInfo struct { commitHash chainhash.Hash chanPoint wire.OutPoint - // TODO(conner) remove the following group of fields after decoupling + // TODO(conner): remove the following group of fields after decoupling // the breach arbiter from the wallet. // Fields copied from channel snapshot when a breach is detected. This @@ -931,7 +932,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, ) } - // TODO(conner) remove dependency on channel snapshot after decoupling + // TODO(conner): remove dependency on channel snapshot after decoupling // channel closure from the breach arbiter. return &retributionInfo{ @@ -967,17 +968,16 @@ func (b *breachArbiter) createJusticeTx( } var txWeight uint64 - // Begin with a base txn weight of 4 * tx_non_wit_data + - // witness_header_size. - txWeight += 4*53 + 2 + // Begin with a base txn weight, e.g. version, nLockTime, etc. + txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize + // Add to_local revoke script and tx input. - txWeight += 154 + 4*41 + txWeight += 4*lnwallet.InputSize + lnwallet.ToLocalPenaltyWitnessSize // Add to_remote p2wpkh witness and tx input. - txWeight += 108 + 4*41 - for range r.htlcOutputs { - // Add revoke offered htlc witness and tx input. - txWeight += 243 + 4*41 - } + txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize + // Add revoked offered-htlc witnesses and tx inputs. + txWeight += uint64(len(r.htlcOutputs)) * + (4*lnwallet.InputSize + lnwallet.OfferedHtlcWitnessSize) return b.sweepSpendableOutputsTxn(txWeight, breachedOutputs...) } @@ -1000,19 +1000,17 @@ func (b *breachArbiter) craftCommitSweepTx( ) var txWeight uint64 - // Begin with a base txn weight of 4 * tx_non_wit_data + - // witness_header_size. - txWeight += 4*53 + 2 - // Add receiver script witness and tx input - txWeight += 325 + 4*41 + // Begin with a base txn weight, e.g. version, nLockTime, etc. + txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize + // Add to_local p2wpkh witness and tx input. + txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize return b.sweepSpendableOutputsTxn(txWeight, selfOutput) } // sweepSpendableOutputsTxn creates a signed transaction from a sequence of // spendable outputs by sweeping the funds into a single p2wkh output. -func (b *breachArbiter) sweepSpendableOutputsTxn( - txWeight uint64, +func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64, inputs ...SpendableOutput) (*wire.MsgTx, error) { // First, we obtain a new public key script from the wallet which we'll @@ -1053,6 +1051,13 @@ func (b *breachArbiter) sweepSpendableOutputsTxn( }) } + // Before signing the transaction, check to ensure that it meets some + // basic validity requirements. + btx := btcutil.NewTx(txn) + if err := blockchain.CheckTransactionSanity(btx); err != nil { + return nil, err + } + // Create a sighash cache to improve the performance of hashing and // signing SigHashAll inputs. hashCache := txscript.NewTxSigHashes(txn) From b3a80f76284cfed1dad93f52e4eb82ca0c8e2f97 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 29 Aug 2017 22:14:04 -0700 Subject: [PATCH 06/15] lnwallet/size: updated with recent BOLT 5 constants --- lnwallet/size.go | 171 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 167 insertions(+), 4 deletions(-) diff --git a/lnwallet/size.go b/lnwallet/size.go index 74037e9ae..55c956c3c 100644 --- a/lnwallet/size.go +++ b/lnwallet/size.go @@ -18,12 +18,31 @@ const ( // - WitnessScriptSHA256: 32 bytes P2WSHSize = 1 + 1 + 32 + // P2WKHOutputSize 31 bytes + // - value: 8 bytes + // - var_int: 1 byte (pkscript_length) + // - pkscript (p2wpkh): 22 bytes + P2WKHOutputSize = 8 + 1 + 22 + + // P2WSHOutputSize 43 bytes + // - value: 8 bytes + // - var_int: 1 byte (pkscript_length) + // - pkscript (p2wsh): 34 bytes + P2WSHOutputSize = 8 + 1 + 34 + // P2WPKHSize 22 bytes // - OP_0: 1 byte // - OP_DATA: 1 byte (PublicKeyHASH160 length) // - PublicKeyHASH160: 20 bytes P2WPKHSize = 1 + 1 + 20 + // P2WKHWitnessSize 108 bytes + // - OP_DATA: 1 byte (signature length) + // - signature + // - OP_DATA: 1 byte (pubkey length) + // - pubkey + P2WKHWitnessSize = 1 + 73 + 1 + 33 + // MultiSigSize 71 bytes // - OP_2: 1 byte // - OP_DATA: 1 byte (pubKeyAlice length) @@ -45,7 +64,7 @@ const ( // - WitnessScript (MultiSig) WitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + MultiSigSize - // FundingInputSize 41 bytes + // InputSize 41 bytes // - PreviousOutPoint: // - Hash: 32 bytes // - Index: 4 bytes @@ -57,7 +76,12 @@ const ( // we separate the calculation of ordinary data // from witness data. // - Sequence: 4 bytes - FundingInputSize = 32 + 4 + 1 + 4 + InputSize = 32 + 4 + 1 + 4 + + // FundingInputSize represents the size of an input to a funding + // transaction, and is equivalent to the size of a standard segwit input + // as calculated above. + FundingInputSize = InputSize // CommitmentDelayOutput 43 bytes // - Value: 8 bytes @@ -82,7 +106,19 @@ const ( // - Marker: 1 byte WitnessHeaderSize = 1 + 1 - // BaseCommitmentTxSize 125 43 * num-htlc-outputs bytes + // BaseSweepTxSize 42 + 41 * num-swept-inputs bytes + // - Version: 4 bytes + // - WitnessHeader <---- part of the witness data + // - CountTxIn: 2 byte + // - TxIn: 41 * num-swept-inputs bytes + // ....SweptInputs.... + // - CountTxOut: 1 byte + // - TxOut: 31 bytes + // P2WPKHOutput: 31 bytes + // - LockTime: 4 bytes + BaseSweepTxSize = 4 + 2 + 1 + P2WKHOutputSize + 4 + + // BaseCommitmentTxSize 125 + 43 * num-htlc-outputs bytes // - Version: 4 bytes // - WitnessHeader <---- part of the witness data // - CountTxIn: 1 byte @@ -119,7 +155,134 @@ const ( // of a contract breach, the punishment transaction is able to sweep // all the HTLC's yet still remain below the widely used standard // weight limits. - MaxHTLCNumber = 967 + MaxHTLCNumber = 966 + + // ToLocalPenaltyScriptSize 83 bytes + // - OP_IF: 1 byte + // - OP_DATA: 1 byte (revocationkey length) + // - revocationkey: 33 bytes + // - OP_CHECKSIG: 1 byte + // - OP_ELSE: 1 byte + // - OP_DATA: 1 byte (localkey length) + // - localkey: 33 bytes + // - OP_CHECKSIG_VERIFY: 1 byte + // - OP_DATA: 1 byte (delay length) + // - delay: 8 bytes + // -OP_CHECKSEQUENCEVERIFY: 1 byte + // - OP_ENDIF: 1 byte + ToLocalPenaltyScriptSize = 1 + 1 + 33 + 1 + 1 + 1 + 33 + 1 + 1 + 8 + 1 + 1 + + // ToLocalPenaltyWitnessSize 160 bytes + // - number_of_witness_elements: 1 byte + // - revocation_sig_length: 1 byte + // - revocation_sig: 73 bytes + // - one_length: 1 byte + // - witness_script_length: 1 byte + // - witness_script (to_local_script) + ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalPenaltyScriptSize + + // AcceptedHtlcPenaltyScriptSize 139 bytes + // - OP_DUP: 1 byte + // - OP_HASH160: 1 byte + // - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length) + // - RIPEMD160(SHA256(revocationkey)): 20 bytes + // - OP_EQUAL: 1 byte + // - OP_IF: 1 byte + // - OP_CHECKSIG: 1 byte + // - OP_ELSE: 1 byte + // - OP_DATA: 1 byte (remotekey length) + // - remotekey: 33 bytes + // - OP_SWAP: 1 byte + // - OP_SIZE: 1 byte + // - 32: 1 byte + // - OP_EQUAL: 1 byte + // - OP_IF: 1 byte + // - OP_HASH160: 1 byte + // - OP_DATA: 1 byte (RIPEMD160(payment_hash) length) + // - RIPEMD160(payment_hash): 20 bytes + // - OP_EQUALVERIFY: 1 byte + // - 2: 1 byte + // - OP_SWAP: 1 byte + // - OP_DATA: 1 byte (localkey length) + // - localkey: 33 bytes + // - 2: 1 byte + // - OP_CHECKMULTISIG: 1 byte + // - OP_ELSE: 1 byte + // - OP_DROP: 1 byte + // - OP_DATA: 1 byte (cltv_expiry length) + // - cltv_expiry: 4 bytes + // - OP_CHECKLOCKTIMEVERIFY: 1 byte + // - OP_DROP: 1 byte + // - OP_CHECKSIG: 1 byte + // - OP_ENDIF: 1 byte + // - OP_ENDIF: 1 byte + AcceptedHtlcPenaltyScriptSize = 3*1 + 20 + 5*1 + 33 + 7*1 + 20 + 4*1 + + 33 + 5*1 + 4 + 5*1 + + // AcceptedHtlcPenaltyWitnessSize 249 bytes + // - number_of_witness_elements: 1 byte + // - revocation_sig_length: 1 byte + // - revocation_sig: 73 bytes + // - revocation_key_length: 1 byte + // - revocation_key: 33 bytes + // - witness_script_length: 1 byte + // - witness_script (accepted_htlc_script) + AcceptedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + + AcceptedHtlcPenaltyScriptSize + + // OfferedHtlcScriptSize 133 bytes + // - OP_DUP: 1 byte + // - OP_HASH160: 1 byte + // - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length) + // - RIPEMD160(SHA256(revocationkey)): 20 bytes + // - OP_EQUAL: 1 byte + // - OP_IF: 1 byte + // - OP_CHECKSIG: 1 byte + // - OP_ELSE: 1 byte + // - OP_DATA: 1 byte (remotekey length) + // - remotekey: 33 bytes + // - OP_SWAP: 1 byte + // - OP_SIZE: 1 byte + // - OP_DATA: 1 byte (32 length) + // - 32: 1 byte + // - OP_EQUAL: 1 byte + // - OP_NOTIF: 1 byte + // - OP_DROP: 1 byte + // - 2: 1 byte + // - OP_SWAP: 1 byte + // - OP_DATA: 1 byte (localkey length) + // - localkey: 33 bytes + // - 2: 1 byte + // - OP_CHECKMULTISIG: 1 byte + // - OP_ELSE: 1 byte + // - OP_HASH160: 1 byte + // - OP_DATA: 1 byte (RIPEMD160(payment_hash) length) + // - RIPEMD160(payment_hash): 20 bytes + // - OP_EQUALVERIFY: 1 byte + // - OP_CHECKSIG: 1 byte + // - OP_ENDIF: 1 byte + // - OP_ENDIF: 1 byte + OfferedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 10*1 + 33 + 5*1 + 20 + 4*1 + + // OfferedHtlcWitnessSize 243 bytes + // - number_of_witness_elements: 1 byte + // - revocation_sig_length: 1 byte + // - revocation_sig: 73 bytes + // - revocation_key_length: 1 byte + // - revocation_key: 33 bytes + // - witness_script_length: 1 byte + // - witness_script (offered_htlc_script) + OfferedHtlcWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSize + + // OfferedHtlcPenaltyWitnessSize 243 bytes + // - number_of_witness_elements: 1 byte + // - revocation_sig_length: 1 byte + // - revocation_sig: 73 bytes + // - revocation_key_length: 1 byte + // - revocation_key: 33 bytes + // - witness_script_length: 1 byte + // - witness_script (offered_htlc_script) + OfferedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + OfferedHtlcScriptSize ) // estimateCommitTxWeight estimate commitment transaction weight depending on From b64d4356c1d2886d3edcda2b5d500ea8942bfe84 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 30 Aug 2017 00:43:38 -0700 Subject: [PATCH 07/15] lnd_test: adds breach test for remote hodl This commit alters the existing retribution post breach conf test case with the intention of testing the correct response in event that the remote party broadcasts a prior state while HTLCs have been extended. This serves as a preliminary integration for an expansion of the breach arbiter integration tests. The primary change involves using the new htlchodl mode for debugging, which causes the remote peer to ignore any intent to settle incoming HTLCs. The result is that any payments sent to the remote party are held in limbo, allowing us to test for these conditions more accurately. Currently the test case only tests that the justice transaction is mined. After we have fully integrated the breach arbiter to sweep 2nd layer HTLCs, this test will be altered to check for spends from the appropriate inputs. --- lnd_test.go | 227 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 149 insertions(+), 78 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index 9f1b89348..f14119ee1 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -2010,10 +2010,10 @@ func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) { } } -// testRevokedCloseRetributinPostBreachConf tests that Alice is able carry out -// retribution in the event that she fails immediately after receiving a -// confirmation of Carol's breach txn. -func testRevokedCloseRetributionPostBreachConf( +// testRevokedCloseRetributionRemoteHodl tests that Alice properly responds to a +// channel breach made by the remote party, specifically in the case that the +// remote party breaches before settling extended HTLCs. +func testRevokedCloseRetributionRemoteHodl( net *networkHarness, t *harnessTest) { @@ -2021,33 +2021,34 @@ func testRevokedCloseRetributionPostBreachConf( const ( timeout = time.Duration(time.Second * 10) chanAmt = maxFundingAmount + pushAmt = 20000 paymentAmt = 10000 numInvoices = 6 ) - // Since we'd like to test some multi-hop failure scenarios, we'll - // introduce another node into our test network: Carol. - carol, err := net.NewNode(nil) + // Since this test will result in the counterparty being left in a weird + // state, we will introduce another node into our test network: Carol. + carol, err := net.NewNode([]string{"--debughtlc", "--hodlhtlc"}) if err != nil { t.Fatalf("unable to create new nodes: %v", err) } - // We must let Dave have an open channel before he can send a node - // announcement, so we open a channel with Carol, + // We must let Alice communicate with Carol before they are able to + // open channel, so we connect Alice and Carol, if err := net.ConnectNodes(ctxb, net.Alice, carol); err != nil { t.Fatalf("unable to connect alice to carol: %v", err) } // In order to test Alice's response to an uncooperative channel // closure by Carol, we'll first open up a channel between them with a - // 0.5 BTC value. + // maxFundingAmount (2^24) satoshis value. ctxt, _ := context.WithTimeout(ctxb, timeout) chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, carol, - chanAmt, 0) + chanAmt, pushAmt) - // With the channel open, we'll create a few invoices for Caro that + // With the channel open, we'll create a few invoices for Carol that // Alice will pay to in order to advance the state of the channel. - bobPaymentHashes := make([][]byte, numInvoices) + carolPaymentHashes := make([][]byte, numInvoices) for i := 0; i < numInvoices; i++ { preimage := bytes.Repeat([]byte{byte(192 - i)}, 32) invoice := &lnrpc.Invoice{ @@ -2060,30 +2061,57 @@ func testRevokedCloseRetributionPostBreachConf( t.Fatalf("unable to add invoice: %v", err) } - bobPaymentHashes[i] = resp.RHash + carolPaymentHashes[i] = resp.RHash } - // As we'll be querying the state of bob's channels frequently we'll + // As we'll be querying the state of Carol's channels frequently we'll // create a closure helper function for the purpose. getCarolChanInfo := func() (*lnrpc.ActiveChannel, error) { req := &lnrpc.ListChannelsRequest{} - bobChannelInfo, err := carol.ListChannels(ctxb, req) + carolChannelInfo, err := carol.ListChannels(ctxb, req) if err != nil { return nil, err } - if len(bobChannelInfo.Channels) != 1 { - t.Fatalf("bob should only have a single channel, instead he has %v", - len(bobChannelInfo.Channels)) + if len(carolChannelInfo.Channels) != 1 { + t.Fatalf("carol should only have a single channel, instead he has %v", + len(carolChannelInfo.Channels)) } - return bobChannelInfo.Channels[0], nil + return carolChannelInfo.Channels[0], nil + } + // We'll introduce a closure to validate that Carol's current balance + // matches the given expected amount. + checkCarolBalance := func(expectedAmt int64) { + carolChan, err := getCarolChanInfo() + if err != nil { + t.Fatalf("unable to get carol's channel info: %v", err) + } + if carolChan.LocalBalance != expectedAmt { + t.Fatalf("carol's balance is incorrect, "+ + "got %v, expected %v", carolChan.LocalBalance, + expectedAmt) + } + } + // We'll introduce another closure to validate that Carol's current + // number of updates is at least as large as the provided minimum + // number. + checkCarolNumUpdatesAtleast := func(minimum uint64) { + carolChan, err := getCarolChanInfo() + if err != nil { + t.Fatalf("unable to get carol's channel info: %v", err) + } + if carolChan.NumUpdates < minimum { + t.Fatalf("carol's numupdates is incorrect, want %v "+ + "to be atleast %v", carolChan.NumUpdates, + minimum) + } } // Wait for Alice to receive the channel edge from the funding manager. ctxt, _ = context.WithTimeout(ctxb, timeout) err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) if err != nil { - t.Fatalf("alice didn't see the alice->bob channel before "+ + t.Fatalf("alice didn't see the alice->carol channel before "+ "timeout: %v", err) } @@ -2094,16 +2122,26 @@ func testRevokedCloseRetributionPostBreachConf( if err != nil { t.Fatalf("unable to create payment stream for alice: %v", err) } - sendPayments := func(start, stop int) error { + sendPayments := func(start, stop int, isHodl bool) error { for i := start; i < stop; i++ { sendReq := &lnrpc.SendRequest{ - PaymentHash: bobPaymentHashes[i], + PaymentHash: carolPaymentHashes[i], Dest: carol.PubKey[:], Amt: paymentAmt, } if err := alicePayStream.Send(sendReq); err != nil { return err } + + // If the remote peer is in hodl mode, we should not + // attempt to receive a message, otherwise the test will + // block. + if isHodl { + continue + } + + // Otherwise, the peer is not in hodl mode, and we will + // expect a response. if resp, err := alicePayStream.Recv(); err != nil { t.Fatalf("payment stream has been closed: %v", err) } else if resp.PaymentError != "" { @@ -2114,80 +2152,94 @@ func testRevokedCloseRetributionPostBreachConf( return nil } + // Ensure that carol's balance starts with the amount we pushed to her. + checkCarolBalance(pushAmt) + // Send payments from Alice to Carol using 3 of Carol's payment hashes // generated above. - if err := sendPayments(0, numInvoices/2); err != nil { + if err := sendPayments(0, numInvoices/2, true); err != nil { t.Fatalf("unable to send payment: %v", err) } + time.Sleep(time.Millisecond * 200) // Next query for Carol's channel state, as we sent 3 payments of 10k - // satoshis each, Carol should now see his balance as being 30k satoshis. - time.Sleep(time.Millisecond * 200) - bobChan, err := getCarolChanInfo() + // satoshis each, however Carol should now see her balance as being + // equal to the push amount in satoshis since she has not settled. + carolChan, err := getCarolChanInfo() if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) + t.Fatalf("unable to get carol's channel info: %v", err) } - if bobChan.LocalBalance != 30000 { - t.Fatalf("bob's balance is incorrect, got %v, expected %v", - bobChan.LocalBalance, 30000) - } - // Grab Carol's current commitment height (update number), we'll later - // revert him to this state after additional updates to force him to + // revert her to this state after additional updates to force her to // broadcast this soon to be revoked state. - bobStateNumPreCopy := bobChan.NumUpdates + carolStateNumPreCopy := carolChan.NumUpdates + + // Ensure that carol's balance still reflects the original amount we + // pushed to her. + checkCarolBalance(pushAmt) + // Since Carol has not settled, she should only see at least one update + // to her channel. + checkCarolNumUpdatesAtleast(1) // Create a temporary file to house Carol's database state at this // particular point in history. - bobTempDbPath, err := ioutil.TempDir("", "bob-past-state") + carolTempDbPath, err := ioutil.TempDir("", "carol-past-state") if err != nil { t.Fatalf("unable to create temp db folder: %v", err) } - bobTempDbFile := filepath.Join(bobTempDbPath, "channel.db") - defer os.Remove(bobTempDbPath) + carolTempDbFile := filepath.Join(carolTempDbPath, "channel.db") + defer os.Remove(carolTempDbPath) // With the temporary file created, copy Carol's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - bobDbPath := filepath.Join(carol.cfg.DataDir, "simnet/bitcoin/channel.db") - if err := copyFile(bobTempDbFile, bobDbPath); err != nil { + carolDbPath := filepath.Join(carol.cfg.DataDir, "simnet/bitcoin/channel.db") + if err := copyFile(carolTempDbFile, carolDbPath); err != nil { t.Fatalf("unable to copy database files: %v", err) } // Finally, send payments from Alice to Carol, consuming Carol's remaining // payment hashes. - if err := sendPayments(numInvoices/2, numInvoices); err != nil { + if err := sendPayments(numInvoices/2, numInvoices, true); err != nil { t.Fatalf("unable to send payment: %v", err) } + time.Sleep(200 * time.Millisecond) - bobChan, err = getCarolChanInfo() - if err != nil { - t.Fatalf("unable to get bob chan info: %v", err) - } + // Ensure that carol's balance still shows the amount we originally + // pushed to her, and that at least one more update has occurred. + checkCarolBalance(pushAmt) + checkCarolNumUpdatesAtleast(carolStateNumPreCopy + 1) - // Now we shutdown Carol, copying over the his temporary database state - // which has the *prior* channel state over his current most up to date + // Now we shutdown Carol, copying over the her temporary database state + // which has the *prior* channel state over her current most up to date // state. With this, we essentially force Carol to travel back in time // within the channel's history. if err = net.RestartNode(carol, func() error { - return os.Rename(bobTempDbFile, bobDbPath) + return os.Rename(carolTempDbFile, carolDbPath) }); err != nil { t.Fatalf("unable to restart node: %v", err) } - // Now query for Carol's channel state, it should show that he's at a - // state number in the past, not the *latest* state. - bobChan, err = getCarolChanInfo() + time.Sleep(200 * time.Millisecond) + + // Ensure that Carol's view of the channel is consistent with the + // state of the channel just before it was snapshotted. + checkCarolBalance(pushAmt) + checkCarolNumUpdatesAtleast(1) + + // Now query for Carol's channel state, it should show that she's at a + // state number in the past, *not* the latest state. + carolChan, err = getCarolChanInfo() if err != nil { - t.Fatalf("unable to get bob chan info: %v", err) + t.Fatalf("unable to get carol chan info: %v", err) } - if bobChan.NumUpdates != bobStateNumPreCopy { - t.Fatalf("db copy failed: %v", bobChan.NumUpdates) + if carolChan.NumUpdates != carolStateNumPreCopy { + t.Fatalf("db copy failed: %v", carolChan.NumUpdates) } // Now force Carol to execute a *force* channel closure by unilaterally - // broadcasting his current channel state. This is actually the - // commitment transaction of a prior *revoked* state, so he'll soon + // broadcasting her current channel state. This is actually the + // commitment transaction of a prior *revoked* state, so she'll soon // feel the wrath of Alice's retribution. force := true closeUpdates, _, err := net.CloseChannel(ctxb, carol, chanPoint, force) @@ -2195,18 +2247,29 @@ func testRevokedCloseRetributionPostBreachConf( t.Fatalf("unable to close channel: %v", err) } - // Finally, generate a single block, wait for the final close status - // update, then ensure that the closing transaction was included in the - // block. + // Query the mempool for Alice's justice transaction, this should be + // broadcast as Bob's contract breaching transaction gets confirmed + // above. + _, err = waitForTxInMempool(net.Miner.Node, 5*time.Second) + if err != nil { + t.Fatalf("unable to find Alice's justice tx in mempool: %v", err) + } + time.Sleep(200 * time.Millisecond) + + // Generate a single block to mine the breach transaction. block := mineBlocks(t, net, 1)[0] - // Here, Alice receives a confirmation of Carol's breach transaction. We - // restart Alice to ensure that she is persisting her retribution state and - // continues exacting justice after her node restarts. + // Wait so Alice receives a confirmation of Carol's breach transaction. + time.Sleep(200 * time.Millisecond) + + // We restart Alice to ensure that she is persisting her retribution + // state and continues exacting justice after her node restarts. if err := net.RestartNode(net.Alice, nil); err != nil { t.Fatalf("unable to stop Alice's node: %v", err) } + // Finally, Wait for the final close status update, then ensure that the + // closing transaction was included in the block. breachTXID, err := net.WaitForChannelClose(ctxb, closeUpdates) if err != nil { t.Fatalf("error while waiting for channel close: %v", err) @@ -2222,20 +2285,6 @@ func testRevokedCloseRetributionPostBreachConf( } time.Sleep(100 * time.Millisecond) - // Query for the mempool transaction found above. Then assert that all - // the inputs of this transaction are spending outputs generated by - // Carol's breach transaction above. - justiceTx, err := net.Miner.Node.GetRawTransaction(justiceTXID) - if err != nil { - t.Fatalf("unable to query for justice tx: %v", err) - } - for _, txIn := range justiceTx.MsgTx().TxIn { - if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) { - t.Fatalf("justice tx not spending commitment utxo "+ - "instead is: %v", txIn.PreviousOutPoint) - } - } - // We restart Alice here to ensure that she persists her retribution state // and successfully continues exacting retribution after restarting. At // this point, Alice has broadcast the justice transaction, but it hasn't @@ -2245,6 +2294,28 @@ func testRevokedCloseRetributionPostBreachConf( t.Fatalf("unable to restart Alice's node: %v", err) } + // Query for the mempool transaction found above. Then assert that (1) + // the justice tx has the appropriate number of inputs, and (2) all + // the inputs of this transaction are spending outputs generated by + // Carol's breach transaction above. + justiceTx, err := net.Miner.Node.GetRawTransaction(justiceTXID) + if err != nil { + t.Fatalf("unable to query for justice tx: %v", err) + } + exNumInputs := 2 + numInvoices/2 + if len(justiceTx.MsgTx().TxIn) != exNumInputs { + t.Fatalf("justice tx should have exactly 2 commitment inputs"+ + "and %v htlc inputs, expected %v in total, got %v", + numInvoices/2, exNumInputs, + len(justiceTx.MsgTx().TxIn)) + } + for _, txIn := range justiceTx.MsgTx().TxIn { + if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) { + t.Fatalf("justice tx not spending commitment utxo "+ + "instead is: %v", txIn.PreviousOutPoint) + } + } + // Now mine a block, this transaction should include Alice's justice // transaction which was just accepted into the mempool. block = mineBlocks(t, net, 1)[0] @@ -3459,8 +3530,8 @@ var testsCases = []*testCase{ test: testRevokedCloseRetribution, }, { - name: "revoked uncooperative close retribution post breach conf", - test: testRevokedCloseRetributionPostBreachConf, + name: "revoked uncooperative close retribution remote hodl", + test: testRevokedCloseRetributionRemoteHodl, }, } From a8d667ba35faaec53c32a340fb5a4da32a004559 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 1 Sep 2017 03:11:14 -0700 Subject: [PATCH 08/15] breacharbiter: sweep incoming + outgoing htlcs This commit also adds a BreachConfig to abstract the instantiation of the breach arbiter, as well as various formatting improvements. --- breacharbiter.go | 191 +++++++++++++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 74 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 30ff4f910..acbbded86 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -30,6 +30,52 @@ import ( // continue from the persisted state. var retributionBucket = []byte("retribution") +// BreachConfig bundles the required subsystems used by the breach arbiter. An +// instance of BreachConfig is passed to newBreachArbiter during instantiation. +type BreachConfig struct { + // Signer is used by the breach arbiter to generate sweep transactions, + // which move coins from previously open channels back to the user's + // wallet. + Signer lnwallet.Signer + + // DB provides access to the user's channels, allowing the breach + // arbiter to determine the current state of a user's channels, and how + // it should respond to channel closure. + DB *channeldb.DB + + // PublishTransaction facilitates the process of broadcasting a + // transaction to the network. + PublishTransaction func(*wire.MsgTx) error + + // Notifier provides a publish/subscribe interface for event driven + // notifications regarding the confirmation of txids. + Notifier chainntnfs.ChainNotifier + + // ChainIO is used by the breach arbiter to determine the current height + // of the blockchain, which is required to subscribe for spend + // notifications from Notifier. + ChainIO lnwallet.BlockChainIO + + // Estimator is used by the breach arbiter to determine an appropriate + // fee level when generating, signing, and broadcasting sweep + // transactions. + Estimator lnwallet.FeeEstimator + + // CloseLink allows the breach arbiter to shutdown any channel links for + // which it detects a breach, ensuring now further activity will + // continue across the link. The method accepts link's channel point and a + // close type to be included in the channel close summary. + CloseLink func(*wire.OutPoint, htlcswitch.ChannelCloseType) + + // Store is a persistent resource that maintains information regarding + // breached channels. This is used in conjunction with DB to recover + // from crashes, restarts, or other failures. + Store RetributionStore + + // GenSweepScript generates the receiving scripts for swept outputs. + GenSweepScript func() ([]byte, error) +} + // breachArbiter is a special subsystem which is responsible for watching and // acting on the detection of any attempted uncooperative channel breaches by // channel counterparties. This file essentially acts as deterrence code for @@ -39,15 +85,7 @@ var retributionBucket = []byte("retribution") // counterparties. // TODO(roasbeef): closures in config for subsystem pointers to decouple? type breachArbiter struct { - wallet *lnwallet.LightningWallet - signer lnwallet.Signer - db *channeldb.DB - notifier chainntnfs.ChainNotifier - chainIO lnwallet.BlockChainIO - estimator lnwallet.FeeEstimator - htlcSwitch *htlcswitch.Switch - - retributionStore RetributionStore + cfg *BreachConfig // breachObservers is a map which tracks all the active breach // observers we're currently managing. The key of the map is the @@ -82,20 +120,9 @@ type breachArbiter struct { // newBreachArbiter creates a new instance of a breachArbiter initialized with // its dependent objects. -func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, - notifier chainntnfs.ChainNotifier, h *htlcswitch.Switch, - chain lnwallet.BlockChainIO, fe lnwallet.FeeEstimator) *breachArbiter { - +func newBreachArbiter(cfg *BreachConfig) *breachArbiter { return &breachArbiter{ - wallet: wallet, - signer: wallet.Cfg.Signer, - db: db, - notifier: notifier, - chainIO: chain, - htlcSwitch: h, - estimator: fe, - - retributionStore: newRetributionStore(db), + cfg: cfg, breachObservers: make(map[wire.OutPoint]chan struct{}), breachedContracts: make(chan *retributionInfo), @@ -121,7 +148,7 @@ func (b *breachArbiter) Start() error { // breach is reflected in channeldb. breachRetInfos := make(map[wire.OutPoint]retributionInfo) closeSummaries := make(map[wire.OutPoint]channeldb.ChannelCloseSummary) - err := b.retributionStore.ForAll(func(ret *retributionInfo) error { + err := b.cfg.Store.ForAll(func(ret *retributionInfo) error { // Extract emitted retribution information. breachRetInfos[ret.chanPoint] = *ret @@ -148,7 +175,7 @@ func (b *breachArbiter) Start() error { // We need to query that database state for all currently active // channels, each of these channels will need a goroutine assigned to // it to watch for channel breaches. - activeChannels, err := b.db.FetchAllChannels() + activeChannels, err := b.cfg.DB.FetchAllChannels() if err != nil && err != channeldb.ErrNoActiveChannels { brarLog.Errorf("unable to fetch active channels: %v", err) return err @@ -185,8 +212,9 @@ func (b *breachArbiter) Start() error { channelsToWatch := make([]*lnwallet.LightningChannel, 0, nActive) for _, chanState := range activeChannels { // Initialize active channel from persisted channel state. - channel, err := lnwallet.NewLightningChannel(nil, b.notifier, - b.estimator, chanState) + channel, err := lnwallet.NewLightningChannel( + nil, b.cfg.Notifier, b.cfg.Estimator, chanState, + ) if err != nil { brarLog.Errorf("unable to load channel from "+ "disk: %v", err) @@ -205,10 +233,8 @@ func (b *breachArbiter) Start() error { // notify the HTLC switch that this link should be // closed, and that all activity on the link should // cease. - b.htlcSwitch.CloseLink( - &chanState.FundingOutpoint, - htlcswitch.CloseBreach, - ) + b.cfg.CloseLink(&chanState.FundingOutpoint, + htlcswitch.CloseBreach) // Ensure channeldb is consistent with the persisted // breach. @@ -231,7 +257,7 @@ func (b *breachArbiter) Start() error { } // TODO(roasbeef): instead use closure height of channel - _, currentHeight, err := b.chainIO.GetBestBlock() + _, currentHeight, err := b.cfg.ChainIO.GetBestBlock() if err != nil { return err } @@ -249,7 +275,7 @@ func (b *breachArbiter) Start() error { // Register for a notification when the breach transaction is // confirmed on chain. breachTXID := closeSummary.ClosingTXID - confChan, err := b.notifier.RegisterConfirmationsNtfn( + confChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn( &breachTXID, 1, uint32(currentHeight)) if err != nil { brarLog.Errorf("unable to register for conf updates "+ @@ -293,7 +319,7 @@ func (b *breachArbiter) watchForPendingCloseConfs(currentHeight int32) error { pendingClose.ChanPoint) closeTXID := pendingClose.ClosingTXID - confNtfn, err := b.notifier.RegisterConfirmationsNtfn( + confNtfn, err := b.cfg.Notifier.RegisterConfirmationsNtfn( &closeTXID, 1, uint32(currentHeight), ) if err != nil { @@ -321,10 +347,10 @@ func (b *breachArbiter) watchForPendingCloseConfs(currentHeight int32) error { // UnilateralCloseSummary on disk so can // possibly sweep output here - err := b.db.MarkChanFullyClosed(&chanPoint) + err := b.cfg.DB.MarkChanFullyClosed(&chanPoint) if err != nil { - brarLog.Errorf("unable to mark channel "+ - "as closed: %v", err) + brarLog.Errorf("unable to mark channel"+ + " as closed: %v", err) } case <-b.quit: @@ -385,7 +411,7 @@ out: for { select { case breachInfo := <-b.breachedContracts: - _, currentHeight, err := b.chainIO.GetBestBlock() + _, currentHeight, err := b.cfg.ChainIO.GetBestBlock() if err != nil { brarLog.Errorf("unable to get best height: %v", err) @@ -397,7 +423,7 @@ out: // transaction) has been confirmed in the chain to // ensure we're not dealing with a moving target. breachTXID := &breachInfo.commitHash - confChan, err := b.notifier.RegisterConfirmationsNtfn( + cfChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn( breachTXID, 1, uint32(currentHeight), ) if err != nil { @@ -417,7 +443,7 @@ out: // retribution after the breach transaction has been // confirmed. b.wg.Add(1) - go b.exactRetribution(confChan, breachInfo) + go b.exactRetribution(cfChan, breachInfo) delete(b.breachObservers, breachInfo.chanPoint) @@ -523,7 +549,7 @@ func (b *breachArbiter) exactRetribution( return spew.Sdump(justiceTx) })) - _, currentHeight, err := b.chainIO.GetBestBlock() + _, currentHeight, err := b.cfg.ChainIO.GetBestBlock() if err != nil { brarLog.Errorf("unable to get current height: %v", err) return @@ -531,7 +557,7 @@ func (b *breachArbiter) exactRetribution( // Finally, broadcast the transaction, finalizing the channels' // retribution against the cheating counterparty. - if err := b.wallet.PublishTransaction(justiceTx); err != nil { + if err := b.cfg.PublishTransaction(justiceTx); err != nil { brarLog.Errorf("unable to broadcast "+ "justice tx: %v", err) return @@ -542,8 +568,8 @@ func (b *breachArbiter) exactRetribution( // notify the caller that initiated the retribution workflow that the // deed has been done. justiceTXID := justiceTx.TxHash() - confChan, err = b.notifier.RegisterConfirmationsNtfn(&justiceTXID, 1, - uint32(currentHeight)) + confChan, err = b.cfg.Notifier.RegisterConfirmationsNtfn( + &justiceTXID, 1, uint32(currentHeight)) if err != nil { brarLog.Errorf("unable to register for conf for txid: %v", justiceTXID) @@ -566,14 +592,14 @@ func (b *breachArbiter) exactRetribution( revokedFunds, totalFunds) // With the channel closed, mark it in the database as such. - err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint) + err := b.cfg.DB.MarkChanFullyClosed(&breachInfo.chanPoint) if err != nil { brarLog.Errorf("unable to mark chan as closed: %v", err) } // Justice has been carried out; we can safely delete the // retribution info from the database. - err = b.retributionStore.Remove(&breachInfo.chanPoint) + err = b.cfg.Store.Remove(&breachInfo.chanPoint) if err != nil { brarLog.Errorf("unable to remove retribution "+ "from the db: %v", err) @@ -640,7 +666,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, // TODO(roasbeef): also notify utxoNursery, might've had // outbound HTLC's in flight go waitForChanToClose(uint32(closeInfo.SpendingHeight), - b.notifier, nil, chanPoint, closeInfo.SpenderTxHash, + b.cfg.Notifier, nil, chanPoint, closeInfo.SpenderTxHash, func() { // As we just detected a channel was closed via // a unilateral commitment broadcast by the @@ -661,13 +687,11 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, goto close } - brarLog.Infof("Sweeping %v breached "+ + brarLog.Infof("Sweeping breached "+ "outputs with: %v", spew.Sdump(sweepTx)) - err = b.wallet.PublishTransaction( - sweepTx, - ) + err = b.cfg.PublishTransaction(sweepTx) if err != nil { brarLog.Errorf("unable to "+ "broadcast tx: %v", err) @@ -679,7 +703,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, "is fully closed, updating DB", chanPoint) - err := b.db.MarkChanFullyClosed(chanPoint) + err := b.cfg.DB.MarkChanFullyClosed(chanPoint) if err != nil { brarLog.Errorf("unable to mark chan "+ "as closed: %v", err) @@ -699,7 +723,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, // breached in order to ensure any incoming or outgoing // multi-hop HTLCs aren't sent over this link, nor any other // links associated with this peer. - b.htlcSwitch.CloseLink(chanPoint, htlcswitch.CloseBreach) + b.cfg.CloseLink(chanPoint, htlcswitch.CloseBreach) // TODO(roasbeef): need to handle case of remote broadcast // mid-local initiated state-transition, possible @@ -715,7 +739,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, retInfo := newRetributionInfo(chanPoint, breachInfo, chanInfo) // Persist the pending retribution state to disk. - if err := b.retributionStore.Add(retInfo); err != nil { + if err := b.cfg.Store.Add(retInfo); err != nil { brarLog.Errorf("unable to persist retribution info "+ "to db: %v", err) } @@ -795,7 +819,7 @@ type breachedOutput struct { amt btcutil.Amount outpoint wire.OutPoint witnessType lnwallet.WitnessType - signDesc *lnwallet.SignDescriptor + signDesc lnwallet.SignDescriptor witnessFunc lnwallet.WitnessGenerator } @@ -812,7 +836,7 @@ func newBreachedOutput(outpoint *wire.OutPoint, amt: btcutil.Amount(amount), outpoint: *outpoint, witnessType: witnessType, - signDesc: signDescriptor, + signDesc: *signDescriptor, } } @@ -841,8 +865,7 @@ func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer, // been initialized for this breached output. if bo.witnessFunc == nil { bo.witnessFunc = bo.witnessType.GenWitnessFunc( - signer, - bo.signDesc, + signer, &bo.signDesc, ) } @@ -923,13 +946,21 @@ func newRetributionInfo(chanPoint *wire.OutPoint, // deterministically generate a valid witness for each output. This will // allow the breach arbiter to recover from failures, in the event that // it must sign and broadcast the justice transaction. - var htlcOutputs = make([]*breachedOutput, nHtlcs) + htlcOutputs := make([]*breachedOutput, nHtlcs) for i, breachedHtlc := range breachInfo.HtlcRetributions { + // Using the breachedHtlc's incoming flag, determine the + // appropriate witness type that needs to be generated in order + // to sweep the HTLC output. + var htlcWitnessType lnwallet.WitnessType + if breachedHtlc.IsIncoming { + htlcWitnessType = lnwallet.HtlcAcceptedRevoke + } else { + htlcWitnessType = lnwallet.HtlcOfferedRevoke + } + htlcOutputs[i] = newBreachedOutput( - &breachedHtlc.OutPoint, - lnwallet.CommitmentRevoke, - &breachedHtlc.SignDesc, - ) + &breachInfo.HtlcRetributions[i].OutPoint, htlcWitnessType, + &breachInfo.HtlcRetributions[i].SignDesc) } // TODO(conner): remove dependency on channel snapshot after decoupling @@ -960,24 +991,35 @@ func (b *breachArbiter) createJusticeTx( // Assemble the breached outputs into a slice of spendable outputs, // starting with the self and revoked outputs, then adding any htlc // outputs. - var breachedOutputs = make([]SpendableOutput, 2+nHtlcs) + breachedOutputs := make([]SpendableOutput, 2+nHtlcs) breachedOutputs[0] = r.selfOutput breachedOutputs[1] = r.revokedOutput for i, htlcOutput := range r.htlcOutputs { breachedOutputs[2+i] = htlcOutput } + // Compute the transaction weight of the justice transaction, which + // includes 2 + nHtlcs inputs and one output. var txWeight uint64 // Begin with a base txn weight, e.g. version, nLockTime, etc. txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize - // Add to_local revoke script and tx input. txWeight += 4*lnwallet.InputSize + lnwallet.ToLocalPenaltyWitnessSize // Add to_remote p2wpkh witness and tx input. txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize - // Add revoked offered-htlc witnesses and tx inputs. - txWeight += uint64(len(r.htlcOutputs)) * - (4*lnwallet.InputSize + lnwallet.OfferedHtlcWitnessSize) + + // Compute the appropriate weight contributed by each revoked accepted + // or offered HTLC witnesses and tx inputs. + for _, htlcOutput := range r.htlcOutputs { + switch htlcOutput.witnessType { + case lnwallet.HtlcOfferedRevoke: + txWeight += 4*lnwallet.InputSize + + lnwallet.OfferedHtlcPenaltyWitnessSize + case lnwallet.HtlcAcceptedRevoke: + txWeight += 4*lnwallet.InputSize + + lnwallet.AcceptedHtlcPenaltyWitnessSize + } + } return b.sweepSpendableOutputsTxn(txWeight, breachedOutputs...) } @@ -999,6 +1041,8 @@ func (b *breachArbiter) craftCommitSweepTx( closeInfo.SelfOutputSignDesc, ) + // Compute the transaction weight of the commit sweep transaction, which + // includes a single input and output. var txWeight uint64 // Begin with a base txn weight, e.g. version, nLockTime, etc. txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize @@ -1017,7 +1061,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64, // sweep the funds to. // TODO(roasbeef): possibly create many outputs to minimize change in // the future? - pkScript, err := newSweepPkScript(b.wallet) + pkScript, err := b.cfg.GenSweepScript() if err != nil { return nil, err } @@ -1028,14 +1072,14 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64, totalAmt += input.Amount() } - feePerWeight := b.estimator.EstimateFeePerWeight(1) + feePerWeight := b.cfg.Estimator.EstimateFeePerWeight(1) txFee := btcutil.Amount(txWeight * feePerWeight) sweepAmt := int64(totalAmt - txFee) // With the fee calculated, we can now create the transaction using the // information gathered above and the provided retribution information. - var txn = wire.NewMsgTx(2) + txn := wire.NewMsgTx(2) // We begin by adding the output to which our funds will be deposited. txn.AddTxOut(&wire.TxOut{ @@ -1072,7 +1116,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64, // transaction using the SpendableOutput's witness generation // function. witness, err := so.BuildWitness( - b.wallet.Cfg.Signer, txn, hashCache, idx, + b.cfg.Signer, txn, hashCache, idx, ) if err != nil { return err @@ -1339,7 +1383,7 @@ func (bo *breachedOutput) Encode(w io.Writer) error { return err } - if err := lnwallet.WriteSignDescriptor(w, bo.signDesc); err != nil { + if err := lnwallet.WriteSignDescriptor(w, &bo.signDesc); err != nil { return err } @@ -1364,8 +1408,7 @@ func (bo *breachedOutput) Decode(r io.Reader) error { return err } - bo.signDesc = &lnwallet.SignDescriptor{} - if err := lnwallet.ReadSignDescriptor(r, bo.signDesc); err != nil { + if err := lnwallet.ReadSignDescriptor(r, &bo.signDesc); err != nil { return err } From e32d3024aeebb9286a926964c96068ea2427da42 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 1 Sep 2017 03:12:02 -0700 Subject: [PATCH 09/15] server: instantiate breacharbiter using BreachConfig --- server.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index be542ac93..e04afd367 100644 --- a/server.go +++ b/server.go @@ -23,6 +23,7 @@ import ( "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/connmgr" + "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" "github.com/go-errors/errors" @@ -288,8 +289,27 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl, return nil, err } - s.breachArbiter = newBreachArbiter(cc.wallet, chanDB, cc.chainNotifier, - s.htlcSwitch, s.cc.chainIO, s.cc.feeEstimator) + // Construct a closure that wraps the htlcswitch's CloseLink method. + closeLink := func(chanPoint *wire.OutPoint, + closureType htlcswitch.ChannelCloseType) { + // TODO(conner): Properly respect the update and error channels + // returned by CloseLink. + s.htlcSwitch.CloseLink(chanPoint, closureType) + } + + s.breachArbiter = newBreachArbiter(&BreachConfig{ + Signer: cc.wallet.Cfg.Signer, + DB: chanDB, + PublishTransaction: cc.wallet.PublishTransaction, + Notifier: cc.chainNotifier, + ChainIO: s.cc.chainIO, + Estimator: s.cc.feeEstimator, + CloseLink: closeLink, + Store: newRetributionStore(chanDB), + GenSweepScript: func() ([]byte, error) { + return newSweepPkScript(cc.wallet) + }, + }) // Create the connection manager which will be responsible for // maintaining persistent outbound connections and also accepting new From 7ee7bf840a7f0c4bbe2b33543883aa890a9f0eea Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 6 Sep 2017 16:41:38 -0700 Subject: [PATCH 10/15] breacharbiter_test: properly assigns SignDescriptor w/o ptr --- breacharbiter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index c82ac2d17..fce8999f6 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -317,7 +317,7 @@ func initBreachedOutputs() error { breachKeys[i]) } sd.PubKey = pubkey - bo.signDesc = sd + bo.signDesc = *sd } return nil From faf1d40bd00380dc8f8ea6c3a88f2716cd523a67 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 6 Sep 2017 13:38:01 -0700 Subject: [PATCH 11/15] lnwallet/channel: adds directionality htlc retributions This commit also adds an incoming flag to HtlcRetribution struct to allow the breach arbiter to generate the appropriate witness based on the htlc's directionality. It also ensures that the size of the htlc retribution slice is now determined by the size of the number of htlcs present in the revoked snapshot, which fixes a minor bug that could lead to nil pointer deferences. --- lnwallet/channel.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index d5f0ed1b5..0bf4dc536 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1032,6 +1032,12 @@ type HtlcRetribution struct { // OutPoint is the target outpoint of this HTLC pointing to the // breached commitment transaction. OutPoint wire.OutPoint + + // IsIncoming is a boolean flag that indicates whether or not this + // HTLC was accepted from the counterparty. A false value indicates that + // this HTLC was offered by us. This flag is used determine the exact + // witness type should be used to sweep the output. + IsIncoming bool } // BreachRetribution contains all the data necessary to bring a channel @@ -1162,7 +1168,7 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // With the commitment outputs located, we'll now generate all the // retribution structs for each of the HTLC transactions active on the // remote commitment transaction. - htlcRetributions := make([]HtlcRetribution, len(chanState.Htlcs)) + htlcRetributions := make([]HtlcRetribution, len(revokedSnapshot.Htlcs)) for i, htlc := range revokedSnapshot.Htlcs { var ( htlcScript []byte @@ -1206,6 +1212,7 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, Hash: commitHash, Index: uint32(htlc.OutputIndex), }, + IsIncoming: htlc.Incoming, } } From 84abc962c4dbd99e24ff7ba71f401784675e50bb Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 6 Sep 2017 13:39:45 -0700 Subject: [PATCH 12/15] lnwallet/witnessgen: adds htlc offered+accepted revoke witness type --- lnwallet/witnessgen.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lnwallet/witnessgen.go b/lnwallet/witnessgen.go index ad5e139ce..434bff4e7 100644 --- a/lnwallet/witnessgen.go +++ b/lnwallet/witnessgen.go @@ -25,6 +25,14 @@ const ( // of a malicious counterparty's who broadcasts a revoked commitment // transaction. CommitmentRevoke WitnessType = 2 + + // HtlcOfferedRevoke is a witness that allows us to sweep an HTLC + // output that we offered to the counterparty. + HtlcOfferedRevoke WitnessType = 3 + + // HtlcAcceptedRevoke is a witness that allows us to sweep an HTLC + // output that we accepted from the counterparty. + HtlcAcceptedRevoke WitnessType = 4 ) // WitnessGenerator represents a function which is able to generate the final @@ -52,6 +60,10 @@ func (wt WitnessType) GenWitnessFunc(signer Signer, return CommitSpendNoDelay(signer, desc, tx) case CommitmentRevoke: return CommitSpendRevoke(signer, desc, tx) + case HtlcOfferedRevoke: + return ReceiverHtlcSpendRevoke(signer, desc, tx) + case HtlcAcceptedRevoke: + return SenderHtlcSpendRevoke(signer, desc, tx) default: return nil, fmt.Errorf("unknown witness type: %v", wt) } From de1063200ac890693c79e4426309af3cd2d3311a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 6 Sep 2017 13:50:06 -0700 Subject: [PATCH 13/15] lnwallet/script_utils: adds public receiver+sender htlc revoke w/key derivation --- lnwallet/script_utils.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lnwallet/script_utils.go b/lnwallet/script_utils.go index 873961a42..509a69d1b 100644 --- a/lnwallet/script_utils.go +++ b/lnwallet/script_utils.go @@ -297,6 +297,24 @@ func senderHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, return witnessStack, nil } +// SenderHtlcSpendRevoke constructs a valid witness allowing the receiver of an +// HTLC to claim the output with knowledge of the revocation private key in the +// scenario that the sender of the HTLC broadcasts a previously revoked +// commitment transaction. This method first derives the appropriate revocation +// key, and requires that the provided SignDescriptor has a local revocation +// basepoint and commitment secret in the PubKey and DoubleTweak fields, +// respectively. +func SenderHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx) (wire.TxWitness, error) { + + // Derive the revocation key using the local revocation base point and + // commitment point. + revokeKey := DeriveRevocationPubkey(signDesc.PubKey, + signDesc.DoubleTweak.PubKey()) + + return senderHtlcSpendRevoke(signer, signDesc, revokeKey, sweepTx) +} + // senderHtlcSpendRedeem constructs a valid witness allowing the receiver of an // HTLC to redeem the pending output in the scenario that the sender broadcasts // their version of the commitment transaction. A valid spend requires @@ -528,6 +546,24 @@ func receiverHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, return witnessStack, nil } +// ReceiverHtlcSpendRevoke constructs a valid witness allowing the sender of an +// HTLC within a previously revoked commitment transaction to re-claim the +// pending funds in the case that the receiver broadcasts this revoked +// commitment transaction. This method first derives the appropriate revocation +// key, and requires that the provided SignDescriptor has a local revocation +// basepoint and commitment secret in the PubKey and DoubleTweak fields, +// respectively. +func ReceiverHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx) (wire.TxWitness, error) { + + // Derive the revocation key using the local revocation base point and + // commitment point. + revokeKey := DeriveRevocationPubkey(signDesc.PubKey, + signDesc.DoubleTweak.PubKey()) + + return receiverHtlcSpendRevoke(signer, signDesc, revokeKey, sweepTx) +} + // receiverHtlcSpendTimeout constructs a valid witness allowing the sender of // an HTLC to recover the pending funds after an absolute timeout in the // scenario that the receiver of the HTLC broadcasts their version of the From 47356493f352839fa63d4b27976de077abe5c6ac Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 6 Sep 2017 13:51:27 -0700 Subject: [PATCH 14/15] lnwallet/signdesc: increases max witness parsing length --- lnwallet/signdescriptor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnwallet/signdescriptor.go b/lnwallet/signdescriptor.go index 56eca39dd..cba39ec22 100644 --- a/lnwallet/signdescriptor.go +++ b/lnwallet/signdescriptor.go @@ -166,7 +166,7 @@ func ReadSignDescriptor(r io.Reader, sd *SignDescriptor) error { return ErrTweakOverdose } - witnessScript, err := wire.ReadVarBytes(r, 0, 100, "witnessScript") + witnessScript, err := wire.ReadVarBytes(r, 0, 500, "witnessScript") if err != nil { return err } From ec288ddf5bc2b729a3433859286f10cce8624099 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 19 Sep 2017 17:30:36 -0700 Subject: [PATCH 15/15] ba+utxno: final pass for formatting --- breacharbiter.go | 88 +++++++++++++++++++++--------------------------- utxonursery.go | 3 +- 2 files changed, 39 insertions(+), 52 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index acbbded86..172a053ef 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -33,47 +33,47 @@ var retributionBucket = []byte("retribution") // BreachConfig bundles the required subsystems used by the breach arbiter. An // instance of BreachConfig is passed to newBreachArbiter during instantiation. type BreachConfig struct { - // Signer is used by the breach arbiter to generate sweep transactions, - // which move coins from previously open channels back to the user's - // wallet. - Signer lnwallet.Signer - - // DB provides access to the user's channels, allowing the breach - // arbiter to determine the current state of a user's channels, and how - // it should respond to channel closure. - DB *channeldb.DB - - // PublishTransaction facilitates the process of broadcasting a - // transaction to the network. - PublishTransaction func(*wire.MsgTx) error - - // Notifier provides a publish/subscribe interface for event driven - // notifications regarding the confirmation of txids. - Notifier chainntnfs.ChainNotifier - // ChainIO is used by the breach arbiter to determine the current height // of the blockchain, which is required to subscribe for spend // notifications from Notifier. ChainIO lnwallet.BlockChainIO - // Estimator is used by the breach arbiter to determine an appropriate - // fee level when generating, signing, and broadcasting sweep - // transactions. - Estimator lnwallet.FeeEstimator - // CloseLink allows the breach arbiter to shutdown any channel links for // which it detects a breach, ensuring now further activity will // continue across the link. The method accepts link's channel point and a // close type to be included in the channel close summary. CloseLink func(*wire.OutPoint, htlcswitch.ChannelCloseType) + // DB provides access to the user's channels, allowing the breach + // arbiter to determine the current state of a user's channels, and how + // it should respond to channel closure. + DB *channeldb.DB + + // Estimator is used by the breach arbiter to determine an appropriate + // fee level when generating, signing, and broadcasting sweep + // transactions. + Estimator lnwallet.FeeEstimator + + // GenSweepScript generates the receiving scripts for swept outputs. + GenSweepScript func() ([]byte, error) + + // Notifier provides a publish/subscribe interface for event driven + // notifications regarding the confirmation of txids. + Notifier chainntnfs.ChainNotifier + + // PublishTransaction facilitates the process of broadcasting a + // transaction to the network. + PublishTransaction func(*wire.MsgTx) error + + // Signer is used by the breach arbiter to generate sweep transactions, + // which move coins from previously open channels back to the user's + // wallet. + Signer lnwallet.Signer + // Store is a persistent resource that maintains information regarding // breached channels. This is used in conjunction with DB to recover // from crashes, restarts, or other failures. Store RetributionStore - - // GenSweepScript generates the receiving scripts for swept outputs. - GenSweepScript func() ([]byte, error) } // breachArbiter is a special subsystem which is responsible for watching and @@ -212,9 +212,8 @@ func (b *breachArbiter) Start() error { channelsToWatch := make([]*lnwallet.LightningChannel, 0, nActive) for _, chanState := range activeChannels { // Initialize active channel from persisted channel state. - channel, err := lnwallet.NewLightningChannel( - nil, b.cfg.Notifier, b.cfg.Estimator, chanState, - ) + channel, err := lnwallet.NewLightningChannel(nil, + b.cfg.Notifier, b.cfg.Estimator, chanState) if err != nil { brarLog.Errorf("unable to load channel from "+ "disk: %v", err) @@ -320,8 +319,7 @@ func (b *breachArbiter) watchForPendingCloseConfs(currentHeight int32) error { closeTXID := pendingClose.ClosingTXID confNtfn, err := b.cfg.Notifier.RegisterConfirmationsNtfn( - &closeTXID, 1, uint32(currentHeight), - ) + &closeTXID, 1, uint32(currentHeight)) if err != nil { return err } @@ -424,8 +422,7 @@ out: // ensure we're not dealing with a moving target. breachTXID := &breachInfo.commitHash cfChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn( - breachTXID, 1, uint32(currentHeight), - ) + breachTXID, 1, uint32(currentHeight)) if err != nil { brarLog.Errorf("unable to register for conf "+ "updates for txid: %v, err: %v", @@ -806,8 +803,7 @@ type SpendableOutput interface { // BuildWitness returns a valid witness allowing this output to be // spent, the witness should be attached to the transaction at the // location determined by the given `txinIdx`. - BuildWitness(signer lnwallet.Signer, - txn *wire.MsgTx, + BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) } @@ -865,8 +861,7 @@ func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer, // been initialized for this breached output. if bo.witnessFunc == nil { bo.witnessFunc = bo.witnessType.GenWitnessFunc( - signer, &bo.signDesc, - ) + signer, &bo.signDesc) } // Now that we have ensured that the witness generation function has @@ -921,22 +916,16 @@ func newRetributionInfo(chanPoint *wire.OutPoint, // witness in the event of failures, as it will be persisted in the // retribution store. Here we use CommitmentNoDelay since this output // belongs to us and has no time-based constraints on spending. - selfOutput := newBreachedOutput( - &breachInfo.LocalOutpoint, - lnwallet.CommitmentNoDelay, - &breachInfo.LocalOutputSignDesc, - ) + selfOutput := newBreachedOutput(&breachInfo.LocalOutpoint, + lnwallet.CommitmentNoDelay, &breachInfo.LocalOutputSignDesc) // Second, record the same information and witness type regarding the // remote outpoint, which belongs to the party who tried to steal our // money! Here we set witnessType of the breachedOutput to // CommitmentRevoke, since we will be using a revoke key, withdrawing // the funds from the commitment transaction immediately. - revokedOutput := newBreachedOutput( - &breachInfo.RemoteOutpoint, - lnwallet.CommitmentRevoke, - &breachInfo.RemoteOutputSignDesc, - ) + revokedOutput := newBreachedOutput(&breachInfo.RemoteOutpoint, + lnwallet.CommitmentRevoke, &breachInfo.RemoteOutputSignDesc) // Determine the number of second layer HTLCs we will attempt to sweep. nHtlcs := len(breachInfo.HtlcRetributions) @@ -1115,9 +1104,8 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64, // First, we construct a valid witness for this outpoint and // transaction using the SpendableOutput's witness generation // function. - witness, err := so.BuildWitness( - b.cfg.Signer, txn, hashCache, idx, - ) + witness, err := so.BuildWitness(b.cfg.Signer, txn, hashCache, + idx) if err != nil { return err } diff --git a/utxonursery.go b/utxonursery.go index f022a0896..12a7999ed 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -786,8 +786,7 @@ func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet, // output or not. for _, kgtnOutput := range kgtnOutputs { kgtnOutput.witnessFunc = kgtnOutput.witnessType.GenWitnessFunc( - wallet.Cfg.Signer, kgtnOutput.signDescriptor, - ) + wallet.Cfg.Signer, kgtnOutput.signDescriptor) } utxnLog.Infof("New block: height=%v, sweeping %v mature outputs",