diff --git a/breacharbiter.go b/breacharbiter.go index 70eedea2..2dda9605 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -410,20 +410,56 @@ out: return } +// convertToSecondLevelRevoke takes a breached output, and a transaction that +// spends it to the second level, and mutates the breach output into one that +// is able to properly sweep that second level output. We'll use this function +// when we go to sweep a breached commitment transaction, but the cheating +// party has already attempted to take it to the second level +func convertToSecondLevelRevoke(bo *breachedOutput, breachInfo *retributionInfo, + spendDetails *chainntnfs.SpendDetail) { + + // In this case, we'll modify the witness type of this output to + // actually prepare for a second level revoke. + bo.witnessType = lnwallet.HtlcSecondLevelRevoke + + // We'll also redirect the outpoint to this second level output, so the + // spending transaction updates it inputs accordingly. + spendingTx := spendDetails.SpendingTx + oldOp := bo.outpoint + bo.outpoint = wire.OutPoint{ + Hash: spendingTx.TxHash(), + Index: 0, + } + + // Next, we need to update the amount so we can do fee estimation + // properly, and also so we can generate a valid signature as we need + // to know the new input value (the second level transactions shaves + // off some funds to fees). + newAmt := spendingTx.TxOut[0].Value + bo.amt = btcutil.Amount(newAmt) + bo.signDesc.Output.Value = newAmt + + // Finally, we'll need to adjust the witness program in the + // SignDescriptor. + bo.signDesc.WitnessScript = bo.secondLevelWitnessScript + + brarLog.Warnf("HTLC(%v) for ChannelPoint(%v) has been spent to the "+ + "second-level, adjusting -> %v", oldOp, breachInfo.chanPoint, + bo.outpoint) +} + // exactRetribution is a goroutine which is executed once a contract breach has // been detected by a breachObserver. This function is responsible for // punishing a counterparty for violating the channel contract by sweeping ALL // the lingering funds within the channel into the daemon's wallet. // // NOTE: This MUST be run as a goroutine. -func (b *breachArbiter) exactRetribution( - confChan *chainntnfs.ConfirmationEvent, +func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, breachInfo *retributionInfo) { defer b.wg.Done() // TODO(roasbeef): state needs to be checkpointed here - var breachConfHeight uint32 select { case breachConf, ok := <-confChan.Confirmed: @@ -455,9 +491,60 @@ func (b *breachArbiter) exactRetribution( // construct a sweep transaction and write it to disk. This will allow // the breach arbiter to re-register for notifications for the justice // txid. +secondLevelCheck: if finalTx == nil { + // Before we create the justice tx, we need to check to see if + // any of the active HTLC's on the commitment transactions has + // been spent. In this case, we'll need to go to the second + // level to sweep them before the remote party can. + for i := 0; i < len(breachInfo.breachedOutputs); i++ { + breachedOutput := &breachInfo.breachedOutputs[i] + + // If this isn't an HTLC output, then we can skip it. + if breachedOutput.witnessType != lnwallet.HtlcAcceptedRevoke && + breachedOutput.witnessType != lnwallet.HtlcOfferedRevoke { + continue + } + + brarLog.Debugf("Checking for second-level attempt on "+ + "HTLC(%v) for ChannelPoint(%v)", + breachedOutput.outpoint, breachInfo.chanPoint) + + // Now that we have an HTLC output, we'll quickly check + // to see if it has been spent or not. + spendNtfn, err := b.cfg.Notifier.RegisterSpendNtfn( + &breachedOutput.outpoint, breachInfo.breachHeight, + ) + if err != nil { + brarLog.Errorf("unable to check for spentness "+ + "of out_point=%v: %v", + breachedOutput.outpoint, err) + continue + } + + select { + // The output has been taken to the second level! + case spendDetails, ok := <-spendNtfn.Spend: + if !ok { + return + } + + // In this case we'll morph our initial revoke + // spend to instead point to the second level + // output, and update the sign descriptor in + // the process. + convertToSecondLevelRevoke( + breachedOutput, breachInfo, spendDetails, + ) + + // It hasn't been spent so we'll continue. + default: + } + } + // With the breach transaction confirmed, we now create the - // justice tx which will claim ALL the funds within the channel. + // justice tx which will claim ALL the funds within the + // channel. finalTx, err = b.createJusticeTx(breachInfo) if err != nil { brarLog.Errorf("unable to create justice tx: %v", err) @@ -474,16 +561,22 @@ func (b *breachArbiter) exactRetribution( } } - brarLog.Debugf("Broadcasting justice tx: %v", - newLogClosure(func() string { - return spew.Sdump(finalTx) - })) + brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string { + return spew.Sdump(finalTx) + })) - // Finally, broadcast the transaction, finalizing the channels' - // retribution against the cheating counterparty. - if err := b.cfg.PublishTransaction(finalTx); err != nil { + // We'll now attempt to broadcast the transaction which finalized the + // channel's retribution against the cheating counter party. + err = b.cfg.PublishTransaction(finalTx) + if err != nil { brarLog.Errorf("unable to broadcast "+ "justice tx: %v", err) + if strings.Contains(err.Error(), "already been spent") { + brarLog.Infof("Attempting to transfer HTLC revocations " + + "to the second level") + finalTx = nil + goto secondLevelCheck + } } // As a conclusionary step, we register for a notification to be @@ -709,6 +802,8 @@ type breachedOutput struct { witnessType lnwallet.WitnessType signDesc lnwallet.SignDescriptor + secondLevelWitnessScript []byte + witnessFunc lnwallet.WitnessGenerator } @@ -716,15 +811,17 @@ type breachedOutput struct { // breach arbiter to construct a justice or sweep transaction. func makeBreachedOutput(outpoint *wire.OutPoint, witnessType lnwallet.WitnessType, + secondLevelScript []byte, signDescriptor *lnwallet.SignDescriptor) breachedOutput { amount := signDescriptor.Output.Value return breachedOutput{ - amt: btcutil.Amount(amount), - outpoint: *outpoint, - witnessType: witnessType, - signDesc: *signDescriptor, + amt: btcutil.Amount(amount), + outpoint: *outpoint, + secondLevelWitnessScript: secondLevelScript, + witnessType: witnessType, + signDesc: *signDescriptor, } } @@ -756,17 +853,14 @@ func (bo *breachedOutput) SignDesc() *lnwallet.SignDescriptor { // 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) { +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()) - } + // First, we ensure that the witness generation function has been + // initialized for this breached output. + 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 @@ -818,6 +912,9 @@ func newRetributionInfo(chanPoint *wire.OutPoint, localOutput := makeBreachedOutput( &breachInfo.LocalOutpoint, lnwallet.CommitmentNoDelay, + // No second level script as this is a commitment + // output. + nil, breachInfo.LocalOutputSignDesc) breachedOutputs = append(breachedOutputs, localOutput) @@ -832,6 +929,9 @@ func newRetributionInfo(chanPoint *wire.OutPoint, remoteOutput := makeBreachedOutput( &breachInfo.RemoteOutpoint, lnwallet.CommitmentRevoke, + // No second level script as this is a commitment + // output. + nil, breachInfo.RemoteOutputSignDesc) breachedOutputs = append(breachedOutputs, remoteOutput) @@ -855,6 +955,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, htlcOutput := makeBreachedOutput( &breachInfo.HtlcRetributions[i].OutPoint, htlcWitnessType, + breachInfo.HtlcRetributions[i].SecondLevelWitnessScript, &breachInfo.HtlcRetributions[i].SignDesc) breachedOutputs = append(breachedOutputs, htlcOutput) @@ -865,6 +966,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, chainHash: breachInfo.ChainHash, chanPoint: *chanPoint, breachedOutputs: breachedOutputs, + breachHeight: breachInfo.BreachHeight, } } @@ -917,6 +1019,9 @@ func (b *breachArbiter) createJusticeTx( case lnwallet.HtlcAcceptedRevoke: witnessWeight = lnwallet.AcceptedHtlcPenaltyWitnessSize + case lnwallet.HtlcSecondLevelRevoke: + witnessWeight = lnwallet.SecondLevelHtlcPenaltyWitnessSize + default: brarLog.Warnf("breached output in retribution info "+ "contains unexpected witness type: %v", @@ -961,6 +1066,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64, } txFee := btcutil.Amount(txWeight * uint64(feePerWeight)) + // TODO(roasbeef): already start to siphon their funds into fees sweepAmt := int64(totalAmt - txFee) // With the fee calculated, we can now create the transaction using the @@ -1353,7 +1459,13 @@ func (bo *breachedOutput) Encode(w io.Writer) error { return err } - if err := lnwallet.WriteSignDescriptor(w, &bo.signDesc); err != nil { + err := lnwallet.WriteSignDescriptor(w, &bo.signDesc) + if err != nil { + return err + } + + err = wire.WriteVarBytes(w, 0, bo.secondLevelWitnessScript) + if err != nil { return err } @@ -1382,11 +1494,18 @@ func (bo *breachedOutput) Decode(r io.Reader) error { return err } + wScript, err := wire.ReadVarBytes(r, 0, 1000, "witness script") + if err != nil { + return err + } + bo.secondLevelWitnessScript = wScript + if _, err := io.ReadFull(r, scratch[:2]); err != nil { return err } bo.witnessType = lnwallet.WitnessType( - binary.BigEndian.Uint16(scratch[:2])) + binary.BigEndian.Uint16(scratch[:2]), + ) return nil } diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 970144c3..29ce7eea 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -8,15 +8,17 @@ import ( "fmt" "io/ioutil" "math/rand" + "net" "os" "reflect" "sync" "testing" + "time" "github.com/btcsuite/btclog" "github.com/go-errors/errors" - "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -128,6 +130,7 @@ var ( }, HashType: txscript.SigHashAll, }, + secondLevelWitnessScript: breachKeys[0], }, { amt: btcutil.Amount(2e9), @@ -171,6 +174,7 @@ var ( }, HashType: txscript.SigHashAll, }, + secondLevelWitnessScript: breachKeys[0], }, { amt: btcutil.Amount(3e4), @@ -214,6 +218,7 @@ var ( }, HashType: txscript.SigHashAll, }, + secondLevelWitnessScript: breachKeys[0], }, } @@ -933,16 +938,26 @@ func TestBreachHandoffSuccess(t *testing.T) { // Create a pair of channels using a notifier that allows us to signal // a spend of the funding transaction. Alice's channel will be the on // observing a breach. - notifier := makeMockSpendNotifier() - alice, bob, cleanUpChans, err := createInitChannelsWithNotifier( - 1, notifier) + alice, bob, cleanUpChans, err := createInitChannels(1) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUpChans() // Instantiate a breach arbiter to handle the breach of alice's channel. - brar, cleanUpArb, err := createTestArbiter(t, notifier, alice.State().Db) + alicePoint := alice.ChannelPoint() + spendEvents := contractcourt.ChainEventSubscription{ + UnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), + CooperativeClosure: make(chan struct{}, 1), + ContractBreach: make(chan *lnwallet.BreachRetribution, 1), + ProcessACK: make(chan error, 1), + ChanPoint: *alicePoint, + Cancel: func() { + }, + } + brar, cleanUpArb, err := createTestArbiter( + t, &spendEvents, alice.State().Db, + ) if err != nil { t.Fatalf("unable to initialize test breach arbiter: %v", err) } @@ -963,7 +978,7 @@ func TestBreachHandoffSuccess(t *testing.T) { // Generate the force close summary at this point in time, this will // serve as the old state bob will broadcast. - forceCloseSummary, err := bob.ForceClose() + bobClose, err := bob.ForceClose() if err != nil { t.Fatalf("unable to force close bob's channel: %v", err) } @@ -982,18 +997,24 @@ func TestBreachHandoffSuccess(t *testing.T) { } chanPoint := alice.ChanPoint - breachTxn := forceCloseSummary.CloseTx // Signal a spend of the funding transaction and wait for the close // observer to exit. - notifier.Spend(chanPoint, 100, breachTxn) - alice.WaitForClose() + spendEvents.ContractBreach <- &lnwallet.BreachRetribution{ + BreachTransaction: bobClose.CloseTx, + } + + // We'll also wait to consume the ACK back from the breach arbiter. + select { + case <-spendEvents.ProcessACK: + case <-time.After(time.Second * 15): + t.Fatalf("breach arbiter didn't send ack back") + } // After exiting, the breach arbiter should have persisted the // retribution information and the channel should be shown as pending // force closed. assertArbiterBreach(t, brar, chanPoint) - assertPendingClosed(t, alice) } // TestBreachHandoffFail tests that a channel's close observer properly @@ -1005,16 +1026,26 @@ func TestBreachHandoffFail(t *testing.T) { // Create a pair of channels using a notifier that allows us to signal // a spend of the funding transaction. Alice's channel will be the on // observing a breach. - notifier := makeMockSpendNotifier() - alice, bob, cleanUpChans, err := createInitChannelsWithNotifier( - 1, notifier) + alice, bob, cleanUpChans, err := createInitChannels(1) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUpChans() // Instantiate a breach arbiter to handle the breach of alice's channel. - brar, cleanUpArb, err := createTestArbiter(t, notifier, alice.State().Db) + alicePoint := alice.ChannelPoint() + spendEvents := contractcourt.ChainEventSubscription{ + UnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), + CooperativeClosure: make(chan struct{}, 1), + ContractBreach: make(chan *lnwallet.BreachRetribution, 1), + ProcessACK: make(chan error, 1), + ChanPoint: *alicePoint, + Cancel: func() { + }, + } + brar, cleanUpArb, err := createTestArbiter( + t, &spendEvents, alice.State().Db, + ) if err != nil { t.Fatalf("unable to initialize test breach arbiter: %v", err) } @@ -1035,7 +1066,7 @@ func TestBreachHandoffFail(t *testing.T) { // Generate the force close summary at this point in time, this will // serve as the old state bob will broadcast. - forceCloseSummary, err := bob.ForceClose() + bobClose, err := bob.ForceClose() if err != nil { t.Fatalf("unable to force close bob's channel: %v", err) } @@ -1062,12 +1093,17 @@ func TestBreachHandoffFail(t *testing.T) { // Signal the notifier to dispatch spend notifications of the funding // transaction using the transaction from bob's closing summary. chanPoint := alice.ChanPoint - breachTxn := forceCloseSummary.CloseTx - notifier.Spend(chanPoint, 100, breachTxn) - - // Wait for the close observer to exit, all persistent effects should be - // observable after this point. - alice.WaitForClose() + spendEvents.ContractBreach <- &lnwallet.BreachRetribution{ + BreachTransaction: bobClose.CloseTx, + } + select { + case err := <-spendEvents.ProcessACK: + if err == nil { + t.Fatalf("breach write should have failed") + } + case <-time.After(time.Second * 15): + t.Fatalf("breach arbiter didn't send ack back") + } // Since the handoff failed, the breach arbiter should not show the // channel as breached, and the channel should also not have been marked @@ -1075,6 +1111,14 @@ func TestBreachHandoffFail(t *testing.T) { assertNoArbiterBreach(t, brar, chanPoint) assertNotPendingClosed(t, alice) + brar, cleanUpArb, err = createTestArbiter( + t, &spendEvents, alice.State().Db, + ) + if err != nil { + t.Fatalf("unable to initialize test breach arbiter: %v", err) + } + defer cleanUpArb() + // Instantiate a second lightning channel for alice, using the state of // her last channel. aliceKeyPriv, _ := btcec.PrivKeyFromBytes(btcec.S256(), @@ -1089,14 +1133,19 @@ func TestBreachHandoffFail(t *testing.T) { // Signal a spend of the funding transaction and wait for the close // observer to exit. This time we are allowing the handoff to succeed. - notifier.Spend(chanPoint, 100, breachTxn) - alice2.WaitForClose() + spendEvents.ContractBreach <- &lnwallet.BreachRetribution{ + BreachTransaction: bobClose.CloseTx, + } + select { + case <-spendEvents.ProcessACK: + case <-time.After(time.Second * 15): + t.Fatalf("breach arbiter didn't send ack back") + } // Check that the breach was properly recorded in the breach arbiter, // and that the close observer marked the channel as pending closed // before exiting. assertArbiterBreach(t, brar, chanPoint) - assertPendingClosed(t, alice) } // assertArbiterBreach checks that the breach arbiter has persisted the breach @@ -1134,24 +1183,6 @@ func assertNoArbiterBreach(t *testing.T, brar *breachArbiter, } } -// assertPendingClosed checks that the channel has been marked pending closed in -// the channel database. -func assertPendingClosed(t *testing.T, c *lnwallet.LightningChannel) { - closedChans, err := c.State().Db.FetchClosedChannels(true) - if err != nil { - t.Fatalf("unable to load pending closed channels: %v", err) - } - - for _, chanSummary := range closedChans { - if chanSummary.ChanPoint == *c.ChanPoint { - return - } - } - - t.Fatalf("channel %v was not marked pending closed", - c.ChanPoint) -} - // assertNotPendingClosed checks that the channel has not been marked pending // closed in the channel database. func assertNotPendingClosed(t *testing.T, c *lnwallet.LightningChannel) { @@ -1170,7 +1201,7 @@ func assertNotPendingClosed(t *testing.T, c *lnwallet.LightningChannel) { // createTestArbiter instantiates a breach arbiter with a failing retribution // store, so that controlled failures can be tested. -func createTestArbiter(t *testing.T, notifier chainntnfs.ChainNotifier, +func createTestArbiter(t *testing.T, chainEvents *contractcourt.ChainEventSubscription, db *channeldb.DB) (*breachArbiter, func(), error) { // Create a failing retribution store, that wraps a normal one. @@ -1183,13 +1214,17 @@ func createTestArbiter(t *testing.T, notifier chainntnfs.ChainNotifier, signer := &mockSigner{key: aliceKeyPriv} // Assemble our test arbiter. + notifier := makeMockSpendNotifier() ba := newBreachArbiter(&BreachConfig{ - CloseLink: func(_ *wire.OutPoint, _ htlcswitch.ChannelCloseType) {}, - DB: db, - Estimator: &lnwallet.StaticFeeEstimator{FeeRate: 50}, - GenSweepScript: func() ([]byte, error) { return nil, nil }, - Notifier: notifier, + CloseLink: func(_ *wire.OutPoint, _ htlcswitch.ChannelCloseType) {}, + DB: db, + Estimator: &lnwallet.StaticFeeEstimator{FeeRate: 50}, + GenSweepScript: func() ([]byte, error) { return nil, nil }, + SubscribeChannelEvents: func(_ wire.OutPoint) (*contractcourt.ChainEventSubscription, error) { + return chainEvents, nil + }, Signer: signer, + Notifier: notifier, PublishTransaction: func(_ *wire.MsgTx) error { return nil }, Store: store, }) @@ -1206,12 +1241,10 @@ func createTestArbiter(t *testing.T, notifier chainntnfs.ChainNotifier, return ba, cleanUp, nil } -// createInitChannelsWithNotifier creates two initialized test channels funded -// with 10 BTC, with 5 BTC allocated to each side. Within the channel, Alice is -// the initiator. -func createInitChannelsWithNotifier(revocationWindow int, - notifier chainntnfs.ChainNotifier) (*lnwallet.LightningChannel, - *lnwallet.LightningChannel, func(), error) { +// createInitChannels creates two initialized test channels funded with 10 BTC, +// with 5 BTC allocated to each side. Within the channel, Alice is the +// initiator. +func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwallet.LightningChannel, func(), error) { aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), alicesPrivKey) @@ -1376,9 +1409,24 @@ func createInitChannelsWithNotifier(revocationWindow int, return nil, nil, nil, err } + addr := &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 18556, + } + if err := channelAlice.State().SyncPending(addr, 101); err != nil { + return nil, nil, nil, err + } if err := channelAlice.State().FullSync(); err != nil { return nil, nil, nil, err } + + addr = &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 18555, + } + if err := channelBob.State().SyncPending(addr, 101); err != nil { + return nil, nil, nil, err + } if err := channelBob.State().FullSync(); err != nil { return nil, nil, nil, err } diff --git a/mock.go b/mock.go index 1f5ae2fe..0d9f64c9 100644 --- a/mock.go +++ b/mock.go @@ -90,9 +90,11 @@ func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, Confirmed: m.confChannel, }, nil } -func (m *mockNotfier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, - error) { - return nil, nil +func (m *mockNotfier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) { + return &chainntnfs.BlockEpochEvent{ + Epochs: make(chan *chainntnfs.BlockEpoch), + Cancel: func() {}, + }, nil } func (m *mockNotfier) Start() error { diff --git a/test_utils.go b/test_utils.go index afdfa9ea..ed01d6b1 100644 --- a/test_utils.go +++ b/test_utils.go @@ -251,8 +251,12 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, breachArbiter := &breachArbiter{} chainArb := contractcourt.NewChainArbitrator( - contractcourt.ChainArbitratorConfig{}, nil, + contractcourt.ChainArbitratorConfig{ + Notifier: notifier, + ChainIO: chainIO, + }, dbAlice, ) + chainArb.WatchNewChannel(aliceChannelState) s := &server{ chanDB: dbAlice, diff --git a/utxonursery.go b/utxonursery.go index 8877f1d3..114012c8 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -1732,7 +1732,7 @@ func makeKidOutput(outpoint, originChanPoint *wire.OutPoint, return kidOutput{ breachedOutput: makeBreachedOutput( - outpoint, witnessType, signDescriptor, + outpoint, witnessType, nil, signDescriptor, ), isHtlc: isHtlc, originChanPoint: *originChanPoint,