You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
14 KiB
438 lines
14 KiB
package htlcswitch |
|
|
|
import ( |
|
"fmt" |
|
"strings" |
|
"sync" |
|
"time" |
|
|
|
"github.com/lightningnetwork/lnd/channeldb" |
|
"github.com/lightningnetwork/lnd/htlcswitch/hop" |
|
"github.com/lightningnetwork/lnd/lntypes" |
|
"github.com/lightningnetwork/lnd/lnwire" |
|
"github.com/lightningnetwork/lnd/subscribe" |
|
) |
|
|
|
// HtlcNotifier notifies clients of htlc forwards, failures and settles for |
|
// htlcs that the switch handles. It takes subscriptions for its events and |
|
// notifies them when htlc events occur. These are served on a best-effort |
|
// basis; events are not persisted, delivery is not guaranteed (in the event |
|
// of a crash in the switch, forward events may be lost) and some events may |
|
// be replayed upon restart. Events consumed from this package should be |
|
// de-duplicated by the htlc's unique combination of incoming+outgoing circuit |
|
// and not relied upon for critical operations. |
|
// |
|
// The htlc notifier sends the following kinds of events: |
|
// Forwarding Event: |
|
// - Represents a htlc which is forwarded onward from our node. |
|
// - Present for htlc forwards through our node and local sends. |
|
// |
|
// Link Failure Event: |
|
// - Indicates that a htlc has failed on our incoming or outgoing link, |
|
// with an incoming boolean which indicates where the failure occurred. |
|
// - Incoming link failures are present for failed attempts to pay one of |
|
// our invoices (insufficient amount or mpp timeout, for example) and for |
|
// forwards that we cannot decode to forward onwards. |
|
// - Outgoing link failures are present for forwards or local payments that |
|
// do not meet our outgoing link's policy (insufficient fees, for example) |
|
// and when we fail to forward the payment on (insufficient outgoing |
|
// capacity, or an unknown outgoing link). |
|
// |
|
// Forwarding Failure Event: |
|
// - Forwarding failures indicate that a htlc we forwarded has failed at |
|
// another node down the route. |
|
// - Present for local sends and htlc forwards which fail after they left |
|
// our node. |
|
// |
|
// Settle event: |
|
// - Settle events are present when a htlc which we added is settled through |
|
// the release of a preimage. |
|
// - Present for local receives, and successful local sends or forwards. |
|
// |
|
// Each htlc is identified by its incoming and outgoing circuit key. Htlcs, |
|
// and their subsequent settles or fails, can be identified by the combination |
|
// of incoming and outgoing circuits. Note that receives to our node will |
|
// have a zero outgoing circuit key because the htlc terminates at our |
|
// node, and sends from our node will have a zero incoming circuit key because |
|
// the send originates at our node. |
|
type HtlcNotifier struct { |
|
started sync.Once |
|
stopped sync.Once |
|
|
|
// now returns the current time, it is set in the htlcnotifier to allow |
|
// for timestamp mocking in tests. |
|
now func() time.Time |
|
|
|
ntfnServer *subscribe.Server |
|
} |
|
|
|
// NewHtlcNotifier creates a new HtlcNotifier which gets htlc forwarded, |
|
// failed and settled events from links our node has established with peers |
|
// and sends notifications to subscribing clients. |
|
func NewHtlcNotifier(now func() time.Time) *HtlcNotifier { |
|
return &HtlcNotifier{ |
|
now: now, |
|
ntfnServer: subscribe.NewServer(), |
|
} |
|
} |
|
|
|
// Start starts the HtlcNotifier and all goroutines it needs to consume |
|
// events and provide subscriptions to clients. |
|
func (h *HtlcNotifier) Start() error { |
|
var err error |
|
h.started.Do(func() { |
|
log.Trace("HtlcNotifier starting") |
|
err = h.ntfnServer.Start() |
|
}) |
|
return err |
|
} |
|
|
|
// Stop signals the notifier for a graceful shutdown. |
|
func (h *HtlcNotifier) Stop() error { |
|
var err error |
|
h.stopped.Do(func() { |
|
if err = h.ntfnServer.Stop(); err != nil { |
|
log.Warnf("error stopping htlc notifier: %v", err) |
|
} |
|
}) |
|
return err |
|
} |
|
|
|
// SubscribeHtlcEvents returns a subscribe.Client that will receive updates |
|
// any time the server is made aware of a new event. |
|
func (h *HtlcNotifier) SubscribeHtlcEvents() (*subscribe.Client, error) { |
|
return h.ntfnServer.Subscribe() |
|
} |
|
|
|
// HtlcKey uniquely identifies the htlc. |
|
type HtlcKey struct { |
|
// IncomingCircuit is the channel an htlc id of the incoming htlc. |
|
IncomingCircuit channeldb.CircuitKey |
|
|
|
// OutgoingCircuit is the channel and htlc id of the outgoing htlc. |
|
OutgoingCircuit channeldb.CircuitKey |
|
} |
|
|
|
// String returns a string representation of a htlc key. |
|
func (k HtlcKey) String() string { |
|
switch { |
|
case k.IncomingCircuit.ChanID == hop.Source: |
|
return k.OutgoingCircuit.String() |
|
|
|
case k.OutgoingCircuit.ChanID == hop.Exit: |
|
return k.IncomingCircuit.String() |
|
|
|
default: |
|
return fmt.Sprintf("%v -> %v", k.IncomingCircuit, |
|
k.OutgoingCircuit) |
|
} |
|
} |
|
|
|
// HtlcInfo provides the details of a htlc that our node has processed. For |
|
// forwards, incoming and outgoing values are set, whereas sends and receives |
|
// will only have outgoing or incoming details set. |
|
type HtlcInfo struct { |
|
// IncomingTimelock is the time lock of the htlc on our incoming |
|
// channel. |
|
IncomingTimeLock uint32 |
|
|
|
// OutgoingTimelock is the time lock the htlc on our outgoing channel. |
|
OutgoingTimeLock uint32 |
|
|
|
// IncomingAmt is the amount of the htlc on our incoming channel. |
|
IncomingAmt lnwire.MilliSatoshi |
|
|
|
// OutgoingAmt is the amount of the htlc on our outgoing channel. |
|
OutgoingAmt lnwire.MilliSatoshi |
|
} |
|
|
|
// String returns a string representation of a htlc. |
|
func (h HtlcInfo) String() string { |
|
var details []string |
|
|
|
// If the incoming information is not zero, as is the case for a send, |
|
// we include the incoming amount and timelock. |
|
if h.IncomingAmt != 0 || h.IncomingTimeLock != 0 { |
|
str := fmt.Sprintf("incoming amount: %v, "+ |
|
"incoming timelock: %v", h.IncomingAmt, |
|
h.IncomingTimeLock) |
|
|
|
details = append(details, str) |
|
} |
|
|
|
// If the outgoing information is not zero, as is the case for a |
|
// receive, we include the outgoing amount and timelock. |
|
if h.OutgoingAmt != 0 || h.OutgoingTimeLock != 0 { |
|
str := fmt.Sprintf("outgoing amount: %v, "+ |
|
"outgoing timelock: %v", h.OutgoingAmt, |
|
h.OutgoingTimeLock) |
|
|
|
details = append(details, str) |
|
} |
|
|
|
return strings.Join(details, ", ") |
|
} |
|
|
|
// HtlcEventType represents the type of event that a htlc was part of. |
|
type HtlcEventType int |
|
|
|
const ( |
|
// HtlcEventTypeSend represents a htlc that was part of a send from |
|
// our node. |
|
HtlcEventTypeSend HtlcEventType = iota |
|
|
|
// HtlcEventTypeReceive represents a htlc that was part of a receive |
|
// to our node. |
|
HtlcEventTypeReceive |
|
|
|
// HtlcEventTypeForward represents a htlc that was forwarded through |
|
// our node. |
|
HtlcEventTypeForward |
|
) |
|
|
|
// String returns a string representation of a htlc event type. |
|
func (h HtlcEventType) String() string { |
|
switch h { |
|
case HtlcEventTypeSend: |
|
return "send" |
|
|
|
case HtlcEventTypeReceive: |
|
return "receive" |
|
|
|
case HtlcEventTypeForward: |
|
return "forward" |
|
|
|
default: |
|
return "unknown" |
|
} |
|
} |
|
|
|
// ForwardingEvent represents a htlc that was forwarded onwards from our node. |
|
// Sends which originate from our node will report forward events with zero |
|
// incoming circuits in their htlc key. |
|
type ForwardingEvent struct { |
|
// HtlcKey uniquely identifies the htlc, and can be used to match the |
|
// forwarding event with subsequent settle/fail events. |
|
HtlcKey |
|
|
|
// HtlcInfo contains details about the htlc. |
|
HtlcInfo |
|
|
|
// HtlcEventType classifies the event as part of a local send or |
|
// receive, or as part of a forward. |
|
HtlcEventType |
|
|
|
// Timestamp is the time when this htlc was forwarded. |
|
Timestamp time.Time |
|
} |
|
|
|
// LinkFailEvent describes a htlc that failed on our incoming or outgoing |
|
// link. The incoming bool is true for failures on incoming links, and false |
|
// for failures on outgoing links. The failure reason is provided by a lnwire |
|
// failure message which is enriched with a failure detail in the cases where |
|
// the wire failure message does not contain full information about the |
|
// failure. |
|
type LinkFailEvent struct { |
|
// HtlcKey uniquely identifies the htlc. |
|
HtlcKey |
|
|
|
// HtlcInfo contains details about the htlc. |
|
HtlcInfo |
|
|
|
// HtlcEventType classifies the event as part of a local send or |
|
// receive, or as part of a forward. |
|
HtlcEventType |
|
|
|
// LinkError is the reason that we failed the htlc. |
|
LinkError *LinkError |
|
|
|
// Incoming is true if the htlc was failed on an incoming link. |
|
// If it failed on the outgoing link, it is false. |
|
Incoming bool |
|
|
|
// Timestamp is the time when the link failure occurred. |
|
Timestamp time.Time |
|
} |
|
|
|
// ForwardingFailEvent represents a htlc failure which occurred down the line |
|
// after we forwarded a htlc onwards. An error is not included in this event |
|
// because errors returned down the route are encrypted. HtlcInfo is not |
|
// reliably available for forwarding failures, so it is omitted. These events |
|
// should be matched with their corresponding forward event to obtain this |
|
// information. |
|
type ForwardingFailEvent struct { |
|
// HtlcKey uniquely identifies the htlc, and can be used to match the |
|
// htlc with its corresponding forwarding event. |
|
HtlcKey |
|
|
|
// HtlcEventType classifies the event as part of a local send or |
|
// receive, or as part of a forward. |
|
HtlcEventType |
|
|
|
// Timestamp is the time when the forwarding failure was received. |
|
Timestamp time.Time |
|
} |
|
|
|
// SettleEvent represents a htlc that was settled. HtlcInfo is not reliably |
|
// available for forwarding failures, so it is omitted. These events should |
|
// be matched with corresponding forward events or invoices (for receives) |
|
// to obtain additional information about the htlc. |
|
type SettleEvent struct { |
|
// HtlcKey uniquely identifies the htlc, and can be used to match |
|
// forwards with their corresponding forwarding event. |
|
HtlcKey |
|
|
|
// Preimage that was released for settling the htlc. |
|
Preimage lntypes.Preimage |
|
|
|
// HtlcEventType classifies the event as part of a local send or |
|
// receive, or as part of a forward. |
|
HtlcEventType |
|
|
|
// Timestamp is the time when this htlc was settled. |
|
Timestamp time.Time |
|
} |
|
|
|
// NotifyForwardingEvent notifies the HtlcNotifier than a htlc has been |
|
// forwarded. |
|
// |
|
// Note this is part of the htlcNotifier interface. |
|
func (h *HtlcNotifier) NotifyForwardingEvent(key HtlcKey, info HtlcInfo, |
|
eventType HtlcEventType) { |
|
|
|
event := &ForwardingEvent{ |
|
HtlcKey: key, |
|
HtlcInfo: info, |
|
HtlcEventType: eventType, |
|
Timestamp: h.now(), |
|
} |
|
|
|
log.Tracef("Notifying forward event: %v over %v, %v", eventType, key, |
|
info) |
|
|
|
if err := h.ntfnServer.SendUpdate(event); err != nil { |
|
log.Warnf("Unable to send forwarding event: %v", err) |
|
} |
|
} |
|
|
|
// NotifyLinkFailEvent notifies that a htlc has failed on our incoming |
|
// or outgoing link. |
|
// |
|
// Note this is part of the htlcNotifier interface. |
|
func (h *HtlcNotifier) NotifyLinkFailEvent(key HtlcKey, info HtlcInfo, |
|
eventType HtlcEventType, linkErr *LinkError, incoming bool) { |
|
|
|
event := &LinkFailEvent{ |
|
HtlcKey: key, |
|
HtlcInfo: info, |
|
HtlcEventType: eventType, |
|
LinkError: linkErr, |
|
Incoming: incoming, |
|
Timestamp: h.now(), |
|
} |
|
|
|
log.Tracef("Notifying link failure event: %v over %v, %v", eventType, |
|
key, info) |
|
|
|
if err := h.ntfnServer.SendUpdate(event); err != nil { |
|
log.Warnf("Unable to send link fail event: %v", err) |
|
} |
|
} |
|
|
|
// NotifyForwardingFailEvent notifies the HtlcNotifier that a htlc we |
|
// forwarded has failed down the line. |
|
// |
|
// Note this is part of the htlcNotifier interface. |
|
func (h *HtlcNotifier) NotifyForwardingFailEvent(key HtlcKey, |
|
eventType HtlcEventType) { |
|
|
|
event := &ForwardingFailEvent{ |
|
HtlcKey: key, |
|
HtlcEventType: eventType, |
|
Timestamp: h.now(), |
|
} |
|
|
|
log.Tracef("Notifying forwarding failure event: %v over %v", eventType, |
|
key) |
|
|
|
if err := h.ntfnServer.SendUpdate(event); err != nil { |
|
log.Warnf("Unable to send forwarding fail event: %v", err) |
|
} |
|
} |
|
|
|
// NotifySettleEvent notifies the HtlcNotifier that a htlc that we committed |
|
// to as part of a forward or a receive to our node has been settled. |
|
// |
|
// Note this is part of the htlcNotifier interface. |
|
func (h *HtlcNotifier) NotifySettleEvent(key HtlcKey, |
|
preimage lntypes.Preimage, eventType HtlcEventType) { |
|
|
|
event := &SettleEvent{ |
|
HtlcKey: key, |
|
Preimage: preimage, |
|
HtlcEventType: eventType, |
|
Timestamp: h.now(), |
|
} |
|
|
|
log.Tracef("Notifying settle event: %v over %v", eventType, key) |
|
|
|
if err := h.ntfnServer.SendUpdate(event); err != nil { |
|
log.Warnf("Unable to send settle event: %v", err) |
|
} |
|
} |
|
|
|
// newHtlc key returns a htlc key for the packet provided. If the packet |
|
// has a zero incoming channel ID, the packet is for one of our own sends, |
|
// which has the payment id stashed in the incoming htlc id. If this is the |
|
// case, we replace the incoming htlc id with zero so that the notifier |
|
// consistently reports zero circuit keys for events that terminate or |
|
// originate at our node. |
|
func newHtlcKey(pkt *htlcPacket) HtlcKey { |
|
htlcKey := HtlcKey{ |
|
IncomingCircuit: channeldb.CircuitKey{ |
|
ChanID: pkt.incomingChanID, |
|
HtlcID: pkt.incomingHTLCID, |
|
}, |
|
OutgoingCircuit: CircuitKey{ |
|
ChanID: pkt.outgoingChanID, |
|
HtlcID: pkt.outgoingHTLCID, |
|
}, |
|
} |
|
|
|
// If the packet has a zero incoming channel ID, it is a send that was |
|
// initiated at our node. If this is the case, our internal pid is in |
|
// the incoming htlc ID, so we overwrite it with 0 for notification |
|
// purposes. |
|
if pkt.incomingChanID == hop.Source { |
|
htlcKey.IncomingCircuit.HtlcID = 0 |
|
} |
|
|
|
return htlcKey |
|
} |
|
|
|
// newHtlcInfo returns HtlcInfo for the packet provided. |
|
func newHtlcInfo(pkt *htlcPacket) HtlcInfo { |
|
return HtlcInfo{ |
|
IncomingTimeLock: pkt.incomingTimeout, |
|
OutgoingTimeLock: pkt.outgoingTimeout, |
|
IncomingAmt: pkt.incomingAmount, |
|
OutgoingAmt: pkt.amount, |
|
} |
|
} |
|
|
|
// getEventType returns the htlc type based on the fields set in the htlc |
|
// packet. Sends that originate at our node have the source (zero) incoming |
|
// channel ID. Receives to our node have the exit (zero) outgoing channel ID |
|
// and forwards have both fields set. |
|
func getEventType(pkt *htlcPacket) HtlcEventType { |
|
switch { |
|
case pkt.incomingChanID == hop.Source: |
|
return HtlcEventTypeSend |
|
|
|
case pkt.outgoingChanID == hop.Exit: |
|
return HtlcEventTypeReceive |
|
|
|
default: |
|
return HtlcEventTypeForward |
|
} |
|
}
|
|
|