# EPF6 - Week 10
## Overview
- Attended core dev call and shared updates
- Focused on refining the design and integration of `initWithKurtosisConfig` in Simulation
- Progressed on phasing out the Docker-based Runner and aligning the new `KurtosisSDKRunner` with existing simulation/trackers
- Worked through questions on how the advanced runner should expose services to the assertion framework
- Iterated on updated `interface.ts`
- Main focus: designing how to replace Docker-based NodePair creation with Kurtosis-based equivalents
## Main points
#### Simulation.ts changes
- Drafted a new workflow after `initWithKurtosisConfig`()
- Proposed call chain:
- `createNodePairsFromKurtosis()` → iterates over participants in the config
- `createNodePairFromKurtosis()` → builds one NodePair at a time
- `createBeacon/Execution/ValidatorNodeFromKurtosis()` → instantiate role-specific clients
- **createNodePairsFromKurtosis()** will act as Kurtosis version of [createNodePair()](https://github.com/ChainSafe/lodestar/blob/01f7ef8da440ea43e25f933b7b977e3c96787180/packages/cli/test/utils/crucible/simulation.ts#L226).
Proposed function signature:
```typescript
private async createNodePairsFromKurtosis(
services: KurtosisServicesMap,
config: KurtosisNetworkConfig
): Promise<NodePair[]>
```
Inside will call *createNodePairFromKurtosis()*
- **createNodePairFromKurtosis()** proposed signature:
```typescript
private async createNodePairFromKurtosis(
nodeId: string,
services: KurtosisServicesMap,
config: KurtosisNetworkConfig,
nodeIndex: number
): Promise<NodePair>
```
Emulating the current node creation, it will return role-specific clients:
```typescript
return {
id: nodeId,
beacon: await this.createBeaconNodeFromKurtosis(beaconService, config, nodeIndex, forkConfig),
execution: await this.createExecutionNodeFromKurtosis(executionService, config, nodeIndex, forkConfig),
validator: validatorService ? await this.createValidatorNodeFromKurtosis(validatorService, config, nodeIndex, forkConfig) : undefined,
};
```
- **createBeacon/Execution/ValidatorNodeFromKurtosis()** proposed signature, they emulate [createBeaconNode()](https://github.com/ChainSafe/lodestar/blob/01f7ef8da440ea43e25f933b7b977e3c96787180/packages/cli/test/utils/crucible/clients/beacon/index.ts#L10), [createValidatorNode()](https://github.com/ChainSafe/lodestar/blob/01f7ef8da440ea43e25f933b7b977e3c96787180/packages/cli/test/utils/crucible/clients/validator/index.ts#L10) and [createExecutionNode()](https://github.com/ChainSafe/lodestar/blob/01f7ef8da440ea43e25f933b7b977e3c96787180/packages/cli/test/utils/crucible/clients/execution/index.ts#L18):
```typescript
private async createBeaconNodeFromKurtosis(
beaconService: NodeService,
config: KurtosisNetworkConfig,
nodeIndex: number,
forkConfig: ChainForkConfig
): Promise<BeaconNode>
```
- The goal is to mirror the old logic but replaces Docker with Kurtosis service contexts and URLs
#### NodePair creation
- As assertions expect the same `NodePair` structure as before, work is now on replicating the NodePair object shape using Kurtosis’ `NodeService` and `KurtosisServicesMap`.
The current NodePair is It returns an object that represents a complete Ethereum node setup with beacon, execution, and optionally validator clients
- The hypotesis is that `ServiceContext` replaces [Job](https://github.com/ChainSafe/lodestar/blob/01f7ef8da440ea43e25f933b7b977e3c96787180/packages/cli/test/utils/crucible/interfaces.ts#L271) and `JobOption` for lifecycle and health checks.
Beacon/Validator/Execution Node **refactoring proposal**:
- **BeaconNode -> BeaconNodeKurtosis**
From:
```typescript
export interface BeaconNode<C extends BeaconClient = BeaconClient> {
readonly client: C;
readonly id: string;
/**
* Beacon Node Rest API URL accessible form the host machine if the process is running in private network inside docker
*/
readonly restPublicUrl: string;
/**
* Beacon Node Rest API URL accessible within private network
*/
readonly restPrivateUrl: string;
readonly api: C extends BeaconClient.Lodestar ? LodestarAPI : LighthouseAPI;
readonly job: Job; // ❌ Removed - Docker
}
```
To:
```typescript
export interface BeaconNodeKurtosis<C extends BeaconClient = BeaconClient> {
readonly client: C;
readonly id: string;
readonly restPublicUrl: string; //Kurtosis-generated?
readonly restPrivateUrl: string; //Kurtosis-generated?
readonly api: C extends BeaconClient.Lodestar ? LodestarAPI : LighthouseAPI;
}
readonly serviceContext: ServiceContext; // ✅ Kurtosis
```
- **ValidatorNode -> ValidatorNodeKurtosis**
From:
```typescript
export interface ValidatorNode<C extends ValidatorClient = ValidatorClient> {
readonly client: C;
readonly id: string;
readonly keyManager: KeyManagerApi;
readonly keys: ValidatorClientKeys;
readonly job: Job; // ❌ Removed - Docker
}
```
To:
```typescript
export interface ValidatorNodeKurtosis<C extends ValidatorClient = ValidatorClient> {
readonly client: C;
readonly id: string;
readonly keyManager: KeyManagerApi;
readonly keys: ValidatorClientKeys;
readonly serviceContext: ServiceContext; // ✅ Kurtosis
}
```
- **Execution Node**
From:
```typescript
export interface ExecutionNode<E extends ExecutionClient = ExecutionClient> {
readonly client: E;
readonly id: string;
readonly ttd: bigint;
/**
* Engine URL accessible form the host machine if the process is running in private network inside docker
*/
readonly engineRpcPublicUrl: string; //MODIFY - Use Kurtosis public URLs
/**
* Engine URL accessible within private network inside docker
*/
readonly engineRpcPrivateUrl: string; //REMOVE - Docker-specific (??)
/**
* RPC URL accessible form the host machine if the process is running in private network inside docker
*/
readonly ethRpcPublicUrl: string; //MODIFY - Use Kurtosis public URLs
/**
* RPC URL accessible within private network inside docker
*/
readonly ethRpcPrivateUrl: string; //MODIFY - Use Kurtosis private URLs
readonly jwtSecretHex: string;
readonly provider: E extends ExecutionClient.Mock ? null : Web3;
readonly job: Job; // ❌ Removed - Docker
}
```
To:
```typescript
export interface ExecutionNode<E extends ExecutionClient = ExecutionClient> {
readonly client: E;
readonly id: string;
readonly ttd: bigint;
readonly engineRpcPublicUrl: string;
readonly engineRpcPrivateUrl: string;
readonly ethRpcPublicUrl: string;
readonly ethRpcPrivateUrl: string;
readonly jwtSecretHex: string;
readonly provider: E extends ExecutionClient.Mock ? null : Web3;
readonly serviceContext: ServiceContext; // ✅ Kurtosis
}
```
Example of Kurtosis services output:
```
Service: cl-1-lodestar-geth
🔹 Basic Properties:
ID: cl-1-lodestar-geth
Beacon API URL: http://localhost:53604
Roles: {"beacon":true,"validator":false,"execution":false}
Metadata: {}
🔹 ServiceContext Details:
Container Details:
Image: chainsafe/lodestar:latest
Status: 1
Entrypoint: node ./packages/cli/bin/lodestar
Command: beacon --logLevel=info --port=9000 --discoveryPort=9000 --dataDir=/data/lodestar/beacon-data --chain.persistInvalidSszObjects=true --eth1.depositContractDeployBlock=0 --network.connectToDiscv5Bootnodes=true --discv5=true --eth1=true --eth1.providerUrls=http://172.16.0.11:8545 --execution.urls=http://172.16.0.11:8551 --rest=true --rest.address=0.0.0.0 --rest.namespace=* --rest.port=4000 --nat=true --jwt-secret=/jwt/jwtsecret --enr.ip=172.16.0.13 --enr.tcp=9000 --enr.udp=9000 --metrics --metrics.address=0.0.0.0 --metrics.port=8008 --paramsFile=/network-configs/config.yaml --genesisStateFile=/network-configs/genesis.ssz
Environment Variables:
NODE_OPTIONS=--max-old-space-size
NODE_VERSION=22.16.0
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
YARN_VERSION=1.22.22
Arguments:
Network:
--port=9000
Metrics:
--metrics
--metrics.address=0.0.0.0
--metrics.port=8008
Other:
--logLevel=info
--discoveryPort=9000
--dataDir=/data/lodestar/beacon-data
--chain.persistInvalidSszObjects=true
--eth1.depositContractDeployBlock=0
--network.connectToDiscv5Bootnodes=true
--discv5=true
--eth1=true
--eth1.providerUrls=http://172.16.0.11:8545
--execution.urls=http://172.16.0.11:8551
--rest=true
--rest.address=0.0.0.0
--rest.namespace=*
--rest.port=4000
--nat=true
--jwt-secret=/jwt/jwtsecret
--enr.ip=172.16.0.13
--enr.tcp=9000
--enr.udp=9000
--paramsFile=/network-configs/config.yaml
--genesisStateFile=/network-configs/genesis.ssz
Public Ports: http:53604, metrics:53605, tcp-discovery:53606, udp-discovery:54078
Private Ports: http:4000, metrics:8008, tcp-discovery:9000, udp-discovery:9000
🔹 Legacy Properties:
Beacon API: http://localhost:53604
EL RPC: undefined
Engine API: undefined
Metrics: undefined
Validator API: undefined
```
#### General refactors and open questions
- Does Kurtosis services expose enough metadata to attempt **NodePair** reconstruction?
## Learnings & Outcomes
- Migration affects only infrastructure; assertions are infrastructure-agnostic
- First draft tested a role-aware mapping (beacon, execution, validator), it requires a nomenclatural standardization to ensure semantic consistency with NodePair convention
- Still exploratory: `NodePair` recreation is feasible in draft form, but final viability depends on service mapping conventions and assertion compatibility
## Week 11 TODOs
- Collect feedback from the team on the current logic and approach before proceeding further
- Investigate how to fully replace `Job` with `ServiceContext`, or decide if a compatibility layer is needed
### Useful resources checked