-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consensus worker task impl #34
Changes from all commits
64fb4b0
caf5099
c7be6d5
21de685
3655d76
71d0060
46538e1
0e9e0ca
1d89bcd
bc65bce
f5cf913
e5dafd5
d149dbd
b089fc4
e1f6be6
a9f0274
62ccb21
5ec8a08
9c04f20
e5edbc8
7f80b87
fc8902c
344a1f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
//! Chain tip tracking. Used to talk to the EL and pick the new chain tip. | ||
|
||
use std::collections::*; | ||
use std::sync::Arc; | ||
|
||
use tokio::sync::mpsc; | ||
use tracing::*; | ||
|
||
use alpen_vertex_db::errors::DbError; | ||
use alpen_vertex_db::traits::{BlockStatus, Database, L2DataProvider, L2DataStore, SyncEventStore}; | ||
use alpen_vertex_evmctl::engine::ExecEngineCtl; | ||
use alpen_vertex_evmctl::messages::ExecPayloadData; | ||
use alpen_vertex_primitives::params::Params; | ||
use alpen_vertex_state::block::{L2Block, L2BlockId}; | ||
use alpen_vertex_state::consensus::ConsensusState; | ||
use alpen_vertex_state::operation::SyncAction; | ||
use alpen_vertex_state::sync_event::SyncEvent; | ||
|
||
use crate::ctl::CsmController; | ||
use crate::message::ChainTipMessage; | ||
use crate::{credential, errors::*, reorg, unfinalized_tracker}; | ||
|
||
/// Tracks the parts of the chain that haven't been finalized on-chain yet. | ||
pub struct ChainTipTrackerState<D: Database> { | ||
/// Consensus parameters. | ||
params: Arc<Params>, | ||
|
||
/// Underlying state database. | ||
database: Arc<D>, | ||
|
||
/// Current consensus state we're considering blocks against. | ||
cur_state: Arc<ConsensusState>, | ||
|
||
/// Tracks unfinalized block tips. | ||
chain_tracker: unfinalized_tracker::UnfinalizedBlockTracker, | ||
|
||
/// Current best block. | ||
// TODO make sure we actually want to have this | ||
cur_best_block: L2BlockId, | ||
} | ||
|
||
impl<D: Database> ChainTipTrackerState<D> { | ||
/// Constructs a new instance we can run the tracker with. | ||
pub fn new( | ||
params: Arc<Params>, | ||
database: Arc<D>, | ||
cur_state: Arc<ConsensusState>, | ||
chain_tracker: unfinalized_tracker::UnfinalizedBlockTracker, | ||
cur_best_block: L2BlockId, | ||
) -> Self { | ||
Self { | ||
params, | ||
database, | ||
cur_state, | ||
chain_tracker, | ||
cur_best_block, | ||
} | ||
} | ||
|
||
fn finalized_tip(&self) -> &L2BlockId { | ||
self.chain_tracker.finalized_tip() | ||
} | ||
|
||
fn set_block_status(&self, id: &L2BlockId, status: BlockStatus) -> Result<(), DbError> { | ||
let l2store = self.database.l2_store(); | ||
l2store.set_block_status(*id, status)?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
/// Main tracker task that takes a ready chain tip tracker and some IO stuff. | ||
pub fn tracker_task<D: Database, E: ExecEngineCtl>( | ||
state: ChainTipTrackerState<D>, | ||
engine: Arc<E>, | ||
ctm_rx: mpsc::Receiver<ChainTipMessage>, | ||
csm_ctl: Arc<CsmController<D>>, | ||
) { | ||
if let Err(e) = tracker_task_inner(state, engine.as_ref(), ctm_rx, &csm_ctl) { | ||
error!(err = %e, "tracker aborted"); | ||
} | ||
} | ||
|
||
fn tracker_task_inner<D: Database, E: ExecEngineCtl>( | ||
mut state: ChainTipTrackerState<D>, | ||
engine: &E, | ||
mut ctm_rx: mpsc::Receiver<ChainTipMessage>, | ||
csm_ctl: &CsmController<D>, | ||
) -> anyhow::Result<()> { | ||
loop { | ||
let Some(m) = ctm_rx.blocking_recv() else { | ||
break; | ||
}; | ||
|
||
// TODO decide when errors are actually failures vs when they're okay | ||
process_ct_msg(m, &mut state, engine, csm_ctl)?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn process_ct_msg<D: Database, E: ExecEngineCtl>( | ||
ctm: ChainTipMessage, | ||
state: &mut ChainTipTrackerState<D>, | ||
engine: &E, | ||
csm_ctl: &CsmController<D>, | ||
) -> anyhow::Result<()> { | ||
match ctm { | ||
ChainTipMessage::NewState(cs, output) => { | ||
let l2_tip = cs.chain_state().chain_tip_blockid(); | ||
|
||
// Update the new state. | ||
state.cur_state = cs; | ||
|
||
// TODO use output actions to clear out dangling states now | ||
delbonis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for act in output.actions() { | ||
match act { | ||
SyncAction::FinalizeBlock(blkid) => { | ||
let fin_report = state.chain_tracker.update_finalized_tip(blkid)?; | ||
info!(?blkid, ?fin_report, "finalized block") | ||
// TODO do something with the finalization report | ||
} | ||
|
||
// TODO | ||
_ => {} | ||
} | ||
} | ||
|
||
// TODO recheck every remaining block's validity using the new state | ||
// starting from the bottom up, putting into a new chain tracker | ||
} | ||
|
||
ChainTipMessage::NewBlock(blkid) => { | ||
delbonis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let l2prov = state.database.l2_provider(); | ||
let block = l2prov | ||
.get_block_data(blkid)? | ||
.ok_or(Error::MissingL2Block(blkid))?; | ||
|
||
// First, decide if the block seems correctly signed and we haven't | ||
// already marked it as invalid. | ||
let cstate = state.cur_state.clone(); | ||
let correctly_signed = check_new_block(&blkid, &block, &cstate, state)?; | ||
if !correctly_signed { | ||
// It's invalid, write that and return. | ||
state.set_block_status(&blkid, BlockStatus::Invalid)?; | ||
return Ok(()); | ||
} | ||
|
||
// Try to execute the payload, seeing if *that's* valid. | ||
let exec_hash = block.header().exec_payload_hash(); | ||
let exec_seg = block.exec_segment(); | ||
let eng_payload = ExecPayloadData::new_simple(exec_seg.payload().to_vec()); | ||
debug!(?blkid, ?exec_hash, "submitting execution payload"); | ||
let res = engine.submit_payload(eng_payload)?; | ||
|
||
// If the payload is invalid then we should write the full block as | ||
// being invalid and return too. | ||
// TODO verify this is reasonable behavior, especially with regard | ||
// to pre-sync | ||
if res == alpen_vertex_evmctl::engine::BlockStatus::Invalid { | ||
// It's invalid, write that and return. | ||
state.set_block_status(&blkid, BlockStatus::Invalid)?; | ||
return Ok(()); | ||
} | ||
|
||
// Insert block into pending block tracker and figure out if we | ||
// should switch to it as a potential head. This returns if we | ||
// created a new tip instead of advancing an existing tip. | ||
let cur_tip = cstate.chain_state().chain_tip_blockid(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought this would be same as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Finalized is what's been proven on chain. The current tip is where we expect new blocks to be attached and what we treat as the current state. We take it out here since we need to figure out after attaching the block to the pending blocks tree if we're going to switch from the current block to that and how. This is still a bit unfinished and we'll have more sophisticated logic after we've figured out the rest of the block signing mechanics. |
||
let new_tip = state.chain_tracker.attach_block(blkid, block.header())?; | ||
if new_tip { | ||
debug!(?blkid, "created new pending chain tip"); | ||
} | ||
|
||
let best_block = pick_best_block( | ||
&cur_tip, | ||
state.chain_tracker.chain_tips_iter(), | ||
state.database.as_ref(), | ||
)?; | ||
|
||
// Figure out what our job is now. | ||
let depth = 100; // TODO change this | ||
let reorg = reorg::compute_reorg(&cur_tip, best_block, depth, &state.chain_tracker) | ||
.ok_or(Error::UnableToFindReorg(cur_tip, *best_block))?; | ||
|
||
// TODO this shouldn't be called "reorg" here, make the types | ||
// context aware so that we know we're not doing anything abnormal | ||
// in the normal case | ||
|
||
// Insert the sync event and submit it to the executor. | ||
let ev = SyncEvent::NewTipBlock(*reorg.new_tip()); | ||
csm_ctl.submit_event(ev)?; | ||
|
||
// Apply the changes to our state. | ||
state.cur_best_block = *reorg.new_tip(); | ||
|
||
// TODO is there anything else we have to do here? | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Considers if the block is plausibly valid and if we should attach it to the | ||
/// pending unfinalized blocks tree. The block is assumed to already be | ||
/// structurally consistent. | ||
fn check_new_block<D: Database>( | ||
blkid: &L2BlockId, | ||
block: &L2Block, | ||
cstate: &ConsensusState, | ||
state: &mut ChainTipTrackerState<D>, | ||
) -> anyhow::Result<bool, Error> { | ||
let params = state.params.as_ref(); | ||
|
||
// Check that the block is correctly signed. | ||
let cred_ok = credential::check_block_credential(block.header(), cstate.chain_state(), params); | ||
if !cred_ok { | ||
warn!(?blkid, "block has invalid credential"); | ||
return Ok(false); | ||
} | ||
|
||
// Check that we haven't already marked the block as invalid. | ||
let l2prov = state.database.l2_provider(); | ||
if let Some(status) = l2prov.get_block_status(*blkid)? { | ||
if status == alpen_vertex_db::traits::BlockStatus::Invalid { | ||
warn!(?blkid, "rejecting block that fails EL validation"); | ||
return Ok(false); | ||
} | ||
} | ||
|
||
// TODO more stuff | ||
|
||
Ok(true) | ||
} | ||
|
||
/// Returns if we should switch to the new fork. This is dependent on our | ||
/// current tip and any of the competing forks. It's "sticky" in that it'll try | ||
/// to stay where we currently are unless there's a definitely-better fork. | ||
fn pick_best_block<'t, D: Database>( | ||
cur_tip: &'t L2BlockId, | ||
mut tips_iter: impl Iterator<Item = &'t L2BlockId>, | ||
database: &D, | ||
) -> Result<&'t L2BlockId, Error> { | ||
let l2prov = database.l2_provider(); | ||
|
||
let mut best_tip = cur_tip; | ||
let mut best_block = l2prov | ||
.get_block_data(*best_tip)? | ||
.ok_or(Error::MissingL2Block(*best_tip))?; | ||
|
||
// The implementation of this will only switch to a new tip if it's a higher | ||
// height than our current tip. We'll make this more sophisticated in the | ||
// future if we have a more sophisticated consensus protocol. | ||
while let Some(other_tip) = tips_iter.next() { | ||
if other_tip == cur_tip { | ||
continue; | ||
} | ||
|
||
let other_block = l2prov | ||
.get_block_data(*other_tip)? | ||
.ok_or(Error::MissingL2Block(*other_tip))?; | ||
|
||
let best_header = best_block.header(); | ||
let other_header = other_block.header(); | ||
|
||
if other_header.blockidx() > best_header.blockidx() { | ||
best_tip = other_tip; | ||
best_block = other_block; | ||
} | ||
} | ||
|
||
Ok(best_tip) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
//! Logic to check block credentials. | ||
|
||
use tracing::*; | ||
|
||
use alpen_vertex_primitives::{ | ||
block_credential::CredRule, | ||
buf::{Buf32, Buf64}, | ||
params::Params, | ||
}; | ||
use alpen_vertex_state::{block::L2BlockHeader, consensus::ConsensusChainState}; | ||
|
||
pub fn check_block_credential( | ||
header: &L2BlockHeader, | ||
cs: &ConsensusChainState, | ||
params: &Params, | ||
) -> bool { | ||
let sigcom = compute_header_sig_commitment(header); | ||
match ¶ms.rollup().cred_rule { | ||
CredRule::Unchecked => true, | ||
CredRule::SchnorrKey(pubkey) => verify_schnorr_sig(header.sig(), &sigcom, pubkey), | ||
} | ||
} | ||
|
||
fn compute_header_sig_commitment(header: &L2BlockHeader) -> Buf32 { | ||
// TODO implement this, just concat all the components together aside from | ||
// the sig, probably should be poseidon | ||
warn!("header commitment generation still unimplemented"); | ||
Buf32::from([0; 32]) | ||
} | ||
|
||
pub fn sign_schnorr_sig(msg: &Buf32, sk: &Buf32) -> Buf64 { | ||
warn!("block signature signing still unimplemented"); | ||
Buf64::from([0; 64]) | ||
} | ||
|
||
fn verify_schnorr_sig(sig: &Buf64, msg: &Buf32, pk: &Buf32) -> bool { | ||
// TODO implement signature verification | ||
warn!("block signature verification still unimplemented"); | ||
true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the function and args are quit abbreviated: what do you think of
process_chaintip_message(message: ChainTipMessage...)
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will make naming changes in another cleanup pass, there's also too many things that are "trackers".