---
id: TIP-1070
title: Current Committee State
description: Add canonical execution-layer state for the current effective committee selected by DKG.
authors: Richard Janis Goldschmidt (@superfluffy), Arsenii Kulikov (@klkvr)
status: Draft
related: TIP-1017
protocolVersion: TBD
---

# TIP-1070: Current Committee State

## Abstract

This TIP proposes an EIP-4788-like system call that writes the current committee members, according to the DKG outcome, to execution-layer state at the end of the last block of each epoch.

## Motivation

`ValidatorConfigV2.getActiveValidators()` is a recurring source of confusion because it returns the configured validator registry, not necessarily the committee currently active in consensus.
Registry changes such as `addValidator`, `rotateValidator`, and `deactivateValidator` immediately affect the return value of `getActiveValidators()` even if the changes have not yet been propagated to the committee.
Likewise, the committee is only updated if the relevant DKG round succeeds; if DKG fails, the committee remains that of the previous epoch, thus potentially containing validators that are marked deactivated and which would therefore be excluded from `getActiveValidators()`.

Callers that need the current effective committee must therefore inspect consensus data or reconstruct it from epoch-boundary block data.
This TIP gives contracts a stable execution-layer query for that value while keeping it separate from `ValidatorConfigV2`: that registry remains the input to future DKG player selection, while the new committee state records the DKG result consensus actually selected for the current epoch.

## Assumptions

- The consensus layer already determines the effective committee from the DKG outcome used for the epoch.
- The DKG outcome encoded in the last block of an epoch is the canonical source of truth for the next epoch's committee.
- Validators verify the DKG outcome and a quorum of validators must arrive at the same outcome. This is ensured by Tempo's use of Commonware's Feldman/Desmedt-style DKG implementation.
- If a DKG round fails, consensus selects the fallback DKG outcome for the next epoch before the execution-layer system call runs.
- The execution layer can persist enough committee state while processing the last block of each epoch to answer current-committee reads without requiring callers to parse historical block data.
- DKG outcome `players` are ordered Ed25519 public keys.

## Threat Model

- **Consensus proposer**: can propose the last block of an epoch and its extra data, but cannot choose whether the committee update runs. The VM executes the required system call during block processing, and invalid committee updates are consensus-invalid.
- **ValidatorConfigV2 owner**: can change configured validator state, but cannot directly change the effective committee except through the normal DKG process.
- **Contract callers and offchain tools**: untrusted readers. They rely on this query as a canonical view of effective committee membership and should not infer committee state from `getActiveValidators()`.
- **Failed or incomplete DKG rounds**: in scope. The interface must expose the DKG outcome selected by consensus, not the intended player set from registry state.

---

# Specification

## State Location

Committee state MUST live in dedicated execution-layer state for the current effective committee.

This TIP defines a network parameter:

```solidity
address constant CURRENT_COMMITTEE_ADDRESS = TBD;
```

Implementations SHOULD expose the committee through the dedicated system contract or precompile at `CURRENT_COMMITTEE_ADDRESS`, following the same broad model as EIP-2935's history contract: consensus writes protocol data into contract storage during block processing, and EVM callers read it through a narrow contract interface.

## Interface

The committee-state component exposes a current-only read query:

```solidity
/// @notice Get the effective committee members for the current epoch.
/// @dev This returns the DKG outcome players selected by consensus, not the registry's active validator set.
/// @return epoch Epoch for which the returned committee is effective.
/// @return publicKeys Effective committee member public keys, in DKG outcome player order.
function getCommitteeMembers()
    external
    view
    returns (uint64 epoch, bytes32[] memory publicKeys);
```

`getCommitteeMembers()` MUST return the committee used by consensus for the epoch containing the block at which the call is evaluated.
The returned `epoch` MUST be computed from the block number at which the call is evaluated and the chain's epoch schedule.

The returned `publicKeys` MUST be ordered exactly as the `players` vector in the DKG outcome selected for that epoch. The execution-layer writer MUST NOT infer whether DKG succeeded or failed; consensus has already selected the DKG outcome whose `players` vector encodes the committee members for the rising epoch.

## Contract Storage

The committee-state component MUST persist only the current committee member public keys:

```solidity
bytes32[] publicKeys;
```

## System Call Execution

The VM MUST execute the committee update as a system call while processing the last block of epoch `E`.

The system call MUST run from a post-execution hook after all user transactions in the last block of epoch `E` have executed. It is not a user transaction and MUST NOT appear in the transaction list. Transactions in the last block of epoch `E` MUST continue to observe the committee for epoch `E`; the persisted committee for epoch `E+1` MUST only become visible to EVM execution in the first block of epoch `E+1`.

This system call follows the same execution convention as EIP-4788-style system operations:

- the call MUST execute to completion;
- the call MUST NOT count against the block gas limit;
- the call MUST NOT transfer value or apply EIP-1559 burn semantics.

The system call MUST call `CURRENT_COMMITTEE_ADDRESS` from the system address with calldata equivalent to:

```solidity
function setCommitteeMembers(bytes32[] calldata publicKeys) external;
```

The system call MUST NOT pass an epoch argument.

The system call MUST read the DKG outcome from the boundary block's extra data and persist:

```text
committee(E+1) = selected_dkg_outcome(E).players
```

The system call MUST overwrite the stored `bytes32[] publicKeys` array with `selected_dkg_outcome(E).players`, converted to `bytes32[]` without reordering. Implementations MUST NOT append a new committee record per epoch or otherwise grow state as epochs advance. A block for the last slot of epoch `E` is invalid if processing it does not perform the system call with the committee implied by that block's extra data.

## Activation

At activation, implementations MUST deploy the committee contract at `CURRENT_COMMITTEE_ADDRESS`. Activation MUST NOT initialize the committee state from the latest DKG outcome.

Before the first boundary system call completes, `getCommitteeMembers()` MUST NOT return `getActiveValidators()` or any inferred fallback committee.

# Invariants

- The boundary system call MUST write exactly the `players` recorded in the boundary block's DKG outcome as the committee members for the next epoch.
- `getCommitteeMembers()` MUST return those same `players` as the effective committee members for the epoch in which the call is evaluated.
- Validators MUST verify that the last block of an epoch contains the required DKG outcome in its extra data and that block processing writes exactly the committee implied by that DKG outcome.
