package lnwallet import ( "bytes" "crypto/sha256" "fmt" "io/ioutil" "math/rand" "os" "testing" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" "github.com/roasbeef/btcd/blockchain" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) var ( privPass = []byte("private-test") // For simplicity a single priv key controls all of our test outputs. testWalletPrivKey = []byte{ 0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf, 0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9, 0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f, 0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90, } // We're alice :) bobsPrivKey = []byte{ 0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, 0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, 0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, 0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9, } // Use a hard-coded HD seed. testHdSeed = chainhash.Hash{ 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, } // The number of confirmations required to consider any created channel // open. numReqConfs = uint16(1) ) type mockSigner struct { key *btcec.PrivateKey } func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) { amt := signDesc.Output.Value witnessScript := signDesc.WitnessScript privKey := m.key if !privKey.PubKey().IsEqual(signDesc.PubKey) { return nil, fmt.Errorf("incorrect key passed") } switch { case signDesc.SingleTweak != nil: privKey = TweakPrivKey(privKey, signDesc.SingleTweak) case signDesc.DoubleTweak != nil: privKey = DeriveRevocationPrivKey(privKey, signDesc.DoubleTweak) } sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, signDesc.InputIndex, amt, witnessScript, txscript.SigHashAll, privKey) if err != nil { return nil, err } return sig[:len(sig)-1], nil } func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) { // TODO(roasbeef): expose tweaked signer from lnwallet so don't need to // duplicate this code? privKey := m.key switch { case signDesc.SingleTweak != nil: privKey = TweakPrivKey(privKey, signDesc.SingleTweak) case signDesc.DoubleTweak != nil: privKey = DeriveRevocationPrivKey(privKey, signDesc.DoubleTweak) } witnessScript, err := txscript.WitnessScript(tx, signDesc.SigHashes, signDesc.InputIndex, signDesc.Output.Value, signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) if err != nil { return nil, err } return &InputScript{ Witness: witnessScript, }, nil } type mockNotfier struct { } func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { return nil, nil } func (m *mockNotfier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) { return nil, nil } func (m *mockNotfier) Start() error { return nil } func (m *mockNotfier) Stop() error { return nil } func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, heightHint uint32) (*chainntnfs.SpendEvent, error) { return &chainntnfs.SpendEvent{ Spend: make(chan *chainntnfs.SpendDetail), }, nil } // initRevocationWindows simulates a new channel being opened within the p2p // network by populating the initial revocation windows of the passed // commitment state machines. // // TODO(roasbeef): rename! func initRevocationWindows(chanA, chanB *LightningChannel, windowSize int) error { aliceNextRevoke, err := chanA.NextRevocationKey() if err != nil { return err } if err := chanB.InitNextRevocation(aliceNextRevoke); err != nil { return err } bobNextRevoke, err := chanB.NextRevocationKey() if err != nil { return err } if err := chanA.InitNextRevocation(bobNextRevoke); err != nil { return err } return nil } // forceStateTransition executes the necessary interaction between the two // commitment state machines to transition to a new state locking in any // pending updates. func forceStateTransition(chanA, chanB *LightningChannel) error { aliceSig, aliceHtlcSigs, err := chanA.SignNextCommitment() if err != nil { return err } if err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs); err != nil { return err } bobRevocation, err := chanB.RevokeCurrentCommitment() if err != nil { return err } bobSig, bobHtlcSigs, err := chanB.SignNextCommitment() if err != nil { return err } if _, err := chanA.ReceiveRevocation(bobRevocation); err != nil { return err } if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil { return err } aliceRevocation, err := chanA.RevokeCurrentCommitment() if err != nil { return err } if _, err := chanB.ReceiveRevocation(aliceRevocation); err != nil { return err } return nil } // createTestChannels creates two test channels funded with 10 BTC, with 5 BTC // allocated to each side. Within the channel, Alice is the initiator. func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChannel, func(), error) { aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) channelCapacity := btcutil.Amount(10 * 1e8) channelBal := channelCapacity / 2 aliceDustLimit := btcutil.Amount(200) bobDustLimit := btcutil.Amount(1300) csvTimeoutAlice := uint32(5) csvTimeoutBob := uint32(4) prevOut := &wire.OutPoint{ Hash: chainhash.Hash(testHdSeed), Index: 0, } fundingTxIn := wire.NewTxIn(prevOut, nil, nil) aliceCfg := channeldb.ChannelConfig{ ChannelConstraints: channeldb.ChannelConstraints{ DustLimit: aliceDustLimit, MaxPendingAmount: btcutil.Amount(rand.Int63()), ChanReserve: btcutil.Amount(rand.Int63()), MinHTLC: btcutil.Amount(rand.Int63()), MaxAcceptedHtlcs: uint16(rand.Int31()), }, CsvDelay: uint16(csvTimeoutAlice), MultiSigKey: aliceKeyPub, RevocationBasePoint: aliceKeyPub, PaymentBasePoint: aliceKeyPub, DelayBasePoint: aliceKeyPub, } bobCfg := channeldb.ChannelConfig{ ChannelConstraints: channeldb.ChannelConstraints{ DustLimit: bobDustLimit, MaxPendingAmount: btcutil.Amount(rand.Int63()), ChanReserve: btcutil.Amount(rand.Int63()), MinHTLC: btcutil.Amount(rand.Int63()), MaxAcceptedHtlcs: uint16(rand.Int31()), }, CsvDelay: uint16(csvTimeoutBob), MultiSigKey: bobKeyPub, RevocationBasePoint: bobKeyPub, PaymentBasePoint: bobKeyPub, DelayBasePoint: bobKeyPub, } bobRoot := DeriveRevocationRoot(bobKeyPriv, testHdSeed, aliceKeyPub) bobPreimageProducer := shachain.NewRevocationProducer(bobRoot) bobFirstRevoke, err := bobPreimageProducer.AtIndex(0) if err != nil { return nil, nil, nil, err } bobCommitPoint := ComputeCommitmentPoint(bobFirstRevoke[:]) aliceRoot := DeriveRevocationRoot(aliceKeyPriv, testHdSeed, bobKeyPub) alicePreimageProducer := shachain.NewRevocationProducer(aliceRoot) aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0) if err != nil { return nil, nil, nil, err } aliceCommitPoint := ComputeCommitmentPoint(aliceFirstRevoke[:]) aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint, fundingTxIn) if err != nil { return nil, nil, nil, err } alicePath, err := ioutil.TempDir("", "alicedb") dbAlice, err := channeldb.Open(alicePath) if err != nil { return nil, nil, nil, err } bobPath, err := ioutil.TempDir("", "bobdb") dbBob, err := channeldb.Open(bobPath) if err != nil { return nil, nil, nil, err } var obsfucator [StateHintSize]byte copy(obsfucator[:], aliceFirstRevoke[:]) estimator := &StaticFeeEstimator{24, 6} feePerKw := btcutil.Amount(estimator.EstimateFeePerWeight(1) * 1000) commitFee := calcStaticFee(0) aliceChannelState := &channeldb.OpenChannel{ LocalChanCfg: aliceCfg, RemoteChanCfg: bobCfg, IdentityPub: aliceKeyPub, CommitFee: commitFee, FundingOutpoint: *prevOut, ChanType: channeldb.SingleFunder, FeePerKw: feePerKw, IsInitiator: true, Capacity: channelCapacity, LocalBalance: channelBal - commitFee, RemoteBalance: channelBal, CommitTx: *aliceCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), RemoteCurrentRevocation: bobCommitPoint, RevocationProducer: alicePreimageProducer, RevocationStore: shachain.NewRevocationStore(), Db: dbAlice, } bobChannelState := &channeldb.OpenChannel{ LocalChanCfg: bobCfg, RemoteChanCfg: aliceCfg, IdentityPub: bobKeyPub, FeePerKw: feePerKw, CommitFee: commitFee, FundingOutpoint: *prevOut, ChanType: channeldb.SingleFunder, IsInitiator: false, Capacity: channelCapacity, LocalBalance: channelBal, RemoteBalance: channelBal - commitFee, CommitTx: *bobCommitTx, CommitSig: bytes.Repeat([]byte{1}, 71), RemoteCurrentRevocation: aliceCommitPoint, RevocationProducer: bobPreimageProducer, RevocationStore: shachain.NewRevocationStore(), Db: dbBob, } cleanUpFunc := func() { os.RemoveAll(bobPath) os.RemoveAll(alicePath) } aliceSigner := &mockSigner{aliceKeyPriv} bobSigner := &mockSigner{bobKeyPriv} notifier := &mockNotfier{} channelAlice, err := NewLightningChannel(aliceSigner, notifier, estimator, aliceChannelState) if err != nil { return nil, nil, nil, err } channelBob, err := NewLightningChannel(bobSigner, notifier, estimator, bobChannelState) if err != nil { return nil, nil, nil, err } // Now that the channel are open, simulate the start of a session by // having Alice and Bob extend their revocation windows to each other. err = initRevocationWindows(channelAlice, channelBob, revocationWindow) if err != nil { return nil, nil, nil, err } return channelAlice, channelBob, cleanUpFunc, nil } // calcStaticFee calculates appropriate fees for commitment transactions. This // function provides a simple way to allow test balance assertions to take fee // calculations into account. // // TODO(bvu): Refactor when dynamic fee estimation is added. func calcStaticFee(numHTLCs int) btcutil.Amount { const ( commitWeight = btcutil.Amount(724) htlcWeight = 172 feePerKw = btcutil.Amount(24/4) * 1000 ) return feePerKw * (commitWeight + btcutil.Amount(htlcWeight*numHTLCs)) / 1000 } // createHTLC is a utility function for generating an HTLC with a given // preimage and a given amount. func createHTLC(data int, amount btcutil.Amount) (*lnwire.UpdateAddHTLC, [32]byte) { preimage := bytes.Repeat([]byte{byte(data)}, 32) paymentHash := sha256.Sum256(preimage) var returnPreimage [32]byte copy(returnPreimage[:], preimage) return &lnwire.UpdateAddHTLC{ PaymentHash: paymentHash, Amount: amount, Expiry: uint32(5), }, returnPreimage } // 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) 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) } // 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 alive'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 := uint64(0) bobSent := uint64(0) if aliceChannel.channelState.TotalSatoshisSent != aliceSent { t.Fatalf("alice has incorrect satoshis sent: %v vs %v", aliceChannel.channelState.TotalSatoshisSent, aliceSent) } if aliceChannel.channelState.TotalSatoshisReceived != bobSent { t.Fatalf("alice has incorrect satoshis received %v vs %v", aliceChannel.channelState.TotalSatoshisReceived, bobSent) } if bobChannel.channelState.TotalSatoshisSent != bobSent { t.Fatalf("bob has incorrect satoshis sent %v vs %v", bobChannel.channelState.TotalSatoshisSent, bobSent) } if bobChannel.channelState.TotalSatoshisReceived != aliceSent { t.Fatalf("bob has incorrect satoshis received %v vs %v", bobChannel.channelState.TotalSatoshisReceived, 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) } // Now we'll repeat a similar exchange, this time with Bob settling the // HTLC once he learns of the preimage. var preimage [32]byte copy(preimage[:], paymentPreimage) settleIndex, err := bobChannel.SettleHTLC(preimage) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } if err := aliceChannel.ReceiveHTLCSettle(preimage, settleIndex); err != nil { t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } bobSig2, bobHtlcSigs2, err := bobChannel.SignNextCommitment() if err != nil { t.Fatalf("bob unable to sign settle commitment: %v", err) } err = aliceChannel.ReceiveNewCommitment(bobSig2, bobHtlcSigs2) if err != nil { t.Fatalf("alice unable to process bob's new commitment: %v", err) } aliceRevocation2, err := aliceChannel.RevokeCurrentCommitment() if err != nil { t.Fatalf("alice unable to generate revocation: %v", err) } aliceSig2, aliceHtlcSigs2, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("alice unable to sign new commitment: %v", err) } if htlcs, err := bobChannel.ReceiveRevocation(aliceRevocation2); err != nil { t.Fatalf("bob unable to process alice's revocation: %v", err) } else if len(htlcs) != 0 { t.Fatalf("bob shouldn't forward any HTLCs after outgoing settle, "+ "instead can forward: %v", spew.Sdump(htlcs)) } err = bobChannel.ReceiveNewCommitment(aliceSig2, aliceHtlcSigs2) if err != nil { t.Fatalf("bob unable to process alice's new commitment: %v", err) } bobRevocation2, err := bobChannel.RevokeCurrentCommitment() if err != nil { t.Fatalf("bob unable to revoke commitment: %v", err) } if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation2); err != nil { t.Fatalf("alice unable to process bob's revocation: %v", err) } else if len(htlcs) != 1 { // Alice should now be able to forward the settlement HTLC to // any down stream peers. t.Fatalf("alice should be able to forward a single HTLC, "+ "instead can forward %v: %v", len(htlcs), spew.Sdump(htlcs)) } // At this point, Bob should have 6 BTC settled, with Alice still having // 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel should // show 1 BTC received. They should also be at commitment height two, // with the revocation window extended by by 1 (5). satoshisTransferred := uint64(100000000) if aliceChannel.channelState.TotalSatoshisSent != satoshisTransferred { t.Fatalf("alice satoshis sent incorrect %v vs %v expected", aliceChannel.channelState.TotalSatoshisSent, satoshisTransferred) } if aliceChannel.channelState.TotalSatoshisReceived != 0 { t.Fatalf("alice satoshis received incorrect %v vs %v expected", aliceChannel.channelState.TotalSatoshisSent, 0) } if bobChannel.channelState.TotalSatoshisReceived != satoshisTransferred { t.Fatalf("bob satoshis received incorrect %v vs %v expected", bobChannel.channelState.TotalSatoshisReceived, satoshisTransferred) } if bobChannel.channelState.TotalSatoshisSent != 0 { t.Fatalf("bob satoshis sent incorrect %v vs %v expected", bobChannel.channelState.TotalSatoshisReceived, 0) } if bobChannel.currentHeight != 2 { t.Fatalf("bob has incorrect commitment height, %v vs %v", bobChannel.currentHeight, 2) } if aliceChannel.currentHeight != 2 { t.Fatalf("alice has incorrect commitment height, %v vs %v", aliceChannel.currentHeight, 2) } // The logs of both sides should now be cleared since the entry adding // the HTLC should have been removed once both sides receive the // revocation. if aliceChannel.localUpdateLog.Len() != 0 { t.Fatalf("alice's local not updated, should be empty, has %v "+ "entries instead", aliceChannel.localUpdateLog.Len()) } if aliceChannel.remoteUpdateLog.Len() != 0 { t.Fatalf("alice's remote not updated, should be empty, has %v "+ "entries instead", aliceChannel.remoteUpdateLog.Len()) } if len(aliceChannel.localUpdateLog.updateIndex) != 0 { t.Fatalf("alice's local log index not cleared, should be empty but "+ "has %v entries", len(aliceChannel.localUpdateLog.updateIndex)) } if len(aliceChannel.remoteUpdateLog.updateIndex) != 0 { t.Fatalf("alice's remote log index not cleared, should be empty but "+ "has %v entries", len(aliceChannel.remoteUpdateLog.updateIndex)) } } // TestCheckCommitTxSize checks that estimation size of commitment // transaction with some degree of error corresponds to the actual size. func TestCheckCommitTxSize(t *testing.T) { t.Parallel() checkSize := func(channel *LightningChannel, count int) { // Due to variable size of the signatures (70-73) in // witness script actual size of commitment transaction might // be lower on 6 weight. BaseCommitmentTxSizeEstimationError := 6 commitTx, err := channel.getSignedCommitTx() if err != nil { t.Fatalf("unable to initiate alice force close: %v", err) } actualCost := blockchain.GetTransactionWeight(btcutil.NewTx(commitTx)) estimatedCost := estimateCommitTxWeight(count, false) diff := int(estimatedCost - actualCost) if 0 > diff || BaseCommitmentTxSizeEstimationError < diff { t.Fatalf("estimation is wrong, diff: %v", diff) } } // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUp() // Check that weight estimation of the commitment transaction without // HTLCs is right. checkSize(aliceChannel, 0) checkSize(bobChannel, 0) // Adding HTLCs and check that size stays in allowable estimation // error window. for i := 1; i <= 10; i++ { htlc, _ := createHTLC(i, btcutil.Amount(1e7)) if _, err := aliceChannel.AddHTLC(htlc); err != nil { t.Fatalf("alice unable to add htlc: %v", err) } if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { t.Fatalf("bob unable to receive htlc: %v", err) } if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to complete state update: %v", err) } checkSize(aliceChannel, i) checkSize(bobChannel, i) } // Settle HTLCs and check that estimation is counting cost of settle // HTLCs properly. for i := 10; i >= 1; i-- { _, preimage := createHTLC(i, btcutil.Amount(1e7)) settleIndex, err := bobChannel.SettleHTLC(preimage) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(preimage, settleIndex) if err != nil { t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } if err := forceStateTransition(bobChannel, aliceChannel); err != nil { t.Fatalf("unable to complete state update: %v", err) } checkSize(aliceChannel, i-1) checkSize(bobChannel, i-1) } } func TestCooperativeChannelClosure(t *testing.T) { t.Parallel() // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUp() aliceDeliveryScript := bobsPrivKey[:] bobDeliveryScript := testHdSeed[:] aliceFeeRate := uint64(aliceChannel.channelState.FeePerKw) bobFeeRate := uint64(bobChannel.channelState.FeePerKw) // We'll store with both Alice and Bob creating a new close proposal // with the same fee rate. aliceSig, _, err := aliceChannel.CreateCloseProposal( aliceFeeRate, aliceDeliveryScript, bobDeliveryScript, ) if err != nil { t.Fatalf("unable to create alice coop close proposal: %v", err) } aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll)) bobSig, _, err := bobChannel.CreateCloseProposal( bobFeeRate, 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, bobFeeRate) if err != nil { t.Fatalf("unable to complete alice cooperative close: %v", err) } bobCloseSha := aliceCloseTx.TxHash() bobCloseTx, err := aliceChannel.CompleteCooperativeClose( aliceCloseSig, bobCloseSig, aliceDeliveryScript, bobDeliveryScript, aliceFeeRate) 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() htlcAmount := btcutil.Amount(500) aliceAmount := aliceChannel.channelState.LocalBalance bobAmount := bobChannel.channelState.LocalBalance closeSummary, err := aliceChannel.ForceClose() if err != nil { t.Fatalf("unable to force close channel: %v", err) } // The SelfOutputSignDesc should be non-nil since the outout 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") } if closeSummary.SelfOutputSignDesc.Output.Value != int64(aliceAmount) { t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+ "expected %v, got %v", aliceChannel.channelState.LocalBalance, closeSummary.SelfOutputSignDesc.Output.Value) } 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) } closeTxHash := closeSummary.CloseTx.TxHash() commitTxHash := aliceChannel.channelState.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) { t.Fatalf("bob incorrect output value in SelfOutputSignDesc, "+ "expected %v, got %v", int64(bobAmount), 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.CommitTx.TxHash() if !bytes.Equal(closeTxHash[:], commitTxHash[:]) { t.Fatalf("bob: incorrect close transaction txid") } // Re-open the channels by clearing the channel's status back to "open, // and also resetting some internal state. aliceChannel.status = channelOpen bobChannel.status = channelOpen aliceChannel.ForceCloseSignal = make(chan struct{}) bobChannel.ForceCloseSignal = make(chan struct{}) // Have Bobs' to-self output be below her dust limit and check // ForceCloseSummary again on both peers. htlc, preimage := createHTLC(0, bobAmount-htlcAmount) if _, err := bobChannel.AddHTLC(htlc); err != nil { t.Fatalf("alice unable to add htlc: %v", err) } if _, err := aliceChannel.ReceiveHTLC(htlc); 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. settleIndex, err := aliceChannel.SettleHTLC(preimage) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = bobChannel.ReceiveHTLCSettle(preimage, settleIndex) if err != nil { t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("Can't update the channel state: %v", err) } aliceAmount = aliceChannel.channelState.LocalBalance bobAmount = bobChannel.channelState.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) { t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+ "expected %v, got %v", aliceChannel.channelState.LocalBalance, 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.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.CommitTx.TxHash() if !bytes.Equal(closeTxHash[:], commitTxHash[:]) { t.Fatalf("bob: incorrect close transaction txid") } } // TestDustHTLCFees checks that fees are calculated correctly when HTLCs fall // below the nodes' dust limit. In these cases, the amount of the dust HTLCs // should be applied to the commitment transaction fee. func TestDustHTLCFees(t *testing.T) { // 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.LocalBalance // This HTLC amount should be lower than the dust limits of both nodes. htlcAmount := btcutil.Amount(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.LocalBalance + aliceChannel.channelState.RemoteBalance + aliceChannel.channelState.CommitFee) if totalSatoshisAlice+htlcAmount != 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.LocalBalance + bobChannel.channelState.RemoteBalance + bobChannel.channelState.CommitFee) if totalSatoshisBob+htlcAmount != 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.CommitFee != defaultFee { t.Fatalf("dust htlc amounts not subtracted from commitment fee "+ "expected %v, got %v", defaultFee, aliceChannel.channelState.CommitFee) } if bobChannel.channelState.CommitFee != defaultFee { t.Fatalf("dust htlc amounts not subtracted from commitment fee "+ "expected %v, got %v", defaultFee, bobChannel.channelState.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.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. htlcAmount := (btcutil.Amount(500) + htlcTimeoutFee(aliceChannel.channelState.FeePerKw)) htlc, preimage := createHTLC(0, htlcAmount) if _, err := aliceChannel.AddHTLC(htlc); err != nil { t.Fatalf("alice unable to add htlc: %v", err) } if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { t.Fatalf("bob unable to receive htlc: %v", err) } if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("Can't update the channel state: %v", err) } // At this point, Alice's commitment transaction should have an HTLC, // while Bob's should not, because the value falls beneath his dust // limit. The amount of the HTLC should be applied to fees in Bob's // commitment transaction. aliceCommitment := aliceChannel.localCommitChain.tip() if len(aliceCommitment.txn.TxOut) != 3 { t.Fatalf("incorrect # of outputs: expected %v, got %v", 3, len(aliceCommitment.txn.TxOut)) } bobCommitment := bobChannel.localCommitChain.tip() if len(bobCommitment.txn.TxOut) != 2 { t.Fatalf("incorrect # of outputs: expected %v, got %v", 2, len(bobCommitment.txn.TxOut)) } defaultFee := calcStaticFee(0) if bobChannel.channelState.CommitFee != defaultFee { t.Fatalf("dust htlc amount was subtracted from commitment fee "+ "expected %v, got %v", defaultFee, bobChannel.channelState.CommitFee) } // Settle HTLC and create a new commitment state. settleIndex, err := bobChannel.SettleHTLC(preimage) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(preimage, settleIndex) if err != nil { t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } if err := forceStateTransition(bobChannel, aliceChannel); err != nil { t.Fatalf("state transition error: %v", err) } // At this point, for Alice's commitment chains, the value of the HTLC // should have been added to Alice's balance and TotalSatoshisSent. commitment := aliceChannel.localCommitChain.tip() if len(commitment.txn.TxOut) != 2 { t.Fatalf("incorrect # of outputs: expected %v, got %v", 2, len(commitment.txn.TxOut)) } if aliceChannel.channelState.TotalSatoshisSent != uint64(htlcAmount) { t.Fatalf("alice satoshis sent incorrect: expected %v, got %v", htlcAmount, aliceChannel.channelState.TotalSatoshisSent) } } // 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) { // 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. defaultFee := calcStaticFee(0) dustLimit := aliceChannel.channelState.LocalChanCfg.DustLimit aliceBalance := aliceChannel.channelState.LocalBalance htlcAmount := aliceBalance - (defaultFee + dustLimit + 100) htlcAmount += htlcSuccessFee(aliceChannel.channelState.FeePerKw) htlc, preimage := createHTLC(0, htlcAmount) if _, err := aliceChannel.AddHTLC(htlc); err != nil { t.Fatalf("alice unable to add htlc: %v", err) } if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { t.Fatalf("bob unable to receive htlc: %v", err) } if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("state transition error: %v", err) } settleIndex, err := bobChannel.SettleHTLC(preimage) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } err = aliceChannel.ReceiveHTLCSettle(preimage, settleIndex) if err != nil { t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } if err := forceStateTransition(bobChannel, aliceChannel); err != nil { t.Fatalf("state transition error: %v", err) } // At the conclusion of this test, in Bob's commitment chains, the // output for Alice's balance should have been removed as dust, leaving // only a single output that will send the remaining funds in the // channel to Bob. commitment := bobChannel.localCommitChain.tip() if len(commitment.txn.TxOut) != 1 { t.Fatalf("incorrect # of outputs: expected %v, got %v", 1, len(commitment.txn.TxOut)) } if aliceChannel.channelState.TotalSatoshisSent != uint64(htlcAmount) { t.Fatalf("alice satoshis sent incorrect: expected %v, got %v", htlcAmount, aliceChannel.channelState.TotalSatoshisSent) } } 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() if err := aliceChannel.channelState.FullSync(); err != nil { t.Fatalf("unable to sync alice's channel: %v", err) } if err := bobChannel.channelState.FullSync(); err != nil { t.Fatalf("unable to sync bob's channel: %v", err) } const numHtlcs = 4 // Alice adds 3 HTLCs to the update log, while Bob adds a single HTLC. var alicePreimage [32]byte copy(alicePreimage[:], bytes.Repeat([]byte{0xaa}, 32)) var bobPreimage [32]byte copy(bobPreimage[:], bytes.Repeat([]byte{0xbb}, 32)) for i := 0; i < 3; i++ { rHash := sha256.Sum256(alicePreimage[:]) h := &lnwire.UpdateAddHTLC{ PaymentHash: rHash, Amount: btcutil.Amount(5000), Expiry: uint32(10), } if _, err := aliceChannel.AddHTLC(h); err != nil { t.Fatalf("unable to add alice's htlc: %v", err) } if _, err := bobChannel.ReceiveHTLC(h); err != nil { t.Fatalf("unable to recv alice's htlc: %v", err) } } rHash := sha256.Sum256(bobPreimage[:]) bobh := &lnwire.UpdateAddHTLC{ PaymentHash: rHash, Amount: btcutil.Amount(5000), Expiry: uint32(10), } if _, err := bobChannel.AddHTLC(bobh); err != nil { t.Fatalf("unable to add bob's htlc: %v", err) } if _, err := aliceChannel.ReceiveHTLC(bobh); err != nil { t.Fatalf("unable to recv bob's htlc: %v", err) } // Next, Alice initiates a state transition to include the HTLC's she // added above in a new commitment state. if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to complete alice's state transition: %v", err) } // Since the HTLC Bob sent wasn't included in Bob's version of the // commitment transaction (but it was in Alice's, as he ACK'd her // changes before creating a new state), Bob needs to trigger another // state update in order to re-sync their states. if err := forceStateTransition(bobChannel, aliceChannel); err != nil { t.Fatalf("unable to complete bob's state transition: %v", err) } // The latest commitment from both sides should have all the HTLCs. numAliceOutgoing := aliceChannel.localCommitChain.tail().outgoingHTLCs numAliceIncoming := aliceChannel.localCommitChain.tail().incomingHTLCs if len(numAliceOutgoing) != 3 { t.Fatalf("expected %v htlcs, instead got %v", 3, numAliceOutgoing) } if len(numAliceIncoming) != 1 { t.Fatalf("expected %v htlcs, instead got %v", 1, numAliceIncoming) } numBobOutgoing := bobChannel.localCommitChain.tail().outgoingHTLCs numBobIncoming := bobChannel.localCommitChain.tail().incomingHTLCs if len(numBobOutgoing) != 1 { t.Fatalf("expected %v htlcs, instead got %v", 1, numBobOutgoing) } if len(numBobIncoming) != 3 { t.Fatalf("expected %v htlcs, instead got %v", 3, numBobIncoming) } // TODO(roasbeef): also ensure signatures were stored // * ensure expiry matches // Now fetch both of the channels created above from disk to simulate a // node restart with persistence. alicePub := aliceChannel.channelState.IdentityPub aliceChannels, err := aliceChannel.channelState.Db.FetchOpenChannels(alicePub) if err != nil { t.Fatalf("unable to fetch channel: %v", err) } bobPub := bobChannel.channelState.IdentityPub bobChannels, err := bobChannel.channelState.Db.FetchOpenChannels(bobPub) if err != nil { t.Fatalf("unable to fetch channel: %v", err) } notifier := aliceChannel.channelEvents aliceChannelNew, err := NewLightningChannel(aliceChannel.signer, notifier, aliceChannel.feeEstimator, aliceChannels[0]) if err != nil { t.Fatalf("unable to create new channel: %v", err) } bobChannelNew, err := NewLightningChannel(bobChannel.signer, notifier, bobChannel.feeEstimator, bobChannels[0]) if err != nil { t.Fatalf("unable to create new channel: %v", err) } if err := initRevocationWindows(aliceChannelNew, bobChannelNew, 3); err != nil { t.Fatalf("unable to init revocation windows: %v", err) } // The state update logs of the new channels and the old channels // should now be identical other than the height the HTLCs were added. if aliceChannel.localUpdateLog.logIndex != aliceChannelNew.localUpdateLog.logIndex { t.Fatalf("alice log counter: expected %v, got %v", aliceChannel.localUpdateLog.logIndex, aliceChannelNew.localUpdateLog.logIndex) } if aliceChannel.remoteUpdateLog.logIndex != aliceChannelNew.remoteUpdateLog.logIndex { t.Fatalf("alice log counter: expected %v, got %v", aliceChannel.remoteUpdateLog.logIndex, aliceChannelNew.remoteUpdateLog.logIndex) } if aliceChannel.localUpdateLog.Len() != aliceChannelNew.localUpdateLog.Len() { t.Fatalf("alice log len: expected %v, got %v", aliceChannel.localUpdateLog.Len(), aliceChannelNew.localUpdateLog.Len()) } if aliceChannel.remoteUpdateLog.Len() != aliceChannelNew.remoteUpdateLog.Len() { t.Fatalf("alice log len: expected %v, got %v", aliceChannel.remoteUpdateLog.Len(), aliceChannelNew.remoteUpdateLog.Len()) } if bobChannel.localUpdateLog.logIndex != bobChannelNew.localUpdateLog.logIndex { t.Fatalf("bob log counter: expected %v, got %v", bobChannel.localUpdateLog.logIndex, bobChannelNew.localUpdateLog.logIndex) } if bobChannel.remoteUpdateLog.logIndex != bobChannelNew.remoteUpdateLog.logIndex { t.Fatalf("bob log counter: expected %v, got %v", bobChannel.remoteUpdateLog.logIndex, bobChannelNew.remoteUpdateLog.logIndex) } if bobChannel.localUpdateLog.Len() != bobChannelNew.localUpdateLog.Len() { t.Fatalf("bob log len: expected %v, got %v", bobChannel.localUpdateLog.Len(), bobChannelNew.localUpdateLog.Len()) } if bobChannel.remoteUpdateLog.Len() != bobChannelNew.remoteUpdateLog.Len() { t.Fatalf("bob log len: expected %v, got %v", bobChannel.remoteUpdateLog.Len(), bobChannelNew.remoteUpdateLog.Len()) } // TODO(roasbeef): expand test to also ensure state revocation log has // proper pk scripts // Newly generated pkScripts for HTLCs should be the same as in the old channel. for _, entry := range aliceChannel.localUpdateLog.updateIndex { htlc := entry.Value.(*PaymentDescriptor) restoredHtlc := aliceChannelNew.localUpdateLog.lookup(htlc.Index) if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) { t.Fatalf("alice ourPkScript in ourLog: expected %X, got %X", htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5]) } if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) { t.Fatalf("alice theirPkScript in ourLog: expected %X, got %X", htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5]) } } for _, entry := range aliceChannel.remoteUpdateLog.updateIndex { htlc := entry.Value.(*PaymentDescriptor) restoredHtlc := aliceChannelNew.remoteUpdateLog.lookup(htlc.Index) if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) { t.Fatalf("alice ourPkScript in theirLog: expected %X, got %X", htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5]) } if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) { t.Fatalf("alice theirPkScript in theirLog: expected %X, got %X", htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5]) } } for _, entry := range bobChannel.localUpdateLog.updateIndex { htlc := entry.Value.(*PaymentDescriptor) restoredHtlc := bobChannelNew.localUpdateLog.lookup(htlc.Index) if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) { t.Fatalf("bob ourPkScript in ourLog: expected %X, got %X", htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5]) } if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) { t.Fatalf("bob theirPkScript in ourLog: expected %X, got %X", htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5]) } } for _, entry := range bobChannel.remoteUpdateLog.updateIndex { htlc := entry.Value.(*PaymentDescriptor) restoredHtlc := bobChannelNew.remoteUpdateLog.lookup(htlc.Index) 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 suceed as both sides have identical. for i := 0; i < 3; i++ { settleIndex, err := bobChannelNew.SettleHTLC(alicePreimage) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = aliceChannelNew.ReceiveHTLCSettle(alicePreimage, settleIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } } settleIndex, err := aliceChannelNew.SettleHTLC(bobPreimage) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = bobChannelNew.ReceiveHTLCSettle(bobPreimage, settleIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } // Similar to the two transitions above, as both Bob and Alice added // entries to the update log before a state transition was initiated by // either side, both sides are required to trigger an update in order // to lock in their changes. if err := forceStateTransition(aliceChannelNew, bobChannelNew); err != nil { t.Fatalf("unable to update commitments: %v", err) } if err := forceStateTransition(bobChannelNew, aliceChannelNew); err != nil { t.Fatalf("unable to update commitments: %v", err) } // The amounts transferred should been updated as per the amounts in // the HTLCs if aliceChannelNew.channelState.TotalSatoshisSent != 15000 { t.Fatalf("expected %v alice satoshis sent, got %v", 15000, aliceChannelNew.channelState.TotalSatoshisSent) } if aliceChannelNew.channelState.TotalSatoshisReceived != 5000 { t.Fatalf("expected %v alice satoshis received, got %v", 5000, aliceChannelNew.channelState.TotalSatoshisReceived) } if bobChannelNew.channelState.TotalSatoshisSent != 5000 { t.Fatalf("expected %v bob satoshis sent, got %v", 5000, bobChannel.channelState.TotalSatoshisSent) } if bobChannelNew.channelState.TotalSatoshisReceived != 15000 { t.Fatalf("expected %v bob satoshis sent, got %v", 15000, bobChannel.channelState.TotalSatoshisSent) } } 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. const htlcAmt = btcutil.SatoshiPerBitcoin var preImage [32]byte copy(preImage[:], bytes.Repeat([]byte{0xaa}, 32)) htlc := &lnwire.UpdateAddHTLC{ PaymentHash: sha256.Sum256(preImage[:]), Amount: htlcAmt, Expiry: 10, } paymentHash := htlc.PaymentHash if _, err := aliceChannel.AddHTLC(htlc); err != nil { t.Fatalf("unable to add alice htlc: %v", err) } if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { t.Fatalf("unable to add bob htlc: %v", err) } if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to create new commitment state: %v", err) } // With the HTLC committed, Alice's balance should reflect the clearing // of the new HTLC. aliceExpectedBalance := btcutil.Amount(btcutil.SatoshiPerBitcoin*4) - calcStaticFee(1) if aliceChannel.channelState.LocalBalance != aliceExpectedBalance { t.Fatalf("Alice's balance is wrong: expected %v, got %v", aliceExpectedBalance, aliceChannel.channelState.LocalBalance) } // Now, with the HTLC committed on both sides, trigger a cancellation // from Bob to Alice, removing the HTLC. htlcCancelIndex, err := bobChannel.FailHTLC(paymentHash) if err != nil { t.Fatalf("unable to cancel HTLC: %v", err) } if err := aliceChannel.ReceiveFailHTLC(htlcCancelIndex); 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.LocalBalance != expectedBalance-calcStaticFee(0) { t.Fatalf("balance is wrong: expected %v, got %v", aliceChannel.channelState.LocalBalance, expectedBalance- calcStaticFee(0)) } if aliceChannel.channelState.RemoteBalance != expectedBalance { t.Fatalf("balance is wrong: expected %v, got %v", aliceChannel.channelState.RemoteBalance, expectedBalance) } if bobChannel.channelState.LocalBalance != expectedBalance { t.Fatalf("balance is wrong: expected %v, got %v", bobChannel.channelState.LocalBalance, expectedBalance) } if bobChannel.channelState.RemoteBalance != expectedBalance-calcStaticFee(0) { t.Fatalf("balance is wrong: expected %v, got %v", bobChannel.channelState.RemoteBalance, 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.FeePerKw) bobFeeRate := uint64(bobChannel.channelState.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 btcutil.Amount) { aliceChannel.channelState.LocalBalance = aliceBalance aliceChannel.channelState.RemoteBalance = bobBalance bobChannel.channelState.LocalBalance = bobBalance bobChannel.channelState.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. aliceSig, _, err := aliceChannel.CreateCloseProposal(aliceFeeRate, aliceDeliveryScript, bobDeliveryScript) if err != nil { t.Fatalf("unable to close channel: %v", err) } aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll)) bobSig, _, err := bobChannel.CreateCloseProposal(bobFeeRate, 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, bobFeeRate) 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 := btcutil.Amount(btcutil.SatoshiPerBitcoin) bobBal := btcutil.Amount(250) setBalances(aliceBal, bobBal) // Attempt another cooperative channel closure. It should succeed // without any issues. aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFeeRate, aliceDeliveryScript, bobDeliveryScript) if err != nil { t.Fatalf("unable to close channel: %v", err) } aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll)) bobSig, _, err = bobChannel.CreateCloseProposal(bobFeeRate, 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, bobFeeRate) if err != nil { t.Fatalf("unable to accept channel close: %v", err) } // The closure transaction should only have a single output, and that // output should be Alice's balance. if len(closeTx.TxOut) != 1 { t.Fatalf("close tx has wrong number of outputs: expected %v "+ "got %v", 1, len(closeTx.TxOut)) } if closeTx.TxOut[0].Value != int64(aliceBal-calcStaticFee(0)) { t.Fatalf("alice's balance is incorrect: expected %v, got %v", int64(aliceBal-calcStaticFee(0)), closeTx.TxOut[0].Value) } // Finally, we'll modify the current balances and dust limits such that // Alice's current balance is _below_ his her limit. setBalances(bobBal, aliceBal) resetChannelState() // Our final attempt at another cooperative channel closure. It should // succeed without any issues. aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFeeRate, aliceDeliveryScript, bobDeliveryScript) if err != nil { t.Fatalf("unable to close channel: %v", err) } aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll)) bobSig, _, err = bobChannel.CreateCloseProposal(bobFeeRate, 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, bobFeeRate) 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) { t.Fatalf("bob's balance is incorrect: expected %v, got %v", aliceBal, 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) { 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) { // 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.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.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.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.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 alive'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) { // 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.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.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 alive'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.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.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 alive'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) { // 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) { // 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.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.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.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.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 alive's revocation: %v", err) } }