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

Minimal rlpx test suite #3126

Merged
merged 13 commits into from
Nov 1, 2023
9 changes: 5 additions & 4 deletions .github/workflows/devp2p-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ jobs:
- run: npm ci --omit=peer
working-directory: ${{github.workspace}}

- uses: nick-fields/retry@v2
- run: npm run coverage

- uses: codecov/codecov-action@v3
with:
timeout_minutes: 10
max_attempts: 3
command: cd ${{github.workspace}}/packages/devp2p && npm run coverage
files: ${{ env.cwd }}/coverage/lcov.info
flags: devp2p

- run: npm run lint
2 changes: 1 addition & 1 deletion .github/workflows/genesis-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ jobs:
working-directory: ${{github.workspace}}

- run: npm run lint
- run: npm run test:node # Only run node tests for now until vitest browser test issues are sorted out
- run: npm run test:node # Only run node tests for now until vitest browser test issues are sorted out
6 changes: 1 addition & 5 deletions .github/workflows/rlp-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,4 @@ jobs:
working-directory: ${{github.workspace}}

- run: npm run lint
- run: npm run coverage
- uses: codecov/codecov-action@v3
with:
files: ${{ env.cwd }}/coverage/lcov.info
flags: rlp

6 changes: 2 additions & 4 deletions packages/devp2p/src/rlpx/rlpx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,11 @@ export class RLPx {
peer.events.once('connect', () => {
let msg = `handshake with ${socket.remoteAddress}:${socket.remotePort} was successful`

// @ts-ignore
if (peer._eciesSession._gotEIP8Auth === true) {
if (peer['_eciesSession']['_gotEIP8Auth'] === true) {
msg += ` (peer eip8 auth)`
}

// @ts-ignore
if (peer._eciesSession._gotEIP8Ack === true) {
if (peer['_eciesSession']['_gotEIP8Ack'] === true) {
msg += ` (peer eip8 ack)`
}
this._debug(msg)
Expand Down
176 changes: 176 additions & 0 deletions packages/devp2p/test/rlpx.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Tests written with help from CodiumAI

import { Common } from '@ethereumjs/common'
import { equalsBytes, randomBytes } from '@ethereumjs/util'
import assert from 'assert'
import { secp256k1 } from 'ethereum-cryptography/secp256k1.js'
import EventEmitter from 'events'
import { describe, expect, it, vi } from 'vitest'

import { RLPx, pk2id } from '../src/index.js'

import type { RLPxOptions } from '../src/index.js'

const privateKey = randomBytes(32)
describe('RLPx', () => {
it('should create an instance of RLPx with the given private key and options', () => {
const options: RLPxOptions = {
timeout: 10000,
maxPeers: 10,
clientId: new Uint8Array([6, 7, 8, 9, 10]),
capabilities: [],
common: new Common({ chain: 1 }),
}

const rlpx = new RLPx(privateKey, options)

expect(rlpx).toBeInstanceOf(RLPx)
expect(rlpx.events).toBeInstanceOf(EventEmitter)
expect(rlpx['_privateKey']).toEqual(privateKey)
expect(rlpx.id).toEqual(pk2id(secp256k1.getPublicKey(privateKey, false)))
expect(rlpx['_timeout']).toEqual(options.timeout)
expect(rlpx['_maxPeers']).toEqual(options.maxPeers)
expect(rlpx.clientId).toEqual(options.clientId)
expect(rlpx['_remoteClientIdFilter']).toBeUndefined()
expect(rlpx['_capabilities']).toEqual(options.capabilities)
assert.deepEqual(rlpx['_common'], options.common)
expect(rlpx['_listenPort']).toBeNull()
expect(rlpx['_dpt']).toBeNull()
expect(rlpx['_peersQueue']).toEqual([])
expect(rlpx['_peers']).toBeInstanceOf(Map)
expect(rlpx['_refillIntervalId']).toBeDefined()
expect(rlpx['_refillIntervalSelectionCounter']).toEqual(0)
})

it('should start listening for incoming connections', () => {
const options: RLPxOptions = {
timeout: 10000,
maxPeers: 10,
clientId: new Uint8Array([6, 7, 8, 9, 10]),
capabilities: [],
common: new Common({ chain: 1 }),
}

const rlpx = new RLPx(privateKey, options)
const mockServer = {
listen: vi.fn(),
}
rlpx['_server'] = mockServer as any

rlpx.listen(30303)

expect(mockServer.listen).toHaveBeenCalledWith(30303)
})

it('should connect to a peer', async () => {
vi.mock('net', () => {
const Socket = vi.fn().mockImplementation(() => {
return {
on: vi.fn(),
once: vi.fn(),
setTimeout: () => {},
connect: vi.fn().mockImplementation(() => {
return 'mocked resolve!'
}),
}
})
return {
createServer: () => {
return {
once: vi.fn(),
on: vi.fn(),
address: () => '0.0.0.0',
}
},
Socket,
}
})
vi.mock('../src/util.js', async () => {
const util: any = await vi.importActual('../src/util.js')
return {
...util,
createDeferred: vi.fn().mockImplementation(() => {
return {
promise: {
resolve: vi.fn().mockResolvedValue(() => 'mocked resolve!'),
reject: vi.fn(),
},
}
}),
}
})
const options: RLPxOptions = {
timeout: 10000,
maxPeers: 10,
clientId: new Uint8Array([6, 7, 8, 9, 10]),
capabilities: [],
common: new Common({ chain: 1 }),
}

const rlpx = new RLPx(privateKey, options)
const mockServer = {
listen: vi.fn(),
once: vi.fn(),
on: vi.fn(),
}
rlpx['_server'] = mockServer as any
const mockPeer = {
tcpPort: 30303,
address: '127.0.0.1',
id: new Uint8Array([11, 12, 13, 14, 15]),
}

const mockOnConnect = vi.spyOn(rlpx, '_onConnect').mockImplementation((_, peerId) => {
assert(equalsBytes(peerId as Uint8Array, mockPeer.id), 'received correct peerId')
})
await rlpx.connect(mockPeer)

expect(mockOnConnect).toHaveBeenCalled()
})

it('should throw an error if already connected to a peer', async () => {
const options: RLPxOptions = {
timeout: 10000,
maxPeers: 10,
clientId: new Uint8Array([6, 7, 8, 9, 10]),
capabilities: [],
common: new Common({ chain: 1 }),
}
const rlpx = new RLPx(privateKey, options)
const mockPeer = {
tcpPort: 30303,
address: '127.0.0.1',
id: new Uint8Array([11, 12, 13, 14, 15]),
}
rlpx['_peers'].set('0b0c0d0e0f', {} as any)

try {
await rlpx.connect(mockPeer)
assert.fail('should throw')
} catch (err: any) {
assert.equal(err.message, 'Already connected')
}
})

it('should return open slots and open queue slots', () => {
const options: RLPxOptions = {
timeout: 10000,
maxPeers: 10,
clientId: new Uint8Array([6, 7, 8, 9, 10]),
capabilities: [],
common: new Common({ chain: 1 }),
}
const rlpx = new RLPx(privateKey, options)

assert.equal(
rlpx['_getOpenSlots'](),
10,
'returns default number of open slots (i.e. `max_peers`) on startup'
)
assert.equal(
rlpx['_getOpenQueueSlots'](),
20,
'returns default number of open queue slots on startup'
)
})
})