mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-28 18:11:31 +02:00
79b39e8985
Foundation work for cross-node DM mailbox replication. Adds the network
rule that makes the replication safe to ship next, plus the primitives
the outbound replication PR will call.
The rule
--------
A single sender can have at most N UNACKED messages parked in a single
recipient's mailbox at any one time. Default N=2, tunable via
``MESH_DM_PENDING_PER_SENDER_LIMIT``. Once the recipient pulls (acks) a
message, the sender's quota for that (sender, recipient) pair frees up.
Network rule, not local rule
----------------------------
The cap is enforced TWICE:
1. ``DMRelay.deposit(...)`` — local check on the sender's own node.
Refuses to spool the (N+1)th message before it can be replicated.
2. ``DMRelay.accept_replica(...)`` — replication-acceptance check on
every receiving peer. Refuses to accept an inbound replica that
would put the local mailbox over the cap.
The second half is what makes the rule a NETWORK rule. A hostile sender
could patch out the deposit check on their own relay and continue to
spool extras locally — but those extras can never propagate, because
every honest peer enforces the same cap on the way in. A recipient who
polls from honest peers therefore never sees more than N pending from
any one sender, regardless of how many spam attempts the hostile
sender's relay accepted.
New API surface on ``DMRelay``
------------------------------
_per_sender_pending_limit() — reads MESH_DM_PENDING_PER_SENDER_LIMIT
_per_sender_pending_count(...) — counts unacked from a sender for a mailbox
accept_replica(envelope=...) — peer-push receive entry point
envelope_for_replication(...) — helper to extract a wire-form envelope
``accept_replica`` is idempotent on duplicate ``msg_id`` (replication
round-trips and multi-path delivery don't double-spool).
``envelope_for_replication`` exposes the exact shape ``accept_replica``
expects, so the follow-up PR (outbound replication wiring) just has to
fetch the envelope and POST it to authenticated peer URLs with the
existing per-peer HMAC pattern from #256.
Why this is PR-1 of two
-----------------------
The full cross-node mailbox replication needs three pieces:
A. cap enforcement on deposit (in this PR)
B. cap enforcement on replica acceptance (in this PR)
C. outbound: push envelope to MESH_RELAY_PEERS after deposit (NEXT PR)
(A) + (B) shipped together close the cap-bypass attack surface BEFORE
(C) introduces the actual cross-node propagation. Shipping them in the
other order would briefly let extras propagate during the window between
"outbound push lands" and "accept_replica cap lands."
Tests
-----
backend/tests/test_dm_relay_per_sender_cap.py — 14 tests:
TestDepositCap:
- first 2 deposits succeed (UX baseline)
- 3rd from same sender rejected with friendly message
- different senders have independent quotas
- different recipients have independent quotas
- ack frees the quota (after recipient pulls, sender can deposit again)
- cap is env-tunable
TestAcceptReplicaCap:
- replica accepted under cap
- idempotent on duplicate msg_id (no double-spool, no rejection)
- rejected at cap with structured ``cap_violation`` marker so
sender's relay can stop retrying
- per-sender, not per-mailbox: different sender_block_ref passes
even when another sender at the same mailbox is capped
- malformed envelope shapes rejected without crash
TestEnvelopeForReplication:
- returns the envelope for stored messages
- returns None for unknown msg_id
- round-trips through accept_replica end-to-end (proves the wire
shape matches across the two sides)
271 lines
9.5 KiB
Python
271 lines
9.5 KiB
Python
"""Per-(sender, recipient) anti-spam cap on the DM relay.
|
|
|
|
The user-stated rule: a single sender can have at most N UNACKED messages
|
|
parked in a single recipient's mailbox at any one time (N=2 by default).
|
|
Once the recipient pulls a message, the sender's quota for that pair
|
|
frees up.
|
|
|
|
Network rule, not local rule
|
|
-----------------------------
|
|
The cap is enforced TWICE:
|
|
|
|
1. ``DMRelay.deposit(...)`` -- local check on the sender's own node.
|
|
Refuses to spool the (N+1)th message before it can be replicated.
|
|
|
|
2. ``DMRelay.accept_replica(...)`` -- replication-acceptance check on
|
|
every receiving peer. Refuses to accept an inbound replica that
|
|
would put the local mailbox over the cap, even if the originating
|
|
peer claims it had cap room.
|
|
|
|
The double enforcement matters because cap (1) is client-side -- a
|
|
hostile relay could patch it out and continue to spool extras locally.
|
|
Cap (2) means those extras can't propagate: every honest peer rejects
|
|
them on the way in. A recipient who polls from honest peers therefore
|
|
never sees more than N pending from any one sender, regardless of how
|
|
many spam attempts the sender's own relay accepted.
|
|
|
|
These tests pin both halves of the rule.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def relay():
|
|
"""Fresh ``DMRelay`` per test."""
|
|
from services.mesh.mesh_dm_relay import DMRelay
|
|
r = DMRelay()
|
|
r._mailboxes.clear()
|
|
r._blocks.clear()
|
|
r._stats = {"messages_in_memory": 0}
|
|
return r
|
|
|
|
|
|
def _deposit(
|
|
relay,
|
|
*,
|
|
sender: str = "alice",
|
|
recipient_token: str = "bob_mailbox_token_abc",
|
|
ciphertext: str = "ciphertext-blob",
|
|
msg_id: str = "",
|
|
):
|
|
"""Convenience wrapper using ``shared`` delivery class."""
|
|
return relay.deposit(
|
|
sender_id=sender,
|
|
raw_sender_id=sender,
|
|
recipient_id="bob",
|
|
ciphertext=ciphertext,
|
|
msg_id=msg_id,
|
|
delivery_class="shared",
|
|
recipient_token=recipient_token,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Local cap on ``deposit``
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestDepositCap:
|
|
def test_two_deposits_from_same_sender_succeed(self, relay):
|
|
r1 = _deposit(relay)
|
|
r2 = _deposit(relay)
|
|
assert r1["ok"] is True
|
|
assert r2["ok"] is True
|
|
assert r1["msg_id"] != r2["msg_id"]
|
|
|
|
def test_third_deposit_from_same_sender_rejected(self, relay):
|
|
_deposit(relay)
|
|
_deposit(relay)
|
|
r3 = _deposit(relay)
|
|
assert r3["ok"] is False
|
|
detail = r3["detail"].lower()
|
|
assert "unread" in detail or "read your messages" in detail
|
|
|
|
def test_different_senders_have_independent_quotas(self, relay):
|
|
for _ in range(2):
|
|
assert _deposit(relay, sender="alice")["ok"] is True
|
|
for _ in range(2):
|
|
assert _deposit(relay, sender="carol")["ok"] is True
|
|
assert _deposit(relay, sender="carol")["ok"] is False
|
|
|
|
def test_different_recipients_have_independent_quotas(self, relay):
|
|
for _ in range(2):
|
|
assert _deposit(relay, sender="alice", recipient_token="bob_token")["ok"] is True
|
|
for _ in range(2):
|
|
assert _deposit(relay, sender="alice", recipient_token="dave_token")["ok"] is True
|
|
|
|
def test_ack_frees_quota(self, relay):
|
|
r1 = _deposit(relay)
|
|
_deposit(relay)
|
|
assert _deposit(relay)["ok"] is False
|
|
|
|
mailbox_key = relay._hashed_mailbox_token("bob_mailbox_token_abc")
|
|
relay._mailboxes[mailbox_key] = [
|
|
m for m in relay._mailboxes[mailbox_key]
|
|
if m.msg_id != r1["msg_id"]
|
|
]
|
|
relay._stats["messages_in_memory"] = sum(
|
|
len(v) for v in relay._mailboxes.values()
|
|
)
|
|
|
|
r3 = _deposit(relay)
|
|
assert r3["ok"] is True, f"expected quota free after ack, got: {r3}"
|
|
|
|
def test_cap_is_env_tunable(self, relay, monkeypatch):
|
|
import services.mesh.mesh_dm_relay as mdr
|
|
monkeypatch.setattr(
|
|
mdr.DMRelay,
|
|
"_per_sender_pending_limit",
|
|
lambda self: 1,
|
|
)
|
|
|
|
assert _deposit(relay)["ok"] is True
|
|
assert _deposit(relay)["ok"] is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Replication-acceptance cap (the half that makes this a network rule)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestAcceptReplicaCap:
|
|
def _envelope(self, *, msg_id: str, sender_block_ref: str, mailbox_key: str):
|
|
return {
|
|
"msg_id": msg_id,
|
|
"mailbox_key": mailbox_key,
|
|
"sender_block_ref": sender_block_ref,
|
|
"sender_id": "alice",
|
|
"sender_seal": "",
|
|
"ciphertext": f"ciphertext-{msg_id}",
|
|
"timestamp": time.time(),
|
|
"delivery_class": "shared",
|
|
"relay_salt": "",
|
|
"payload_format": "dm1",
|
|
"session_welcome": "",
|
|
}
|
|
|
|
def test_replica_accepted_under_cap(self, relay):
|
|
env = self._envelope(
|
|
msg_id="dm_replica_1",
|
|
sender_block_ref="alice_block_ref",
|
|
mailbox_key="mailbox_xyz",
|
|
)
|
|
result = relay.accept_replica(envelope=env)
|
|
assert result["ok"] is True
|
|
|
|
def test_replica_idempotent_on_duplicate_msg_id(self, relay):
|
|
mailbox_key = "mailbox_xyz"
|
|
env = self._envelope(
|
|
msg_id="dm_dup_1",
|
|
sender_block_ref="alice_block_ref",
|
|
mailbox_key=mailbox_key,
|
|
)
|
|
r1 = relay.accept_replica(envelope=env)
|
|
r2 = relay.accept_replica(envelope=env)
|
|
assert r1["ok"] is True
|
|
assert r2["ok"] is True
|
|
assert r2.get("duplicate") is True
|
|
assert len(relay._mailboxes[mailbox_key]) == 1
|
|
|
|
def test_replica_rejected_when_local_count_already_at_cap(self, relay):
|
|
mailbox_key = "mailbox_xyz"
|
|
for i in (1, 2):
|
|
relay.accept_replica(envelope=self._envelope(
|
|
msg_id=f"dm_seeded_{i}",
|
|
sender_block_ref="alice_block_ref",
|
|
mailbox_key=mailbox_key,
|
|
))
|
|
|
|
result = relay.accept_replica(envelope=self._envelope(
|
|
msg_id="dm_overcap_3",
|
|
sender_block_ref="alice_block_ref",
|
|
mailbox_key=mailbox_key,
|
|
))
|
|
assert result["ok"] is False
|
|
assert result.get("cap_violation") is True
|
|
assert result.get("pending") == 2
|
|
assert result.get("limit") == 2
|
|
assert len(relay._mailboxes[mailbox_key]) == 2
|
|
|
|
def test_replica_from_different_sender_passes_when_one_is_at_cap(self, relay):
|
|
mailbox_key = "mailbox_xyz"
|
|
for i in (1, 2):
|
|
relay.accept_replica(envelope=self._envelope(
|
|
msg_id=f"dm_alice_{i}",
|
|
sender_block_ref="alice_block_ref",
|
|
mailbox_key=mailbox_key,
|
|
))
|
|
assert relay.accept_replica(envelope=self._envelope(
|
|
msg_id="dm_alice_3",
|
|
sender_block_ref="alice_block_ref",
|
|
mailbox_key=mailbox_key,
|
|
))["ok"] is False
|
|
assert relay.accept_replica(envelope=self._envelope(
|
|
msg_id="dm_carol_1",
|
|
sender_block_ref="carol_block_ref",
|
|
mailbox_key=mailbox_key,
|
|
))["ok"] is True
|
|
|
|
def test_replica_rejects_malformed_envelopes(self, relay):
|
|
for bad in (
|
|
{},
|
|
{"msg_id": "x"},
|
|
{"msg_id": "x", "mailbox_key": "y"},
|
|
"not an object at all",
|
|
):
|
|
result = relay.accept_replica(envelope=bad)
|
|
assert result["ok"] is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ``envelope_for_replication`` -- helper for the outbound replication path
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestEnvelopeForReplication:
|
|
def test_returns_envelope_for_stored_message(self, relay):
|
|
r = _deposit(relay, ciphertext="hello-ciphertext")
|
|
msg_id = r["msg_id"]
|
|
mailbox_key = relay._hashed_mailbox_token("bob_mailbox_token_abc")
|
|
|
|
env = relay.envelope_for_replication(mailbox_key=mailbox_key, msg_id=msg_id)
|
|
assert env is not None
|
|
assert env["msg_id"] == msg_id
|
|
assert env["mailbox_key"] == mailbox_key
|
|
assert env["ciphertext"] == "hello-ciphertext"
|
|
assert env["delivery_class"] == "shared"
|
|
for k in ("msg_id", "mailbox_key", "sender_block_ref", "ciphertext"):
|
|
assert env.get(k), f"envelope missing required field {k!r}"
|
|
|
|
def test_returns_none_for_unknown_message(self, relay):
|
|
env = relay.envelope_for_replication(
|
|
mailbox_key="never_existed", msg_id="never_existed",
|
|
)
|
|
assert env is None
|
|
|
|
def test_envelope_round_trips_through_accept_replica(self, relay):
|
|
from services.mesh.mesh_dm_relay import DMRelay
|
|
receiver_relay = DMRelay()
|
|
receiver_relay._mailboxes.clear()
|
|
receiver_relay._stats = {"messages_in_memory": 0}
|
|
|
|
r = _deposit(relay)
|
|
msg_id = r["msg_id"]
|
|
mailbox_key = relay._hashed_mailbox_token("bob_mailbox_token_abc")
|
|
env = relay.envelope_for_replication(
|
|
mailbox_key=mailbox_key, msg_id=msg_id,
|
|
)
|
|
assert env is not None
|
|
|
|
result = receiver_relay.accept_replica(envelope=env)
|
|
assert result["ok"] is True
|
|
stored = receiver_relay._mailboxes.get(mailbox_key, [])
|
|
assert len(stored) == 1
|
|
assert stored[0].msg_id == msg_id
|
|
assert stored[0].ciphertext == "ciphertext-blob"
|