From 7a4817b06638eea48bdc2f9bb1274e17c168c1c2 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 5 Apr 2018 19:14:26 +0200 Subject: [PATCH 1/5] fundingmanager: preserve custom remoteCsvDelay This commit fixes a bug that would cause the local and remote commitment to be incompatible when using custom remote CSV delay when opening a channel. This would happen because we wouldn't store the CSV value before we received the FundingAccept message, and here we would use the default value. This commit fixes this by making the csv value part of the reservationWithCtx struct, such that it can be recorded for use when the FundingAccept msg comes back. --- fundingmanager.go | 55 +++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/fundingmanager.go b/fundingmanager.go index 15e1912f..6e564201 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -79,7 +79,8 @@ type reservationWithCtx struct { reservation *lnwallet.ChannelReservation peerAddress *lnwire.NetAddress - chanAmt btcutil.Amount + chanAmt btcutil.Amount + remoteCsvDelay uint16 updateMtx sync.RWMutex lastUpdated time.Time @@ -995,8 +996,8 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { // We'll also validate and apply all the constraints the initiating // party is attempting to dictate for our commitment transaction. err = reservation.CommitConstraints( - uint16(msg.CsvDelay), msg.MaxAcceptedHTLCs, - msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve, + msg.CsvDelay, msg.MaxAcceptedHTLCs, msg.MaxValueInFlight, + msg.HtlcMinimum, msg.ChannelReserve, ) if err != nil { fndgLog.Errorf("Unaccaptable channel constraints: %v", err) @@ -1011,6 +1012,10 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { "amt=%v, push_amt=%v", numConfsReq, fmsg.msg.PendingChannelID, amt, msg.PushAmount) + // Using the RequiredRemoteDelay closure, we'll compute the remote CSV + // delay we require given the total amount of funds within the channel. + remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt) + // Once the reservation has been created successfully, we add it to // this peer's map of pending reservations to track this particular // reservation until either abort or completion. @@ -1019,10 +1024,11 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { f.activeReservations[peerIDKey] = make(pendingChannels) } resCtx := &reservationWithCtx{ - reservation: reservation, - chanAmt: amt, - err: make(chan error, 1), - peerAddress: fmsg.peerAddress, + reservation: reservation, + chanAmt: amt, + remoteCsvDelay: remoteCsvDelay, + err: make(chan error, 1), + peerAddress: fmsg.peerAddress, } f.activeReservations[peerIDKey][msg.PendingChannelID] = resCtx f.resMtx.Unlock() @@ -1030,10 +1036,6 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { // Update the timestamp once the fundingOpenMsg has been handled. defer resCtx.updateTimestamp() - // Using the RequiredRemoteDelay closure, we'll compute the remote CSV - // delay we require given the total amount of funds within the channel. - remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt) - // We'll also generate our required constraints for the remote party, chanReserve := f.cfg.RequiredRemoteChanReserve(amt) maxValue := f.cfg.RequiredRemoteMaxValue(amt) @@ -1148,8 +1150,8 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) { // they've specified for commitment states we can create. resCtx.reservation.SetNumConfsRequired(uint16(msg.MinAcceptDepth)) err = resCtx.reservation.CommitConstraints( - uint16(msg.CsvDelay), msg.MaxAcceptedHTLCs, - msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve, + msg.CsvDelay, msg.MaxAcceptedHTLCs, msg.MaxValueInFlight, + msg.HtlcMinimum, msg.ChannelReserve, ) if err != nil { fndgLog.Warnf("Unacceptable channel constraints: %v", err) @@ -1179,6 +1181,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) { MinHTLC: msg.HtlcMinimum, MaxAcceptedHtlcs: maxHtlcs, }, + CsvDelay: resCtx.remoteCsvDelay, MultiSigKey: keychain.KeyDescriptor{ PubKey: copyPubKey(msg.FundingKey), }, @@ -1196,7 +1199,6 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) { }, }, } - remoteContribution.CsvDelay = f.cfg.RequiredRemoteDelay(resCtx.chanAmt) err = resCtx.reservation.ProcessContribution(remoteContribution) if err != nil { fndgLog.Errorf("Unable to process contribution from %v: %v", @@ -2538,6 +2540,13 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { fndgLog.Infof("Target commit tx sat/kw for pendingID(%x): %v", chanID, int64(commitFeePerKw)) + // If the remote CSV delay was not set in the open channel request, + // we'll use the RequiredRemoteDelay closure to compute the delay we + // require given the total amount of funds within the channel. + if remoteCsvDelay == 0 { + remoteCsvDelay = f.cfg.RequiredRemoteDelay(capacity) + } + // If a pending channel map for this peer isn't already created, then // we create one, ultimately allowing us to track this pending // reservation within the target peer. @@ -2548,11 +2557,12 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { } resCtx := &reservationWithCtx{ - chanAmt: capacity, - reservation: reservation, - peerAddress: msg.peerAddress, - updates: msg.updates, - err: msg.err, + chanAmt: capacity, + remoteCsvDelay: remoteCsvDelay, + reservation: reservation, + peerAddress: msg.peerAddress, + updates: msg.updates, + err: msg.err, } f.activeReservations[peerIDKey][chanID] = resCtx f.resMtx.Unlock() @@ -2560,13 +2570,6 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // Update the timestamp once the initFundingMsg has been handled. defer resCtx.updateTimestamp() - // If the remote CSV delay was not set in the open channel request, - // we'll use the RequiredRemoteDelay closure to compute the delay we - // require given the total amount of funds within the channel. - if remoteCsvDelay == 0 { - remoteCsvDelay = f.cfg.RequiredRemoteDelay(capacity) - } - // If no minimum HTLC value was specified, use the default one. if minHtlc == 0 { minHtlc = f.cfg.DefaultRoutingPolicy.MinHTLC From cbfba79f46087bb0be1f40bb729adc0f8ced7bcc Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 5 Apr 2018 19:17:17 +0200 Subject: [PATCH 2/5] fundingmanager test: add test for custom channel parameters This commit adds TestFundingManagerCustomChannelParameters, which checks that custom channel parameters specified at channel creation is preserved and recorded correctly on both sides of the channel. --- fundingmanager_test.go | 156 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/fundingmanager_test.go b/fundingmanager_test.go index 7090a3e6..c8a20e6a 100644 --- a/fundingmanager_test.go +++ b/fundingmanager_test.go @@ -1934,3 +1934,159 @@ func TestFundingManagerPrivateRestart(t *testing.T) { // from the database, as the channel is announced. assertNoChannelState(t, alice, bob, fundingOutPoint) } + +// TestFundingManagerCustomChannelParameters checks that custom requirements we +// specify during the channel funding flow is preserved correcly on both sides. +func TestFundingManagerCustomChannelParameters(t *testing.T) { + alice, bob := setupFundingManagers(t) + defer tearDownFundingManagers(t, alice, bob) + + // This is the custom CSV delay we'll use. + const csvDelay = 67 + + // We will consume the channel updates as we go, so no buffering is + // needed. + updateChan := make(chan *lnrpc.OpenStatusUpdate) + + // Create a funding request with the custom parameters and start the + // workflow. + errChan := make(chan error, 1) + initReq := &openChanReq{ + targetPubkey: bob.privKey.PubKey(), + chainHash: *activeNetParams.GenesisHash, + localFundingAmt: 5000000, + pushAmt: lnwire.NewMSatFromSatoshis(0), + private: false, + remoteCsvDelay: csvDelay, + updates: updateChan, + err: errChan, + } + + alice.fundingMgr.initFundingWorkflow(bobAddr, initReq) + + // Alice should have sent the OpenChannel message to Bob. + var aliceMsg lnwire.Message + select { + case aliceMsg = <-alice.msgChan: + case err := <-initReq.err: + t.Fatalf("error init funding workflow: %v", err) + case <-time.After(time.Second * 5): + t.Fatalf("alice did not send OpenChannel message") + } + + openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel) + if !ok { + errorMsg, gotError := aliceMsg.(*lnwire.Error) + if gotError { + t.Fatalf("expected OpenChannel to be sent "+ + "from bob, instead got error: %v", + lnwire.ErrorCode(errorMsg.Data[0])) + } + t.Fatalf("expected OpenChannel to be sent from "+ + "alice, instead got %T", aliceMsg) + } + + // Check that the custom CSV delay is sent as part of OpenChannel. + if openChannelReq.CsvDelay != csvDelay { + t.Fatalf("expected OpenChannel to have CSV delay %v, got %v", + csvDelay, openChannelReq.CsvDelay) + } + + chanID := openChannelReq.PendingChannelID + + // Let Bob handle the init message. + bob.fundingMgr.processFundingOpen(openChannelReq, aliceAddr) + + // Bob should answer with an AcceptChannel message. + acceptChannelResponse := assertFundingMsgSent( + t, bob.msgChan, "AcceptChannel", + ).(*lnwire.AcceptChannel) + + // Bob should require the default delay of 4. + if acceptChannelResponse.CsvDelay != 4 { + t.Fatalf("expected AcceptChannel to have CSV delay %v, got %v", + 4, acceptChannelResponse.CsvDelay) + } + + // Forward the response to Alice. + alice.fundingMgr.processFundingAccept(acceptChannelResponse, bobAddr) + + // Alice responds with a FundingCreated message. + fundingCreated := assertFundingMsgSent( + t, alice.msgChan, "FundingCreated", + ).(*lnwire.FundingCreated) + + // Give the message to Bob. + bob.fundingMgr.processFundingCreated(fundingCreated, aliceAddr) + + // Finally, Bob should send the FundingSigned message. + fundingSigned := assertFundingMsgSent( + t, bob.msgChan, "FundingSigned", + ).(*lnwire.FundingSigned) + + // Forward the signature to Alice. + alice.fundingMgr.processFundingSigned(fundingSigned, bobAddr) + + // After Alice processes the singleFundingSignComplete message, she will + // broadcast the funding transaction to the network. We expect to get a + // channel update saying the channel is pending. + var pendingUpdate *lnrpc.OpenStatusUpdate + select { + case pendingUpdate = <-updateChan: + case <-time.After(time.Second * 5): + t.Fatalf("alice did not send OpenStatusUpdate_ChanPending") + } + + _, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending) + if !ok { + t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanPending") + } + + // Wait for Alice to published the funding tx to the network. + select { + case <-alice.publTxChan: + case <-time.After(time.Second * 5): + t.Fatalf("alice did not publish funding tx") + } + + // Helper method for checking the CSV delay stored for a reservation. + assertDelay := func(resCtx *reservationWithCtx, + ourDelay, theirDelay uint16) error { + + ourCsvDelay := resCtx.reservation.OurContribution().CsvDelay + if ourCsvDelay != ourDelay { + return fmt.Errorf("expected our CSV delay to be %v, "+ + "was %v", ourDelay, ourCsvDelay) + } + + theirCsvDelay := resCtx.reservation.TheirContribution().CsvDelay + if theirCsvDelay != theirDelay { + return fmt.Errorf("expected their CSV delay to be %v, "+ + "was %v", theirDelay, theirCsvDelay) + } + return nil + } + + // Check that the custom channel parameters were properly set in the + // channel reservation. + resCtx, err := alice.fundingMgr.getReservationCtx(bobPubKey, chanID) + if err != nil { + t.Fatalf("unable to find ctx: %v", err) + } + + // Alice's CSV delay should be 4 since Bob sent the fedault value, and + // Bob's should be 67 since Alice sent the custom value. + if err := assertDelay(resCtx, 4, csvDelay); err != nil { + t.Fatal(err) + } + + // Also make sure the parameters are properly set on Bob's end. + resCtx, err = bob.fundingMgr.getReservationCtx(alicePubKey, chanID) + if err != nil { + t.Fatalf("unable to find ctx: %v", err) + } + + if err := assertDelay(resCtx, csvDelay, 4); err != nil { + t.Fatal(err) + } +} From ca0b4cb8c568da852e1b4d08a192d11405119697 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 5 Apr 2018 19:48:41 +0200 Subject: [PATCH 3/5] fundingmanager: preserve remote MinHtlc during funding flow This commit fixes a bug within the funding manager, where we would use the wrong min_htlc_value parameter. Instead of attributing the custom passed value for MinHtlc to the remote's constraints, we would add it to our own constraints. --- fundingmanager.go | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/fundingmanager.go b/fundingmanager.go index 6e564201..2aac5b89 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -79,8 +79,11 @@ type reservationWithCtx struct { reservation *lnwallet.ChannelReservation peerAddress *lnwire.NetAddress - chanAmt btcutil.Amount + chanAmt btcutil.Amount + + // Constraints we require for the remote. remoteCsvDelay uint16 + remoteMinHtlc lnwire.MilliSatoshi updateMtx sync.RWMutex lastUpdated time.Time @@ -1006,7 +1009,6 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { ) return } - reservation.RegisterMinHTLC(f.cfg.DefaultRoutingPolicy.MinHTLC) fndgLog.Infof("Requiring %v confirmations for pendingChan(%x): "+ "amt=%v, push_amt=%v", numConfsReq, fmsg.msg.PendingChannelID, @@ -1016,6 +1018,12 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { // delay we require given the total amount of funds within the channel. remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt) + // We'll also generate our required constraints for the remote party, + chanReserve := f.cfg.RequiredRemoteChanReserve(amt) + maxValue := f.cfg.RequiredRemoteMaxValue(amt) + maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(amt) + minHtlc := f.cfg.DefaultRoutingPolicy.MinHTLC + // Once the reservation has been created successfully, we add it to // this peer's map of pending reservations to track this particular // reservation until either abort or completion. @@ -1027,6 +1035,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { reservation: reservation, chanAmt: amt, remoteCsvDelay: remoteCsvDelay, + remoteMinHtlc: minHtlc, err: make(chan error, 1), peerAddress: fmsg.peerAddress, } @@ -1036,11 +1045,6 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { // Update the timestamp once the fundingOpenMsg has been handled. defer resCtx.updateTimestamp() - // We'll also generate our required constraints for the remote party, - chanReserve := f.cfg.RequiredRemoteChanReserve(amt) - maxValue := f.cfg.RequiredRemoteMaxValue(amt) - maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(amt) - // With our parameters set, we'll now process their contribution so we // can move the funding workflow ahead. remoteContribution := &lnwallet.ChannelContribution{ @@ -1051,7 +1055,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { DustLimit: msg.DustLimit, MaxPendingAmount: maxValue, ChanReserve: chanReserve, - MinHTLC: msg.HtlcMinimum, + MinHTLC: minHtlc, MaxAcceptedHtlcs: maxHtlcs, }, CsvDelay: remoteCsvDelay, @@ -1094,7 +1098,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { MaxValueInFlight: maxValue, ChannelReserve: chanReserve, MinAcceptDepth: uint32(numConfsReq), - HtlcMinimum: ourContribution.MinHTLC, + HtlcMinimum: minHtlc, CsvDelay: remoteCsvDelay, MaxAcceptedHTLCs: maxHtlcs, FundingKey: ourContribution.MultiSigKey.PubKey, @@ -1178,7 +1182,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) { DustLimit: msg.DustLimit, MaxPendingAmount: maxValue, ChanReserve: chanReserve, - MinHTLC: msg.HtlcMinimum, + MinHTLC: resCtx.remoteMinHtlc, MaxAcceptedHtlcs: maxHtlcs, }, CsvDelay: resCtx.remoteCsvDelay, @@ -2547,6 +2551,11 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { remoteCsvDelay = f.cfg.RequiredRemoteDelay(capacity) } + // If no minimum HTLC value was specified, use the default one. + if minHtlc == 0 { + minHtlc = f.cfg.DefaultRoutingPolicy.MinHTLC + } + // If a pending channel map for this peer isn't already created, then // we create one, ultimately allowing us to track this pending // reservation within the target peer. @@ -2559,6 +2568,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { resCtx := &reservationWithCtx{ chanAmt: capacity, remoteCsvDelay: remoteCsvDelay, + remoteMinHtlc: minHtlc, reservation: reservation, peerAddress: msg.peerAddress, updates: msg.updates, @@ -2570,14 +2580,8 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // Update the timestamp once the initFundingMsg has been handled. defer resCtx.updateTimestamp() - // If no minimum HTLC value was specified, use the default one. - if minHtlc == 0 { - minHtlc = f.cfg.DefaultRoutingPolicy.MinHTLC - } - // Once the reservation has been created, and indexed, queue a funding // request to the remote peer, kicking off the funding workflow. - reservation.RegisterMinHTLC(minHtlc) ourContribution := reservation.OurContribution() // Finally, we'll use the current value of the channels and our default @@ -2598,7 +2602,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { DustLimit: ourContribution.DustLimit, MaxValueInFlight: maxValue, ChannelReserve: chanReserve, - HtlcMinimum: ourContribution.MinHTLC, + HtlcMinimum: minHtlc, FeePerKiloWeight: uint32(commitFeePerKw), CsvDelay: remoteCsvDelay, MaxAcceptedHTLCs: maxHtlcs, From 3d557678383f4a8cedbebda1f876fc0892ff32fc Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 5 Apr 2018 19:52:03 +0200 Subject: [PATCH 4/5] lnwallet/reservation: remove RegisterMinHTLC We remove this method, as our minHtlc value is set using the CommitConstraints method. --- lnwallet/reservation.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 2865c74b..554b691a 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -276,16 +276,6 @@ func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) { r.partialState.NumConfsRequired = numConfs } -// RegisterMinHTLC registers our desired amount for the smallest acceptable -// HTLC we'll accept within this channel. Any HTLC's that are extended which -// are below this value will SHOULD be rejected. -func (r *ChannelReservation) RegisterMinHTLC(minHTLC lnwire.MilliSatoshi) { - r.Lock() - defer r.Unlock() - - r.ourContribution.MinHTLC = minHTLC -} - // CommitConstraints takes the constraints that the remote party specifies for // the type of commitments that we can generate for them. These constraints // include several parameters that serve as flow control restricting the amount From ed2cfd74c1d25b396867f295d9ab6b84a5a29a7a Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 5 Apr 2018 19:53:06 +0200 Subject: [PATCH 5/5] fundingmanager test: test that MinHtlc is preserved during flow --- fundingmanager_test.go | 52 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/fundingmanager_test.go b/fundingmanager_test.go index c8a20e6a..62376c86 100644 --- a/fundingmanager_test.go +++ b/fundingmanager_test.go @@ -16,6 +16,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" + "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" @@ -282,6 +283,12 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, return nil, fmt.Errorf("unable to find channel") }, + DefaultRoutingPolicy: htlcswitch.ForwardingPolicy{ + MinHTLC: 5, + BaseFee: 100, + FeeRate: 1000, + TimeLockDelta: 10, + }, NumRequiredConfs: func(chanAmt btcutil.Amount, pushAmt lnwire.MilliSatoshi) uint16 { return 3 @@ -1941,8 +1948,9 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) { alice, bob := setupFundingManagers(t) defer tearDownFundingManagers(t, alice, bob) - // This is the custom CSV delay we'll use. + // This is the custom parameters we'll use. const csvDelay = 67 + const minHtlc = 1234 // We will consume the channel updates as we go, so no buffering is // needed. @@ -1957,6 +1965,7 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) { localFundingAmt: 5000000, pushAmt: lnwire.NewMSatFromSatoshis(0), private: false, + minHtlc: minHtlc, remoteCsvDelay: csvDelay, updates: updateChan, err: errChan, @@ -1992,6 +2001,12 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) { csvDelay, openChannelReq.CsvDelay) } + // Check that the custom minHTLC value is sent. + if openChannelReq.HtlcMinimum != minHtlc { + t.Fatalf("expected OpenChannel to have minHtlc %v, got %v", + minHtlc, openChannelReq.HtlcMinimum) + } + chanID := openChannelReq.PendingChannelID // Let Bob handle the init message. @@ -2008,6 +2023,12 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) { 4, acceptChannelResponse.CsvDelay) } + // And the default MinHTLC value of 5. + if acceptChannelResponse.HtlcMinimum != 5 { + t.Fatalf("expected AcceptChannel to have minHtlc %v, got %v", + 5, acceptChannelResponse.HtlcMinimum) + } + // Forward the response to Alice. alice.fundingMgr.processFundingAccept(acceptChannelResponse, bobAddr) @@ -2067,6 +2088,25 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) { return nil } + // Helper method for checking the MinHtlc value stored for a + // reservation. + assertMinHtlc := func(resCtx *reservationWithCtx, + expOurMinHtlc, expTheirMinHtlc lnwire.MilliSatoshi) error { + + ourMinHtlc := resCtx.reservation.OurContribution().MinHTLC + if ourMinHtlc != expOurMinHtlc { + return fmt.Errorf("expected our minHtlc to be %v, "+ + "was %v", expOurMinHtlc, ourMinHtlc) + } + + theirMinHtlc := resCtx.reservation.TheirContribution().MinHTLC + if theirMinHtlc != expTheirMinHtlc { + return fmt.Errorf("expected their minHtlc to be %v, "+ + "was %v", expTheirMinHtlc, theirMinHtlc) + } + return nil + } + // Check that the custom channel parameters were properly set in the // channel reservation. resCtx, err := alice.fundingMgr.getReservationCtx(bobPubKey, chanID) @@ -2080,6 +2120,12 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) { t.Fatal(err) } + // The minimum HTLC value Alice can offer should be 5, and the minimum + // Bob can offer should be 1234. + if err := assertMinHtlc(resCtx, 5, minHtlc); err != nil { + t.Fatal(err) + } + // Also make sure the parameters are properly set on Bob's end. resCtx, err = bob.fundingMgr.getReservationCtx(alicePubKey, chanID) if err != nil { @@ -2089,4 +2135,8 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) { if err := assertDelay(resCtx, csvDelay, 4); err != nil { t.Fatal(err) } + + if err := assertMinHtlc(resCtx, minHtlc, 5); err != nil { + t.Fatal(err) + } }