multi: add channel open parameters to channel acceptor

Add more fields to channel acceptor response so that users can have more
fine grained control over their incoming channels. With our chained
acceptor, it is possible that we get inconsistent responses from
multiple chained acceptors. We create a conjugate repsponse from all the
set fields in our various responses, but fail if we get different, non-
zero responses from our various acceptors. Separate merge functions are
used per type so that we avoid unexpected outcomes comparing interfaces
(panic on comparing types that aren't comparable), with casting used
where applicable to avoid code duplication.
This commit is contained in:
carla
2020-11-09 09:34:52 +02:00
parent 0d35ce7561
commit 5679dde1bc
11 changed files with 1568 additions and 810 deletions

View File

@@ -7,9 +7,13 @@ import (
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const testTimeout = time.Second
@@ -52,7 +56,7 @@ func newChanAcceptorCtx(t *testing.T, acceptCallCount int,
testCtx.acceptor = NewRPCAcceptor(
testCtx.receiveResponse, testCtx.sendRequest, testTimeout*5,
testCtx.quit,
&chaincfg.TestNet3Params, testCtx.quit,
)
return testCtx
@@ -157,6 +161,12 @@ func (c *channelAcceptorCtx) queryAndAssert(queries map[*lnwire.OpenChannel]*Cha
// TestMultipleAcceptClients tests that the RPC acceptor is capable of handling
// multiple requests to its Accept function and responding to them correctly.
func TestMultipleAcceptClients(t *testing.T) {
testAddr := "bcrt1qwrmq9uca0t3dy9t9wtuq5tm4405r7tfzyqn9pp"
testUpfront, err := chancloser.ParseUpfrontShutdownAddress(
testAddr, &chaincfg.TestNet3Params,
)
require.NoError(t, err)
var (
chan1 = &lnwire.OpenChannel{
PendingChannelID: [32]byte{1},
@@ -173,17 +183,31 @@ func TestMultipleAcceptClients(t *testing.T) {
// Queries is a map of the channel IDs we will query Accept
// with, and the set of outcomes we expect.
queries = map[*lnwire.OpenChannel]*ChannelAcceptResponse{
chan1: NewChannelAcceptResponse(true, nil),
chan2: NewChannelAcceptResponse(false, errChannelRejected),
chan3: NewChannelAcceptResponse(false, customError),
chan1: NewChannelAcceptResponse(
true, nil, testUpfront, 1, 2, 3, 4, 5, 6,
),
chan2: NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0, 0,
0, 0, 0,
),
chan3: NewChannelAcceptResponse(
false, customError, nil, 0, 0, 0, 0, 0, 0,
),
}
// Responses is a mocked set of responses from the remote
// channel acceptor.
responses = map[[32]byte]*lnrpc.ChannelAcceptResponse{
chan1.PendingChannelID: {
PendingChanId: chan1.PendingChannelID[:],
Accept: true,
PendingChanId: chan1.PendingChannelID[:],
Accept: true,
UpfrontShutdown: testAddr,
CsvDelay: 1,
MaxHtlcCount: 2,
MinAcceptDepth: 3,
ReserveSat: 4,
InFlightMaxMsat: 5,
MinHtlcIn: 6,
},
chan2.PendingChannelID: {
PendingChanId: chan2.PendingChannelID[:],
@@ -221,7 +245,8 @@ func TestInvalidResponse(t *testing.T) {
{
PendingChannelID: chan1,
}: NewChannelAcceptResponse(
false, errChannelRejected,
false, errChannelRejected, nil, 0, 0,
0, 0, 0, 0,
),
}
@@ -246,3 +271,46 @@ func TestInvalidResponse(t *testing.T) {
// response, so we shutdown and assert here.
testCtx.stop()
}
// TestInvalidReserve tests validation of the channel reserve proposed by the
// acceptor against the dust limit that was proposed by the remote peer.
func TestInvalidReserve(t *testing.T) {
var (
chan1 = [32]byte{1}
dustLimit = btcutil.Amount(1000)
reserve = dustLimit / 2
// We make a single query, and expect it to fail with our
// generic error because channel reserve is too low.
queries = map[*lnwire.OpenChannel]*ChannelAcceptResponse{
{
PendingChannelID: chan1,
DustLimit: dustLimit,
}: NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0,
0, reserve, 0, 0,
),
}
// Create a single response which is invalid because the
// proposed reserve is below our dust limit.
responses = map[[32]byte]*lnrpc.ChannelAcceptResponse{
chan1: {
PendingChanId: chan1[:],
Accept: true,
ReserveSat: uint64(reserve),
},
}
)
// Create and start our channel acceptor.
testCtx := newChanAcceptorCtx(t, len(queries), responses)
testCtx.start()
testCtx.queryAndAssert(queries)
// We do not expect our channel acceptor to exit because of one invalid
// response, so we shutdown and assert here.
testCtx.stop()
}

View File

@@ -50,6 +50,8 @@ func (c *ChainedAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptRespon
c.acceptorsMtx.RLock()
defer c.acceptorsMtx.RUnlock()
var finalResp ChannelAcceptResponse
for _, acceptor := range c.acceptors {
// Call our acceptor to determine whether we want to accept this
// channel.
@@ -61,11 +63,31 @@ func (c *ChainedAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptRespon
if acceptorResponse.RejectChannel() {
return acceptorResponse
}
// If we have accepted the channel, we need to set the other
// fields that were set in the response. However, since we are
// dealing with multiple responses, we need to make sure that we
// have not received inconsistent values (eg a csv delay of 1
// from one acceptor, and a delay of 120 from another). We
// set each value on our final response if it has not been set
// yet, and allow duplicate sets if the value is the same. If
// we cannot set a field, we return an error response.
var err error
finalResp, err = mergeResponse(finalResp, *acceptorResponse)
if err != nil {
log.Errorf("response for: %x has inconsistent values: %v",
req.OpenChanMsg.PendingChannelID, err)
return NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0,
0, 0, 0, 0,
)
}
}
// If we have gone through all of our acceptors with no objections, we
// can return an acceptor with a nil error.
return NewChannelAcceptResponse(true, nil)
return &finalResp
}
// A compile-time constraint to ensure ChainedAcceptor implements the

View File

@@ -4,6 +4,7 @@ import (
"errors"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
)
@@ -28,11 +29,39 @@ type ChannelAcceptRequest struct {
}
// ChannelAcceptResponse is a struct containing the response to a request to
// open an inbound channel.
// open an inbound channel. Note that fields added to this struct must be added
// to the mergeResponse function to allow combining of responses from different
// acceptors.
type ChannelAcceptResponse struct {
// ChanAcceptError the error returned by the channel acceptor. If the
// channel was accepted, this value will be nil.
ChanAcceptError
// UpfrontShutdown is the address that we will set as our upfront
// shutdown address.
UpfrontShutdown lnwire.DeliveryAddress
// CSVDelay is the csv delay we require for the remote peer.
CSVDelay uint16
// Reserve is the amount that require the remote peer hold in reserve
// on the channel.
Reserve btcutil.Amount
// InFlightTotal is the maximum amount that we allow the remote peer to
// hold in outstanding htlcs.
InFlightTotal lnwire.MilliSatoshi
// HtlcLimit is the maximum number of htlcs that we allow the remote
// peer to offer us.
HtlcLimit uint16
// MinHtlcIn is the minimum incoming htlc value allowed on the channel.
MinHtlcIn lnwire.MilliSatoshi
// MinAcceptDepth is the minimum depth that the initiator of the
// channel should wait before considering the channel open.
MinAcceptDepth uint16
}
// NewChannelAcceptResponse is a constructor for a channel accept response,
@@ -40,13 +69,25 @@ type ChannelAcceptResponse struct {
// a rejection) so that the error will be whitelisted and delivered to the
// initiating peer. Accepted channels simply return a response containing a nil
// error.
func NewChannelAcceptResponse(accept bool,
acceptErr error) *ChannelAcceptResponse {
func NewChannelAcceptResponse(accept bool, acceptErr error,
upfrontShutdown lnwire.DeliveryAddress, csvDelay, htlcLimit,
minDepth uint16, reserve btcutil.Amount, inFlight,
minHtlcIn lnwire.MilliSatoshi) *ChannelAcceptResponse {
resp := &ChannelAcceptResponse{
UpfrontShutdown: upfrontShutdown,
CSVDelay: csvDelay,
Reserve: reserve,
InFlightTotal: inFlight,
HtlcLimit: htlcLimit,
MinHtlcIn: minHtlcIn,
MinAcceptDepth: minDepth,
}
// If we want to accept the channel, we return a response with a nil
// error.
if accept {
return &ChannelAcceptResponse{}
return resp
}
// Use a generic error when no custom error is provided.
@@ -54,11 +95,11 @@ func NewChannelAcceptResponse(accept bool,
acceptErr = errChannelRejected
}
return &ChannelAcceptResponse{
ChanAcceptError: ChanAcceptError{
error: acceptErr,
},
resp.ChanAcceptError = ChanAcceptError{
error: acceptErr,
}
return resp
}
// RejectChannel returns a boolean that indicates whether we should reject the

152
chanacceptor/merge.go Normal file
View File

@@ -0,0 +1,152 @@
package chanacceptor
import (
"bytes"
"fmt"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// We use field names in our errors for more readable errors. Create
// consts for them here so that we can exactly match in our unit tests.
fieldCSV = "csv delay"
fieldHtlcLimit = "htlc limit"
fieldMinDep = "min depth"
fieldReserve = "reserve"
fieldMinIn = "min htlc in"
fieldInFlightTotal = "in flight total"
fieldUpfrontShutdown = "upfront shutdown"
)
// fieldMismatchError returns a merge error for a named field when we get two
// channel acceptor responses which have different values set.
func fieldMismatchError(name string, current, new interface{}) error {
return fmt.Errorf("multiple values set for: %v, %v and %v",
name, current, new)
}
// mergeInt64 merges two int64 values, failing if they have different non-zero
// values.
func mergeInt64(name string, current, new int64) (int64, error) {
switch {
case current == 0:
return new, nil
case new == 0:
return current, nil
case current != new:
return 0, fieldMismatchError(name, current, new)
default:
return new, nil
}
}
// mergeMillisatoshi merges two msat values, failing if they have different
// non-zero values.
func mergeMillisatoshi(name string, current,
new lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) {
switch {
case current == 0:
return new, nil
case new == 0:
return current, nil
case current != new:
return 0, fieldMismatchError(name, current, new)
default:
return new, nil
}
}
// mergeDeliveryAddress merges two delivery address values, failing if they have
// different non-zero values.
func mergeDeliveryAddress(name string, current,
new lnwire.DeliveryAddress) (lnwire.DeliveryAddress, error) {
switch {
case current == nil:
return new, nil
case new == nil:
return current, nil
case !bytes.Equal(current, new):
return nil, fieldMismatchError(name, current, new)
default:
return new, nil
}
}
// mergeResponse takes two channel accept responses, and attempts to merge their
// fields, failing if any fields conflict (are non-zero and not equal). It
// returns a new response that has all the merged fields in it.
func mergeResponse(current, new ChannelAcceptResponse) (ChannelAcceptResponse,
error) {
csv, err := mergeInt64(
fieldCSV, int64(current.CSVDelay), int64(new.CSVDelay),
)
if err != nil {
return current, err
}
current.CSVDelay = uint16(csv)
htlcLimit, err := mergeInt64(
fieldHtlcLimit, int64(current.HtlcLimit),
int64(new.HtlcLimit),
)
if err != nil {
return current, err
}
current.HtlcLimit = uint16(htlcLimit)
minDepth, err := mergeInt64(
fieldMinDep, int64(current.MinAcceptDepth),
int64(new.MinAcceptDepth),
)
if err != nil {
return current, err
}
current.MinAcceptDepth = uint16(minDepth)
reserve, err := mergeInt64(
fieldReserve, int64(current.Reserve), int64(new.Reserve),
)
if err != nil {
return current, err
}
current.Reserve = btcutil.Amount(reserve)
current.MinHtlcIn, err = mergeMillisatoshi(
fieldMinIn, current.MinHtlcIn, new.MinHtlcIn,
)
if err != nil {
return current, err
}
current.InFlightTotal, err = mergeMillisatoshi(
fieldInFlightTotal, current.InFlightTotal,
new.InFlightTotal,
)
if err != nil {
return current, err
}
current.UpfrontShutdown, err = mergeDeliveryAddress(
fieldUpfrontShutdown, current.UpfrontShutdown,
new.UpfrontShutdown,
)
if err != nil {
return current, err
}
return current, nil
}

188
chanacceptor/merge_test.go Normal file
View File

@@ -0,0 +1,188 @@
package chanacceptor
import (
"testing"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// TestMergeResponse tests merging of channel acceptor responses.
func TestMergeResponse(t *testing.T) {
var (
addr1 = lnwire.DeliveryAddress{1}
addr2 = lnwire.DeliveryAddress{2}
populatedResp = ChannelAcceptResponse{
UpfrontShutdown: addr1,
CSVDelay: 2,
Reserve: 3,
InFlightTotal: 4,
HtlcLimit: 5,
MinHtlcIn: 6,
MinAcceptDepth: 7,
}
)
tests := []struct {
name string
current ChannelAcceptResponse
new ChannelAcceptResponse
merged ChannelAcceptResponse
err error
}{
{
name: "same response",
current: populatedResp,
new: populatedResp,
merged: populatedResp,
err: nil,
},
{
name: "different upfront",
current: ChannelAcceptResponse{
UpfrontShutdown: addr1,
},
new: ChannelAcceptResponse{
UpfrontShutdown: addr2,
},
err: fieldMismatchError(fieldUpfrontShutdown, addr1, addr2),
},
{
name: "different csv",
current: ChannelAcceptResponse{
CSVDelay: 1,
},
new: ChannelAcceptResponse{
CSVDelay: 2,
},
err: fieldMismatchError(fieldCSV, 1, 2),
},
{
name: "different reserve",
current: ChannelAcceptResponse{
Reserve: 1,
},
new: ChannelAcceptResponse{
Reserve: 2,
},
err: fieldMismatchError(fieldReserve, 1, 2),
},
{
name: "different in flight",
current: ChannelAcceptResponse{
InFlightTotal: 1,
},
new: ChannelAcceptResponse{
InFlightTotal: 2,
},
err: fieldMismatchError(
fieldInFlightTotal, lnwire.MilliSatoshi(1),
lnwire.MilliSatoshi(2),
),
},
{
name: "different htlc limit",
current: ChannelAcceptResponse{
HtlcLimit: 1,
},
new: ChannelAcceptResponse{
HtlcLimit: 2,
},
err: fieldMismatchError(fieldHtlcLimit, 1, 2),
},
{
name: "different min in",
current: ChannelAcceptResponse{
MinHtlcIn: 1,
},
new: ChannelAcceptResponse{
MinHtlcIn: 2,
},
err: fieldMismatchError(
fieldMinIn, lnwire.MilliSatoshi(1),
lnwire.MilliSatoshi(2),
),
},
{
name: "different depth",
current: ChannelAcceptResponse{
MinAcceptDepth: 1,
},
new: ChannelAcceptResponse{
MinAcceptDepth: 2,
},
err: fieldMismatchError(fieldMinDep, 1, 2),
},
{
name: "merge all values",
current: ChannelAcceptResponse{
UpfrontShutdown: lnwire.DeliveryAddress{1},
CSVDelay: 1,
Reserve: 0,
InFlightTotal: 3,
HtlcLimit: 0,
MinHtlcIn: 5,
MinAcceptDepth: 0,
},
new: ChannelAcceptResponse{
UpfrontShutdown: nil,
CSVDelay: 0,
Reserve: 2,
InFlightTotal: 0,
HtlcLimit: 4,
MinHtlcIn: 0,
MinAcceptDepth: 6,
},
merged: ChannelAcceptResponse{
UpfrontShutdown: lnwire.DeliveryAddress{1},
CSVDelay: 1,
Reserve: 2,
InFlightTotal: 3,
HtlcLimit: 4,
MinHtlcIn: 5,
MinAcceptDepth: 6,
},
err: nil,
},
{
// Test the case where fields have the same non-zero
// value, and the case where only response value is
// non-zero.
name: "empty and identical",
current: ChannelAcceptResponse{
CSVDelay: 1,
Reserve: 2,
InFlightTotal: 0,
},
new: ChannelAcceptResponse{
CSVDelay: 0,
Reserve: 2,
InFlightTotal: 3,
},
merged: ChannelAcceptResponse{
CSVDelay: 1,
Reserve: 2,
InFlightTotal: 3,
},
err: nil,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
resp, err := mergeResponse(test.current, test.new)
require.Equal(t, test.err, err)
// If we expect an error, exit early rather than compare
// our result.
if test.err != nil {
return
}
require.Equal(t, test.merged, resp)
})
}
}

View File

@@ -1,12 +1,18 @@
package chanacceptor
import (
"encoding/hex"
"errors"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
@@ -17,11 +23,26 @@ var (
errCustomLength = fmt.Errorf("custom error message exceeds length "+
"limit: %v", maxErrorLength)
// errInvalidUpfrontShutdown is returned when we cannot parse the
// upfront shutdown address returned.
errInvalidUpfrontShutdown = fmt.Errorf("could not parse upfront " +
"shutdown address")
// errInsufficientReserve is returned when the reserve proposed by for
// a channel is less than the dust limit originally supplied.
errInsufficientReserve = fmt.Errorf("reserve lower than proposed dust " +
"limit")
// errAcceptWithError is returned when we get a response which accepts
// a channel but ambiguously also sets a custom error message.
errAcceptWithError = errors.New("channel acceptor response accepts " +
"channel, but also includes custom error")
// errMaxHtlcTooHigh is returned if our htlc count exceeds the number
// hard-set by BOLT 2.
errMaxHtlcTooHigh = fmt.Errorf("htlc limit exceeds spec limit of: %v",
input.MaxHTLCNumber/2)
// maxErrorLength is the maximum error length we allow the error we
// send to our peer to be.
maxErrorLength = 500
@@ -54,6 +75,9 @@ type RPCAcceptor struct {
// acceptor, and the time it takes to receive a response.
timeout time.Duration
// params are our current chain params.
params *chaincfg.Params
// done is closed when the rpc client terminates.
done chan struct{}
@@ -83,7 +107,7 @@ func (r *RPCAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse {
// Create a rejection response which we can use for the cases where we
// reject the channel.
rejectChannel := NewChannelAcceptResponse(
false, errChannelRejected,
false, errChannelRejected, nil, 0, 0, 0, 0, 0, 0,
)
// Send the request to the newRequests channel.
@@ -123,14 +147,15 @@ func (r *RPCAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse {
// NewRPCAcceptor creates and returns an instance of the RPCAcceptor.
func NewRPCAcceptor(receive func() (*lnrpc.ChannelAcceptResponse, error),
send func(*lnrpc.ChannelAcceptRequest) error,
timeout time.Duration, quit chan struct{}) *RPCAcceptor {
send func(*lnrpc.ChannelAcceptRequest) error, timeout time.Duration,
params *chaincfg.Params, quit chan struct{}) *RPCAcceptor {
return &RPCAcceptor{
receive: receive,
send: send,
requests: make(chan *chanAcceptInfo),
timeout: timeout,
params: params,
done: make(chan struct{}),
quit: quit,
}
@@ -181,9 +206,16 @@ func (r *RPCAcceptor) receiveResponses(errChan chan error,
copy(pendingID[:], resp.PendingChanId)
openChanResp := lnrpc.ChannelAcceptResponse{
Accept: resp.Accept,
PendingChanId: pendingID[:],
Error: resp.Error,
Accept: resp.Accept,
PendingChanId: pendingID[:],
Error: resp.Error,
UpfrontShutdown: resp.UpfrontShutdown,
CsvDelay: resp.CsvDelay,
ReserveSat: resp.ReserveSat,
InFlightMaxMsat: resp.InFlightMaxMsat,
MaxHtlcCount: resp.MaxHtlcCount,
MinHtlcIn: resp.MinHtlcIn,
MinAcceptDepth: resp.MinAcceptDepth,
}
// We have received a decision for one of our channel
@@ -210,7 +242,10 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
// listening and any in-progress requests should be terminated.
defer close(r.done)
acceptRequests := make(map[[32]byte]chan *ChannelAcceptResponse)
// Create a map of pending channel IDs to our original open channel
// request and a response channel. We keep the original chanel open
// message so that we can validate our response against it.
acceptRequests := make(map[[32]byte]*chanAcceptInfo)
for {
select {
@@ -221,7 +256,7 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
req := newRequest.request
pendingChanID := req.OpenChanMsg.PendingChannelID
acceptRequests[pendingChanID] = newRequest.response
acceptRequests[pendingChanID] = newRequest
// A ChannelAcceptRequest has been received, send it to the client.
chanAcceptReq := &lnrpc.ChannelAcceptRequest{
@@ -253,7 +288,7 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
// over it.
var pendingID [32]byte
copy(pendingID[:], resp.PendingChanId)
respChan, ok := acceptRequests[pendingID]
requestInfo, ok := acceptRequests[pendingID]
if !ok {
continue
}
@@ -261,14 +296,22 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
// Validate the response we have received. If it is not
// valid, we log our error and proceed to deliver the
// rejection.
accept, acceptErr, err := validateAcceptorResponse(resp)
accept, acceptErr, shutdown, err := r.validateAcceptorResponse(
requestInfo.request.OpenChanMsg.DustLimit, resp,
)
if err != nil {
log.Errorf("Invalid acceptor response: %v", err)
}
// Send the response boolean over the buffered response
// channel.
respChan <- NewChannelAcceptResponse(accept, acceptErr)
requestInfo.response <- NewChannelAcceptResponse(
accept, acceptErr, shutdown,
uint16(resp.CsvDelay),
uint16(resp.MaxHtlcCount),
uint16(resp.MinAcceptDepth),
btcutil.Amount(resp.ReserveSat),
lnwire.MilliSatoshi(resp.InFlightMaxMsat),
lnwire.MilliSatoshi(resp.MinHtlcIn),
)
// Delete the channel from the acceptRequests map.
delete(acceptRequests, pendingID)
@@ -288,12 +331,49 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
// validateAcceptorResponse validates the response we get from the channel
// acceptor, returning a boolean indicating whether to accept the channel, an
// error to send to the peer, and any validation errors that occurred.
func validateAcceptorResponse(req lnrpc.ChannelAcceptResponse) (bool, error,
func (r *RPCAcceptor) validateAcceptorResponse(dustLimit btcutil.Amount,
req lnrpc.ChannelAcceptResponse) (bool, error, lnwire.DeliveryAddress,
error) {
channelStr := hex.EncodeToString(req.PendingChanId)
// Check that the max htlc count is within the BOLT 2 hard-limit of 483.
// The initiating side should fail values above this anyway, but we
// catch the invalid user input here.
if req.MaxHtlcCount > input.MaxHTLCNumber/2 {
log.Errorf("Max htlc count: %v for channel: %v is greater "+
"than limit of: %v", req.MaxHtlcCount, channelStr,
input.MaxHTLCNumber/2)
return false, errChannelRejected, nil, errMaxHtlcTooHigh
}
// Ensure that the reserve that has been proposed, if it is set, is at
// least the dust limit that was proposed by the remote peer. This is
// required by BOLT 2.
reserveSat := btcutil.Amount(req.ReserveSat)
if reserveSat != 0 && reserveSat < dustLimit {
log.Errorf("Remote reserve: %v sat for channel: %v must be "+
"at least equal to proposed dust limit: %v",
req.ReserveSat, channelStr, dustLimit)
return false, errChannelRejected, nil, errInsufficientReserve
}
// Attempt to parse the upfront shutdown address provided.
upfront, err := chancloser.ParseUpfrontShutdownAddress(
req.UpfrontShutdown, r.params,
)
if err != nil {
log.Errorf("Could not parse upfront shutdown for "+
"%v: %v", channelStr, err)
return false, errChannelRejected, nil, errInvalidUpfrontShutdown
}
// Check that the custom error provided is valid.
if len(req.Error) > maxErrorLength {
return false, errChannelRejected, errCustomLength
return false, errChannelRejected, nil, errCustomLength
}
var haveCustomError = len(req.Error) != 0
@@ -302,21 +382,21 @@ func validateAcceptorResponse(req lnrpc.ChannelAcceptResponse) (bool, error,
// If accept is true, but we also have an error specified, we fail
// because this result is ambiguous.
case req.Accept && haveCustomError:
return false, errChannelRejected, errAcceptWithError
return false, errChannelRejected, nil, errAcceptWithError
// If we accept without an error message, we can just return a nil
// error.
case req.Accept:
return true, nil, nil
return true, nil, upfront, nil
// If we reject the channel, and have a custom error, then we use it.
case haveCustomError:
return false, fmt.Errorf(req.Error), nil
return false, fmt.Errorf(req.Error), nil, nil
// Otherwise, we have rejected the channel with no custom error, so we
// just use a generic error to fail the channel.
default:
return false, errChannelRejected, nil
return false, errChannelRejected, nil, nil
}
}

View File

@@ -5,20 +5,33 @@ import (
"strings"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// TestValidateAcceptorResponse test validation of acceptor responses.
func TestValidateAcceptorResponse(t *testing.T) {
customError := errors.New("custom error")
var (
customError = errors.New("custom error")
validAddr = "bcrt1qwrmq9uca0t3dy9t9wtuq5tm4405r7tfzyqn9pp"
addr, _ = chancloser.ParseUpfrontShutdownAddress(
validAddr, &chaincfg.TestNet3Params,
)
)
tests := []struct {
name string
dustLimit btcutil.Amount
response lnrpc.ChannelAcceptResponse
accept bool
acceptorErr error
error error
shutdown lnwire.DeliveryAddress
}{
{
name: "accepted with error",
@@ -43,11 +56,13 @@ func TestValidateAcceptorResponse(t *testing.T) {
{
name: "accepted",
response: lnrpc.ChannelAcceptResponse{
Accept: true,
Accept: true,
UpfrontShutdown: validAddr,
},
accept: true,
acceptorErr: nil,
error: nil,
shutdown: addr,
},
{
name: "rejected with error",
@@ -68,18 +83,57 @@ func TestValidateAcceptorResponse(t *testing.T) {
acceptorErr: errChannelRejected,
error: nil,
},
{
name: "invalid upfront shutdown",
response: lnrpc.ChannelAcceptResponse{
Accept: true,
UpfrontShutdown: "invalid addr",
},
accept: false,
acceptorErr: errChannelRejected,
error: errInvalidUpfrontShutdown,
},
{
name: "reserve too low",
dustLimit: 100,
response: lnrpc.ChannelAcceptResponse{
Accept: true,
ReserveSat: 10,
},
accept: false,
acceptorErr: errChannelRejected,
error: errInsufficientReserve,
},
{
name: "max htlcs too high",
dustLimit: 100,
response: lnrpc.ChannelAcceptResponse{
Accept: true,
MaxHtlcCount: 1 + input.MaxHTLCNumber/2,
},
accept: false,
acceptorErr: errChannelRejected,
error: errMaxHtlcTooHigh,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
accept, acceptErr, err := validateAcceptorResponse(
test.response,
// Create an acceptor, everything can be nil because
// we just need the params.
acceptor := NewRPCAcceptor(
nil, nil, 0, &chaincfg.TestNet3Params, nil,
)
accept, acceptErr, shutdown, err := acceptor.validateAcceptorResponse(
test.dustLimit, test.response,
)
require.Equal(t, test.accept, accept)
require.Equal(t, test.acceptorErr, acceptErr)
require.Equal(t, test.error, err)
require.Equal(t, test.shutdown, shutdown)
})
}
}