-
Notifications
You must be signed in to change notification settings - Fork 60
node
This package is responsible for p2p interface of Skandha.
P2P layer consists of 3 domains:
-
discovery
domain for finding peers -
req/resp
domain that is used during initial handshake process where bundlers exchange metadata like what kind of mempools they support, also if they find some mempool that they support, during this handshake process, req/resp domain is used for exchanging userops that are already in the mempool of the bundlers, so that the bundler that just connected to shared mempool could catch up with everyone -
gossip
domain - is used only to publish and to listen for newly submitted userops. Each mempool has a separate libp2p topic in gossip domain.
Libp2p discovery is used to find peers. When a node starts, it connects to a bootstrap node and starts to discover other peers
Most of the discovery logic is implemented by libp2p itself. We just need to provide a list of bootstrap nodes and libp2p will take care of the rest.
We only need to run a worker script that will connect to the bootstrap nodes and start discovering other peers.
The worker script is located in node/src/network/discv5
There are 3 types of messages that are exchanged between peers during the initial handshake process.
- Metadata
- PooledUserOpHashes
- PooledUserOpsByHash
Metadata is exchanged first. It contains information about the mempool that the bundler supports. If the bundler finds some mempool that it supports, it will request PooledUserOpHashes from the other bundler. The other bundler will respond with PooledUserOpHashes. After that, the bundler will request PooledUserOpsByHash from the other bundler. The other bundler will respond with UserOperation[].
The initial handshake process is called syncing. The logic for syncing is implemented in node/src/sync::SyncService
The addPeer
and addPeerMetadata
stores peers and their metadata info on local storage.
private addPeer = (peerId: PeerId, status: ts.Status): void => {
/// ...
}
private addPeerMetadata = (peerId: PeerId, metadata: ts.Metadata): void => {
/// ...
}
The startSyncing
runs requestBatches
function that requests PooledUserOpHashes
from the list of known peers and then PooledUserOpsByHash
for each hash from the list of known peers.
private startSyncing(): void {
logger.debug(`Sync service: attempt syncing, status = ${this.state}`);
if (this.state === SyncState.Syncing) {
return; // Skip, already started
}
this.state = SyncState.Syncing;
void this.requestBatches();
}
private async requestBatches(): Promise<void> {
logger.debug("Sync service: requested batches");
/// ...
}
Received req/resp message triggers ReqResp::on[MessageType] methods.
/// ...
private async *onStatus(
req: ts.Status,
peerId: PeerId
): AsyncIterable<EncodedPayload<ts.Status>> {
this.onIncomingRequestBody(
{ method: ReqRespMethod.Status, body: req },
peerId
);
yield* this.reqRespHandlers.onStatus();
}
private async *onGoodbye(
req: ts.Goodbye,
peerId: PeerId
): AsyncIterable<EncodedPayload<ts.Goodbye>> {
this.onIncomingRequestBody(
{ method: ReqRespMethod.Goodbye, body: req },
peerId
);
yield { type: EncodedPayloadType.ssz, data: BigInt(0) };
}
private async *onPing(
req: ts.Ping,
peerId: PeerId
): AsyncIterable<EncodedPayload<ts.Ping>> {
this.onIncomingRequestBody(
{ method: ReqRespMethod.Ping, body: req },
peerId
);
yield {
type: EncodedPayloadType.ssz,
data: this.metadataController.seq_number,
};
}
/// ...
Handlers for a PooledUserOpsByHash or PooledUserOpHashes messages are located in node/src/network/reqresp/handlers
There are other message types that are described in p2p specs:
- Status
- Goodbye
- Ping
Handlers for them are also located in node/src/network/reqresp/ReqRespNode.ts