package lnwallet import ( "bytes" "crypto/sha256" "errors" "fmt" "io/ioutil" "math/rand" "os" "reflect" "runtime" "sync" "testing" "time" "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, signDesc.HashType, 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, signDesc.HashType, 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 } // mockSpendNotifier extends the mockNotifier so that spend notifications can be // triggered and delivered to subscribers. type mockSpendNotifier struct { *mockNotfier spendMap map[wire.OutPoint][]chan *chainntnfs.SpendDetail } func makeMockSpendNotifier() *mockSpendNotifier { return &mockSpendNotifier{ spendMap: make(map[wire.OutPoint][]chan *chainntnfs.SpendDetail), } } func (m *mockSpendNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, heightHint uint32) (*chainntnfs.SpendEvent, error) { spendChan := make(chan *chainntnfs.SpendDetail, 1) m.spendMap[*outpoint] = append(m.spendMap[*outpoint], spendChan) return &chainntnfs.SpendEvent{ Spend: spendChan, Cancel: func() { }, }, nil } // Spend dispatches SpendDetails to all subscribers of the outpoint. The details // will include the transaction and height provided by the caller. func (m *mockSpendNotifier) Spend(outpoint *wire.OutPoint, height int32, txn *wire.MsgTx) { if spendChans, ok := m.spendMap[*outpoint]; ok { delete(m.spendMap, *outpoint) for _, spendChan := range spendChans { txnHash := txn.TxHash() spendChan <- &chainntnfs.SpendDetail{ SpentOutPoint: outpoint, SpendingHeight: height, SpendingTx: txn, SpenderTxHash: &txnHash, SpenderInputIndex: outpoint.Index, } } } } // initRevocationWindows simulates a new channel being opened within the p2p // 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 } // createSpendableTestChannels initializes a pair of channels using a // mockSpendNotifier. This allows us to test the behavior of the closeObserver, // which is activated when the funding transaction is spent. func createSpendableTestChannels(revocationWindow int) (*LightningChannel, *LightningChannel, *mockSpendNotifier, func(), error) { notifier := makeMockSpendNotifier() alice, bob, cleanup, err := createTestChannelsWithNotifier( revocationWindow, notifier, ) return alice, bob, notifier, cleanup, err } // createTestChannels initializes a pair of channels using a mock notifier. func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChannel, func(), error) { notifier := &mockNotfier{} return createTestChannelsWithNotifier(revocationWindow, notifier) } // createTestChannelsWithNotifier creates two test lightning channels using the // provided notifier. The channel itself is funded with 10 BTC, with 5 BTC // allocated to each side. Within the channel, Alice is the initiator. func createTestChannelsWithNotifier(revocationWindow int, notifier chainntnfs.ChainNotifier) (*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) // TODO(roasbeef): use distinct keys aliceCfg := channeldb.ChannelConfig{ ChannelConstraints: channeldb.ChannelConstraints{ DustLimit: aliceDustLimit, MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()), ChanReserve: channelCapacity / 100, MinHTLC: lnwire.MilliSatoshi(rand.Int63()), MaxAcceptedHtlcs: uint16(rand.Int31()), }, CsvDelay: uint16(csvTimeoutAlice), MultiSigKey: aliceKeyPub, RevocationBasePoint: aliceKeyPub, PaymentBasePoint: aliceKeyPub, DelayBasePoint: aliceKeyPub, HtlcBasePoint: aliceKeyPub, } bobCfg := channeldb.ChannelConfig{ ChannelConstraints: channeldb.ChannelConstraints{ DustLimit: bobDustLimit, MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()), ChanReserve: channelCapacity / 100, MinHTLC: lnwire.MilliSatoshi(rand.Int63()), MaxAcceptedHtlcs: uint16(rand.Int31()), }, CsvDelay: uint16(csvTimeoutBob), MultiSigKey: bobKeyPub, RevocationBasePoint: bobKeyPub, PaymentBasePoint: bobKeyPub, DelayBasePoint: bobKeyPub, HtlcBasePoint: 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} feePerWeight, err := estimator.EstimateFeePerWeight(1) if err != nil { return nil, nil, nil, err } feePerKw := feePerWeight * 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} 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(id int, amount lnwire.MilliSatoshi) (*lnwire.UpdateAddHTLC, [32]byte) { preimage := bytes.Repeat([]byte{byte(id)}, 32) paymentHash := sha256.Sum256(preimage) var returnPreimage [32]byte copy(returnPreimage[:], preimage) return &lnwire.UpdateAddHTLC{ ID: uint64(id), 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. aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc) if err != nil { t.Fatalf("unable to add htlc: %v", err) } bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc) if 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) err = bobChannel.SettleHTLC(preimage, bobHtlcIndex) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(preimage, aliceHtlcIndex) if 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 := 0; 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+1) checkSize(bobChannel, i+1) } // Settle HTLCs and check that estimation is counting cost of settle // HTLCs properly. for i := 10; i >= 0; i-- { _, preimage := createHTLC(i, lnwire.MilliSatoshi(1e7)) err := bobChannel.SettleHTLC(preimage, uint64(i)) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(preimage, uint64(i)) 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) checkSize(bobChannel, i) } } 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 := btcutil.Amount(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 := btcutil.Amount(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) bobHtlcIndex, err := bobChannel.AddHTLC(htlc) if err != nil { t.Fatalf("alice unable to add htlc: %v", err) } aliceHtlcIndex, err := aliceChannel.ReceiveHTLC(htlc) if err != nil { t.Fatalf("bob 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. err = aliceChannel.SettleHTLC(preimage, aliceHtlcIndex) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = bobChannel.ReceiveHTLCSettle(preimage, bobHtlcIndex) 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") } } // TestBreachClose checks that the resulting ForceCloseSummary is correct when a // peer is ForceClosing the channel. Will check outputs both above and below // the dust limit. func TestBreachClose(t *testing.T) { t.Parallel() // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. aliceChannel, bobChannel, notifier, cleanUp, err := createSpendableTestChannels(1) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUp() // Send one HTLC from Alice to Bob, and advance the state of both // channels. htlcAmount := lnwire.NewMSatFromSatoshis(20000) htlc, _ := createHTLC(0, htlcAmount) if _, err := aliceChannel.AddHTLC(htlc); err != nil { t.Fatalf("alice unable to add htlc: %v", err) } if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { t.Fatalf("bob unable to recv add htlc: %v", err) } if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("Can't update the channel state: %v", err) } // Construct a force close summary of Bob's channel, this includes the // breach transaction that will be used to spend the funding point. forceCloseSummary, err := bobChannel.ForceClose() if err != nil { t.Fatalf("unable to force close bob's channel: %v", err) } // Send another HTLC and advance the state of both channels again. This // ensures that Alice's state will be ahead of the breach transaction // generated above. htlc2, _ := createHTLC(1, htlcAmount) if _, err := aliceChannel.AddHTLC(htlc2); err != nil { t.Fatalf("alice unable to add htlc: %v", err) } if _, err := bobChannel.ReceiveHTLC(htlc2); err != nil { t.Fatalf("bob unable to recv add htlc: %v", err) } if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("Can't update the channel state: %v", err) } chanPoint := aliceChannel.ChanPoint breachTxn := forceCloseSummary.CloseTx // Spend the funding point using the breach transaction. notifier.Spend(chanPoint, 100, breachTxn) // Set up a separate routine to monitor alice's channel for a response // to the spend. We use a generous timeout to ensure the test doesn't // stall indefinitely, but allows us to block the main routine until the // close observer exits. errChan := make(chan error, 1) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() select { case ret := <-aliceChannel.ContractBreach: errChan <- nil // Acknowledge a successful processing of the // retribution information. ret.Err <- nil case <-aliceChannel.UnilateralClose: errChan <- errors.New("expected breach close to " + "be signaled, not unilateral") case <-time.After(60 * time.Second): errChan <- errors.New("breach was not signaled") } }() // Wait for both the close observer to exit and our background process // to exit before attempting to read from the error channel. aliceChannel.WaitForClose() wg.Wait() // Now that all tasks have been shutdown, handle the result. The result // should be available immediately, we allow five seconds to handle any // variance in scheduling on travis. select { case err := <-errChan: if err != nil { t.Fatalf(err.Error()) } case <-time.After(5 * time.Second): t.Fatalf("breach was not received") } } // TestDustHTLCFees checks that fees are calculated correctly when HTLCs fall // 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) aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc) if err != nil { t.Fatalf("alice unable to add htlc: %v", err) } bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc) if 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. err = bobChannel.SettleHTLC(preimage, bobHtlcIndex) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(preimage, aliceHtlcIndex) 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) aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc) if err != nil { t.Fatalf("alice unable to add htlc: %v", err) } bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc) if 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) } err = bobChannel.SettleHTLC(preimage, bobHtlcIndex) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(preimage, aliceHtlcIndex) 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) var fakeOnionBlob [lnwire.OnionPacketSize]byte copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize)) // 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{ ID: uint64(i), PaymentHash: rHash, Amount: htlcAmt, Expiry: uint32(10), OnionBlob: fakeOnionBlob, } 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), OnionBlob: fakeOnionBlob, } 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.htlcIndex { 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.htlcIndex { 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.htlcIndex { 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.htlcIndex { 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++ { err := bobChannelNew.SettleHTLC(alicePreimage, uint64(i)) if err != nil { t.Fatalf("unable to settle htlc #%v: %v", i, err) } err = aliceChannelNew.ReceiveHTLCSettle(alicePreimage, uint64(i)) if err != nil { t.Fatalf("unable to settle htlc#%v: %v", i, err) } } err = aliceChannelNew.SettleHTLC(bobPreimage, 0) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = bobChannelNew.ReceiveHTLCSettle(bobPreimage, 0) 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, } aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc) if err != nil { t.Fatalf("unable to add alice htlc: %v", err) } bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc) if 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. err = bobChannel.FailHTLC(bobHtlcIndex, []byte("failreason")) if err != nil { t.Fatalf("unable to cancel HTLC: %v", err) } err = aliceChannel.ReceiveFailHTLC(aliceHtlcIndex, []byte("bad")) if 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 := btcutil.Amount(aliceChannel.CalcFee(aliceFeeRate)) + 1000 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 := btcutil.Amount(bobChannel.CalcFee(bobFeeRate)) + 1000 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)) } commitFee := aliceChannel.channelState.LocalCommitment.CommitFee aliceExpectedBalance := aliceBal.ToSatoshis() - aliceFee + commitFee if closeTx.TxOut[0].Value != int64(aliceExpectedBalance) { t.Fatalf("alice's balance is incorrect: expected %v, got %v", aliceExpectedBalance, int64(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) { _, _, line, _ := runtime.Caller(1) aliceChanSyncMsg, err := aliceChannel.ChanSyncMsg() if err != nil { t.Fatalf("line #%v: unable to produce chan sync msg: %v", line, err) } bobMsgsToSend, err := bobChannel.ProcessChanSyncMsg(aliceChanSyncMsg) if err != nil { t.Fatalf("line #%v: unable to process ChannelReestablish "+ "msg: %v", line, err) } if len(bobMsgsToSend) != 0 { t.Fatalf("line #%v: bob shouldn't have to send any messages, "+ "instead wants to send: %v", line, spew.Sdump(bobMsgsToSend)) } bobChanSyncMsg, err := bobChannel.ChanSyncMsg() if err != nil { t.Fatalf("line #%v: unable to produce chan sync msg: %v", line, err) } aliceMsgsToSend, err := aliceChannel.ProcessChanSyncMsg(bobChanSyncMsg) if err != nil { t.Fatalf("line #%v: unable to process ChannelReestablish "+ "msg: %v", line, err) } if len(bobMsgsToSend) != 0 { t.Fatalf("line #%v: alice shouldn't have to send any "+ "messages, instead wants to send: %v", line, 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() // If we exchange channel sync messages from the get-go , then both // sides should conclude that no further synchronization is needed. assertNoChanSyncNeeded(t, aliceChannel, bobChannel) // 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), } aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc) if err != nil { t.Fatalf("unable to add htlc: %v", err) } bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc) if 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. err = bobChannel.SettleHTLC(paymentPreimage, bobHtlcIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(paymentPreimage, aliceHtlcIndex) 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 reads the passe channel from disk, and returns a newly // initialized instance. This simulates one party restarting and losing their // in memory state. 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() var fakeOnionBlob [lnwire.OnionPacketSize]byte copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize)) // 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), OnionBlob: fakeOnionBlob, } htlcIndex, err := bobChannel.AddHTLC(h) if err != nil { t.Fatalf("unable to add bob's htlc: %v", err) } h.ID = htlcIndex 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++ { err := aliceChannel.SettleHTLC(bobPreimage, uint64(i)) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = bobChannel.ReceiveHTLCSettle(bobPreimage, uint64(i)) 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), OnionBlob: fakeOnionBlob, } aliceHtlcIndex, err := aliceChannel.AddHTLC(aliceHtlc) if err != nil { t.Fatalf("unable to add alice's htlc: %v", err) } bobHtlcIndex, err := bobChannel.ReceiveHTLC(aliceHtlc) if 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, err := aliceChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } bobSyncMsg, err := bobChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } // 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. err = bobChannel.SettleHTLC(alicePreimage, bobHtlcIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(alicePreimage, aliceHtlcIndex) 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 the amount of HTLC's sent. 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), } bobHtlcIndex, err := bobChannel.AddHTLC(bobHtlc) if err != nil { t.Fatalf("unable to add bob's htlc: %v", err) } aliceHtlcIndex, err := aliceChannel.ReceiveHTLC(bobHtlc) if 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. err = aliceChannel.SettleHTLC(bobPreimage, aliceHtlcIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = bobChannel.ReceiveHTLCSettle(bobPreimage, bobHtlcIndex) 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, err := aliceChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } bobSyncMsg, err := bobChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } 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), } bobHtlcIndex, err := bobChannel.AddHTLC(bobHtlc) if err != nil { t.Fatalf("unable to add bob's htlc: %v", err) } aliceHtlcIndex, err := aliceChannel.ReceiveHTLC(bobHtlc) if 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. err = aliceChannel.SettleHTLC(bobPreimage, aliceHtlcIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = bobChannel.ReceiveHTLCSettle(bobPreimage, bobHtlcIndex) 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, err := aliceChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } bobSyncMsg, err := bobChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } 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) } defer bobChannel.Stop() 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), } bobHtlcIndex, err := bobChannel.AddHTLC(bobHtlc) if err != nil { t.Fatalf("unable to add bob's htlc: %v", err) } aliceHtlcIndex, err := aliceChannel.ReceiveHTLC(bobHtlc) if 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. err = aliceChannel.SettleHTLC(bobPreimage, aliceHtlcIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = bobChannel.ReceiveHTLCSettle(bobPreimage, bobHtlcIndex) 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, err := aliceChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } bobSyncMsg, err := bobChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } 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) } defer bobChannel.Stop() 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) } } // TestFeeUpdateRejectInsaneFee tests that if the initiator tries to attach a // fee that would put them below their current reserve, then it's rejected by // the state machine. func TestFeeUpdateRejectInsaneFee(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, _, cleanUp, err := createTestChannels(1) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUp() // Next, we'll try to add a fee rate to Alice which is 1,000,000x her // starting fee rate. startingFeeRate := aliceChannel.channelState.LocalCommitment.FeePerKw newFeeRate := startingFeeRate * 1000000 // Both Alice and Bob should reject this new fee rate as it it far too // large. if err := aliceChannel.UpdateFee(newFeeRate); err == nil { t.Fatalf("alice should've rejected fee update") } } // TestChannelRetransmissionFeeUpdate tests that the initiator will include any // pending fee updates if it needs to retransmit signatures. func TestChannelRetransmissionFeeUpdate(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() // First, we'll fetch the current fee rate present within the // commitment transactions. startingFeeRate := aliceChannel.channelState.LocalCommitment.FeePerKw // Next, we'll start a commitment update, with Alice sending a new // update to double the fee rate of the commitment. newFeeRate := startingFeeRate * 2 if err := aliceChannel.UpdateFee(newFeeRate); err != nil { t.Fatalf("unable to update fee for Alice's channel: %v", err) } if err := bobChannel.ReceiveUpdateFee(newFeeRate); err != nil { t.Fatalf("unable to update fee for Bob's channel: %v", err) } // Now, Alice will send a new commitment to Bob, but we'll simulate a // connection failure, so Bob doesn't get her signature. aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("unable to sign commitment: %v", err) } // Restart both channels to simulate a connection restart. aliceChannel, err = restartChannel(aliceChannel) if err != nil { t.Fatalf("unable to restart alice: %v", err) } defer aliceChannel.Stop() bobChannel, err = restartChannel(bobChannel) if err != nil { t.Fatalf("unable to restart channel: %v", err) } defer bobChannel.Stop() // 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, err := aliceChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } bobSyncMsg, err := bobChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to produce chan sync msg: %v", err) } // Bob should detect that he doesn't need to send anything to Alice. 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", 0, len(bobMsgsToSend), spew.Sdump(bobMsgsToSend)) } // When Alice processes Bob's chan sync message, she should realize // that she needs to first send a new UpdateFee message, and also a // CommitSig. aliceMsgsToSend, err := aliceChannel.ProcessChanSyncMsg( bobSyncMsg, ) if err != nil { t.Fatalf("unable to process chan sync msg: %v", err) } if len(aliceMsgsToSend) != 2 { t.Fatalf("expected alice to send %v messages instead "+ "will send %v: %v", 2, len(aliceMsgsToSend), spew.Sdump(aliceMsgsToSend)) } // The first message should be an UpdateFee message. retransFeeMsg, ok := aliceMsgsToSend[0].(*lnwire.UpdateFee) if !ok { t.Fatalf("expected UpdateFee message, instead have: %v", spew.Sdump(aliceMsgsToSend[0])) } // The fee should match exactly the new fee update we applied above. if retransFeeMsg.FeePerKw != uint32(newFeeRate) { t.Fatalf("fee update doesn't match: expected %v, got %v", uint32(newFeeRate), retransFeeMsg) } // The second, should be a CommitSig message, and be identical to the // sig message she sent prior. commitSigMsg, ok := aliceMsgsToSend[1].(*lnwire.CommitSig) if !ok { t.Fatalf("expected a CommitSig message, instead have %v", spew.Sdump(aliceMsgsToSend[1])) } 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()) } } // Now, we if re-apply the updates to Bob, we should be able to resume // the commitment update as normal. if err := bobChannel.ReceiveUpdateFee(newFeeRate); err != nil { t.Fatalf("unable to update fee for Bob's channel: %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) } 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) } // Both parties should now have the latest fee rate locked-in. if aliceChannel.channelState.LocalCommitment.FeePerKw != newFeeRate { t.Fatalf("alice's feePerKw was not locked in") } if bobChannel.channelState.LocalCommitment.FeePerKw != newFeeRate { t.Fatalf("bob's feePerKw was not locked in") } // Finally, we'll add with adding a new HTLC, then forcing a state // transition. This should also proceed as normal. var bobPreimage [32]byte copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32)) rHash := sha256.Sum256(bobPreimage[:]) bobHtlc := &lnwire.UpdateAddHTLC{ PaymentHash: rHash, Amount: lnwire.NewMSatFromSatoshis(20000), 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) } } // 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) } } // TestChanSyncInvalidLastSecret ensures that if Alice and Bob have completed // state transitions in an existing channel, and then send a ChannelReestablish // message after a restart, the following holds: if Alice has lost data, so she // sends an invalid commit secret then both parties recognize this as possible // data loss. func TestChanSyncInvalidLastSecret(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 create a new instances of Alice before doing any state updates // such that we have the initial in memory state at the start of the // channel. aliceOld, err := restartChannel(aliceChannel) if err != nil { t.Fatalf("unable to restart alice") } // First, we'll add an HTLC, and then initiate a state transition // between the two parties such that we actually have a prior // revocation to send. 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) } // Next, we'll restart both parties in order to simulate a connection // re-establishment. aliceChannel, err = restartChannel(aliceChannel) if err != nil { t.Fatalf("unable to restart alice: %v", err) } bobChannel, err = restartChannel(bobChannel) if err != nil { t.Fatalf("unable to restart bob: %v", err) } // Next, we'll produce the ChanSync messages for both parties. aliceChanSync, err := aliceChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to generate chan sync msg: %v", err) } bobChanSync, err := bobChannel.ChanSyncMsg() if err != nil { t.Fatalf("unable to generate chan sync msg: %v", err) } // We'll modify Alice's sync message to have an invalid commitment // secret. aliceChanSync.LastRemoteCommitSecret[4] ^= 0x01 // Alice's former self should conclude that she possibly lost data as // Bob is sending a valid commit secret for the latest state. _, err = aliceOld.ProcessChanSyncMsg(bobChanSync) if err != ErrCommitSyncDataLoss { t.Fatalf("wrong error, expected ErrCommitSyncDataLoss "+ "instead got: %v", err) } // Bob should conclude that he should force close the channel, as Alice // cannot continue operation. _, err = bobChannel.ProcessChanSyncMsg(aliceChanSync) if err != ErrInvalidLastCommitSecret { t.Fatalf("wrong error, expected ErrInvalidLastCommitSecret, "+ "instead got: %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(numHtlcs+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] err := bobChannel.SettleHTLC(preImage, uint64(i)) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(preImage, uint64(i)) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } } htlcIndex := uint64((numHtlcs * 2) - 1) err = bobChannel.FailHTLC(htlcIndex, []byte("f")) if err != nil { t.Fatalf("unable to cancel HTLC: %v", err) } err = aliceChannel.ReceiveFailHTLC(htlcIndex, []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 } // TestSignCommitmentFailNotLockedIn tests that a channel will not attempt to // create a new state if it doesn't yet know of the next revocation point for // the remote party. func TestSignCommitmentFailNotLockedIn(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, _, cleanUp, err := createTestChannels(1) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUp() // Next, we'll modify Alice's internal state to omit knowledge of Bob's // next revocation point. aliceChannel.channelState.RemoteNextRevocation = nil // If we now try to initiate a state update, then it should fail as // Alice is unable to actually create a new state. _, _, err = aliceChannel.SignNextCommitment() if err != ErrNoWindow { t.Fatalf("expected ErrNoWindow, instead have: %v", err) } } // TODO(roasbeef): testing.Quick test case for retrans!!!