Skip to content

Byzantine tests

Yury edited this page Dec 9, 2021 · 16 revisions

Notation

  • Leader. Generator with top rank, that should produce notarized block if honest
  • Replica. Node that is not generator in current round
  • Adversarial. Node that don't follow the rules
  • Proposal. Block that sent by generator for verification
  • HNB. Highest notarized block
  • T0. Start time
  • δ. Network latency and time needed to create and process message.
  • Δ. Time needed to collect proposals for verification

Attack round transfer

//Attack VRF and ``GetBlockToExtend()``. If adversarial can force network to create VRF, it will force generators to propose. To propose, they will need ``NotarizedBlock`` from previous rounds, if they have none, they should query miners. Miners won’t have any and won’t answer. (Peter hardcoded check for HNB==nil -> return, seems like it can prevent proposing at all, since it is called in computeRoundRandomSeed). Note: it takes at least 2δ + Δ to transfer to new round via (VRF->RRS->Propose->Notarization)
(T0) Replica_0(Adv): send VRF_Share0_0 #normal send
(T0) Replica_0(Adv): send VRF_Share1_0
(T0 + δ) Replica_i: send VRF_Share1_i
(T0 + 2δ) Replica_i: create RRS_1
(T0 + 2δ) Leader_j: Propose Block0_j, query HNB_0
(T0 + 2δ) Replica_0(Adv): send VRF_Share2_0
(T0 + 3δ) Replica_i: send VRF_Share2_i
(T0 + 4δ) Replica_i: create RRS_2
(T0 + 4δ) Leader_j: Propose Block2_j, query HNB_1
//Protocol tells to verify all messages on the same rank, alas we don’t follow protocol and vote for only one top ranked block.
//Propose different block for every replica, this round should achieve notarization (with different block equality proof, not implemented yet)
(T0) Leader_0(Adv): send Proposal0_i to Replica_i i= notarization_threshold
(T0 + δ + Δ) Replica_i: send VerificationTicket0_i 
//check no depletion, all first j leaders are adversarial, they send different blocks to different replicas but not enough for notarization
(T0) Leader_0(Adv): send Proposalj_i to Replica_i, j= f , 0 < i < notarization_threshold
(T0 + δ + Δ) Replica_i: send VerificationTicket0_i 
//check that proposer can’t break single block 
(T0) Leader_0:  send Proposal0_0
(T0 + δ + Δ) Replica_i: send Verification_0
(T0 + 2δ + Δ) Leader_0: send Proposal0_1
(T0 + 3δ + Δ) Replica_i: send Notarization0_0 #ignore Proposal0_1
//check make progress for time out leader
(T0) Leader_0:  hangs
(T0) Leader_1:  send Proposal0_1
(T0 + δ + Δ) Replica_i: send Verification0_1 
//check make progress for adversarial leader
(T0) Leader_0 (ad):  send Proposal0_0 for replica j , 0 <= j <1/3f
(T0) Leader_1:  send Proposal0_1
(T0 + δ + Δ) Replica_i: send Verification0_1 
//check make no progress for adversarial leader
(T0) Leader_0 (ad):  send Proposal0_0 for replica j , 0 <= j <1/2f
(T0) Leader_1:  send Proposal0_1
(T0 + δ + Δ) Replica_i: timeout
//No deterministic finality for f
(T0) Leader_0: send Proposal0_0 Proposal0_1 
(T0 + δ + Δ) Replica_i: send Verification0_0 Verification0_1
(T0 + 3δ + Δ) Leader_1: Proposal1_0(block0_0)  Proposal1_1(block0_1)
(T0 + 3δ + 2Δ) Replica_i: send Verification1_0 Verification1_1
. . . (f)
//Leader extends not notarized prev_block
(T0) Leader_0: send Proposal0_0, extends not notarized prev_block
(T0) Leader_1: send Proposal1_0
(T0 + δ) Replica_0: reject Proposal0_, verify Proposal1_0
//Verify not existent block, this should work fine, since we only formally verify verification_tickets
(T0) Replica_0: send Verification0_X
(T0 + δ) Replica_1: check and forget
//Notarize not existent block and fetch it
(T0) Replica_i: send Verification_i_X  0<i<=2f/3 + 1
(T0 + δ) Replica_1: fetch Block_i
(T0 + 2*δ) Replica_1: send Notarization_i
//Ignore proposal for 5 round in the future round, ignore it since not_ahead = 5, then receive lfb and start round
(T0) Leader_0: send Notarization0_6
(T0 + δ) Replica_0: ignore Notarization0_6
(T0 + 2*δ) Sharder0: send LFB5_i to Replica_0
T0 + 3*δ) Replica_0: send VRFShare0_7

Test restart

//Half of nodes are down, generator proposes but can't get enough verification tickets, restarts. Soon half of nodes get up and restarts round.
(T0) Replica_i: down, 0<=i<1/2f
(T0) Leader0_0: send Proposal0_0
(T0 + δ + Δ) Replica_0: send VerificationTicket0_0
(T0 + timeout) Replica_j: send VRFShare(timeout), 1/2f<j<=f
(T0 + 4*timeout) Replica_i: get up, send VRFShare(timeout), 0<=i<1/2f
(T0 + 4*timeout + δ): Leader0_1: send Proposal0_1
//Malicious replicas start to send VRFShares for current round as if they get up after restart
(T0) Leader_0: send Proposal0_0
(T0 + δ) Replica_i(Adv): send VRFShare(timeout), 0<=i<1/3f
(T0 + δ + Δ) Replica_j: send VerificationTicket0_j 

Resend past messages

//Miner sends previous proposed block used in notarized round with one/several blocks notarized in it to propose in new round, rejected (already processed)
(T0) Leader_0: send Proposal0_0, Proposal0_1
(T0 + δ + Δ) Replica_j: send VerificationTicket0_j, VerificationTicket1_j  
(T0 + 2δ + Δ) Replica_j: send Notarization0_0, Notarization0_1 
(T0 + 3δ + Δ) Leader_1: send Proposal0_0
(T0 + 4δ + Δ) Replica_j: reject Proposal0_0
//Miner sends previous notarization used in notarized/finalized round in new round, rejected (already processed)
(T0) Replica_j: obtain Notarization0_0
(T1) Leader_1: send Proposal1_0
(T1 + δ + Δ) Replica_j: send VerificationTicket1_j  
(T1 + 2δ + Δ) Replica_j: send Notarization1_1 
(T1 + 3δ + Δ) Replica_j: reject Notarization0_0
//Miner resends notarized block to sharders, it is rejected (already processed)
(T0) Leader_1: send Proposal1_0, Proposal1_1
(T0 + δ + Δ) Replica_j: send VerificationTicket0_j, VerificationTicket1_j  
(T0 + 2δ + Δ) Replica_j: send Notarization0_0, Notarization1_1 
(T0 + 3δ + Δ) Replica_j: send NotarizedBlock0_0
(T1) Replica_j: send NotarizedBlock0_0

Attack fetching

requestor entity test
SetupX2MRequestors
MinerNotarizedBlockRequestor *block.Block forged, withheld, another
BlockStateChangeRequestor *block.StateChange forged, withheld, another
PartialStateRequestor *state.PartialState forged, withheld, another
StateNodesRequestor *state.Nodes forged, withheld, another
SetupX2SRequestors
LatestFinalizedMagicBlockRequestor *block.Block forged, withheld, another
FBRequestor *block.Block forged, withheld, another
SetupM2MRequestors
DKGShareSender *bls.DKGKeyShare forged, withheld, another
ChainStartSender *StartChain forged, withheld, another
SetupM2SRequestors
MinerLatestFinalizedBlockRequestor *block.Block forged, withheld, another
SetupS2SRequestors
RoundRequestor *round.Round forged, withheld, another
BlockRequestor *block.Block forged, withheld, another
BlockSummaryRequestor *block.BlockSummary forged, withheld, another
RoundSummariesRequestor *RoundSummaries forged, withheld, another
BlockSummariesRequestor *BlockSummaries forged, withheld, another
//fetching q peers is executed in similar way, q is 1/10f
(T0) Replica_0: query Replica_i/Sharder_i, 1<= i< q
(T0 + δ) Replica_i(adv): send data (forged, withheld, another)

Attack delayed (woken) proposer

//Ignore proposal when missed VRF phase and don't have RRS (can be future proposal)
(T0) Leader_0: send Proposal0_0
(T0 + δ) Replica_0(missed VRF): ignore Proposal0_0
//Collect verification when missed VRF phase and don't have RRS
(T0) Replica_i: send Verification0_i
(T0 + δ) Replica_0(missed VRF): collect Verification0_i, notarize
//Process notarization when missed VRF phase and don't have RRS
(T0) Replica_i: send Notarization0_i
(T0 + δ) Replica_0(missed VRF): process Notarization0_i, fetch block0_0

Dynamic attacks

Attack on broascast

One of attacks that inspired by technic described this post. If malicious node can connect honest ones through itself (see pic 1) and delay connection between them it is possible for him to make very tough to handle attacks. pic 1

We should keep in mind such attacks for the future, at first phase they are not easy to execute at all