From 24ad3e17de050fb917877a316ac9a20896062043 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Sun, 26 Nov 2017 13:32:57 -0600 Subject: [PATCH] lnwallet: reject funding flows if local amount is insufficient w.r.t fees --- lnwallet/interface_test.go | 32 +++++++++++++++++++++++++++++++- lnwallet/reservation.go | 16 ++++++++++++++-- lnwallet/wallet.go | 8 ++++++-- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 26cfaf86..a2244b5a 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" "time" @@ -501,9 +502,12 @@ func testCancelNonExistantReservation(miner *rpctest.Harness, } // Create our own reservation, give it some ID. - res := lnwallet.NewChannelReservation( + res, err := lnwallet.NewChannelReservation( 1000, 1000, feeRate, alice, 22, 10, &testHdSeed, ) + if err != nil { + t.Fatalf("unable to create res: %v", err) + } // Attempt to cancel this reservation. This should fail, we know // nothing of it. @@ -512,6 +516,28 @@ func testCancelNonExistantReservation(miner *rpctest.Harness, } } +func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness, + alice, _ *lnwallet.LightningWallet, t *testing.T) { + + // We'll attempt to create a new reservation with an extremely high fee + // rate. This should push our balance into the negative and result in a + // failure to create the reservation. + fundingAmount := btcutil.Amount(4 * 1e8) + feePerKw := btcutil.Amount(btcutil.SatoshiPerBitcoin * 10) + _, err := alice.InitChannelReservation( + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, + ) + switch { + case err == nil: + t.Fatalf("initialization should've failed due to " + + "insufficient local amount") + + case !strings.Contains(err.Error(), "local output is too small"): + t.Fatalf("incorrect error: %v", err) + } +} + func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContribution) { _, _, line, _ := runtime.Caller(1) @@ -1276,6 +1302,10 @@ type walletTestCase struct { } var walletTests = []walletTestCase{ + { + name: "insane fee reject", + test: testReservationInitiatorBalanceBelowDustCancel, + }, { name: "single funding workflow", test: testSingleFunderReservationWorkflow, diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 77325e04..4b6d858c 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -1,6 +1,7 @@ package lnwallet import ( + "fmt" "net" "sync" @@ -136,7 +137,7 @@ type ChannelReservation struct { // lnwallet.InitChannelReservation interface. func NewChannelReservation(capacity, fundingAmt, commitFeePerKw btcutil.Amount, wallet *LightningWallet, id uint64, pushMSat lnwire.MilliSatoshi, - chainHash *chainhash.Hash) *ChannelReservation { + chainHash *chainhash.Hash) (*ChannelReservation, error) { var ( ourBalance lnwire.MilliSatoshi @@ -181,6 +182,17 @@ func NewChannelReservation(capacity, fundingAmt, commitFeePerKw btcutil.Amount, initiator = true } + // If we're the initiator and our starting balance within the channel + // after we take account of fees is below dust, then we'll reject this + // channel creation request. + // + // TODO(roasbeef): reject if 30% goes to fees? dust channel + if initiator && ourBalance.ToSatoshis() <= DefaultDustLimit() { + return nil, fmt.Errorf("unable to init reservation, with "+ + "fee=%v sat/kw, local output is too small: %v sat", + int64(commitFee), int64(ourBalance.ToSatoshis())) + } + // Next we'll set the channel type based on what we can ascertain about // the balances/push amount within the channel. var chanType channeldb.ChannelType @@ -231,7 +243,7 @@ func NewChannelReservation(capacity, fundingAmt, commitFeePerKw btcutil.Amount, chanOpen: make(chan *openChanDetails, 1), chanOpenErr: make(chan error, 1), wallet: wallet, - } + }, nil } // SetNumConfsRequired sets the number of confirmations that are required for diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index dad47aaa..b6dced7a 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -492,8 +492,13 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg } id := atomic.AddUint64(&l.nextFundingID, 1) - reservation := NewChannelReservation(req.capacity, req.fundingAmount, + reservation, err := NewChannelReservation(req.capacity, req.fundingAmount, req.commitFeePerKw, l, id, req.pushMSat, l.Cfg.NetParams.GenesisHash) + if err != nil { + req.err <- err + req.resp <- nil + return + } // Grab the mutex on the ChannelReservation to ensure thread-safety reservation.Lock() @@ -523,7 +528,6 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg // for the duration of the channel. The keys include: our multi-sig // key, the base revocation key, the base htlc key,the base payment // key, and the delayed payment key. - var err error reservation.ourContribution.MultiSigKey, err = l.NewRawKey() if err != nil { req.err <- err