lnwallet/channel_test: adds TestBreachClose

This commit is contained in:
Conner Fromknecht 2017-12-13 01:30:12 -08:00
parent 9703ab9161
commit 20f4c61c8b
No known key found for this signature in database
GPG Key ID: 39DE78FBE6ACB0EF

@ -3,13 +3,16 @@ package lnwallet
import ( import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"os" "os"
"reflect" "reflect"
"runtime" "runtime"
"sync"
"testing" "testing"
"time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
@ -79,7 +82,7 @@ func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]
} }
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, amt, witnessScript, txscript.SigHashAll, signDesc.InputIndex, amt, witnessScript, signDesc.HashType,
privKey) privKey)
if err != nil { if err != nil {
return nil, err return nil, err
@ -105,7 +108,7 @@ func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor
witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes, witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, signDesc.Output.Value, signDesc.Output.PkScript, signDesc.InputIndex, signDesc.Output.Value, signDesc.Output.PkScript,
txscript.SigHashAll, privKey, true) signDesc.HashType, privKey, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -121,6 +124,7 @@ type mockNotfier struct {
func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
return nil, nil return nil, nil
} }
func (m *mockNotfier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) { func (m *mockNotfier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) {
return nil, nil return nil, nil
} }
@ -132,6 +136,7 @@ func (m *mockNotfier) Start() error {
func (m *mockNotfier) Stop() error { func (m *mockNotfier) Stop() error {
return nil return nil
} }
func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, heightHint uint32) (*chainntnfs.SpendEvent, error) { func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{ return &chainntnfs.SpendEvent{
Spend: make(chan *chainntnfs.SpendDetail), Spend: make(chan *chainntnfs.SpendDetail),
@ -140,6 +145,51 @@ func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, heightHint uint
}, nil }, nil
} }
// mockSpendNotifier extends the mockNotifier so that spend notifications can be
// triggered and delivered to subscribers.
type mockSpendNotifier struct {
*mockNotfier
spendMap map[wire.OutPoint][]chan *chainntnfs.SpendDetail
}
func makeMockSpendNotifier() *mockSpendNotifier {
return &mockSpendNotifier{
spendMap: make(map[wire.OutPoint][]chan *chainntnfs.SpendDetail),
}
}
func (m *mockSpendNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32) (*chainntnfs.SpendEvent, error) {
spendChan := make(chan *chainntnfs.SpendDetail, 1)
m.spendMap[*outpoint] = append(m.spendMap[*outpoint], spendChan)
return &chainntnfs.SpendEvent{
Spend: spendChan,
Cancel: func() {
},
}, nil
}
// Spend dispatches SpendDetails to all subscribers of the outpoint. The details
// will include the transaction and height provided by the caller.
func (m *mockSpendNotifier) Spend(outpoint *wire.OutPoint, height int32,
txn *wire.MsgTx) {
if spendChans, ok := m.spendMap[*outpoint]; ok {
delete(m.spendMap, *outpoint)
for _, spendChan := range spendChans {
txnHash := txn.TxHash()
spendChan <- &chainntnfs.SpendDetail{
SpentOutPoint: outpoint,
SpendingHeight: height,
SpendingTx: txn,
SpenderTxHash: &txnHash,
SpenderInputIndex: outpoint.Index,
}
}
}
}
// initRevocationWindows simulates a new channel being opened within the p2p // initRevocationWindows simulates a new channel being opened within the p2p
// network by populating the initial revocation windows of the passed // network by populating the initial revocation windows of the passed
// commitment state machines. // commitment state machines.
@ -204,9 +254,36 @@ func forceStateTransition(chanA, chanB *LightningChannel) error {
return nil return nil
} }
// createTestChannels creates two test channels funded with 10 BTC, with 5 BTC // createSpendableTestChannels initializes a pair of channels using a
// mockSpendNotifier. This allows us to test the behavior of the closeObserver,
// which is activated when the funding transaction is spent.
func createSpendableTestChannels(revocationWindow int) (*LightningChannel,
*LightningChannel, *mockSpendNotifier, func(), error) {
notifier := makeMockSpendNotifier()
alice, bob, cleanup, err := createTestChannelsWithNotifier(
revocationWindow, notifier,
)
return alice, bob, notifier, cleanup, err
}
// createTestChannels initializes a pair of channels using a mock notifier.
func createTestChannels(revocationWindow int) (*LightningChannel,
*LightningChannel, func(), error) {
notifier := &mockNotfier{}
return createTestChannelsWithNotifier(revocationWindow, notifier)
}
// createTestChannelsWithNotifier creates two test lightning channels using the
// provided notifier. The channel itself is funded with 10 BTC, with 5 BTC
// allocated to each side. Within the channel, Alice is the initiator. // allocated to each side. Within the channel, Alice is the initiator.
func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChannel, func(), error) { func createTestChannelsWithNotifier(revocationWindow int,
notifier chainntnfs.ChainNotifier) (*LightningChannel,
*LightningChannel, func(), error) {
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey) testWalletPrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
@ -353,8 +430,6 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
aliceSigner := &mockSigner{aliceKeyPriv} aliceSigner := &mockSigner{aliceKeyPriv}
bobSigner := &mockSigner{bobKeyPriv} bobSigner := &mockSigner{bobKeyPriv}
notifier := &mockNotfier{}
channelAlice, err := NewLightningChannel(aliceSigner, notifier, channelAlice, err := NewLightningChannel(aliceSigner, notifier,
estimator, aliceChannelState) estimator, aliceChannelState)
if err != nil { if err != nil {
@ -1151,6 +1226,106 @@ func TestForceCloseDustOutput(t *testing.T) {
} }
} }
// TestBreachClose checks that the resulting ForceCloseSummary is correct when a
// peer is ForceClosing the channel. Will check outputs both above and below
// the dust limit.
func TestBreachClose(t *testing.T) {
t.Parallel()
// Create a test channel which will be used for the duration of this
// unittest. The channel will be funded evenly with Alice having 5 BTC,
// and Bob having 5 BTC.
aliceChannel, bobChannel, notifier, cleanUp, err :=
createSpendableTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// Send one HTLC from Alice to Bob, and advance the state of both
// channels.
htlcAmount := lnwire.NewMSatFromSatoshis(20000)
htlc, _ := createHTLC(0, htlcAmount)
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("alice unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("bob unable to recv add htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}
// Construct a force close summary of Bob's channel, this includes the
// breach transaction that will be used to spend the funding point.
forceCloseSummary, err := bobChannel.ForceClose()
if err != nil {
t.Fatalf("unable to force close bob's channel: %v", err)
}
// Send another HTLC and advance the state of both channels again. This
// ensures that Alice's state will be ahead of the breach transaction
// generated above.
htlc2, _ := createHTLC(1, htlcAmount)
if _, err := aliceChannel.AddHTLC(htlc2); err != nil {
t.Fatalf("alice unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc2); err != nil {
t.Fatalf("bob unable to recv add htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}
chanPoint := aliceChannel.ChanPoint
breachTxn := forceCloseSummary.CloseTx
// Spend the funding point using the breach transaction.
notifier.Spend(chanPoint, 100, breachTxn)
// Set up a separate routine to monitor alice's channel for a response
// to the spend. We use a generous timeout to ensure the test doesn't
// stall indefinitely, but allows us to block the main routine until the
// close observer exits.
errChan := make(chan error, 1)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
select {
case ret := <-aliceChannel.ContractBreach:
errChan <- nil
// Acknowledge a successful processing of the
// retribution information.
ret.Err <- nil
case <-aliceChannel.UnilateralClose:
errChan <- errors.New("expected breach close to " +
"be signaled, not unilateral")
case <-time.After(60 * time.Second):
errChan <- errors.New("breach was not signaled")
}
}()
// Wait for both the close observer to exit and our background process
// to exit before attempting to read from the error channel.
aliceChannel.WaitForClose()
wg.Wait()
// Now that all tasks have been shutdown, handle the result. The result
// should be available immediately, we allow five seconds to handle any
// variance in scheduling on travis.
select {
case err := <-errChan:
if err != nil {
t.Fatalf(err.Error())
}
case <-time.After(5 * time.Second):
t.Fatalf("breach was not received")
}
}
// TestDustHTLCFees checks that fees are calculated correctly when HTLCs fall // TestDustHTLCFees checks that fees are calculated correctly when HTLCs fall
// below the nodes' dust limit. In these cases, the amount of the dust HTLCs // below the nodes' dust limit. In these cases, the amount of the dust HTLCs
// should be applied to the commitment transaction fee. // should be applied to the commitment transaction fee.