onionmessage

package
v0.21.0-beta.rc1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// DefaultOnionMailboxSize is the buffer capacity for per-peer onion
	// message actor mailboxes.
	DefaultOnionMailboxSize = 50

	// DefaultMinREDThreshold is the queue depth at which Random Early
	// Detection begins probabilistically dropping onion messages. Below
	// this threshold no drops occur; above DefaultOnionMailboxSize all
	// messages are dropped. Must be strictly less than
	// DefaultOnionMailboxSize.
	DefaultMinREDThreshold = 40

	// DefaultPeerOnionMsgKbps is the default sustained per-peer onion
	// message ingress rate, in decimal kilobits per second (1 Kbps =
	// 1000 bits/s). Sizing is expressed against a 32 KiB onion_message
	// packet (the BOLT 4 spec cap on the sphinx-level payload inside
	// onion_message), not the 65 KiB lnwire envelope cap — at ~32 KiB
	// per packet this is roughly two such messages per second per peer.
	// A value of zero disables the per-peer limiter entirely.
	DefaultPeerOnionMsgKbps = 512

	// DefaultPeerOnionMsgBurstBytes is the default per-peer token bucket
	// depth, in bytes. Sized to hold approximately eight 32 KiB onion
	// message packets (see DefaultPeerOnionMsgKbps for why we measure
	// against 32 KiB rather than the 65 KiB lnwire envelope cap) so a
	// peer can briefly burst above the sustained rate without drops.
	DefaultPeerOnionMsgBurstBytes = 8 * 32 * 1024

	// DefaultGlobalOnionMsgKbps is the default sustained aggregate onion
	// message ingress rate across all peers, in decimal kilobits per
	// second. Targets ~5 Mbps worst-case ingress so that onion message
	// bandwidth cannot dwarf a typical routing node's payment traffic.
	// A value of zero disables the global limiter entirely.
	DefaultGlobalOnionMsgKbps = 5120

	// DefaultGlobalOnionMsgBurstBytes is the default global token bucket
	// depth, in bytes. Sized to hold approximately fifty 32 KiB onion
	// message packets, measured against the BOLT 4 onion_message_packet
	// cap rather than the 65 KiB lnwire envelope cap (see
	// DefaultPeerOnionMsgKbps).
	DefaultGlobalOnionMsgBurstBytes = 50 * 32 * 1024
)
View Source
const Subsystem = "OMSG"

Subsystem defines the logging code for this subsystem.

Variables

View Source
var (
	// ErrActorShuttingDown is returned by the actor logic when its context
	// is cancelled.
	ErrActorShuttingDown = errors.New("actor shutting down")

	// ErrNextNodeIdEmpty is returned when the next node ID is missing from
	// the route data.
	ErrNextNodeIdEmpty = errors.New("next node ID empty")

	// ErrSCIDEmpty is returned when the short channel ID is missing from
	// the route data.
	ErrSCIDEmpty = errors.New("short channel ID empty")

	// ErrSamePeerCycle is returned when a forwarding onion message
	// would be sent back to the same peer it was received from.
	ErrSamePeerCycle = errors.New("onion message cycle: next " +
		"hop is the sending peer")
)
View Source
var (
	// ErrPeerRateLimit is the sentinel error returned by
	// IngressLimiter.AllowN when the per-peer token bucket rejects an
	// incoming onion message. Callers match on it with errors.Is to
	// distinguish per-peer drops from global drops.
	ErrPeerRateLimit = errors.New("per-peer rate limit exceeded")

	// ErrGlobalRateLimit is the sentinel error returned by
	// IngressLimiter.AllowN when the global token bucket rejects an
	// incoming onion message. Callers match on it with errors.Is to
	// distinguish global drops from per-peer drops.
	ErrGlobalRateLimit = errors.New("global rate limit exceeded")
)

Functions

func BuildBlindedPath

func BuildBlindedPath(t *testing.T,
	hops []*sphinx.HopInfo) *sphinx.BlindedPathInfo

BuildBlindedPath creates a BlindedPathInfo from a list of HopInfo. This is a test helper that wraps sphinx.BuildBlindedPath with a fresh session key.

func BuildOnionMessage

func BuildOnionMessage(t *testing.T, blindedPath *sphinx.BlindedPathInfo,
	finalHopTLVs []*lnwire.FinalHopTLV) (*lnwire.OnionMessage,
	[][]byte)

BuildOnionMessage builds an onion message from a BlindedPathInfo and returns the message along with the ciphertexts for each blinded hop (in hop order). If finalPayloads is nil or empty, no final hop payload data is included.

func ConcatBlindedPaths

func ConcatBlindedPaths(t *testing.T, senderPath,
	receiverPath *sphinx.BlindedPathInfo) *sphinx.BlindedPathInfo

ConcatBlindedPaths concatenates two blinded paths. The sender's path points TO the introduction node (with NextBlindingOverride), and the receiver's path starts AT the introduction node. The concatenated path includes all hops from both paths - the sender's last hop instructs forwarding to the intro node, and all receiver hops follow.

func DefaultOnionActorOpts

func DefaultOnionActorOpts() []actor.ActorOption[*Request, *Response]

DefaultOnionActorOpts returns ActorOptions that configure a BackpressureMailbox with a RED drop predicate and the default onion mailbox size. The RED thresholds are derived from the mailbox capacity so that all parameters are centralised and self-consistent.

func DisableLog

func DisableLog()

DisableLog disables all library log output. Logging output is disabled by default until UseLogger is called.

func EncodeBlindedRouteData

func EncodeBlindedRouteData(t *testing.T,
	data *record.BlindedRouteData) []byte

EncodeBlindedRouteData encodes BlindedRouteData to bytes for use in test hop payloads.

func NewOnionMessageServiceKey

func NewOnionMessageServiceKey(
	pubKey [33]byte) (actor.ServiceKey[*Request, *Response], string)

NewOnionMessageServiceKey creates a service key for registering and looking up onion peer actors. The service key uses the peer's compressed public key (hex-encoded) as the identifier. It returns both the service key and the hex-encoded public key string for use in actor naming and logging.

func StopOnionActor

func StopOnionActor(system *actor.ActorSystem, pubKey [33]byte,
	ref OnionPeerActorRef)

StopOnionActor stops the onion peer actor for the given public key using the provided actor reference. This should be called when a peer disconnects to clean up the actor.

func UseLogger

func UseLogger(logger btclog.Logger)

UseLogger uses a specified Logger to output package logging info. This should be used in preference to SetLogWriter if the caller is also using btclog.

Types

type GraphNodeResolver

type GraphNodeResolver struct {
	// contains filtered or unexported fields
}

GraphNodeResolver resolves node public keys from short channel IDs using the channel graph. It maintains an LRU cache to avoid repeated database lookups for frequently used SCIDs.

func NewGraphNodeResolver

func NewGraphNodeResolver(graph *graphdb.ChannelGraph,
	ourPub *btcec.PublicKey) *GraphNodeResolver

NewGraphNodeResolver creates a new GraphNodeResolver with the given channel graph and our node's public key. It initializes an LRU cache for SCID lookups.

func (*GraphNodeResolver) RemotePubFromSCID

func (r *GraphNodeResolver) RemotePubFromSCID(ctx context.Context,
	scid lnwire.ShortChannelID) (*btcec.PublicKey, error)

RemotePubFromSCID resolves a node public key from a short channel ID.

type IngressLimiter

type IngressLimiter interface {
	// AllowN reports whether an onion message of n bytes from the
	// given peer is permitted. A successful result wraps fn.Unit; a
	// rejection wraps either ErrPeerRateLimit or ErrGlobalRateLimit
	// depending on which bucket fired. Callers use errors.Is against
	// those sentinels to pick their log / metric / drop path.
	//
	// Per-peer state is retained for the lifetime of the process so
	// that a peer cannot reset its bucket by cycling the connection;
	// see the PeerRateLimiter doc for the memory-bound argument.
	AllowN(peer [33]byte, n int) fn.Result[fn.Unit]

	// FirstPeerDropClaim atomically returns true exactly once, on
	// the first call, and is intended to gate a one-shot info log
	// when the per-peer limiter first trips.
	FirstPeerDropClaim() bool

	// FirstGlobalDropClaim atomically returns true exactly once, on
	// the first call, and is intended to gate a one-shot info log
	// when the global limiter first trips.
	FirstGlobalDropClaim() bool
}

IngressLimiter is the combined per-peer + global rate limiter surface consumed by the onion message ingress path. It hides the split between the two underlying buckets so callers in peer/brontide.go only need to thread a single object through Config and call a single method on every incoming onion message. The per-peer bucket is always checked first so that a hostile peer whose own budget is already empty cannot burn global tokens on every rejected attempt and starve legitimate peers.

Implementations must be safe for concurrent use from per-peer readHandler goroutines. A nil IngressLimiter is a valid "disabled" sentinel at call sites and means "accept everything".

func NewIngressLimiter

func NewIngressLimiter(peer *PeerRateLimiter,
	global RateLimiter) IngressLimiter

NewIngressLimiter constructs an IngressLimiter that first consults the given per-peer limiter and then the given global limiter for each incoming onion message. Either argument may be nil (or the zero-value disabled limiter returned by the constructors in this package) in which case that side of the check is skipped.

type NodeIDResolver

type NodeIDResolver interface {
	RemotePubFromSCID(ctx context.Context,
		scid lnwire.ShortChannelID) (*btcec.PublicKey, error)
}

NodeIDResolver defines an interface to resolve a node public key from a short channel ID.

type OnionActorFactory

type OnionActorFactory func(system *actor.ActorSystem,
	peerPubKey [33]byte,
	opts ...actor.ActorOption[*Request, *Response]) (OnionPeerActorRef,
	error)

OnionActorFactory is a function that spawns a new OnionPeerActor for a given peer within the actor system. The factory captures shared dependencies (router, resolver, sender, dispatcher) and only requires per-peer parameters at spawn time. Callers may pass ActorOptions to customise the mailbox (size, drop predicate, etc.) on a per-peer basis.

func NewOnionActorFactory

func NewOnionActorFactory(router OnionRouter, resolver NodeIDResolver,
	peerSender PeerMessageSender,
	dispatcher OnionMessageUpdateDispatcher) OnionActorFactory

NewOnionActorFactory creates a factory function that spawns OnionPeerActors with shared dependencies. The returned factory captures the router, resolver, peer sender, and update dispatcher, requiring only the actor system, peer public key, and optional per-peer ActorOptions at spawn time.

Callers supply ActorOptions (mailbox factory, size overrides, etc.) via the opts variadic so that backpressure policy can be customised per peer.

type OnionMessageUpdate

type OnionMessageUpdate struct {
	// Peer is the peer pubkey
	Peer [33]byte

	// PathKey is the route blinding ephemeral pubkey to be used for
	// the onion message.
	PathKey [33]byte

	// OnionBlob is the raw serialized mix header used to relay messages in
	// a privacy-preserving manner. This blob should be handled in the same
	// manner as onions used to route HTLCs, with the exception that it uses
	// blinded routes by default.
	OnionBlob []byte

	// CustomRecords contains any custom TLV records included in the
	// payload.
	CustomRecords record.CustomSet

	// ReplyPath contains the reply path information for the onion message.
	ReplyPath *sphinx.BlindedPath

	// EncryptedRecipientData contains the encrypted recipient data for the
	// onion message, created by the creator of the blinded route. This is
	// the receiver for the last leg of the route, and the sender for the
	// first leg up to the introduction point.
	EncryptedRecipientData []byte
}

OnionMessageUpdate is onion message update dispatched to any potential subscriber.

type OnionMessageUpdateDispatcher

type OnionMessageUpdateDispatcher interface {
	// SendUpdate sends an onion message update to all subscribers.
	SendUpdate(update any) error
}

OnionMessageUpdateDispatcher dispatches onion message updates to subscribers.

type OnionPeerActor

type OnionPeerActor struct {
	// contains filtered or unexported fields
}

OnionPeerActor handles the full onion message processing pipeline for a specific peer connection. It decodes incoming onion messages, determines the routing action (forward or deliver), executes the action, and dispatches updates to subscribers.

func (*OnionPeerActor) Receive

func (a *OnionPeerActor) Receive(ctx context.Context,
	req *Request) fn.Result[*Response]

Receive processes an incoming onion message from the peer. It decodes the onion packet, determines whether to forward or deliver the message, executes the routing action, and dispatches an update to subscribers.

This method implements the actor.ActorBehavior interface.

type OnionPeerActorRef

type OnionPeerActorRef actor.ActorRef[*Request, *Response]

OnionPeerActorRef is a reference to an OnionPeerActor.

type OnionRouter

type OnionRouter interface {
	// ProcessOnionPacket processes an onion packet and returns the
	// processed result.
	ProcessOnionPacket(pkt *sphinx.OnionPacket, assocData []byte,
		incomingCltv uint32,
		opts ...sphinx.ProcessOnionOpt) (*sphinx.ProcessedPacket, error)

	// DecryptBlindedHopData decrypts the encrypted hop data using the
	// given path key.
	DecryptBlindedHopData(pathKey *btcec.PublicKey,
		encData []byte) ([]byte, error)

	// NextEphemeral derives the next ephemeral key from the current path
	// key.
	NextEphemeral(
		currentPathKey *btcec.PublicKey) (*btcec.PublicKey, error)
}

OnionRouter wraps the sphinx router operations needed for onion message processing.

type PeeledHop

type PeeledHop struct {
	EncryptedData []byte
	Payload       *lnwire.OnionMessagePayload
	IsFinal       bool
}

PeeledHop captures decrypted state for a single hop when peeling an onion.

func PeelOnionLayers

func PeelOnionLayers(t *testing.T, privKeys []*btcec.PrivateKey,
	msg *lnwire.OnionMessage) []PeeledHop

PeelOnionLayers sequentially processes an onion message, creating a fresh router for each hop using the provided private keys (one per hop), returning the encrypted data and decoded payload for each hop until the final hop.

type PeerMessageSender

type PeerMessageSender interface {
	// SendToPeer sends an onion message to the peer identified by the
	// given compressed public key.
	SendToPeer(pubKey [33]byte, msg *lnwire.OnionMessage) error
}

PeerMessageSender sends onion messages to peers identified by public key.

type PeerRateLimiter

type PeerRateLimiter struct {
	// contains filtered or unexported fields
}

PeerRateLimiter is a registry of per-peer onion message token buckets, keyed by the peer's compressed public key. Tokens are bytes: callers pass the on-the-wire size of each message to AllowN and the per-peer bucket is debited accordingly. Buckets are created lazily on the first call to AllowN for a given peer and retained for the lifetime of the process. When the configured rate or burst is zero the registry operates in disabled mode and AllowN is a no-op.

Retention across disconnect is load-bearing. Without it, a peer could drain its burst, disconnect, reconnect, and get a fresh full-burst bucket on every cycle, effectively promoting the global limiter into its per-peer rate and using the shared budget as a personal allowance until the global bucket trips. By keeping the bucket, a drained peer stays drained until its bucket naturally refills regardless of how often it cycles the connection, and the per-peer rate becomes a real ceiling rather than a per-connection ceiling.

The memory cost of retention is bounded by the number of channel peers that have ever sent an onion message: the ingress call site gates AllowN on the peer having at least one open channel before touching this registry, so random connecting strangers never allocate a bucket. At a realistic few hundred to few thousand channel partners and ~200 bytes per entry (rate.Limiter plus SyncMap overhead), the registry stays comfortably sub-megabyte for the lifetime of the process.

The underlying bucket registry is an lnutils.SyncMap rather than a plain map guarded by a mutex. Per-peer keys are stable for the lifetime of the connection and the common path is a Load hit, which sync.Map serves without any write contention across peers. A plain map would serialize every hot-path AllowN call behind a single mutex even though rate.Limiter is already safe for concurrent use.

func NewPeerRateLimiter

func NewPeerRateLimiter(kbps uint64, burstBytes uint64) *PeerRateLimiter

NewPeerRateLimiter constructs a per-peer onion message rate limiter. kbps is the per-peer sustained rate in kilobits per second and burstBytes is the per-peer token bucket depth in bytes. A zero rate or a zero burst disables limiting; in that case AllowN always returns true and no per-peer state is retained.

func (*PeerRateLimiter) AllowN

func (p *PeerRateLimiter) AllowN(peer [33]byte, n int) bool

AllowN reports whether an onion message of n bytes from the given peer is permitted at the current instant. The peer's bucket is created on first use. Rejected calls are counted and visible via Dropped.

func (*PeerRateLimiter) Dropped

func (p *PeerRateLimiter) Dropped() uint64

Dropped returns the total number of onion messages this registry has rejected since process start, summed across all peers.

func (*PeerRateLimiter) FirstDropClaim

func (p *PeerRateLimiter) FirstDropClaim() bool

FirstDropClaim atomically returns true exactly once, on the first call. The caller is responsible for only invoking it after a rejection has actually occurred. The ingress site uses this to emit a single info-level log line when per-peer rate limiting first trips, rather than spamming the log on every drop.

type RateLimiter

type RateLimiter interface {
	// AllowN reports whether an onion message of n bytes is permitted
	// to proceed at the current instant. It must be non-blocking and
	// safe for concurrent use.
	AllowN(n int) bool
}

RateLimiter is the minimal token-bucket interface used at the onion message ingress path. Tokens are bytes: each call reports whether a message of size n bytes is permitted to proceed, and on success consumes n bytes from the underlying bucket. The interface is satisfied by *rate.Limiter (via a small counting wrapper) and a noop implementation used when a limit is configured as zero (disabled). It exists so that callers and tests can substitute alternate implementations without taking a hard dependency on the x/time/rate package.

Implementations of AllowN must be safe for concurrent use by multiple goroutines; the ingress call site invokes it from per-peer readHandler goroutines without additional synchronization.

func NewGlobalLimiter

func NewGlobalLimiter(kbps uint64, burstBytes uint64) RateLimiter

NewGlobalLimiter constructs a process-wide onion message rate limiter. kbps is the sustained rate in kilobits per second (1 Kbps = 1000 bits/s); burstBytes is the token bucket depth in bytes. A zero rate or a zero burst disables limiting and returns a noopLimiter. Otherwise the returned RateLimiter is a token bucket whose tokens are bytes.

type Request

type Request struct {
	// Embed BaseMessage to satisfy the actor package Message interface.
	actor.BaseMessage
	// contains filtered or unexported fields
}

Request is a message sent to an OnionPeerActor when an onion message is received from the peer. The actor processes the message through the full onion message pipeline: decode, decrypt, route, and forward/deliver.

func NewRequest

func NewRequest(msg lnwire.OnionMessage) *Request

NewRequest creates a new Request from an onion message.

func (*Request) MessageType

func (m *Request) MessageType() string

MessageType returns a string identifier for the Request message type.

type Response

type Response struct {
	actor.BaseMessage
	Success bool
}

Response is the response message sent back from an OnionPeerActor after processing an incoming onion message.

func (*Response) MessageType

func (m *Response) MessageType() string

MessageType returns a string identifier for the Response message type.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL