lnd/lnwire/onion_message_test.go
Olaoluwa Osuntokun 9cad57bfce peer: enforce onion message rate limits at ingress
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.
2026-04-15 13:23:50 -07:00

39 lines
1.2 KiB
Go

package lnwire
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
)
// TestOnionMessageWireSizeMatchesEncode verifies that the value produced by
// OnionMessage.WireSize — the value fed to the onion message rate limiter on
// every incoming packet — matches the number of bytes WriteMessage actually
// emits for that same message. WireSize computes its result directly from the
// in-memory fields without round-tripping through Encode, which is fast but
// creates a risk of silent divergence if the OnionMessage wire format ever
// gains an optional TLV extension or extra field. This test is the
// compile-time-cheap regression guard that divergence does not go undetected.
func TestOnionMessageWireSizeMatchesEncode(t *testing.T) {
t.Parallel()
rapid.Check(t, func(rt *rapid.T) {
msg, ok := (*OnionMessage)(nil).RandTestMessage(
rt,
).(*OnionMessage)
require.True(
rt, ok, "RandTestMessage did "+
"not return an OnionMessage",
)
var buf bytes.Buffer
written, err := WriteMessage(&buf, msg, 0)
require.NoError(rt, err, "WriteMessage error")
require.Equal(rt, written, msg.WireSize(),
"WireSize=%d, WriteMessage wrote=%d bytes",
msg.WireSize(), written,
)
})
}