2020-05-19 11:56:58 +02:00
|
|
|
package htlcswitch
|
|
|
|
|
|
|
|
import (
|
2022-01-18 15:17:05 +01:00
|
|
|
"crypto/sha256"
|
2020-05-19 11:56:58 +02:00
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/go-errors/errors"
|
2022-08-15 15:28:23 +02:00
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
2022-11-18 12:15:22 +01:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
2020-05-19 11:56:58 +02:00
|
|
|
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
)
|
|
|
|
|
|
|
|
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")
|
2022-01-18 15:17:05 +01:00
|
|
|
|
|
|
|
// ErrUnsupportedFailureCode when processing of an unsupported failure
|
|
|
|
// code is attempted.
|
|
|
|
ErrUnsupportedFailureCode = errors.New("unsupported failure code")
|
2022-08-15 15:28:23 +02:00
|
|
|
|
|
|
|
errBlockStreamStopped = errors.New("block epoch stream stopped")
|
2020-05-19 11:56:58 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// InterceptableSwitch is an implementation of ForwardingSwitch interface.
|
|
|
|
// This implementation is used like a proxy that wraps the switch and
|
|
|
|
// intercepts forward requests. A reference to the Switch is held in order
|
|
|
|
// to communicate back the interception result where the options are:
|
|
|
|
// Resume - forwards the original request to the switch as is.
|
|
|
|
// Settle - routes UpdateFulfillHTLC to the originating link.
|
|
|
|
// Fail - routes UpdateFailHTLC to the originating link.
|
|
|
|
type InterceptableSwitch struct {
|
|
|
|
// htlcSwitch is the underline switch
|
|
|
|
htlcSwitch *Switch
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
// intercepted is where we stream all intercepted packets coming from
|
|
|
|
// the switch.
|
|
|
|
intercepted chan *interceptedPackets
|
|
|
|
|
|
|
|
// resolutionChan is where we stream all responses coming from the
|
|
|
|
// interceptor client.
|
|
|
|
resolutionChan chan *fwdResolution
|
|
|
|
|
2022-02-04 14:32:15 +01:00
|
|
|
onchainIntercepted chan InterceptedForward
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
// interceptorRegistration is a channel that we use to synchronize
|
|
|
|
// client connect and disconnect.
|
|
|
|
interceptorRegistration chan ForwardInterceptor
|
|
|
|
|
2022-02-03 15:34:25 +01:00
|
|
|
// requireInterceptor indicates whether processing should block if no
|
|
|
|
// interceptor is connected.
|
|
|
|
requireInterceptor bool
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
// interceptor is the handler for intercepted packets.
|
|
|
|
interceptor ForwardInterceptor
|
|
|
|
|
2022-08-15 16:24:38 +02:00
|
|
|
// heldHtlcSet keeps track of outstanding intercepted forwards.
|
|
|
|
heldHtlcSet *heldHtlcSet
|
2022-02-07 08:53:10 +01:00
|
|
|
|
2022-01-28 12:56:17 +01:00
|
|
|
// cltvRejectDelta defines the number of blocks before the expiry of the
|
|
|
|
// htlc where we no longer intercept it and instead cancel it back.
|
|
|
|
cltvRejectDelta uint32
|
|
|
|
|
2022-08-15 17:28:19 +02:00
|
|
|
// cltvInterceptDelta defines the number of blocks before the expiry of
|
|
|
|
// the htlc where we don't intercept anymore. This value must be greater
|
|
|
|
// than CltvRejectDelta, because we don't want to offer htlcs to the
|
|
|
|
// interceptor client for which there is no time left to resolve them
|
|
|
|
// anymore.
|
|
|
|
cltvInterceptDelta uint32
|
|
|
|
|
2022-08-15 15:28:23 +02:00
|
|
|
// notifier is an instance of a chain notifier that we'll use to signal
|
|
|
|
// the switch when a new block has arrived.
|
|
|
|
notifier chainntnfs.ChainNotifier
|
|
|
|
|
|
|
|
// blockEpochStream is an active block epoch event stream backed by an
|
|
|
|
// active ChainNotifier instance. This will be used to retrieve the
|
|
|
|
// latest height of the chain.
|
|
|
|
blockEpochStream *chainntnfs.BlockEpochEvent
|
|
|
|
|
|
|
|
// currentHeight is the currently best known height.
|
|
|
|
currentHeight int32
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
wg sync.WaitGroup
|
|
|
|
quit chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type interceptedPackets struct {
|
|
|
|
packets []*htlcPacket
|
|
|
|
linkQuit chan struct{}
|
2022-02-03 15:34:25 +01:00
|
|
|
isReplay bool
|
2022-02-07 08:53:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// FwdAction defines the various resolution types.
|
|
|
|
type FwdAction int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// FwdActionResume forwards the intercepted packet to the switch.
|
|
|
|
FwdActionResume FwdAction = iota
|
|
|
|
|
|
|
|
// FwdActionSettle settles the intercepted packet with a preimage.
|
|
|
|
FwdActionSettle
|
|
|
|
|
|
|
|
// FwdActionFail fails the intercepted packet back to the sender.
|
|
|
|
FwdActionFail
|
|
|
|
)
|
|
|
|
|
|
|
|
// FwdResolution defines the action to be taken on an intercepted packet.
|
|
|
|
type FwdResolution struct {
|
|
|
|
// Key is the incoming circuit key of the htlc.
|
2022-11-18 12:15:22 +01:00
|
|
|
Key models.CircuitKey
|
2022-02-07 08:53:10 +01:00
|
|
|
|
|
|
|
// Action is the action to take on the intercepted htlc.
|
|
|
|
Action FwdAction
|
|
|
|
|
|
|
|
// Preimage is the preimage that is to be used for settling if Action is
|
|
|
|
// FwdActionSettle.
|
|
|
|
Preimage lntypes.Preimage
|
|
|
|
|
|
|
|
// FailureMessage is the encrypted failure message that is to be passed
|
|
|
|
// back to the sender if action is FwdActionFail.
|
|
|
|
FailureMessage []byte
|
|
|
|
|
|
|
|
// FailureCode is the failure code that is to be passed back to the
|
|
|
|
// sender if action is FwdActionFail.
|
|
|
|
FailureCode lnwire.FailCode
|
|
|
|
}
|
|
|
|
|
|
|
|
type fwdResolution struct {
|
|
|
|
resolution *FwdResolution
|
|
|
|
errChan chan error
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
2022-08-15 13:00:08 +02:00
|
|
|
// InterceptableSwitchConfig contains the configuration of InterceptableSwitch.
|
|
|
|
type InterceptableSwitchConfig struct {
|
|
|
|
// Switch is a reference to the actual switch implementation that
|
|
|
|
// packets get sent to on resume.
|
|
|
|
Switch *Switch
|
|
|
|
|
2022-08-15 15:28:23 +02:00
|
|
|
// Notifier is an instance of a chain notifier that we'll use to signal
|
|
|
|
// the switch when a new block has arrived.
|
|
|
|
Notifier chainntnfs.ChainNotifier
|
|
|
|
|
2022-08-15 13:00:08 +02:00
|
|
|
// CltvRejectDelta defines the number of blocks before the expiry of the
|
2022-08-15 17:28:19 +02:00
|
|
|
// htlc where we auto-fail an intercepted htlc to prevent channel
|
|
|
|
// force-closure.
|
2022-08-15 13:00:08 +02:00
|
|
|
CltvRejectDelta uint32
|
2022-02-03 15:34:25 +01:00
|
|
|
|
2022-08-15 17:28:19 +02:00
|
|
|
// CltvInterceptDelta defines the number of blocks before the expiry of
|
|
|
|
// the htlc where we don't intercept anymore. This value must be greater
|
|
|
|
// than CltvRejectDelta, because we don't want to offer htlcs to the
|
|
|
|
// interceptor client for which there is no time left to resolve them
|
|
|
|
// anymore.
|
|
|
|
CltvInterceptDelta uint32
|
|
|
|
|
2022-08-15 13:00:08 +02:00
|
|
|
// RequireInterceptor indicates whether processing should block if no
|
|
|
|
// interceptor is connected.
|
|
|
|
RequireInterceptor bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInterceptableSwitch returns an instance of InterceptableSwitch.
|
2022-10-14 14:25:25 +02:00
|
|
|
func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) (
|
|
|
|
*InterceptableSwitch, error) {
|
|
|
|
|
2022-08-15 17:28:19 +02:00
|
|
|
if cfg.CltvInterceptDelta <= cfg.CltvRejectDelta {
|
|
|
|
return nil, fmt.Errorf("cltv intercept delta %v not greater "+
|
|
|
|
"than cltv reject delta %v",
|
|
|
|
cfg.CltvInterceptDelta, cfg.CltvRejectDelta)
|
|
|
|
}
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
return &InterceptableSwitch{
|
2022-08-15 13:00:08 +02:00
|
|
|
htlcSwitch: cfg.Switch,
|
2022-02-07 08:53:10 +01:00
|
|
|
intercepted: make(chan *interceptedPackets),
|
2022-02-04 14:32:15 +01:00
|
|
|
onchainIntercepted: make(chan InterceptedForward),
|
2022-02-07 08:53:10 +01:00
|
|
|
interceptorRegistration: make(chan ForwardInterceptor),
|
2022-08-15 16:24:38 +02:00
|
|
|
heldHtlcSet: newHeldHtlcSet(),
|
2022-02-07 08:53:10 +01:00
|
|
|
resolutionChan: make(chan *fwdResolution),
|
2022-08-15 13:00:08 +02:00
|
|
|
requireInterceptor: cfg.RequireInterceptor,
|
|
|
|
cltvRejectDelta: cfg.CltvRejectDelta,
|
2022-08-15 17:28:19 +02:00
|
|
|
cltvInterceptDelta: cfg.CltvInterceptDelta,
|
2022-08-15 15:28:23 +02:00
|
|
|
notifier: cfg.Notifier,
|
2022-02-07 08:53:10 +01:00
|
|
|
|
|
|
|
quit: make(chan struct{}),
|
2022-10-14 14:25:25 +02:00
|
|
|
}, nil
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
// SetInterceptor sets the ForwardInterceptor to be used. A nil argument
|
|
|
|
// unregisters the current interceptor.
|
2020-05-19 11:56:58 +02:00
|
|
|
func (s *InterceptableSwitch) SetInterceptor(
|
|
|
|
interceptor ForwardInterceptor) {
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
// Synchronize setting the handler with the main loop to prevent race
|
|
|
|
// conditions.
|
|
|
|
select {
|
|
|
|
case s.interceptorRegistration <- interceptor:
|
|
|
|
|
|
|
|
case <-s.quit:
|
|
|
|
}
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
func (s *InterceptableSwitch) Start() error {
|
2022-08-15 15:28:23 +02:00
|
|
|
blockEpochStream, err := s.notifier.RegisterBlockEpochNtfn(nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.blockEpochStream = blockEpochStream
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
s.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer s.wg.Done()
|
|
|
|
|
2022-08-15 15:28:23 +02:00
|
|
|
err := s.run()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("InterceptableSwitch stopped: %v", err)
|
|
|
|
}
|
2022-02-07 08:53:10 +01:00
|
|
|
}()
|
2020-05-19 11:56:58 +02:00
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
return nil
|
|
|
|
}
|
2020-05-19 11:56:58 +02:00
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
func (s *InterceptableSwitch) Stop() error {
|
|
|
|
close(s.quit)
|
|
|
|
s.wg.Wait()
|
|
|
|
|
2022-08-15 15:28:23 +02:00
|
|
|
s.blockEpochStream.Cancel()
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-15 15:28:23 +02:00
|
|
|
func (s *InterceptableSwitch) run() error {
|
|
|
|
// The block epoch stream will immediately stream the current height.
|
|
|
|
// Read it out here.
|
|
|
|
select {
|
|
|
|
case currentBlock, ok := <-s.blockEpochStream.Epochs:
|
|
|
|
if !ok {
|
|
|
|
return errBlockStreamStopped
|
|
|
|
}
|
|
|
|
s.currentHeight = currentBlock.Height
|
|
|
|
|
|
|
|
case <-s.quit:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("InterceptableSwitch running: height=%v, "+
|
|
|
|
"requireInterceptor=%v", s.currentHeight, s.requireInterceptor)
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
// An interceptor registration or de-registration came in.
|
|
|
|
case interceptor := <-s.interceptorRegistration:
|
|
|
|
s.setInterceptor(interceptor)
|
|
|
|
|
|
|
|
case packets := <-s.intercepted:
|
|
|
|
var notIntercepted []*htlcPacket
|
|
|
|
for _, p := range packets.packets {
|
2022-08-15 16:24:38 +02:00
|
|
|
intercepted, err := s.interceptForward(
|
|
|
|
p, packets.isReplay,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !intercepted {
|
2022-02-07 08:53:10 +01:00
|
|
|
notIntercepted = append(
|
|
|
|
notIntercepted, p,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err := s.htlcSwitch.ForwardPackets(
|
|
|
|
packets.linkQuit, notIntercepted...,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Cannot forward packets: %v", err)
|
|
|
|
}
|
|
|
|
|
2022-02-04 14:32:15 +01:00
|
|
|
case fwd := <-s.onchainIntercepted:
|
|
|
|
// For on-chain interceptions, we don't know if it has
|
|
|
|
// already been offered before. This information is in
|
|
|
|
// the forwarding package which isn't easily accessible
|
|
|
|
// from contractcourt. It is likely though that it was
|
|
|
|
// already intercepted in the off-chain flow. And even
|
|
|
|
// if not, it is safe to signal replay so that we won't
|
|
|
|
// unexpectedly skip over this htlc.
|
2022-08-15 16:24:38 +02:00
|
|
|
if _, err := s.forward(fwd, true); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-02-04 14:32:15 +01:00
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
case res := <-s.resolutionChan:
|
|
|
|
res.errChan <- s.resolve(res.resolution)
|
|
|
|
|
2022-08-15 15:28:23 +02:00
|
|
|
case currentBlock, ok := <-s.blockEpochStream.Epochs:
|
|
|
|
if !ok {
|
|
|
|
return errBlockStreamStopped
|
|
|
|
}
|
|
|
|
|
|
|
|
s.currentHeight = currentBlock.Height
|
|
|
|
|
2022-08-15 17:28:19 +02:00
|
|
|
// A new block is appended. Fail any held htlcs that
|
|
|
|
// expire at this height to prevent channel force-close.
|
|
|
|
s.failExpiredHtlcs()
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
case <-s.quit:
|
2022-08-15 15:28:23 +02:00
|
|
|
return nil
|
2022-02-07 08:53:10 +01:00
|
|
|
}
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
2022-02-07 08:53:10 +01:00
|
|
|
}
|
2022-08-15 17:28:19 +02:00
|
|
|
|
|
|
|
func (s *InterceptableSwitch) failExpiredHtlcs() {
|
|
|
|
s.heldHtlcSet.popAutoFails(
|
|
|
|
uint32(s.currentHeight),
|
|
|
|
func(fwd InterceptedForward) {
|
|
|
|
err := fwd.FailWithCode(
|
|
|
|
lnwire.CodeTemporaryChannelFailure,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Cannot fail packet: %v", err)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
func (s *InterceptableSwitch) sendForward(fwd InterceptedForward) {
|
|
|
|
err := s.interceptor(fwd.Packet())
|
|
|
|
if err != nil {
|
|
|
|
// Only log the error. If we couldn't send the packet, we assume
|
|
|
|
// that the interceptor will reconnect so that we can retry.
|
|
|
|
log.Debugf("Interceptor cannot handle forward: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2020-05-19 11:56:58 +02:00
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) {
|
|
|
|
s.interceptor = interceptor
|
|
|
|
|
2022-02-03 15:34:25 +01:00
|
|
|
// Replay all currently held htlcs. When an interceptor is not required,
|
|
|
|
// there may be none because they've been cleared after the previous
|
|
|
|
// disconnect.
|
2022-02-07 08:53:10 +01:00
|
|
|
if interceptor != nil {
|
|
|
|
log.Debugf("Interceptor connected")
|
|
|
|
|
2022-08-15 16:24:38 +02:00
|
|
|
s.heldHtlcSet.forEach(s.sendForward)
|
2022-02-03 15:34:25 +01:00
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-02-03 15:34:25 +01:00
|
|
|
// The interceptor disconnects. If an interceptor is required, keep the
|
|
|
|
// held htlcs.
|
|
|
|
if s.requireInterceptor {
|
|
|
|
log.Infof("Interceptor disconnected, retaining held packets")
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Interceptor is not required. Release held forwards.
|
2022-02-07 08:53:10 +01:00
|
|
|
log.Infof("Interceptor disconnected, resolving held packets")
|
|
|
|
|
2022-08-15 16:24:38 +02:00
|
|
|
s.heldHtlcSet.popAll(func(fwd InterceptedForward) {
|
|
|
|
err := fwd.Resume()
|
|
|
|
if err != nil {
|
2022-02-07 08:53:10 +01:00
|
|
|
log.Errorf("Failed to resume hold forward %v", err)
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
2022-08-15 16:24:38 +02:00
|
|
|
})
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
func (s *InterceptableSwitch) resolve(res *FwdResolution) error {
|
2022-08-15 16:24:38 +02:00
|
|
|
intercepted, err := s.heldHtlcSet.pop(res.Key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2022-02-07 08:53:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
switch res.Action {
|
|
|
|
case FwdActionResume:
|
|
|
|
return intercepted.Resume()
|
|
|
|
|
|
|
|
case FwdActionSettle:
|
|
|
|
return intercepted.Settle(res.Preimage)
|
|
|
|
|
|
|
|
case FwdActionFail:
|
|
|
|
if len(res.FailureMessage) > 0 {
|
|
|
|
return intercepted.Fail(res.FailureMessage)
|
|
|
|
}
|
|
|
|
|
|
|
|
return intercepted.FailWithCode(res.FailureCode)
|
|
|
|
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unrecognized action %v", res.Action)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve resolves an intercepted packet.
|
|
|
|
func (s *InterceptableSwitch) Resolve(res *FwdResolution) error {
|
|
|
|
internalRes := &fwdResolution{
|
|
|
|
resolution: res,
|
|
|
|
errChan: make(chan error, 1),
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case s.resolutionChan <- internalRes:
|
|
|
|
|
|
|
|
case <-s.quit:
|
|
|
|
return errors.New("switch shutting down")
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case err := <-internalRes.errChan:
|
|
|
|
return err
|
2020-05-19 11:56:58 +02:00
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
case <-s.quit:
|
|
|
|
return errors.New("switch shutting down")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForwardPackets attempts to forward the batch of htlcs to a connected
|
|
|
|
// interceptor. If the interceptor signals the resume action, the htlcs are
|
|
|
|
// forwarded to the switch. The link's quit signal should be provided to allow
|
|
|
|
// cancellation of forwarding during link shutdown.
|
2022-02-03 15:34:25 +01:00
|
|
|
func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{}, isReplay bool,
|
2022-02-07 08:53:10 +01:00
|
|
|
packets ...*htlcPacket) error {
|
|
|
|
|
|
|
|
// Synchronize with the main event loop. This should be light in the
|
|
|
|
// case where there is no interceptor.
|
|
|
|
select {
|
|
|
|
case s.intercepted <- &interceptedPackets{
|
|
|
|
packets: packets,
|
|
|
|
linkQuit: linkQuit,
|
2022-02-03 15:34:25 +01:00
|
|
|
isReplay: isReplay,
|
2022-02-07 08:53:10 +01:00
|
|
|
}:
|
|
|
|
|
|
|
|
case <-linkQuit:
|
|
|
|
log.Debugf("Forward cancelled because link quit")
|
|
|
|
|
|
|
|
case <-s.quit:
|
|
|
|
return errors.New("interceptable switch quit")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-02-04 14:32:15 +01:00
|
|
|
// ForwardPacket forwards a single htlc to the external interceptor.
|
|
|
|
func (s *InterceptableSwitch) ForwardPacket(
|
|
|
|
fwd InterceptedForward) error {
|
|
|
|
|
|
|
|
select {
|
|
|
|
case s.onchainIntercepted <- fwd:
|
|
|
|
|
|
|
|
case <-s.quit:
|
|
|
|
return errors.New("interceptable switch quit")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
// interceptForward forwards the packet to the external interceptor after
|
|
|
|
// checking the interception criteria.
|
2022-02-03 15:34:25 +01:00
|
|
|
func (s *InterceptableSwitch) interceptForward(packet *htlcPacket,
|
2022-08-15 16:24:38 +02:00
|
|
|
isReplay bool) (bool, error) {
|
2022-02-03 15:34:25 +01:00
|
|
|
|
2020-05-19 11:56:58 +02:00
|
|
|
switch htlc := packet.htlc.(type) {
|
|
|
|
case *lnwire.UpdateAddHTLC:
|
2022-01-13 17:29:43 +01:00
|
|
|
// We are not interested in intercepting initiated payments.
|
2020-05-19 11:56:58 +02:00
|
|
|
if packet.incomingChanID == hop.Source {
|
2022-08-15 16:24:38 +02:00
|
|
|
return false, nil
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
intercepted := &interceptedForward{
|
|
|
|
htlc: htlc,
|
|
|
|
packet: packet,
|
|
|
|
htlcSwitch: s.htlcSwitch,
|
2022-08-15 17:28:19 +02:00
|
|
|
autoFailHeight: int32(packet.incomingTimeout -
|
|
|
|
s.cltvRejectDelta),
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
2022-01-28 12:56:17 +01:00
|
|
|
// Handle forwards that are too close to expiry.
|
|
|
|
handled, err := s.handleExpired(intercepted)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error handling intercepted htlc "+
|
|
|
|
"that expires too soon: circuit=%v, "+
|
|
|
|
"incoming_timeout=%v, err=%v",
|
|
|
|
packet.inKey(), packet.incomingTimeout, err)
|
|
|
|
|
|
|
|
// Return false so that the packet is offered as normal
|
|
|
|
// to the switch. This isn't ideal because interception
|
|
|
|
// may be configured as always-on and is skipped now.
|
|
|
|
// Returning true isn't great either, because the htlc
|
|
|
|
// will remain stuck and potentially force-close the
|
|
|
|
// channel. But in the end, we should never get here, so
|
|
|
|
// the actual return value doesn't matter that much.
|
2022-08-15 16:24:38 +02:00
|
|
|
return false, nil
|
2022-01-28 12:56:17 +01:00
|
|
|
}
|
|
|
|
if handled {
|
2022-08-15 16:24:38 +02:00
|
|
|
return true, nil
|
2022-01-28 12:56:17 +01:00
|
|
|
}
|
|
|
|
|
2022-03-20 08:19:02 +01:00
|
|
|
return s.forward(intercepted, isReplay)
|
|
|
|
|
|
|
|
default:
|
2022-08-15 16:24:38 +02:00
|
|
|
return false, nil
|
2022-03-20 08:19:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// forward records the intercepted htlc and forwards it to the interceptor.
|
|
|
|
func (s *InterceptableSwitch) forward(
|
2022-08-15 16:24:38 +02:00
|
|
|
fwd InterceptedForward, isReplay bool) (bool, error) {
|
2022-03-20 08:19:02 +01:00
|
|
|
|
|
|
|
inKey := fwd.Packet().IncomingCircuit
|
|
|
|
|
|
|
|
// Ignore already held htlcs.
|
2022-08-15 16:24:38 +02:00
|
|
|
if s.heldHtlcSet.exists(inKey) {
|
|
|
|
return true, nil
|
2022-03-20 08:19:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// If there is no interceptor currently registered, configuration and packet
|
|
|
|
// replay status determine how the packet is handled.
|
|
|
|
if s.interceptor == nil {
|
|
|
|
// Process normally if an interceptor is not required.
|
|
|
|
if !s.requireInterceptor {
|
2022-08-15 16:24:38 +02:00
|
|
|
return false, nil
|
2022-03-20 08:19:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// We are in interceptor-required mode. If this is a new packet, it is
|
|
|
|
// still safe to fail back. The interceptor has never seen this packet
|
|
|
|
// yet. This limits the backlog of htlcs when the interceptor is down.
|
|
|
|
if !isReplay {
|
|
|
|
err := fwd.FailWithCode(
|
2022-02-03 15:34:25 +01:00
|
|
|
lnwire.CodeTemporaryChannelFailure,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Cannot fail packet: %v", err)
|
|
|
|
}
|
|
|
|
|
2022-08-15 16:24:38 +02:00
|
|
|
return true, nil
|
2022-02-03 15:34:25 +01:00
|
|
|
}
|
|
|
|
|
2022-03-20 08:19:02 +01:00
|
|
|
// This packet is a replay. It is not safe to fail back, because the
|
|
|
|
// interceptor may still signal otherwise upon reconnect. Keep the
|
|
|
|
// packet in the queue until then.
|
2022-08-15 16:24:38 +02:00
|
|
|
if err := s.heldHtlcSet.push(inKey, fwd); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2022-02-07 08:53:10 +01:00
|
|
|
|
2022-08-15 16:24:38 +02:00
|
|
|
return true, nil
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
2022-03-20 08:19:02 +01:00
|
|
|
|
|
|
|
// There is an interceptor registered. We can forward the packet right now.
|
|
|
|
// Hold it in the queue too to track what is outstanding.
|
2022-08-15 16:24:38 +02:00
|
|
|
if err := s.heldHtlcSet.push(inKey, fwd); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2022-03-20 08:19:02 +01:00
|
|
|
s.sendForward(fwd)
|
|
|
|
|
2022-08-15 16:24:38 +02:00
|
|
|
return true, nil
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
2022-01-28 12:56:17 +01:00
|
|
|
// handleExpired checks that the htlc isn't too close to the channel
|
|
|
|
// force-close broadcast height. If it is, it is cancelled back.
|
|
|
|
func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) (
|
|
|
|
bool, error) {
|
|
|
|
|
2022-08-15 15:28:23 +02:00
|
|
|
height := uint32(s.currentHeight)
|
2022-08-15 17:28:19 +02:00
|
|
|
if fwd.packet.incomingTimeout >= height+s.cltvInterceptDelta {
|
2022-01-28 12:56:17 +01:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("Interception rejected because htlc "+
|
|
|
|
"expires too soon: circuit=%v, "+
|
|
|
|
"height=%v, incoming_timeout=%v",
|
|
|
|
fwd.packet.inKey(), height,
|
|
|
|
fwd.packet.incomingTimeout)
|
|
|
|
|
|
|
|
err := fwd.FailWithCode(
|
|
|
|
lnwire.CodeExpiryTooSoon,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2020-05-19 11:56:58 +02:00
|
|
|
// interceptedForward implements the InterceptedForward interface.
|
|
|
|
// It is passed from the switch to external interceptors that are interested
|
|
|
|
// in holding forwards and resolve them manually.
|
|
|
|
type interceptedForward struct {
|
2022-08-15 17:28:19 +02:00
|
|
|
htlc *lnwire.UpdateAddHTLC
|
|
|
|
packet *htlcPacket
|
|
|
|
htlcSwitch *Switch
|
|
|
|
autoFailHeight int32
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Packet returns the intercepted htlc packet.
|
2020-06-23 16:19:41 +02:00
|
|
|
func (f *interceptedForward) Packet() InterceptedPacket {
|
|
|
|
return InterceptedPacket{
|
2022-11-18 12:15:22 +01:00
|
|
|
IncomingCircuit: models.CircuitKey{
|
2020-06-23 16:19:41 +02:00
|
|
|
ChanID: f.packet.incomingChanID,
|
|
|
|
HtlcID: f.packet.incomingHTLCID,
|
|
|
|
},
|
2020-06-23 16:22:00 +02:00
|
|
|
OutgoingChanID: f.packet.outgoingChanID,
|
2020-06-23 16:19:41 +02:00
|
|
|
Hash: f.htlc.PaymentHash,
|
|
|
|
OutgoingExpiry: f.htlc.Expiry,
|
|
|
|
OutgoingAmount: f.htlc.Amount,
|
2020-06-23 16:22:00 +02:00
|
|
|
IncomingAmount: f.packet.incomingAmount,
|
|
|
|
IncomingExpiry: f.packet.incomingTimeout,
|
2020-06-24 12:03:00 +02:00
|
|
|
CustomRecords: f.packet.customRecords,
|
2020-06-25 18:43:47 +02:00
|
|
|
OnionBlob: f.htlc.OnionBlob,
|
2022-08-15 17:28:19 +02:00
|
|
|
AutoFailHeight: f.autoFailHeight,
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resume resumes the default behavior as if the packet was not intercepted.
|
|
|
|
func (f *interceptedForward) Resume() error {
|
2022-02-07 08:53:10 +01:00
|
|
|
// Forward to the switch. A link quit channel isn't needed, because we
|
|
|
|
// are on a different thread now.
|
|
|
|
return f.htlcSwitch.ForwardPackets(nil, f.packet)
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
|
2022-02-07 08:53:10 +01:00
|
|
|
// Fail notifies the intention to Fail an existing hold forward with an
|
2022-01-18 15:17:05 +01:00
|
|
|
// encrypted failure reason.
|
|
|
|
func (f *interceptedForward) Fail(reason []byte) error {
|
|
|
|
obfuscatedReason := f.packet.obfuscator.IntermediateEncrypt(reason)
|
|
|
|
|
|
|
|
return f.resolve(&lnwire.UpdateFailHTLC{
|
|
|
|
Reason: obfuscatedReason,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// FailWithCode notifies the intention to fail an existing hold forward with the
|
|
|
|
// specified failure code.
|
|
|
|
func (f *interceptedForward) FailWithCode(code lnwire.FailCode) error {
|
|
|
|
shaOnionBlob := func() [32]byte {
|
|
|
|
return sha256.Sum256(f.htlc.OnionBlob[:])
|
2021-04-22 19:39:37 +02:00
|
|
|
}
|
|
|
|
|
2022-01-18 15:17:05 +01:00
|
|
|
// Create a local failure.
|
|
|
|
var failureMsg lnwire.FailureMessage
|
|
|
|
|
|
|
|
switch code {
|
|
|
|
case lnwire.CodeInvalidOnionVersion:
|
|
|
|
failureMsg = &lnwire.FailInvalidOnionVersion{
|
|
|
|
OnionSHA256: shaOnionBlob(),
|
|
|
|
}
|
|
|
|
|
|
|
|
case lnwire.CodeInvalidOnionHmac:
|
|
|
|
failureMsg = &lnwire.FailInvalidOnionHmac{
|
|
|
|
OnionSHA256: shaOnionBlob(),
|
|
|
|
}
|
|
|
|
|
|
|
|
case lnwire.CodeInvalidOnionKey:
|
|
|
|
failureMsg = &lnwire.FailInvalidOnionKey{
|
|
|
|
OnionSHA256: shaOnionBlob(),
|
|
|
|
}
|
|
|
|
|
|
|
|
case lnwire.CodeTemporaryChannelFailure:
|
server+htlcswitch: prevent privacy leaks, allow alias routing
This intent of this change is to prevent privacy leaks when routing
with aliases and also to allow routing when using an alias. The
aliases are our aliases.
Introduces are two maps:
* aliasToReal:
This is an N->1 mapping for a channel. The keys are the set of
aliases and the value is the confirmed, on-chain SCID.
* baseIndex:
This is also an N->1 mapping for a channel. The keys are the set
of aliases and the value is the "base" SCID (whatever is in the
OpenChannel.ShortChannelID field). There is also a base->base
mapping, so not all keys are aliases.
The above maps are populated when a link is added to the switch and
when the channel has confirmed on-chain. The maps are not removed
from if the link is removed, but this is fine since forwarding won't
occur.
* getLinkByMapping
This function is introduced to adhere to the spec requirements that
using the confirmed SCID of a private, scid-alias-feature-bit
channel does not work. Lnd implements a stricter version of the spec
and disallows this behavior if the feature-bit was negotiated, rather
than just the channel type. The old, privacy-leak behavior is
preserved.
The spec also requires that if we must fail back an HTLC, the
ChannelUpdate must use the SCID of whatever was in the onion, to avoid
a privacy leak. This is also done by passing in the relevant SCID to
the mailbox and link. Lnd will also cancel back on the "incoming" side
if the InterceptableSwitch was used or if the link failed to decrypt
the onion. In this case, we are cautious and replace the SCID if an
alias exists.
2022-04-04 22:44:51 +02:00
|
|
|
update := f.htlcSwitch.failAliasUpdate(
|
|
|
|
f.packet.incomingChanID, true,
|
2022-01-18 15:17:05 +01:00
|
|
|
)
|
server+htlcswitch: prevent privacy leaks, allow alias routing
This intent of this change is to prevent privacy leaks when routing
with aliases and also to allow routing when using an alias. The
aliases are our aliases.
Introduces are two maps:
* aliasToReal:
This is an N->1 mapping for a channel. The keys are the set of
aliases and the value is the confirmed, on-chain SCID.
* baseIndex:
This is also an N->1 mapping for a channel. The keys are the set
of aliases and the value is the "base" SCID (whatever is in the
OpenChannel.ShortChannelID field). There is also a base->base
mapping, so not all keys are aliases.
The above maps are populated when a link is added to the switch and
when the channel has confirmed on-chain. The maps are not removed
from if the link is removed, but this is fine since forwarding won't
occur.
* getLinkByMapping
This function is introduced to adhere to the spec requirements that
using the confirmed SCID of a private, scid-alias-feature-bit
channel does not work. Lnd implements a stricter version of the spec
and disallows this behavior if the feature-bit was negotiated, rather
than just the channel type. The old, privacy-leak behavior is
preserved.
The spec also requires that if we must fail back an HTLC, the
ChannelUpdate must use the SCID of whatever was in the onion, to avoid
a privacy leak. This is also done by passing in the relevant SCID to
the mailbox and link. Lnd will also cancel back on the "incoming" side
if the InterceptableSwitch was used or if the link failed to decrypt
the onion. In this case, we are cautious and replace the SCID if an
alias exists.
2022-04-04 22:44:51 +02:00
|
|
|
if update == nil {
|
|
|
|
// Fallback to the original, non-alias behavior.
|
|
|
|
var err error
|
|
|
|
update, err = f.htlcSwitch.cfg.FetchLastChannelUpdate(
|
|
|
|
f.packet.incomingChanID,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-18 15:17:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
failureMsg = lnwire.NewTemporaryChannelFailure(update)
|
|
|
|
|
2022-01-28 12:56:17 +01:00
|
|
|
case lnwire.CodeExpiryTooSoon:
|
|
|
|
update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate(
|
|
|
|
f.packet.incomingChanID,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
failureMsg = lnwire.NewExpiryTooSoon(*update)
|
|
|
|
|
2022-01-18 15:17:05 +01:00
|
|
|
default:
|
|
|
|
return ErrUnsupportedFailureCode
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encrypt the failure for the first hop. This node will be the origin
|
|
|
|
// of the failure.
|
|
|
|
reason, err := f.packet.obfuscator.EncryptFirstHop(failureMsg)
|
2020-05-19 11:56:58 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to encrypt failure reason %v", err)
|
|
|
|
}
|
2022-01-18 15:17:05 +01:00
|
|
|
|
2020-05-19 11:56:58 +02:00
|
|
|
return f.resolve(&lnwire.UpdateFailHTLC{
|
|
|
|
Reason: reason,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Settle forwards a settled packet to the switch.
|
|
|
|
func (f *interceptedForward) Settle(preimage lntypes.Preimage) error {
|
|
|
|
if !preimage.Matches(f.htlc.PaymentHash) {
|
|
|
|
return errors.New("preimage does not match hash")
|
|
|
|
}
|
|
|
|
return f.resolve(&lnwire.UpdateFulfillHTLC{
|
|
|
|
PaymentPreimage: preimage,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// resolve is used for both Settle and Fail and forwards the message to the
|
|
|
|
// switch.
|
|
|
|
func (f *interceptedForward) resolve(message lnwire.Message) error {
|
|
|
|
pkt := &htlcPacket{
|
|
|
|
incomingChanID: f.packet.incomingChanID,
|
|
|
|
incomingHTLCID: f.packet.incomingHTLCID,
|
|
|
|
outgoingChanID: f.packet.outgoingChanID,
|
|
|
|
outgoingHTLCID: f.packet.outgoingHTLCID,
|
|
|
|
isResolution: true,
|
|
|
|
circuit: f.packet.circuit,
|
|
|
|
htlc: message,
|
|
|
|
obfuscator: f.packet.obfuscator,
|
2021-05-07 00:17:49 +02:00
|
|
|
sourceRef: f.packet.sourceRef,
|
2020-05-19 11:56:58 +02:00
|
|
|
}
|
|
|
|
return f.htlcSwitch.mailOrchestrator.Deliver(pkt.incomingChanID, pkt)
|
|
|
|
}
|