Skip to content

Conversation

@AshwinSekar
Copy link
Contributor

@AshwinSekar AshwinSekar commented Jan 22, 2026

Problem

We need to verify the slow / fast finalization certificates from the block footer and ingest it into our consensus pool.
Ingestion here is key for unstaked nodes.

Summary of Changes

  • rename HighestFinalizedSlotCert -> BlockFinalization since this is used both in the consensus pool to track highest finalized block and for extraction of the footer
  • removed the Arc from BlockFinalization, copying should be cheap enough ~ once every 400ms
  • refactor some functions to allow for code reuse in verification w/ the genesis certificate
  • reorganize the limit map so it's accessible by block component processor
  • send the verified certs to consensus pool

@AshwinSekar AshwinSekar force-pushed the finalization-footer branch 2 times, most recently from a1a080a to 9f437fb Compare January 22, 2026 02:36
@AshwinSekar AshwinSekar marked this pull request as draft January 22, 2026 02:44
bank: Arc<Bank>,
parent_bank: Arc<Bank>,
marker: VersionedBlockMarker,
finalization_cert_sender: Option<&Sender<ConsensusMessage>>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is optional (similar to replay_vote_sender in TowerBFT) because we don't need it for startup blocks

/// `VoteType`s that contribute to this certificate.
///
/// Must be in sync with `Vote::to_cert_types`
pub const fn limits_and_vote_types(&self) -> (Fraction, &'static [VoteType]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just moved these over from votor::common to here

pub enum HighestFinalizedSlotCert {
/// Standard finalization: requires both a Finalize cert and a Notarize cert for the same slot
#[allow(clippy::large_enum_variant)]
pub enum BlockFinalization {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open to naming suggestions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be tempted to to add a Cert suffix otherwise this is definitely an improvement.

@AshwinSekar AshwinSekar requested review from akhi3030 and ksn6 January 22, 2026 13:41
@AshwinSekar AshwinSekar marked this pull request as ready for review January 22, 2026 13:41
Copy link
Contributor

@akhi3030 akhi3030 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good. Minor comments really. It would've been easier to review if the PR was done in parts e.g. removal of Arc and renaming, etc. being done separately.


/// Converts the block marker representation of a finalization certificate into verification friendly
/// representation of Slow or Fast finalization certificates
pub fn to_certificates(self) -> Result<BlockFinalization, BlsError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this fails, we will get a generic bls error that will not explain which verification failed. Might make things hard to debug from logs.


let final_certs = final_cert
.to_certificates()
.map_err(|_| BlockComponentProcessorError::InvalidFinalizationCertificate)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we return a more informed error here then this might have to be updated. I think just printing out the error in the warn would suffice.


/// Converts the block marker representation of a finalization certificate into verification friendly
/// representation of Slow or Fast finalization certificates
pub fn to_certificates(self) -> Result<BlockFinalization, BlsError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since we are consuming self, the function should be called into_....

.to_certificates()
.map_err(|_| BlockComponentProcessorError::InvalidFinalizationCertificate)?;

match &final_certs {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I wonder if this verification can be moved into to_certificates() so that we cannot even construct BlockFinalization unless all the checks pass and then we do not have to worry about whether or not such checks were performed when we are using a object of type BlockFinalization. Bonus points for using a new type e.g. ValidatedBlockFinalization 😄

finalize_cert,
notarize_cert,
} => {
let _ = sender.send(ConsensusMessage::from(notarize_cert));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we print a warn if this fails to help with debugging? If someone changes the unbounded or the receiver is disconnected, we would at least capture some logs.

pub enum HighestFinalizedSlotCert {
/// Standard finalization: requires both a Finalize cert and a Notarize cert for the same slot
#[allow(clippy::large_enum_variant)]
pub enum BlockFinalization {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be tempted to to add a Cert suffix otherwise this is definitely an improvement.

pub fn slot(&self) -> Slot {
match self {
HighestFinalizedSlotCert::Finalize {
BlockFinalization::Finalize {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
BlockFinalization::Finalize {
Self::Finalize {

So we do not have to update this in case of renaming.

@AshwinSekar AshwinSekar marked this pull request as draft January 23, 2026 18:04
@AshwinSekar AshwinSekar force-pushed the finalization-footer branch 3 times, most recently from 656763a to 2a2de5e Compare January 23, 2026 18:27
@AshwinSekar
Copy link
Contributor Author

I wonder if this verification can be moved into to_certificates() so that we cannot even construct BlockFinalization unless all the checks pass and then we do not have to worry about whether or not such checks were performed when we are using a object of type BlockFinalization. Bonus points for using a new type e.g. ValidatedBlockFinalization 😄

Excellent suggestion, this makes the code a lot easier to look at and lets me encapsulate all the deserialize and verification logic.

Should be good for another look, and apologies for how long this PR is, it got out of hand as I was writing it.

@AshwinSekar AshwinSekar marked this pull request as ready for review January 23, 2026 20:37
Copy link
Contributor

@akhi3030 akhi3030 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, looks much better. Just a couple of small comments now.

Comment on lines 44 to 45
/// This type can only be constructed through validation methods, providing
/// compile-time guarantees that the contained certificates are valid.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, since this is an enum, this can be constructed directly. E.g. the compiler will let us just do let foo = ValidatedBlockFinalizationCert::FastFinalize(...);.

The way to get this guarantee is to turn this into a struct. Maybe we can define it as:

struct ValidatedBlockFinalizationCert {
  finalize_cert: Certificate,
  notar_cert: Option<Certificate>,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a really good point, I do like keeping the enum instead of having the option. I hid it in an inner type here c903a11 seems like an idiomatic approach

}
}

fn verify_certificate(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add some doc comments to explain what is being returned in the u64s please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done 7e0b411

/// - BLS signature conversion fails
/// - Certificate signature verification fails
/// - The certificates don't meet the required stake thresholds
pub fn try_from_footer(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is fine to defer it for now but we should add some unit tests to ensure that this function does fail for invalid certs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I did have some basic unit tests but forgot to move them over, done here 7acac02

"Received a slow finalization in the footer for slot {slot} block_id \
{block_id:?} in bank slot {} with notarize {notarize_percent} stake \
expecting at least {notarize_threshold}",
bank.slot()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we take the slot from the footer instead of the bank here? Given the call tree, they should match up but it isn't clear in this function that that will always be the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the footer itself doesn't have a slot.
Here we log both the finalization cert slot and the bank slot.


BlockComponentProcessor::update_bank_with_footer(
working_bank.bank.clone_without_scheduler(),
&working_bank.bank.clone_without_scheduler(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we are taking a reference to a cloned object. Could this just be &working_bank.bank?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch 1c4c67d

Copy link
Contributor

@akhi3030 akhi3030 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants