Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions crates/net/rpc/src/beacon/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,21 @@ async fn get_proposer_slashings(State(store): State<Store>) -> Response {
crate::json_response(serde_json::json!({ "data": data }))
}

/// The pool's attester slashings, versioned by the fork of the wall clock's
/// current epoch, as the Beacon API's "active consensus version" means. The
/// head's fork lags the clock at a fork boundary whose block is late or
/// missing, and a validator client would then decode the list as the wrong
/// fork's container.
///
/// The pool holds electra-shaped slashings. Their JSON is the same for every
/// fork (the containers differ only in the SSZ list bound of the attesting
/// indices), so no conversion is needed for the version to be honest.
async fn get_attester_slashings(State(store): State<Store>) -> Response {
let Some((head_slot, _root)) = store.beacon_head() else {
return ApiError::Internal("no head block").into_response();
};
let fork = store
.config()
.fork_at_epoch(compute_epoch_at_slot(head_slot));
.fork_at_epoch(compute_epoch_at_slot(crate::beacon::node::wall_slot(
&store,
)));
let data = store.operation_pool().attester_slashings();
let response = crate::json_response(serde_json::json!({
"version": fork.as_str(),
Expand Down Expand Up @@ -534,6 +542,28 @@ mod tests {
assert_eq!(json["data"], serde_json::to_value([&slashing]).unwrap());
}

/// The head is before the fork boundary and the wall clock after it: the
/// version is the clock's fork, not the head's.
#[tokio::test]
async fn the_attester_slashings_version_follows_the_wall_clock_not_the_head() {
let mut fixture = fixture();
let state = with_signing_validators_at(ForkName::Fulu, 64);
let config = fixture.store.config();
let head_fork = config.fork_at_epoch(compute_epoch_at_slot(state.slot()));
let wall_epoch = compute_epoch_at_slot(crate::beacon::node::wall_slot(&fixture.store));
let wall_fork = config.fork_at_epoch(wall_epoch);
assert_ne!(
head_fork, wall_fork,
"the head must sit before a fork boundary the clock is past"
);
fixture.store = beacon_store_at(state).0;

let (status, headers, json) = get(&fixture, "/eth/v2/beacon/pool/attester_slashings").await;
assert_eq!(status, StatusCode::OK);
assert_eq!(headers["eth-consensus-version"], wall_fork.as_str());
assert_eq!(json["version"], wall_fork.as_str());
}

#[tokio::test]
async fn a_mixed_bls_change_batch_pools_the_valid_one_and_reports_the_other() {
let fixture = fixture();
Expand Down
2 changes: 1 addition & 1 deletion docs/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ surface rather than sitting beside it; a `/lean/v0` path on a beacon node is a
| `GET` | `/eth/v1/validator/attestation_data` | JSON | What to attest to at `slot` |
| `POST` | `/eth/v2/beacon/pool/attestations` | *(status only)* | Validate and gossip `SingleAttestation`s |
| `GET`, `POST` | `/eth/v1/beacon/pool/proposer_slashings` | JSON | The operation pool's `ProposerSlashing`s; validate, pool and gossip one |
| `GET`, `POST` | `/eth/v2/beacon/pool/attester_slashings` | JSON | The pool's `AttesterSlashing`s (GET carries `Eth-Consensus-Version`); validate, pool and gossip one |
| `GET`, `POST` | `/eth/v2/beacon/pool/attester_slashings` | JSON | The pool's `AttesterSlashing`s (GET carries `Eth-Consensus-Version`, the fork of the wall clock's current epoch); validate, pool and gossip one |
| `GET`, `POST` | `/eth/v1/beacon/pool/voluntary_exits` | JSON | The pool's `SignedVoluntaryExit`s; validate, pool and gossip one |
| `GET`, `POST` | `/eth/v1/beacon/pool/bls_to_execution_changes` | JSON | The pool's `SignedBLSToExecutionChange`s; POST takes an array |
| `POST` | `/eth/v1/validator/beacon_committee_subscriptions` | *(status only)* | Aggregators' entries join their committee's subnet |
Expand Down
Loading