Skip to content
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

Add additional BlockCommit validation for Append() #2541

Closed
greymistcube opened this issue Nov 10, 2022 · 3 comments
Closed

Add additional BlockCommit validation for Append() #2541

greymistcube opened this issue Nov 10, 2022 · 3 comments
Assignees
Labels
pbft Related to PBFT consensus

Comments

@greymistcube
Copy link
Contributor

greymistcube commented Nov 10, 2022

Related to #2504.

Premise

I think we were thinking about the issue in a wrong way. Although we have implemented Block<T>.LastCommit validation, we are missing the entire other half for validating the BlockCommit as a proof that a consensus has been reached for the Block<T> we are trying to append. Said BlockCommit is different from Block<T>.LastCommit as it will reference the Block<T> itself, not the Block<T>'s previous Block<T>.

Explanation

Any PBFT Block<T> that is not a genesis Block<T> should require a BlockCommit to be present as an evidence that a consensus has been reached to include the said Block<T> (albeit not included in the Block<T> as data) in order to be appended to BlockChain<T>. It is important to differentiate the purpose of this BlockCommit from Block<T>.LastCommit. If Block<T> has an index of n, then Block<T>.LastCommit with height of n - 1 "signals" that whoever proposed the Block<T> had the right to propose Block<T> in the sense that the validator has observed all the evidence to actually append the previous Block<T> of index n - 1. This, however, does not mean the Block<T> in question, of index n, is in any way valid, i.e. that a consensus has been reached, for it to be appended to a BlockChain<T>. It just so happens Swarm<T> propagates any Block<T>'s in its own BlockChain<T>.

Any node observing Block<T>, either through Swarm<T> or Context, must do its due diligence by whether it can observe/gather a BlockCommit of height n that is valid. If it is through Swarm<T>, this can be observed/hijacked from a Block<T> of index n + 1 which would have a LastCommit field of height n. This means, when syncing Block<T>s through a Swarm<T>, it should never be possible to completely sync up with a peer's BlockChain<T>.Tip, but only up to the penultimate Block<T>.

Additionally, this is possible since for Block<T> of index n, the set of validators can be derived for height n as all the necessary state transitions must have taken place by having a BlockChain<T> of height n - 1. This allows to verify and validate the LastCommit of height n, i.e. the one attached to a Block<T> of index n + 1.

Remedy

A starting point would be to add an additional parameter for BlockChain<T>.Append() that is of type BlockCommit and prevent appending a Block<T> if a valid BlockCommit is not provided (except for the genesis Block<T>1).

Notes

By some unforeseen fault, either by missing ConsensusMsgs due to some network failure or by receiving invalid ConsensusMsgs due to an activity of a malicious node, a node's Context might become "out-of-sync" with the rest of the network. As long as it is below tolerance, i.e. 1/3 of the total power of the network, it seems the network should recover on its own. Even when there was a race condition between the Swarm<T> and a Context, it seems this wasn't really much of a problem (other than there being an obvious security hole where a malicious actor could've forced a fork on an honest node since Swarm<T> currently bypasses the consensus check). With the updated approach provided here, Context would generally take precedence to Swarm<T>. Consider the following thought experiment.

As-is scenario:

  • A local node is running a Context of height n (i.e. its BlockChain<T>.Tip.Index is n - 1).
  • There is some fault and the node's Context is stuck at n.
  • Swarm<T> forcibly syncs its BlockChain<T>.Tip to a known peer of height n (although it shouldn't as explained above).
  • Running Context is discarded and a new Context of height n + 1 gets started.
    • At this point, the network's Context is on height n + 1.
  • Since there is no BlockCommit for height n to provide as LastCommit of Block<T> of index n + 1, the node is incapable of proposing a Block<T> for a Context of height n + 12.
  • Once a consensus is reached for Context of height n + 1, Context of height n + 2 should start normally.

To-be scenario:

  • A local node is running a Context of height n (i.e. its BlockChain<T>.Tip.Index is n - 1).
  • There is some fault and the node's Context is stuck at n.
  • Swarm<T> forcibly syncs its BlockChain<T>.Tip to a known peer of height n + 1.
  • Running Context is discarded and a new Context of height n + 1 gets started.
    • At this point, the network's Context3 is on height n + 2.
  • Although there is a BlockCommit for height n to provide as LastCommit of Block<T> of index n + 1, and the node is free to propose a Block<T> of index n + 1, it will be ignored.
    • As other honest nodes are on height n + 2, the node will not get the votes for its proposed Block<T>.
  • As the node has cached ConsensusMsgs (assuming there were no fault during network's Context of height n + 1 phase), the node will quickly reach the same consensus as other nodes (even if it hasn't participated in it) for height n + 1.
  • Depending on when the node join's the network's Context for height n + 2, although the node might've missed several rounds, the rest should progress normally onwards. This means, the node Context will become normal by the time Context for height n + 3 starts.

Footnotes

  1. Note that this requirement is slightly different from whether LastCommit should or should not be null for a PBFT Block<T>. For example, a PBFT Block<T> of index 1 should be provided with a BlockCommit during the BlockChain<T>.Append() call, but the Block<T> itself would be missing a LastCommit, i.e. it'll be null.

  2. This is not entirely true, as the node can propose under certain conditions by "copying" the Block<T> proposed by someone else.

  3. I am using this term very loosely. 😗

@colibrishin
Copy link
Contributor

colibrishin commented Nov 10, 2022

A starting point would be to add an additional parameter for BlockChain.Append() that is of type BlockCommit and prevent appending a Block if a valid BlockCommit is not provided (except for the genesis Block1).

Please help me out by having a clear view of this. Let's say someone is trying to "forge" a block of index ${n}$ that is committed by Context<T> in height ${n}$.

  • Fresh voted block ${B_{committed}}$, which has the index ${n}$, will be committed by a majority of nodes, Context<T> of height ${n}$.

  • Creates a new block ${B_{{\sim}committed}}$, which has the same index as ${B_{committed}}$, before the commitment in Context<T> and broadcasts a Block<T> through Swarm<T>.1

  • A node does not check whether a block is committed by consensus, so it will append any block that comes first. this means ${B_{{\sim}committed}}$ can be appended into BlockChain<T>. This could lead to network partition.12


  • A Block<T> should be voted on and committed by consensus monotonically, and any Block<T> that meets these conditions can be appended into BlockChain<T>.

  • We can trust any Block<T> committed by Context<T> because we did join and observe3 the consensus of given Block<T>.

  • We cannot trust any Block<T> from Swarm<T> until the information about a consensus of given Block<T> is available.41

  • A BlockCommit of height ${n}$ is required before the Append() block of height ${n}$, because a node is not sure whether a block of index ${n}$ is voted by consensus.

Am I following correctly?

Footnotes

  1. This is separate from the validity of a block, more like "cut in a line" the consensus. 2 3

  2. Plus, in the current state of synchronization, even the validator can be affected by this. ${B_{{\sim}committed}}$ that is different Block<T> with currently committing/voting Block<T> ${B_{comitted}}$ in Context<T> can be appended into BlockChain<T> through Swarm<T> before the commitment of a block by Context<T>.

  3. is that we have seen that the Block<T> has been voted by +2/3 majority.

  4. is that we don't have enough information to determine whether the block is committed by +2/3 majority.

@greymistcube greymistcube added the pbft Related to PBFT consensus label Nov 11, 2022
@greymistcube
Copy link
Contributor Author

Am I following correctly?

That's the gist of it. 😗

@greymistcube
Copy link
Contributor Author

There were some changes made to the plan while implementing. The main difference being Swarm<T>'s block syncing can interrupt Context of height n using a fetched BlockCommit from a peer node. We are moving forward with the original "as-is" scenario with the addition of retrieving a BlockCommit for every Block<T> downloaded and validation of a BlockCommit when calling Append().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pbft Related to PBFT consensus
Projects
Status: Done
Development

No branches or pull requests

2 participants