-
Notifications
You must be signed in to change notification settings - Fork 785
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
Client: New Mechanism to Keep Peer Latest Block Updated #3354
Changes from all commits
6a2b266
4dd9bf8
e268f9a
b053277
72a28d1
f29c3c6
6ea23b0
fabf53c
ccf9366
4cd01aa
5777c42
9ea28d8
9a6ae27
5c42d73
9e60a09
d6c2a6c
58cb2b4
c53fa4b
20bf36f
fe9a4ec
0117083
7d877f2
4e8aee6
4e7bd84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
import { BIGINT_0 } from '@ethereumjs/util' | ||
import { EventEmitter } from 'events' | ||
|
||
import { short } from '../../util' | ||
import { BoundEthProtocol, BoundLesProtocol, BoundSnapProtocol } from '../protocol' | ||
|
||
import type { Config } from '../../config' | ||
import type { BoundProtocol, Protocol, Sender } from '../protocol' | ||
import type { Server } from '../server' | ||
import type { BlockHeader } from '@ethereumjs/block' | ||
|
||
export interface PeerOptions { | ||
/* Config */ | ||
|
@@ -90,6 +93,64 @@ export abstract class Peer extends EventEmitter { | |
|
||
abstract connect(): Promise<void> | ||
|
||
/** | ||
* Eventually updates and returns the latest header of peer | ||
*/ | ||
async latest(): Promise<BlockHeader | undefined> { | ||
if (!this.eth) { | ||
return | ||
} | ||
let block: bigint | Uint8Array | ||
if (!this.eth!.updatedBestHeader) { | ||
// If there is no updated best header stored yet, start with the status hash | ||
block = this.eth!.status.bestHash | ||
} else { | ||
block = this.getPotentialBestHeaderNum() | ||
} | ||
const result = await this.eth!.getBlockHeaders({ | ||
block, | ||
max: 1, | ||
}) | ||
if (result !== undefined) { | ||
const latest = result[1][0] | ||
this.eth!.updatedBestHeader = latest | ||
if (latest !== undefined) { | ||
const height = latest.number | ||
if ( | ||
height > BIGINT_0 && | ||
(this.config.syncTargetHeight === undefined || | ||
this.config.syncTargetHeight === BIGINT_0 || | ||
this.config.syncTargetHeight < latest.number) | ||
) { | ||
this.config.syncTargetHeight = height | ||
this.config.logger.info(`New sync target height=${height} hash=${short(latest.hash())}`) | ||
} | ||
} | ||
} | ||
return this.eth!.updatedBestHeader | ||
} | ||
|
||
/** | ||
* Returns a potential best block header number for the peer | ||
* (not necessarily verified by block request) derived from | ||
* either the client-wide sync target height or the last best | ||
* header timestamp "forward-calculated" by block/slot times (12s). | ||
*/ | ||
getPotentialBestHeaderNum(): bigint { | ||
let forwardCalculatedNum = BIGINT_0 | ||
const bestSyncTargetNum = this.config.syncTargetHeight ?? BIGINT_0 | ||
if (this.eth?.updatedBestHeader !== undefined) { | ||
const bestHeaderNum = this.eth!.updatedBestHeader.number | ||
const nowSec = Math.floor(Date.now() / 1000) | ||
const diffSec = nowSec - Number(this.eth!.updatedBestHeader.timestamp) | ||
const SLOT_TIME = 12 | ||
const diffBlocks = BigInt(Math.floor(diffSec / SLOT_TIME)) | ||
forwardCalculatedNum = bestHeaderNum + diffBlocks | ||
} | ||
const best = forwardCalculatedNum > bestSyncTargetNum ? forwardCalculatedNum : bestSyncTargetNum | ||
return best | ||
} | ||
|
||
/** | ||
* Handle unhandled messages along handshake | ||
*/ | ||
|
@@ -108,28 +169,25 @@ export abstract class Peer extends EventEmitter { | |
|
||
if (protocol.name === 'eth') { | ||
bound = new BoundEthProtocol(boundOpts) | ||
|
||
await bound!.handshake(sender) | ||
|
||
this.eth = <BoundEthProtocol>bound | ||
} else if (protocol.name === 'les') { | ||
bound = new BoundLesProtocol(boundOpts) | ||
} else if (protocol.name === 'snap') { | ||
bound = new BoundSnapProtocol(boundOpts) | ||
} else { | ||
throw new Error(`addProtocol: ${protocol.name} protocol not supported`) | ||
} | ||
|
||
// Handshake only when snap, else | ||
if (protocol.name !== 'snap') { | ||
await bound!.handshake(sender) | ||
} else { | ||
if (sender.status === undefined) throw Error('Snap can only be bound on handshaked peer') | ||
} | ||
|
||
if (protocol.name === 'eth') { | ||
this.eth = <BoundEthProtocol>bound | ||
this.les = <BoundLesProtocol>bound | ||
} else if (protocol.name === 'snap') { | ||
bound = new BoundSnapProtocol(boundOpts) | ||
if (sender.status === undefined) throw Error('Snap can only be bound on handshaked peer') | ||
|
||
this.snap = <BoundSnapProtocol>bound | ||
} else if (protocol.name === 'les') { | ||
this.les = <BoundLesProtocol>bound | ||
} else { | ||
throw new Error(`addProtocol: ${protocol.name} protocol not supported`) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks wild but is only a small local refactor making things a bit cleaner. 🙂 |
||
|
||
this.boundProtocols.push(bound) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,9 @@ import { Hardfork } from '@ethereumjs/common' | |
|
||
import { Event } from '../types' | ||
|
||
import { type Peer, RlpxPeer } from './peer' | ||
|
||
import type { Config } from '../config' | ||
import type { Peer } from './peer' | ||
|
||
export interface PeerPoolOptions { | ||
/* Config */ | ||
|
@@ -31,7 +32,13 @@ export class PeerPool { | |
*/ | ||
private DEFAULT_STATUS_CHECK_INTERVAL = 20000 | ||
|
||
/** | ||
* Default peer best header update interval (in ms) | ||
*/ | ||
private DEFAULT_PEER_BEST_HEADER_UPDATE_INTERVAL = 5000 | ||
|
||
private _statusCheckInterval: NodeJS.Timeout | undefined /* global NodeJS */ | ||
private _peerBestHeaderUpdateInterval: NodeJS.Timeout | undefined | ||
private _reconnectTimeout: NodeJS.Timeout | undefined | ||
|
||
/** | ||
|
@@ -87,6 +94,12 @@ export class PeerPool { | |
this.DEFAULT_STATUS_CHECK_INTERVAL | ||
) | ||
|
||
this._peerBestHeaderUpdateInterval = setInterval( | ||
// eslint-disable-next-line @typescript-eslint/await-thenable | ||
await this._peerBestHeaderUpdate.bind(this), | ||
this.DEFAULT_PEER_BEST_HEADER_UPDATE_INTERVAL | ||
) | ||
|
||
this.running = true | ||
return true | ||
} | ||
|
@@ -99,6 +112,7 @@ export class PeerPool { | |
await this.close() | ||
} | ||
clearInterval(this._statusCheckInterval as NodeJS.Timeout) | ||
clearInterval(this._peerBestHeaderUpdateInterval as NodeJS.Timeout) | ||
clearTimeout(this._reconnectTimeout as NodeJS.Timeout) | ||
this.running = false | ||
return true | ||
|
@@ -252,4 +266,17 @@ export class PeerPool { | |
this.noPeerPeriods = 0 | ||
} | ||
} | ||
|
||
/** | ||
* Periodically update the latest best known header for peers | ||
*/ | ||
async _peerBestHeaderUpdate() { | ||
for (const p of this.peers) { | ||
if (p.idle && p.eth !== undefined && p instanceof RlpxPeer) { | ||
p.idle = false | ||
await p.latest() | ||
p.idle = true | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I generally would think it would make more sense to have all (or: most, so except for the first one) |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if a slot is missed? Then this block is definitely not there 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I initially planned to do 2-3 more commits fine tuning the existing solution a bit with a somewhat more adaptive algorithm (basically remember the last unsuccessful try (or remove on success again) and if present then gradually downgrade the request (so: try a lower block number)).
Guess we likely can/want to do this in a small follow-up PR, should be not such a big effort.