* the working attackers isolation spec, needs more comments * the TLA+ spec of the attackers isolationpull/7804/head
@ -0,0 +1,166 @@ | |||
------------------------ MODULE Blockchain_003_draft ----------------------------- | |||
(* | |||
This is a high-level specification of Tendermint blockchain | |||
that is designed specifically for the light client. | |||
Validators have the voting power of one. If you like to model various | |||
voting powers, introduce multiple copies of the same validator | |||
(do not forget to give them unique names though). | |||
*) | |||
EXTENDS Integers, FiniteSets, Apalache | |||
Min(a, b) == IF a < b THEN a ELSE b | |||
CONSTANT | |||
AllNodes, | |||
(* a set of all nodes that can act as validators (correct and faulty) *) | |||
ULTIMATE_HEIGHT, | |||
(* a maximal height that can be ever reached (modelling artifact) *) | |||
TRUSTING_PERIOD | |||
(* the period within which the validators are trusted *) | |||
Heights == 1..ULTIMATE_HEIGHT (* possible heights *) | |||
(* A commit is just a set of nodes who have committed the block *) | |||
Commits == SUBSET AllNodes | |||
(* The set of all block headers that can be on the blockchain. | |||
This is a simplified version of the Block data structure in the actual implementation. *) | |||
BlockHeaders == [ | |||
height: Heights, | |||
\* the block height | |||
time: Int, | |||
\* the block timestamp in some integer units | |||
lastCommit: Commits, | |||
\* the nodes who have voted on the previous block, the set itself instead of a hash | |||
(* in the implementation, only the hashes of V and NextV are stored in a block, | |||
as V and NextV are stored in the application state *) | |||
VS: SUBSET AllNodes, | |||
\* the validators of this bloc. We store the validators instead of the hash. | |||
NextVS: SUBSET AllNodes | |||
\* the validators of the next block. We store the next validators instead of the hash. | |||
] | |||
(* A signed header is just a header together with a set of commits *) | |||
LightBlocks == [header: BlockHeaders, Commits: Commits] | |||
VARIABLES | |||
refClock, | |||
(* the current global time in integer units as perceived by the reference chain *) | |||
blockchain, | |||
(* A sequence of BlockHeaders, which gives us a bird view of the blockchain. *) | |||
Faulty | |||
(* A set of faulty nodes, which can act as validators. We assume that the set | |||
of faulty processes is non-decreasing. If a process has recovered, it should | |||
connect using a different id. *) | |||
(* all variables, to be used with UNCHANGED *) | |||
vars == <<refClock, blockchain, Faulty>> | |||
(* The set of all correct nodes in a state *) | |||
Corr == AllNodes \ Faulty | |||
(* APALACHE annotations *) | |||
a <: b == a \* type annotation | |||
NT == STRING | |||
NodeSet(S) == S <: {NT} | |||
EmptyNodeSet == NodeSet({}) | |||
BT == [height |-> Int, time |-> Int, lastCommit |-> {NT}, VS |-> {NT}, NextVS |-> {NT}] | |||
LBT == [header |-> BT, Commits |-> {NT}] | |||
(* end of APALACHE annotations *) | |||
(****************************** BLOCKCHAIN ************************************) | |||
(* the header is still within the trusting period *) | |||
InTrustingPeriod(header) == | |||
refClock < header.time + TRUSTING_PERIOD | |||
(* | |||
Given a function pVotingPower \in D -> Powers for some D \subseteq AllNodes | |||
and pNodes \subseteq D, test whether the set pNodes \subseteq AllNodes has | |||
more than 2/3 of voting power among the nodes in D. | |||
*) | |||
TwoThirds(pVS, pNodes) == | |||
LET TP == Cardinality(pVS) | |||
SP == Cardinality(pVS \intersect pNodes) | |||
IN | |||
3 * SP > 2 * TP \* when thinking in real numbers, not integers: SP > 2.0 / 3.0 * TP | |||
(* | |||
Given a set of FaultyNodes, test whether the voting power of the correct nodes in D | |||
is more than 2/3 of the voting power of the faulty nodes in D. | |||
Parameters: | |||
- pFaultyNodes is a set of nodes that are considered faulty | |||
- pVS is a set of all validators, maybe including Faulty, intersecting with it, etc. | |||
- pMaxFaultRatio is a pair <<a, b>> that limits the ratio a / b of the faulty | |||
validators from above (exclusive) | |||
*) | |||
FaultyValidatorsFewerThan(pFaultyNodes, pVS, maxRatio) == | |||
LET FN == pFaultyNodes \intersect pVS \* faulty nodes in pNodes | |||
CN == pVS \ pFaultyNodes \* correct nodes in pNodes | |||
CP == Cardinality(CN) \* power of the correct nodes | |||
FP == Cardinality(FN) \* power of the faulty nodes | |||
IN | |||
\* CP + FP = TP is the total voting power | |||
LET TP == CP + FP IN | |||
FP * maxRatio[2] < TP * maxRatio[1] | |||
(* Can a block be produced by a correct peer, or an authenticated Byzantine peer *) | |||
IsLightBlockAllowedByDigitalSignatures(ht, block) == | |||
\/ block.header = blockchain[ht] \* signed by correct and faulty (maybe) | |||
\/ /\ block.Commits \subseteq Faulty | |||
/\ block.header.height = ht | |||
/\ block.header.time >= 0 \* signed only by faulty | |||
(* | |||
Initialize the blockchain to the ultimate height right in the initial states. | |||
We pick the faulty validators statically, but that should not affect the light client. | |||
Parameters: | |||
- pMaxFaultyRatioExclusive is a pair <<a, b>> that bound the number of | |||
faulty validators in each block by the ratio a / b (exclusive) | |||
*) | |||
InitToHeight(pMaxFaultyRatioExclusive) == | |||
/\ \E Nodes \in SUBSET AllNodes: | |||
Faulty := Nodes \* pick a subset of nodes to be faulty | |||
\* pick the validator sets and last commits | |||
/\ \E vs, lastCommit \in [Heights -> SUBSET AllNodes]: | |||
\E timestamp \in [Heights -> Int]: | |||
\* refClock is at least as early as the timestamp in the last block | |||
/\ \E tm \in Int: | |||
refClock := tm /\ tm >= timestamp[ULTIMATE_HEIGHT] | |||
\* the genesis starts on day 1 | |||
/\ timestamp[1] = 1 | |||
/\ vs[1] = AllNodes | |||
/\ lastCommit[1] = EmptyNodeSet | |||
/\ \A h \in Heights \ {1}: | |||
/\ lastCommit[h] \subseteq vs[h - 1] \* the non-validators cannot commit | |||
/\ TwoThirds(vs[h - 1], lastCommit[h]) \* the commit has >2/3 of validator votes | |||
\* the faulty validators have the power below the threshold | |||
/\ FaultyValidatorsFewerThan(Faulty, vs[h], pMaxFaultyRatioExclusive) | |||
/\ timestamp[h] > timestamp[h - 1] \* the time grows monotonically | |||
/\ timestamp[h] < timestamp[h - 1] + TRUSTING_PERIOD \* but not too fast | |||
\* form the block chain out of validator sets and commits (this makes apalache faster) | |||
/\ blockchain := [h \in Heights |-> | |||
[height |-> h, | |||
time |-> timestamp[h], | |||
VS |-> vs[h], | |||
NextVS |-> IF h < ULTIMATE_HEIGHT THEN vs[h + 1] ELSE AllNodes, | |||
lastCommit |-> lastCommit[h]] | |||
] \****** | |||
(********************* BLOCKCHAIN ACTIONS ********************************) | |||
(* | |||
Advance the clock by zero or more time units. | |||
*) | |||
AdvanceTime == | |||
/\ \E tm \in Int: tm >= refClock /\ refClock' = tm | |||
/\ UNCHANGED <<blockchain, Faulty>> | |||
============================================================================= | |||
\* Modification History | |||
\* Last modified Wed Jun 10 14:10:54 CEST 2020 by igor | |||
\* Created Fri Oct 11 15:45:11 CEST 2019 by igor |
@ -0,0 +1,159 @@ | |||
----------------------- MODULE Isolation_001_draft ---------------------------- | |||
(** | |||
* The specification of the attackers isolation at full node, | |||
* when it has received an evidence from the light client. | |||
* We check that the isolation spec produces a set of validators | |||
* that have more than 1/3 of the voting power. | |||
* | |||
* It follows the English specification: | |||
* | |||
* https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/attacks/isolate-attackers_001_draft.md | |||
* | |||
* The assumptions made in this specification: | |||
* | |||
* - the voting power of every validator is 1 | |||
* (add more validators, if you need more validators) | |||
* | |||
* - Tendermint security model is violated | |||
* (there are Byzantine validators who signed a conflicting block) | |||
* | |||
* Igor Konnov, Zarko Milosevic, Josef Widder, Informal Systems, 2020 | |||
*) | |||
EXTENDS Integers, FiniteSets, Apalache | |||
\* algorithm parameters | |||
CONSTANTS | |||
AllNodes, | |||
(* a set of all nodes that can act as validators (correct and faulty) *) | |||
COMMON_HEIGHT, | |||
(* an index of the block header that two peers agree upon *) | |||
CONFLICT_HEIGHT, | |||
(* an index of the block header that two peers disagree upon *) | |||
TRUSTING_PERIOD, | |||
(* the period within which the validators are trusted *) | |||
FAULTY_RATIO | |||
(* a pair <<a, b>> that limits that ratio of faulty validator in the blockchain | |||
from above (exclusive). Tendermint security model prescribes 1 / 3. *) | |||
VARIABLES | |||
blockchain, (* the chain at the full node *) | |||
refClock, (* the reference clock at the full node *) | |||
Faulty, (* the set of faulty validators *) | |||
conflictingBlock, (* an evidence that two peers reported conflicting blocks *) | |||
state, (* the state of the attack isolation machine at the full node *) | |||
attackers (* the set of the identified attackers *) | |||
vars == <<blockchain, refClock, Faulty, conflictingBlock, state>> | |||
\* instantiate the chain at the full node | |||
ULTIMATE_HEIGHT == CONFLICT_HEIGHT + 1 | |||
BC == INSTANCE Blockchain_003_draft | |||
\* use the light client API | |||
TRUSTING_HEIGHT == COMMON_HEIGHT | |||
TARGET_HEIGHT == CONFLICT_HEIGHT | |||
LC == INSTANCE LCVerificationApi_003_draft | |||
WITH localClock <- refClock, REAL_CLOCK_DRIFT <- 0, CLOCK_DRIFT <- 0 | |||
\* old-style type annotations in apalache | |||
a <: b == a | |||
\* [LCAI-NONVALID-OUTPUT.1::TLA.1] | |||
ViolatesValidity(header1, header2) == | |||
\/ header1.VS /= header2.VS | |||
\/ header1.NextVS /= header2.NextVS | |||
\/ header1.height /= header2.height | |||
\/ header1.time /= header2.time | |||
(* The English specification also checks the fields that we do not have | |||
at this level of abstraction: | |||
- header1.ConsensusHash != header2.ConsensusHash or | |||
- header1.AppHash != header2.AppHash or | |||
- header1.LastResultsHash header2 != ev.LastResultsHash | |||
*) | |||
Init == | |||
/\ state := "init" | |||
\* Pick an arbitrary blockchain from 1 to COMMON_HEIGHT + 1. | |||
/\ BC!InitToHeight(FAULTY_RATIO) \* initializes blockchain, Faulty, and refClock | |||
/\ attackers := {} <: {STRING} \* attackers are unknown | |||
\* Receive an arbitrary evidence. | |||
\* Instantiate the light block fields one by one, | |||
\* to avoid combinatorial explosion of records. | |||
/\ \E time \in Int: | |||
\E VS, NextVS, lastCommit, Commits \in SUBSET AllNodes: | |||
LET conflicting == | |||
[ Commits |-> Commits, | |||
header |-> | |||
[height |-> CONFLICT_HEIGHT, | |||
time |-> time, | |||
VS |-> VS, | |||
NextVS |-> NextVS, | |||
lastCommit |-> lastCommit] ] | |||
IN | |||
LET refBlock == [ header |-> blockchain[COMMON_HEIGHT], | |||
Commits |-> blockchain[COMMON_HEIGHT + 1].lastCommit ] | |||
IN | |||
/\ "SUCCESS" = LC!ValidAndVerifiedUntimed(refBlock, conflicting) | |||
\* More than third of next validators in the common reference block | |||
\* is faulty. That is a precondition for a fork. | |||
/\ 3 * Cardinality(Faulty \intersect refBlock.header.NextVS) | |||
> Cardinality(refBlock.header.NextVS) | |||
\* correct validators cannot sign an invalid block | |||
/\ ViolatesValidity(conflicting.header, refBlock.header) | |||
=> conflicting.Commits \subseteq Faulty | |||
/\ conflictingBlock := conflicting | |||
\* This is a specification of isolateMisbehavingProcesses. | |||
\* | |||
\* [LCAI-FUNC-MAIN.1::TLA.1] | |||
Next == | |||
/\ state = "init" | |||
\* Extract the rounds from the reference block and the conflicting block. | |||
\* In this specification, we just pick rounds non-deterministically. | |||
\* The English specification calls RoundOf on the blocks. | |||
/\ \E referenceRound, evidenceRound \in Int: | |||
/\ referenceRound >= 0 /\ evidenceRound >= 0 | |||
/\ LET reference == blockchain[CONFLICT_HEIGHT] | |||
referenceCommit == blockchain[CONFLICT_HEIGHT + 1].lastCommit | |||
evidenceHeader == conflictingBlock.header | |||
evidenceCommit == conflictingBlock.Commits | |||
IN | |||
IF ViolatesValidity(reference, evidenceHeader) | |||
THEN /\ attackers' := blockchain[COMMON_HEIGHT].NextVS \intersect evidenceCommit | |||
/\ state' := "Lunatic" | |||
ELSE IF referenceRound = evidenceRound | |||
THEN /\ attackers' := referenceCommit \intersect evidenceCommit | |||
/\ state' := "Equivocation" | |||
ELSE | |||
\* This property is shown in property | |||
\* Accountability of TendermintAcc3.tla | |||
/\ state' := "Amnesia" | |||
/\ \E Attackers \in SUBSET (Faulty \intersect reference.VS): | |||
/\ 3 * Cardinality(Attackers) > Cardinality(reference.VS) | |||
/\ attackers' := Attackers | |||
/\ blockchain' := blockchain | |||
/\ refClock' := refClock | |||
/\ Faulty' := Faulty | |||
/\ conflictingBlock' := conflictingBlock | |||
(********************************** INVARIANTS *******************************) | |||
\* This invariant ensure that the attackers have | |||
\* more than 1/3 of the voting power | |||
\* | |||
\* [LCAI-INV-Output.1::TLA-DETECTION-COMPLETENESS.1] | |||
DetectionCompleteness == | |||
state /= "init" => | |||
3 * Cardinality(attackers) > Cardinality(blockchain[CONFLICT_HEIGHT].VS) | |||
\* This invariant ensures that only the faulty validators are detected | |||
\* | |||
\* [LCAI-INV-Output.1::TLA-DETECTION-ACCURACY.1] | |||
DetectionAccuracy == | |||
attackers \subseteq Faulty | |||
============================================================================== |
@ -0,0 +1,192 @@ | |||
-------------------- MODULE LCVerificationApi_003_draft -------------------------- | |||
(** | |||
* The common interface of the light client verification and detection. | |||
*) | |||
EXTENDS Integers, FiniteSets | |||
\* the parameters of Light Client | |||
CONSTANTS | |||
TRUSTING_PERIOD, | |||
(* the period within which the validators are trusted *) | |||
CLOCK_DRIFT, | |||
(* the assumed precision of the clock *) | |||
REAL_CLOCK_DRIFT, | |||
(* the actual clock drift, which under normal circumstances should not | |||
be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) | |||
FAULTY_RATIO | |||
(* a pair <<a, b>> that limits that ratio of faulty validator in the blockchain | |||
from above (exclusive). Tendermint security model prescribes 1 / 3. *) | |||
VARIABLES | |||
localClock (* current time as measured by the light client *) | |||
(* the header is still within the trusting period *) | |||
InTrustingPeriodLocal(header) == | |||
\* note that the assumption about the drift reduces the period of trust | |||
localClock < header.time + TRUSTING_PERIOD - CLOCK_DRIFT | |||
(* the header is still within the trusting period, even if the clock can go backwards *) | |||
InTrustingPeriodLocalSurely(header) == | |||
\* note that the assumption about the drift reduces the period of trust | |||
localClock < header.time + TRUSTING_PERIOD - 2 * CLOCK_DRIFT | |||
(* ensure that the local clock does not drift far away from the global clock *) | |||
IsLocalClockWithinDrift(local, global) == | |||
/\ global - REAL_CLOCK_DRIFT <= local | |||
/\ local <= global + REAL_CLOCK_DRIFT | |||
(** | |||
* Check that the commits in an untrusted block form 1/3 of the next validators | |||
* in a trusted header. | |||
*) | |||
SignedByOneThirdOfTrusted(trusted, untrusted) == | |||
LET TP == Cardinality(trusted.header.NextVS) | |||
SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) | |||
IN | |||
3 * SP > TP | |||
(** | |||
The first part of the precondition of ValidAndVerified, which does not take | |||
the current time into account. | |||
[LCV-FUNC-VALID.1::TLA-PRE-UNTIMED.1] | |||
*) | |||
ValidAndVerifiedPreUntimed(trusted, untrusted) == | |||
LET thdr == trusted.header | |||
uhdr == untrusted.header | |||
IN | |||
/\ thdr.height < uhdr.height | |||
\* the trusted block has been created earlier | |||
/\ thdr.time < uhdr.time | |||
/\ untrusted.Commits \subseteq uhdr.VS | |||
/\ LET TP == Cardinality(uhdr.VS) | |||
SP == Cardinality(untrusted.Commits) | |||
IN | |||
3 * SP > 2 * TP | |||
/\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS | |||
(* As we do not have explicit hashes we ignore these three checks of the English spec: | |||
1. "trusted.Commit is a commit is for the header trusted.Header, | |||
i.e. it contains the correct hash of the header". | |||
2. untrusted.Validators = hash(untrusted.Header.Validators) | |||
3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) | |||
*) | |||
(** | |||
Check the precondition of ValidAndVerified, including the time checks. | |||
[LCV-FUNC-VALID.1::TLA-PRE.1] | |||
*) | |||
ValidAndVerifiedPre(trusted, untrusted, checkFuture) == | |||
LET thdr == trusted.header | |||
uhdr == untrusted.header | |||
IN | |||
/\ InTrustingPeriodLocal(thdr) | |||
\* The untrusted block is not from the future (modulo clock drift). | |||
\* Do the check, if it is required. | |||
/\ checkFuture => uhdr.time < localClock + CLOCK_DRIFT | |||
/\ ValidAndVerifiedPreUntimed(trusted, untrusted) | |||
(** | |||
Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. | |||
This test does take current time into account, but only looks at the block structure. | |||
[LCV-FUNC-VALID.1::TLA-UNTIMED.1] | |||
*) | |||
ValidAndVerifiedUntimed(trusted, untrusted) == | |||
IF ~ValidAndVerifiedPreUntimed(trusted, untrusted) | |||
THEN "INVALID" | |||
ELSE IF untrusted.header.height = trusted.header.height + 1 | |||
\/ SignedByOneThirdOfTrusted(trusted, untrusted) | |||
THEN "SUCCESS" | |||
ELSE "NOT_ENOUGH_TRUST" | |||
(** | |||
Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. | |||
[LCV-FUNC-VALID.1::TLA.1] | |||
*) | |||
ValidAndVerified(trusted, untrusted, checkFuture) == | |||
IF ~ValidAndVerifiedPre(trusted, untrusted, checkFuture) | |||
THEN "INVALID" | |||
ELSE IF ~InTrustingPeriodLocal(untrusted.header) | |||
(* We leave the following test for the documentation purposes. | |||
The implementation should do this test, as signature verification may be slow. | |||
In the TLA+ specification, ValidAndVerified happens in no time. | |||
*) | |||
THEN "FAILED_TRUSTING_PERIOD" | |||
ELSE IF untrusted.header.height = trusted.header.height + 1 | |||
\/ SignedByOneThirdOfTrusted(trusted, untrusted) | |||
THEN "SUCCESS" | |||
ELSE "NOT_ENOUGH_TRUST" | |||
(** | |||
The invariant of the light store that is not related to the blockchain | |||
*) | |||
LightStoreInv(fetchedLightBlocks, lightBlockStatus) == | |||
\A lh, rh \in DOMAIN fetchedLightBlocks: | |||
\* for every pair of stored headers that have been verified | |||
\/ lh >= rh | |||
\/ lightBlockStatus[lh] /= "StateVerified" | |||
\/ lightBlockStatus[rh] /= "StateVerified" | |||
\* either there is a header between them | |||
\/ \E mh \in DOMAIN fetchedLightBlocks: | |||
lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" | |||
\* or the left header is outside the trusting period, so no guarantees | |||
\/ LET lhdr == fetchedLightBlocks[lh] | |||
rhdr == fetchedLightBlocks[rh] | |||
IN | |||
\* we can verify the right one using the left one | |||
"SUCCESS" = ValidAndVerifiedUntimed(lhdr, rhdr) | |||
(** | |||
Correctness states that all the obtained headers are exactly like in the blockchain. | |||
It is always the case that every verified header in LightStore was generated by | |||
an instance of Tendermint consensus. | |||
[LCV-DIST-SAFE.1::CORRECTNESS-INV.1] | |||
*) | |||
CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) == | |||
\A h \in DOMAIN fetchedLightBlocks: | |||
lightBlockStatus[h] = "StateVerified" => | |||
fetchedLightBlocks[h].header = blockchain[h] | |||
(** | |||
* When the light client terminates, there are no failed blocks. | |||
* (Otherwise, someone lied to us.) | |||
*) | |||
NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) == | |||
\A h \in DOMAIN fetchedLightBlocks: | |||
lightBlockStatus[h] /= "StateFailed" | |||
(** | |||
The expected post-condition of VerifyToTarget. | |||
*) | |||
VerifyToTargetPost(blockchain, isPeerCorrect, | |||
fetchedLightBlocks, lightBlockStatus, | |||
trustedHeight, targetHeight, finalState) == | |||
LET trustedHeader == fetchedLightBlocks[trustedHeight].header IN | |||
\* The light client is not lying us on the trusted block. | |||
\* It is straightforward to detect. | |||
/\ lightBlockStatus[trustedHeight] = "StateVerified" | |||
/\ trustedHeight \in DOMAIN fetchedLightBlocks | |||
/\ trustedHeader = blockchain[trustedHeight] | |||
\* the invariants we have found in the light client verification | |||
\* there is a problem with trusting period | |||
/\ isPeerCorrect | |||
=> CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) | |||
\* a correct peer should fail the light client, | |||
\* if the trusted block is in the trusting period | |||
/\ isPeerCorrect /\ InTrustingPeriodLocalSurely(trustedHeader) | |||
=> finalState = "finishedSuccess" | |||
/\ finalState = "finishedSuccess" => | |||
/\ lightBlockStatus[targetHeight] = "StateVerified" | |||
/\ targetHeight \in DOMAIN fetchedLightBlocks | |||
/\ NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) | |||
/\ LightStoreInv(fetchedLightBlocks, lightBlockStatus) | |||
================================================================================== |
@ -0,0 +1,18 @@ | |||
------------------------- MODULE MC_5_3 ------------------------------------- | |||
AllNodes == {"n1", "n2", "n3", "n4", "n5"} | |||
COMMON_HEIGHT == 1 | |||
CONFLICT_HEIGHT == 3 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
FAULTY_RATIO == <<1, 2>> \* < 1 / 2 faulty validators | |||
VARIABLES | |||
blockchain, \* the reference blockchain | |||
refClock, \* current time in the reference blockchain | |||
Faulty, \* the set of faulty validators | |||
state, \* the state of the light client detector | |||
conflictingBlock, \* an evidence that two peers reported conflicting blocks | |||
attackers | |||
INSTANCE Isolation_001_draft | |||
============================================================================ |