Skip to content
This repository has been archived by the owner on Dec 10, 2020. It is now read-only.

Implement Eth64 support #82

Merged
merged 5 commits into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
},
rules: {
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
'no-redeclare': 'off'
}
}
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,22 @@ Connect to a peer, organize the communication, see [./src/rlpx/](./src/rlpx/)

### Usage

Create your `RLPx` object, e.g.:
Instantiate an [@ethereumjs/common](https://github.com/ethereumjs/ethereumjs-vm/tree/master/packages/common)
instance with the network you want to connect to:

```typescript
const common = new Common({ chain: 'mainnet' })
```

Create your `RLPx` object, e.g.:

```typescript
const rlpx = new devp2p.RLPx(PRIVATE_KEY, {
dpt: dpt,
maxPeers: 25,
capabilities: [
devp2p.ETH.eth63,
devp2p.ETH.eth62
],
listenPort: null
capabilities: [devp2p.ETH.eth63, devp2p.ETH.eth62],
common: common,
listenPort: null,
})
```

Expand Down Expand Up @@ -246,7 +251,7 @@ Normally not instantiated directly but created as a `SubProtocol` in the `Peer`

Send initial status message.

- `status` - Status message to send, format `{ networkId: CHAIN_ID, td: TOTAL_DIFFICULTY_BUFFER, bestHash: BEST_HASH_BUFFER, genesisHash: GENESIS_HASH_BUFFER }`.
- `status` - Status message to send, format `{td: TOTAL_DIFFICULTY_BUFFER, bestHash: BEST_HASH_BUFFER, genesisHash: GENESIS_HASH_BUFFER }`, `networkId` (respectively `chainId`) is taken from the `Common` instance

#### `eth.sendMessage(code, payload)`

Expand Down Expand Up @@ -315,7 +320,7 @@ Normally not instantiated directly but created as a `SubProtocol` in the `Peer`

Send initial status message.

- `status` - Status message to send, format `{ networkId: CHAIN_ID, headTd: TOTAL_DIFFICULTY_BUFFER, headHash: HEAD_HASH_BUFFER, headNum: HEAD_NUM_BUFFER, genesisHash: GENESIS_HASH_BUFFER }`.
- `status` - Status message to send, format `{ headTd: TOTAL_DIFFICULTY_BUFFER, headHash: HEAD_HASH_BUFFER, headNum: HEAD_NUM_BUFFER, genesisHash: GENESIS_HASH_BUFFER }`, `networkId` (respectively `chainId`) is taken from the `Common` instance

#### `les.sendMessage(code, reqId, payload)`

Expand Down
25 changes: 10 additions & 15 deletions examples/peer-communication-les.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,21 @@ import { randomBytes } from 'crypto'

const PRIVATE_KEY = randomBytes(32)

const CHAIN_ID = 4 // Rinkeby
const GENESIS_TD = 1
const GENESIS_HASH = Buffer.from(
'6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177',
'hex'
)

const config = new Common({ chain: 'mainnet' })
const bootstrapNodes = config.bootstrapNodes()
const BOOTNODES = bootstrapNodes
.filter((node: any) => {
return node.chainId === CHAIN_ID
})
.map((node: any) => {
return {
address: node.ip,
udpPort: node.port,
tcpPort: node.port
}
})
const common = new Common({ chain: 'rinkeby' })
const bootstrapNodes = common.bootstrapNodes()
const BOOTNODES = bootstrapNodes.map((node: any) => {
return {
address: node.ip,
udpPort: node.port,
tcpPort: node.port
}
})
const REMOTE_CLIENTID_FILTER = [
'go1.5',
'go1.6',
Expand Down Expand Up @@ -65,6 +60,7 @@ const rlpx = new devp2p.RLPx(PRIVATE_KEY, {
dpt: dpt,
maxPeers: 25,
capabilities: [devp2p.LES.les2],
common: common,
remoteClientIdFilter: REMOTE_CLIENTID_FILTER,
listenPort: null
})
Expand All @@ -84,7 +80,6 @@ rlpx.on('peer:added', peer => {
)

les.sendStatus({
networkId: CHAIN_ID,
headTd: devp2p.int2buffer(GENESIS_TD),
headHash: GENESIS_HASH,
headNum: Buffer.from([]),
Expand Down
9 changes: 4 additions & 5 deletions examples/peer-communication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import * as devp2p from '../src/index'
import { ETH, Peer } from '../src/index'

const PRIVATE_KEY = randomBytes(32)
const CHAIN_ID = 1

const config = new Common({ chain: 'mainnet' })
const bootstrapNodes = config.bootstrapNodes()
const common = new Common({ chain: 'mainnet' })
const bootstrapNodes = common.bootstrapNodes()
const BOOTNODES = bootstrapNodes.map((node: any) => {
return {
address: node.ip,
Expand Down Expand Up @@ -65,7 +64,8 @@ dpt.on('error', err => console.error(chalk.red(`DPT error: ${err}`)))
const rlpx = new devp2p.RLPx(PRIVATE_KEY, {
dpt: dpt,
maxPeers: 25,
capabilities: [devp2p.ETH.eth63, devp2p.ETH.eth62],
capabilities: [devp2p.ETH.eth64],
common: common,
remoteClientIdFilter: REMOTE_CLIENTID_FILTER,
listenPort: null
})
Expand All @@ -89,7 +89,6 @@ rlpx.on('peer:added', peer => {
)

eth.sendStatus({
networkId: CHAIN_ID,
td: devp2p.int2buffer(17179869184), // total difficulty in genesis block
bestHash: Buffer.from(
'd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@types/bl": "^2.1.0",
"@types/k-bucket": "^5.0.0",
"@types/lru-cache": "^5.1.0",
"@ethereumjs/common": "^2.0.0-beta.2",
"babel-runtime": "^6.11.6",
"bl": "^1.1.2",
"debug": "^2.2.0",
Expand All @@ -73,7 +74,6 @@
},
"devDependencies": {
"@ethereumjs/block": "^3.0.0-beta.1",
"@ethereumjs/common": "^2.0.0-beta.1",
"@ethereumjs/config-coverage": "^2.0.0",
"@ethereumjs/config-typescript": "^2.0.0",
"@ethereumjs/eslint-config-defaults": "^2.0.0",
Expand Down
120 changes: 102 additions & 18 deletions src/eth/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from 'assert'
import { EventEmitter } from 'events'
import * as rlp from 'rlp'
import ms from 'ms'
Expand All @@ -18,6 +19,12 @@ export class ETH extends EventEmitter {
_statusTimeoutId: NodeJS.Timeout
_send: SendMethod

// Eth64
_hardfork: string = 'chainstart'
_latestBlock: number = 0
_forkHash: string = ''
_nextForkBlock: number = 0

constructor(version: number, peer: Peer, send: SendMethod) {
super()

Expand All @@ -30,10 +37,24 @@ export class ETH extends EventEmitter {
this._statusTimeoutId = setTimeout(() => {
this._peer.disconnect(DISCONNECT_REASONS.TIMEOUT)
}, ms('5s'))

// Set forkHash and nextForkBlock
if (this._version >= 64) {
const c = this._peer._common
this._hardfork = c.hardfork() ? (c.hardfork() as string) : this._hardfork
// Set latestBlock minimally to start block of fork to have some more
// accurate basis if no latestBlock is provided along status send
this._latestBlock = c.hardforkBlock(this._hardfork)
this._forkHash = c.forkHash(this._hardfork)
// Next fork block number or 0 if none available
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evertonfraga thanks, I think I will open a small PR on this towards Common as well before a release, makes really more sense to add this code along a small nextForkBlockNumber() function to Common which we can then reuse.

const nextForkBlock = c.nextHardforkBlock(this._hardfork)
this._nextForkBlock = nextForkBlock ? nextForkBlock : 0
}
}

static eth62 = { name: 'eth', version: 62, length: 8, constructor: ETH }
static eth63 = { name: 'eth', version: 63, length: 17, constructor: ETH }
static eth64 = { name: 'eth', version: 64, length: 29, constructor: ETH }

_handleMessage(code: ETH.MESSAGE_CODES, data: any) {
const payload = rlp.decode(data) as unknown
Expand Down Expand Up @@ -80,6 +101,42 @@ export class ETH extends EventEmitter {
this.emit('message', code, payload)
}

/**
* Eth 64 Fork ID validation (EIP-2124)
* @param forkId Remote fork ID
*/
_validateForkId(forkId: Buffer[]) {
const c = this._peer._common

const peerForkHash = `0x${forkId[0].toString('hex')}`
const peerNextFork = buffer2int(forkId[1])

if (this._forkHash === peerForkHash) {
// There is a known next fork
if (peerNextFork !== 0) {
if (this._latestBlock >= peerNextFork) {
const msg = 'Remote is advertising a future fork that passed locally'
debug(msg)
throw new assert.AssertionError({ message: msg })
}
}
}
const peerFork: any = c.hardforkForForkHash(peerForkHash)
if (peerFork === null) {
const msg = 'Unknown fork hash'
debug(msg)
throw new assert.AssertionError({ message: msg })
}

if (!c.hardforkGteHardfork(peerFork.name, this._hardfork)) {
if (peerNextFork === null || c.nextHardforkBlock(peerFork.name) !== peerNextFork) {
const msg = 'Outdated fork status, remote needs software update'
debug(msg)
throw new assert.AssertionError({ message: msg })
}
}
}

_handleStatus(): void {
if (this._status === null || this._peerStatus === null) return
clearTimeout(this._statusTimeoutId)
Expand All @@ -88,38 +145,72 @@ export class ETH extends EventEmitter {
assertEq(this._status[1], this._peerStatus[1], 'NetworkId mismatch', debug)
assertEq(this._status[4], this._peerStatus[4], 'Genesis block mismatch', debug)

this.emit('status', {
const status: any = {
networkId: this._peerStatus[1],
td: Buffer.from(this._peerStatus[2]),
bestHash: Buffer.from(this._peerStatus[3]),
genesisHash: Buffer.from(this._peerStatus[4])
})
}

if (this._version >= 64) {
assertEq(this._peerStatus[5].length, 2, 'Incorrect forkId msg format', debug)
this._validateForkId(this._peerStatus[5] as Buffer[])
status['forkId'] = this._peerStatus[5]
}

this.emit('status', status)
}

getVersion() {
return this._version
}

_forkHashFromForkId(forkId: Buffer): string {
return `0x${forkId.toString('hex')}`
}

_nextForkFromForkId(forkId: Buffer): number {
return buffer2int(forkId)
}

_getStatusString(status: ETH.StatusMsg) {
let sStr = `[V:${buffer2int(status[0])}, NID:${buffer2int(status[1])}, TD:${buffer2int(
status[2]
)}`
let sStr = `[V:${buffer2int(status[0] as Buffer)}, NID:${buffer2int(
status[1] as Buffer
)}, TD:${buffer2int(status[2] as Buffer)}`
sStr += `, BestH:${formatLogId(status[3].toString('hex'), verbose)}, GenH:${formatLogId(
status[4].toString('hex'),
verbose
)}]`
)}`
if (this._version >= 64) {
sStr += `, ForkHash: 0x${(status[5][0] as Buffer).toString('hex')}`
sStr += `, ForkNext: ${buffer2int(status[5][1] as Buffer)}`
}
sStr += `]`
return sStr
}

sendStatus(status: ETH.Status) {
sendStatus(status: ETH.StatusOpts) {
if (this._status !== null) return
this._status = [
int2buffer(this._version),
int2buffer(status.networkId),
int2buffer(this._peer._common.chainId()),
status.td,
status.bestHash,
status.genesisHash
]
if (this._version >= 64) {
if (status.latestBlock) {
if (status.latestBlock < this._latestBlock) {
throw new Error(
'latest block provided is not matching the HF setting of the Common instance (Rlpx)'
)
}
this._latestBlock = status.latestBlock
}
const forkHashB = Buffer.from(this._forkHash.substr(2), 'hex')
const nextForkB = Buffer.from(this._nextForkBlock.toString(16), 'hex')
this._status.push([forkHashB, nextForkB])
}

debug(
`Send STATUS message to ${this._peer._socket.remoteAddress}:${
Expand Down Expand Up @@ -171,20 +262,13 @@ export class ETH extends EventEmitter {
}

export namespace ETH {
export type StatusMsg = {
0: Buffer
1: Buffer
2: Buffer
3: Buffer
4: Buffer
length: 5
}
export interface StatusMsg extends Array<Buffer | Buffer[]> {}

export type Status = {
export type StatusOpts = {
version: number
networkId: number
td: Buffer
bestHash: Buffer
latestBlock?: number
genesisHash: Buffer
}

Expand Down
5 changes: 2 additions & 3 deletions src/les/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const DEFAULT_ANNOUNCE_TYPE = 1

export class LES extends EventEmitter {
_version: any
_peer: any
_peer: Peer
_send: any
_status: LES.Status | null
_peerStatus: LES.Status | null
Expand Down Expand Up @@ -140,7 +140,7 @@ export class LES extends EventEmitter {
}
status['announceType'] = int2buffer(status['announceType'] as number)
status['protocolVersion'] = int2buffer(this._version)
status['networkId'] = int2buffer(status['networkId'] as number)
status['networkId'] = int2buffer(this._peer._common.chainId())

this._status = status

Expand Down Expand Up @@ -209,7 +209,6 @@ export namespace LES {
export interface Status {
[key: string]: Buffer | number
protocolVersion: Buffer
networkId: Buffer | number
headTd: Buffer
headHash: Buffer
headNum: Buffer
Expand Down
Loading