At MigaLabs, we are actively developing goteth, a consensus layer indexer. In the process of comparing data with other sources that contain indexed data from the ethereum beacon chain, we found that the number of validators slashed that we tracked did not match with the amounts reported on beaconcha.in and beaconscan.
As of September 17th, 2024, we had been tracking 438 slashed validators, while the other sources mentioned had 439 in their lists. This led us to conduct a detailed verification process to determine whether our list contained an error or if the difference lay with the other sources.
How do we track slashed validators?
The standard beacon API contains an endpoint for evaluating the state of the validator set at a given slot. Through a simple query to the /eth/v1/beacon/states/finalized/validators, the node reports a list of the states of every validator, containing information on whether the validator was slashed or not. Example output for validator 155949:
{
"execution_optimistic": false,
"finalized": true,
"data": [
{
"index": "155949",
"balance": "32018895662",
"status": "active_ongoing",
"validator": {
"pubkey": "0x88ad0eb3ca4075dd7723528c4a2274175d01cc968639976a8083ec9416cb7121fe6ee35ec55e5c4fbdba3d34a7b36eba",
"withdrawal_credentials": "0x010000000000000000000000c15326383f39781c8a0b73fcd7ecf5c6a55287af",
"effective_balance": "32000000000",
"slashed": false,
"activation_eligibility_epoch": "39782",
"activation_epoch": "41499",
"exit_epoch": "18446744073709551615",
"withdrawable_epoch": "18446744073709551615"
}
}
]
}
This is a simple source of truth for verifying, and we quickly concluded that our tracking method was error-free.
Discrepancy with beaconscan
Beaconscan maked validator 155949 as slashed on their validator slashings list: When inspecting the validator, it was not slashed (see the output from the beacon API above). Even on their validator page, it appeared as active. This is likely an indexing error.
Discrepancy with beaconcha.in
While comparing our slashed validator list with the one on beaconcha.in, we found that validator 63338 had been marked as slashed twice, once in slot 476904 and then at slot 476969: This is not really the case. Validator 63338 was only slashed at slot 476904, not at slot 476969; even their slot 476969 page shows no slashing. Another error that we found in this data was that validator 38061 was also marked as slashed on two slots, 138194 and 138221:
Can a validator be slashed twice?
The short answer is no. The spec defines the conditions for a validator being slashable and it clearly states that a validator cannot be slashed twice.
def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool:
"""
Check if ``validator`` is slashable.
"""
return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch)
However, investigating the case of validator 38061, where we found discrepancies with beaconcha.in, led to a curious edgy case where one could think that a validator was slashed twice. Validator 38061 was slashed at slot 138194 due to a double vote: At slot 138221, validator 38113 was slashed due to producing a double vote on the same block roots as validator 38061 and this double vote was aggregated to the ones from slot 138194 (where validator 38061 was slashed). While processing attester slashings for this double vote, validator 38061 should be slashed again if it weren't for the condition in is_slashable_validator. Not accounting for this on an indexer could lead to thinking that it is possible to have a double slashing on a validator.
def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None:
attestation_1 = attester_slashing.attestation_1
attestation_2 = attester_slashing.attestation_2
assert is_slashable_attestation_data(attestation_1.data, attestation_2.data)
assert is_valid_indexed_attestation(state, attestation_1)
assert is_valid_indexed_attestation(state, attestation_2)
slashed_any = False
indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)
for index in sorted(indices):
if is_slashable_validator(state.validators[index], get_current_epoch(state)):
slash_validator(state, index)
slashed_any = True
assert slashed_any
TL;DR
- Validator 155949 was not slashed, it is an error on beaconscan.
- Validators 63338 and 38061 were slashed once, not twice, as shown on beaconcha.in.
- A validator cannot be slashed twice
- There have been 438 validators slashed since the genesis of the beacon chain
Conclusion
It is crucial to continuously verify the accuracy of data provided by third parties, as discrepancies can arise even from seemingly reliable sources. Developing rigorous testing and verification procedures is essential to ensure the integrity of our data, allowing us to identify and correct inconsistencies promptly. At MigaLabs, we focus on maintaining high standards to strengthen trust in our indexing services and guarantee that users can rely on the accuracy of the information we provide.
Acknowledgments
We would like to thank the Ethereum pandaOps team (Sam, Andrew, and Pari) for helping us cross-check our data and understand the edge cases mentioned.