lnd.xprv/chanacceptor/rpcacceptor.go
carla 5679dde1bc
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.
2020-11-10 21:09:10 +02:00

406 lines
13 KiB
Go

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 (
errShuttingDown = errors.New("server shutting down")
// errCustomLength is returned when our custom error's length exceeds
// our maximum.
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
)
// chanAcceptInfo contains a request for a channel acceptor decision, and a
// channel that the response should be sent on.
type chanAcceptInfo struct {
request *ChannelAcceptRequest
response chan *ChannelAcceptResponse
}
// RPCAcceptor represents the RPC-controlled variant of the ChannelAcceptor.
// One RPCAcceptor allows one RPC client.
type RPCAcceptor struct {
// receive is a function from which we receive channel acceptance
// decisions. Note that this function is expected to block.
receive func() (*lnrpc.ChannelAcceptResponse, error)
// send is a function which sends requests for channel acceptance
// decisions into our rpc stream.
send func(request *lnrpc.ChannelAcceptRequest) error
// requests is a channel that we send requests for a acceptor response
// into.
requests chan *chanAcceptInfo
// timeout is the amount of time we allow the channel acceptance
// decision to take. This time includes the time to send a query to the
// 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{}
// quit is closed when lnd is shutting down.
quit chan struct{}
wg sync.WaitGroup
}
// Accept is a predicate on the ChannelAcceptRequest which is sent to the RPC
// client who will respond with the ultimate decision. This function passes the
// request into the acceptor's requests channel, and returns the response it
// receives, failing the request if the timeout elapses.
//
// NOTE: Part of the ChannelAcceptor interface.
func (r *RPCAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse {
respChan := make(chan *ChannelAcceptResponse, 1)
newRequest := &chanAcceptInfo{
request: req,
response: respChan,
}
// timeout is the time after which ChannelAcceptRequests expire.
timeout := time.After(r.timeout)
// Create a rejection response which we can use for the cases where we
// reject the channel.
rejectChannel := NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0, 0, 0, 0, 0,
)
// Send the request to the newRequests channel.
select {
case r.requests <- newRequest:
case <-timeout:
log.Errorf("RPCAcceptor returned false - reached timeout of %v",
r.timeout)
return rejectChannel
case <-r.done:
return rejectChannel
case <-r.quit:
return rejectChannel
}
// Receive the response and return it. If no response has been received
// in AcceptorTimeout, then return false.
select {
case resp := <-respChan:
return resp
case <-timeout:
log.Errorf("RPCAcceptor returned false - reached timeout of %v",
r.timeout)
return rejectChannel
case <-r.done:
return rejectChannel
case <-r.quit:
return rejectChannel
}
}
// NewRPCAcceptor creates and returns an instance of the RPCAcceptor.
func NewRPCAcceptor(receive func() (*lnrpc.ChannelAcceptResponse, error),
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,
}
}
// Run is the main loop for the RPC Acceptor. This function will block until
// it receives the signal that lnd is shutting down, or the rpc stream is
// cancelled by the client.
func (r *RPCAcceptor) Run() error {
// Wait for our goroutines to exit before we return.
defer r.wg.Wait()
// Create a channel that responses from acceptors are sent into.
responses := make(chan lnrpc.ChannelAcceptResponse)
// errChan is used by the receive loop to signal any errors that occur
// during reading from the stream. This is primarily used to shutdown
// the send loop in the case of an RPC client disconnecting.
errChan := make(chan error, 1)
// Start a goroutine to receive responses from the channel acceptor.
// We expect the receive function to block, so it must be run in a
// goroutine (otherwise we could not send more than one channel accept
// request to the client).
r.wg.Add(1)
go func() {
r.receiveResponses(errChan, responses)
r.wg.Done()
}()
return r.sendAcceptRequests(errChan, responses)
}
// receiveResponses receives responses for our channel accept requests and
// dispatches them into the responses channel provided, sending any errors that
// occur into the error channel provided.
func (r *RPCAcceptor) receiveResponses(errChan chan error,
responses chan lnrpc.ChannelAcceptResponse) {
for {
resp, err := r.receive()
if err != nil {
errChan <- err
return
}
var pendingID [32]byte
copy(pendingID[:], resp.PendingChanId)
openChanResp := lnrpc.ChannelAcceptResponse{
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
// acceptor requests.
select {
case responses <- openChanResp:
case <-r.done:
return
case <-r.quit:
return
}
}
}
// sendAcceptRequests handles channel acceptor requests sent to us by our
// Accept() function, dispatching them to our acceptor stream and coordinating
// return of responses to their callers.
func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
responses chan lnrpc.ChannelAcceptResponse) error {
// Close the done channel to indicate that the acceptor is no longer
// listening and any in-progress requests should be terminated.
defer close(r.done)
// 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 {
// Consume requests passed to us from our Accept() function and
// send them into our stream.
case newRequest := <-r.requests:
req := newRequest.request
pendingChanID := req.OpenChanMsg.PendingChannelID
acceptRequests[pendingChanID] = newRequest
// A ChannelAcceptRequest has been received, send it to the client.
chanAcceptReq := &lnrpc.ChannelAcceptRequest{
NodePubkey: req.Node.SerializeCompressed(),
ChainHash: req.OpenChanMsg.ChainHash[:],
PendingChanId: req.OpenChanMsg.PendingChannelID[:],
FundingAmt: uint64(req.OpenChanMsg.FundingAmount),
PushAmt: uint64(req.OpenChanMsg.PushAmount),
DustLimit: uint64(req.OpenChanMsg.DustLimit),
MaxValueInFlight: uint64(req.OpenChanMsg.MaxValueInFlight),
ChannelReserve: uint64(req.OpenChanMsg.ChannelReserve),
MinHtlc: uint64(req.OpenChanMsg.HtlcMinimum),
FeePerKw: uint64(req.OpenChanMsg.FeePerKiloWeight),
CsvDelay: uint32(req.OpenChanMsg.CsvDelay),
MaxAcceptedHtlcs: uint32(req.OpenChanMsg.MaxAcceptedHTLCs),
ChannelFlags: uint32(req.OpenChanMsg.ChannelFlags),
}
if err := r.send(chanAcceptReq); err != nil {
return err
}
// Process newly received responses from our channel acceptor,
// looking the original request up in our map of requests and
// dispatching the response.
case resp := <-responses:
// Look up the appropriate channel to send on given the
// pending ID. If a channel is found, send the response
// over it.
var pendingID [32]byte
copy(pendingID[:], resp.PendingChanId)
requestInfo, ok := acceptRequests[pendingID]
if !ok {
continue
}
// Validate the response we have received. If it is not
// valid, we log our error and proceed to deliver the
// rejection.
accept, acceptErr, shutdown, err := r.validateAcceptorResponse(
requestInfo.request.OpenChanMsg.DustLimit, resp,
)
if err != nil {
log.Errorf("Invalid acceptor response: %v", err)
}
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)
// If we failed to receive from our acceptor, we exit.
case err := <-errChan:
log.Errorf("Received an error: %v, shutting down", err)
return err
// Exit if we are shutting down.
case <-r.quit:
return errShuttingDown
}
}
}
// 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 (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, nil, errCustomLength
}
var haveCustomError = len(req.Error) != 0
switch {
// 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, nil, errAcceptWithError
// If we accept without an error message, we can just return a nil
// error.
case req.Accept:
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, 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, nil
}
}
// A compile-time constraint to ensure RPCAcceptor implements the ChannelAcceptor
// interface.
var _ ChannelAcceptor = (*RPCAcceptor)(nil)