Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rate limit peers in request response p2p interactions #8498

Merged
merged 53 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b51e908
feat: cleanup publisher
LHerskind Aug 22, 2024
da2eb7a
refactor: get rid of timetraveler from l1-publisher
LHerskind Aug 28, 2024
3388966
feat: revert if timestamp in future
LHerskind Aug 28, 2024
13a60a3
feat: including txhashes explicitly in the rollup attestations
Maddiaa0 Aug 29, 2024
86026f2
temp
Maddiaa0 Aug 30, 2024
f3eac5b
Merge branch 'master' into md/check-tx-requests-before-signing
Maddiaa0 Aug 30, 2024
fc7a04a
temp
Maddiaa0 Aug 30, 2024
9eed298
temp
Maddiaa0 Sep 2, 2024
cc09455
temp
Maddiaa0 Sep 2, 2024
06f950f
Merge branch 'master' into md/check-tx-requests-before-signing
Maddiaa0 Sep 2, 2024
4727cd9
temp: get passing with txhash payloads
Maddiaa0 Sep 3, 2024
b4c2a46
fix: make sure transactions are available in the tx pool
Maddiaa0 Sep 5, 2024
4a8d178
chore: remove logs
Maddiaa0 Sep 5, 2024
b4324fc
fmt
Maddiaa0 Sep 5, 2024
052641a
Merge branch 'master' into md/check-tx-requests-before-signing
Maddiaa0 Sep 5, 2024
a803a94
🪿
Maddiaa0 Sep 5, 2024
164c117
chore: validator tests
Maddiaa0 Sep 6, 2024
27da59d
Add timeouts to individual reqresp connections
Maddiaa0 Sep 6, 2024
9e7d2d8
fix
Maddiaa0 Sep 6, 2024
3c8e1b9
chore: include tests for specific error messages
Maddiaa0 Sep 6, 2024
9a738e5
fmt
Maddiaa0 Sep 6, 2024
c10260c
Merge branch 'master' into md/check-tx-requests-before-signing
Maddiaa0 Sep 6, 2024
045af5a
clean
Maddiaa0 Sep 6, 2024
73d26ec
🧹
Maddiaa0 Sep 6, 2024
d358228
chore: fix sequencing tests
Maddiaa0 Sep 7, 2024
4b31953
Merge branch 'master' into md/check-tx-requests-before-signing
Maddiaa0 Sep 7, 2024
2f82a8f
Merge branch 'md/check-tx-requests-before-signing' into md/09-06-add_…
Maddiaa0 Sep 7, 2024
f673593
fmt
Maddiaa0 Sep 7, 2024
a15ab17
fmt
Maddiaa0 Sep 7, 2024
2e3f80b
fmt solidity
Maddiaa0 Sep 7, 2024
ae2a05e
Merge branch 'md/check-tx-requests-before-signing' into md/09-06-add_…
Maddiaa0 Sep 7, 2024
5734006
Merge branch 'master' into md/check-tx-requests-before-signing
Maddiaa0 Sep 7, 2024
1bde1fe
fix: test hash
Maddiaa0 Sep 8, 2024
c775b26
Merge branch 'md/check-tx-requests-before-signing' into md/09-06-add_…
Maddiaa0 Sep 8, 2024
7a50a2b
exp: adjust test nodes
Maddiaa0 Sep 8, 2024
72f98bd
Merge branch 'md/check-tx-requests-before-signing' into md/09-06-add_…
Maddiaa0 Sep 8, 2024
998f38c
chore: add reqresp configuration values to p2p config
Maddiaa0 Sep 8, 2024
1c2b151
fix: use abi.encode vs encodePacked
Maddiaa0 Sep 11, 2024
e6e7f6b
fix
Maddiaa0 Sep 11, 2024
6f417fc
Merge branch 'master' into md/check-tx-requests-before-signing
Maddiaa0 Sep 11, 2024
cde6283
fix: merge fix
Maddiaa0 Sep 11, 2024
8290c99
fmt
Maddiaa0 Sep 11, 2024
b13ca93
Merge branch 'md/check-tx-requests-before-signing' into md/09-06-add_…
Maddiaa0 Sep 11, 2024
1452017
Merge branch 'master' into md/check-tx-requests-before-signing
Maddiaa0 Sep 11, 2024
120c9a3
Merge branch 'md/check-tx-requests-before-signing' into md/09-06-add_…
Maddiaa0 Sep 11, 2024
b189270
feat: initial rate limiter impl
Maddiaa0 Sep 11, 2024
9b90fe1
Merge branch 'master' into md/09-06-add_timeouts_to_individual_reqres…
Maddiaa0 Sep 11, 2024
b7d815f
Merge branch 'md/09-06-add_timeouts_to_individual_reqresp_connections…
Maddiaa0 Sep 11, 2024
97a6a14
feat: tests + documentation
Maddiaa0 Sep 11, 2024
0e22558
test: add to reqresp.test.ts
Maddiaa0 Sep 11, 2024
b464dcf
Merge branch 'master' into md/add-rate-limits-to-reqresp-peers
Maddiaa0 Sep 11, 2024
d0da214
fix: add todo pr number
Maddiaa0 Sep 11, 2024
ce0bd17
fix: incorrect implementation
Maddiaa0 Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions yarn-project/p2p/src/service/reqresp/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,36 @@ export type ReqRespSubProtocol = typeof PING_PROTOCOL | typeof STATUS_PROTOCOL |
*/
export type ReqRespSubProtocolHandler = (msg: Buffer) => Promise<Uint8Array>;

/**
* A type mapping from supprotocol to it's rate limits
*/
export type ReqRespSubProtocolRateLimits = Record<ReqRespSubProtocol, ProtocolRateLimitQuota>;

/**
* A rate limit quota
*/
export interface RateLimitQuota {
/**
* The time window in ms
*/
quotaTimeMs: number;
/**
* The number of requests allowed within the time window
*/
quotaCount: number;
}

export interface ProtocolRateLimitQuota {
/**
* The rate limit quota for a single peer
*/
peerLimit: RateLimitQuota;
/**
* The rate limit quota for the global peer set
*/
globalLimit: RateLimitQuota;
}

/**
* A type mapping from supprotocol to it's handling funciton
*/
Expand Down
1 change: 1 addition & 0 deletions yarn-project/p2p/src/service/reqresp/rate_limiter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { RequestResponseRateLimiter } from './rate_limiter.js';
175 changes: 175 additions & 0 deletions yarn-project/p2p/src/service/reqresp/rate_limiter/rate_limiter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { jest } from '@jest/globals';
import { type PeerId } from '@libp2p/interface';

import { PING_PROTOCOL, type ReqRespSubProtocolRateLimits, TX_REQ_PROTOCOL } from '../interface.js';
import { RequestResponseRateLimiter } from './rate_limiter.js';

class MockPeerId {
private id: string;
constructor(id: string) {
this.id = id;
}
public toString(): string {
return this.id;
}
}

const makePeer = (id: string): PeerId => {
return new MockPeerId(id) as unknown as PeerId;
};

describe('rate limiter', () => {
let rateLimiter: RequestResponseRateLimiter;

beforeEach(() => {
jest.useFakeTimers();
const config = {
[TX_REQ_PROTOCOL]: {
// One request every 200ms
peerLimit: {
quotaCount: 5,
quotaTimeMs: 1000,
},
// One request every 100ms
globalLimit: {
quotaCount: 10,
quotaTimeMs: 1000,
},
},
} as ReqRespSubProtocolRateLimits; // force type as we will not provide descriptions of all protocols
rateLimiter = new RequestResponseRateLimiter(config);
});

afterEach(() => {
jest.useRealTimers();
rateLimiter.stop();
});

it('Should allow requests within a peer limit', () => {
const peerId = makePeer('peer1');
// Expect to allow a burst of 5, then not allow
for (let i = 0; i < 5; i++) {
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(true);
}
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(false);

// Smooth requests
for (let i = 0; i < 5; i++) {
jest.advanceTimersByTime(200);
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(true);
}
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(false);

// Reset after quota has passed
jest.advanceTimersByTime(1000);
// Second burst
for (let i = 0; i < 5; i++) {
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(true);
}
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(false);
});

it('Should allow requests within the global limit', () => {
// Initial burst
for (let i = 0; i < 10; i++) {
expect(rateLimiter.allow(TX_REQ_PROTOCOL, makePeer(`peer${i}`))).toBe(true);
}
expect(rateLimiter.allow(TX_REQ_PROTOCOL, makePeer('nolettoinno'))).toBe(false);

// Smooth requests
for (let i = 0; i < 10; i++) {
jest.advanceTimersByTime(100);
expect(rateLimiter.allow(TX_REQ_PROTOCOL, makePeer(`peer${i}`))).toBe(true);
}
expect(rateLimiter.allow(TX_REQ_PROTOCOL, makePeer('nolettoinno'))).toBe(false);

// Reset after quota has passed
jest.advanceTimersByTime(1000);
// Second burst
for (let i = 0; i < 10; i++) {
expect(rateLimiter.allow(TX_REQ_PROTOCOL, makePeer(`peer${i}`))).toBe(true);
}
expect(rateLimiter.allow(TX_REQ_PROTOCOL, makePeer('nolettoinno'))).toBe(false);
});

it('Should reset after quota has passed', () => {
const peerId = makePeer('peer1');
for (let i = 0; i < 5; i++) {
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(true);
}
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(false);
jest.advanceTimersByTime(1000);
expect(rateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(true);
});

it('Should handle multiple protocols separately', () => {
const config = {
[TX_REQ_PROTOCOL]: {
peerLimit: {
quotaCount: 5,
quotaTimeMs: 1000,
},
globalLimit: {
quotaCount: 10,
quotaTimeMs: 1000,
},
},
[PING_PROTOCOL]: {
peerLimit: {
quotaCount: 2,
quotaTimeMs: 1000,
},
globalLimit: {
quotaCount: 4,
quotaTimeMs: 1000,
},
},
} as ReqRespSubProtocolRateLimits;
const multiProtocolRateLimiter = new RequestResponseRateLimiter(config);

const peerId = makePeer('peer1');

// Protocol 1
for (let i = 0; i < 5; i++) {
expect(multiProtocolRateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(true);
}
expect(multiProtocolRateLimiter.allow(TX_REQ_PROTOCOL, peerId)).toBe(false);

// Protocol 2
for (let i = 0; i < 2; i++) {
expect(multiProtocolRateLimiter.allow(PING_PROTOCOL, peerId)).toBe(true);
}
expect(multiProtocolRateLimiter.allow(PING_PROTOCOL, peerId)).toBe(false);

multiProtocolRateLimiter.stop();
});

it('Should allow requests if no rate limiter is configured', () => {
const rateLimiter = new RequestResponseRateLimiter({} as ReqRespSubProtocolRateLimits);
expect(rateLimiter.allow(TX_REQ_PROTOCOL, makePeer('peer1'))).toBe(true);
});

it('Should smooth out spam', () => {
const requests = 1000;
const peers = 100;
let allowedRequests = 0;

for (let i = 0; i < requests; i++) {
const peerId = makePeer(`peer${i % peers}`);
if (rateLimiter.allow(TX_REQ_PROTOCOL, peerId)) {
allowedRequests++;
}
jest.advanceTimersByTime(5);
}
// With 1000 iterations of 5ms per iteration, we run over 5 seconds
// With the configuration of 5 requests per second per peer and 10 requests per second globally, we expect:
// most of the allowed request to come through the global limit
// This sets a floor of 50 requests per second, but allowing for the initial burst, we expect there to be upto an additional 10 requests

// (upon running a few times we actually see 59)
const expectedRequestsFloor = 50;
const expectedRequestsCeiling = 60;
expect(allowedRequests).toBeGreaterThan(expectedRequestsFloor);
expect(allowedRequests).toBeLessThan(expectedRequestsCeiling);
});
});
Loading
Loading