use super::{
Participant, StateHandler, ValidatorContext, ValidatorState, coordinator::CoordinatorBoot,
};
use anyhow::{Context as _, Result, anyhow};
use derive_more::{Debug, Display};
use ethexe_common::{
SimpleBlockData,
db::{BlockMetaStorageRO, OnChainStorageRO},
};
use gprimitives::H256;
#[derive(Debug, Display)]
#[display("IDLE in state {state:?}")]
pub struct Idle {
ctx: ValidatorContext,
state: SubState,
}
#[allow(clippy::enum_variant_names)]
#[derive(Debug)]
enum SubState {
WaitingForChainHead,
WaitingForSynced { block: SimpleBlockData },
WaitingForPrepared { block: SimpleBlockData },
}
impl StateHandler for Idle {
fn context(&self) -> &ValidatorContext {
&self.ctx
}
fn context_mut(&mut self) -> &mut ValidatorContext {
&mut self.ctx
}
fn into_context(self) -> ValidatorContext {
self.ctx
}
fn process_new_head(self, block: SimpleBlockData) -> Result<ValidatorState> {
Self::create_with_chain_head(self.ctx, block)
}
fn process_synced_block(mut self, block: H256) -> Result<ValidatorState> {
match &self.state {
SubState::WaitingForSynced { block: pending } if pending.hash == block => {
let pending = *pending;
self.state = SubState::WaitingForPrepared { block: pending };
self.maybe_advance_to_role()
}
_ => {
tracing::trace!(
received = %block,
"synced block skipped - not waiting for this block",
);
Ok(self.into())
}
}
}
fn process_prepared_block(self, block: H256) -> Result<ValidatorState> {
match &self.state {
SubState::WaitingForPrepared { block: pending } if pending.hash == block => {
self.maybe_advance_to_role()
}
_ => {
tracing::trace!(
received = %block,
"prepared block skipped - not waiting for this block",
);
Ok(self.into())
}
}
}
}
impl Idle {
pub fn create(ctx: ValidatorContext) -> Result<ValidatorState> {
Ok(Self {
ctx,
state: SubState::WaitingForChainHead,
}
.into())
}
pub fn create_with_chain_head(
ctx: ValidatorContext,
block: SimpleBlockData,
) -> Result<ValidatorState> {
let s = Self {
ctx,
state: SubState::WaitingForSynced { block },
};
s.maybe_advance_to_role()
}
fn maybe_advance_to_role(mut self) -> Result<ValidatorState> {
if let SubState::WaitingForSynced { block } = &self.state
&& self.ctx.core.db.block_synced(block.hash)
{
let block = *block;
self.state = SubState::WaitingForPrepared { block };
}
let SubState::WaitingForPrepared { block } = self.state else {
return Ok(self.into());
};
if !self.ctx.core.db.block_meta(block.hash).prepared {
return Ok(Self {
ctx: self.ctx,
state: SubState::WaitingForPrepared { block },
}
.into());
}
let validators = {
let timelines = self.ctx.core.timelines;
let block_era = timelines
.era_from_ts(block.header.timestamp)
.context("failed to calculate era from block timestamp")?;
self.ctx
.core
.db
.validators(block_era)
.ok_or_else(|| anyhow!("validators not found for era {block_era}"))?
};
let coordinator_addr = self
.ctx
.core
.timelines
.block_coordinator_at(&validators, block.header.timestamp)
.ok_or_else(|| anyhow!("cannot determine coordinator for block {}", block.hash))?;
if coordinator_addr == self.ctx.core.pub_key.to_address() {
CoordinatorBoot::start(self.ctx, block, validators)
} else {
Participant::create(self.ctx, block, coordinator_addr)
}
}
}