To understand the Engine API flow for starting up a beacon node, I want to learn what happens behind the scenes when you launch Lighthouse with the beacon node command.
I'll be focusing on the interactions with the execution layer. Including how is the EL node represented, which Engine API endpoints are called, etc.
I will ignore a lot of functionality not related to the EL. You can check Jimmy Chen's article for a slightly different focus.
Let's analyze what happens when you run this on your terminal: [1]
lighthouse bn \
--network mainnet \
--execution-endpoint http://localhost:8551 \
--execution-jwt /secrets/jwt.hex \
--checkpoint-sync-url https://mainnet.checkpoint.sigp.io \
--disable-deposit-contract-sync
Here's a detailed version of the process:
You can find this diagram in higher quality here.
This diagram goes deep into what function is called where, but here's a more general overview of the process:
bn
command (short for beacon_node
) is run.bn
in the provided command are configuration flags. That config is parsed into a ClientConfig
struct.
ClientConfig
is an execution_layer::Config
. It contains info about the EL client that Lighthouse will communicate with to run the node. The info from the CLI is used to build this struct. E.g. the --execution-endpoint
flag is parsed into execution_endpoints: Vec<SensitiveUrl>
.ClientConfig
built in the previous step is used to build a ClientBuilder
.
ClientBuilder
is a BeaconChainBuilder
. That one has an execution_layer: Option<ExecutionLayer<T::EthSpec>>
.execution_layer
field of the BeaconChainBuilder
, an ExecutionLayer
struct is built from the execution_layer::Config
field of the ClientConfig
.ClientBuilder
of the previous step is consumed to start all necessary processes and build a Client
.
BeaconChainBuilder
. Which up to this point was storing our representation of the EL client. The ExecutionClient
gets transferred directly to the BeaconChain
that is being built.ClientBuilder
is consumed. This does a lot of stuff, but what matters for the scope of this article is a call to BeaconChain::update_execution_engine_forkchoice
. Note that this is a method of the BeaconChain
, where our ExecutionLayer
is currently stored.update_execution_engine_forkchoice
starts a series of function calls [2]. The bottom of these calls are Engine API endpoints! Specifically:
In my Flashbots article, I mentioned that there might be a minimal set of Engine API endpoints that can be implemented in a Portal Client just to start a CL client. Well, it looks like I found them!
I don't know how far can a CL client go only with those endpoints. I guess I'll figure that out as I learn more about the Engine API and make sequence diagrams to visualize the main flows of it.
Here's the article with those diagrams. The CL client won't go very far with those endpoints, it'll eventually start calling engine_newPayload
. But besides that, it should be able to perform validator tasks with only those endpoints and a builder client attached to it.
[1] This is the command that the lighthouse book suggests using to start a non-staker node. The startup process described in the article is the same for launching the node with the staker command.
[2] You might be wondering what's the point of calling functions that just call other functions. Well: 1) I'm ignoring a lot of functionality, 2) There are a lot of parameters to prepare and other things to do before calling some of the functions, and 3) In rust the error handling gets quite extensive in programs this big, so wrapping a function just to prepare its arguments and handle its errors is not something crazy.