mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
246 lines
6.9 KiB
Go
246 lines
6.9 KiB
Go
package routerrpc
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
|
"github.com/lightningnetwork/lnd/fn"
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
var (
|
|
// ErrFwdNotExists is an error returned when the caller tries to resolve
|
|
// a forward that doesn't exist anymore.
|
|
ErrFwdNotExists = errors.New("forward does not exist")
|
|
|
|
// ErrMissingPreimage is an error returned when the caller tries to settle
|
|
// a forward and doesn't provide a preimage.
|
|
ErrMissingPreimage = errors.New("missing preimage")
|
|
)
|
|
|
|
// forwardInterceptor is a helper struct that handles the lifecycle of an RPC
|
|
// interceptor streaming session.
|
|
// It is created when the stream opens and disconnects when the stream closes.
|
|
type forwardInterceptor struct {
|
|
// stream is the bidirectional RPC stream
|
|
stream Router_HtlcInterceptorServer
|
|
|
|
htlcSwitch htlcswitch.InterceptableHtlcForwarder
|
|
}
|
|
|
|
// newForwardInterceptor creates a new forwardInterceptor.
|
|
func newForwardInterceptor(htlcSwitch htlcswitch.InterceptableHtlcForwarder,
|
|
stream Router_HtlcInterceptorServer) *forwardInterceptor {
|
|
|
|
return &forwardInterceptor{
|
|
htlcSwitch: htlcSwitch,
|
|
stream: stream,
|
|
}
|
|
}
|
|
|
|
// run sends the intercepted packets to the client and receives the
|
|
// corresponding responses. On one hand it registered itself as an interceptor
|
|
// that receives the switch packets and on the other hand launches a go routine
|
|
// to read from the client stream.
|
|
// To coordinate all this and make sure it is safe for concurrent access all
|
|
// packets are sent to the main where they are handled.
|
|
func (r *forwardInterceptor) run() error {
|
|
// Register our interceptor so we receive all forwarded packets.
|
|
r.htlcSwitch.SetInterceptor(r.onIntercept)
|
|
defer r.htlcSwitch.SetInterceptor(nil)
|
|
|
|
for {
|
|
resp, err := r.stream.Recv()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := r.resolveFromClient(resp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// onIntercept is the function that is called by the switch for every forwarded
|
|
// packet. Our interceptor makes sure we hold the packet and then signal to the
|
|
// main loop to handle the packet. We only return true if we were able
|
|
// to deliver the packet to the main loop.
|
|
func (r *forwardInterceptor) onIntercept(
|
|
htlc htlcswitch.InterceptedPacket) error {
|
|
|
|
log.Tracef("Sending intercepted packet to client %v", htlc)
|
|
|
|
inKey := htlc.IncomingCircuit
|
|
|
|
// First hold the forward, then send to client.
|
|
interceptionRequest := &ForwardHtlcInterceptRequest{
|
|
IncomingCircuitKey: &CircuitKey{
|
|
ChanId: inKey.ChanID.ToUint64(),
|
|
HtlcId: inKey.HtlcID,
|
|
},
|
|
OutgoingRequestedChanId: htlc.OutgoingChanID.ToUint64(),
|
|
PaymentHash: htlc.Hash[:],
|
|
OutgoingAmountMsat: uint64(htlc.OutgoingAmount),
|
|
OutgoingExpiry: htlc.OutgoingExpiry,
|
|
IncomingAmountMsat: uint64(htlc.IncomingAmount),
|
|
IncomingExpiry: htlc.IncomingExpiry,
|
|
CustomRecords: htlc.InOnionCustomRecords,
|
|
OnionBlob: htlc.OnionBlob[:],
|
|
AutoFailHeight: htlc.AutoFailHeight,
|
|
InWireCustomRecords: htlc.InWireCustomRecords,
|
|
}
|
|
|
|
return r.stream.Send(interceptionRequest)
|
|
}
|
|
|
|
// resolveFromClient handles a resolution arrived from the client.
|
|
func (r *forwardInterceptor) resolveFromClient(
|
|
in *ForwardHtlcInterceptResponse) error {
|
|
|
|
if in.IncomingCircuitKey == nil {
|
|
return status.Errorf(codes.InvalidArgument,
|
|
"CircuitKey missing from ForwardHtlcInterceptResponse")
|
|
}
|
|
|
|
log.Tracef("Resolving intercepted packet %v", in)
|
|
|
|
circuitKey := models.CircuitKey{
|
|
ChanID: lnwire.NewShortChanIDFromInt(
|
|
in.IncomingCircuitKey.ChanId,
|
|
),
|
|
HtlcID: in.IncomingCircuitKey.HtlcId,
|
|
}
|
|
|
|
switch in.Action {
|
|
case ResolveHoldForwardAction_RESUME:
|
|
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
|
|
Key: circuitKey,
|
|
Action: htlcswitch.FwdActionResume,
|
|
})
|
|
|
|
case ResolveHoldForwardAction_RESUME_MODIFIED:
|
|
// Modify HTLC and resume forward.
|
|
inAmtMsat := fn.None[lnwire.MilliSatoshi]()
|
|
if in.InAmountMsat > 0 {
|
|
inAmtMsat = fn.Some(lnwire.MilliSatoshi(
|
|
in.InAmountMsat,
|
|
))
|
|
}
|
|
|
|
outAmtMsat := fn.None[lnwire.MilliSatoshi]()
|
|
if in.OutAmountMsat > 0 {
|
|
outAmtMsat = fn.Some(lnwire.MilliSatoshi(
|
|
in.OutAmountMsat,
|
|
))
|
|
}
|
|
|
|
outWireCustomRecords := fn.None[lnwire.CustomRecords]()
|
|
if len(in.OutWireCustomRecords) > 0 {
|
|
// Validate custom records.
|
|
cr := lnwire.CustomRecords(in.OutWireCustomRecords)
|
|
if err := cr.Validate(); err != nil {
|
|
return status.Errorf(
|
|
codes.InvalidArgument,
|
|
"failed to validate custom records: %v",
|
|
err,
|
|
)
|
|
}
|
|
|
|
outWireCustomRecords = fn.Some[lnwire.CustomRecords](cr)
|
|
}
|
|
|
|
//nolint:lll
|
|
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
|
|
Key: circuitKey,
|
|
Action: htlcswitch.FwdActionResumeModified,
|
|
InAmountMsat: inAmtMsat,
|
|
OutAmountMsat: outAmtMsat,
|
|
OutWireCustomRecords: outWireCustomRecords,
|
|
})
|
|
|
|
case ResolveHoldForwardAction_FAIL:
|
|
// Fail with an encrypted reason.
|
|
if in.FailureMessage != nil {
|
|
if in.FailureCode != 0 {
|
|
return status.Errorf(
|
|
codes.InvalidArgument,
|
|
"failure message and failure code "+
|
|
"are mutually exclusive",
|
|
)
|
|
}
|
|
|
|
// Verify that the size is equal to the fixed failure
|
|
// message size + hmac + two uint16 lengths. See BOLT
|
|
// #4.
|
|
if len(in.FailureMessage) !=
|
|
lnwire.FailureMessageLength+32+2+2 {
|
|
|
|
return status.Errorf(
|
|
codes.InvalidArgument,
|
|
"failure message length invalid",
|
|
)
|
|
}
|
|
|
|
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
|
|
Key: circuitKey,
|
|
Action: htlcswitch.FwdActionFail,
|
|
FailureMessage: in.FailureMessage,
|
|
})
|
|
}
|
|
|
|
var code lnwire.FailCode
|
|
switch in.FailureCode {
|
|
case lnrpc.Failure_INVALID_ONION_HMAC:
|
|
code = lnwire.CodeInvalidOnionHmac
|
|
|
|
case lnrpc.Failure_INVALID_ONION_KEY:
|
|
code = lnwire.CodeInvalidOnionKey
|
|
|
|
case lnrpc.Failure_INVALID_ONION_VERSION:
|
|
code = lnwire.CodeInvalidOnionVersion
|
|
|
|
// Default to TemporaryChannelFailure.
|
|
case 0, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE:
|
|
code = lnwire.CodeTemporaryChannelFailure
|
|
|
|
default:
|
|
return status.Errorf(
|
|
codes.InvalidArgument,
|
|
"unsupported failure code: %v", in.FailureCode,
|
|
)
|
|
}
|
|
|
|
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
|
|
Key: circuitKey,
|
|
Action: htlcswitch.FwdActionFail,
|
|
FailureCode: code,
|
|
})
|
|
|
|
case ResolveHoldForwardAction_SETTLE:
|
|
if in.Preimage == nil {
|
|
return ErrMissingPreimage
|
|
}
|
|
preimage, err := lntypes.MakePreimage(in.Preimage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
|
|
Key: circuitKey,
|
|
Action: htlcswitch.FwdActionSettle,
|
|
Preimage: preimage,
|
|
})
|
|
|
|
default:
|
|
return status.Errorf(
|
|
codes.InvalidArgument,
|
|
"unrecognized resolve action %v", in.Action,
|
|
)
|
|
}
|
|
}
|