Merge pull request #784 from halseth/protocol-errors

Wire protocol errors
This commit is contained in:
Olaoluwa Osuntokun 2018-03-06 16:54:49 -05:00 committed by GitHub
commit 1c5f1885d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 184 additions and 55 deletions

@ -689,21 +689,40 @@ func (f *fundingManager) CancelPeerReservations(nodePub [33]byte) {
} }
// failFundingFlow will fail the active funding flow with the target peer, // failFundingFlow will fail the active funding flow with the target peer,
// identified by its unique temporary channel ID. This method is send an error // identified by its unique temporary channel ID. This method will send an
// to the remote peer, and also remove the reservation from our set of pending // error to the remote peer, and also remove the reservation from our set of
// reservations. // pending reservations.
// //
// TODO(roasbeef): if peer disconnects, and haven't yet broadcast funding // TODO(roasbeef): if peer disconnects, and haven't yet broadcast funding
// transaction, then all reservations should be cleared. // transaction, then all reservations should be cleared.
func (f *fundingManager) failFundingFlow(peer *btcec.PublicKey, func (f *fundingManager) failFundingFlow(peer *btcec.PublicKey,
tempChanID [32]byte, msg []byte) { tempChanID [32]byte, fundingErr error) {
// We only send the exact error if it is part of out whitelisted set of
// errors (lnwire.ErrorCode or lnwallet.ReservationError).
var msg lnwire.ErrorData
switch e := fundingErr.(type) {
// Let the actual error message be sent to the remote.
case lnwallet.ReservationError:
msg = lnwire.ErrorData(e.Error())
// Send the status code.
case lnwire.ErrorCode:
msg = lnwire.ErrorData{byte(e)}
// We just send a generic error.
default:
msg = lnwire.ErrorData("funding failed due to internal error")
}
errMsg := &lnwire.Error{ errMsg := &lnwire.Error{
ChanID: tempChanID, ChanID: tempChanID,
Data: msg, Data: msg,
} }
fndgLog.Errorf("Failing funding flow: %v", spew.Sdump(errMsg)) fndgLog.Errorf("Failing funding flow: %v (%v)", fundingErr,
spew.Sdump(errMsg))
if _, err := f.cancelReservationCtx(peer, tempChanID); err != nil { if _, err := f.cancelReservationCtx(peer, tempChanID); err != nil {
fndgLog.Errorf("unable to cancel reservation: %v", err) fndgLog.Errorf("unable to cancel reservation: %v", err)
@ -819,8 +838,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
if len(f.activeReservations[peerIDKey]) >= cfg.MaxPendingChannels { if len(f.activeReservations[peerIDKey]) >= cfg.MaxPendingChannels {
f.failFundingFlow( f.failFundingFlow(
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID, fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID,
lnwire.ErrorData{byte(lnwire.ErrMaxPendingChannels)}, lnwire.ErrMaxPendingChannels)
)
return return
} }
@ -834,8 +852,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
} }
f.failFundingFlow( f.failFundingFlow(
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID, fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID,
lnwire.ErrorData{byte(lnwire.ErrSynchronizingChain)}, lnwire.ErrSynchronizingChain)
)
return return
} }
@ -844,8 +861,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
if msg.FundingAmount > maxFundingAmount { if msg.FundingAmount > maxFundingAmount {
f.failFundingFlow( f.failFundingFlow(
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID, fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID,
lnwire.ErrorData{byte(lnwire.ErrChanTooLarge)}, lnwire.ErrChanTooLarge)
)
return return
} }
@ -871,7 +887,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
if err != nil { if err != nil {
fndgLog.Errorf("Unable to initialize reservation: %v", err) fndgLog.Errorf("Unable to initialize reservation: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error())) msg.PendingChannelID, err)
return return
} }
@ -890,10 +906,9 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve, msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve,
) )
if err != nil { if err != nil {
f.failFundingFlow( fndgLog.Errorf("Unaccaptable channel constraints: %v", err)
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
[]byte(fmt.Sprintf("Unacceptable channel "+ fmsg.msg.PendingChannelID, err,
"constraints: %v", err)),
) )
return return
} }
@ -951,9 +966,8 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
err = reservation.ProcessSingleContribution(remoteContribution) err = reservation.ProcessSingleContribution(remoteContribution)
if err != nil { if err != nil {
fndgLog.Errorf("unable to add contribution reservation: %v", err) fndgLog.Errorf("unable to add contribution reservation: %v", err)
// TODO(roasbeef): verify only sending sane info over
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error())) msg.PendingChannelID, err)
return return
} }
@ -985,7 +999,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
if err != nil { if err != nil {
fndgLog.Errorf("unable to send funding response to peer: %v", err) fndgLog.Errorf("unable to send funding response to peer: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error())) msg.PendingChannelID, err)
return return
} }
} }
@ -1028,11 +1042,9 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve, msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve,
) )
if err != nil { if err != nil {
f.failFundingFlow( fndgLog.Warnf("Unacceptable channel constraints: %v", err)
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
[]byte(fmt.Sprintf("Unacceptable channel "+ fmsg.msg.PendingChannelID, err)
"constraints: %v", err)),
)
return return
} }
@ -1070,7 +1082,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
fndgLog.Errorf("Unable to process contribution from %v: %v", fndgLog.Errorf("Unable to process contribution from %v: %v",
fmsg.peerAddress.IdentityKey, err) fmsg.peerAddress.IdentityKey, err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error())) msg.PendingChannelID, err)
resCtx.err <- err resCtx.err <- err
return return
} }
@ -1115,7 +1127,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
if err != nil { if err != nil {
fndgLog.Errorf("Unable to parse signature: %v", err) fndgLog.Errorf("Unable to parse signature: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error())) msg.PendingChannelID, err)
resCtx.err <- err resCtx.err <- err
return return
} }
@ -1123,7 +1135,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
if err != nil { if err != nil {
fndgLog.Errorf("Unable to send funding complete message: %v", err) fndgLog.Errorf("Unable to send funding complete message: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error())) msg.PendingChannelID, err)
resCtx.err <- err resCtx.err <- err
return return
} }
@ -1177,7 +1189,7 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
// TODO(roasbeef): better error logging: peerID, channelID, etc. // TODO(roasbeef): better error logging: peerID, channelID, etc.
fndgLog.Errorf("unable to complete single reservation: %v", err) fndgLog.Errorf("unable to complete single reservation: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error())) pendingChanID, err)
return return
} }
@ -1218,7 +1230,7 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
if err != nil { if err != nil {
fndgLog.Errorf("unable to parse signature: %v", err) fndgLog.Errorf("unable to parse signature: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error())) pendingChanID, err)
deleteFromDatabase() deleteFromDatabase()
return return
} }
@ -1230,7 +1242,7 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
if err := f.cfg.SendToPeer(peerKey, fundingSigned); err != nil { if err := f.cfg.SendToPeer(peerKey, fundingSigned); err != nil {
fndgLog.Errorf("unable to send FundingSigned message: %v", err) fndgLog.Errorf("unable to send FundingSigned message: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error())) pendingChanID, err)
deleteFromDatabase() deleteFromDatabase()
return return
} }
@ -1332,11 +1344,12 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
delete(f.signedReservations, fmsg.msg.ChanID) delete(f.signedReservations, fmsg.msg.ChanID)
f.resMtx.Unlock() f.resMtx.Unlock()
if !ok { if !ok {
err := fmt.Sprintf("Unable to find signed reservation for "+ err := fmt.Errorf("Unable to find signed reservation for "+
"chan_id=%x", fmsg.msg.ChanID) "chan_id=%x", fmsg.msg.ChanID)
fndgLog.Warnf(err) fndgLog.Warnf(err.Error())
// TODO: add ErrChanNotFound?
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err)) pendingChanID, err)
return return
} }
@ -1346,8 +1359,9 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
if err != nil { if err != nil {
fndgLog.Warnf("Unable to find reservation (peerID:%v, chanID:%x)", fndgLog.Warnf("Unable to find reservation (peerID:%v, chanID:%x)",
peerKey, pendingChanID[:]) peerKey, pendingChanID[:])
// TODO: add ErrChanNotFound?
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error())) pendingChanID, err)
return return
} }
@ -1369,7 +1383,7 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
fndgLog.Errorf("Unable to complete reservation sign complete: %v", err) fndgLog.Errorf("Unable to complete reservation sign complete: %v", err)
resCtx.err <- err resCtx.err <- err
f.failFundingFlow(fmsg.peerAddress.IdentityKey, f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error())) pendingChanID, err)
return return
} }

108
lnwallet/errors.go Normal file

@ -0,0 +1,108 @@
package lnwallet
import (
"errors"
"fmt"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcutil"
)
// ReservationError wraps certain errors returned during channel reservation
// that can be sent across the wire to the remote peer. Errors not being
// ReservationErrors will not be sent to the remote in case of a failed channel
// reservation, as they may contain private information.
type ReservationError struct {
error
}
// A compile time check to ensure ReservationError implements the error
// interface.
var _ error = (*ReservationError)(nil)
// ErrZeroCapacity returns an error indicating the funder attempted to put zero
// funds into the channel.
func ErrZeroCapacity() ReservationError {
return ReservationError{
errors.New("zero channel funds"),
}
}
// ErrChainMismatch returns an error indicating that the initiator tried to
// open a channel for an unknown chain.
func ErrChainMismatch(knownChain,
unknownChain *chainhash.Hash) ReservationError {
return ReservationError{
fmt.Errorf("Unknown chain=%v. Supported chain=%v",
unknownChain, knownChain),
}
}
// ErrFunderBalanceDust returns an error indicating the initial balance of the
// funder is considered dust at the current commitment fee.
func ErrFunderBalanceDust(commitFee, funderBalance,
minBalance int64) ReservationError {
return ReservationError{
fmt.Errorf("Funder balance too small (%v) with fee=%v sat, "+
"minimum=%v sat required", funderBalance,
commitFee, minBalance),
}
}
// ErrCsvDelayTooLarge returns an error indicating that the CSV delay was to
// large to be accepted, along with the current max.
func ErrCsvDelayTooLarge(remoteDelay, maxDelay uint16) ReservationError {
return ReservationError{
fmt.Errorf("CSV delay too large: %v, max is %v",
remoteDelay, maxDelay),
}
}
// ErrChanReserveTooLarge returns an error indicating that the chan reserve the
// remote is requiring, is too large to be accepted.
func ErrChanReserveTooLarge(reserve,
maxReserve btcutil.Amount) ReservationError {
return ReservationError{
fmt.Errorf("Channel reserve is too large: %v sat, max "+
"is %v sat", int64(reserve), int64(maxReserve)),
}
}
// ErrMinHtlcTooLarge returns an error indicating that the MinHTLC value the
// remote required is too large to be accepted.
func ErrMinHtlcTooLarge(minHtlc,
maxMinHtlc lnwire.MilliSatoshi) ReservationError {
return ReservationError{
fmt.Errorf("Minimum HTLC value is too large: %v, max is %v",
minHtlc, maxMinHtlc),
}
}
// ErrMaxHtlcNumTooLarge returns an error indicating that the 'max HTLCs in
// flight' value the remote required is too large to be accepted.
func ErrMaxHtlcNumTooLarge(maxHtlc, maxMaxHtlc uint16) ReservationError {
return ReservationError{
fmt.Errorf("maxHtlcs is too large: %d, max is %d",
maxHtlc, maxMaxHtlc),
}
}
// ErrMaxHtlcNumTooSmall returns an error indicating that the 'max HTLCs in
// flight' value the remote required is too small to be accepted.
func ErrMaxHtlcNumTooSmall(maxHtlc, minMaxHtlc uint16) ReservationError {
return ReservationError{
fmt.Errorf("maxHtlcs is too small: %d, min is %d",
maxHtlc, minMaxHtlc),
}
}
// ErrMaxValueInFlightTooSmall returns an error indicating that the 'max HTLC
// value in flight' the remote required is too small to be accepted.
func ErrMaxValueInFlightTooSmall(maxValInFlight,
minMaxValInFlight lnwire.MilliSatoshi) ReservationError {
return ReservationError{
fmt.Errorf("maxValueInFlight too small: %v, min is %v",
maxValInFlight, minMaxValInFlight),
}
}

@ -581,7 +581,7 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
t.Fatalf("initialization should have failed due to " + t.Fatalf("initialization should have failed due to " +
"insufficient local amount") "insufficient local amount")
case !strings.Contains(err.Error(), "local output is too small"): case !strings.Contains(err.Error(), "Funder balance too small"):
t.Fatalf("incorrect error: %v", err) t.Fatalf("incorrect error: %v", err)
} }
} }

@ -1,7 +1,6 @@
package lnwallet package lnwallet
import ( import (
"fmt"
"net" "net"
"sync" "sync"
@ -186,9 +185,11 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount,
// //
// TODO(roasbeef): reject if 30% goes to fees? dust channel // TODO(roasbeef): reject if 30% goes to fees? dust channel
if initiator && ourBalance.ToSatoshis() <= 2*DefaultDustLimit() { if initiator && ourBalance.ToSatoshis() <= 2*DefaultDustLimit() {
return nil, fmt.Errorf("unable to init reservation, with "+ return nil, ErrFunderBalanceDust(
"fee=%v sat/kw, local output is too small: %v sat", int64(commitFee),
int64(commitFee), int64(ourBalance.ToSatoshis())) int64(ourBalance.ToSatoshis()),
int64(2*DefaultDustLimit()),
)
} }
// Next we'll set the channel type based on what we can ascertain about // Next we'll set the channel type based on what we can ascertain about
@ -282,8 +283,9 @@ func (r *ChannelReservation) CommitConstraints(csvDelay, maxHtlcs uint16,
// Fail if we consider csvDelay excessively large. // Fail if we consider csvDelay excessively large.
// TODO(halseth): find a more scientific choice of value. // TODO(halseth): find a more scientific choice of value.
if csvDelay > 10000 { const maxDelay = 10000
return fmt.Errorf("csvDelay is too large: %d", csvDelay) if csvDelay > maxDelay {
return ErrCsvDelayTooLarge(csvDelay, maxDelay)
} }
// Fail if we consider the channel reserve to be too large. // Fail if we consider the channel reserve to be too large.
@ -291,8 +293,7 @@ func (r *ChannelReservation) CommitConstraints(csvDelay, maxHtlcs uint16,
// channel capacity. // channel capacity.
maxChanReserve := r.partialState.Capacity / 5 maxChanReserve := r.partialState.Capacity / 5
if chanReserve > maxChanReserve { if chanReserve > maxChanReserve {
return fmt.Errorf("chanReserve is too large: %g", return ErrChanReserveTooLarge(chanReserve, maxChanReserve)
chanReserve.ToBTC())
} }
// Fail if the minimum HTLC value is too large. If this is // Fail if the minimum HTLC value is too large. If this is
@ -302,29 +303,28 @@ func (r *ChannelReservation) CommitConstraints(csvDelay, maxHtlcs uint16,
// it wants. // it wants.
// TODO(halseth): set a reasonable/dynamic value. // TODO(halseth): set a reasonable/dynamic value.
if minHtlc > maxValueInFlight { if minHtlc > maxValueInFlight {
return fmt.Errorf("minimum HTLC value is too large: %g", return ErrMinHtlcTooLarge(minHtlc, maxValueInFlight)
r.ourContribution.MinHTLC.ToBTC())
} }
// Fail if maxHtlcs is above the maximum allowed number of 483. // Fail if maxHtlcs is above the maximum allowed number of 483.
// This number is specified in BOLT-02. // This number is specified in BOLT-02.
if maxHtlcs > uint16(MaxHTLCNumber/2) { if maxHtlcs > uint16(MaxHTLCNumber/2) {
return fmt.Errorf("maxHtlcs is too large: %d", maxHtlcs) return ErrMaxHtlcNumTooLarge(maxHtlcs, uint16(MaxHTLCNumber/2))
} }
// Fail if we consider maxHtlcs too small. If this is too small // Fail if we consider maxHtlcs too small. If this is too small
// we cannot offer many HTLCs to the remote. // we cannot offer many HTLCs to the remote.
const minNumHtlc = 5 const minNumHtlc = 5
if maxHtlcs < minNumHtlc { if maxHtlcs < minNumHtlc {
return fmt.Errorf("maxHtlcs is too small: %d", maxHtlcs) return ErrMaxHtlcNumTooSmall(maxHtlcs, minNumHtlc)
} }
// Fail if we consider maxValueInFlight too small. We currently // Fail if we consider maxValueInFlight too small. We currently
// require the remote to at least allow minNumHtlc * minHtlc // require the remote to at least allow minNumHtlc * minHtlc
// in flight. // in flight.
if maxValueInFlight < minNumHtlc*minHtlc { if maxValueInFlight < minNumHtlc*minHtlc {
return fmt.Errorf("maxValueInFlight is too small: %g", return ErrMaxValueInFlightTooSmall(maxValueInFlight,
maxValueInFlight.ToBTC()) minNumHtlc*minHtlc)
} }
r.ourContribution.ChannelConfig.CsvDelay = csvDelay r.ourContribution.ChannelConfig.CsvDelay = csvDelay

@ -479,8 +479,8 @@ func (l *LightningWallet) InitChannelReservation(
func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) { func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) {
// It isn't possible to create a channel with zero funds committed. // It isn't possible to create a channel with zero funds committed.
if req.fundingAmount+req.capacity == 0 { if req.fundingAmount+req.capacity == 0 {
req.err <- fmt.Errorf("cannot have channel with zero " + err := ErrZeroCapacity()
"satoshis funded") req.err <- err
req.resp <- nil req.resp <- nil
return return
} }
@ -488,9 +488,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
// If the funding request is for a different chain than the one the // If the funding request is for a different chain than the one the
// wallet is aware of, then we'll reject the request. // wallet is aware of, then we'll reject the request.
if !bytes.Equal(l.Cfg.NetParams.GenesisHash[:], req.chainHash[:]) { if !bytes.Equal(l.Cfg.NetParams.GenesisHash[:], req.chainHash[:]) {
req.err <- fmt.Errorf("unable to create channel reservation "+ err := ErrChainMismatch(l.Cfg.NetParams.GenesisHash,
"for chain=%v, wallet is on chain=%v", req.chainHash)
req.chainHash, l.Cfg.NetParams.GenesisHash) req.err <- err
req.resp <- nil req.resp <- nil
return return
} }

@ -48,6 +48,13 @@ func (e ErrorCode) String() string {
} }
} }
// Error returns the human redable version of the target ErrorCode.
//
// Satisfies the Error interface.
func (e ErrorCode) Error() string {
return e.String()
}
// ErrorData is a set of bytes associated with a particular sent error. A // ErrorData is a set of bytes associated with a particular sent error. A
// receiving node SHOULD only print out data verbatim if the string is composed // receiving node SHOULD only print out data verbatim if the string is composed
// solely of printable ASCII characters. For reference, the printable character // solely of printable ASCII characters. For reference, the printable character