lnd.xprv/lnwallet/channel_test.go
Olaoluwa Osuntokun b6f64932c2
htlcswitch: face race condition in unit tests by returning invoice
In this commit we modify the primary InvoiceRegistry interface within
the package to instead return a direct value for LookupInvoice rather
than a pointer. This fixes an existing race condition wherein a caller
could modify or read the value of the returned invoice.
2017-11-11 16:09:29 -08:00

3479 lines
121 KiB
Go

package lnwallet
import (
"bytes"
"crypto/sha256"
"fmt"
"io/ioutil"
"math/rand"
"os"
"reflect"
"runtime"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/blockchain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
var (
privPass = []byte("private-test")
// For simplicity a single priv key controls all of our test outputs.
testWalletPrivKey = []byte{
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
}
// We're alice :)
bobsPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
// Use a hard-coded HD seed.
testHdSeed = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
// The number of confirmations required to consider any created channel
// open.
numReqConfs = uint16(1)
)
type mockSigner struct {
key *btcec.PrivateKey
}
func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) {
amt := signDesc.Output.Value
witnessScript := signDesc.WitnessScript
privKey := m.key
if !privKey.PubKey().IsEqual(signDesc.PubKey) {
return nil, fmt.Errorf("incorrect key passed")
}
switch {
case signDesc.SingleTweak != nil:
privKey = TweakPrivKey(privKey,
signDesc.SingleTweak)
case signDesc.DoubleTweak != nil:
privKey = DeriveRevocationPrivKey(privKey,
signDesc.DoubleTweak)
}
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, amt, witnessScript, txscript.SigHashAll,
privKey)
if err != nil {
return nil, err
}
return sig[:len(sig)-1], nil
}
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) {
// TODO(roasbeef): expose tweaked signer from lnwallet so don't need to
// duplicate this code?
privKey := m.key
switch {
case signDesc.SingleTweak != nil:
privKey = TweakPrivKey(privKey,
signDesc.SingleTweak)
case signDesc.DoubleTweak != nil:
privKey = DeriveRevocationPrivKey(privKey,
signDesc.DoubleTweak)
}
witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, signDesc.Output.Value, signDesc.Output.PkScript,
txscript.SigHashAll, privKey, true)
if err != nil {
return nil, err
}
return &InputScript{
Witness: witnessScript,
}, nil
}
type mockNotfier struct {
}
func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
return nil, nil
}
func (m *mockNotfier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) {
return nil, nil
}
func (m *mockNotfier) Start() error {
return nil
}
func (m *mockNotfier) Stop() error {
return nil
}
func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{
Spend: make(chan *chainntnfs.SpendDetail),
Cancel: func() {
},
}, nil
}
// initRevocationWindows simulates a new channel being opened within the p2p
// network by populating the initial revocation windows of the passed
// commitment state machines.
//
// TODO(roasbeef): rename!
func initRevocationWindows(chanA, chanB *LightningChannel, windowSize int) error {
aliceNextRevoke, err := chanA.NextRevocationKey()
if err != nil {
return err
}
if err := chanB.InitNextRevocation(aliceNextRevoke); err != nil {
return err
}
bobNextRevoke, err := chanB.NextRevocationKey()
if err != nil {
return err
}
if err := chanA.InitNextRevocation(bobNextRevoke); err != nil {
return err
}
return nil
}
// forceStateTransition executes the necessary interaction between the two
// commitment state machines to transition to a new state locking in any
// pending updates.
func forceStateTransition(chanA, chanB *LightningChannel) error {
aliceSig, aliceHtlcSigs, err := chanA.SignNextCommitment()
if err != nil {
return err
}
if err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs); err != nil {
return err
}
bobRevocation, err := chanB.RevokeCurrentCommitment()
if err != nil {
return err
}
bobSig, bobHtlcSigs, err := chanB.SignNextCommitment()
if err != nil {
return err
}
if _, err := chanA.ReceiveRevocation(bobRevocation); err != nil {
return err
}
if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil {
return err
}
aliceRevocation, err := chanA.RevokeCurrentCommitment()
if err != nil {
return err
}
if _, err := chanB.ReceiveRevocation(aliceRevocation); err != nil {
return err
}
return nil
}
// createTestChannels creates two test channels funded with 10 BTC, with 5 BTC
// allocated to each side. Within the channel, Alice is the initiator.
func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChannel, func(), error) {
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
bobsPrivKey)
channelCapacity := btcutil.Amount(10 * 1e8)
channelBal := channelCapacity / 2
aliceDustLimit := btcutil.Amount(200)
bobDustLimit := btcutil.Amount(1300)
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
prevOut := &wire.OutPoint{
Hash: chainhash.Hash(testHdSeed),
Index: 0,
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
aliceCfg := channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: aliceDustLimit,
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
ChanReserve: btcutil.Amount(rand.Int63()),
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
MaxAcceptedHtlcs: uint16(rand.Int31()),
},
CsvDelay: uint16(csvTimeoutAlice),
MultiSigKey: aliceKeyPub,
RevocationBasePoint: aliceKeyPub,
PaymentBasePoint: aliceKeyPub,
DelayBasePoint: aliceKeyPub,
}
bobCfg := channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: bobDustLimit,
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
ChanReserve: btcutil.Amount(rand.Int63()),
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
MaxAcceptedHtlcs: uint16(rand.Int31()),
},
CsvDelay: uint16(csvTimeoutBob),
MultiSigKey: bobKeyPub,
RevocationBasePoint: bobKeyPub,
PaymentBasePoint: bobKeyPub,
DelayBasePoint: bobKeyPub,
}
bobRoot := DeriveRevocationRoot(bobKeyPriv, testHdSeed, aliceKeyPub)
bobPreimageProducer := shachain.NewRevocationProducer(bobRoot)
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
bobCommitPoint := ComputeCommitmentPoint(bobFirstRevoke[:])
aliceRoot := DeriveRevocationRoot(aliceKeyPriv, testHdSeed, bobKeyPub)
alicePreimageProducer := shachain.NewRevocationProducer(aliceRoot)
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
aliceCommitPoint := ComputeCommitmentPoint(aliceFirstRevoke[:])
aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(channelBal,
channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint,
fundingTxIn)
if err != nil {
return nil, nil, nil, err
}
alicePath, err := ioutil.TempDir("", "alicedb")
dbAlice, err := channeldb.Open(alicePath)
if err != nil {
return nil, nil, nil, err
}
bobPath, err := ioutil.TempDir("", "bobdb")
dbBob, err := channeldb.Open(bobPath)
if err != nil {
return nil, nil, nil, err
}
estimator := &StaticFeeEstimator{24, 6}
feePerKw := btcutil.Amount(estimator.EstimateFeePerWeight(1) * 1000)
commitFee := calcStaticFee(0)
aliceCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee),
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal),
CommitFee: commitFee,
FeePerKw: feePerKw,
CommitTx: aliceCommitTx,
CommitSig: bytes.Repeat([]byte{1}, 71),
}
bobCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal),
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee),
CommitFee: commitFee,
FeePerKw: feePerKw,
CommitTx: bobCommitTx,
CommitSig: bytes.Repeat([]byte{1}, 71),
}
aliceChannelState := &channeldb.OpenChannel{
LocalChanCfg: aliceCfg,
RemoteChanCfg: bobCfg,
IdentityPub: aliceKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunder,
IsInitiator: true,
Capacity: channelCapacity,
RemoteCurrentRevocation: bobCommitPoint,
RevocationProducer: alicePreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: aliceCommit,
RemoteCommitment: aliceCommit,
Db: dbAlice,
}
bobChannelState := &channeldb.OpenChannel{
LocalChanCfg: bobCfg,
RemoteChanCfg: aliceCfg,
IdentityPub: bobKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunder,
IsInitiator: false,
Capacity: channelCapacity,
RemoteCurrentRevocation: aliceCommitPoint,
RevocationProducer: bobPreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: bobCommit,
RemoteCommitment: bobCommit,
Db: dbBob,
}
aliceSigner := &mockSigner{aliceKeyPriv}
bobSigner := &mockSigner{bobKeyPriv}
notifier := &mockNotfier{}
channelAlice, err := NewLightningChannel(aliceSigner, notifier,
estimator, aliceChannelState)
if err != nil {
return nil, nil, nil, err
}
channelBob, err := NewLightningChannel(bobSigner, notifier,
estimator, bobChannelState)
if err != nil {
return nil, nil, nil, err
}
if err := channelAlice.channelState.FullSync(); err != nil {
return nil, nil, nil, err
}
if err := channelBob.channelState.FullSync(); err != nil {
return nil, nil, nil, err
}
cleanUpFunc := func() {
os.RemoveAll(bobPath)
os.RemoveAll(alicePath)
channelAlice.Stop()
channelBob.Stop()
}
// Now that the channel are open, simulate the start of a session by
// having Alice and Bob extend their revocation windows to each other.
err = initRevocationWindows(channelAlice, channelBob, revocationWindow)
if err != nil {
return nil, nil, nil, err
}
return channelAlice, channelBob, cleanUpFunc, nil
}
// calcStaticFee calculates appropriate fees for commitment transactions. This
// function provides a simple way to allow test balance assertions to take fee
// calculations into account.
//
// TODO(bvu): Refactor when dynamic fee estimation is added.
func calcStaticFee(numHTLCs int) btcutil.Amount {
const (
commitWeight = btcutil.Amount(724)
htlcWeight = 172
feePerKw = btcutil.Amount(24/4) * 1000
)
return feePerKw * (commitWeight +
btcutil.Amount(htlcWeight*numHTLCs)) / 1000
}
// createHTLC is a utility function for generating an HTLC with a given
// preimage and a given amount.
func createHTLC(data int, amount lnwire.MilliSatoshi) (*lnwire.UpdateAddHTLC, [32]byte) {
preimage := bytes.Repeat([]byte{byte(data)}, 32)
paymentHash := sha256.Sum256(preimage)
var returnPreimage [32]byte
copy(returnPreimage[:], preimage)
return &lnwire.UpdateAddHTLC{
PaymentHash: paymentHash,
Amount: amount,
Expiry: uint32(5),
}, returnPreimage
}
func assertOutputExistsByValue(t *testing.T, commitTx *wire.MsgTx,
value btcutil.Amount) {
for _, txOut := range commitTx.TxOut {
if txOut.Value == int64(value) {
return
}
}
t.Fatalf("unable to find output of value %v within tx %v", value,
spew.Sdump(commitTx))
}
// TestSimpleAddSettleWorkflow tests a simple channel scenario wherein the
// local node (Alice in this case) creates a new outgoing HTLC to bob, commits
// this change, then bob immediately commits a settlement of the HTLC after the
// initial add is fully committed in both commit chains.
//
// TODO(roasbeef): write higher level framework to exercise various states of
// the state machine
// * DSL language perhaps?
// * constructed via input/output files
func TestSimpleAddSettleWorkflow(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
paymentPreimage := bytes.Repeat([]byte{1}, 32)
paymentHash := sha256.Sum256(paymentPreimage)
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
htlc := &lnwire.UpdateAddHTLC{
PaymentHash: paymentHash,
Amount: htlcAmt,
Expiry: uint32(5),
}
// First Alice adds the outgoing HTLC to her local channel's state
// update log. Then Alice sends this wire message over to Bob who adds
// this htlc to his remote state update log.
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("unable to recv htlc: %v", err)
}
// Next alice commits this change by sending a signature message. Since
// we expect the messages to be ordered, Bob will receive the HTLC we
// just sent before he receives this signature, so the signature will
// cover the HTLC.
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("alice unable to sign commitment: %v", err)
}
// Bob receives this signature message, and checks that this covers the
// state he has in his remote log. This includes the HTLC just sent
// from Alice.
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("bob unable to process alice's new commitment: %v", err)
}
// Bob revokes his prior commitment given to him by Alice, since he now
// has a valid signature for a newer commitment.
bobRevocation, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to generate bob revocation: %v", err)
}
// Bob finally send a signature for Alice's commitment transaction.
// This signature will cover the HTLC, since Bob will first send the
// revocation just created. The revocation also acks every received
// HTLC up to the point where Alice sent here signature.
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("bob unable to sign alice's commitment: %v", err)
}
// Alice then processes this revocation, sending her own revocation for
// her prior commitment transaction. Alice shouldn't have any HTLCs to
// forward since she's sending an outgoing HTLC.
if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
t.Fatalf("alice unable to process bob's revocation: %v", err)
} else if len(htlcs) != 0 {
t.Fatalf("alice forwards %v htlcs, should forward none: ", len(htlcs))
}
// Alice then processes bob's signature, and since she just received
// the revocation, she expect this signature to cover everything up to
// the point where she sent her signature, including the HTLC.
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
if err != nil {
t.Fatalf("alice unable to process bob's new commitment: %v", err)
}
// Alice then generates a revocation for bob.
aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to revoke alice channel: %v", err)
}
// Finally Bob processes Alice's revocation, at this point the new HTLC
// is fully locked in within both commitment transactions. Bob should
// also be able to forward an HTLC now that the HTLC has been locked
// into both commitment transactions.
if htlcs, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("bob unable to process alice's revocation: %v", err)
} else if len(htlcs) != 1 {
t.Fatalf("bob should be able to forward an HTLC, instead can "+
"forward %v", len(htlcs))
}
// At this point, both sides should have the proper number of satoshis
// sent, and commitment height updated within their local channel
// state.
aliceSent := lnwire.MilliSatoshi(0)
bobSent := lnwire.MilliSatoshi(0)
if aliceChannel.channelState.TotalMSatSent != aliceSent {
t.Fatalf("alice has incorrect milli-satoshis sent: %v vs %v",
aliceChannel.channelState.TotalMSatSent, aliceSent)
}
if aliceChannel.channelState.TotalMSatReceived != bobSent {
t.Fatalf("alice has incorrect milli-satoshis received %v vs %v",
aliceChannel.channelState.TotalMSatReceived, bobSent)
}
if bobChannel.channelState.TotalMSatSent != bobSent {
t.Fatalf("bob has incorrect milli-satoshis sent %v vs %v",
bobChannel.channelState.TotalMSatSent, bobSent)
}
if bobChannel.channelState.TotalMSatReceived != aliceSent {
t.Fatalf("bob has incorrect milli-satoshis received %v vs %v",
bobChannel.channelState.TotalMSatReceived, aliceSent)
}
if bobChannel.currentHeight != 1 {
t.Fatalf("bob has incorrect commitment height, %v vs %v",
bobChannel.currentHeight, 1)
}
if aliceChannel.currentHeight != 1 {
t.Fatalf("alice has incorrect commitment height, %v vs %v",
aliceChannel.currentHeight, 1)
}
// Both commitment transactions should have three outputs, and one of
// them should be exactly the amount of the HTLC.
if len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut) != 3 {
t.Fatalf("alice should have three commitment outputs, instead "+
"have %v",
len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut))
}
if len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut) != 3 {
t.Fatalf("bob should have three commitment outputs, instead "+
"have %v",
len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut))
}
assertOutputExistsByValue(t,
aliceChannel.channelState.LocalCommitment.CommitTx,
htlcAmt.ToSatoshis())
assertOutputExistsByValue(t,
bobChannel.channelState.LocalCommitment.CommitTx,
htlcAmt.ToSatoshis())
// Now we'll repeat a similar exchange, this time with Bob settling the
// HTLC once he learns of the preimage.
var preimage [32]byte
copy(preimage[:], paymentPreimage)
settleIndex, _, err := bobChannel.SettleHTLC(preimage)
if err != nil {
t.Fatalf("bob unable to settle inbound htlc: %v", err)
}
if err := aliceChannel.ReceiveHTLCSettle(preimage, settleIndex); err != nil {
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
}
bobSig2, bobHtlcSigs2, err := bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("bob unable to sign settle commitment: %v", err)
}
err = aliceChannel.ReceiveNewCommitment(bobSig2, bobHtlcSigs2)
if err != nil {
t.Fatalf("alice unable to process bob's new commitment: %v", err)
}
aliceRevocation2, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("alice unable to generate revocation: %v", err)
}
aliceSig2, aliceHtlcSigs2, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("alice unable to sign new commitment: %v", err)
}
if htlcs, err := bobChannel.ReceiveRevocation(aliceRevocation2); err != nil {
t.Fatalf("bob unable to process alice's revocation: %v", err)
} else if len(htlcs) != 0 {
t.Fatalf("bob shouldn't forward any HTLCs after outgoing settle, "+
"instead can forward: %v", spew.Sdump(htlcs))
}
err = bobChannel.ReceiveNewCommitment(aliceSig2, aliceHtlcSigs2)
if err != nil {
t.Fatalf("bob unable to process alice's new commitment: %v", err)
}
bobRevocation2, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("bob unable to revoke commitment: %v", err)
}
if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation2); err != nil {
t.Fatalf("alice unable to process bob's revocation: %v", err)
} else if len(htlcs) != 1 {
// Alice should now be able to forward the settlement HTLC to
// any down stream peers.
t.Fatalf("alice should be able to forward a single HTLC, "+
"instead can forward %v: %v", len(htlcs), spew.Sdump(htlcs))
}
// At this point, Bob should have 6 BTC settled, with Alice still having
// 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel
// should show 1 BTC received. They should also be at commitment height
// two, with the revocation window extended by by 1 (5).
mSatTransferred := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
if aliceChannel.channelState.TotalMSatSent != mSatTransferred {
t.Fatalf("alice satoshis sent incorrect %v vs %v expected",
aliceChannel.channelState.TotalMSatSent,
mSatTransferred)
}
if aliceChannel.channelState.TotalMSatReceived != 0 {
t.Fatalf("alice satoshis received incorrect %v vs %v expected",
aliceChannel.channelState.TotalMSatReceived, 0)
}
if bobChannel.channelState.TotalMSatReceived != mSatTransferred {
t.Fatalf("bob satoshis received incorrect %v vs %v expected",
bobChannel.channelState.TotalMSatReceived,
mSatTransferred)
}
if bobChannel.channelState.TotalMSatSent != 0 {
t.Fatalf("bob satoshis sent incorrect %v vs %v expected",
bobChannel.channelState.TotalMSatSent, 0)
}
if bobChannel.currentHeight != 2 {
t.Fatalf("bob has incorrect commitment height, %v vs %v",
bobChannel.currentHeight, 2)
}
if aliceChannel.currentHeight != 2 {
t.Fatalf("alice has incorrect commitment height, %v vs %v",
aliceChannel.currentHeight, 2)
}
// The logs of both sides should now be cleared since the entry adding
// the HTLC should have been removed once both sides receive the
// revocation.
if aliceChannel.localUpdateLog.Len() != 0 {
t.Fatalf("alice's local not updated, should be empty, has %v "+
"entries instead", aliceChannel.localUpdateLog.Len())
}
if aliceChannel.remoteUpdateLog.Len() != 0 {
t.Fatalf("alice's remote not updated, should be empty, has %v "+
"entries instead", aliceChannel.remoteUpdateLog.Len())
}
if len(aliceChannel.localUpdateLog.updateIndex) != 0 {
t.Fatalf("alice's local log index not cleared, should be empty but "+
"has %v entries", len(aliceChannel.localUpdateLog.updateIndex))
}
if len(aliceChannel.remoteUpdateLog.updateIndex) != 0 {
t.Fatalf("alice's remote log index not cleared, should be empty but "+
"has %v entries", len(aliceChannel.remoteUpdateLog.updateIndex))
}
}
// TestCheckCommitTxSize checks that estimation size of commitment
// transaction with some degree of error corresponds to the actual size.
func TestCheckCommitTxSize(t *testing.T) {
t.Parallel()
checkSize := func(channel *LightningChannel, count int) {
// Due to variable size of the signatures (70-73) in
// witness script actual size of commitment transaction might
// be lower on 6 weight.
BaseCommitmentTxSizeEstimationError := 6
commitTx, err := channel.getSignedCommitTx()
if err != nil {
t.Fatalf("unable to initiate alice force close: %v", err)
}
actualCost := blockchain.GetTransactionWeight(btcutil.NewTx(commitTx))
estimatedCost := estimateCommitTxWeight(count, false)
diff := int(estimatedCost - actualCost)
if 0 > diff || BaseCommitmentTxSizeEstimationError < diff {
t.Fatalf("estimation is wrong, diff: %v", diff)
}
}
// 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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// Check that weight estimation of the commitment transaction without
// HTLCs is right.
checkSize(aliceChannel, 0)
checkSize(bobChannel, 0)
// Adding HTLCs and check that size stays in allowable estimation
// error window.
for i := 1; i <= 10; i++ {
htlc, _ := createHTLC(i, lnwire.MilliSatoshi(1e7))
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 receive htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("unable to complete state update: %v", err)
}
checkSize(aliceChannel, i)
checkSize(bobChannel, i)
}
// Settle HTLCs and check that estimation is counting cost of settle
// HTLCs properly.
for i := 10; i >= 1; i-- {
_, preimage := createHTLC(i, lnwire.MilliSatoshi(1e7))
settleIndex, _, err := bobChannel.SettleHTLC(preimage)
if err != nil {
t.Fatalf("bob unable to settle inbound htlc: %v", err)
}
err = aliceChannel.ReceiveHTLCSettle(preimage, settleIndex)
if err != nil {
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
}
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to complete state update: %v", err)
}
checkSize(aliceChannel, i-1)
checkSize(bobChannel, i-1)
}
}
func TestCooperativeChannelClosure(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
aliceDeliveryScript := bobsPrivKey[:]
bobDeliveryScript := testHdSeed[:]
aliceFeeRate := uint64(aliceChannel.channelState.LocalCommitment.FeePerKw)
bobFeeRate := uint64(bobChannel.channelState.LocalCommitment.FeePerKw)
// We'll store with both Alice and Bob creating a new close proposal
// with the same fee.
aliceFee := aliceChannel.CalcFee(aliceFeeRate)
aliceSig, _, err := aliceChannel.CreateCloseProposal(
aliceFee, aliceDeliveryScript, bobDeliveryScript,
)
if err != nil {
t.Fatalf("unable to create alice coop close proposal: %v", err)
}
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobFee := bobChannel.CalcFee(bobFeeRate)
bobSig, _, err := bobChannel.CreateCloseProposal(
bobFee, bobDeliveryScript, aliceDeliveryScript,
)
if err != nil {
t.Fatalf("unable to create bob coop close proposal: %v", err)
}
bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
// With the proposals created, both sides should be able to properly
// process the other party's signature. This indicates that the
// transaction is well formed, and the signatures verify.
aliceCloseTx, err := bobChannel.CompleteCooperativeClose(
bobCloseSig, aliceCloseSig, bobDeliveryScript,
aliceDeliveryScript, bobFee)
if err != nil {
t.Fatalf("unable to complete alice cooperative close: %v", err)
}
bobCloseSha := aliceCloseTx.TxHash()
bobCloseTx, err := aliceChannel.CompleteCooperativeClose(
aliceCloseSig, bobCloseSig, aliceDeliveryScript,
bobDeliveryScript, aliceFee)
if err != nil {
t.Fatalf("unable to complete bob cooperative close: %v", err)
}
aliceCloseSha := bobCloseTx.TxHash()
if bobCloseSha != aliceCloseSha {
t.Fatalf("alice and bob close transactions don't match: %v", err)
}
}
// TestForceClose 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 TestForceClose(t *testing.T) {
t.Parallel()
// TODO(roasbeef): modify to add some HTLC's before closing?
// 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, cleanUp, err := createTestChannels(3)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
bobAmount := bobChannel.channelState.LocalCommitment.LocalBalance
// First, we'll add an outgoing HTLC from Alice to Bob, such that it
// will still be present within the broadcast commitment transaction.
// We'll ensure that the HTLC amount is above Alice's dust limit.
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)
}
// Now with the HTLC in tact, we'll perform a force close on Alice's
// part.
closeSummary, err := aliceChannel.ForceClose()
if err != nil {
t.Fatalf("unable to force close channel: %v", err)
}
// Alice's force close summary should have a single HTLC resolution.
if len(closeSummary.HtlcResolutions) != 1 {
t.Fatalf("alice htlc resolutions not populated: expected %v "+
"htlcs, got %v htlcs",
1, len(closeSummary.HtlcResolutions))
}
// The SelfOutputSignDesc should be non-nil since the output to-self is
// non-dust.
if closeSummary.SelfOutputSignDesc == nil {
t.Fatalf("alice fails to include to-self output in " +
"ForceCloseSummary")
}
// The rest of the close summary should have been populated properly.
aliceDelayPoint := aliceChannel.channelState.LocalChanCfg.DelayBasePoint
if !closeSummary.SelfOutputSignDesc.PubKey.IsEqual(aliceDelayPoint) {
t.Fatalf("alice incorrect pubkey in SelfOutputSignDesc")
}
// Factoring in the fee rate, Alice's amount should properly reflect
// that we've added an additional HTLC to the commitment transaction.
totalCommitWeight := CommitWeight + HtlcWeight
feePerKw := aliceChannel.channelState.LocalCommitment.FeePerKw
commitFee := btcutil.Amount((int64(feePerKw) * totalCommitWeight) / 1000)
expectedAmount := (aliceChannel.Capacity / 2) - htlcAmount.ToSatoshis() - commitFee
if closeSummary.SelfOutputSignDesc.Output.Value != int64(expectedAmount) {
t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+
"expected %v, got %v", int64(expectedAmount),
closeSummary.SelfOutputSignDesc.Output.Value)
}
// Alice's listed CSV delay should also match the delay that was
// pre-committed to at channel opening.
if closeSummary.SelfOutputMaturity !=
uint32(aliceChannel.localChanCfg.CsvDelay) {
t.Fatalf("alice: incorrect local CSV delay in ForceCloseSummary, "+
"expected %v, got %v",
aliceChannel.channelState.LocalChanCfg.CsvDelay,
closeSummary.SelfOutputMaturity)
}
// Next, we'll ensure that the second level HTLC transaction it itself
// spendable, and also that the delivery output (with delay) itself has
// a valid sign descriptor.
var senderHtlcPkScript []byte
for _, txOut := range closeSummary.CloseTx.TxOut {
if txOut.Value == int64(htlcAmount.ToSatoshis()) {
senderHtlcPkScript = txOut.PkScript
break
}
}
if senderHtlcPkScript == nil {
t.Fatalf("unable to find htlc script")
}
// First, verify that the second level transaction can properly spend
// the multi-sig clause within the
htlcResolution := closeSummary.HtlcResolutions[0]
timeoutTx := htlcResolution.SignedTimeoutTx
vm, err := txscript.NewEngine(senderHtlcPkScript,
timeoutTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(htlcAmount.ToSatoshis()))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("htlc timeout spend is invalid: %v", err)
}
// Next, we'll ensure that we can spend the output of the second level
// transaction given a properly crafted sweep transaction.
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: htlcResolution.SignedTimeoutTx.TxHash(),
Index: 0,
},
})
sweepTx.AddTxOut(&wire.TxOut{
PkScript: senderHtlcPkScript,
Value: htlcResolution.SweepSignDesc.Output.Value,
})
htlcResolution.SweepSignDesc.InputIndex = 0
sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.signer,
&htlcResolution.SweepSignDesc, sweepTx,
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay))
if err != nil {
t.Fatalf("unable to gen witness for timeout output: %v", err)
}
// With the witness fully populated for the success spend from the
// second-level transaction, we ensure that the scripts properly
// validate given the information within the htlc resolution struct.
vm, err = txscript.NewEngine(
htlcResolution.SweepSignDesc.Output.PkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, htlcResolution.SweepSignDesc.Output.Value,
)
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("htlc timeout spend is invalid: %v", err)
}
// Finally, the txid of the commitment transaction and the one returned
// as the closing transaction should also match.
closeTxHash := closeSummary.CloseTx.TxHash()
commitTxHash := aliceChannel.channelState.LocalCommitment.CommitTx.TxHash()
if !bytes.Equal(closeTxHash[:], commitTxHash[:]) {
t.Fatalf("alice: incorrect close transaction txid")
}
// Check the same for Bob's ForceCloseSummary.
closeSummary, err = bobChannel.ForceClose()
if err != nil {
t.Fatalf("unable to force close channel: %v", err)
}
if closeSummary.SelfOutputSignDesc == nil {
t.Fatalf("bob fails to include to-self output in ForceCloseSummary")
}
bobDelayPoint := bobChannel.channelState.LocalChanCfg.DelayBasePoint
if !closeSummary.SelfOutputSignDesc.PubKey.IsEqual(bobDelayPoint) {
t.Fatalf("bob incorrect pubkey in SelfOutputSignDesc")
}
if closeSummary.SelfOutputSignDesc.Output.Value !=
int64(bobAmount.ToSatoshis()) {
t.Fatalf("bob incorrect output value in SelfOutputSignDesc, "+
"expected %v, got %v",
bobAmount.ToSatoshis(),
int64(closeSummary.SelfOutputSignDesc.Output.Value))
}
if closeSummary.SelfOutputMaturity !=
uint32(bobChannel.channelState.LocalChanCfg.CsvDelay) {
t.Fatalf("bob: incorrect local CSV delay in ForceCloseSummary, "+
"expected %v, got %v",
bobChannel.channelState.LocalChanCfg.CsvDelay,
closeSummary.SelfOutputMaturity)
}
closeTxHash = closeSummary.CloseTx.TxHash()
commitTxHash = bobChannel.channelState.LocalCommitment.CommitTx.TxHash()
if !bytes.Equal(closeTxHash[:], commitTxHash[:]) {
t.Fatalf("bob: incorrect close transaction txid")
}
}
// TestForceCloseDustOutput tests that if either side force closes with an
// active dust output (for only a single party due to asymmetric dust values),
// then the force close summary is well crafted.
func TestForceCloseDustOutput(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, cleanUp, err := createTestChannels(3)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
htlcAmount := lnwire.NewMSatFromSatoshis(500)
aliceAmount := aliceChannel.channelState.LocalCommitment.LocalBalance
bobAmount := bobChannel.channelState.LocalCommitment.LocalBalance
// Have Bobs' to-self output be below her dust limit and check
// ForceCloseSummary again on both peers.
htlc, preimage := createHTLC(0, bobAmount-htlcAmount)
if _, err := bobChannel.AddHTLC(htlc); err != nil {
t.Fatalf("bob unable to add htlc: %v", err)
}
if _, err := aliceChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("alice unable to receive htlc: %v", err)
}
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}
// Settle HTLC and sign new commitment.
settleIndex, _, err := aliceChannel.SettleHTLC(preimage)
if err != nil {
t.Fatalf("bob unable to settle inbound htlc: %v", err)
}
err = bobChannel.ReceiveHTLCSettle(preimage, settleIndex)
if err != nil {
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}
aliceAmount = aliceChannel.channelState.LocalCommitment.LocalBalance
bobAmount = bobChannel.channelState.LocalCommitment.RemoteBalance
closeSummary, err := aliceChannel.ForceClose()
if err != nil {
t.Fatalf("unable to force close channel: %v", err)
}
// Alice's to-self output should still be in the commitment
// transaction.
if closeSummary.SelfOutputSignDesc == nil {
t.Fatalf("alice fails to include to-self output in " +
"ForceCloseSummary")
}
if !closeSummary.SelfOutputSignDesc.PubKey.IsEqual(
aliceChannel.channelState.LocalChanCfg.DelayBasePoint,
) {
t.Fatalf("alice incorrect pubkey in SelfOutputSignDesc")
}
if closeSummary.SelfOutputSignDesc.Output.Value !=
int64(aliceAmount.ToSatoshis()) {
t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+
"expected %v, got %v",
aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis(),
closeSummary.SelfOutputSignDesc.Output.Value)
}
if closeSummary.SelfOutputMaturity !=
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay) {
t.Fatalf("alice: incorrect local CSV delay in ForceCloseSummary, "+
"expected %v, got %v",
aliceChannel.channelState.LocalChanCfg.CsvDelay,
closeSummary.SelfOutputMaturity)
}
closeTxHash := closeSummary.CloseTx.TxHash()
commitTxHash := aliceChannel.channelState.LocalCommitment.CommitTx.TxHash()
if !bytes.Equal(closeTxHash[:], commitTxHash[:]) {
t.Fatalf("alice: incorrect close transaction txid")
}
closeSummary, err = bobChannel.ForceClose()
if err != nil {
t.Fatalf("unable to force close channel: %v", err)
}
// Bob's to-self output is below Bob's dust value and should be
// reflected in the ForceCloseSummary.
if closeSummary.SelfOutputSignDesc != nil {
t.Fatalf("bob incorrectly includes to-self output in " +
"ForceCloseSummary")
}
closeTxHash = closeSummary.CloseTx.TxHash()
commitTxHash = bobChannel.channelState.LocalCommitment.CommitTx.TxHash()
if !bytes.Equal(closeTxHash[:], commitTxHash[:]) {
t.Fatalf("bob: incorrect close transaction txid")
}
}
// TestDustHTLCFees checks that fees are calculated correctly when HTLCs fall
// below the nodes' dust limit. In these cases, the amount of the dust HTLCs
// should be applied to the commitment transaction fee.
func TestDustHTLCFees(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, cleanUp, err := createTestChannels(3)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
aliceStartingBalance := aliceChannel.channelState.LocalCommitment.LocalBalance
// This HTLC amount should be lower than the dust limits of both nodes.
htlcAmount := lnwire.NewMSatFromSatoshis(100)
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 receive htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}
// After the transition, we'll ensure that we performed fee accounting
// properly. Namely, the local+remote+commitfee values should add up to
// the total capacity of the channel. This same should hold for both
// sides.
totalSatoshisAlice := (aliceChannel.channelState.LocalCommitment.LocalBalance +
aliceChannel.channelState.LocalCommitment.RemoteBalance +
lnwire.NewMSatFromSatoshis(aliceChannel.channelState.LocalCommitment.CommitFee))
if totalSatoshisAlice+htlcAmount != lnwire.NewMSatFromSatoshis(aliceChannel.Capacity) {
t.Fatalf("alice's funds leaked: total satoshis are %v, but channel "+
"capacity is %v", int64(totalSatoshisAlice),
int64(aliceChannel.Capacity))
}
totalSatoshisBob := (bobChannel.channelState.LocalCommitment.LocalBalance +
bobChannel.channelState.LocalCommitment.RemoteBalance +
lnwire.NewMSatFromSatoshis(bobChannel.channelState.LocalCommitment.CommitFee))
if totalSatoshisBob+htlcAmount != lnwire.NewMSatFromSatoshis(bobChannel.Capacity) {
t.Fatalf("bob's funds leaked: total satoshis are %v, but channel "+
"capacity is %v", int64(totalSatoshisBob),
int64(bobChannel.Capacity))
}
// The commitment fee paid should be the same, as there have been no
// new material outputs added.
defaultFee := calcStaticFee(0)
if aliceChannel.channelState.LocalCommitment.CommitFee != defaultFee {
t.Fatalf("dust htlc amounts not subtracted from commitment fee "+
"expected %v, got %v", defaultFee,
aliceChannel.channelState.LocalCommitment.CommitFee)
}
if bobChannel.channelState.LocalCommitment.CommitFee != defaultFee {
t.Fatalf("dust htlc amounts not subtracted from commitment fee "+
"expected %v, got %v", defaultFee,
bobChannel.channelState.LocalCommitment.CommitFee)
}
// Alice's final balance should reflect the HTLC deficit even though
// the HTLC was paid to fees as it was trimmed.
aliceEndBalance := aliceChannel.channelState.LocalCommitment.LocalBalance
aliceExpectedBalance := aliceStartingBalance - htlcAmount
if aliceEndBalance != aliceExpectedBalance {
t.Fatalf("alice not credited for dust: expected %v, got %v",
aliceExpectedBalance, aliceEndBalance)
}
}
// TestHTLCDustLimit checks the situation in which an HTLC is larger than one
// channel participant's dust limit, but smaller than the other participant's
// dust limit. In this case, the participants' commitment chains will diverge.
// In one commitment chain, the HTLC will be added as normal, in the other
// chain, the amount of the HTLC will contribute to the fees to be paid.
func TestHTLCDustLimit(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, cleanUp, err := createTestChannels(3)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// The amount of the HTLC should be above Alice's dust limit and below
// Bob's dust limit.
htlcSat := (btcutil.Amount(500) +
htlcTimeoutFee(aliceChannel.channelState.LocalCommitment.FeePerKw))
htlcAmount := lnwire.NewMSatFromSatoshis(htlcSat)
htlc, preimage := 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 receive htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}
// At this point, Alice's commitment transaction should have an HTLC,
// while Bob's should not, because the value falls beneath his dust
// limit. The amount of the HTLC should be applied to fees in Bob's
// commitment transaction.
aliceCommitment := aliceChannel.localCommitChain.tip()
if len(aliceCommitment.txn.TxOut) != 3 {
t.Fatalf("incorrect # of outputs: expected %v, got %v",
3, len(aliceCommitment.txn.TxOut))
}
bobCommitment := bobChannel.localCommitChain.tip()
if len(bobCommitment.txn.TxOut) != 2 {
t.Fatalf("incorrect # of outputs: expected %v, got %v",
2, len(bobCommitment.txn.TxOut))
}
defaultFee := calcStaticFee(0)
if bobChannel.channelState.LocalCommitment.CommitFee != defaultFee {
t.Fatalf("dust htlc amount was subtracted from commitment fee "+
"expected %v, got %v", defaultFee,
bobChannel.channelState.LocalCommitment.CommitFee)
}
// Settle HTLC and create a new commitment state.
settleIndex, _, err := bobChannel.SettleHTLC(preimage)
if err != nil {
t.Fatalf("bob unable to settle inbound htlc: %v", err)
}
err = aliceChannel.ReceiveHTLCSettle(preimage, settleIndex)
if err != nil {
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
}
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("state transition error: %v", err)
}
// At this point, for Alice's commitment chains, the value of the HTLC
// should have been added to Alice's balance and TotalSatoshisSent.
commitment := aliceChannel.localCommitChain.tip()
if len(commitment.txn.TxOut) != 2 {
t.Fatalf("incorrect # of outputs: expected %v, got %v",
2, len(commitment.txn.TxOut))
}
if aliceChannel.channelState.TotalMSatSent != htlcAmount {
t.Fatalf("alice satoshis sent incorrect: expected %v, got %v",
htlcAmount, aliceChannel.channelState.TotalMSatSent)
}
}
// TestChannelBalanceDustLimit tests the condition when the remaining balance
// for one of the channel participants is so small as to be considered dust. In
// this case, the output for that participant is removed and all funds (minus
// fees) in the commitment transaction are allocated to the remaining channel
// participant.
//
// TODO(roasbeef): test needs to be fixed after reserve limits are done
func TestChannelBalanceDustLimit(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, cleanUp, err := createTestChannels(3)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// This amount should leave an amount larger than Alice's dust limit
// once fees have been subtracted, but smaller than Bob's dust limit.
// We account in fees for the HTLC we will be adding.
defaultFee := calcStaticFee(1)
aliceBalance := aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis()
htlcSat := aliceBalance - defaultFee
htlcSat += htlcSuccessFee(
aliceChannel.channelState.LocalCommitment.FeePerKw,
)
htlcAmount := lnwire.NewMSatFromSatoshis(htlcSat)
htlc, preimage := 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 receive htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("state transition error: %v", err)
}
settleIndex, _, err := bobChannel.SettleHTLC(preimage)
if err != nil {
t.Fatalf("bob unable to settle inbound htlc: %v", err)
}
err = aliceChannel.ReceiveHTLCSettle(preimage, settleIndex)
if err != nil {
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
}
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("state transition error: %v", err)
}
// At the conclusion of this test, in Bob's commitment chains, the
// output for Alice's balance should have been removed as dust, leaving
// only a single output that will send the remaining funds in the
// channel to Bob.
commitment := bobChannel.localCommitChain.tip()
if len(commitment.txn.TxOut) != 1 {
t.Fatalf("incorrect # of outputs: expected %v, got %v",
1, len(commitment.txn.TxOut))
}
if aliceChannel.channelState.TotalMSatSent != htlcAmount {
t.Fatalf("alice satoshis sent incorrect: expected %v, got %v",
htlcAmount, aliceChannel.channelState.TotalMSatSent)
}
}
func TestStateUpdatePersistence(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
const numHtlcs = 4
htlcAmt := lnwire.NewMSatFromSatoshis(5000)
// Alice adds 3 HTLCs to the update log, while Bob adds a single HTLC.
var alicePreimage [32]byte
copy(alicePreimage[:], bytes.Repeat([]byte{0xaa}, 32))
var bobPreimage [32]byte
copy(bobPreimage[:], bytes.Repeat([]byte{0xbb}, 32))
for i := 0; i < 3; i++ {
rHash := sha256.Sum256(alicePreimage[:])
h := &lnwire.UpdateAddHTLC{
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
}
if _, err := aliceChannel.AddHTLC(h); err != nil {
t.Fatalf("unable to add alice's htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(h); err != nil {
t.Fatalf("unable to recv alice's htlc: %v", err)
}
}
rHash := sha256.Sum256(bobPreimage[:])
bobh := &lnwire.UpdateAddHTLC{
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
}
if _, err := bobChannel.AddHTLC(bobh); err != nil {
t.Fatalf("unable to add bob's htlc: %v", err)
}
if _, err := aliceChannel.ReceiveHTLC(bobh); err != nil {
t.Fatalf("unable to recv bob's htlc: %v", err)
}
// Next, Alice initiates a state transition to include the HTLC's she
// added above in a new commitment state.
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("unable to complete alice's state transition: %v", err)
}
// Since the HTLC Bob sent wasn't included in Bob's version of the
// commitment transaction (but it was in Alice's, as he ACK'd her
// changes before creating a new state), Bob needs to trigger another
// state update in order to re-sync their states.
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to complete bob's state transition: %v", err)
}
// The latest commitment from both sides should have all the HTLCs.
numAliceOutgoing := aliceChannel.localCommitChain.tail().outgoingHTLCs
numAliceIncoming := aliceChannel.localCommitChain.tail().incomingHTLCs
if len(numAliceOutgoing) != 3 {
t.Fatalf("expected %v htlcs, instead got %v", 3, numAliceOutgoing)
}
if len(numAliceIncoming) != 1 {
t.Fatalf("expected %v htlcs, instead got %v", 1, numAliceIncoming)
}
numBobOutgoing := bobChannel.localCommitChain.tail().outgoingHTLCs
numBobIncoming := bobChannel.localCommitChain.tail().incomingHTLCs
if len(numBobOutgoing) != 1 {
t.Fatalf("expected %v htlcs, instead got %v", 1, numBobOutgoing)
}
if len(numBobIncoming) != 3 {
t.Fatalf("expected %v htlcs, instead got %v", 3, numBobIncoming)
}
// TODO(roasbeef): also ensure signatures were stored
// * ensure expiry matches
// Now fetch both of the channels created above from disk to simulate a
// node restart with persistence.
alicePub := aliceChannel.channelState.IdentityPub
aliceChannels, err := aliceChannel.channelState.Db.FetchOpenChannels(
alicePub,
)
if err != nil {
t.Fatalf("unable to fetch channel: %v", err)
}
bobPub := bobChannel.channelState.IdentityPub
bobChannels, err := bobChannel.channelState.Db.FetchOpenChannels(bobPub)
if err != nil {
t.Fatalf("unable to fetch channel: %v", err)
}
notifier := aliceChannel.channelEvents
aliceChannelNew, err := NewLightningChannel(aliceChannel.signer,
notifier, aliceChannel.feeEstimator, aliceChannels[0])
if err != nil {
t.Fatalf("unable to create new channel: %v", err)
}
bobChannelNew, err := NewLightningChannel(bobChannel.signer, notifier,
bobChannel.feeEstimator, bobChannels[0])
if err != nil {
t.Fatalf("unable to create new channel: %v", err)
}
// The state update logs of the new channels and the old channels
// should now be identical other than the height the HTLCs were added.
if aliceChannel.localUpdateLog.logIndex !=
aliceChannelNew.localUpdateLog.logIndex {
t.Fatalf("alice log counter: expected %v, got %v",
aliceChannel.localUpdateLog.logIndex,
aliceChannelNew.localUpdateLog.logIndex)
}
if aliceChannel.remoteUpdateLog.logIndex !=
aliceChannelNew.remoteUpdateLog.logIndex {
t.Fatalf("alice log counter: expected %v, got %v",
aliceChannel.remoteUpdateLog.logIndex,
aliceChannelNew.remoteUpdateLog.logIndex)
}
if aliceChannel.localUpdateLog.Len() !=
aliceChannelNew.localUpdateLog.Len() {
t.Fatalf("alice log len: expected %v, got %v",
aliceChannel.localUpdateLog.Len(),
aliceChannelNew.localUpdateLog.Len())
}
if aliceChannel.remoteUpdateLog.Len() !=
aliceChannelNew.remoteUpdateLog.Len() {
t.Fatalf("alice log len: expected %v, got %v",
aliceChannel.remoteUpdateLog.Len(),
aliceChannelNew.remoteUpdateLog.Len())
}
if bobChannel.localUpdateLog.logIndex !=
bobChannelNew.localUpdateLog.logIndex {
t.Fatalf("bob log counter: expected %v, got %v",
bobChannel.localUpdateLog.logIndex,
bobChannelNew.localUpdateLog.logIndex)
}
if bobChannel.remoteUpdateLog.logIndex !=
bobChannelNew.remoteUpdateLog.logIndex {
t.Fatalf("bob log counter: expected %v, got %v",
bobChannel.remoteUpdateLog.logIndex,
bobChannelNew.remoteUpdateLog.logIndex)
}
if bobChannel.localUpdateLog.Len() !=
bobChannelNew.localUpdateLog.Len() {
t.Fatalf("bob log len: expected %v, got %v",
bobChannel.localUpdateLog.Len(),
bobChannelNew.localUpdateLog.Len())
}
if bobChannel.remoteUpdateLog.Len() !=
bobChannelNew.remoteUpdateLog.Len() {
t.Fatalf("bob log len: expected %v, got %v",
bobChannel.remoteUpdateLog.Len(),
bobChannelNew.remoteUpdateLog.Len())
}
// TODO(roasbeef): expand test to also ensure state revocation log has
// proper pk scripts
// Newly generated pkScripts for HTLCs should be the same as in the old channel.
for _, entry := range aliceChannel.localUpdateLog.updateIndex {
htlc := entry.Value.(*PaymentDescriptor)
restoredHtlc := aliceChannelNew.localUpdateLog.lookupHtlc(htlc.HtlcIndex)
if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) {
t.Fatalf("alice ourPkScript in ourLog: expected %X, got %X",
htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5])
}
if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) {
t.Fatalf("alice theirPkScript in ourLog: expected %X, got %X",
htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5])
}
}
for _, entry := range aliceChannel.remoteUpdateLog.updateIndex {
htlc := entry.Value.(*PaymentDescriptor)
restoredHtlc := aliceChannelNew.remoteUpdateLog.lookupHtlc(htlc.HtlcIndex)
if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) {
t.Fatalf("alice ourPkScript in theirLog: expected %X, got %X",
htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5])
}
if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) {
t.Fatalf("alice theirPkScript in theirLog: expected %X, got %X",
htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5])
}
}
for _, entry := range bobChannel.localUpdateLog.updateIndex {
htlc := entry.Value.(*PaymentDescriptor)
restoredHtlc := bobChannelNew.localUpdateLog.lookupHtlc(htlc.HtlcIndex)
if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) {
t.Fatalf("bob ourPkScript in ourLog: expected %X, got %X",
htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5])
}
if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) {
t.Fatalf("bob theirPkScript in ourLog: expected %X, got %X",
htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5])
}
}
for _, entry := range bobChannel.remoteUpdateLog.updateIndex {
htlc := entry.Value.(*PaymentDescriptor)
restoredHtlc := bobChannelNew.remoteUpdateLog.lookupHtlc(htlc.HtlcIndex)
if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) {
t.Fatalf("bob ourPkScript in theirLog: expected %X, got %X",
htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5])
}
if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) {
t.Fatalf("bob theirPkScript in theirLog: expected %X, got %X",
htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5])
}
}
// Now settle all the HTLCs, then force a state update. The state
// update should succeed as both sides have identical.
for i := 0; i < 3; i++ {
settleIndex, _, err := bobChannelNew.SettleHTLC(alicePreimage)
if err != nil {
t.Fatalf("unable to settle htlc #%v: %v", i, err)
}
err = aliceChannelNew.ReceiveHTLCSettle(alicePreimage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc#%v: %v", i, err)
}
}
settleIndex, _, err := aliceChannelNew.SettleHTLC(bobPreimage)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = bobChannelNew.ReceiveHTLCSettle(bobPreimage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
// Similar to the two transitions above, as both Bob and Alice added
// entries to the update log before a state transition was initiated by
// either side, both sides are required to trigger an update in order
// to lock in their changes.
if err := forceStateTransition(aliceChannelNew, bobChannelNew); err != nil {
t.Fatalf("unable to update commitments: %v", err)
}
if err := forceStateTransition(bobChannelNew, aliceChannelNew); err != nil {
t.Fatalf("unable to update commitments: %v", err)
}
// The amounts transferred should been updated as per the amounts in
// the HTLCs
if aliceChannelNew.channelState.TotalMSatSent != htlcAmt*3 {
t.Fatalf("expected %v alice satoshis sent, got %v",
htlcAmt*3, aliceChannelNew.channelState.TotalMSatSent)
}
if aliceChannelNew.channelState.TotalMSatReceived != htlcAmt {
t.Fatalf("expected %v alice satoshis received, got %v",
htlcAmt, aliceChannelNew.channelState.TotalMSatReceived)
}
if bobChannelNew.channelState.TotalMSatSent != htlcAmt {
t.Fatalf("expected %v bob satoshis sent, got %v",
htlcAmt, bobChannel.channelState.TotalMSatSent)
}
if bobChannelNew.channelState.TotalMSatReceived != htlcAmt*3 {
t.Fatalf("expected %v bob satoshis sent, got %v",
htlcAmt*3, bobChannel.channelState.TotalMSatReceived)
}
// As a final test, we'll ensure that the HTLC counters for both sides
// has been persisted properly. If we instruct Alice to add a new HTLC,
// it should have an index of 3. If we instruct Bob to do the
// same, it should have an index of 1.
aliceHtlcIndex, err := aliceChannel.AddHTLC(bobh)
if err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if aliceHtlcIndex != 3 {
t.Fatalf("wrong htlc index: expected %v, got %v", 3, aliceHtlcIndex)
}
bobHtlcIndex, err := bobChannel.AddHTLC(bobh)
if err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if bobHtlcIndex != 1 {
t.Fatalf("wrong htlc index: expected %v, got %v", 1, aliceHtlcIndex)
}
}
func TestCancelHTLC(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, cleanUp, err := createTestChannels(5)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// Add a new HTLC from Alice to Bob, then trigger a new state
// transition in order to include it in the latest state.
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
var preImage [32]byte
copy(preImage[:], bytes.Repeat([]byte{0xaa}, 32))
htlc := &lnwire.UpdateAddHTLC{
PaymentHash: sha256.Sum256(preImage[:]),
Amount: htlcAmt,
Expiry: 10,
}
paymentHash := htlc.PaymentHash
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("unable to add alice htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("unable to add bob htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("unable to create new commitment state: %v", err)
}
// With the HTLC committed, Alice's balance should reflect the clearing
// of the new HTLC.
aliceExpectedBalance := btcutil.Amount(btcutil.SatoshiPerBitcoin*4) -
calcStaticFee(1)
if aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis() !=
aliceExpectedBalance {
t.Fatalf("Alice's balance is wrong: expected %v, got %v",
aliceExpectedBalance,
aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis())
}
// Now, with the HTLC committed on both sides, trigger a cancellation
// from Bob to Alice, removing the HTLC.
htlcCancelIndex, err := bobChannel.FailHTLC(paymentHash, []byte("failreason"))
if err != nil {
t.Fatalf("unable to cancel HTLC: %v", err)
}
if _, err := aliceChannel.ReceiveFailHTLC(htlcCancelIndex, []byte("bad")); err != nil {
t.Fatalf("unable to recv htlc cancel: %v", err)
}
// Now trigger another state transition, the HTLC should now be removed
// from both sides, with balances reflected.
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to create new commitment: %v", err)
}
// Now HTLCs should be present on the commitment transaction for either
// side.
if len(aliceChannel.localCommitChain.tip().outgoingHTLCs) != 0 ||
len(aliceChannel.remoteCommitChain.tip().outgoingHTLCs) != 0 {
t.Fatalf("htlc's still active from alice's POV")
}
if len(aliceChannel.localCommitChain.tip().incomingHTLCs) != 0 ||
len(aliceChannel.remoteCommitChain.tip().incomingHTLCs) != 0 {
t.Fatalf("htlc's still active from alice's POV")
}
if len(bobChannel.localCommitChain.tip().outgoingHTLCs) != 0 ||
len(bobChannel.remoteCommitChain.tip().outgoingHTLCs) != 0 {
t.Fatalf("htlc's still active from bob's POV")
}
if len(bobChannel.localCommitChain.tip().incomingHTLCs) != 0 ||
len(bobChannel.remoteCommitChain.tip().incomingHTLCs) != 0 {
t.Fatalf("htlc's still active from bob's POV")
}
expectedBalance := btcutil.Amount(btcutil.SatoshiPerBitcoin * 5)
if aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis() !=
expectedBalance-calcStaticFee(0) {
t.Fatalf("balance is wrong: expected %v, got %v",
aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis(),
expectedBalance-calcStaticFee(0))
}
if aliceChannel.channelState.LocalCommitment.RemoteBalance.ToSatoshis() !=
expectedBalance {
t.Fatalf("balance is wrong: expected %v, got %v",
aliceChannel.channelState.LocalCommitment.RemoteBalance.ToSatoshis(),
expectedBalance)
}
if bobChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis() !=
expectedBalance {
t.Fatalf("balance is wrong: expected %v, got %v",
bobChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis(),
expectedBalance)
}
if bobChannel.channelState.LocalCommitment.RemoteBalance.ToSatoshis() !=
expectedBalance-calcStaticFee(0) {
t.Fatalf("balance is wrong: expected %v, got %v",
bobChannel.channelState.LocalCommitment.RemoteBalance.ToSatoshis(),
expectedBalance-calcStaticFee(0))
}
}
func TestCooperativeCloseDustAdherence(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, cleanUp, err := createTestChannels(5)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
aliceFeeRate := uint64(aliceChannel.channelState.LocalCommitment.FeePerKw)
bobFeeRate := uint64(bobChannel.channelState.LocalCommitment.FeePerKw)
setDustLimit := func(dustVal btcutil.Amount) {
aliceChannel.channelState.LocalChanCfg.DustLimit = dustVal
aliceChannel.channelState.RemoteChanCfg.DustLimit = dustVal
bobChannel.channelState.LocalChanCfg.DustLimit = dustVal
bobChannel.channelState.RemoteChanCfg.DustLimit = dustVal
}
resetChannelState := func() {
aliceChannel.status = channelOpen
bobChannel.status = channelOpen
}
setBalances := func(aliceBalance, bobBalance lnwire.MilliSatoshi) {
aliceChannel.channelState.LocalCommitment.LocalBalance = aliceBalance
aliceChannel.channelState.LocalCommitment.RemoteBalance = bobBalance
bobChannel.channelState.LocalCommitment.LocalBalance = bobBalance
bobChannel.channelState.LocalCommitment.RemoteBalance = aliceBalance
}
aliceDeliveryScript := bobsPrivKey[:]
bobDeliveryScript := testHdSeed[:]
// We'll start be initializing the limit of both Alice and Bob to 10k
// satoshis.
dustLimit := btcutil.Amount(10000)
setDustLimit(dustLimit)
// Both sides currently have over 1 BTC settled as part of their
// balances. As a result, performing a cooperative closure now result
// in both sides having an output within the closure transaction.
aliceFee := aliceChannel.CalcFee(aliceFeeRate)
aliceSig, _, err := aliceChannel.CreateCloseProposal(aliceFee,
aliceDeliveryScript, bobDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobFee := bobChannel.CalcFee(bobFeeRate)
bobSig, _, err := bobChannel.CreateCloseProposal(bobFee,
bobDeliveryScript, aliceDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
closeTx, err := bobChannel.CompleteCooperativeClose(
bobCloseSig, aliceCloseSig,
bobDeliveryScript, aliceDeliveryScript, bobFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}
// The closure transaction should have exactly two outputs.
if len(closeTx.TxOut) != 2 {
t.Fatalf("close tx has wrong number of outputs: expected %v "+
"got %v", 2, len(closeTx.TxOut))
}
// We'll reset the channel states before proceeding to our nest test.
resetChannelState()
// Next we'll modify the current balances and dust limits such that
// Bob's current balance is above _below_ his dust limit.
aliceBal := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
bobBal := lnwire.NewMSatFromSatoshis(250)
setBalances(aliceBal, bobBal)
// Attempt another cooperative channel closure. It should succeed
// without any issues.
aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFee,
aliceDeliveryScript, bobDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, _, err = bobChannel.CreateCloseProposal(bobFee,
bobDeliveryScript, aliceDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(
bobCloseSig, aliceCloseSig,
bobDeliveryScript, aliceDeliveryScript, bobFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}
// The closure transaction should only have a single output, and that
// output should be Alice's balance.
if len(closeTx.TxOut) != 1 {
t.Fatalf("close tx has wrong number of outputs: expected %v "+
"got %v", 1, len(closeTx.TxOut))
}
if closeTx.TxOut[0].Value != int64(aliceBal.ToSatoshis()-calcStaticFee(0)) {
t.Fatalf("alice's balance is incorrect: expected %v, got %v",
int64(aliceBal.ToSatoshis()-calcStaticFee(0)),
closeTx.TxOut[0].Value)
}
// Finally, we'll modify the current balances and dust limits such that
// Alice's current balance is _below_ his her limit.
setBalances(bobBal, aliceBal)
resetChannelState()
// Our final attempt at another cooperative channel closure. It should
// succeed without any issues.
aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFee,
aliceDeliveryScript, bobDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, _, err = bobChannel.CreateCloseProposal(bobFee,
bobDeliveryScript, aliceDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(
bobCloseSig, aliceCloseSig,
bobDeliveryScript, aliceDeliveryScript, bobFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}
// The closure transaction should only have a single output, and that
// output should be Bob's balance.
if len(closeTx.TxOut) != 1 {
t.Fatalf("close tx has wrong number of outputs: expected %v "+
"got %v", 1, len(closeTx.TxOut))
}
if closeTx.TxOut[0].Value != int64(aliceBal.ToSatoshis()) {
t.Fatalf("bob's balance is incorrect: expected %v, got %v",
aliceBal.ToSatoshis(), closeTx.TxOut[0].Value)
}
}
// TestUpdateFeeFail tests that the signature verification will fail if they
// fee updates are out of sync.
func TestUpdateFeeFail(t *testing.T) {
t.Parallel()
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// Bob receives the update, that will apply to his commitment
// transaction.
bobChannel.ReceiveUpdateFee(111)
// Alice sends signature for commitment that does not cover any fee
// update.
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("alice unable to sign commitment: %v", err)
}
// Bob verifies this commit, meaning that he checks that it is
// consistent everything he has received. This should fail, since he got
// the fee update, but Alice never sent it.
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err == nil {
t.Fatalf("expected bob to fail receiving alice's signature")
}
}
// TestUpdateFeeSenderCommits veriefies that the state machine progresses as
// expected if we send a fee update, and then the sender of the fee update
// sends a commitment signature.
func TestUpdateFeeSenderCommits(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
paymentPreimage := bytes.Repeat([]byte{1}, 32)
paymentHash := sha256.Sum256(paymentPreimage)
htlc := &lnwire.UpdateAddHTLC{
PaymentHash: paymentHash,
Amount: btcutil.SatoshiPerBitcoin,
Expiry: uint32(5),
}
// First Alice adds the outgoing HTLC to her local channel's state
// update log. Then Alice sends this wire message over to Bob who
// adds this htlc to his remote state update log.
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("unable to recv htlc: %v", err)
}
// Simulate Alice sending update fee message to bob.
fee := btcutil.Amount(111)
aliceChannel.UpdateFee(fee)
bobChannel.ReceiveUpdateFee(fee)
// Alice signs a commitment, which will cover everything sent to Bob
// (the HTLC and the fee update), and everything acked by Bob (nothing
// so far).
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("alice unable to sign commitment: %v", err)
}
// Bob receives this signature message, and verifies that it is
// consistent with the state he had for Alice, including the received
// HTLC and fee update.
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("bob unable to process alice's new commitment: %v", err)
}
if bobChannel.channelState.LocalCommitment.FeePerKw == fee {
t.Fatalf("bob's feePerKw was unexpectedly locked in")
}
// Bob can revoke the prior commitment he had. This should lock in the
// fee update for him.
bobRevocation, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to generate bob revocation: %v", err)
}
if bobChannel.channelState.LocalCommitment.FeePerKw != fee {
t.Fatalf("bob's feePerKw was not locked in")
}
// Bob commits to all updates he has received from Alice. This includes
// the HTLC he received, and the fee update.
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("bob unable to sign alice's commitment: %v", err)
}
// Alice receives the revocation of the old one, and can now assume
// that Bob's received everything up to the signature she sent,
// including the HTLC and fee update.
if _, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
t.Fatalf("alice unable to rocess bob's revocation: %v", err)
}
// Alice receives new signature from Bob, and assumes this covers the
// changes.
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
if err != nil {
t.Fatalf("alice unable to process bob's new commitment: %v", err)
}
if aliceChannel.channelState.LocalCommitment.FeePerKw == fee {
t.Fatalf("alice's feePerKw was unexpectedly locked in")
}
// Alice can revoke the old commitment, which will lock in the fee
// update.
aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to revoke alice channel: %v", err)
}
if aliceChannel.channelState.LocalCommitment.FeePerKw != fee {
t.Fatalf("alice's feePerKw was not locked in")
}
// Bob receives revocation from Alice.
if _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("bob unable to process alice's revocation: %v", err)
}
}
// TestUpdateFeeReceiverCommits tests that the state machine progresses as
// expected if we send a fee update, and then the receiver of the fee update
// sends a commitment signature.
func TestUpdateFeeReceiverCommits(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
paymentPreimage := bytes.Repeat([]byte{1}, 32)
paymentHash := sha256.Sum256(paymentPreimage)
htlc := &lnwire.UpdateAddHTLC{
PaymentHash: paymentHash,
Amount: btcutil.SatoshiPerBitcoin,
Expiry: uint32(5),
}
// First Alice adds the outgoing HTLC to her local channel's state
// update log. Then Alice sends this wire message over to Bob who
// adds this htlc to his remote state update log.
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("unable to recv htlc: %v", err)
}
// Simulate Alice sending update fee message to bob
fee := btcutil.Amount(111)
aliceChannel.UpdateFee(fee)
bobChannel.ReceiveUpdateFee(fee)
// Bob commits to every change he has sent since last time (none). He
// does not commit to the received HTLC and fee update, since Alice
// cannot know if he has received them.
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("alice unable to sign commitment: %v", err)
}
// Alice receives this signature message, and verifies that it is
// consistent with the remote state, not including any of the updates.
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
if err != nil {
t.Fatalf("bob unable to process alice's new commitment: %v", err)
}
// Alice can revoke the prior commitment she had, this will ack
// everything received before last commitment signature, but in this
// case that is nothing.
aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to generate bob revocation: %v", err)
}
// Bob receives the revocation of the old commitment
if _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("alice unable to rocess bob's revocation: %v", err)
}
// Alice will sign next commitment. Since she sent the revocation, she
// also ack'ed everything received, but in this case this is nothing.
// Since she sent the two updates, this signature will cover those two.
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("bob unable to sign alice's commitment: %v", err)
}
// Bob gets the signature for the new commitment from Alice. He assumes
// this covers everything received from alice, including the two updates.
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("alice unable to process bob's new commitment: %v", err)
}
if bobChannel.channelState.LocalCommitment.FeePerKw == fee {
t.Fatalf("bob's feePerKw was unexpectedly locked in")
}
// Bob can revoke the old commitment. This will ack what he has
// received, including the HTLC and fee update. This will lock in the
// fee update for bob.
bobRevocation, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to revoke alice channel: %v", err)
}
if bobChannel.channelState.LocalCommitment.FeePerKw != fee {
t.Fatalf("bob's feePerKw was not locked in")
}
// Bob will send a new signature, which will cover what he just acked:
// the HTLC and fee update.
bobSig, bobHtlcSigs, err = bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("alice unable to sign commitment: %v", err)
}
// Alice receives revokation from Bob, and can now be sure that Bob
// received the two updates, and they are considered locked in.
if _, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
t.Fatalf("bob unable to process alice's revocation: %v", err)
}
// Alice will receive the signature from Bob, which will cover what was
// just acked by his revocation.
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
if err != nil {
t.Fatalf("alice unable to process bob's new commitment: %v", err)
}
if aliceChannel.channelState.LocalCommitment.FeePerKw == fee {
t.Fatalf("alice's feePerKw was unexpectedly locked in")
}
// After Alice now revokes her old commitment, the fee update should
// lock in.
aliceRevocation, err = aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to generate bob revocation: %v", err)
}
if aliceChannel.channelState.LocalCommitment.FeePerKw != fee {
t.Fatalf("Alice's feePerKw was not locked in")
}
// Bob receives revocation from Alice.
if _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("bob unable to process alice's revocation: %v", err)
}
}
// TestUpdateFeeReceiverSendsUpdate tests that receiving a fee update as channel
// initiator fails, and that trying to initiate fee update as non-initiation
// fails.
func TestUpdateFeeReceiverSendsUpdate(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// Since Alice is the channel initiator, she should fail when receiving
// fee update
fee := btcutil.Amount(111)
err = aliceChannel.ReceiveUpdateFee(fee)
if err == nil {
t.Fatalf("expected alice to fail receiving fee update")
}
// Similarly, initiating fee update should fail for Bob.
err = bobChannel.UpdateFee(fee)
if err == nil {
t.Fatalf("expected bob to fail initiating fee update")
}
}
// Test that if multiple update fee messages are sent consecutively, then the
// last one is the one that is being committed to.
func TestUpdateFeeMultipleUpdates(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// Simulate Alice sending update fee message to bob.
fee1 := btcutil.Amount(111)
fee2 := btcutil.Amount(222)
fee := btcutil.Amount(333)
aliceChannel.UpdateFee(fee1)
aliceChannel.UpdateFee(fee2)
aliceChannel.UpdateFee(fee)
// Alice signs a commitment, which will cover everything sent to Bob
// (the HTLC and the fee update), and everything acked by Bob (nothing
// so far).
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("alice unable to sign commitment: %v", err)
}
bobChannel.ReceiveUpdateFee(fee1)
bobChannel.ReceiveUpdateFee(fee2)
bobChannel.ReceiveUpdateFee(fee)
// Bob receives this signature message, and verifies that it is
// consistent with the state he had for Alice, including the received
// HTLC and fee update.
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("bob unable to process alice's new commitment: %v", err)
}
if bobChannel.channelState.LocalCommitment.FeePerKw == fee {
t.Fatalf("bob's feePerKw was unexpectedly locked in")
}
// Alice sending more fee updates now should not mess up the old fee
// they both committed to.
fee3 := btcutil.Amount(444)
fee4 := btcutil.Amount(555)
fee5 := btcutil.Amount(666)
aliceChannel.UpdateFee(fee3)
aliceChannel.UpdateFee(fee4)
aliceChannel.UpdateFee(fee5)
bobChannel.ReceiveUpdateFee(fee3)
bobChannel.ReceiveUpdateFee(fee4)
bobChannel.ReceiveUpdateFee(fee5)
// Bob can revoke the prior commitment he had. This should lock in the
// fee update for him.
bobRevocation, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to generate bob revocation: %v", err)
}
if bobChannel.channelState.LocalCommitment.FeePerKw != fee {
t.Fatalf("bob's feePerKw was not locked in")
}
// Bob commits to all updates he has received from Alice. This includes
// the HTLC he received, and the fee update.
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("bob unable to sign alice's commitment: %v", err)
}
// Alice receives the revocation of the old one, and can now assume that
// Bob's received everything up to the signature she sent, including the
// HTLC and fee update.
if _, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
t.Fatalf("alice unable to rocess bob's revocation: %v", err)
}
// Alice receives new signature from Bob, and assumes this covers the
// changes.
if err := aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil {
t.Fatalf("alice unable to process bob's new commitment: %v", err)
}
if aliceChannel.channelState.LocalCommitment.FeePerKw == fee {
t.Fatalf("alice's feePerKw was unexpectedly locked in")
}
// Alice can revoke the old commitment, which will lock in the fee
// update.
aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to revoke alice channel: %v", err)
}
if aliceChannel.channelState.LocalCommitment.FeePerKw != fee {
t.Fatalf("alice's feePerKw was not locked in")
}
// Bob receives revocation from Alice.
if _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("bob unable to process alice's revocation: %v", err)
}
}
// TestAddHTLCNegativeBalance tests that if enough HTLC's are added to the
// state machine to drive the balance to zero, then the next HTLC attempted to
// be added will result in an error being returned.
func TestAddHTLCNegativeBalance(t *testing.T) {
t.Parallel()
// We'll kick off the test by creating our channels which both are
// loaded with 5 BTC each.
aliceChannel, _, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// First, we'll add 5 HTLCs of 1 BTC each to Alice's commitment.
const numHTLCs = 4
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
for i := 0; i < numHTLCs; i++ {
htlc, _ := createHTLC(i, htlcAmt)
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
}
// We'll then craft another HTLC with 2 BTC to add to Alice's channel.
// This attempt should put Alice in the negative, meaning she should
// reject the HTLC.
htlc, _ := createHTLC(numHTLCs+1, htlcAmt*2)
_, err = aliceChannel.AddHTLC(htlc)
if err != ErrInsufficientBalance {
t.Fatalf("expected insufficient balance, instead got: %v", err)
}
}
// assertNoChanSyncNeeded is a helper function that asserts that upon restart,
// two channels conclude that they're fully synchronized and don't need to
// retransmit any new messages.
func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel,
bobChannel *LightningChannel) {
aliceChanSyncMsg := aliceChannel.ChanSyncMsg()
bobMsgsToSend, err := bobChannel.ProcessChanSyncMsg(aliceChanSyncMsg)
if err != nil {
t.Fatalf("unable to process ChannelReestablish msg: %v", err)
}
if len(bobMsgsToSend) != 0 {
t.Fatalf("bob shouldn't have to send any messages, instead wants "+
"to send: %v", spew.Sdump(bobMsgsToSend))
}
bobChanSyncMsg := bobChannel.ChanSyncMsg()
aliceMsgsToSend, err := aliceChannel.ProcessChanSyncMsg(bobChanSyncMsg)
if err != nil {
t.Fatalf("unable to process ChannelReestablish msg: %v", err)
}
if len(bobMsgsToSend) != 0 {
t.Fatalf("alice shouldn't have to send any messages, instead wants "+
"to send: %v", spew.Sdump(aliceMsgsToSend))
}
}
// TestChanSyncFullySynced tests that after a successful commitment exchange,
// and a forced restart, both nodes conclude that they're fully synchronized
// and don't need to retransmit any messages.
func TestChanSyncFullySynced(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// Next, we'll create an HTLC for Alice to extend to Bob.
var paymentPreimage [32]byte
copy(paymentPreimage[:], bytes.Repeat([]byte{1}, 32))
paymentHash := sha256.Sum256(paymentPreimage[:])
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
htlc := &lnwire.UpdateAddHTLC{
PaymentHash: paymentHash,
Amount: htlcAmt,
Expiry: uint32(5),
}
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("unable to recv htlc: %v", err)
}
// Then we'll initiate a state transition to lock in this new HTLC.
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("unable to complete alice's state transition: %v", err)
}
// At this point, if both sides generate a ChannelReestablish message,
// they should both conclude that they're fully in sync.
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
// If bob settles the HTLC, and then initiates a state transition, they
// should both still think that they're in sync.
settleIndex, _, err := bobChannel.SettleHTLC(paymentPreimage)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = aliceChannel.ReceiveHTLCSettle(paymentPreimage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
// Next, we'll complete Bob's state transition, and assert again that
// they think they're fully synced.
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to complete bob's state transition: %v", err)
}
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
// Finally, if we simulate a restart on both sides, then both should
// still conclude that they don't need to synchronize their state.
alicePub := aliceChannel.channelState.IdentityPub
aliceChannels, err := aliceChannel.channelState.Db.FetchOpenChannels(
alicePub,
)
if err != nil {
t.Fatalf("unable to fetch channel: %v", err)
}
bobPub := bobChannel.channelState.IdentityPub
bobChannels, err := bobChannel.channelState.Db.FetchOpenChannels(bobPub)
if err != nil {
t.Fatalf("unable to fetch channel: %v", err)
}
notifier := aliceChannel.channelEvents
aliceChannelNew, err := NewLightningChannel(aliceChannel.signer,
notifier, aliceChannel.feeEstimator, aliceChannels[0])
if err != nil {
t.Fatalf("unable to create new channel: %v", err)
}
defer aliceChannelNew.Stop()
bobChannelNew, err := NewLightningChannel(bobChannel.signer, notifier,
bobChannel.feeEstimator, bobChannels[0])
if err != nil {
t.Fatalf("unable to create new channel: %v", err)
}
defer bobChannelNew.Stop()
assertNoChanSyncNeeded(t, aliceChannelNew, bobChannelNew)
}
// restartChannel...
func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
nodePub := channelOld.channelState.IdentityPub
nodeChannels, err := channelOld.channelState.Db.FetchOpenChannels(
nodePub,
)
if err != nil {
return nil, err
}
notifier := channelOld.channelEvents
channelNew, err := NewLightningChannel(channelOld.signer,
notifier, channelOld.feeEstimator, nodeChannels[0])
if err != nil {
return nil, err
}
return channelNew, nil
}
// TestChanSyncOweCommitment tests that if Bob restarts (and then Alice) before
// he receives Alice's CommitSig message, then Alice concludes that she needs
// to re-send the CommitDiff. After the diff has been sent, both nodes should
// resynchronize and be able to complete the dangling commit.
func TestChanSyncOweCommitment(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// We'll start off the scenario with Bob sending 3 HTLC's to Alice in a
// single state update.
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
const numBobHtlcs = 3
var bobPreimage [32]byte
copy(bobPreimage[:], bytes.Repeat([]byte{0xbb}, 32))
for i := 0; i < 3; i++ {
rHash := sha256.Sum256(bobPreimage[:])
h := &lnwire.UpdateAddHTLC{
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
}
if _, err := bobChannel.AddHTLC(h); err != nil {
t.Fatalf("unable to add bob's htlc: %v", err)
}
if _, err := aliceChannel.ReceiveHTLC(h); err != nil {
t.Fatalf("unable to recv bob's htlc: %v", err)
}
}
chanID := lnwire.NewChanIDFromOutPoint(
&aliceChannel.channelState.FundingOutpoint,
)
// With the HTLC's applied to both update logs, we'll initiate a state
// transition from Bob.
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to complete bob's state transition: %v", err)
}
// Next, Alice's settles all 3 HTLC's from Bob, and also adds a new
// HTLC of her own.
for i := 0; i < 3; i++ {
settleIndex, _, err := aliceChannel.SettleHTLC(bobPreimage)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = bobChannel.ReceiveHTLCSettle(bobPreimage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
}
var alicePreimage [32]byte
copy(alicePreimage[:], bytes.Repeat([]byte{0xaa}, 32))
rHash := sha256.Sum256(alicePreimage[:])
aliceHtlc := &lnwire.UpdateAddHTLC{
ChanID: chanID,
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
}
if _, err := aliceChannel.AddHTLC(aliceHtlc); err != nil {
t.Fatalf("unable to add alice's htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(aliceHtlc); err != nil {
t.Fatalf("unable to recv alice's htlc: %v", err)
}
// Now we'll begin the core of the test itself. Alice will extend a new
// commitment to Bob, but the connection drops before Bob can process
// it.
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("unable to sign commitment: %v", err)
}
// Bob doesn't get this message so upon reconnection, they need to
// synchronize. Alice should conclude that she owes Bob a commitment,
// while Bob should think he's properly synchronized.
aliceSyncMsg := aliceChannel.ChanSyncMsg()
bobSyncMsg := bobChannel.ChanSyncMsg()
// This is a helper function that asserts Alice concludes that she
// needs to retransmit the exact commitment that we failed to send
// above.
assertAliceCommitRetransmit := func() {
aliceMsgsToSend, err := aliceChannel.ProcessChanSyncMsg(
bobSyncMsg,
)
if err != nil {
t.Fatalf("unable to process chan sync msg: %v", err)
}
if len(aliceMsgsToSend) != 5 {
t.Fatalf("expected alice to send %v messages instead "+
"will send %v: %v", 5, len(aliceMsgsToSend),
spew.Sdump(aliceMsgsToSend))
}
// Each of the settle messages that Alice sent should match her
// original intent.
for i := 0; i < 3; i++ {
settleMsg, ok := aliceMsgsToSend[i].(*lnwire.UpdateFufillHTLC)
if !ok {
t.Fatalf("expected a htlc settle message, "+
"instead have %v", spew.Sdump(settleMsg))
}
if settleMsg.ID != uint64(i) {
t.Fatalf("wrong ID in settle msg: expected %v, "+
"got %v", i, settleMsg.ID)
}
if settleMsg.ChanID != chanID {
t.Fatalf("incorrect chan id: expected %v, got %v",
chanID, settleMsg.ChanID)
}
if settleMsg.PaymentPreimage != bobPreimage {
t.Fatalf("wrong pre-image: expected %v, got %v",
alicePreimage, settleMsg.PaymentPreimage)
}
}
// The HTLC add message should be identical.
if _, ok := aliceMsgsToSend[3].(*lnwire.UpdateAddHTLC); !ok {
t.Fatalf("expected a htlc add message, instead have %v",
spew.Sdump(aliceMsgsToSend[3]))
}
if !reflect.DeepEqual(aliceHtlc, aliceMsgsToSend[3]) {
t.Fatalf("htlc msg doesn't match exactly: "+
"expected %v got %v", spew.Sdump(aliceHtlc),
spew.Sdump(aliceMsgsToSend[3]))
}
// Next, we'll ensure that the CommitSig message exactly
// matches what Alice originally intended to send.
commitSigMsg, ok := aliceMsgsToSend[4].(*lnwire.CommitSig)
if !ok {
t.Fatalf("expected a CommitSig message, instead have %v",
spew.Sdump(aliceMsgsToSend[4]))
}
if !commitSigMsg.CommitSig.IsEqual(aliceSig) {
t.Fatalf("commit sig msgs don't match: expected %x got %x",
aliceSig.Serialize(), commitSigMsg.CommitSig.Serialize())
}
if len(commitSigMsg.HtlcSigs) != len(aliceHtlcSigs) {
t.Fatalf("wrong number of htlc sigs: expected %v, got %v",
len(aliceHtlcSigs), len(commitSigMsg.HtlcSigs))
}
for i, htlcSig := range commitSigMsg.HtlcSigs {
if !htlcSig.IsEqual(aliceHtlcSigs[i]) {
t.Fatalf("htlc sig msgs don't match: "+
"expected %x got %x",
aliceHtlcSigs[i].Serialize(),
htlcSig.Serialize())
}
}
}
// Alice should detect that she needs to re-send 5 messages: the 3
// settles, her HTLC add, and finally her commit sig message.
assertAliceCommitRetransmit()
// From Bob's Pov he has nothing else to send, so he should conclude he
// has no further action remaining.
bobMsgsToSend, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
if err != nil {
t.Fatalf("unable to process chan sync msg: %v", err)
}
if len(bobMsgsToSend) != 0 {
t.Fatalf("expected bob to send %v messages instead will "+
"send %v: %v", 5, len(bobMsgsToSend),
spew.Sdump(bobMsgsToSend))
}
// If we restart Alice, she should still conclude that she needs to
// send the exact same set of messages.
aliceChannel, err = restartChannel(aliceChannel)
if err != nil {
t.Fatalf("unable to restart alice: %v", err)
}
defer aliceChannel.Stop()
assertAliceCommitRetransmit()
// TODO(roasbeef): restart bob as well???
// At this point, we should be able to resume the prior state update
// without any issues, resulting in Alice settling the 3 htlc's, and
// adding one of her own.
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("bob unable to process alice's commitment: %v", err)
}
bobRevocation, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to revoke bob commitment: %v", err)
}
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("bob unable to sign commitment: %v", err)
}
_, err = aliceChannel.ReceiveRevocation(bobRevocation)
if err != nil {
t.Fatalf("alice unable to recv revocation: %v", err)
}
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
if err != nil {
t.Fatalf("alice unable to rev bob's commitment: %v", err)
}
aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("alice unable to revoke commitment: %v", err)
}
if _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("bob unable to recv revocation: %v", err)
}
// At this point, we'll now assert that their log states are what we
// expect.
//
// Alice's local log counter should be 4 and her HTLC index 3. She
// should detect Bob's remote log counter as being 3 and his HTLC index
// 3 as well.
if aliceChannel.localUpdateLog.logIndex != 4 {
t.Fatalf("incorrect log index: expected %v, got %v", 4,
aliceChannel.localUpdateLog.logIndex)
}
if aliceChannel.localUpdateLog.htlcCounter != 1 {
t.Fatalf("incorrect htlc index: expected %v, got %v", 1,
aliceChannel.localUpdateLog.htlcCounter)
}
if aliceChannel.remoteUpdateLog.logIndex != 3 {
t.Fatalf("incorrect log index: expected %v, got %v", 3,
aliceChannel.localUpdateLog.logIndex)
}
if aliceChannel.remoteUpdateLog.htlcCounter != 3 {
t.Fatalf("incorrect htlc index: expected %v, got %v", 3,
aliceChannel.localUpdateLog.htlcCounter)
}
// Bob should also have the same state, but mirrored.
if bobChannel.localUpdateLog.logIndex != 3 {
t.Fatalf("incorrect log index: expected %v, got %v", 3,
bobChannel.localUpdateLog.logIndex)
}
if bobChannel.localUpdateLog.htlcCounter != 3 {
t.Fatalf("incorrect htlc index: expected %v, got %v", 3,
bobChannel.localUpdateLog.htlcCounter)
}
if bobChannel.remoteUpdateLog.logIndex != 4 {
t.Fatalf("incorrect log index: expected %v, got %v", 4,
bobChannel.localUpdateLog.logIndex)
}
if bobChannel.remoteUpdateLog.htlcCounter != 1 {
t.Fatalf("incorrect htlc index: expected %v, got %v", 1,
bobChannel.localUpdateLog.htlcCounter)
}
// We'll conclude the test by having Bob settle Alice's HTLC, then
// initiate a state transition.
settleIndex, _, err := bobChannel.SettleHTLC(alicePreimage)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = aliceChannel.ReceiveHTLCSettle(alicePreimage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to complete bob's state transition: %v", err)
}
// At this point, the final balances of both parties should properly
// reflect
bobMsatSent := numBobHtlcs * htlcAmt
if aliceChannel.channelState.TotalMSatSent != htlcAmt {
t.Fatalf("wrong value for msat sent: expected %v, got %v",
htlcAmt, aliceChannel.channelState.TotalMSatSent)
}
if aliceChannel.channelState.TotalMSatReceived != bobMsatSent {
t.Fatalf("wrong value for msat recv: expected %v, got %v",
bobMsatSent, aliceChannel.channelState.TotalMSatReceived)
}
if bobChannel.channelState.TotalMSatSent != bobMsatSent {
t.Fatalf("wrong value for msat sent: expected %v, got %v",
bobMsatSent, bobChannel.channelState.TotalMSatSent)
}
if bobChannel.channelState.TotalMSatReceived != htlcAmt {
t.Fatalf("wrong value for msat recv: expected %v, got %v",
htlcAmt, bobChannel.channelState.TotalMSatReceived)
}
}
// TestChanSyncOweRevocation tests that if Bob restarts (and then Alice) before
// he receiver's Alice's RevokeAndAck message, then Alice concludes that she
// needs to re-send the RevokeAndAck. After the revocation has been sent, both
// nodes should be able to successfully complete another state transition.
func TestChanSyncOweRevocation(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
chanID := lnwire.NewChanIDFromOutPoint(
&aliceChannel.channelState.FundingOutpoint,
)
// We'll start the test with Bob extending a single HTLC to Alice, and
// then initiating a state transition.
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
var bobPreimage [32]byte
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
rHash := sha256.Sum256(bobPreimage[:])
bobHtlc := &lnwire.UpdateAddHTLC{
ChanID: chanID,
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
}
if _, err := bobChannel.AddHTLC(bobHtlc); err != nil {
t.Fatalf("unable to add bob's htlc: %v", err)
}
if _, err := aliceChannel.ReceiveHTLC(bobHtlc); err != nil {
t.Fatalf("unable to recv bob's htlc: %v", err)
}
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to complete bob's state transition: %v", err)
}
// Next, Alice will settle that single HTLC, the _begin_ the start of a
// state transition.
settleIndex, _, err := aliceChannel.SettleHTLC(bobPreimage)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = bobChannel.ReceiveHTLCSettle(bobPreimage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
// We'll model the state transition right up until Alice needs to send
// her revocation message to complete the state transition.
//
// Alice signs the next state, then Bob receives and sends his
// revocation message.
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("unable to sign commitment: %v", err)
}
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("bob unable to process alice's commitment: %v", err)
}
bobRevocation, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to revoke bob commitment: %v", err)
}
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("bob unable to sign commitment: %v", err)
}
_, err = aliceChannel.ReceiveRevocation(bobRevocation)
if err != nil {
t.Fatalf("alice unable to recv revocation: %v", err)
}
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
if err != nil {
t.Fatalf("alice unable to rev bob's commitment: %v", err)
}
// At this point, we'll simulate the connection breaking down by Bob's
// lack of knowledge of the revocation message that Alice just sent.
aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("alice unable to revoke commitment: %v", err)
}
// If we fetch the channel sync messages at this state, then Alice
// should report that she owes Bob a revocation message, while Bob
// thinks they're fully in sync.
aliceSyncMsg := aliceChannel.ChanSyncMsg()
bobSyncMsg := bobChannel.ChanSyncMsg()
assertAliceOwesRevoke := func() {
aliceMsgsToSend, err := aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
if err != nil {
t.Fatalf("unable to process chan sync msg: %v", err)
}
if len(aliceMsgsToSend) != 1 {
t.Fatalf("expected single message retransmission from Alice, "+
"instead got %v", spew.Sdump(aliceMsgsToSend))
}
aliceReRevoke, ok := aliceMsgsToSend[0].(*lnwire.RevokeAndAck)
if !ok {
t.Fatalf("expected to retransmit revocation msg, instead "+
"have: %v", spew.Sdump(aliceMsgsToSend[0]))
}
// Alice should re-send the revocation message for her prior
// state.
expectedRevocation, err := aliceChannel.generateRevocation(
aliceChannel.currentHeight - 1,
)
if err != nil {
t.Fatalf("unable to regenerate revocation: %v", err)
}
if !reflect.DeepEqual(expectedRevocation, aliceReRevoke) {
t.Fatalf("wrong re-revocation: expected %v, got %v",
expectedRevocation, aliceReRevoke)
}
}
// From Bob's PoV he shouldn't think that he owes Alice any messages.
bobMsgsToSend, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
if err != nil {
t.Fatalf("unable to process chan sync msg: %v", err)
}
if len(bobMsgsToSend) != 0 {
t.Fatalf("expected bob to not retransmit, instead has: %v",
spew.Sdump(bobMsgsToSend))
}
// Alice should detect that she owes Bob a revocation message, and only
// that single message.
assertAliceOwesRevoke()
// If we restart Alice, then she should still decide that she owes a
// revocation message to Bob.
aliceChannel, err = restartChannel(aliceChannel)
if err != nil {
t.Fatalf("unable to restart alice: %v", err)
}
defer aliceChannel.Stop()
assertAliceOwesRevoke()
// TODO(roasbeef): restart bob too???
// We'll continue by then allowing bob to process Alice's revocation message.
if _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("bob unable to recv revocation: %v", err)
}
// Finally, Alice will add an HTLC over her own such that we assert the
// channel can continue to receive updates.
var alicePreimage [32]byte
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
rHash = sha256.Sum256(alicePreimage[:])
aliceHtlc := &lnwire.UpdateAddHTLC{
ChanID: chanID,
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
}
if _, err := aliceChannel.AddHTLC(aliceHtlc); err != nil {
t.Fatalf("unable to add alice's htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(aliceHtlc); err != nil {
t.Fatalf("unable to recv alice's htlc: %v", err)
}
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
t.Fatalf("unable to complete alice's state transition: %v", err)
}
// At this point, both sides should detect that they're fully synced.
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
}
// TestChanSyncOweRevocationAndCommit tests that if Alice initiates a state
// transition with Bob and Bob sends both a RevokeAndAck and CommitSig message
// but Alice doesn't receive them before the connection dies, then he'll
// retransmit them both.
func TestChanSyncOweRevocationAndCommit(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
// We'll kick off the test by having Bob send Alice an HTLC, then lock
// it in with a state transition.
var bobPreimage [32]byte
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
rHash := sha256.Sum256(bobPreimage[:])
bobHtlc := &lnwire.UpdateAddHTLC{
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
}
if _, err := bobChannel.AddHTLC(bobHtlc); err != nil {
t.Fatalf("unable to add bob's htlc: %v", err)
}
if _, err := aliceChannel.ReceiveHTLC(bobHtlc); err != nil {
t.Fatalf("unable to recv bob's htlc: %v", err)
}
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to complete bob's state transition: %v", err)
}
// Next, Alice will settle that incoming HTLC, then we'll start the
// core of the test itself.
settleIndex, _, err := aliceChannel.SettleHTLC(bobPreimage)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = bobChannel.ReceiveHTLCSettle(bobPreimage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
// Progressing the exchange: Alice will send her signature, Bob will
// receive, send a revocation and also a signature for Alice's state.
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("unable to sign commitment: %v", err)
}
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("bob unable to process alice's commitment: %v", err)
}
// Bob generates the revoke and sig message, but the messages don't
// reach Alice before the connection dies.
bobRevocation, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to revoke bob commitment: %v", err)
}
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
if err != nil {
t.Fatalf("bob unable to sign commitment: %v", err)
}
// If we now attempt to resync, then Alice should conclude that she
// doesn't need any further updates, while Bob concludes that he needs
// to re-send both his revocation and commit sig message.
aliceSyncMsg := aliceChannel.ChanSyncMsg()
bobSyncMsg := bobChannel.ChanSyncMsg()
aliceMsgsToSend, err := aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
if err != nil {
t.Fatalf("unable to process chan sync msg: %v", err)
}
if len(aliceMsgsToSend) != 0 {
t.Fatalf("expected alice to not retransmit, instead she's "+
"sending: %v", spew.Sdump(aliceMsgsToSend))
}
assertBobSendsRevokeAndCommit := func() {
bobMsgsToSend, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
if err != nil {
t.Fatalf("unable to process chan sync msg: %v", err)
}
if len(bobMsgsToSend) != 2 {
t.Fatalf("expected bob to send %v messages, instead "+
"sends: %v", 2, spew.Sdump(bobMsgsToSend))
}
bobReRevoke, ok := bobMsgsToSend[0].(*lnwire.RevokeAndAck)
if !ok {
t.Fatalf("expected bob to re-send revoke, instead sending: %v",
spew.Sdump(bobMsgsToSend[0]))
}
if !reflect.DeepEqual(bobReRevoke, bobRevocation) {
t.Fatalf("revocation msgs don't match: expected %v, got %v",
bobRevocation, bobReRevoke)
}
bobReCommitSigMsg, ok := bobMsgsToSend[1].(*lnwire.CommitSig)
if !ok {
t.Fatalf("expected bob to re-send commit sig, instead sending: %v",
spew.Sdump(bobMsgsToSend[1]))
}
if !bobReCommitSigMsg.CommitSig.IsEqual(bobSig) {
t.Fatalf("commit sig msgs don't match: expected %x got %x",
bobSig.Serialize(), bobReCommitSigMsg.CommitSig.Serialize())
}
if len(bobReCommitSigMsg.HtlcSigs) != len(bobHtlcSigs) {
t.Fatalf("wrong number of htlc sigs: expected %v, got %v",
len(bobHtlcSigs), len(bobReCommitSigMsg.HtlcSigs))
}
for i, htlcSig := range bobReCommitSigMsg.HtlcSigs {
if !htlcSig.IsEqual(aliceHtlcSigs[i]) {
t.Fatalf("htlc sig msgs don't match: "+
"expected %x got %x",
bobHtlcSigs[i].Serialize(),
htlcSig.Serialize())
}
}
}
// We expect Bob to send exactly two messages: first his revocation
// message to Alice, and second his original commit sig message.
assertBobSendsRevokeAndCommit()
// At this point we simulate the connection failing with a restart from
// Bob. He should still re-send the exact same set of messages.
bobChannel, err = restartChannel(bobChannel)
if err != nil {
t.Fatalf("unable to restart channel: %v", err)
}
assertBobSendsRevokeAndCommit()
// We'll now finish the state transition by having Alice process both
// messages, and send her final revocation.
_, err = aliceChannel.ReceiveRevocation(bobRevocation)
if err != nil {
t.Fatalf("alice unable to recv revocation: %v", err)
}
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
if err != nil {
t.Fatalf("alice unable to rev bob's commitment: %v", err)
}
aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("alice unable to revoke commitment: %v", err)
}
if _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("bob unable to recv revocation: %v", err)
}
}
// TestChanSyncOweRevocationAndCommitForceTransition tests that if Alice
// initiates a state transition with Bob, but Alice fails to receive his
// RevokeAndAck and the connection dies before Bob sends his CommitSig message,
// then Bob will re-send her RevokeAndAck message. Bob will also send and
// _identical_ CommitSig as he detects his commitment chain is ahead of
// Alice's.
func TestChanSyncOweRevocationAndCommitForceTransition(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
// We'll kick off the test by having Bob send Alice an HTLC, then lock
// it in with a state transition.
var bobPreimage [32]byte
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
rHash := sha256.Sum256(bobPreimage[:])
bobHtlc := &lnwire.UpdateAddHTLC{
PaymentHash: rHash,
Amount: htlcAmt,
Expiry: uint32(10),
}
if _, err := bobChannel.AddHTLC(bobHtlc); err != nil {
t.Fatalf("unable to add bob's htlc: %v", err)
}
if _, err := aliceChannel.ReceiveHTLC(bobHtlc); err != nil {
t.Fatalf("unable to recv bob's htlc: %v", err)
}
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
t.Fatalf("unable to complete bob's state transition: %v", err)
}
// Next, Alice will settle that incoming HTLC, then we'll start the
// core of the test itself.
settleIndex, _, err := aliceChannel.SettleHTLC(bobPreimage)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = bobChannel.ReceiveHTLCSettle(bobPreimage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
// Progressing the exchange: Alice will send her signature, with Bob
// processing the new state locally.
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
if err != nil {
t.Fatalf("unable to sign commitment: %v", err)
}
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
if err != nil {
t.Fatalf("bob unable to process alice's commitment: %v", err)
}
// Bob then sends his revocation message, but before Alice can process
// it (and before he scan send his CommitSig message), then connection
// dies.
bobRevocation, err := bobChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("unable to revoke bob commitment: %v", err)
}
// Now if we attempt to synchronize states at this point, Alice should
// detect that she owes nothing, while Bob should re-send both his
// RevokeAndAck as well as his commitment message.
aliceSyncMsg := aliceChannel.ChanSyncMsg()
bobSyncMsg := bobChannel.ChanSyncMsg()
aliceMsgsToSend, err := aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
if err != nil {
t.Fatalf("unable to process chan sync msg: %v", err)
}
if len(aliceMsgsToSend) != 0 {
t.Fatalf("expected alice to not retransmit, instead she's "+
"sending: %v", spew.Sdump(aliceMsgsToSend))
}
// If we process Alice's sync message from Bob's PoV, then he should
// send his RevokeAndAck message again. Additionally, the CommitSig
// message that he sends should be sufficient to finalize the state
// transition.
bobMsgsToSend, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
if err != nil {
t.Fatalf("unable to process chan sync msg: %v", err)
}
if len(bobMsgsToSend) != 2 {
t.Fatalf("expected bob to send %v messages, instead "+
"sends: %v", 2, spew.Sdump(bobMsgsToSend))
}
bobReRevoke, ok := bobMsgsToSend[0].(*lnwire.RevokeAndAck)
if !ok {
t.Fatalf("expected bob to re-send revoke, instead sending: %v",
spew.Sdump(bobMsgsToSend[0]))
}
if !reflect.DeepEqual(bobReRevoke, bobRevocation) {
t.Fatalf("revocation msgs don't match: expected %v, got %v",
bobRevocation, bobReRevoke)
}
// The second message should be his CommitSig message that he never
// sent, but will send in order to force both states to synchronize.
bobReCommitSigMsg, ok := bobMsgsToSend[1].(*lnwire.CommitSig)
if !ok {
t.Fatalf("expected bob to re-send commit sig, instead sending: %v",
spew.Sdump(bobMsgsToSend[1]))
}
// At this point we simulate the connection failing with a restart from
// Bob. He should still re-send the exact same set of messages.
bobChannel, err = restartChannel(bobChannel)
if err != nil {
t.Fatalf("unable to restart channel: %v", err)
}
if len(bobMsgsToSend) != 2 {
t.Fatalf("expected bob to send %v messages, instead "+
"sends: %v", 2, spew.Sdump(bobMsgsToSend))
}
bobReRevoke, ok = bobMsgsToSend[0].(*lnwire.RevokeAndAck)
if !ok {
t.Fatalf("expected bob to re-send revoke, instead sending: %v",
spew.Sdump(bobMsgsToSend[0]))
}
bobSigMsg, ok := bobMsgsToSend[1].(*lnwire.CommitSig)
if !ok {
t.Fatalf("expected bob to re-send commit sig, instead sending: %v",
spew.Sdump(bobMsgsToSend[1]))
}
if !reflect.DeepEqual(bobReRevoke, bobRevocation) {
t.Fatalf("revocation msgs don't match: expected %v, got %v",
bobRevocation, bobReRevoke)
}
if !bobReCommitSigMsg.CommitSig.IsEqual(bobSigMsg.CommitSig) {
t.Fatalf("commit sig msgs don't match: expected %x got %x",
bobSigMsg.CommitSig.Serialize(),
bobReCommitSigMsg.CommitSig.Serialize())
}
if len(bobReCommitSigMsg.HtlcSigs) != len(bobSigMsg.HtlcSigs) {
t.Fatalf("wrong number of htlc sigs: expected %v, got %v",
len(bobSigMsg.HtlcSigs), len(bobReCommitSigMsg.HtlcSigs))
}
for i, htlcSig := range bobReCommitSigMsg.HtlcSigs {
if htlcSig.IsEqual(bobSigMsg.HtlcSigs[i]) {
t.Fatalf("htlc sig msgs don't match: "+
"expected %x got %x",
bobSigMsg.HtlcSigs[i].Serialize(),
htlcSig.Serialize())
}
}
// Now, we'll continue the exchange, sending Bob's revocation and
// signature message to Alice, ending with Alice sending her revocation
// message to Bob.
_, err = aliceChannel.ReceiveRevocation(bobRevocation)
if err != nil {
t.Fatalf("alice unable to recv revocation: %v", err)
}
err = aliceChannel.ReceiveNewCommitment(
bobSigMsg.CommitSig, bobSigMsg.HtlcSigs,
)
if err != nil {
t.Fatalf("alice unable to rev bob's commitment: %v", err)
}
aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
if err != nil {
t.Fatalf("alice unable to revoke commitment: %v", err)
}
if _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
t.Fatalf("bob unable to recv revocation: %v", err)
}
}
// TestChanSyncUnableToSync tests that if Alice or Bob receive an invalid
// ChannelReestablish messages,then they reject the message and declare the
// channel un-continuable by returning ErrCannotSyncCommitChains.
func TestChanSyncUnableToSync(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// If we immediately send both sides a "bogus" ChanSync message, then
// they both should conclude that they're unable to synchronize the
// state.
badChanSync := &lnwire.ChannelReestablish{
ChanID: lnwire.NewChanIDFromOutPoint(
&aliceChannel.channelState.FundingOutpoint,
),
NextLocalCommitHeight: 1000,
RemoteCommitTailHeight: 9000,
}
_, err = bobChannel.ProcessChanSyncMsg(badChanSync)
if err != ErrCannotSyncCommitChains {
t.Fatalf("expected error instead have: %v", err)
}
_, err = aliceChannel.ProcessChanSyncMsg(badChanSync)
if err != ErrCannotSyncCommitChains {
t.Fatalf("expected error instead have: %v", err)
}
}
// TestChanAvailableBandwidth tests the accuracy of the AvailableBalance()
// method. The value returned from this message should reflect the value
// returned within the commitment state of a channel after the transition is
// initiated.
func TestChanAvailableBandwidth(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, cleanUp, err := createTestChannels(1)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
assertBandwidthEstimateCorrect := func(aliceInitiate bool) {
// With the HTLC's added, we'll now query the AvailableBalance
// method for the current available channel bandwidth from
// Alice's PoV.
aliceAvailableBalance := aliceChannel.AvailableBalance()
// With this balance obtained, we'll now trigger a state update
// to actually determine what the current up to date balance
// is.
if aliceInitiate {
err := forceStateTransition(aliceChannel, bobChannel)
if err != nil {
t.Fatalf("unable to complete alice's state "+
"transition: %v", err)
}
} else {
err := forceStateTransition(bobChannel, aliceChannel)
if err != nil {
t.Fatalf("unable to complete alice's state "+
"transition: %v", err)
}
}
// Now, we'll obtain the current available bandwidth in Alice's
// latest commitment and compare that to the prior estimate.
aliceBalance := aliceChannel.channelState.LocalCommitment.LocalBalance
if aliceBalance != aliceAvailableBalance {
_, _, line, _ := runtime.Caller(1)
t.Fatalf("line: %v, incorrect balance: expected %v, "+
"got %v", line, aliceBalance,
aliceAvailableBalance)
}
}
// First, we'll add 3 outgoing HTLC's from Alice to Bob.
const numHtlcs = 3
var htlcAmt lnwire.MilliSatoshi = 100000
alicePreimages := make([][32]byte, numHtlcs)
for i := 0; i < numHtlcs; i++ {
htlc, preImage := createHTLC(i, htlcAmt)
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("unable to recv htlc: %v", err)
}
alicePreimages[i] = preImage
}
assertBandwidthEstimateCorrect(true)
// We'll repeat the same exercise, but with non-dust HTLCs. So we'll
// crank up the value of the HTLC's we're adding to the commitment
// transaction.
htlcAmt = lnwire.NewMSatFromSatoshis(30000)
for i := 0; i < numHtlcs; i++ {
htlc, preImage := createHTLC(i, htlcAmt)
if _, err := aliceChannel.AddHTLC(htlc); err != nil {
t.Fatalf("unable to add htlc: %v", err)
}
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
t.Fatalf("unable to recv htlc: %v", err)
}
alicePreimages = append(alicePreimages, preImage)
}
assertBandwidthEstimateCorrect(true)
// Next, we'll have Bob 5 of Alice's HTLC's, and cancel one of them (in
// the update log).
for i := 0; i < (numHtlcs*2)-1; i++ {
preImage := alicePreimages[i]
settleIndex, _, err := bobChannel.SettleHTLC(preImage)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
err = aliceChannel.ReceiveHTLCSettle(preImage, settleIndex)
if err != nil {
t.Fatalf("unable to settle htlc: %v", err)
}
}
failHash := sha256.Sum256(alicePreimages[5][:])
failIndex, err := bobChannel.FailHTLC(failHash, []byte("f"))
if err != nil {
t.Fatalf("unable to cancel HTLC: %v", err)
}
_, err = aliceChannel.ReceiveFailHTLC(failIndex, []byte("bad"))
if err != nil {
t.Fatalf("unable to recv htlc cancel: %v", err)
}
// With the HTLC's settled in the log, we'll now assert that if we
// initiate a state transition, then our guess was correct.
assertBandwidthEstimateCorrect(false)
// TODO(roasbeef): additional tests from diff starting conditions
}
// TODO(roasbeef): testing.Quick test case for retrans!!!