mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:20:42 +00:00
fix(nostr): dedupe deterministic rejected events
This commit is contained in:
@@ -144,6 +144,59 @@ describe("startNostrBus inbound guards", () => {
|
||||
bus.close();
|
||||
});
|
||||
|
||||
it("dedupes replayed invalid-signature events before verify fans out again", async () => {
|
||||
mockState.verifyEvent.mockReturnValue(false);
|
||||
const onMessage = vi.fn(async () => {});
|
||||
const authorizeSender = vi.fn(async () => "allow" as const);
|
||||
const bus = await startNostrBus({
|
||||
privateKey: TEST_HEX_PRIVATE_KEY,
|
||||
onMessage,
|
||||
authorizeSender,
|
||||
onMetric: () => {},
|
||||
});
|
||||
|
||||
const invalidEvent = createEvent({ id: "invalid-replay" });
|
||||
|
||||
await emitEvent(invalidEvent);
|
||||
await emitEvent(invalidEvent);
|
||||
|
||||
expect(mockState.verifyEvent).toHaveBeenCalledTimes(1);
|
||||
expect(authorizeSender).not.toHaveBeenCalled();
|
||||
expect(mockState.decrypt).not.toHaveBeenCalled();
|
||||
expect(onMessage).not.toHaveBeenCalled();
|
||||
expect(bus.getMetrics().eventsRejected.invalidSignature).toBe(1);
|
||||
expect(bus.getMetrics().eventsDuplicate).toBe(1);
|
||||
|
||||
bus.close();
|
||||
});
|
||||
|
||||
it("dedupes replayed self-message events before other guards rerun", async () => {
|
||||
const onMessage = vi.fn(async () => {});
|
||||
const authorizeSender = vi.fn(async () => "allow" as const);
|
||||
const bus = await startNostrBus({
|
||||
privateKey: TEST_HEX_PRIVATE_KEY,
|
||||
onMessage,
|
||||
authorizeSender,
|
||||
onMetric: () => {},
|
||||
});
|
||||
|
||||
const selfEvent = createEvent({
|
||||
id: "self-replay",
|
||||
pubkey: BOT_PUBKEY,
|
||||
});
|
||||
|
||||
await emitEvent(selfEvent);
|
||||
await emitEvent(selfEvent);
|
||||
|
||||
expect(mockState.verifyEvent).not.toHaveBeenCalled();
|
||||
expect(authorizeSender).not.toHaveBeenCalled();
|
||||
expect(mockState.decrypt).not.toHaveBeenCalled();
|
||||
expect(onMessage).not.toHaveBeenCalled();
|
||||
expect(bus.getMetrics().eventsDuplicate).toBe(1);
|
||||
|
||||
bus.close();
|
||||
});
|
||||
|
||||
it("rate limits repeated events before decrypt", async () => {
|
||||
const onMessage = vi.fn(async () => {});
|
||||
const bus = await startNostrBus({
|
||||
|
||||
@@ -505,15 +505,24 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
|
||||
}
|
||||
inflight.add(event.id);
|
||||
|
||||
const markSeen = () => {
|
||||
seen.add(event.id);
|
||||
metrics.emit("memory.seen_tracker_size", seen.size());
|
||||
};
|
||||
const rejectAndMarkSeen = (metric: Parameters<typeof metrics.emit>[0]) => {
|
||||
markSeen();
|
||||
metrics.emit(metric);
|
||||
};
|
||||
|
||||
// Self-message loop prevention: skip our own messages
|
||||
if (event.pubkey === pk) {
|
||||
metrics.emit("event.rejected.self_message");
|
||||
rejectAndMarkSeen("event.rejected.self_message");
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip events older than our `since` (relay may ignore filter)
|
||||
if (event.created_at < since) {
|
||||
metrics.emit("event.rejected.stale");
|
||||
rejectAndMarkSeen("event.rejected.stale");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -523,7 +532,7 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
|
||||
}
|
||||
|
||||
if (!guardPolicy.allowedKinds.includes(event.kind)) {
|
||||
metrics.emit("event.rejected.wrong_kind");
|
||||
rejectAndMarkSeen("event.rejected.wrong_kind");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -536,7 +545,7 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
|
||||
}
|
||||
}
|
||||
if (!targetsUs) {
|
||||
metrics.emit("event.rejected.wrong_kind");
|
||||
rejectAndMarkSeen("event.rejected.wrong_kind");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -578,16 +587,11 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
|
||||
return false;
|
||||
};
|
||||
|
||||
const markSeen = () => {
|
||||
seen.add(event.id);
|
||||
metrics.emit("memory.seen_tracker_size", seen.size());
|
||||
};
|
||||
|
||||
if (Buffer.byteLength(event.content, "utf8") > guardPolicy.maxCiphertextBytes) {
|
||||
if (rejectIfGlobalRateLimited()) {
|
||||
return;
|
||||
}
|
||||
metrics.emit("event.rejected.oversized_ciphertext");
|
||||
rejectAndMarkSeen("event.rejected.oversized_ciphertext");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,7 +601,7 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
|
||||
|
||||
// Verify signature (must pass before we trust the event)
|
||||
if (!verifyEvent(event)) {
|
||||
metrics.emit("event.rejected.invalid_signature");
|
||||
rejectAndMarkSeen("event.rejected.invalid_signature");
|
||||
onError?.(new Error("Invalid signature"), `event ${event.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user