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

View File

@ -689,21 +689,40 @@ func (f *fundingManager) CancelPeerReservations(nodePub [33]byte) {
}
// failFundingFlow will fail the active funding flow with the target peer,
// identified by its unique temporary channel ID. This method is send an error
// to the remote peer, and also remove the reservation from our set of pending
// reservations.
// identified by its unique temporary channel ID. This method will send an
// error to the remote peer, and also remove the reservation from our set of
// pending reservations.
//
// TODO(roasbeef): if peer disconnects, and haven't yet broadcast funding
// transaction, then all reservations should be cleared.
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{
ChanID: tempChanID,
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 {
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 {
f.failFundingFlow(
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID,
lnwire.ErrorData{byte(lnwire.ErrMaxPendingChannels)},
)
lnwire.ErrMaxPendingChannels)
return
}
@ -834,8 +852,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
}
f.failFundingFlow(
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID,
lnwire.ErrorData{byte(lnwire.ErrSynchronizingChain)},
)
lnwire.ErrSynchronizingChain)
return
}
@ -844,8 +861,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
if msg.FundingAmount > maxFundingAmount {
f.failFundingFlow(
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID,
lnwire.ErrorData{byte(lnwire.ErrChanTooLarge)},
)
lnwire.ErrChanTooLarge)
return
}
@ -871,7 +887,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
if err != nil {
fndgLog.Errorf("Unable to initialize reservation: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error()))
msg.PendingChannelID, err)
return
}
@ -890,10 +906,9 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve,
)
if err != nil {
f.failFundingFlow(
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID,
[]byte(fmt.Sprintf("Unacceptable channel "+
"constraints: %v", err)),
fndgLog.Errorf("Unaccaptable channel constraints: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
fmsg.msg.PendingChannelID, err,
)
return
}
@ -951,9 +966,8 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
err = reservation.ProcessSingleContribution(remoteContribution)
if err != nil {
fndgLog.Errorf("unable to add contribution reservation: %v", err)
// TODO(roasbeef): verify only sending sane info over
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error()))
msg.PendingChannelID, err)
return
}
@ -985,7 +999,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
if err != nil {
fndgLog.Errorf("unable to send funding response to peer: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error()))
msg.PendingChannelID, err)
return
}
}
@ -1028,11 +1042,9 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
msg.MaxValueInFlight, msg.HtlcMinimum, msg.ChannelReserve,
)
if err != nil {
f.failFundingFlow(
fmsg.peerAddress.IdentityKey, fmsg.msg.PendingChannelID,
[]byte(fmt.Sprintf("Unacceptable channel "+
"constraints: %v", err)),
)
fndgLog.Warnf("Unacceptable channel constraints: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
fmsg.msg.PendingChannelID, err)
return
}
@ -1070,7 +1082,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
fndgLog.Errorf("Unable to process contribution from %v: %v",
fmsg.peerAddress.IdentityKey, err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error()))
msg.PendingChannelID, err)
resCtx.err <- err
return
}
@ -1115,7 +1127,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
if err != nil {
fndgLog.Errorf("Unable to parse signature: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error()))
msg.PendingChannelID, err)
resCtx.err <- err
return
}
@ -1123,7 +1135,7 @@ func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
if err != nil {
fndgLog.Errorf("Unable to send funding complete message: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
msg.PendingChannelID, []byte(err.Error()))
msg.PendingChannelID, err)
resCtx.err <- err
return
}
@ -1177,7 +1189,7 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
// TODO(roasbeef): better error logging: peerID, channelID, etc.
fndgLog.Errorf("unable to complete single reservation: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error()))
pendingChanID, err)
return
}
@ -1218,7 +1230,7 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
if err != nil {
fndgLog.Errorf("unable to parse signature: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error()))
pendingChanID, err)
deleteFromDatabase()
return
}
@ -1230,7 +1242,7 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
if err := f.cfg.SendToPeer(peerKey, fundingSigned); err != nil {
fndgLog.Errorf("unable to send FundingSigned message: %v", err)
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error()))
pendingChanID, err)
deleteFromDatabase()
return
}
@ -1332,11 +1344,12 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
delete(f.signedReservations, fmsg.msg.ChanID)
f.resMtx.Unlock()
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)
fndgLog.Warnf(err)
fndgLog.Warnf(err.Error())
// TODO: add ErrChanNotFound?
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err))
pendingChanID, err)
return
}
@ -1346,8 +1359,9 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
if err != nil {
fndgLog.Warnf("Unable to find reservation (peerID:%v, chanID:%x)",
peerKey, pendingChanID[:])
// TODO: add ErrChanNotFound?
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error()))
pendingChanID, err)
return
}
@ -1369,7 +1383,7 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
fndgLog.Errorf("Unable to complete reservation sign complete: %v", err)
resCtx.err <- err
f.failFundingFlow(fmsg.peerAddress.IdentityKey,
pendingChanID, []byte(err.Error()))
pendingChanID, err)
return
}

108
lnwallet/errors.go Normal file
View 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),
}
}

View File

@ -581,7 +581,7 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
t.Fatalf("initialization should have failed due to " +
"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)
}
}

View File

@ -1,7 +1,6 @@
package lnwallet
import (
"fmt"
"net"
"sync"
@ -186,9 +185,11 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount,
//
// TODO(roasbeef): reject if 30% goes to fees? dust channel
if initiator && ourBalance.ToSatoshis() <= 2*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()))
return nil, ErrFunderBalanceDust(
int64(commitFee),
int64(ourBalance.ToSatoshis()),
int64(2*DefaultDustLimit()),
)
}
// 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.
// TODO(halseth): find a more scientific choice of value.
if csvDelay > 10000 {
return fmt.Errorf("csvDelay is too large: %d", csvDelay)
const maxDelay = 10000
if csvDelay > maxDelay {
return ErrCsvDelayTooLarge(csvDelay, maxDelay)
}
// Fail if we consider the channel reserve to be too large.
@ -291,8 +293,7 @@ func (r *ChannelReservation) CommitConstraints(csvDelay, maxHtlcs uint16,
// channel capacity.
maxChanReserve := r.partialState.Capacity / 5
if chanReserve > maxChanReserve {
return fmt.Errorf("chanReserve is too large: %g",
chanReserve.ToBTC())
return ErrChanReserveTooLarge(chanReserve, maxChanReserve)
}
// 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.
// TODO(halseth): set a reasonable/dynamic value.
if minHtlc > maxValueInFlight {
return fmt.Errorf("minimum HTLC value is too large: %g",
r.ourContribution.MinHTLC.ToBTC())
return ErrMinHtlcTooLarge(minHtlc, maxValueInFlight)
}
// Fail if maxHtlcs is above the maximum allowed number of 483.
// This number is specified in BOLT-02.
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
// we cannot offer many HTLCs to the remote.
const minNumHtlc = 5
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
// require the remote to at least allow minNumHtlc * minHtlc
// in flight.
if maxValueInFlight < minNumHtlc*minHtlc {
return fmt.Errorf("maxValueInFlight is too small: %g",
maxValueInFlight.ToBTC())
return ErrMaxValueInFlightTooSmall(maxValueInFlight,
minNumHtlc*minHtlc)
}
r.ourContribution.ChannelConfig.CsvDelay = csvDelay

View File

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

View File

@ -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
// receiving node SHOULD only print out data verbatim if the string is composed
// solely of printable ASCII characters. For reference, the printable character