From ebe05f6568d91acd5905914430ac94c8a6569e76 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 14 Jul 2017 20:38:35 +0200 Subject: [PATCH] lnwallet: add update_fee message support. This commit adds the possibility for the initiator of a channel to send the update_fee message, as specified in BOLT#2. After the message is sent and both parties have committed to the updated fee, all new commitment messages in the channel will use the specified fee. --- channeldb/channel.go | 18 ++ lnwallet/channel.go | 110 +++++++++- lnwallet/channel_test.go | 433 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 552 insertions(+), 9 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index d72e222c..f3d7f6f2 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -428,6 +428,7 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *wire.MsgTx, c.NumUpdates = delta.UpdateNum c.Htlcs = delta.Htlcs c.CommitFee = delta.CommitFee + c.FeePerKw = delta.FeePerKw // First we'll write out the current latest dynamic channel // state: the current channel balance, the number of updates, @@ -445,6 +446,9 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *wire.MsgTx, if err := putChanCommitFee(chanBucket, c); err != nil { return err } + if err := putChanFeePerKw(chanBucket, c); err != nil { + return err + } if err := putChanCommitTxns(nodeChanBucket, c); err != nil { return err } @@ -517,6 +521,10 @@ type ChannelDelta struct { // initiator's balance at this point in the commitment chain. CommitFee btcutil.Amount + // FeePerKw is the fee per kw used to calculate the commit fee at this point + // in the commit chain. + FeePerKw btcutil.Amount + // UpdateNum is the update number that this ChannelDelta represents the // total number of commitment updates to this point. This can be viewed // as sort of a "commitment height" as this number is monotonically @@ -2206,6 +2214,11 @@ func serializeChannelDelta(w io.Writer, delta *ChannelDelta) error { return err } + byteOrder.PutUint64(scratch[:], uint64(delta.FeePerKw)) + if _, err := w.Write(scratch[:]); err != nil { + return err + } + return nil } @@ -2249,6 +2262,11 @@ func deserializeChannelDelta(r io.Reader) (*ChannelDelta, error) { } delta.CommitFee = btcutil.Amount(byteOrder.Uint64(scratch[:])) + if _, err := r.Read(scratch[:]); err != nil { + return nil, err + } + delta.FeePerKw = btcutil.Amount(byteOrder.Uint64(scratch[:])) + return delta, nil } diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 554bc92e..6b5d6a94 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -243,6 +243,10 @@ type commitment struct { // back and recalculated for each new update to the channel state. fee btcutil.Amount + // feePerKw is the fee per kw used to calculate this commitment + // transaction's fee. + feePerKw btcutil.Amount + // htlcs is the set of HTLCs which remain unsettled within this // commitment. outgoingHTLCs []PaymentDescriptor @@ -263,6 +267,7 @@ func (c *commitment) toChannelDelta(ourCommit bool) (*channeldb.ChannelDelta, er RemoteBalance: c.theirBalance, UpdateNum: c.height, CommitFee: c.fee, + FeePerKw: c.feePerKw, Htlcs: make([]*channeldb.HTLC, 0, numHtlcs), } @@ -631,6 +636,16 @@ type LightningChannel struct { localUpdateLog *updateLog remoteUpdateLog *updateLog + // pendingFeeUpdate is set to the fee-per-kw we last sent (if we are + // channel initiator) or received (if non-initiator) in an update fee + // message, which haven't yet been included in a commitment. + // It will be nil if no fee update is un-committed. + pendingFeeUpdate *btcutil.Amount + + // pendingAckFeeUpdate is set to the last committed fee update which is + // not yet ACKed. Set to nil if no such update. + pendingAckFeeUpdate *btcutil.Amount + // rHashMap is a map with PaymentHashes pointing to their respective // PaymentDescriptors. We insert *PaymentDescriptors whenever we // receive HTLCs. When a state transition happens (settling or @@ -727,6 +742,7 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier, theirBalance: state.TheirBalance, theirMessageIndex: 0, fee: state.CommitFee, + feePerKw: state.FeePerKw, }) walletLog.Debugf("ChannelPoint(%v), starting local commitment: %v", state.ChanID, newLogClosure(func() string { @@ -748,6 +764,7 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier, theirBalance: state.TheirBalance, theirMessageIndex: 0, fee: state.CommitFee, + feePerKw: state.FeePerKw, } if logTail == nil { remoteCommitment.height = 0 @@ -1329,12 +1346,45 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, numHTLCs++ } + // Initiate feePerKw to the last committed fee for this chain. + feePerKw := commitChain.tail().feePerKw + + // Check if any fee updates have taken place since that last commitment. + if lc.channelState.IsInitiator { + + // The case where we sent an update_fee message since our last + // commitment, and now we are signing that one. + if remoteChain && lc.pendingFeeUpdate != nil { + feePerKw = *lc.pendingFeeUpdate + } + + // The case where we committed to a sent fee update, and now we + // got a commitment that ACKed that update. + if !remoteChain && lc.pendingAckFeeUpdate != nil { + feePerKw = *lc.pendingAckFeeUpdate + } + } else { + + // We received a fee update since last received commitment, so + // this received commitment will sign that update. + if !remoteChain && lc.pendingFeeUpdate != nil { + feePerKw = *lc.pendingFeeUpdate + } + + // Earlier we received a commitment that signed an earlier fee + // update, and now we must ACK that update. + if remoteChain && lc.pendingAckFeeUpdate != nil { + feePerKw = *lc.pendingAckFeeUpdate + } + } + // Next, we'll calculate the fee for the commitment transaction based // on its total weight. Once we have the total weight, we'll multiply // by the current fee-per-kw, then divide by 1000 to get the proper // fee. totalCommitWeight := commitWeight + btcutil.Amount(htlcWeight*numHTLCs) - commitFee := lc.channelState.FeePerKw * totalCommitWeight / 1000 + + commitFee := (feePerKw * totalCommitWeight) / 1000 commitFee -= dustFees // Currently, within the protocol, the initiator always pays the fees. @@ -1421,6 +1471,7 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, theirMessageIndex: theirLogIndex, theirBalance: theirBalance, fee: commitFee, + feePerKw: feePerKw, } // In order to ensure _none_ of the HTLC's associated with this new @@ -1685,6 +1736,16 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, error) { // latest commitment update. lc.remoteCommitChain.addCommitment(newCommitView) + // If we are the channel initiator then we would have signed any sent + // fee update at this point, so mark this update as pending ACK, and set + // pendingFeeUpdate to nil. We can do this since we know we won't sign + // any new commitment before receiving a revoke_and_ack, because of the + // revocation window of 1. + if lc.channelState.IsInitiator { + lc.pendingAckFeeUpdate = lc.pendingFeeUpdate + lc.pendingFeeUpdate = nil + } + // Move the now used revocation hash from the unused set to the used set. // We only do this at the end, as we know at this point the procedure will // succeed without any errors. @@ -1858,6 +1919,16 @@ func (lc *LightningChannel) ReceiveNewCommitment(rawSig []byte) error { localCommitmentView.sig = rawSig lc.localCommitChain.addCommitment(localCommitmentView) + // If we are not channel initiator, then the commitment just received + // would've signed any received fee update since last commitment. Mark + // any such fee update as pending ACK (so we remember to ACK it on our + // next commitment), and set pendingFeeUpdate to nil. We can do this + // since we won't receive any new commitment before ACKing. + if !lc.channelState.IsInitiator { + lc.pendingAckFeeUpdate = lc.pendingFeeUpdate + lc.pendingFeeUpdate = nil + } + // Finally we'll keep track of the current pending index for the remote // party so we can ACK up to this value once we revoke our current // commitment. @@ -2763,6 +2834,43 @@ func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot { return lc.channelState.Snapshot() } +// UpdateFee initiates a fee update for this channel. Must only be called by +// the channel initiator, and must be called before sending update_fee to +// the remote. +func (lc *LightningChannel) UpdateFee(feePerKw btcutil.Amount) error { + lc.Lock() + defer lc.Unlock() + + // Only initiator can send fee update, so trying to send one as + // non-initiatior will fail. + if !lc.channelState.IsInitiator { + return fmt.Errorf("local fee update as non-initiatior") + } + + lc.pendingFeeUpdate = &feePerKw + + return nil +} + +// ReceiveUpdateFee handles an updated fee sent from remote. This method will +// return an error if called as channel initiator. +func (lc *LightningChannel) ReceiveUpdateFee(feePerKw btcutil.Amount) error { + lc.Lock() + defer lc.Unlock() + + // Only initiator can send fee update, and we must fail if we receive + // fee update as initiatior + if lc.channelState.IsInitiator { + return fmt.Errorf("received fee update as initiatior") + } + + // TODO(halseth): should fail if fee update is unreasonable, + // as specified in BOLT#2. + lc.pendingFeeUpdate = &feePerKw + + return nil +} + // CreateCommitTx creates a commitment transaction, spending from specified // funding output. The commitment transaction contains two outputs: one paying // to the "owner" of the commitment transaction which can be spent after a diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 552ee32d..3324c458 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -402,8 +402,8 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { } // First Alice adds the outgoing HTLC to her local channel's state - // update log. Then Alice sends this wire message over to Bob who also - // adds this htlc to his local state update log. + // 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) } @@ -411,22 +411,31 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { t.Fatalf("unable to recv htlc: %v", err) } - // Next alice commits this change by sending a signature message. + // 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, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("alice unable to sign commitment: %v", err) } - // Bob receives this signature message, revokes his prior commitment - // given to him by Alice,a nd then finally send a signature for Alice's - // commitment transaction. + // 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. if err := bobChannel.ReceiveNewCommitment(aliceSig); 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, err := bobChannel.SignNextCommitment() if err != nil { t.Fatalf("bob unable to sign alice's commitment: %v", err) @@ -441,12 +450,14 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { t.Fatalf("alice forwards %v htlcs, should forward none: ", len(htlcs)) } - // Alice then processes bob's signature, and generates a revocation for - // bob. + // 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. if err := aliceChannel.ReceiveNewCommitment(bobSig); 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) @@ -1599,3 +1610,409 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { 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, 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) + 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, 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. + if err := bobChannel.ReceiveNewCommitment(aliceSig); 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, 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); 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, 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. + if err := aliceChannel.ReceiveNewCommitment(bobSig); 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, 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. + if err := bobChannel.ReceiveNewCommitment(aliceSig); 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, 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. + if err := aliceChannel.ReceiveNewCommitment(bobSig); 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 revokation 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-initiatior +// 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, 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. + if err := bobChannel.ReceiveNewCommitment(aliceSig); 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, 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); 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) + } +}