---
# System prepended metadata

title: EPF6 - Week 10
tags: [Ethereum Protocol Fellowship 6]

---

# 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
