UNPKG

@ethereumjs/devp2p

Version:
285 lines 13.1 kB
import { RLP } from '@ethereumjs/rlp'; import { BIGINT_0, EthereumJSErrorWithoutCode, bigIntToBytes, bytesToBigInt, bytesToHex, bytesToInt, bytesToUnprefixedHex, hexToBytes, intToBytes, isHexString, } from '@ethereumjs/util'; import * as snappy from 'snappyjs'; import { ProtocolType } from "../types.js"; import { assertEq, formatLogData, formatLogId } from "../util.js"; import { Protocol } from "./protocol.js"; export const EthMessageCodes = { // eth62 STATUS: 0x00, NEW_BLOCK_HASHES: 0x01, TX: 0x02, GET_BLOCK_HEADERS: 0x03, BLOCK_HEADERS: 0x04, GET_BLOCK_BODIES: 0x05, BLOCK_BODIES: 0x06, NEW_BLOCK: 0x07, // eth63 GET_NODE_DATA: 0x0d, NODE_DATA: 0x0e, GET_RECEIPTS: 0x0f, RECEIPTS: 0x10, // eth65 NEW_POOLED_TRANSACTION_HASHES: 0x08, GET_POOLED_TRANSACTIONS: 0x09, POOLED_TRANSACTIONS: 0x0a, }; // Create a reverse mapping: from numeric value back to the key name export const EthMessageCodeNames = Object.entries(EthMessageCodes).reduce((acc, [key, value]) => { acc[value] = key; return acc; }, {}); export class ETH extends Protocol { constructor(version, peer, send) { super(peer, send, ProtocolType.ETH, version, EthMessageCodes); this._status = null; this._peerStatus = null; this.DEBUG = false; // Eth64 this._hardfork = 'chainstart'; this._latestBlock = BIGINT_0; this._forkHash = ''; this._nextForkBlock = BIGINT_0; // Set forkHash and nextForkBlock if (this._version >= 64) { const c = this._peer.common; this._hardfork = c.hardfork() ?? 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) ?? BIGINT_0; this._forkHash = c.forkHash(this._hardfork); // Next fork block number or 0 if none available this._nextForkBlock = c.nextHardforkBlockOrTimestamp(this._hardfork) ?? BIGINT_0; } // Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables this.DEBUG = process?.env?.DEBUG?.includes('ethjs') ?? false; } _handleMessage(code, data) { const payload = RLP.decode(data); if (code !== EthMessageCodes.STATUS && this.DEBUG) { const debugMsg = this.DEBUG ? `Received ${this.getMsgPrefix(code)} message from ${this._peer['_socket'].remoteAddress}:${this._peer['_socket'].remotePort}` : undefined; const logData = formatLogData(bytesToHex(data), this._verbose); this.debug(this.getMsgPrefix(code), `${debugMsg}: ${logData}`); } switch (code) { case EthMessageCodes.STATUS: { assertEq(this._peerStatus, null, 'Uncontrolled status message', this.debug.bind(this), 'STATUS'); this._peerStatus = payload; const peerStatusMsg = `${this._peerStatus !== undefined ? this._getStatusString(this._peerStatus) : ''}`; if (this.DEBUG) { const debugMsg = this.DEBUG ? `Received ${this.getMsgPrefix(code)} message from ${this._peer['_socket'].remoteAddress}:${this._peer['_socket'].remotePort}` : undefined; this.debug(this.getMsgPrefix(code), `${debugMsg}: ${peerStatusMsg}`); } this._handleStatus(); break; } case EthMessageCodes.NEW_BLOCK_HASHES: case EthMessageCodes.TX: case EthMessageCodes.GET_BLOCK_HEADERS: case EthMessageCodes.BLOCK_HEADERS: case EthMessageCodes.GET_BLOCK_BODIES: case EthMessageCodes.BLOCK_BODIES: case EthMessageCodes.NEW_BLOCK: if (this._version >= ETH.eth62.version) break; return; case EthMessageCodes.GET_RECEIPTS: case EthMessageCodes.RECEIPTS: if (this._version >= ETH.eth63.version) break; return; case EthMessageCodes.NEW_POOLED_TRANSACTION_HASHES: case EthMessageCodes.GET_POOLED_TRANSACTIONS: case EthMessageCodes.POOLED_TRANSACTIONS: if (this._version >= ETH.eth65.version) break; return; case EthMessageCodes.GET_NODE_DATA: case EthMessageCodes.NODE_DATA: if (this._version >= ETH.eth63.version && this._version <= ETH.eth66.version) break; return; default: return; } this.events.emit('message', code, payload); } /** * Eth 64 Fork ID validation (EIP-2124) * @param forkId Remote fork ID */ _validateForkId(forkId) { const c = this._peer.common; const peerForkHash = bytesToHex(forkId[0]); const peerNextFork = bytesToBigInt(forkId[1]); if (this._forkHash === peerForkHash) { // There is a known next fork if (peerNextFork > BIGINT_0) { if (this._latestBlock >= peerNextFork) { const msg = 'Remote is advertising a future fork that passed locally'; if (this.DEBUG) { this.debug('STATUS', msg); } throw EthereumJSErrorWithoutCode(msg); } } } const peerFork = c.hardforkForForkHash(peerForkHash); if (peerFork === null) { const msg = 'Unknown fork hash'; if (this.DEBUG) { this.debug('STATUS', msg); } throw EthereumJSErrorWithoutCode(msg); } if (!c.hardforkGteHardfork(peerFork.name, this._hardfork)) { const nextHardforkBlock = c.nextHardforkBlockOrTimestamp(peerFork.name); if (peerNextFork === null || nextHardforkBlock === null || nextHardforkBlock !== peerNextFork) { const msg = 'Outdated fork status, remote needs software update'; if (this.DEBUG) { this.debug('STATUS', msg); } throw EthereumJSErrorWithoutCode(msg); } } } _handleStatus() { if (this._status === null || this._peerStatus === null) return; clearTimeout(this._statusTimeoutId); assertEq(this._status[0], this._peerStatus[0], 'Protocol version mismatch', this.debug.bind(this), 'STATUS'); assertEq(this._status[1], this._peerStatus[1], 'NetworkId mismatch', this.debug.bind(this), 'STATUS'); assertEq(this._status[4], this._peerStatus[4], 'Genesis block mismatch', this.debug.bind(this), 'STATUS'); const status = { chainId: this._peerStatus[1], td: this._peerStatus[2], bestHash: this._peerStatus[3], genesisHash: this._peerStatus[4], forkId: undefined, }; if (this._version >= 64) { assertEq(this._peerStatus[5].length, 2, 'Incorrect forkId msg format', this.debug.bind(this), 'STATUS'); this._validateForkId(this._peerStatus[5]); status.forkId = this._peerStatus[5]; } this.events.emit('status', status); if (this._firstPeer === '') { this._addFirstPeerDebugger(); } } getVersion() { return this._version; } _forkHashFromForkId(forkId) { return bytesToUnprefixedHex(forkId); } _nextForkFromForkId(forkId) { return bytesToInt(forkId); } _getStatusString(status) { let sStr = `[V:${bytesToInt(status[0])}, NID:${bytesToInt(status[1])}, TD:${status[2].length === 0 ? 0 : bytesToBigInt(status[2]).toString()}`; sStr += `, BestH:${formatLogId(bytesToHex(status[3]), this._verbose)}, GenH:${formatLogId(bytesToHex(status[4]), this._verbose)}`; if (this._version >= 64) { sStr += `, ForkHash: ${status[5] !== undefined ? bytesToHex(status[5][0]) : '-'}`; sStr += `, ForkNext: ${status[5][1].length > 0 ? bytesToHex(status[5][1]) : '-'}`; } sStr += `]`; return sStr; } sendStatus(status) { if (this._status !== null) return; this._status = [ intToBytes(this._version), bigIntToBytes(this._peer.common.chainId()), status.td, status.bestHash, status.genesisHash, ]; if (this._version >= 64) { if (status.latestBlock) { const latestBlock = bytesToBigInt(status.latestBlock); if (latestBlock < this._latestBlock) { throw EthereumJSErrorWithoutCode('latest block provided is not matching the HF setting of the Common instance (Rlpx)'); } this._latestBlock = latestBlock; } const forkHashB = hexToBytes(isHexString(this._forkHash) ? this._forkHash : `0x${this._forkHash}`); const nextForkB = this._nextForkBlock === BIGINT_0 ? new Uint8Array() : bigIntToBytes(this._nextForkBlock); this._status.push([forkHashB, nextForkB]); } if (this.DEBUG) { this.debug('STATUS', `Send STATUS message to ${this._peer['_socket'].remoteAddress}:${this._peer['_socket'].remotePort} (eth${this._version}): ${this._getStatusString(this._status)}`); } let payload = RLP.encode(this._status); // Use snappy compression if peer supports DevP2P >=v5 if (this._peer['_hello'] !== null && this._peer['_hello'].protocolVersion >= 5) { payload = snappy.compress(payload); } this._send(EthMessageCodes.STATUS, payload); this._handleStatus(); } sendMessage(code, payload) { if (this.DEBUG) { const logData = formatLogData(bytesToHex(RLP.encode(payload)), this._verbose); const messageName = this.getMsgPrefix(code); const debugMsg = `Send ${messageName} message to ${this._peer['_socket'].remoteAddress}:${this._peer['_socket'].remotePort}: ${logData}`; this.debug(messageName, debugMsg); } switch (code) { case EthMessageCodes.STATUS: throw EthereumJSErrorWithoutCode('Please send status message through .sendStatus'); case EthMessageCodes.NEW_BLOCK_HASHES: case EthMessageCodes.TX: case EthMessageCodes.GET_BLOCK_HEADERS: case EthMessageCodes.BLOCK_HEADERS: case EthMessageCodes.GET_BLOCK_BODIES: case EthMessageCodes.BLOCK_BODIES: case EthMessageCodes.NEW_BLOCK: if (this._version >= ETH.eth62.version) break; throw EthereumJSErrorWithoutCode(`Code ${code} not allowed with version ${this._version}`); case EthMessageCodes.GET_RECEIPTS: case EthMessageCodes.RECEIPTS: if (this._version >= ETH.eth63.version) break; throw EthereumJSErrorWithoutCode(`Code ${code} not allowed with version ${this._version}`); case EthMessageCodes.NEW_POOLED_TRANSACTION_HASHES: case EthMessageCodes.GET_POOLED_TRANSACTIONS: case EthMessageCodes.POOLED_TRANSACTIONS: if (this._version >= ETH.eth65.version) break; throw EthereumJSErrorWithoutCode(`Code ${code} not allowed with version ${this._version}`); case EthMessageCodes.GET_NODE_DATA: case EthMessageCodes.NODE_DATA: if (this._version >= ETH.eth63.version && this._version <= ETH.eth66.version) break; throw EthereumJSErrorWithoutCode(`Code ${code} not allowed with version ${this._version}`); default: throw EthereumJSErrorWithoutCode(`Unknown code ${code}`); } payload = RLP.encode(payload); // Use snappy compression if peer supports DevP2P >=v5 if (this._peer['_hello'] !== null && this._peer['_hello'].protocolVersion >= 5) { payload = snappy.compress(payload); } this._send(code, payload); } getMsgPrefix(msgCode) { return EthMessageCodeNames[msgCode]; } } ETH.eth62 = { name: 'eth', version: 62, length: 8, constructor: ETH }; ETH.eth63 = { name: 'eth', version: 63, length: 17, constructor: ETH }; ETH.eth64 = { name: 'eth', version: 64, length: 17, constructor: ETH }; ETH.eth65 = { name: 'eth', version: 65, length: 17, constructor: ETH }; ETH.eth66 = { name: 'eth', version: 66, length: 17, constructor: ETH }; ETH.eth67 = { name: 'eth', version: 67, length: 17, constructor: ETH }; ETH.eth68 = { name: 'eth', version: 68, length: 17, constructor: ETH }; //# sourceMappingURL=eth.js.map