htlcswitch: add CheckCircularForward to handlePacketForward
Add a CheckCircularForward function which detects packets which are forwards over the same incoming and outgoing link, and errors if the node is configured to disallow forwards of this nature. This check is added to increase the cost of a liquidity lockup attack, because it increases the length of the route required to lock up an individual node's bandwidth. Since nodes are currently limited to 20 hops, increasing the length of the route needed to lock up capital increases the number of malicious payments an attacker will have to route, which increases the capital requirement of the attack overall.
This commit is contained in:
3 files changed
+225
No files matched your search
@@ -167,6 +167,10 @@ type Config struct {
|
||||
// fails in forwarding packages.
|
||||
AckEventTicker ticker.Ticker
|
||||
|
||||
// AllowCircularRoute is true if the user has configured their node to
|
||||
// allow forwards that arrive and depart our node over the same channel.
|
||||
AllowCircularRoute bool
|
||||
|
||||
// RejectHTLC is a flag that instructs the htlcswitch to reject any
|
||||
// HTLCs that are not from the source hop.
|
||||
RejectHTLC bool
|
||||
@@ -986,6 +990,22 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
||||
return s.handleLocalDispatch(packet)
|
||||
}
|
||||
|
||||
// Before we attempt to find a non-strict forwarding path for
|
||||
// this htlc, check whether the htlc is being routed over the
|
||||
// same incoming and outgoing channel. If our node does not
|
||||
// allow forwards of this nature, we fail the htlc early. This
|
||||
// check is in place to disallow inefficiently routed htlcs from
|
||||
// locking up our balance.
|
||||
linkErr := checkCircularForward(
|
||||
packet.incomingChanID, packet.outgoingChanID,
|
||||
s.cfg.AllowCircularRoute, htlc.PaymentHash,
|
||||
)
|
||||
if linkErr != nil {
|
||||
return s.failAddPacket(
|
||||
packet, linkErr.WireMessage(), linkErr,
|
||||
)
|
||||
}
|
||||
|
||||
s.indexMtx.RLock()
|
||||
targetLink, err := s.getLinkByShortID(packet.outgoingChanID)
|
||||
if err != nil {
|
||||
@@ -1170,6 +1190,37 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
||||
}
|
||||
}
|
||||
|
||||
// checkCircularForward checks whether a forward is circular (arrives and
|
||||
// departs on the same link) and returns a link error if the switch is
|
||||
// configured to disallow this behaviour.
|
||||
func checkCircularForward(incoming, outgoing lnwire.ShortChannelID,
|
||||
allowCircular bool, paymentHash lntypes.Hash) *LinkError {
|
||||
|
||||
// If the route is not circular we do not need to perform any further
|
||||
// checks.
|
||||
if incoming != outgoing {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the incoming and outgoing link are equal, the htlc is part of a
|
||||
// circular route which may be used to lock up our liquidity. If the
|
||||
// switch is configured to allow circular routes, log that we are
|
||||
// allowing the route then return nil.
|
||||
if allowCircular {
|
||||
log.Debugf("allowing circular route over link: %v "+
|
||||
"(payment hash: %x)", incoming, paymentHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If our node disallows circular routes, return a temporary channel
|
||||
// failure. There is nothing wrong with the policy used by the remote
|
||||
// node, so we do not include a channel update.
|
||||
return NewDetailedLinkError(
|
||||
lnwire.NewTemporaryChannelFailure(nil),
|
||||
FailureDetailCircularRoute,
|
||||
)
|
||||
}
|
||||
|
||||
// failAddPacket encrypts a fail packet back to an add packet's source.
|
||||
// The ciphertext will be derived from the failure message proivded by context.
|
||||
// This method returns the failErr if all other steps complete successfully.
|
||||
|
||||
Reference in new issue
Block a user