mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
539 lines
16 KiB
Go
539 lines
16 KiB
Go
package chanacceptor
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"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, false,
|
|
)
|
|
|
|
// 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,
|
|
ZeroConf: resp.ZeroConf,
|
|
}
|
|
|
|
// 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 channel open
|
|
// message so that we can validate our response against it.
|
|
acceptRequests := make(map[[32]byte]*chanAcceptInfo)
|
|
|
|
for {
|
|
//nolint:lll
|
|
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
|
|
|
|
// Map the channel commitment type to its RPC
|
|
// counterpart. Also determine whether the zero-conf or
|
|
// scid-alias channel types are set.
|
|
var (
|
|
commitmentType lnrpc.CommitmentType
|
|
wantsZeroConf bool
|
|
wantsScidAlias bool
|
|
)
|
|
|
|
if req.OpenChanMsg.ChannelType != nil {
|
|
channelFeatures := lnwire.RawFeatureVector(
|
|
*req.OpenChanMsg.ChannelType,
|
|
)
|
|
switch {
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.ZeroConfRequired,
|
|
lnwire.ScidAliasRequired,
|
|
lnwire.ScriptEnforcedLeaseRequired,
|
|
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.ZeroConfRequired,
|
|
lnwire.ScriptEnforcedLeaseRequired,
|
|
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.ScidAliasRequired,
|
|
lnwire.ScriptEnforcedLeaseRequired,
|
|
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.ScriptEnforcedLeaseRequired,
|
|
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.ZeroConfRequired,
|
|
lnwire.ScidAliasRequired,
|
|
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_ANCHORS
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.ZeroConfRequired,
|
|
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_ANCHORS
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.ScidAliasRequired,
|
|
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_ANCHORS
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_ANCHORS
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.SimpleTaprootChannelsRequiredStaging,
|
|
lnwire.ZeroConfRequired,
|
|
lnwire.ScidAliasRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.SimpleTaprootChannelsRequiredStaging,
|
|
lnwire.ZeroConfRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.SimpleTaprootChannelsRequiredStaging,
|
|
lnwire.ScidAliasRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.SimpleTaprootChannelsRequiredStaging,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
|
|
|
case channelFeatures.OnlyContains(
|
|
lnwire.StaticRemoteKeyRequired,
|
|
):
|
|
commitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
|
|
|
|
case channelFeatures.OnlyContains():
|
|
commitmentType = lnrpc.CommitmentType_LEGACY
|
|
|
|
default:
|
|
log.Warnf("Unhandled commitment type "+
|
|
"in channel acceptor request: %v",
|
|
req.OpenChanMsg.ChannelType)
|
|
}
|
|
|
|
if channelFeatures.IsSet(
|
|
lnwire.ZeroConfRequired,
|
|
) {
|
|
|
|
wantsZeroConf = true
|
|
}
|
|
|
|
if channelFeatures.IsSet(
|
|
lnwire.ScidAliasRequired,
|
|
) {
|
|
|
|
wantsScidAlias = true
|
|
}
|
|
}
|
|
|
|
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),
|
|
CommitmentType: commitmentType,
|
|
WantsZeroConf: wantsZeroConf,
|
|
WantsScidAlias: wantsScidAlias,
|
|
}
|
|
|
|
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),
|
|
resp.ZeroConf,
|
|
)
|
|
|
|
// 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)
|