mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
This commit plumbs the combined IngressLimiter (per-peer + global) through peer.Config and consults it from the readHandler's *lnwire.OnionMessage case. The decision is factored into a small allowOnionMessage helper so that the ingress policy is directly unit-testable without standing up a full Brontide harness. Per-peer is checked first inside the IngressLimiter: if we consulted the global limiter first, a peer whose own bucket was already empty would still get to burn a global token on each attempt, letting a single hostile peer drain the shared budget and starve legitimate peers. peer.Config carries a single OnionLimiter field of IngressLimiter type; the brontide readHandler calls a single AllowN per incoming onion message and dispatches on sentinel errors via errors.Is for the first-drop log path. Nil limiter values are treated as "disabled" throughout, which both preserves the pre-change behavior when onion messaging is entirely turned off and keeps the brontide test harness from needing to construct real limiters. Per-peer bucket state is retained across disconnect at the IngressLimiter layer so a peer cannot cycle the connection to reset its per-peer allowance. OnionMessage also gains a WireSize method that computes the on-the-wire size directly from the in-memory fields (no round-trip through Encode) so the hot ingress path can charge the right number of byte tokens without paying for a full serialization. The accompanying unit tests cover the nil/disabled path, the per-peer-rejects-first ordering invariant (asserting the global limiter is not consulted when the per-peer bucket is empty), the global rejection path, per-peer isolation across distinct pubkeys, and a small concurrent stress test that asserts every attempt is accounted for as either accepted or dropped and that the total accepted count equals the configured burst under -race. A property-based rapid test on WireSize guards against silent divergence from WriteMessage if the OnionMessage wire format ever gains a TLV extension.
107 lines
2.9 KiB
Go
107 lines
2.9 KiB
Go
package lnwire
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
)
|
|
|
|
// OnionMessage is a message that carries an onion-encrypted payload.
|
|
// This is used for BOLT12 messages.
|
|
type OnionMessage struct {
|
|
// PathKey is the route blinding ephemeral pubkey to be used for
|
|
// the onion message.
|
|
PathKey *btcec.PublicKey
|
|
|
|
// OnionBlob contains the onion_message_packet, the raw serialized
|
|
// Sphinx onion packet (BOLT 4) containing the layered, per-hop
|
|
// encrypted payloads and routing instructions used to forward this
|
|
// message along its designated path. This blob should be handled in the
|
|
// same manner as onion_routing_packet used to route HTLCs, with the
|
|
// exception that it uses blinded routes by default.
|
|
OnionBlob []byte
|
|
}
|
|
|
|
// NewOnionMessage creates a new OnionMessage.
|
|
func NewOnionMessage(pathKey *btcec.PublicKey,
|
|
onion []byte) *OnionMessage {
|
|
|
|
return &OnionMessage{
|
|
PathKey: pathKey,
|
|
OnionBlob: onion,
|
|
}
|
|
}
|
|
|
|
// A compile-time check to ensure OnionMessage implements the Message interface.
|
|
var _ Message = (*OnionMessage)(nil)
|
|
|
|
var _ SizeableMessage = (*OnionMessage)(nil)
|
|
|
|
// Decode reads the bytes stream and converts it to the object.
|
|
func (o *OnionMessage) Decode(r io.Reader, _ uint32) error {
|
|
if err := ReadElement(r, &o.PathKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
var onionLen uint16
|
|
if err := ReadElement(r, &onionLen); err != nil {
|
|
return err
|
|
}
|
|
|
|
o.OnionBlob = make([]byte, onionLen)
|
|
if err := ReadElement(r, o.OnionBlob); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Encode converts object to the bytes stream and write it into the
|
|
// write buffer.
|
|
func (o *OnionMessage) Encode(w *bytes.Buffer, _ uint32) error {
|
|
if err := WritePublicKey(w, o.PathKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
onionLen := len(o.OnionBlob)
|
|
if err := WriteUint16(w, uint16(onionLen)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := WriteBytes(w, o.OnionBlob); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MsgType returns the integer uniquely identifying this message type on the
|
|
// wire.
|
|
func (o *OnionMessage) MsgType() MessageType {
|
|
return MsgOnionMessage
|
|
}
|
|
|
|
// WireSize returns the on-the-wire size of the message in bytes, including
|
|
// the 2-byte message type prefix, the 33-byte compressed path key, the
|
|
// 2-byte onion blob length prefix, and the onion blob itself. It computes
|
|
// the size directly from the in-memory fields rather than round-tripping
|
|
// through Encode, so callers in the hot ingress path — notably the onion
|
|
// message rate limiter — can charge the right number of byte tokens
|
|
// without paying for a full serialization.
|
|
func (o *OnionMessage) WireSize() int {
|
|
const (
|
|
msgTypeBytes = 2
|
|
pathKeyBytes = 33
|
|
onionLenBytes = 2
|
|
)
|
|
|
|
return msgTypeBytes + pathKeyBytes + onionLenBytes + len(o.OnionBlob)
|
|
}
|
|
|
|
// SerializedSize returns the serialized size of the message in bytes.
|
|
//
|
|
// This is part of the lnwire.SizeableMessage interface.
|
|
func (o *OnionMessage) SerializedSize() (uint32, error) {
|
|
return uint32(o.WireSize()), nil
|
|
}
|