Tendermint epoch transitions#6085
Conversation
|
It looks like @feynmanliang hasn'signed our Contributor License Agreement, yet.
You can read and sign our full Contributor License Agreement at the following URL: https://cla.parity.io Once you've signed, plesae reply to this thread with Many thanks, Parity Technologies CLA Bot |
|
[clabot:check] |
|
It looks like @feynmanliang signed our Contributor License Agreement. 👍 Many thanks, Parity Technologies CLA Bot |
|
many thanks for the contribution! |
|
@feynmanliang Thanks! It's almost there (and sorry for being a bit ambiguous about this), but there are just a couple things missing:
How to check the seal: For checking the seal of a header, take a look at the
|
| } | ||
|
|
||
| fn check_finality_proof(&self, proof: &[u8]) -> Option<Vec<H256>> { | ||
| panic!("Tendermint blocks have instant finality, so `check_finality_proof` should never get called.") |
There was a problem hiding this comment.
tendermint blocks do have instant finality, so the "proof" here would just be an encoded header (which we check for seal validity).
the way warp sync for PoA works is by providing a list of validator set changes along with proofs of finality of the change. A change is signalled, and the old validator set approves the signal by finalizing the block where the signal occurred. From this proved list of validator set handoffs we can get to the most recent finalized block with full security. check_finality_proof is the way we check that signals were finalized, so it can't panic.
There was a problem hiding this comment.
👍
To clarify, you're referring to https://github.com/paritytech/parity/blob/master/ethcore/src/snapshot/consensus/authority.rs?utf8=%E2%9C%93#L365?
There was a problem hiding this comment.
| let ref header_signatures_field = header.seal().get(2).ok_or(BlockError::InvalidSeal)?; | ||
| for rlp in UntrustedRlp::new(header_signatures_field).iter() { | ||
| let signature = rlp.as_val()?; // TODO: why is `rlp` have type ()? | ||
| signatures.insert(signature); |
There was a problem hiding this comment.
this should also ensure that
a) each signature is a valid signature of header.bare_hash()
b) the public key recovered from each signature corresponds to an address in self.subchain_validators
I would recommend making the hashset store only those Addresses as opposed to the signatures themselves.
There was a problem hiding this comment.
I am a little bit lost here. Looking at verify_block_family, it pattern matches on first converting the Header into either a Proposal or VoteStep. Then for Proposals it checks if the proposer is an authority in the parent block and is the proposer of the current proposal, and for VoteStep it does the counting of signatures.
Are we to assume here that the Header will always correspond to a VoteStep?
| match self.validators.epoch_set(first, self, signal_number, set_proof) { | ||
| Ok((list, _)) => { | ||
| // Tendermint blocks have instant finality, so no need to check finality proof | ||
| ConstructedVerifier::Trusted(Box::new(EpochVerifier { |
There was a problem hiding this comment.
should still return Unconfirmed if some block hash is returned by epoch_set, and finality proofs may be checked (but not here). The "proof" passed here may be invalid, so this is also something of a verification function under the consensus logic.
There was a problem hiding this comment.
👍 will add the logic for checking finality of the block hash returned by epoch_set.
Does something need to be done wrt checking validity of the "proof" passed here, or is it sufficient to have that happen at https://github.com/paritytech/parity/blob/master/ethcore/src/snapshot/consensus/authority.rs?utf8=%E2%9C%93#L199?
There was a problem hiding this comment.
it should decode correctly into the two portions (signal proof and finality proof), and each portion should be checked. signal proof is checked if epoch_set returns Ok, and finality_proof will be checked at that point long as we return ConstructedVerifier::Unconfirmed from epoch_verifier
There was a problem hiding this comment.
Is the finality proof for tendermint just ::rlp::encode(header)?
| let mut signatures = HashSet::new(); | ||
| let ref header_signatures_field = header.seal().get(2).ok_or(BlockError::InvalidSeal)?; | ||
| for rlp in UntrustedRlp::new(header_signatures_field).iter() { | ||
| let signature = rlp.as_val()?; // TODO: why is `rlp` have type ()? |
There was a problem hiding this comment.
i don't understand this comment. rlp has type UntrustedRlp
There was a problem hiding this comment.
My bad, this was left over from my debugging >.<
|
Some comments I just made are shown as outdated, but they are still relevant |
|
@rphmeier thank you for your comments and helping me through this! |
| use spec::CommonParams; | ||
| use engines::{Engine, Seal, EngineError}; | ||
| use engines::{Engine, Seal, EngineError, ConstructedVerifier}; | ||
| use engines::authority_round::{combine_proofs, destructure_proofs}; |
There was a problem hiding this comment.
could the tendermint module get its own versions of these functions (as the engines will be split into seperate crates in the future)?
| } | ||
|
|
||
| fn destructure_proofs(combined: &[u8]) -> Result<(BlockNumber, &[u8], &[u8]), Error> { | ||
| pub fn destructure_proofs(combined: &[u8]) -> Result<(BlockNumber, &[u8], &[u8]), Error> { |
There was a problem hiding this comment.
if these are kept private it gives us more freedom to upgrade their internal behavior
|
LGTM except for |
|
Copied |
| } | ||
|
|
||
| let n = addresses.len(); | ||
| let threshold = self.subchain_validators.len() * 2/3; |
There was a problem hiding this comment.
Should this be a majority (i.e. 1/2) or 2/3 (for Byzantine fault tolerance)
|
|
||
| let n = addresses.len(); | ||
| let threshold = self.subchain_validators.len() * 2/3; | ||
| if n > threshold { |
There was a problem hiding this comment.
Does it matter if it's ">" or ">="? I'd imagine in the case of three subchain_validators we'd want verification to pass when we get 2 of them
There was a problem hiding this comment.
should always be >. In the case of 3 subchain validators you would actually need all 3 to be honest. With an amount of byzantine players f, we have to have at least 3f + 1 players total for safety.
|
One way you could test is by making |
|
@rphmeier I added a |
|
|
||
| struct EpochVerifier { | ||
| subchain_validators: SimpleList, | ||
| recover: Box<Fn(&Signature, &Message) -> Result<Address, Error> + Send + Sync>, |
There was a problem hiding this comment.
Can we make this a generic parameter instead or even one thing in cfg(test) and another in cfg(not(test))? Doing several virtual calls per header verification will probably be a performance hit we don't have to take during ancient block download after warp sync.
There was a problem hiding this comment.
I think we might need a virtual here if we want to have the signatures used in the mock recover to be grabbed from the environment. The alternative would be to hard code those addresses (which should remain fairly stable as long as res/tendermint.json isn't modified) and signatures (which will change whenever vote_info.sha3() or tap.sign change). I'll push a commit for this and see what you think
|
|
||
| struct EpochVerifier { | ||
| subchain_validators: SimpleList, | ||
| recover: Box<Fn(&Signature, &Message) -> Result<Address, Error> + Send + Sync>, |
There was a problem hiding this comment.
Try
struct EpochVerifier<F>
where F: Fn(&Signature, &Message) -> Result<Address, Error> + Send + Sync
{
subchain_validators: SimpleList,
recover: F,
}and just don't box the closures when you instantiate EpochVerifier.
|
|
||
| struct EpochVerifier { | ||
| struct EpochVerifier<F> | ||
| where F: Fn(&Signature, &Message) -> Result<Address, Error> + Send + Sync { |
There was a problem hiding this comment.
style: brace on next line, unindented
There was a problem hiding this comment.
also the where clause should be indented one level, not two
|
|
||
| impl super::EpochVerifier for EpochVerifier { | ||
| impl <F> super::EpochVerifier for EpochVerifier<F> | ||
| where F: Fn(&Signature, &Message) -> Result<Address, Error> + Send + Sync { |
There was a problem hiding this comment.
style: brace on next line, unindented
|
LGTM, just will ask for another quick look-over by @keorn |
|
@keorn ? |
Resolves #6046