From cbc7a1abcfe871cc0122ee89b841b3803f9478a9 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Fri, 12 Aug 2022 14:33:47 -0400 Subject: [PATCH 1/7] spec: Sync Light Client TLA+ code with `master` (#9238) * Typo fix in README.md (#350) * Updated Apalache type annotations (#395) Co-authored-by: Prajjwol Gautam Co-authored-by: Kukovec --- spec/light-client/accountability/MC_n4_f1.tla | 30 ++- spec/light-client/accountability/MC_n4_f2.tla | 30 ++- .../accountability/MC_n4_f2_amnesia.tla | 36 ++- spec/light-client/accountability/MC_n4_f3.tla | 30 ++- spec/light-client/accountability/MC_n5_f1.tla | 30 ++- spec/light-client/accountability/MC_n5_f2.tla | 30 ++- spec/light-client/accountability/MC_n6_f1.tla | 30 ++- spec/light-client/accountability/README.md | 2 +- .../TendermintAccDebug_004_draft.tla | 9 +- .../TendermintAccInv_004_draft.tla | 34 +-- .../TendermintAccTrace_004_draft.tla | 8 +- .../TendermintAcc_004_draft.tla | 218 ++++++++++++++---- spec/light-client/accountability/typedefs.tla | 36 +++ 13 files changed, 429 insertions(+), 94 deletions(-) create mode 100644 spec/light-client/accountability/typedefs.tla diff --git a/spec/light-client/accountability/MC_n4_f1.tla b/spec/light-client/accountability/MC_n4_f1.tla index 7a828b498..62bcb30de 100644 --- a/spec/light-client/accountability/MC_n4_f1.tla +++ b/spec/light-client/accountability/MC_n4_f1.tla @@ -1,10 +1,34 @@ ----------------------------- MODULE MC_n4_f1 ------------------------------- -CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N +CONSTANT + \* @type: ROUND -> PROCESS; + Proposer \* the variables declared in TendermintAcc3 VARIABLES - round, step, decision, lockedValue, lockedRound, validValue, validRound, - msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + \* @type: PROCESS -> ROUND; + round, + \* @type: PROCESS -> STEP; + step, + \* @type: PROCESS -> VALUE; + decision, + \* @type: PROCESS -> VALUE; + lockedValue, + \* @type: PROCESS -> ROUND; + lockedRound, + \* @type: PROCESS -> VALUE; + validValue, + \* @type: PROCESS -> ROUND; + validRound, + \* @type: ROUND -> Set(PROPMESSAGE); + msgsPropose, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrevote, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrecommit, + \* @type: Set(MESSAGE); + evidence, + \* @type: ACTION; + action INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2", "c3"}, diff --git a/spec/light-client/accountability/MC_n4_f2.tla b/spec/light-client/accountability/MC_n4_f2.tla index 893f18db6..baab2a21d 100644 --- a/spec/light-client/accountability/MC_n4_f2.tla +++ b/spec/light-client/accountability/MC_n4_f2.tla @@ -1,10 +1,34 @@ ----------------------------- MODULE MC_n4_f2 ------------------------------- -CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N +CONSTANT + \* @type: ROUND -> PROCESS; + Proposer \* the variables declared in TendermintAcc3 VARIABLES - round, step, decision, lockedValue, lockedRound, validValue, validRound, - msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + \* @type: PROCESS -> ROUND; + round, + \* @type: PROCESS -> STEP; + step, + \* @type: PROCESS -> VALUE; + decision, + \* @type: PROCESS -> VALUE; + lockedValue, + \* @type: PROCESS -> ROUND; + lockedRound, + \* @type: PROCESS -> VALUE; + validValue, + \* @type: PROCESS -> ROUND; + validRound, + \* @type: ROUND -> Set(PROPMESSAGE); + msgsPropose, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrevote, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrecommit, + \* @type: Set(MESSAGE); + evidence, + \* @type: ACTION; + action INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2"}, diff --git a/spec/light-client/accountability/MC_n4_f2_amnesia.tla b/spec/light-client/accountability/MC_n4_f2_amnesia.tla index 434fffaeb..940903a76 100644 --- a/spec/light-client/accountability/MC_n4_f2_amnesia.tla +++ b/spec/light-client/accountability/MC_n4_f2_amnesia.tla @@ -1,20 +1,42 @@ ---------------------- MODULE MC_n4_f2_amnesia ------------------------------- EXTENDS Sequences -CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N +CONSTANT + \* @type: ROUND -> PROCESS; + Proposer \* the variables declared in TendermintAcc3 VARIABLES - round, step, decision, lockedValue, lockedRound, validValue, validRound, - msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + \* @type: PROCESS -> ROUND; + round, + \* @type: PROCESS -> STEP; + step, + \* @type: PROCESS -> VALUE; + decision, + \* @type: PROCESS -> VALUE; + lockedValue, + \* @type: PROCESS -> ROUND; + lockedRound, + \* @type: PROCESS -> VALUE; + validValue, + \* @type: PROCESS -> ROUND; + validRound, + \* @type: ROUND -> Set(PROPMESSAGE); + msgsPropose, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrevote, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrecommit, + \* @type: Set(MESSAGE); + evidence, + \* @type: ACTION; + action \* the variable declared in TendermintAccTrace3 VARIABLE + \* @type: TRACE; toReplay -\* old apalache annotations, fix with the new release -a <: b == a - INSTANCE TendermintAccTrace_004_draft WITH Corr <- {"c1", "c2"}, Faulty <- {"f3", "f4"}, @@ -31,7 +53,7 @@ INSTANCE TendermintAccTrace_004_draft WITH "UponProposalInPropose", "UponProposalInPrevoteOrCommitAndPrevote", "UponProposalInPrecommitNoDecision" - >> <: Seq(STRING) + >> \* run Apalache with --cinit=ConstInit ConstInit == \* the proposer is arbitrary -- works for safety diff --git a/spec/light-client/accountability/MC_n4_f3.tla b/spec/light-client/accountability/MC_n4_f3.tla index b794fff5e..d4c64e6d0 100644 --- a/spec/light-client/accountability/MC_n4_f3.tla +++ b/spec/light-client/accountability/MC_n4_f3.tla @@ -1,10 +1,34 @@ ----------------------------- MODULE MC_n4_f3 ------------------------------- -CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N +CONSTANT + \* @type: ROUND -> PROCESS; + Proposer \* the variables declared in TendermintAcc3 VARIABLES - round, step, decision, lockedValue, lockedRound, validValue, validRound, - msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + \* @type: PROCESS -> ROUND; + round, + \* @type: PROCESS -> STEP; + step, + \* @type: PROCESS -> VALUE; + decision, + \* @type: PROCESS -> VALUE; + lockedValue, + \* @type: PROCESS -> ROUND; + lockedRound, + \* @type: PROCESS -> VALUE; + validValue, + \* @type: PROCESS -> ROUND; + validRound, + \* @type: ROUND -> Set(PROPMESSAGE); + msgsPropose, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrevote, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrecommit, + \* @type: Set(MESSAGE); + evidence, + \* @type: ACTION; + action INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1"}, diff --git a/spec/light-client/accountability/MC_n5_f1.tla b/spec/light-client/accountability/MC_n5_f1.tla index d65673a58..3d7ff979e 100644 --- a/spec/light-client/accountability/MC_n5_f1.tla +++ b/spec/light-client/accountability/MC_n5_f1.tla @@ -1,10 +1,34 @@ ----------------------------- MODULE MC_n5_f1 ------------------------------- -CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N +CONSTANT + \* @type: ROUND -> PROCESS; + Proposer \* the variables declared in TendermintAcc3 VARIABLES - round, step, decision, lockedValue, lockedRound, validValue, validRound, - msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + \* @type: PROCESS -> ROUND; + round, + \* @type: PROCESS -> STEP; + step, + \* @type: PROCESS -> VALUE; + decision, + \* @type: PROCESS -> VALUE; + lockedValue, + \* @type: PROCESS -> ROUND; + lockedRound, + \* @type: PROCESS -> VALUE; + validValue, + \* @type: PROCESS -> ROUND; + validRound, + \* @type: ROUND -> Set(PROPMESSAGE); + msgsPropose, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrevote, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrecommit, + \* @type: Set(MESSAGE); + evidence, + \* @type: ACTION; + action INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2", "c3", "c4"}, diff --git a/spec/light-client/accountability/MC_n5_f2.tla b/spec/light-client/accountability/MC_n5_f2.tla index c19aa98cc..24400dc07 100644 --- a/spec/light-client/accountability/MC_n5_f2.tla +++ b/spec/light-client/accountability/MC_n5_f2.tla @@ -1,10 +1,34 @@ ----------------------------- MODULE MC_n5_f2 ------------------------------- -CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N +CONSTANT + \* @type: ROUND -> PROCESS; + Proposer \* the variables declared in TendermintAcc3 VARIABLES - round, step, decision, lockedValue, lockedRound, validValue, validRound, - msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + \* @type: PROCESS -> ROUND; + round, + \* @type: PROCESS -> STEP; + step, + \* @type: PROCESS -> VALUE; + decision, + \* @type: PROCESS -> VALUE; + lockedValue, + \* @type: PROCESS -> ROUND; + lockedRound, + \* @type: PROCESS -> VALUE; + validValue, + \* @type: PROCESS -> ROUND; + validRound, + \* @type: ROUND -> Set(PROPMESSAGE); + msgsPropose, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrevote, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrecommit, + \* @type: Set(MESSAGE); + evidence, + \* @type: ACTION; + action INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2", "c3"}, diff --git a/spec/light-client/accountability/MC_n6_f1.tla b/spec/light-client/accountability/MC_n6_f1.tla index 2e992974f..a58f8c78a 100644 --- a/spec/light-client/accountability/MC_n6_f1.tla +++ b/spec/light-client/accountability/MC_n6_f1.tla @@ -1,10 +1,34 @@ ----------------------------- MODULE MC_n6_f1 ------------------------------- -CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N +CONSTANT + \* @type: ROUND -> PROCESS; + Proposer \* the variables declared in TendermintAcc3 VARIABLES - round, step, decision, lockedValue, lockedRound, validValue, validRound, - msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + \* @type: PROCESS -> ROUND; + round, + \* @type: PROCESS -> STEP; + step, + \* @type: PROCESS -> VALUE; + decision, + \* @type: PROCESS -> VALUE; + lockedValue, + \* @type: PROCESS -> ROUND; + lockedRound, + \* @type: PROCESS -> VALUE; + validValue, + \* @type: PROCESS -> ROUND; + validRound, + \* @type: ROUND -> Set(PROPMESSAGE); + msgsPropose, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrevote, + \* @type: ROUND -> Set(PREMESSAGE); + msgsPrecommit, + \* @type: Set(MESSAGE); + evidence, + \* @type: ACTION; + action INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2", "c3", "c4", "c5"}, diff --git a/spec/light-client/accountability/README.md b/spec/light-client/accountability/README.md index bb872649b..a6eda7e67 100644 --- a/spec/light-client/accountability/README.md +++ b/spec/light-client/accountability/README.md @@ -22,7 +22,7 @@ The agreement property says that for a given height, any two correct validators However, faulty nodes may forge blocks and try to convince users (light clients) that the blocks had been correctly generated. In addition, Tendermint agreement might be violated in the case where 1/3 or more of the voting power belongs to faulty validators: Two correct validators decide on different blocks. The latter case motivates the term "fork": as Tendermint consensus also agrees on the next validator set, correct validators may have decided on disjoint next validator sets, and the chain branches into two or more partitions (possibly having faulty validators in common) and each branch continues to generate blocks independently of the other. -We say that a fork is a case in which there are two commits for different blocks at the same height of the blockchain. The proplem is to ensure that in those cases we are able to detect faulty validators (and not mistakenly accuse correct validators), and incentivize therefore validators to behave according to the protocol specification. +We say that a fork is a case in which there are two commits for different blocks at the same height of the blockchain. The problem is to ensure that in those cases we are able to detect faulty validators (and not mistakenly accuse correct validators), and incentivize therefore validators to behave according to the protocol specification. **Conceptual Limit.** In order to prove misbehavior of a node, we have to show that the behavior deviates from correct behavior with respect to a given algorithm. Thus, an algorithm that detects misbehavior of nodes executing some algorithm *A* must be defined with respect to algorithm *A*. In our case, *A* is Tendermint consensus (+ other protocols in the infrastructure; e.g.,full nodes and the Light Client). If the consensus algorithm is changed/updated/optimized in the future, we have to check whether changes to the accountability algorithm are also required. All the discussions in this document are thus inherently specific to Tendermint consensus and the Light Client specification. diff --git a/spec/light-client/accountability/TendermintAccDebug_004_draft.tla b/spec/light-client/accountability/TendermintAccDebug_004_draft.tla index deaa990ea..9281b8726 100644 --- a/spec/light-client/accountability/TendermintAccDebug_004_draft.tla +++ b/spec/light-client/accountability/TendermintAccDebug_004_draft.tla @@ -19,6 +19,7 @@ NFaultyPrecommits == 6 \* the number of injected faulty PRECOMMIT messages \* rounds to sets of messages. \* Importantly, there will be exactly k messages in the image of msgFun. \* We use this action to produce k faults in an initial state. +\* @type: (ROUND -> Set(MESSAGE), Set(MESSAGE), Int) => Bool; ProduceFaults(msgFun, From, k) == \E f \in [1..k -> From]: msgFun = [r \in Rounds |-> {m \in {f[i]: i \in 1..k}: m.round = r}] @@ -50,14 +51,14 @@ InitFewFaults == /\ validValue = [p \in Corr |-> NilValue] /\ validRound = [p \in Corr |-> NilRound] /\ ProduceFaults(msgsPrevote', - SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Values]), + [type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Values], NFaultyPrevotes) /\ ProduceFaults(msgsPrecommit', - SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Values]), + [type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Values], NFaultyPrecommits) /\ ProduceFaults(msgsPropose', - SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, round: Rounds, - proposal: Values, validRound: Rounds \cup {NilRound}]), + [type: {"PROPOSAL"}, src: Faulty, round: Rounds, + proposal: Values, validRound: Rounds \cup {NilRound}], NFaultyProposals) /\ evidence = EmptyMsgSet diff --git a/spec/light-client/accountability/TendermintAccInv_004_draft.tla b/spec/light-client/accountability/TendermintAccInv_004_draft.tla index 5dd15396d..2eeec1fb2 100644 --- a/spec/light-client/accountability/TendermintAccInv_004_draft.tla +++ b/spec/light-client/accountability/TendermintAccInv_004_draft.tla @@ -13,24 +13,27 @@ EXTENDS TendermintAcc_004_draft (************************** TYPE INVARIANT ***********************************) (* first, we define the sets of all potential messages *) +\* @type: Set(PROPMESSAGE); AllProposals == - SetOfMsgs([type: {"PROPOSAL"}, - src: AllProcs, - round: Rounds, - proposal: ValuesOrNil, - validRound: RoundsOrNil]) + [type: {"PROPOSAL"}, + src: AllProcs, + round: Rounds, + proposal: ValuesOrNil, + validRound: RoundsOrNil] +\* @type: Set(PREMESSAGE); AllPrevotes == - SetOfMsgs([type: {"PREVOTE"}, - src: AllProcs, - round: Rounds, - id: ValuesOrNil]) + [type: {"PREVOTE"}, + src: AllProcs, + round: Rounds, + id: ValuesOrNil] +\* @type: Set(PREMESSAGE); AllPrecommits == - SetOfMsgs([type: {"PRECOMMIT"}, - src: AllProcs, - round: Rounds, - id: ValuesOrNil]) + [type: {"PRECOMMIT"}, + src: AllProcs, + round: Rounds, + id: ValuesOrNil] (* the standard type invariant -- importantly, it is inductive *) TypeOK == @@ -226,7 +229,7 @@ LatestPrecommitHasLockedRound(p) == LET pPrecommits == {mm \in UNION { msgsPrecommit[r]: r \in Rounds }: mm.src = p /\ mm.id /= NilValue } IN - pPrecommits /= {} <: {MT} + pPrecommits /= {} => LET latest == CHOOSE m \in pPrecommits: \A m2 \in pPrecommits: @@ -242,6 +245,7 @@ AllLatestPrecommitHasLockedRound == \* Every correct process sends only one value or NilValue. \* This test has quantifier alternation -- a threat to all decision procedures. \* Luckily, the sets Corr and ValidValues are small. +\* @type: (ROUND, ROUND -> Set(PREMESSAGE)) => Bool; NoEquivocationByCorrect(r, msgs) == \A p \in Corr: \E v \in ValidValues \union {NilValue}: @@ -250,6 +254,7 @@ NoEquivocationByCorrect(r, msgs) == \/ m.id = v \* a proposer nevers sends two values +\* @type: (ROUND, ROUND -> Set(PROPMESSAGE)) => Bool; ProposalsByProposer(r, msgs) == \* if the proposer is not faulty, it sends only one value \E v \in ValidValues: @@ -264,6 +269,7 @@ AllNoEquivocationByCorrect == /\ NoEquivocationByCorrect(r, msgsPrecommit) \* construct the set of the message senders +\* @type: (Set(MESSAGE)) => Set(PROCESS); Senders(M) == { m.src: m \in M } \* The final piece by Josef Widder: diff --git a/spec/light-client/accountability/TendermintAccTrace_004_draft.tla b/spec/light-client/accountability/TendermintAccTrace_004_draft.tla index 436c2275a..bbc708063 100644 --- a/spec/light-client/accountability/TendermintAccTrace_004_draft.tla +++ b/spec/light-client/accountability/TendermintAccTrace_004_draft.tla @@ -13,9 +13,13 @@ EXTENDS Sequences, Apalache, TendermintAcc_004_draft \* a sequence of action names that should appear in the given order, \* excluding "Init" -CONSTANT Trace +CONSTANT + \* @type: TRACE; + Trace -VARIABLE toReplay +VARIABLE + \* @type: TRACE; + toReplay TraceInit == /\ toReplay = Trace diff --git a/spec/light-client/accountability/TendermintAcc_004_draft.tla b/spec/light-client/accountability/TendermintAcc_004_draft.tla index 952d8c569..fccf993d4 100644 --- a/spec/light-client/accountability/TendermintAcc_004_draft.tla +++ b/spec/light-client/accountability/TendermintAcc_004_draft.tla @@ -37,31 +37,43 @@ Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020. *) -EXTENDS Integers, FiniteSets +EXTENDS Integers, FiniteSets, typedefs (********************* PROTOCOL PARAMETERS **********************************) CONSTANTS + \* @type: Set(PROCESS); Corr, \* the set of correct processes + \* @type: Set(PROCESS); Faulty, \* the set of Byzantine processes, may be empty + \* @type: Int; N, \* the total number of processes: correct, defective, and Byzantine + \* @type: Int; T, \* an upper bound on the number of Byzantine processes + \* @type: Set(VALUE); ValidValues, \* the set of valid values, proposed both by correct and faulty + \* @type: Set(VALUE); InvalidValues, \* the set of invalid values, never proposed by the correct ones + \* @type: ROUND; MaxRound, \* the maximal round number - Proposer \* the proposer function from 0..NRounds to 1..N + \* @type: ROUND -> PROCESS; + Proposer \* the proposer function from Rounds to AllProcs ASSUME(N = Cardinality(Corr \union Faulty)) (*************************** DEFINITIONS ************************************) AllProcs == Corr \union Faulty \* the set of all processes +\* @type: Set(ROUND); Rounds == 0..MaxRound \* the set of potential rounds +\* @type: ROUND; NilRound == -1 \* a special value to denote a nil round, outside of Rounds RoundsOrNil == Rounds \union {NilRound} Values == ValidValues \union InvalidValues \* the set of all values +\* @type: VALUE; NilValue == "None" \* a special value for a nil round, outside of Values ValuesOrNil == Values \union {NilValue} \* a value hash is modeled as identity +\* @type: (t) => t; Id(v) == v \* The validity predicate @@ -72,36 +84,39 @@ THRESHOLD1 == T + 1 \* at least one process is not faulty THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T (********************* TYPE ANNOTATIONS FOR APALACHE **************************) -\* the operator for type annotations -a <: b == a -\* the type of message records -MT == [type |-> STRING, src |-> STRING, round |-> Int, - proposal |-> STRING, validRound |-> Int, id |-> STRING] - -\* a type annotation for a message -AsMsg(m) == m <: MT -\* a type annotation for a set of messages -SetOfMsgs(S) == S <: {MT} -\* a type annotation for an empty set of messages -EmptyMsgSet == SetOfMsgs({}) +\* An empty set of messages +\* @type: Set(MESSAGE); +EmptyMsgSet == {} (********************* PROTOCOL STATE VARIABLES ******************************) VARIABLES + \* @type: PROCESS -> ROUND; round, \* a process round number: Corr -> Rounds + \* @type: PROCESS -> STEP; step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + \* @type: PROCESS -> VALUE; decision, \* process decision: Corr -> ValuesOrNil + \* @type: PROCESS -> VALUE; lockedValue, \* a locked value: Corr -> ValuesOrNil + \* @type: PROCESS -> ROUND; lockedRound, \* a locked round: Corr -> RoundsOrNil + \* @type: PROCESS -> VALUE; validValue, \* a valid value: Corr -> ValuesOrNil + \* @type: PROCESS -> ROUND; validRound \* a valid round: Corr -> RoundsOrNil \* book-keeping variables VARIABLES + \* @type: ROUND -> Set(PROPMESSAGE); msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + \* @type: ROUND -> Set(PREMESSAGE); msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + \* @type: ROUND -> Set(PREMESSAGE); msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + \* @type: Set(MESSAGE); evidence, \* the messages that were used by the correct processes to make transitions + \* @type: ACTION; action \* we use this variable to see which action was taken (* to see a type invariant, check TendermintAccInv3 *) @@ -111,26 +126,63 @@ vars == <> (********************* PROTOCOL INITIALIZATION ******************************) +\* @type: (ROUND) => Set(PROPMESSAGE); FaultyProposals(r) == - SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, - round: {r}, proposal: Values, validRound: RoundsOrNil]) + [ + type : {"PROPOSAL"}, + src : Faulty, + round : {r}, + proposal : Values, + validRound: RoundsOrNil + ] +\* @type: Set(PROPMESSAGE); AllFaultyProposals == - SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, - round: Rounds, proposal: Values, validRound: RoundsOrNil]) + [ + type : {"PROPOSAL"}, + src : Faulty, + round : Rounds, + proposal : Values, + validRound: RoundsOrNil + ] +\* @type: (ROUND) => Set(PREMESSAGE); FaultyPrevotes(r) == - SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: {r}, id: Values]) + [ + type : {"PREVOTE"}, + src : Faulty, + round: {r}, + id : Values + ] +\* @type: Set(PREMESSAGE); AllFaultyPrevotes == - SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Values]) + [ + type : {"PREVOTE"}, + src : Faulty, + round: Rounds, + id : Values + ] +\* @type: (ROUND) => Set(PREMESSAGE); FaultyPrecommits(r) == - SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: {r}, id: Values]) + [ + type : {"PRECOMMIT"}, + src : Faulty, + round: {r}, + id : Values + ] +\* @type: Set(PREMESSAGE); AllFaultyPrecommits == - SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Values]) - + [ + type : {"PRECOMMIT"}, + src : Faulty, + round: Rounds, + id : Values + ] + +\* @type: (ROUND -> Set(MESSAGE)) => Bool; BenignRoundsInMessages(msgfun) == \* the message function never contains a message for a wrong round \A r \in Rounds: @@ -153,25 +205,49 @@ Init == /\ BenignRoundsInMessages(msgsPrevote) /\ BenignRoundsInMessages(msgsPrecommit) /\ evidence = EmptyMsgSet - /\ action' = "Init" + /\ action = "Init" (************************ MESSAGE PASSING ********************************) +\* @type: (PROCESS, ROUND, VALUE, ROUND) => Bool; BroadcastProposal(pSrc, pRound, pProposal, pValidRound) == - LET newMsg == - AsMsg([type |-> "PROPOSAL", src |-> pSrc, round |-> pRound, - proposal |-> pProposal, validRound |-> pValidRound]) + LET + \* @type: PROPMESSAGE; + newMsg == + [ + type |-> "PROPOSAL", + src |-> pSrc, + round |-> pRound, + proposal |-> pProposal, + validRound |-> pValidRound + ] IN msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}] +\* @type: (PROCESS, ROUND, VALUE) => Bool; BroadcastPrevote(pSrc, pRound, pId) == - LET newMsg == AsMsg([type |-> "PREVOTE", - src |-> pSrc, round |-> pRound, id |-> pId]) + LET + \* @type: PREMESSAGE; + newMsg == + [ + type |-> "PREVOTE", + src |-> pSrc, + round |-> pRound, + id |-> pId + ] IN msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}] +\* @type: (PROCESS, ROUND, VALUE) => Bool; BroadcastPrecommit(pSrc, pRound, pId) == - LET newMsg == AsMsg([type |-> "PRECOMMIT", - src |-> pSrc, round |-> pRound, id |-> pId]) + LET + \* @type: PREMESSAGE; + newMsg == + [ + type |-> "PRECOMMIT", + src |-> pSrc, + round |-> pRound, + id |-> pId + ] IN msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}] @@ -184,6 +260,7 @@ StartRound(p, r) == /\ step' = [step EXCEPT ![p] = "PROPOSE"] \* lines 14-19, a proposal may be sent later +\* @type: (PROCESS) => Bool; InsertProposal(p) == LET r == round[p] IN /\ p = Proposer[r] @@ -192,8 +269,13 @@ InsertProposal(p) == \* by the correct processes for the same round /\ \A m \in msgsPropose[r]: m.src /= p /\ \E v \in ValidValues: - LET proposal == IF validValue[p] /= NilValue THEN validValue[p] ELSE v IN - BroadcastProposal(p, round[p], proposal, validRound[p]) + LET + \* @type: VALUE; + proposal == + IF validValue[p] /= NilValue + THEN validValue[p] + ELSE v + IN BroadcastProposal(p, round[p], proposal, validRound[p]) /\ UNCHANGED <> /\ action' = "InsertProposal" @@ -202,9 +284,17 @@ InsertProposal(p) == UponProposalInPropose(p) == \E v \in Values: /\ step[p] = "PROPOSE" (* line 22 *) - /\ LET msg == - AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], - round |-> round[p], proposal |-> v, validRound |-> NilRound]) IN + /\ LET + \* @type: PROPMESSAGE; + msg == + [ + type |-> "PROPOSAL", + src |-> Proposer[round[p]], + round |-> round[p], + proposal |-> v, + validRound |-> NilRound + ] + IN /\ msg \in msgsPropose[round[p]] \* line 22 /\ evidence' = {msg} \union evidence /\ LET mid == (* line 23 *) @@ -222,9 +312,16 @@ UponProposalInPropose(p) == UponProposalInProposeAndPrevote(p) == \E v \in Values, vr \in Rounds: /\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < round[p] \* line 28, the while part - /\ LET msg == - AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], - round |-> round[p], proposal |-> v, validRound |-> vr]) + /\ LET + \* @type: PROPMESSAGE; + msg == + [ + type |-> "PROPOSAL", + src |-> Proposer[round[p]], + round |-> round[p], + proposal |-> v, + validRound |-> vr + ] IN /\ msg \in msgsPropose[round[p]] \* line 28 /\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(v) } IN @@ -260,9 +357,17 @@ UponQuorumOfPrevotesAny(p) == UponProposalInPrevoteOrCommitAndPrevote(p) == \E v \in ValidValues, vr \in RoundsOrNil: /\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36 - /\ LET msg == - AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], - round |-> round[p], proposal |-> v, validRound |-> vr]) IN + /\ LET + \* @type: PROPMESSAGE; + msg == + [ + type |-> "PROPOSAL", + src |-> Proposer[round[p]], + round |-> round[p], + proposal |-> v, + validRound |-> vr + ] + IN /\ msg \in msgsPropose[round[p]] \* line 36 /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(v) } IN /\ Cardinality(PV) >= THRESHOLD2 \* line 36 @@ -299,8 +404,17 @@ UponQuorumOfPrecommitsAny(p) == UponProposalInPrecommitNoDecision(p) == /\ decision[p] = NilValue \* line 49 /\ \E v \in ValidValues (* line 50*) , r \in Rounds, vr \in RoundsOrNil: - /\ LET msg == AsMsg([type |-> "PROPOSAL", src |-> Proposer[r], - round |-> r, proposal |-> v, validRound |-> vr]) IN + /\ LET + \* @type: PROPMESSAGE; + msg == + [ + type |-> "PROPOSAL", + src |-> Proposer[r], + round |-> r, + proposal |-> v, + validRound |-> vr + ] + IN /\ msg \in msgsPropose[r] \* line 49 /\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(v) } IN /\ Cardinality(PV) >= THRESHOLD2 \* line 49 @@ -386,10 +500,18 @@ AmnesiaBy(p) == /\ r1 < r2 /\ \E v1, v2 \in ValidValues: /\ v1 /= v2 - /\ AsMsg([type |-> "PRECOMMIT", src |-> p, - round |-> r1, id |-> Id(v1)]) \in evidence - /\ AsMsg([type |-> "PREVOTE", src |-> p, - round |-> r2, id |-> Id(v2)]) \in evidence + /\ [ + type |-> "PRECOMMIT", + src |-> p, + round |-> r1, + id |-> Id(v1) + ] \in evidence + /\ [ + type |-> "PREVOTE", + src |-> p, + round |-> r2, + id |-> Id(v2) + ] \in evidence /\ \A r \in { rnd \in Rounds: r1 <= rnd /\ rnd < r2 }: LET prevotes == { m \in evidence: diff --git a/spec/light-client/accountability/typedefs.tla b/spec/light-client/accountability/typedefs.tla new file mode 100644 index 000000000..5b4f7de52 --- /dev/null +++ b/spec/light-client/accountability/typedefs.tla @@ -0,0 +1,36 @@ +-------------------- MODULE typedefs --------------------------- +(* + @typeAlias: PROCESS = Str; + @typeAlias: VALUE = Str; + @typeAlias: STEP = Str; + @typeAlias: ROUND = Int; + @typeAlias: ACTION = Str; + @typeAlias: TRACE = Seq(Str); + @typeAlias: PROPMESSAGE = + [ + type: STEP, + src: PROCESS, + round: ROUND, + proposal: VALUE, + validRound: ROUND + ]; + @typeAlias: PREMESSAGE = + [ + type: STEP, + src: PROCESS, + round: ROUND, + id: VALUE + ]; + @typeAlias: MESSAGE = + [ + type: STEP, + src: PROCESS, + round: ROUND, + proposal: VALUE, + validRound: ROUND, + id: VALUE + ]; +*) +TypeAliases == TRUE + +============================================================================= \ No newline at end of file From c322b89b2a7908baf32e5f41a17d5cdb4d18e22a Mon Sep 17 00:00:00 2001 From: William Banfield <4561443+williambanfield@users.noreply.github.com> Date: Tue, 16 Aug 2022 09:51:55 -0400 Subject: [PATCH 2/7] RFC-024: block structure consolidation (#8457) This RFC lays out a proposed set of changes for consolidating the set of information that may be included in the block structure. {{ [rendered](https://github.com/tendermint/tendermint/blob/wb/rfc-block-structure/docs/rfc/rfc-020-block-structure-consolidation.md) }} --- docs/rfc/README.md | 1 + .../rfc-024-block-structure-consolidation.md | 364 ++++++++++++++++++ 2 files changed, 365 insertions(+) create mode 100644 docs/rfc/rfc-024-block-structure-consolidation.md diff --git a/docs/rfc/README.md b/docs/rfc/README.md index 346318c7b..a0918cc64 100644 --- a/docs/rfc/README.md +++ b/docs/rfc/README.md @@ -60,5 +60,6 @@ sections. - [RFC-020: Onboarding Projects](./rfc-020-onboarding-projects.rst) - [RFC-021: The Future of the Socket Protocol](./rfc-021-socket-protocol.md) - [RFC-023: Semi-permanent Testnet](./rfc-023-semi-permanent-testnet.md) +- [RFC-024: Block Structure Consolidation](./rfc-024-block-structure-consolidation.md) diff --git a/docs/rfc/rfc-024-block-structure-consolidation.md b/docs/rfc/rfc-024-block-structure-consolidation.md new file mode 100644 index 000000000..c8cab17f5 --- /dev/null +++ b/docs/rfc/rfc-024-block-structure-consolidation.md @@ -0,0 +1,364 @@ +# RFC 024: Block Structure Consolidation + +## Changelog + +- 19-Apr-2022: Initial draft started (@williambanfield). +- 3-May-2022: Initial draft complete (@williambanfield). + +## Abstract + +The `Block` data structure is a very central structure within Tendermint. Because +of its centrality, it has gained several fields over the years through accretion. +Not all of these fields may be necessary any more. This document examines which +of these fields may no longer be necessary for inclusion in the block and makes +recommendations about how to proceed with each of them. + +## Background + +The current block structure contains multiple fields that are not required for +validation or execution of a Tendermint block. Some of these fields had vestigial +purposes that they no longer serve and some of these fields exist as a result of +internal Tendermint domain objects leaking out into the external data structure. + +In so far as is possible, we should consolidate and prune these superfluous +fields before releasing a 1.0 version of Tendermint. All pruning of these +fields should be done with the aim of simplifying the structures to what +is needed while preserving information that aids with debugging and that also +allow external protocols to function more efficiently than if they were removed. + +### Current Block Structure + +The current block structures are included here to aid discussion. + +```proto +message Block { + Header header = 1; + Data data = 2; + tendermint.types.EvidenceList evidence = 3; + Commit last_commit = 4; +} +``` + +```proto +message Header { + tendermint.version.Consensus version = 1; + string chain_id = 2; + int64 height = 3; + google.protobuf.Timestamp time = 4; + BlockID last_block_id = 5; + bytes last_commit_hash = 6; + bytes data_hash = 7; + bytes validators_hash = 8; + bytes next_validators_hash = 9; + bytes consensus_hash = 10; + bytes app_hash = 11; + bytes last_results_hash = 12; + bytes evidence_hash = 13; + bytes proposer_address = 14; +} + +``` + +```proto +message Data { + repeated bytes txs = 1; +} +``` + +```proto +message EvidenceList { + repeated Evidence evidence = 1; +} +``` + +```proto +message Commit { + int64 height = 1; + int32 round = 2; + BlockID block_id = 3; + repeated CommitSig signatures = 4; +} +``` + +```proto +message CommitSig { + BlockIDFlag block_id_flag = 1; + bytes validator_address = 2; + google.protobuf.Timestamp timestamp = 3; + bytes signature = 4; +} +``` + +```proto +message BlockID { + bytes hash = 1; + PartSetHeader part_set_header = 2; +} +``` + +### On Tendermint Blocks + +#### What is a Tendermint 'Block'? + +A block is the structure produced as the result of an instance of the Tendermint +consensus algorithm. At its simplest, the 'block' can be represented as a Merkle +root hash of all of the data used to construct and produce the hash. Our current +block proto structure includes _far_ from all of the data used to produce the +hashes included in the block. + +It does not contain the full `AppState`, it does not contain the `ConsensusParams`, +nor the `LastResults`, nor the `ValidatorSet`. Additionally, the layout of +the block structure is not inherently tied to this Merkle root hash. Different +layouts of the same set of data could trivially be used to construct the +exact same hash. The thing we currently call the 'Block' is really just a view +into a subset of the data used to construct the root hash. Sections of the +structure can be modified as long as alternative methods exist to query and +retrieve the constituent values. + +#### Why this digression? + +This digression is aimed at informing what it means to consolidate 'fields' in the +'block'. The discussion of what should be included in the block can be teased +apart into a few different lines of inquiry. + +1. What values need to be included as part of the Merkle tree so that the +consensus algorithm can use proof-of-stake consensus to validate all of the +properties of the chain that we would like? +2. How can we create views of the data that can be easily retrieved, stored, and +verified by the relevant protocols? + +These two concerns are intertwined at the moment as a result of how we store +and propagate our data but they don't necessarily need to be. This document +focuses primarily on the first concern by suggesting fields that can be +completely removed without any loss in the function of our consensus algorithm. + +This document also suggests ways that we may update our storage and propagation +mechanisms to better take advantage of Merkle tree nature of our data although +these are not its primary concern. + +## Discussion + +### Data to consider removing + +This section proposes a list of data that could be completely removed from the +Merkle tree with no loss to the functionality of our consensus algorithm. + +Where the change is possible but would hamper external protocols or make +debugging more difficult, that is noted in discussion. + +#### CommitSig.Timestamp + +This field contains the timestamp included in the precommit message that was +issued for the block. The field was once used to produce the timestamp of the block. +With the introduction of Proposer-Based Timestamps, This field is no longer used +in any Tendermint algorithms and can be completely removed. + +#### CommitSig.ValidatorAddress + +The `ValidatorAddress` is included in each `CommitSig` structure. This field +is hashed along with all of the other fields of the `CommitSig`s in the block +to form the `LastCommitHash` field in the `Header`. The `ValidatorAddress` is +somewhat redundant in the hash. Each validator has a unique position in the +`CommitSig` and the hash is built preserving this order. Therefore, the +information of which signature corresponds to which validator is included in +the root hash, even if the address is absent. + +It's worth noting that the validator address could still be included in the +_hash_ even if it is absent from the `CommitSig` structure in the block by +simply hashing it locally at each validator but not including it in the block. +The reverse is also true. It would be perfectly possible to not include the +`ValidatorAddress` data in the `LastCommitHash` but still include the field in +the block. + +#### BlockID.PartSetHeader + +The [BlockID][block-id] field comprises the [PartSetHeader][part-set-header] and the hash of the block. +The `PartSetHeader` is used by nodes to gossip the block by dividing it into +parts. Nodes receive the `PartSetHeader` from their peers, informing them of +what pieces of the block to gather. There is no strong reason to include this +value in the block. Validators will still be able to gossip and validate the +blocks that they received from their peers using this mechanism even if it is +not written into the block. The `BlockID` can therefore be consolidated into +just the hash of the block. This is by far the most uncontroversial change +and there appears to be no good reason _not_ to do it. Further evidence that +the field is not meaningful can be found in the fact that the field is not +actually validated to ensure it is correct during block validation. Validation +only checks that the [field is well formed][psh-check]. + +#### ChainID + +The `ChainID` is a string selected by the chain operators, usually a +human-readable name for the network. This value is immutable for the lifetime +of the chain and is defined in the genesis file. It is therefore hashed into the +original block and therefore transitively included as in the Merkle root hash of +every block. The redundant field is a candidate for removal from the root hash +of each block. However, aesthetically, it's somewhat nice to include in each +block, as if the block was 'stamped' with the ID. Additionally, re-validating +the value from genesis would be painful and require reconstituting potentially +large chains. I'm therefore mildly in favor of maintaining this redundant +piece of information. We pay almost no storage cost for maintaining this +identical data, so the only cost is in the time required to hash it into the +structure. + +#### LastResultsHash + +`LastResultsHash` is a hash covering the result of executing the transactions +from the previous block. It covers the response `Code`, `Data`, `GasWanted`, +and `GasUsed` with the aim of ensuring that execution of all of the transactions +was performed identically on each node. The data covered by this field _should_ +be also reflected in the `AppHash`. The `AppHash` is provided by the application +and should be deterministically calculated by each node. This field could +therefore be removed on the grounds that its data is already reflected elsewhere. + +I would advocate for keeping this field. This field provides an additional check +for determinism across nodes. Logic to update the application hash is more +complicated for developers to implement because it relies either on building a +complete view of the state of the application data. The `Results` returned by +the application contain simple response codes and deterministic data bytes. +Leaving the field will allow for transaction execution issues that are not +correctly reflected in the `AppHash` to be more completely diagnosed. + +Take the case of mismatch of `LastResultsHash` between two nodes, A and B, where both +nodes report divergent values. If `A` and `B` both report +the same `AppHash`, then some non-deterministic behavior occurred that was not +accurately reflected in the `AppHash`. The issue caused by this +non-determinism may not show itself for several blocks, but catching the divergent +state earlier will improve the chances that a chain is able to recover. + +#### ValidatorsHash + +Both `ValidatorsHash` and `NextValidatorsHash` are included in the block +header. `Validatorshash` contains the hash of the [public key and voting power][val-hash] +of each validator in the active set for the current block and `NextValidatorsHash` +contains the same data but for the next height. + +This data is effectively redundant. Having both values present in the block +structure is helpful for light client verification. The light client is able to +easily determine if two sequential blocks used the same validator set by querying +only one header. + +`ValidatorsHash` is also important to the light client algorithm for performing block +validation. The light client uses this field to ensure that the validator set +it fetched from a full node is correct. It can be sure of the correctness of +the retrieved structure by hashing it and checking the hash against the `ValidatorsHash` +of the block it is verifying. Because a validator that the light client trusts +signed over the `ValidatorsHash`, it can be certain of the validity of the +structure. Without this check, phony validator sets could be handed to the light +client and the code tricked into believing a different validator set was present +at a height, opening up a major hole in the light client security model. + +This creates a recursive problem. To verify the validator set that signed the +block at height `H`, what information do we need? We could fetch the +`NextValidatorsHash` from height `H-1`, but how do we verify that that hash is correct? + +#### ProposerAddress + +The section below details a change to allow the `ProposerAddress` to be calculated +from a field added to the block. This would allow the `Address` to be dropped +from the block. Consumers of the chain could run the proposer selection [algorithm][proposer-selection] +to determine who proposed each block. + +I would advocate against this. Any consumer of the chain that wanted to +know which validator proposed a block would have to run the proposer selection +algorithm. This algorithm is not widely implemented, meaning that consumers +in other languages would need to implement the algorithm to determine a piece +of basic information about the chain. + +### Data to consider adding + +#### ProofOfLockRound + +The *proof of lock round* is the round of consensus for a height in which the +Tendermint algorithm observed a super majority of voting power on the network for +a block. + +Including this value in the block will allow validation of currently +un-validated metadata. Specifically, including this value will allow Tendermint +to validate that the `ProposerAddress` in the block is correct. Without knowing +the locked round number, Tendermint cannot calculate which validator was supposed +to propose a height. Because of this, our [validation logic][proposer-check] does not check that +the `ProposerAddress` included in the block corresponds to the validator that +proposed the height. Instead, the validation logic simply checks that the value +is an address of one of the known validators. + +Currently, we maintain the _committed round_ in the `Commit` for height `H`, which is +written into the block at height `H+1`. This value corresponds to the round in +which the proposer of height `H+1` received the commit for height `H`. The proof +of lock round would not subsume this value. + +### Additional possible updates + +#### Updates to storage + +Currently we store the [every piece of each block][save-block] in the `BlockStore`. +I suspect that this has lead to some mistakes in reasoning around the merits of +consolidating fields in the block. We could update the storage scheme we use to +store only some pieces of each block and still achieve a space savings without having +to change the block structure at all. + +The main way to achieve this would be by _no longer saving data that does not change_. +At each height we save a set of data that is unlikely to have changed from the +previous height in the block structure, this includes the `ValidatorAddress`es, +the `ValidatorsHash`, the `ChainID`. These do not need to be saved along with +_each_ block. We could easily save the value and the height at which the value +was updated and construct each block using the data that existed at the time. + +This document does not make any specific recommendations around storage since +that is likely to change with upcoming improvements to to the database infrastructure. +However, it's important to note that removing fields from the block for the +purposes of 'saving space' may not be that meaningful. We should instead focus +our attention of removing fields from the block that are no longer needed +for correct functioning of the protocol. + +#### Updates to propagation + +Block propagation suffers from the same issue that plagues block storage, we +propagate all of the contents of each block proto _even when these contents are redundant +or unchanged from previous blocks_. For example, we propagate the `ValidatorAddress`es +for each block in the `CommitSig` structure even when it never changed from a +previous height. We could achieve a speed-up in many cases by communicating the +hashes _first_ and letting peers request additional information when they do not +recognize the communicated hash. + +For example, in the case of the `ValidatorAddress`es, the node would first +communicate the `ValidatorsHash` of the block to its peers. The peers would +check their storage for a validator set matching the provided hash. If the peer +has a matching set, it would populate its local block structure with the +appropriate values from its store. If peer did not have a matching set, it would +issue a request to its peers, either via P2P or RPC for the data it did not have. + +Conceptually, this is very similar to how content addressing works in protocols +such as git where pushing a commit does not require pushing the entire contents +of the tree referenced by the commit. + +### Impact on light clients + +As outlined in the section [On Tendermint Blocks](#on-tendermint-blocks), there +is a distinction between what data is referenced in the Merkle root hash and the +contents of the proto structure we currently call the `Block`. + +Any changes to the Merkle root hash will necessarily be breaking for legacy light clients. +Either a soft-upgrades scheme will need to be implemented or a hard fork will +be required for chains and light clients to function with the new hashes. +This means that all of the additions and deletions from the Merkle root hash +proposed by this document will be light client breaking. + +Changes to the block structure alone are not necessarily light client breaking if the +data being hashed are identical and legacy views into the data are provided +for old light clients during transitions. For example, a newer version of the +block structure could move the `ValidatorAddress` field to a different field +in the block while still including it in the hashed data of the `LastCommitHash`. +As long as old light clients could still fetch the old data structure, then +this would not be light client breaking. + +## References + +[light-verify-trusting]: https://github.com/tendermint/tendermint/blob/208a15dadf01e4e493c187d8c04a55a61758c3cc/types/validation.go#L124 +[part-set-header]: https://github.com/tendermint/tendermint/blob/208a15dadf01e4e493c187d8c04a55a61758c3cc/types/part_set.go#L94 +[block-id]: https://github.com/tendermint/tendermint/blob/208a15dadf01e4e493c187d8c04a55a61758c3cc/types/block.go#L1090 +[psh-check]: https://github.com/tendermint/tendermint/blob/208a15dadf01e4e493c187d8c04a55a61758c3cc/types/part_set.go#L116 +[proposer-selection]: https://github.com/tendermint/tendermint/blob/208a15dadf01e4e493c187d8c04a55a61758c3cc/spec/consensus/proposer-selection.md +[chain-experiment]: https://github.com/williambanfield/tmtools/blob/master/hash-changes/RUN.txt +[val-hash]: https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/types/validator.go#L160 +[proposer-check]: https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/internal/state/validation.go#L102 +[save-block]: https://github.com/tendermint/tendermint/blob/59f0236b845c83009bffa62ed44053b04370b8a9/internal/store/store.go#L490 From 498657f128b8b624ae59b1ddd24492b9e99b2d2d Mon Sep 17 00:00:00 2001 From: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com> Date: Tue, 16 Aug 2022 14:59:24 +0100 Subject: [PATCH 3/7] test/fuzz: fix OSS-Fuzz build (#9183) --- test/fuzz/oss-fuzz-build.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100755 test/fuzz/oss-fuzz-build.sh diff --git a/test/fuzz/oss-fuzz-build.sh b/test/fuzz/oss-fuzz-build.sh new file mode 100755 index 000000000..deb3ef9f7 --- /dev/null +++ b/test/fuzz/oss-fuzz-build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# This script is invoked by OSS-Fuzz to run fuzz tests against Tendermint core. +# See https://github.com/google/oss-fuzz/blob/master/projects/tendermint/build.sh + +compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/mempool/v0 Fuzz mempool_v0_fuzzer +compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/mempool/v1 Fuzz mempool_v1_fuzzer +compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/p2p/addrbook Fuzz p2p_addrbook_fuzzer +compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/p2p/pex Fuzz p2p_pex_fuzzer +compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/p2p/secret_connection Fuzz p2p_secret_connection_fuzzer +compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/rpc/jsonrpc/server Fuzz rpc_jsonrpc_server_fuzzer From 3003e05581f8f88ac4ecf6c60068b1d0fb64efd6 Mon Sep 17 00:00:00 2001 From: Igor Konnov Date: Tue, 16 Aug 2022 16:12:04 +0200 Subject: [PATCH 4/7] Update type annotations in the TLA+ spec of Tendermint for accountability (#9263) * update Apalache type annotations and split evidence into 3 variables * remove the duplicate of AllPrevotes, due to merge --- spec/light-client/accountability/MC_n4_f1.tla | 58 +-- spec/light-client/accountability/MC_n4_f2.tla | 58 +-- .../accountability/MC_n4_f2_amnesia.tla | 62 +-- spec/light-client/accountability/MC_n4_f3.tla | 58 +-- spec/light-client/accountability/MC_n5_f1.tla | 58 +-- spec/light-client/accountability/MC_n5_f2.tla | 58 +-- spec/light-client/accountability/MC_n6_f1.tla | 58 +-- .../TendermintAccDebug_004_draft.tla | 21 +- .../TendermintAccInv_004_draft.tla | 111 +++--- .../TendermintAccTrace_004_draft.tla | 12 +- .../TendermintAcc_004_draft.tla | 361 +++++++++--------- spec/light-client/accountability/typedefs.tla | 53 ++- 12 files changed, 500 insertions(+), 468 deletions(-) diff --git a/spec/light-client/accountability/MC_n4_f1.tla b/spec/light-client/accountability/MC_n4_f1.tla index 62bcb30de..00444c24c 100644 --- a/spec/light-client/accountability/MC_n4_f1.tla +++ b/spec/light-client/accountability/MC_n4_f1.tla @@ -1,34 +1,38 @@ ----------------------------- MODULE MC_n4_f1 ------------------------------- -CONSTANT - \* @type: ROUND -> PROCESS; +CONSTANT + \* @type: $round -> $process; Proposer \* the variables declared in TendermintAcc3 VARIABLES - \* @type: PROCESS -> ROUND; - round, - \* @type: PROCESS -> STEP; - step, - \* @type: PROCESS -> VALUE; - decision, - \* @type: PROCESS -> VALUE; - lockedValue, - \* @type: PROCESS -> ROUND; - lockedRound, - \* @type: PROCESS -> VALUE; - validValue, - \* @type: PROCESS -> ROUND; - validRound, - \* @type: ROUND -> Set(PROPMESSAGE); - msgsPropose, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrevote, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrecommit, - \* @type: Set(MESSAGE); - evidence, - \* @type: ACTION; - action + \* @type: $process -> $round; + round, \* a process round number: Corr -> Rounds + \* @type: $process -> $step; + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + \* @type: $process -> $value; + decision, \* process decision: Corr -> ValuesOrNil + \* @type: $process -> $value; + lockedValue, \* a locked value: Corr -> ValuesOrNil + \* @type: $process -> $round; + lockedRound, \* a locked round: Corr -> RoundsOrNil + \* @type: $process -> $value; + validValue, \* a valid value: Corr -> ValuesOrNil + \* @type: $process -> $round; + validRound, \* a valid round: Corr -> RoundsOrNil + \* @type: $round -> Set($proposeMsg); + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + \* @type: Set($proposeMsg); + evidencePropose, \* the PROPOSE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrevote, \* the PREVOTE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrecommit, \* the PRECOMMIT messages used by some correct processes to make transitions + \* @type: $action; + action \* we use this variable to see which action was taken INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2", "c3"}, @@ -43,4 +47,4 @@ INSTANCE TendermintAccDebug_004_draft WITH ConstInit == \* the proposer is arbitrary -- works for safety Proposer \in [Rounds -> AllProcs] -============================================================================= +============================================================================= diff --git a/spec/light-client/accountability/MC_n4_f2.tla b/spec/light-client/accountability/MC_n4_f2.tla index baab2a21d..df6813816 100644 --- a/spec/light-client/accountability/MC_n4_f2.tla +++ b/spec/light-client/accountability/MC_n4_f2.tla @@ -1,34 +1,38 @@ ----------------------------- MODULE MC_n4_f2 ------------------------------- -CONSTANT - \* @type: ROUND -> PROCESS; +CONSTANT + \* @type: $round -> $process; Proposer \* the variables declared in TendermintAcc3 VARIABLES - \* @type: PROCESS -> ROUND; - round, - \* @type: PROCESS -> STEP; - step, - \* @type: PROCESS -> VALUE; - decision, - \* @type: PROCESS -> VALUE; - lockedValue, - \* @type: PROCESS -> ROUND; - lockedRound, - \* @type: PROCESS -> VALUE; - validValue, - \* @type: PROCESS -> ROUND; - validRound, - \* @type: ROUND -> Set(PROPMESSAGE); - msgsPropose, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrevote, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrecommit, - \* @type: Set(MESSAGE); - evidence, - \* @type: ACTION; - action + \* @type: $process -> $round; + round, \* a process round number: Corr -> Rounds + \* @type: $process -> $step; + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + \* @type: $process -> $value; + decision, \* process decision: Corr -> ValuesOrNil + \* @type: $process -> $value; + lockedValue, \* a locked value: Corr -> ValuesOrNil + \* @type: $process -> $round; + lockedRound, \* a locked round: Corr -> RoundsOrNil + \* @type: $process -> $value; + validValue, \* a valid value: Corr -> ValuesOrNil + \* @type: $process -> $round; + validRound, \* a valid round: Corr -> RoundsOrNil + \* @type: $round -> Set($proposeMsg); + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + \* @type: Set($proposeMsg); + evidencePropose, \* the PROPOSE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrevote, \* the PREVOTE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrecommit, \* the PRECOMMIT messages used by some correct processes to make transitions + \* @type: $action; + action \* we use this variable to see which action was taken INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2"}, @@ -43,4 +47,4 @@ INSTANCE TendermintAccDebug_004_draft WITH ConstInit == \* the proposer is arbitrary -- works for safety Proposer \in [Rounds -> AllProcs] -============================================================================= +============================================================================= diff --git a/spec/light-client/accountability/MC_n4_f2_amnesia.tla b/spec/light-client/accountability/MC_n4_f2_amnesia.tla index 940903a76..186bdacee 100644 --- a/spec/light-client/accountability/MC_n4_f2_amnesia.tla +++ b/spec/light-client/accountability/MC_n4_f2_amnesia.tla @@ -1,40 +1,44 @@ ---------------------- MODULE MC_n4_f2_amnesia ------------------------------- EXTENDS Sequences -CONSTANT - \* @type: ROUND -> PROCESS; +CONSTANT + \* @type: $round -> $process; Proposer \* the variables declared in TendermintAcc3 VARIABLES - \* @type: PROCESS -> ROUND; - round, - \* @type: PROCESS -> STEP; - step, - \* @type: PROCESS -> VALUE; - decision, - \* @type: PROCESS -> VALUE; - lockedValue, - \* @type: PROCESS -> ROUND; - lockedRound, - \* @type: PROCESS -> VALUE; - validValue, - \* @type: PROCESS -> ROUND; - validRound, - \* @type: ROUND -> Set(PROPMESSAGE); - msgsPropose, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrevote, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrecommit, - \* @type: Set(MESSAGE); - evidence, - \* @type: ACTION; - action + \* @type: $process -> $round; + round, \* a process round number: Corr -> Rounds + \* @type: $process -> $step; + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + \* @type: $process -> $value; + decision, \* process decision: Corr -> ValuesOrNil + \* @type: $process -> $value; + lockedValue, \* a locked value: Corr -> ValuesOrNil + \* @type: $process -> $round; + lockedRound, \* a locked round: Corr -> RoundsOrNil + \* @type: $process -> $value; + validValue, \* a valid value: Corr -> ValuesOrNil + \* @type: $process -> $round; + validRound, \* a valid round: Corr -> RoundsOrNil + \* @type: $round -> Set($proposeMsg); + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + \* @type: Set($proposeMsg); + evidencePropose, \* the PROPOSE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrevote, \* the PREVOTE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrecommit, \* the PRECOMMIT messages used by some correct processes to make transitions + \* @type: $action; + action \* we use this variable to see which action was taken \* the variable declared in TendermintAccTrace3 VARIABLE - \* @type: TRACE; + \* @type: $trace; toReplay INSTANCE TendermintAccTrace_004_draft WITH @@ -49,7 +53,7 @@ INSTANCE TendermintAccTrace_004_draft WITH "UponProposalInPropose", "UponProposalInPrevoteOrCommitAndPrevote", "UponProposalInPrecommitNoDecision", - "OnRoundCatchup", + "OnRoundCatchup", "UponProposalInPropose", "UponProposalInPrevoteOrCommitAndPrevote", "UponProposalInPrecommitNoDecision" @@ -59,4 +63,4 @@ INSTANCE TendermintAccTrace_004_draft WITH ConstInit == \* the proposer is arbitrary -- works for safety Proposer \in [Rounds -> AllProcs] -============================================================================= +============================================================================= diff --git a/spec/light-client/accountability/MC_n4_f3.tla b/spec/light-client/accountability/MC_n4_f3.tla index d4c64e6d0..eb54c573c 100644 --- a/spec/light-client/accountability/MC_n4_f3.tla +++ b/spec/light-client/accountability/MC_n4_f3.tla @@ -1,34 +1,38 @@ ----------------------------- MODULE MC_n4_f3 ------------------------------- -CONSTANT - \* @type: ROUND -> PROCESS; +CONSTANT + \* @type: $round -> $process; Proposer \* the variables declared in TendermintAcc3 VARIABLES - \* @type: PROCESS -> ROUND; - round, - \* @type: PROCESS -> STEP; - step, - \* @type: PROCESS -> VALUE; - decision, - \* @type: PROCESS -> VALUE; - lockedValue, - \* @type: PROCESS -> ROUND; - lockedRound, - \* @type: PROCESS -> VALUE; - validValue, - \* @type: PROCESS -> ROUND; - validRound, - \* @type: ROUND -> Set(PROPMESSAGE); - msgsPropose, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrevote, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrecommit, - \* @type: Set(MESSAGE); - evidence, - \* @type: ACTION; - action + \* @type: $process -> $round; + round, \* a process round number: Corr -> Rounds + \* @type: $process -> $step; + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + \* @type: $process -> $value; + decision, \* process decision: Corr -> ValuesOrNil + \* @type: $process -> $value; + lockedValue, \* a locked value: Corr -> ValuesOrNil + \* @type: $process -> $round; + lockedRound, \* a locked round: Corr -> RoundsOrNil + \* @type: $process -> $value; + validValue, \* a valid value: Corr -> ValuesOrNil + \* @type: $process -> $round; + validRound, \* a valid round: Corr -> RoundsOrNil + \* @type: $round -> Set($proposeMsg); + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + \* @type: Set($proposeMsg); + evidencePropose, \* the PROPOSE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrevote, \* the PREVOTE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrecommit, \* the PRECOMMIT messages used by some correct processes to make transitions + \* @type: $action; + action \* we use this variable to see which action was taken INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1"}, @@ -43,4 +47,4 @@ INSTANCE TendermintAccDebug_004_draft WITH ConstInit == \* the proposer is arbitrary -- works for safety Proposer \in [Rounds -> AllProcs] -============================================================================= +============================================================================= diff --git a/spec/light-client/accountability/MC_n5_f1.tla b/spec/light-client/accountability/MC_n5_f1.tla index 3d7ff979e..4238ab87d 100644 --- a/spec/light-client/accountability/MC_n5_f1.tla +++ b/spec/light-client/accountability/MC_n5_f1.tla @@ -1,34 +1,38 @@ ----------------------------- MODULE MC_n5_f1 ------------------------------- -CONSTANT - \* @type: ROUND -> PROCESS; +CONSTANT + \* @type: $round -> $process; Proposer \* the variables declared in TendermintAcc3 VARIABLES - \* @type: PROCESS -> ROUND; - round, - \* @type: PROCESS -> STEP; - step, - \* @type: PROCESS -> VALUE; - decision, - \* @type: PROCESS -> VALUE; - lockedValue, - \* @type: PROCESS -> ROUND; - lockedRound, - \* @type: PROCESS -> VALUE; - validValue, - \* @type: PROCESS -> ROUND; - validRound, - \* @type: ROUND -> Set(PROPMESSAGE); - msgsPropose, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrevote, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrecommit, - \* @type: Set(MESSAGE); - evidence, - \* @type: ACTION; - action + \* @type: $process -> $round; + round, \* a process round number: Corr -> Rounds + \* @type: $process -> $step; + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + \* @type: $process -> $value; + decision, \* process decision: Corr -> ValuesOrNil + \* @type: $process -> $value; + lockedValue, \* a locked value: Corr -> ValuesOrNil + \* @type: $process -> $round; + lockedRound, \* a locked round: Corr -> RoundsOrNil + \* @type: $process -> $value; + validValue, \* a valid value: Corr -> ValuesOrNil + \* @type: $process -> $round; + validRound, \* a valid round: Corr -> RoundsOrNil + \* @type: $round -> Set($proposeMsg); + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + \* @type: Set($proposeMsg); + evidencePropose, \* the PROPOSE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrevote, \* the PREVOTE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrecommit, \* the PRECOMMIT messages used by some correct processes to make transitions + \* @type: $action; + action \* we use this variable to see which action was taken INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2", "c3", "c4"}, @@ -43,4 +47,4 @@ INSTANCE TendermintAccDebug_004_draft WITH ConstInit == \* the proposer is arbitrary -- works for safety Proposer \in [Rounds -> AllProcs] -============================================================================= +============================================================================= diff --git a/spec/light-client/accountability/MC_n5_f2.tla b/spec/light-client/accountability/MC_n5_f2.tla index 24400dc07..e928b1929 100644 --- a/spec/light-client/accountability/MC_n5_f2.tla +++ b/spec/light-client/accountability/MC_n5_f2.tla @@ -1,34 +1,38 @@ ----------------------------- MODULE MC_n5_f2 ------------------------------- -CONSTANT - \* @type: ROUND -> PROCESS; +CONSTANT + \* @type: $round -> $process; Proposer \* the variables declared in TendermintAcc3 VARIABLES - \* @type: PROCESS -> ROUND; - round, - \* @type: PROCESS -> STEP; - step, - \* @type: PROCESS -> VALUE; - decision, - \* @type: PROCESS -> VALUE; - lockedValue, - \* @type: PROCESS -> ROUND; - lockedRound, - \* @type: PROCESS -> VALUE; - validValue, - \* @type: PROCESS -> ROUND; - validRound, - \* @type: ROUND -> Set(PROPMESSAGE); - msgsPropose, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrevote, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrecommit, - \* @type: Set(MESSAGE); - evidence, - \* @type: ACTION; - action + \* @type: $process -> $round; + round, \* a process round number: Corr -> Rounds + \* @type: $process -> $step; + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + \* @type: $process -> $value; + decision, \* process decision: Corr -> ValuesOrNil + \* @type: $process -> $value; + lockedValue, \* a locked value: Corr -> ValuesOrNil + \* @type: $process -> $round; + lockedRound, \* a locked round: Corr -> RoundsOrNil + \* @type: $process -> $value; + validValue, \* a valid value: Corr -> ValuesOrNil + \* @type: $process -> $round; + validRound, \* a valid round: Corr -> RoundsOrNil + \* @type: $round -> Set($proposeMsg); + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + \* @type: Set($proposeMsg); + evidencePropose, \* the PROPOSE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrevote, \* the PREVOTE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrecommit, \* the PRECOMMIT messages used by some correct processes to make transitions + \* @type: $action; + action \* we use this variable to see which action was taken INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2", "c3"}, @@ -43,4 +47,4 @@ INSTANCE TendermintAccDebug_004_draft WITH ConstInit == \* the proposer is arbitrary -- works for safety Proposer \in [Rounds -> AllProcs] -============================================================================= +============================================================================= diff --git a/spec/light-client/accountability/MC_n6_f1.tla b/spec/light-client/accountability/MC_n6_f1.tla index a58f8c78a..5941ada33 100644 --- a/spec/light-client/accountability/MC_n6_f1.tla +++ b/spec/light-client/accountability/MC_n6_f1.tla @@ -1,34 +1,38 @@ ----------------------------- MODULE MC_n6_f1 ------------------------------- -CONSTANT - \* @type: ROUND -> PROCESS; +CONSTANT + \* @type: $round -> $process; Proposer \* the variables declared in TendermintAcc3 VARIABLES - \* @type: PROCESS -> ROUND; - round, - \* @type: PROCESS -> STEP; - step, - \* @type: PROCESS -> VALUE; - decision, - \* @type: PROCESS -> VALUE; - lockedValue, - \* @type: PROCESS -> ROUND; - lockedRound, - \* @type: PROCESS -> VALUE; - validValue, - \* @type: PROCESS -> ROUND; - validRound, - \* @type: ROUND -> Set(PROPMESSAGE); - msgsPropose, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrevote, - \* @type: ROUND -> Set(PREMESSAGE); - msgsPrecommit, - \* @type: Set(MESSAGE); - evidence, - \* @type: ACTION; - action + \* @type: $process -> $round; + round, \* a process round number: Corr -> Rounds + \* @type: $process -> $step; + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + \* @type: $process -> $value; + decision, \* process decision: Corr -> ValuesOrNil + \* @type: $process -> $value; + lockedValue, \* a locked value: Corr -> ValuesOrNil + \* @type: $process -> $round; + lockedRound, \* a locked round: Corr -> RoundsOrNil + \* @type: $process -> $value; + validValue, \* a valid value: Corr -> ValuesOrNil + \* @type: $process -> $round; + validRound, \* a valid round: Corr -> RoundsOrNil + \* @type: $round -> Set($proposeMsg); + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + \* @type: $round -> Set($preMsg); + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + \* @type: Set($proposeMsg); + evidencePropose, \* the PROPOSE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrevote, \* the PREVOTE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrecommit, \* the PRECOMMIT messages used by some correct processes to make transitions + \* @type: $action; + action \* we use this variable to see which action was taken INSTANCE TendermintAccDebug_004_draft WITH Corr <- {"c1", "c2", "c3", "c4", "c5"}, @@ -43,4 +47,4 @@ INSTANCE TendermintAccDebug_004_draft WITH ConstInit == \* the proposer is arbitrary -- works for safety Proposer \in [Rounds -> AllProcs] -============================================================================= +============================================================================= diff --git a/spec/light-client/accountability/TendermintAccDebug_004_draft.tla b/spec/light-client/accountability/TendermintAccDebug_004_draft.tla index 9281b8726..ab72f9616 100644 --- a/spec/light-client/accountability/TendermintAccDebug_004_draft.tla +++ b/spec/light-client/accountability/TendermintAccDebug_004_draft.tla @@ -19,7 +19,9 @@ NFaultyPrecommits == 6 \* the number of injected faulty PRECOMMIT messages \* rounds to sets of messages. \* Importantly, there will be exactly k messages in the image of msgFun. \* We use this action to produce k faults in an initial state. -\* @type: (ROUND -> Set(MESSAGE), Set(MESSAGE), Int) => Bool; +\* @type: ($round -> Set({ round: $round, a }), +\* Set({ round: $round, a }), Int) +\* => Bool; ProduceFaults(msgFun, From, k) == \E f \in [1..k -> From]: msgFun = [r \in Rounds |-> {m \in {f[i]: i \in 1..k}: m.round = r}] @@ -33,10 +35,12 @@ InitNoFaults == /\ lockedRound = [p \in Corr |-> NilRound] /\ validValue = [p \in Corr |-> NilValue] /\ validRound = [p \in Corr |-> NilRound] - /\ msgsPropose = [r \in Rounds |-> EmptyMsgSet] - /\ msgsPrevote = [r \in Rounds |-> EmptyMsgSet] - /\ msgsPrecommit = [r \in Rounds |-> EmptyMsgSet] - /\ evidence = EmptyMsgSet + /\ msgsPropose = [r \in Rounds |-> {}] + /\ msgsPrevote = [r \in Rounds |-> {}] + /\ msgsPrecommit = [r \in Rounds |-> {}] + /\ evidencePropose = {} + /\ evidencePrevote = {} + /\ evidencePrecommit = {} (* A specialized version of Init that injects NFaultyProposals proposals, @@ -60,7 +64,9 @@ InitFewFaults == [type: {"PROPOSAL"}, src: Faulty, round: Rounds, proposal: Values, validRound: Rounds \cup {NilRound}], NFaultyProposals) - /\ evidence = EmptyMsgSet + /\ evidencePropose = {} + /\ evidencePrevote = {} + /\ evidencePrecommit = {} \* Add faults incrementally NextWithFaults == @@ -68,7 +74,8 @@ NextWithFaults == \/ Next \* or a faulty process sends a message \//\ UNCHANGED <> + lockedRound, validValue, validRound, + evidencePropose, evidencePrevote, evidencePrecommit>> /\ \E p \in Faulty: \E r \in Rounds: \//\ UNCHANGED <> diff --git a/spec/light-client/accountability/TendermintAccInv_004_draft.tla b/spec/light-client/accountability/TendermintAccInv_004_draft.tla index 2eeec1fb2..5f9bfcf4e 100644 --- a/spec/light-client/accountability/TendermintAccInv_004_draft.tla +++ b/spec/light-client/accountability/TendermintAccInv_004_draft.tla @@ -10,25 +10,22 @@ *) EXTENDS TendermintAcc_004_draft - + (************************** TYPE INVARIANT ***********************************) (* first, we define the sets of all potential messages *) -\* @type: Set(PROPMESSAGE); -AllProposals == +AllProposals == [type: {"PROPOSAL"}, src: AllProcs, round: Rounds, proposal: ValuesOrNil, validRound: RoundsOrNil] - -\* @type: Set(PREMESSAGE); + AllPrevotes == [type: {"PREVOTE"}, src: AllProcs, round: Rounds, id: ValuesOrNil] -\* @type: Set(PREMESSAGE); AllPrecommits == [type: {"PRECOMMIT"}, src: AllProcs, @@ -50,7 +47,9 @@ TypeOK == /\ BenignRoundsInMessages(msgsPrevote) /\ msgsPrecommit \in [Rounds -> SUBSET AllPrecommits] /\ BenignRoundsInMessages(msgsPrecommit) - /\ evidence \in SUBSET (AllProposals \union AllPrevotes \union AllPrecommits) + /\ evidencePropose \in SUBSET AllProposals + /\ evidencePrevote \in SUBSET AllPrevotes + /\ evidencePrecommit \in SUBSET AllPrecommits /\ action \in { "Init", "InsertProposal", @@ -69,13 +68,12 @@ TypeOK == EvidenceContainsMessages == \* evidence contains only the messages from: \* msgsPropose, msgsPrevote, and msgsPrecommit - \A m \in evidence: - LET r == m.round - t == m.type - IN - CASE t = "PROPOSAL" -> m \in msgsPropose[r] - [] t = "PREVOTE" -> m \in msgsPrevote[r] - [] OTHER -> m \in msgsPrecommit[r] + /\ \A m \in evidencePropose: + m \in msgsPropose[m.round] + /\ \A m \in evidencePrevote: + m \in msgsPrevote[m.round] + /\ \A m \in evidencePrecommit: + m \in msgsPrecommit[m.round] NoFutureMessagesForLargerRounds(p) == \* a correct process does not send messages for the future rounds @@ -89,14 +87,14 @@ NoFutureMessagesForCurrentRound(p) == LET r == round[p] IN /\ Proposer[r] = p \/ \A m \in msgsPropose[r]: m.src /= p /\ \/ step[p] \in {"PREVOTE", "PRECOMMIT", "DECIDED"} - \/ \A m \in msgsPrevote[r]: m.src /= p + \/ \A m \in msgsPrevote[r]: m.src /= p /\ \/ step[p] \in {"PRECOMMIT", "DECIDED"} - \/ \A m \in msgsPrecommit[r]: m.src /= p - + \/ \A m \in msgsPrecommit[r]: m.src /= p + \* the correct processes never send future messages AllNoFutureMessagesSent == \A p \in Corr: - /\ NoFutureMessagesForCurrentRound(p) + /\ NoFutureMessagesForCurrentRound(p) /\ NoFutureMessagesForLargerRounds(p) \* a correct process in the PREVOTE state has sent a PREVOTE message @@ -105,9 +103,9 @@ IfInPrevoteThenSentPrevote(p) == \E m \in msgsPrevote[round[p]]: /\ m.id \in ValidValues \cup { NilValue } /\ m.src = p - + AllIfInPrevoteThenSentPrevote == - \A p \in Corr: IfInPrevoteThenSentPrevote(p) + \A p \in Corr: IfInPrevoteThenSentPrevote(p) \* a correct process in the PRECOMMIT state has sent a PRECOMMIT message IfInPrecommitThenSentPrecommit(p) == @@ -115,42 +113,44 @@ IfInPrecommitThenSentPrecommit(p) == \E m \in msgsPrecommit[round[p]]: /\ m.id \in ValidValues \cup { NilValue } /\ m.src = p - + AllIfInPrecommitThenSentPrecommit == - \A p \in Corr: IfInPrecommitThenSentPrecommit(p) + \A p \in Corr: IfInPrecommitThenSentPrecommit(p) \* a process in the PRECOMMIT state has sent a PRECOMMIT message IfInDecidedThenValidDecision(p) == step[p] = "DECIDED" <=> decision[p] \in ValidValues - + AllIfInDecidedThenValidDecision == - \A p \in Corr: IfInDecidedThenValidDecision(p) + \A p \in Corr: IfInDecidedThenValidDecision(p) \* a decided process should have received a proposal on its decision IfInDecidedThenReceivedProposal(p) == step[p] = "DECIDED" => \E r \in Rounds: \* r is not necessarily round[p] - /\ \E m \in msgsPropose[r] \intersect evidence: + /\ \E m \in msgsPropose[r] \intersect evidencePropose: /\ m.src = Proposer[r] /\ m.proposal = decision[p] \* not inductive: /\ m.src \in Corr => (m.validRound <= r) - + AllIfInDecidedThenReceivedProposal == \A p \in Corr: - IfInDecidedThenReceivedProposal(p) + IfInDecidedThenReceivedProposal(p) \* a decided process has received two-thirds of precommit messages IfInDecidedThenReceivedTwoThirds(p) == step[p] = "DECIDED" => \E r \in Rounds: - LET PV == - { m \in msgsPrecommit[r] \intersect evidence: m.id = decision[p] } + LET PV == { + m \in msgsPrecommit[r] \intersect evidencePrecommit: + m.id = decision[p] + } IN Cardinality(PV) >= THRESHOLD2 - + AllIfInDecidedThenReceivedTwoThirds == \A p \in Corr: - IfInDecidedThenReceivedTwoThirds(p) + IfInDecidedThenReceivedTwoThirds(p) \* for a round r, there is proposal by the round proposer for a valid round vr ProposalInRound(r, proposedVal, vr) == @@ -160,7 +160,7 @@ ProposalInRound(r, proposedVal, vr) == /\ m.validRound = vr TwoThirdsPrevotes(vr, v) == - LET PV == { mm \in msgsPrevote[vr] \intersect evidence: mm.id = v } IN + LET PV == { mm \in msgsPrevote[vr] \intersect evidencePrevote: mm.id = v } IN Cardinality(PV) >= THRESHOLD2 \* if a process sends a PREVOTE, then there are three possibilities: @@ -189,8 +189,9 @@ IfSentPrecommitThenReceivedTwoThirds == \A mpc \in msgsPrecommit[r]: mpc.src \in Corr => \/ /\ mpc.id \in ValidValues - /\ LET PV == - { m \in msgsPrevote[r] \intersect evidence: m.id = mpc.id } + /\ LET PV == { + m \in msgsPrevote[r] \intersect evidencePrevote: m.id = mpc.id + } IN Cardinality(PV) >= THRESHOLD2 \/ /\ mpc.id = NilValue @@ -208,22 +209,22 @@ IfSentPrecommitThenSentPrevote == \* there is a locked round if a only if there is a locked value LockedRoundIffLockedValue(p) == (lockedRound[p] = NilRound) <=> (lockedValue[p] = NilValue) - + AllLockedRoundIffLockedValue == \A p \in Corr: LockedRoundIffLockedValue(p) - + \* when a process locked a round, it must have sent a precommit on the locked value. IfLockedRoundThenSentCommit(p) == lockedRound[p] /= NilRound => \E r \in { rr \in Rounds: rr <= round[p] }: \E m \in msgsPrecommit[r]: m.src = p /\ m.id = lockedValue[p] - + AllIfLockedRoundThenSentCommit == \A p \in Corr: IfLockedRoundThenSentCommit(p) - + \* a process always locks the latest round, for which it has sent a PRECOMMIT LatestPrecommitHasLockedRound(p) == LET pPrecommits == @@ -237,7 +238,7 @@ LatestPrecommitHasLockedRound(p) == IN /\ lockedRound[p] = latest.round /\ lockedValue[p] = latest.id - + AllLatestPrecommitHasLockedRound == \A p \in Corr: LatestPrecommitHasLockedRound(p) @@ -245,7 +246,7 @@ AllLatestPrecommitHasLockedRound == \* Every correct process sends only one value or NilValue. \* This test has quantifier alternation -- a threat to all decision procedures. \* Luckily, the sets Corr and ValidValues are small. -\* @type: (ROUND, ROUND -> Set(PREMESSAGE)) => Bool; +\* @type: ($round, $round -> Set($preMsg)) => Bool; NoEquivocationByCorrect(r, msgs) == \A p \in Corr: \E v \in ValidValues \union {NilValue}: @@ -254,22 +255,22 @@ NoEquivocationByCorrect(r, msgs) == \/ m.id = v \* a proposer nevers sends two values -\* @type: (ROUND, ROUND -> Set(PROPMESSAGE)) => Bool; +\* @type: ($round, $round -> Set($proposeMsg)) => Bool; ProposalsByProposer(r, msgs) == \* if the proposer is not faulty, it sends only one value \E v \in ValidValues: \A m \in msgs[r]: \/ m.src \in Faulty \/ m.src = Proposer[r] /\ m.proposal = v - + AllNoEquivocationByCorrect == \A r \in Rounds: - /\ ProposalsByProposer(r, msgsPropose) - /\ NoEquivocationByCorrect(r, msgsPrevote) - /\ NoEquivocationByCorrect(r, msgsPrecommit) + /\ ProposalsByProposer(r, msgsPropose) + /\ NoEquivocationByCorrect(r, msgsPrevote) + /\ NoEquivocationByCorrect(r, msgsPrecommit) \* construct the set of the message senders -\* @type: (Set(MESSAGE)) => Set(PROCESS); +\* @type: (Set({ src: $process, a })) => Set($process); Senders(M) == { m.src: m \in M } \* The final piece by Josef Widder: @@ -286,15 +287,15 @@ PrecommitsLockValue == LET Prevotes == {m \in msgsPrevote[fr]: m.id = w} IN Cardinality(Senders(Prevotes)) < THRESHOLD2 - + \* a combination of all lemmas Inv == /\ EvidenceContainsMessages /\ AllNoFutureMessagesSent /\ AllIfInPrevoteThenSentPrevote /\ AllIfInPrecommitThenSentPrecommit - /\ AllIfInDecidedThenReceivedProposal - /\ AllIfInDecidedThenReceivedTwoThirds + /\ AllIfInDecidedThenReceivedProposal + /\ AllIfInDecidedThenReceivedTwoThirds /\ AllIfInDecidedThenValidDecision /\ AllLockedRoundIffLockedValue /\ AllIfLockedRoundThenSentCommit @@ -306,8 +307,8 @@ Inv == /\ PrecommitsLockValue \* this is the inductive invariant we like to check -TypedInv == TypeOK /\ Inv - +TypedInv == TypeOK /\ Inv + \* UNUSED FOR SAFETY ValidRoundNotSmallerThanLockedRound(p) == validRound[p] >= lockedRound[p] @@ -325,10 +326,10 @@ IfValidRoundThenTwoThirds(p) == \/ validRound[p] = NilRound \/ LET PV == { m \in msgsPrevote[validRound[p]]: m.id = validValue[p] } IN Cardinality(PV) >= THRESHOLD2 - + \* UNUSED FOR SAFETY AllIfValidRoundThenTwoThirds == - \A p \in Corr: IfValidRoundThenTwoThirds(p) + \A p \in Corr: IfValidRoundThenTwoThirds(p) \* a valid round can be only set to a valid value that was proposed earlier IfValidRoundThenProposal(p) == @@ -372,5 +373,5 @@ THEOREM AgreementWhenLessThanThirdFaulty == THEOREM AgreementOrFork == ~FaultyQuorum /\ TypedInv => Accountability -============================================================================= - +============================================================================= + diff --git a/spec/light-client/accountability/TendermintAccTrace_004_draft.tla b/spec/light-client/accountability/TendermintAccTrace_004_draft.tla index bbc708063..decd6b733 100644 --- a/spec/light-client/accountability/TendermintAccTrace_004_draft.tla +++ b/spec/light-client/accountability/TendermintAccTrace_004_draft.tla @@ -3,22 +3,22 @@ When Apalache is running too slow and we have an idea of a counterexample, we use this module to restrict the behaviors only to certain actions. Once the whole trace is replayed, the system deadlocks. - + Version 1. Igor Konnov, 2020. *) -EXTENDS Sequences, Apalache, TendermintAcc_004_draft +EXTENDS Sequences, Apalache, typedefs, TendermintAcc_004_draft \* a sequence of action names that should appear in the given order, \* excluding "Init" -CONSTANT - \* @type: TRACE; +CONSTANT + \* @type: $trace; Trace -VARIABLE - \* @type: TRACE; +VARIABLE + \* @type: $trace; toReplay TraceInit == diff --git a/spec/light-client/accountability/TendermintAcc_004_draft.tla b/spec/light-client/accountability/TendermintAcc_004_draft.tla index fccf993d4..0749e1b49 100644 --- a/spec/light-client/accountability/TendermintAcc_004_draft.tla +++ b/spec/light-client/accountability/TendermintAcc_004_draft.tla @@ -21,7 +21,7 @@ Byzantine processes can demonstrate arbitrary behavior, including no communication. We show that if agreement is violated, then the Byzantine - processes demonstrate one of the two behaviors: + processes demonstrate one of the two behaviours: - Equivocation: a Byzantine process may send two different values in the same round. @@ -29,6 +29,7 @@ - Amnesia: a Byzantine process may lock a value without unlocking the previous value that it has locked in the past. + * Version 5. Refactor evidence, migrate to Apalache Type System 1.2. * Version 4. Remove defective processes, fix bugs, collect global evidence. * Version 3. Modular and parameterized definitions. * Version 2. Bugfixes in the spec and an inductive invariant. @@ -41,34 +42,34 @@ EXTENDS Integers, FiniteSets, typedefs (********************* PROTOCOL PARAMETERS **********************************) CONSTANTS - \* @type: Set(PROCESS); - Corr, \* the set of correct processes - \* @type: Set(PROCESS); + \* @type: Set($process); + Corr, \* the set of correct processes + \* @type: Set($process); Faulty, \* the set of Byzantine processes, may be empty - \* @type: Int; + \* @type: Int; N, \* the total number of processes: correct, defective, and Byzantine - \* @type: Int; + \* @type: Int; T, \* an upper bound on the number of Byzantine processes - \* @type: Set(VALUE); + \* @type: Set($value); ValidValues, \* the set of valid values, proposed both by correct and faulty - \* @type: Set(VALUE); + \* @type: Set($value); InvalidValues, \* the set of invalid values, never proposed by the correct ones - \* @type: ROUND; + \* @type: $round; MaxRound, \* the maximal round number - \* @type: ROUND -> PROCESS; + \* @type: $round -> $process; Proposer \* the proposer function from Rounds to AllProcs ASSUME(N = Cardinality(Corr \union Faulty)) (*************************** DEFINITIONS ************************************) AllProcs == Corr \union Faulty \* the set of all processes -\* @type: Set(ROUND); +\* @type: Set($round); Rounds == 0..MaxRound \* the set of potential rounds -\* @type: ROUND; +\* @type: $round; NilRound == -1 \* a special value to denote a nil round, outside of Rounds RoundsOrNil == Rounds \union {NilRound} Values == ValidValues \union InvalidValues \* the set of all values -\* @type: VALUE; +\* @type: $value; NilValue == "None" \* a special value for a nil round, outside of Values ValuesOrNil == Values \union {NilValue} @@ -83,106 +84,105 @@ IsValid(v) == v \in ValidValues THRESHOLD1 == T + 1 \* at least one process is not faulty THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T -(********************* TYPE ANNOTATIONS FOR APALACHE **************************) - -\* An empty set of messages -\* @type: Set(MESSAGE); -EmptyMsgSet == {} - (********************* PROTOCOL STATE VARIABLES ******************************) VARIABLES - \* @type: PROCESS -> ROUND; + \* @type: $process -> $round; round, \* a process round number: Corr -> Rounds - \* @type: PROCESS -> STEP; + \* @type: $process -> $step; step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } - \* @type: PROCESS -> VALUE; + \* @type: $process -> $value; decision, \* process decision: Corr -> ValuesOrNil - \* @type: PROCESS -> VALUE; + \* @type: $process -> $value; lockedValue, \* a locked value: Corr -> ValuesOrNil - \* @type: PROCESS -> ROUND; + \* @type: $process -> $round; lockedRound, \* a locked round: Corr -> RoundsOrNil - \* @type: PROCESS -> VALUE; + \* @type: $process -> $value; validValue, \* a valid value: Corr -> ValuesOrNil - \* @type: PROCESS -> ROUND; + \* @type: $process -> $round; validRound \* a valid round: Corr -> RoundsOrNil \* book-keeping variables VARIABLES - \* @type: ROUND -> Set(PROPMESSAGE); + \* @type: $round -> Set($proposeMsg); msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages - \* @type: ROUND -> Set(PREMESSAGE); + \* @type: $round -> Set($preMsg); msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages - \* @type: ROUND -> Set(PREMESSAGE); + \* @type: $round -> Set($preMsg); msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages - \* @type: Set(MESSAGE); - evidence, \* the messages that were used by the correct processes to make transitions - \* @type: ACTION; + \* @type: Set($proposeMsg); + evidencePropose, \* the PROPOSE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrevote, \* the PREVOTE messages used by some correct processes to make transitions + \* @type: Set($preMsg); + evidencePrecommit, \* the PRECOMMIT messages used by some correct processes to make transitions + \* @type: $action; action \* we use this variable to see which action was taken -(* to see a type invariant, check TendermintAccInv3 *) - +(* to see a type invariant, check TendermintAccInv3 *) + \* a handy definition used in UNCHANGED vars == <> + validValue, validRound, msgsPropose, msgsPrevote, msgsPrecommit, + evidencePropose, evidencePrevote, evidencePrecommit>> (********************* PROTOCOL INITIALIZATION ******************************) -\* @type: (ROUND) => Set(PROPMESSAGE); +\* @type: ($round) => Set($proposeMsg); FaultyProposals(r) == [ - type : {"PROPOSAL"}, + type : {"PROPOSAL"}, src : Faulty, - round : {r}, - proposal : Values, + round : {r}, + proposal : Values, validRound: RoundsOrNil ] -\* @type: Set(PROPMESSAGE); +\* @type: Set($proposeMsg); AllFaultyProposals == [ - type : {"PROPOSAL"}, + type : {"PROPOSAL"}, src : Faulty, - round : Rounds, - proposal : Values, + round : Rounds, + proposal : Values, validRound: RoundsOrNil ] -\* @type: (ROUND) => Set(PREMESSAGE); +\* @type: ($round) => Set($preMsg); FaultyPrevotes(r) == [ - type : {"PREVOTE"}, - src : Faulty, - round: {r}, + type : {"PREVOTE"}, + src : Faulty, + round: {r}, id : Values ] -\* @type: Set(PREMESSAGE); -AllFaultyPrevotes == +\* @type: Set($preMsg); +AllFaultyPrevotes == [ - type : {"PREVOTE"}, - src : Faulty, - round: Rounds, + type : {"PREVOTE"}, + src : Faulty, + round: Rounds, id : Values ] -\* @type: (ROUND) => Set(PREMESSAGE); +\* @type: ($round) => Set($preMsg); FaultyPrecommits(r) == [ - type : {"PRECOMMIT"}, - src : Faulty, - round: {r}, + type : {"PRECOMMIT"}, + src : Faulty, + round: {r}, id : Values ] -\* @type: Set(PREMESSAGE); +\* @type: Set($preMsg); AllFaultyPrecommits == [ - type : {"PRECOMMIT"}, - src : Faulty, - round: Rounds, + type : {"PRECOMMIT"}, + src : Faulty, + round: Rounds, id : Values ] -\* @type: (ROUND -> Set(MESSAGE)) => Bool; +\* @type: ($round -> Set({ round: Int, a })) => Bool; BenignRoundsInMessages(msgfun) == \* the message function never contains a message for a wrong round \A r \in Rounds: @@ -204,50 +204,43 @@ Init == /\ BenignRoundsInMessages(msgsPropose) /\ BenignRoundsInMessages(msgsPrevote) /\ BenignRoundsInMessages(msgsPrecommit) - /\ evidence = EmptyMsgSet + /\ evidencePropose = {} + /\ evidencePrevote = {} + /\ evidencePrecommit = {} /\ action = "Init" (************************ MESSAGE PASSING ********************************) -\* @type: (PROCESS, ROUND, VALUE, ROUND) => Bool; +\* @type: ($process, $round, $value, $round) => Bool; BroadcastProposal(pSrc, pRound, pProposal, pValidRound) == - LET - \* @type: PROPMESSAGE; - newMsg == - [ - type |-> "PROPOSAL", - src |-> pSrc, + LET newMsg == [ + type |-> "PROPOSAL", + src |-> pSrc, round |-> pRound, - proposal |-> pProposal, + proposal |-> pProposal, validRound |-> pValidRound - ] + ] IN msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}] -\* @type: (PROCESS, ROUND, VALUE) => Bool; +\* @type: ($process, $round, $value) => Bool; BroadcastPrevote(pSrc, pRound, pId) == - LET - \* @type: PREMESSAGE; - newMsg == - [ + LET newMsg == [ type |-> "PREVOTE", - src |-> pSrc, - round |-> pRound, + src |-> pSrc, + round |-> pRound, id |-> pId - ] + ] IN msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}] -\* @type: (PROCESS, ROUND, VALUE) => Bool; +\* @type: ($process, $round, $value) => Bool; BroadcastPrecommit(pSrc, pRound, pId) == - LET - \* @type: PREMESSAGE; - newMsg == - [ + LET newMsg == [ type |-> "PRECOMMIT", - src |-> pSrc, - round |-> pRound, + src |-> pSrc, + round |-> pRound, id |-> pId - ] + ] IN msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}] @@ -257,46 +250,42 @@ BroadcastPrecommit(pSrc, pRound, pId) == StartRound(p, r) == /\ step[p] /= "DECIDED" \* a decided process does not participate in consensus /\ round' = [round EXCEPT ![p] = r] - /\ step' = [step EXCEPT ![p] = "PROPOSE"] + /\ step' = [step EXCEPT ![p] = "PROPOSE"] \* lines 14-19, a proposal may be sent later -\* @type: (PROCESS) => Bool; -InsertProposal(p) == +\* @type: $process => Bool; +InsertProposal(p) == LET r == round[p] IN /\ p = Proposer[r] /\ step[p] = "PROPOSE" \* if the proposer is sending a proposal, then there are no other proposals \* by the correct processes for the same round /\ \A m \in msgsPropose[r]: m.src /= p - /\ \E v \in ValidValues: - LET - \* @type: VALUE; - proposal == - IF validValue[p] /= NilValue - THEN validValue[p] - ELSE v + /\ \E v \in ValidValues: + LET proposal == + IF validValue[p] /= NilValue + THEN validValue[p] + ELSE v IN BroadcastProposal(p, round[p], proposal, validRound[p]) - /\ UNCHANGED <> + /\ UNCHANGED <> /\ action' = "InsertProposal" \* lines 22-27 UponProposalInPropose(p) == \E v \in Values: /\ step[p] = "PROPOSE" (* line 22 *) - /\ LET - \* @type: PROPMESSAGE; - msg == - [ - type |-> "PROPOSAL", + /\ LET msg == [ + type |-> "PROPOSAL", src |-> Proposer[round[p]], - round |-> round[p], - proposal |-> v, + round |-> round[p], + proposal |-> v, validRound |-> NilRound - ] + ] IN /\ msg \in msgsPropose[round[p]] \* line 22 - /\ evidence' = {msg} \union evidence + /\ evidencePropose' = {msg} \union evidencePropose /\ LET mid == (* line 23 *) IF IsValid(v) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v) THEN Id(v) @@ -305,28 +294,27 @@ UponProposalInPropose(p) == BroadcastPrevote(p, round[p], mid) \* lines 24-26 /\ step' = [step EXCEPT ![p] = "PREVOTE"] /\ UNCHANGED <> + validValue, validRound, msgsPropose, msgsPrecommit, + evidencePrevote, evidencePrecommit>> /\ action' = "UponProposalInPropose" -\* lines 28-33 +\* lines 28-33 UponProposalInProposeAndPrevote(p) == \E v \in Values, vr \in Rounds: /\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < round[p] \* line 28, the while part - /\ LET - \* @type: PROPMESSAGE; - msg == - [ - type |-> "PROPOSAL", + /\ LET msg == [ + type |-> "PROPOSAL", src |-> Proposer[round[p]], - round |-> round[p], - proposal |-> v, + round |-> round[p], + proposal |-> v, validRound |-> vr - ] + ] IN /\ msg \in msgsPropose[round[p]] \* line 28 /\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(v) } IN /\ Cardinality(PV) >= THRESHOLD2 \* line 28 - /\ evidence' = PV \union {msg} \union evidence + /\ evidencePropose' = {msg} \union evidencePropose + /\ evidencePrevote' = PV \union evidencePrevote /\ LET mid == (* line 29 *) IF IsValid(v) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v) THEN Id(v) @@ -335,9 +323,10 @@ UponProposalInProposeAndPrevote(p) == BroadcastPrevote(p, round[p], mid) \* lines 24-26 /\ step' = [step EXCEPT ![p] = "PREVOTE"] /\ UNCHANGED <> + validValue, validRound, msgsPropose, msgsPrecommit, + evidencePrecommit>> /\ action' = "UponProposalInProposeAndPrevote" - + \* lines 34-35 + lines 61-64 (onTimeoutPrevote) UponQuorumOfPrevotesAny(p) == /\ step[p] = "PREVOTE" \* line 34 and 61 @@ -346,32 +335,31 @@ UponQuorumOfPrevotesAny(p) == LET Voters == { m.src: m \in MyEvidence } IN \* compare the number of the unique voters against the threshold /\ Cardinality(Voters) >= THRESHOLD2 \* line 34 - /\ evidence' = MyEvidence \union evidence + /\ evidencePrevote' = MyEvidence \union evidencePrevote /\ BroadcastPrecommit(p, round[p], NilValue) /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] /\ UNCHANGED <> + validValue, validRound, msgsPropose, msgsPrevote, + evidencePropose, evidencePrecommit>> /\ action' = "UponQuorumOfPrevotesAny" - + \* lines 36-46 UponProposalInPrevoteOrCommitAndPrevote(p) == \E v \in ValidValues, vr \in RoundsOrNil: /\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36 - /\ LET - \* @type: PROPMESSAGE; - msg == - [ - type |-> "PROPOSAL", + /\ LET msg == [ + type |-> "PROPOSAL", src |-> Proposer[round[p]], - round |-> round[p], - proposal |-> v, + round |-> round[p], + proposal |-> v, validRound |-> vr - ] + ] IN /\ msg \in msgsPropose[round[p]] \* line 36 /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(v) } IN /\ Cardinality(PV) >= THRESHOLD2 \* line 36 - /\ evidence' = PV \union {msg} \union evidence + /\ evidencePrevote' = PV \union evidencePrevote + /\ evidencePropose' = {msg} \union evidencePropose /\ IF step[p] = "PREVOTE" THEN \* lines 38-41: /\ lockedValue' = [lockedValue EXCEPT ![p] = v] @@ -383,7 +371,8 @@ UponProposalInPrevoteOrCommitAndPrevote(p) == \* lines 42-43 /\ validValue' = [validValue EXCEPT ![p] = v] /\ validRound' = [validRound EXCEPT ![p] = round[p]] - /\ UNCHANGED <> + /\ UNCHANGED <> /\ action' = "UponProposalInPrevoteOrCommitAndPrevote" \* lines 47-48 + 65-67 (onTimeoutPrecommit) @@ -393,40 +382,40 @@ UponQuorumOfPrecommitsAny(p) == LET Committers == { m.src: m \in MyEvidence } IN \* compare the number of the unique committers against the threshold /\ Cardinality(Committers) >= THRESHOLD2 \* line 47 - /\ evidence' = MyEvidence \union evidence + /\ evidencePrecommit' = MyEvidence \union evidencePrecommit /\ round[p] + 1 \in Rounds - /\ StartRound(p, round[p] + 1) + /\ StartRound(p, round[p] + 1) /\ UNCHANGED <> + validRound, msgsPropose, msgsPrevote, msgsPrecommit, + evidencePropose, evidencePrevote>> /\ action' = "UponQuorumOfPrecommitsAny" - -\* lines 49-54 + +\* lines 49-54 UponProposalInPrecommitNoDecision(p) == /\ decision[p] = NilValue \* line 49 /\ \E v \in ValidValues (* line 50*) , r \in Rounds, vr \in RoundsOrNil: - /\ LET - \* @type: PROPMESSAGE; - msg == - [ - type |-> "PROPOSAL", + /\ LET msg == [ + type |-> "PROPOSAL", src |-> Proposer[r], - round |-> r, - proposal |-> v, + round |-> r, + proposal |-> v, validRound |-> vr - ] + ] IN /\ msg \in msgsPropose[r] \* line 49 /\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(v) } IN /\ Cardinality(PV) >= THRESHOLD2 \* line 49 - /\ evidence' = PV \union {msg} \union evidence + /\ evidencePrecommit' = PV \union evidencePrecommit + /\ evidencePropose' = evidencePropose \union { msg } /\ decision' = [decision EXCEPT ![p] = v] \* update the decision, line 51 \* The original algorithm does not have 'DECIDED', but it increments the height. \* We introduced 'DECIDED' here to prevent the process from changing its decision. /\ step' = [step EXCEPT ![p] = "DECIDED"] /\ UNCHANGED <> + validRound, msgsPropose, msgsPrevote, msgsPrecommit, + evidencePrevote>> /\ action' = "UponProposalInPrecommitNoDecision" - + \* the actions below are not essential for safety, but added for completeness \* lines 20-21 + 57-60 @@ -436,7 +425,8 @@ OnTimeoutPropose(p) == /\ BroadcastPrevote(p, round[p], NilValue) /\ step' = [step EXCEPT ![p] = "PREVOTE"] /\ UNCHANGED <> + validRound, decision, msgsPropose, msgsPrecommit, + evidencePropose, evidencePrevote, evidencePrecommit>> /\ action' = "OnTimeoutPropose" \* lines 44-46 @@ -444,21 +434,30 @@ OnQuorumOfNilPrevotes(p) == /\ step[p] = "PREVOTE" /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilValue) } IN /\ Cardinality(PV) >= THRESHOLD2 \* line 36 - /\ evidence' = PV \union evidence + /\ evidencePrevote' = PV \union evidencePrevote /\ BroadcastPrecommit(p, round[p], Id(NilValue)) /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] /\ UNCHANGED <> + validRound, decision, msgsPropose, msgsPrevote, + evidencePropose, evidencePrecommit>> /\ action' = "OnQuorumOfNilPrevotes" \* lines 55-56 OnRoundCatchup(p) == \E r \in {rr \in Rounds: rr > round[p]}: - LET RoundMsgs == msgsPropose[r] \union msgsPrevote[r] \union msgsPrecommit[r] IN - \E MyEvidence \in SUBSET RoundMsgs: - LET Faster == { m.src: m \in MyEvidence } IN + \E EvPropose \in SUBSET msgsPropose[r], + EvPrevote \in SUBSET msgsPrevote[r], + EvPrecommit \in SUBSET msgsPrecommit[r]: + LET \* @type: Set({ src: $process, a }) => Set($process); + Src(E) == { m.src: m \in E } + IN + LET Faster == + Src(EvPropose) \union Src(EvPrevote) \union Src(EvPrecommit) + IN /\ Cardinality(Faster) >= THRESHOLD1 - /\ evidence' = MyEvidence \union evidence + /\ evidencePropose' = EvPropose \union evidencePropose + /\ evidencePrevote' = EvPrevote \union evidencePrevote + /\ evidencePrecommit' = EvPrecommit \union evidencePrecommit /\ StartRound(p, r) /\ UNCHANGED <> @@ -482,17 +481,24 @@ Next == \/ OnQuorumOfNilPrevotes(p) \/ OnRoundCatchup(p) - + (**************************** FORK SCENARIOS ***************************) \* equivocation by a process p EquivocationBy(p) == - \E m1, m2 \in evidence: - /\ m1 /= m2 - /\ m1.src = p - /\ m2.src = p - /\ m1.round = m2.round - /\ m1.type = m2.type + LET + \* @type: Set({ src: $process, a }) => Bool; + EquivocationIn(S) == + \E m1, m2 \in S: + /\ m1 /= m2 + /\ m1.src = p + /\ m2.src = p + /\ m1.round = m2.round + /\ m1.type = m2.type + IN + \/ EquivocationIn(evidencePropose) + \/ EquivocationIn(evidencePrevote) + \/ EquivocationIn(evidencePrecommit) \* amnesic behavior by a process p AmnesiaBy(p) == @@ -501,21 +507,20 @@ AmnesiaBy(p) == /\ \E v1, v2 \in ValidValues: /\ v1 /= v2 /\ [ - type |-> "PRECOMMIT", + type |-> "PRECOMMIT", src |-> p, - round |-> r1, + round |-> r1, id |-> Id(v1) - ] \in evidence + ] \in evidencePrecommit /\ [ - type |-> "PREVOTE", + type |-> "PREVOTE", src |-> p, - round |-> r2, + round |-> r2, id |-> Id(v2) - ] \in evidence + ] \in evidencePrevote /\ \A r \in { rnd \in Rounds: r1 <= rnd /\ rnd < r2 }: LET prevotes == - { m \in evidence: - m.type = "PREVOTE" /\ m.round = r /\ m.id = Id(v2) } + { m \in evidencePrevote: m.round = r /\ m.id = Id(v2) } IN Cardinality(prevotes) < THRESHOLD2 @@ -545,7 +550,7 @@ Accountability == EquivocationBy(p) \/ AmnesiaBy(p) (****************** FALSE INVARIANTS TO PRODUCE EXAMPLES ***********************) - + \* This property is violated. You can check it to see how amnesic behavior \* appears in the evidence variable. NoAmnesia == @@ -562,12 +567,12 @@ NoAgreement == \A p, q \in Corr: (p /= q /\ decision[p] /= NilValue /\ decision[q] /= NilValue) => decision[p] /= decision[q] - + \* Either agreement holds, or the faulty processes indeed demonstrate amnesia. \* This property is violated. A counterexample should demonstrate equivocation. AgreementOrAmnesia == Agreement \/ (\A p \in Faulty: AmnesiaBy(p)) - + \* We expect this property to be violated. It shows us a protocol run, \* where one faulty process demonstrates amnesia without equivocation. \* However, the absence of amnesia @@ -584,7 +589,7 @@ AmnesiaImpliesEquivocation == (* This property is violated. You can check it to see that all correct processes - may reach MaxRound without making a decision. + may reach MaxRound without making a decision. *) NeverUndecidedInMaxRound == LET AllInMax == \A p \in Corr: round[p] = MaxRound @@ -592,5 +597,5 @@ NeverUndecidedInMaxRound == IN AllInMax => AllDecided -============================================================================= - +============================================================================= + diff --git a/spec/light-client/accountability/typedefs.tla b/spec/light-client/accountability/typedefs.tla index 5b4f7de52..ce232f9e9 100644 --- a/spec/light-client/accountability/typedefs.tla +++ b/spec/light-client/accountability/typedefs.tla @@ -1,36 +1,27 @@ -------------------- MODULE typedefs --------------------------- (* - @typeAlias: PROCESS = Str; - @typeAlias: VALUE = Str; - @typeAlias: STEP = Str; - @typeAlias: ROUND = Int; - @typeAlias: ACTION = Str; - @typeAlias: TRACE = Seq(Str); - @typeAlias: PROPMESSAGE = - [ - type: STEP, - src: PROCESS, - round: ROUND, - proposal: VALUE, - validRound: ROUND - ]; - @typeAlias: PREMESSAGE = - [ - type: STEP, - src: PROCESS, - round: ROUND, - id: VALUE - ]; - @typeAlias: MESSAGE = - [ - type: STEP, - src: PROCESS, - round: ROUND, - proposal: VALUE, - validRound: ROUND, - id: VALUE - ]; + @typeAlias: process = Str; + @typeAlias: value = Str; + @typeAlias: step = Str; + @typeAlias: round = Int; + @typeAlias: action = Str; + @typeAlias: trace = Seq(Str); + @typeAlias: proposeMsg = + { + type: $step, + src: $process, + round: $round, + proposal: $value, + validRound: $round + }; + @typeAlias: preMsg = + { + type: $step, + src: $process, + round: $round, + id: $value + }; *) TypeAliases == TRUE -============================================================================= \ No newline at end of file +============================================================================= From 1069ffc6aafa54ace10632c54bdc0c9872fb7ee9 Mon Sep 17 00:00:00 2001 From: William Banfield <4561443+williambanfield@users.noreply.github.com> Date: Wed, 17 Aug 2022 11:19:20 -0400 Subject: [PATCH 5/7] config: backport the rename of fastsync to blocksync (#9259) This is largely a cherry pick of #6755 with some additional fixups added where detected. This change moves the blockchain package to a package called blocksync. Additionally, it renames the relevant uses of the term `fastsync` to `blocksync`. closes: #9227 #### PR checklist - [ ] Tests written/updated, or no tests needed - [x] `CHANGELOG_PENDING.md` updated, or no changelog entry needed - [x] Updated relevant documentation (`docs/`) and code comments, or no documentation updates needed --- CHANGELOG_PENDING.md | 2 + {blockchain => blocksync}/msgs.go | 4 +- {blockchain => blocksync}/msgs_test.go | 13 +-- {blockchain => blocksync}/pool.go | 4 +- {blockchain => blocksync}/pool_test.go | 2 +- {blockchain => blocksync}/reactor.go | 42 +++++----- {blockchain => blocksync}/reactor_test.go | 2 +- cmd/tendermint/commands/root.go | 5 ++ cmd/tendermint/commands/run_node.go | 2 +- config/config.go | 76 ++++++++++------- config/config_test.go | 4 +- config/toml.go | 16 ++-- consensus/metrics.gen.go | 8 +- consensus/metrics.go | 4 +- consensus/reactor.go | 14 ++-- docs/tendermint-core/README.md | 2 +- .../{fast-sync.md => block-sync.md} | 31 +++---- docs/tendermint-core/configuration.md | 16 ++-- docs/tendermint-core/metrics.md | 2 +- docs/tendermint-core/state-sync.md | 2 +- node/node.go | 58 ++++++------- .../{blockchain => blocksync}/types.pb.go | 81 +++++++++---------- .../{blockchain => blocksync}/types.proto | 4 +- spec/abci/abci.md | 2 +- spec/p2p/messages/block-sync.md | 2 +- test/e2e/README.md | 2 +- test/e2e/generator/generate.go | 4 +- test/e2e/networks/ci.toml | 8 +- test/e2e/pkg/manifest.go | 5 +- test/e2e/pkg/testnet.go | 9 ++- test/e2e/runner/setup.go | 6 +- 31 files changed, 235 insertions(+), 197 deletions(-) rename {blockchain => blocksync}/msgs.go (99%) rename {blockchain => blocksync}/msgs_test.go (88%) rename {blockchain => blocksync}/pool.go (99%) rename {blockchain => blocksync}/pool_test.go (99%) rename {blockchain => blocksync}/reactor.go (93%) rename {blockchain => blocksync}/reactor_test.go (99%) rename docs/tendermint-core/{fast-sync.md => block-sync.md} (63%) rename proto/tendermint/{blockchain => blocksync}/types.pb.go (90%) rename proto/tendermint/{blockchain => blocksync}/types.proto (94%) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6ddb8f795..2f2eb506b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -10,6 +10,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - CLI/RPC/Config + - [config] \#9259 Rename the fastsync section and the fast_sync key blocksync and block_sync respectively + - Apps - [abci/counter] \#6684 Delete counter example app diff --git a/blockchain/msgs.go b/blocksync/msgs.go similarity index 99% rename from blockchain/msgs.go rename to blocksync/msgs.go index 35868830b..081cbe4f8 100644 --- a/blockchain/msgs.go +++ b/blocksync/msgs.go @@ -1,4 +1,4 @@ -package blockchain +package blocksync import ( "errors" @@ -6,7 +6,7 @@ import ( "github.com/gogo/protobuf/proto" - bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain" + bcproto "github.com/tendermint/tendermint/proto/tendermint/blocksync" "github.com/tendermint/tendermint/types" ) diff --git a/blockchain/msgs_test.go b/blocksync/msgs_test.go similarity index 88% rename from blockchain/msgs_test.go rename to blocksync/msgs_test.go index df8efca14..b27376b14 100644 --- a/blockchain/msgs_test.go +++ b/blocksync/msgs_test.go @@ -1,4 +1,4 @@ -package blockchain +package blocksync_test import ( "encoding/hex" @@ -9,7 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain" + "github.com/tendermint/tendermint/blocksync" + bcproto "github.com/tendermint/tendermint/proto/tendermint/blocksync" "github.com/tendermint/tendermint/types" ) @@ -28,7 +29,7 @@ func TestBcBlockRequestMessageValidateBasic(t *testing.T) { tc := tc t.Run(tc.testName, func(t *testing.T) { request := bcproto.BlockRequest{Height: tc.requestHeight} - assert.Equal(t, tc.expectErr, ValidateMsg(&request) != nil, "Validate Basic had an unexpected result") + assert.Equal(t, tc.expectErr, blocksync.ValidateMsg(&request) != nil, "Validate Basic had an unexpected result") }) } } @@ -48,14 +49,14 @@ func TestBcNoBlockResponseMessageValidateBasic(t *testing.T) { tc := tc t.Run(tc.testName, func(t *testing.T) { nonResponse := bcproto.NoBlockResponse{Height: tc.nonResponseHeight} - assert.Equal(t, tc.expectErr, ValidateMsg(&nonResponse) != nil, "Validate Basic had an unexpected result") + assert.Equal(t, tc.expectErr, blocksync.ValidateMsg(&nonResponse) != nil, "Validate Basic had an unexpected result") }) } } func TestBcStatusRequestMessageValidateBasic(t *testing.T) { request := bcproto.StatusRequest{} - assert.NoError(t, ValidateMsg(&request)) + assert.NoError(t, blocksync.ValidateMsg(&request)) } func TestBcStatusResponseMessageValidateBasic(t *testing.T) { @@ -73,7 +74,7 @@ func TestBcStatusResponseMessageValidateBasic(t *testing.T) { tc := tc t.Run(tc.testName, func(t *testing.T) { response := bcproto.StatusResponse{Height: tc.responseHeight} - assert.Equal(t, tc.expectErr, ValidateMsg(&response) != nil, "Validate Basic had an unexpected result") + assert.Equal(t, tc.expectErr, blocksync.ValidateMsg(&response) != nil, "Validate Basic had an unexpected result") }) } } diff --git a/blockchain/pool.go b/blocksync/pool.go similarity index 99% rename from blockchain/pool.go rename to blocksync/pool.go index ceab887ad..99dcb79e2 100644 --- a/blockchain/pool.go +++ b/blocksync/pool.go @@ -1,4 +1,4 @@ -package blockchain +package blocksync import ( "errors" @@ -58,7 +58,7 @@ var peerTimeout = 15 * time.Second // not const so we can override with tests are not at peer limits, we can probably switch to consensus reactor */ -// BlockPool keeps track of the fast sync peers, block requests and block responses. +// BlockPool keeps track of the block sync peers, block requests and block responses. type BlockPool struct { service.BaseService startTime time.Time diff --git a/blockchain/pool_test.go b/blocksync/pool_test.go similarity index 99% rename from blockchain/pool_test.go rename to blocksync/pool_test.go index c67da0afd..9fcc9dde4 100644 --- a/blockchain/pool_test.go +++ b/blocksync/pool_test.go @@ -1,4 +1,4 @@ -package blockchain +package blocksync import ( "fmt" diff --git a/blockchain/reactor.go b/blocksync/reactor.go similarity index 93% rename from blockchain/reactor.go rename to blocksync/reactor.go index 06a77de4d..1f867ca98 100644 --- a/blockchain/reactor.go +++ b/blocksync/reactor.go @@ -1,4 +1,4 @@ -package blockchain +package blocksync import ( "fmt" @@ -7,15 +7,15 @@ import ( "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" - bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain" + bcproto "github.com/tendermint/tendermint/proto/tendermint/blocksync" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/store" "github.com/tendermint/tendermint/types" ) const ( - // BlockchainChannel is a channel for blocks and status updates (`BlockStore` height) - BlockchainChannel = byte(0x40) + // BlocksyncChannel is a channel for blocks and status updates (`BlockStore` height) + BlocksyncChannel = byte(0x40) trySyncIntervalMS = 10 @@ -30,7 +30,7 @@ const ( ) type consensusReactor interface { - // for when we switch from blockchain reactor and fast sync to + // for when we switch from blockchain reactor and block sync to // the consensus machine SwitchToConsensus(state sm.State, skipWAL bool) } @@ -54,7 +54,7 @@ type Reactor struct { blockExec *sm.BlockExecutor store *store.BlockStore pool *BlockPool - fastSync bool + blockSync bool requestsCh <-chan BlockRequest errorsCh <-chan peerError @@ -62,7 +62,7 @@ type Reactor struct { // NewReactor returns new reactor instance. func NewReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore, - fastSync bool) *Reactor { + blockSync bool) *Reactor { if state.LastBlockHeight != store.Height() { panic(fmt.Sprintf("state (%v) and store (%v) height mismatch", state.LastBlockHeight, @@ -85,7 +85,7 @@ func NewReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockS blockExec: blockExec, store: store, pool: pool, - fastSync: fastSync, + blockSync: blockSync, requestsCh: requestsCh, errorsCh: errorsCh, } @@ -101,7 +101,7 @@ func (bcR *Reactor) SetLogger(l log.Logger) { // OnStart implements service.Service. func (bcR *Reactor) OnStart() error { - if bcR.fastSync { + if bcR.blockSync { err := bcR.pool.Start() if err != nil { return err @@ -111,9 +111,9 @@ func (bcR *Reactor) OnStart() error { return nil } -// SwitchToFastSync is called by the state sync reactor when switching to fast sync. -func (bcR *Reactor) SwitchToFastSync(state sm.State) error { - bcR.fastSync = true +// SwitchToBlockSync is called by the state sync reactor when switching to block sync. +func (bcR *Reactor) SwitchToBlockSync(state sm.State) error { + bcR.blockSync = true bcR.initialState = state bcR.pool.height = state.LastBlockHeight + 1 @@ -127,7 +127,7 @@ func (bcR *Reactor) SwitchToFastSync(state sm.State) error { // OnStop implements service.Service. func (bcR *Reactor) OnStop() { - if bcR.fastSync { + if bcR.blockSync { if err := bcR.pool.Stop(); err != nil { bcR.Logger.Error("Error stopping pool", "err", err) } @@ -138,7 +138,7 @@ func (bcR *Reactor) OnStop() { func (bcR *Reactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ { - ID: BlockchainChannel, + ID: BlocksyncChannel, Priority: 5, SendQueueCapacity: 1000, RecvBufferCapacity: 50 * 4096, @@ -157,7 +157,7 @@ func (bcR *Reactor) AddPeer(peer p2p.Peer) { return } - peer.Send(BlockchainChannel, msgBytes) + peer.Send(BlocksyncChannel, msgBytes) // it's OK if send fails. will try later in poolRoutine // peer is added to the pool once we receive the first @@ -188,7 +188,7 @@ func (bcR *Reactor) respondToPeer(msg *bcproto.BlockRequest, return false } - return src.TrySend(BlockchainChannel, msgBytes) + return src.TrySend(BlocksyncChannel, msgBytes) } bcR.Logger.Info("Peer asking for a block we don't have", "src", src, "height", msg.Height) @@ -199,7 +199,7 @@ func (bcR *Reactor) respondToPeer(msg *bcproto.BlockRequest, return false } - return src.TrySend(BlockchainChannel, msgBytes) + return src.TrySend(BlocksyncChannel, msgBytes) } // Receive implements Reactor by handling 4 types of messages (look below). @@ -239,7 +239,7 @@ func (bcR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { bcR.Logger.Error("could not convert msg to protobut", "err", err) return } - src.TrySend(BlockchainChannel, msgBytes) + src.TrySend(BlocksyncChannel, msgBytes) case *bcproto.StatusResponse: // Got a peer status. Unverified. bcR.pool.SetPeerRange(src.ID(), msg.Base, msg.Height) @@ -291,7 +291,7 @@ func (bcR *Reactor) poolRoutine(stateSynced bool) { continue } - queued := peer.TrySend(BlockchainChannel, msgBytes) + queued := peer.TrySend(BlocksyncChannel, msgBytes) if !queued { bcR.Logger.Debug("Send queue is full, drop block request", "peer", peer.ID(), "height", request.Height) } @@ -409,7 +409,7 @@ FOR_LOOP: if blocksSynced%100 == 0 { lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds()) - bcR.Logger.Info("Fast Sync Rate", "height", bcR.pool.height, + bcR.Logger.Info("Block Sync Rate", "height", bcR.pool.height, "max_peer_height", bcR.pool.MaxPeerHeight(), "blocks/s", lastRate) lastHundred = time.Now() } @@ -430,7 +430,7 @@ func (bcR *Reactor) BroadcastStatusRequest() error { return fmt.Errorf("could not convert msg to proto: %w", err) } - bcR.Switch.Broadcast(BlockchainChannel, bm) + bcR.Switch.Broadcast(BlocksyncChannel, bm) return nil } diff --git a/blockchain/reactor_test.go b/blocksync/reactor_test.go similarity index 99% rename from blockchain/reactor_test.go rename to blocksync/reactor_test.go index db4cbead2..18c8c3059 100644 --- a/blockchain/reactor_test.go +++ b/blocksync/reactor_test.go @@ -1,4 +1,4 @@ -package blockchain +package blocksync import ( "fmt" diff --git a/cmd/tendermint/commands/root.go b/cmd/tendermint/commands/root.go index 46c9ac7c7..16ea8a391 100644 --- a/cmd/tendermint/commands/root.go +++ b/cmd/tendermint/commands/root.go @@ -53,6 +53,11 @@ func ParseConfig(cmd *cobra.Command) (*cfg.Config, error) { if err := conf.ValidateBasic(); err != nil { return nil, fmt.Errorf("error in config file: %v", err) } + if warnings := conf.CheckDeprecated(); len(warnings) > 0 { + for _, warning := range warnings { + logger.Info("deprecated usage found in configuration file", "usage", warning) + } + } return conf, nil } diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index d78eab2c8..ac2984111 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -31,7 +31,7 @@ func AddNodeFlags(cmd *cobra.Command) { "socket address to listen on for connections from external priv_validator process") // node flags - cmd.Flags().Bool("fast_sync", config.FastSyncMode, "fast blockchain syncing") + cmd.Flags().Bool("block_sync", config.BlockSyncMode, "sync the block chain using the blocksync algorithm") cmd.Flags().BytesHexVar( &genesisHash, "genesis_hash", diff --git a/config/config.go b/config/config.go index 9c8c3c236..6cb12835d 100644 --- a/config/config.go +++ b/config/config.go @@ -68,14 +68,17 @@ type Config struct { BaseConfig `mapstructure:",squash"` // Options for services - RPC *RPCConfig `mapstructure:"rpc"` - P2P *P2PConfig `mapstructure:"p2p"` - Mempool *MempoolConfig `mapstructure:"mempool"` - StateSync *StateSyncConfig `mapstructure:"statesync"` - FastSync *FastSyncConfig `mapstructure:"fastsync"` - Consensus *ConsensusConfig `mapstructure:"consensus"` - TxIndex *TxIndexConfig `mapstructure:"tx_index"` - Instrumentation *InstrumentationConfig `mapstructure:"instrumentation"` + RPC *RPCConfig `mapstructure:"rpc"` + P2P *P2PConfig `mapstructure:"p2p"` + Mempool *MempoolConfig `mapstructure:"mempool"` + StateSync *StateSyncConfig `mapstructure:"statesync"` + BlockSync *BlockSyncConfig `mapstructure:"blocksync"` + //TODO(williambanfield): remove this field once v0.37 is released. + // https://github.com/tendermint/tendermint/issues/9279 + DeprecatedFastSyncConfig map[interface{}]interface{} `mapstructure:"fastsync"` + Consensus *ConsensusConfig `mapstructure:"consensus"` + TxIndex *TxIndexConfig `mapstructure:"tx_index"` + Instrumentation *InstrumentationConfig `mapstructure:"instrumentation"` } // DefaultConfig returns a default configuration for a Tendermint node @@ -86,7 +89,7 @@ func DefaultConfig() *Config { P2P: DefaultP2PConfig(), Mempool: DefaultMempoolConfig(), StateSync: DefaultStateSyncConfig(), - FastSync: DefaultFastSyncConfig(), + BlockSync: DefaultBlockSyncConfig(), Consensus: DefaultConsensusConfig(), TxIndex: DefaultTxIndexConfig(), Instrumentation: DefaultInstrumentationConfig(), @@ -101,7 +104,7 @@ func TestConfig() *Config { P2P: TestP2PConfig(), Mempool: TestMempoolConfig(), StateSync: TestStateSyncConfig(), - FastSync: TestFastSyncConfig(), + BlockSync: TestBlockSyncConfig(), Consensus: TestConsensusConfig(), TxIndex: TestTxIndexConfig(), Instrumentation: TestInstrumentationConfig(), @@ -136,8 +139,8 @@ func (cfg *Config) ValidateBasic() error { if err := cfg.StateSync.ValidateBasic(); err != nil { return fmt.Errorf("error in [statesync] section: %w", err) } - if err := cfg.FastSync.ValidateBasic(); err != nil { - return fmt.Errorf("error in [fastsync] section: %w", err) + if err := cfg.BlockSync.ValidateBasic(); err != nil { + return fmt.Errorf("error in [blocksync] section: %w", err) } if err := cfg.Consensus.ValidateBasic(); err != nil { return fmt.Errorf("error in [consensus] section: %w", err) @@ -148,6 +151,17 @@ func (cfg *Config) ValidateBasic() error { return nil } +func (cfg *Config) CheckDeprecated() []string { + var warnings []string + if cfg.DeprecatedFastSyncConfig != nil { + warnings = append(warnings, "[fastsync] table detected. This section has been renamed to [blocksync]. The values in this deprecated section will be disregarded.") + } + if cfg.BaseConfig.DeprecatedFastSyncMode != nil { + warnings = append(warnings, "fast_sync key detected. This key has been renamed to block_sync. The value of this deprecated key will be disregarded.") + } + return warnings +} + //----------------------------------------------------------------------------- // BaseConfig @@ -167,10 +181,14 @@ type BaseConfig struct { //nolint: maligned // A custom human readable name for this node Moniker string `mapstructure:"moniker"` - // If this node is many blocks behind the tip of the chain, FastSync + // If this node is many blocks behind the tip of the chain, Blocksync // allows them to catchup quickly by downloading blocks in parallel // and verifying their commits - FastSyncMode bool `mapstructure:"fast_sync"` + BlockSyncMode bool `mapstructure:"block_sync"` + + //TODO(williambanfield): remove this field once v0.37 is released. + // https://github.com/tendermint/tendermint/issues/9279 + DeprecatedFastSyncMode interface{} `mapstructure:"fast_sync"` // Database backend: goleveldb | cleveldb | boltdb | rocksdb // * goleveldb (github.com/syndtr/goleveldb - most popular implementation) @@ -238,7 +256,7 @@ func DefaultBaseConfig() BaseConfig { ABCI: "socket", LogLevel: DefaultLogLevel, LogFormat: LogFormatPlain, - FastSyncMode: true, + BlockSyncMode: true, FilterPeers: false, DBBackend: "goleveldb", DBPath: "data", @@ -250,7 +268,7 @@ func TestBaseConfig() BaseConfig { cfg := DefaultBaseConfig() cfg.chainID = "tendermint_test" cfg.ProxyApp = "kvstore" - cfg.FastSyncMode = false + cfg.BlockSyncMode = false cfg.DBBackend = "memdb" return cfg } @@ -817,7 +835,7 @@ func DefaultStateSyncConfig() *StateSyncConfig { } } -// TestFastSyncConfig returns a default configuration for the state sync service +// TestStateSyncConfig returns a default configuration for the state sync service func TestStateSyncConfig() *StateSyncConfig { return DefaultStateSyncConfig() } @@ -873,34 +891,34 @@ func (cfg *StateSyncConfig) ValidateBasic() error { } //----------------------------------------------------------------------------- -// FastSyncConfig +// BlockSyncConfig -// FastSyncConfig defines the configuration for the Tendermint fast sync service -type FastSyncConfig struct { +// BlockSyncConfig (formerly known as FastSync) defines the configuration for the Tendermint block sync service +type BlockSyncConfig struct { Version string `mapstructure:"version"` } -// DefaultFastSyncConfig returns a default configuration for the fast sync service -func DefaultFastSyncConfig() *FastSyncConfig { - return &FastSyncConfig{ +// DefaultBlockSyncConfig returns a default configuration for the block sync service +func DefaultBlockSyncConfig() *BlockSyncConfig { + return &BlockSyncConfig{ Version: "v0", } } -// TestFastSyncConfig returns a default configuration for the fast sync. -func TestFastSyncConfig() *FastSyncConfig { - return DefaultFastSyncConfig() +// TestBlockSyncConfig returns a default configuration for the block sync. +func TestBlockSyncConfig() *BlockSyncConfig { + return DefaultBlockSyncConfig() } // ValidateBasic performs basic validation. -func (cfg *FastSyncConfig) ValidateBasic() error { +func (cfg *BlockSyncConfig) ValidateBasic() error { switch cfg.Version { case "v0": return nil case "v1", "v2": - return fmt.Errorf("fast sync version %s has been deprecated. Please use v0 instead", cfg.Version) + return fmt.Errorf("blocksync version %s has been deprecated. Please use v0 instead", cfg.Version) default: - return fmt.Errorf("unknown fastsync version %s", cfg.Version) + return fmt.Errorf("unknown blocksync version %s", cfg.Version) } } diff --git a/config/config_test.go b/config/config_test.go index cdcddf427..2446f08e6 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -128,8 +128,8 @@ func TestStateSyncConfigValidateBasic(t *testing.T) { require.NoError(t, cfg.ValidateBasic()) } -func TestFastSyncConfigValidateBasic(t *testing.T) { - cfg := TestFastSyncConfig() +func TestBlockSyncConfigValidateBasic(t *testing.T) { + cfg := TestBlockSyncConfig() assert.NoError(t, cfg.ValidateBasic()) // tamper with version diff --git a/config/toml.go b/config/toml.go index a0112351d..d8ef7c86d 100644 --- a/config/toml.go +++ b/config/toml.go @@ -87,10 +87,10 @@ proxy_app = "{{ .BaseConfig.ProxyApp }}" # A custom human readable name for this node moniker = "{{ .BaseConfig.Moniker }}" -# If this node is many blocks behind the tip of the chain, FastSync +# If this node is many blocks behind the tip of the chain, BlockSync # allows them to catchup quickly by downloading blocks in parallel # and verifying their commits -fast_sync = {{ .BaseConfig.FastSyncMode }} +block_sync = {{ .BaseConfig.BlockSyncMode }} # Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb # * goleveldb (github.com/syndtr/goleveldb - most popular implementation) @@ -429,17 +429,17 @@ chunk_request_timeout = "{{ .StateSync.ChunkRequestTimeout }}" chunk_fetchers = "{{ .StateSync.ChunkFetchers }}" ####################################################### -### Fast Sync Configuration Connections ### +### Block Sync Configuration Options ### ####################################################### -[fastsync] +[blocksync] -# Fast Sync version to use: +# Block Sync version to use: # -# In v0.37, v1 and v2 of the fast sync protocol were deprecated. +# In v0.37, v1 and v2 of the block sync protocols were deprecated. # Please use v0 instead. # -# 1) "v0" - the default fast sync implementation -version = "{{ .FastSync.Version }}" +# 1) "v0" - the default block sync implementation +version = "{{ .BlockSync.Version }}" ####################################################### ### Consensus Configuration Options ### diff --git a/consensus/metrics.gen.go b/consensus/metrics.gen.go index 70be488ef..e627dee57 100644 --- a/consensus/metrics.gen.go +++ b/consensus/metrics.gen.go @@ -118,11 +118,11 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { Name: "latest_block_height", Help: "The latest block height.", }, labels).With(labelsAndValues...), - FastSyncing: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + BlockSyncing: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, - Name: "fast_syncing", - Help: "Whether or not a node is fast syncing. 1 if yes, 0 if no.", + Name: "block_syncing", + Help: "Whether or not a node is block syncing. 1 if yes, 0 if no.", }, labels).With(labelsAndValues...), StateSyncing: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, @@ -184,7 +184,7 @@ func NopMetrics() *Metrics { BlockSizeBytes: discard.NewGauge(), TotalTxs: discard.NewGauge(), CommittedHeight: discard.NewGauge(), - FastSyncing: discard.NewGauge(), + BlockSyncing: discard.NewGauge(), StateSyncing: discard.NewGauge(), BlockParts: discard.NewCounter(), StepDurationSeconds: discard.NewHistogram(), diff --git a/consensus/metrics.go b/consensus/metrics.go index de0da45de..2a82d732f 100644 --- a/consensus/metrics.go +++ b/consensus/metrics.go @@ -59,8 +59,8 @@ type Metrics struct { TotalTxs metrics.Gauge // The latest block height. CommittedHeight metrics.Gauge `metrics_name:"latest_block_height"` - // Whether or not a node is fast syncing. 1 if yes, 0 if no. - FastSyncing metrics.Gauge + // Whether or not a node is block syncing. 1 if yes, 0 if no. + BlockSyncing metrics.Gauge // Whether or not a node is state syncing. 1 if yes, 0 if no. StateSyncing metrics.Gauge diff --git a/consensus/reactor.go b/consensus/reactor.go index d6b22786b..c86f21c98 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -72,7 +72,7 @@ func NewReactor(consensusState *State, waitSync bool, options ...ReactorOption) } // OnStart implements BaseService by subscribing to events, which later will be -// broadcasted to other peers and starting state if we're not in fast sync. +// broadcasted to other peers and starting state if we're not in block sync. func (conR *Reactor) OnStart() error { conR.Logger.Info("Reactor ", "waitSync", conR.WaitSync()) @@ -104,8 +104,8 @@ func (conR *Reactor) OnStop() { } } -// SwitchToConsensus switches from fast_sync mode to consensus mode. -// It resets the state, turns off fast_sync, and starts the consensus state-machine +// SwitchToConsensus switches from block_sync mode to consensus mode. +// It resets the state, turns off block_sync, and starts the consensus state-machine func (conR *Reactor) SwitchToConsensus(state sm.State, skipWAL bool) { conR.Logger.Info("SwitchToConsensus") @@ -121,7 +121,7 @@ func (conR *Reactor) SwitchToConsensus(state sm.State, skipWAL bool) { conR.mtx.Lock() conR.waitSync = false conR.mtx.Unlock() - conR.Metrics.FastSyncing.Set(0) + conR.Metrics.BlockSyncing.Set(0) conR.Metrics.StateSyncing.Set(0) if skipWAL { @@ -198,7 +198,7 @@ func (conR *Reactor) AddPeer(peer p2p.Peer) { go conR.queryMaj23Routine(peer, peerState) // Send our state to peer. - // If we're fast_syncing, broadcast a RoundStepMessage later upon SwitchToConsensus(). + // If we're block_syncing, broadcast a RoundStepMessage later upon SwitchToConsensus(). if !conR.WaitSync() { conR.sendNewRoundStepMessage(peer) } @@ -218,7 +218,7 @@ func (conR *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) { } // Receive implements Reactor -// NOTE: We process these messages even when we're fast_syncing. +// NOTE: We process these messages even when we're block_syncing. // Messages affect either a peer state or the consensus state. // Peer state updates can happen in parallel, but processing of // proposals, block parts, and votes are ordered by the receiveRoutine @@ -386,7 +386,7 @@ func (conR *Reactor) SetEventBus(b *types.EventBus) { conR.conS.SetEventBus(b) } -// WaitSync returns whether the consensus reactor is waiting for state/fast sync. +// WaitSync returns whether the consensus reactor is waiting for state/block sync. func (conR *Reactor) WaitSync() bool { conR.mtx.RLock() defer conR.mtx.RUnlock() diff --git a/docs/tendermint-core/README.md b/docs/tendermint-core/README.md index fa94f3a1e..60f995204 100644 --- a/docs/tendermint-core/README.md +++ b/docs/tendermint-core/README.md @@ -17,7 +17,7 @@ This section dives into the internals of Tendermint the implementation. - [Subscribing to events](./subscription.md) - [Block Structure](./block-structure.md) - [RPC](./rpc.md) -- [Fast Sync](./fast-sync.md) +- [Block Sync](./block-sync.md) - [State Sync](./state-sync.md) - [Mempool](./mempool.md) - [Light Client](./light-client.md) diff --git a/docs/tendermint-core/fast-sync.md b/docs/tendermint-core/block-sync.md similarity index 63% rename from docs/tendermint-core/fast-sync.md rename to docs/tendermint-core/block-sync.md index 9bbeade38..4d55d52b8 100644 --- a/docs/tendermint-core/fast-sync.md +++ b/docs/tendermint-core/block-sync.md @@ -2,7 +2,8 @@ order: 10 --- -# Fast Sync +# Block Sync +*Formerly known as Fast Sync* In a proof of work blockchain, syncing with the chain is the same process as staying up-to-date with the consensus: download blocks, and @@ -14,35 +15,37 @@ scratch can take a very long time. It's much faster to just download blocks and check the merkle tree of validators than to run the real-time consensus gossip protocol. -## Using Fast Sync +## Using Block Sync -To support faster syncing, Tendermint offers a `fast-sync` mode, which +To support faster syncing, Tendermint offers a `block-sync` mode, which is enabled by default, and can be toggled in the `config.toml` or via -`--fast_sync=false`. +`--block_sync=false`. In this mode, the Tendermint daemon will sync hundreds of times faster than if it used the real-time consensus process. Once caught up, the -daemon will switch out of fast sync and into the normal consensus mode. +daemon will switch out of Block Sync and into the normal consensus mode. After running for some time, the node is considered `caught up` if it has at least one peer and it's height is at least as high as the max reported peer height. See [the IsCaughtUp method](https://github.com/tendermint/tendermint/blob/b467515719e686e4678e6da4e102f32a491b85a0/blockchain/pool.go#L128). -Note: There are three versions of fast sync. We recommend using v0 as v1 and v2 are still in beta. - If you would like to use a different version you can do so by changing the version in the `config.toml`: +Note: While there have historically been multiple versions of blocksync, v0, v1, and v2, all versions +other than v0 have been deprecated in favor of the simplest and most well understood algorithm. ```toml ####################################################### -### Fast Sync Configuration Connections ### +### Block Sync Configuration Options ### ####################################################### -[fastsync] +[blocksync] -# Fast Sync version to use: -# 1) "v0" (default) - the legacy fast sync implementation -# 2) "v1" - refactor of v0 version for better testability -# 2) "v2" - complete redesign of v0, optimized for testability & readability +# Block Sync version to use: +# +# In v0.37, v1 and v2 of the block sync protocols were deprecated. +# Please use v0 instead. +# +# 1) "v0" - the default block sync implementation version = "v0" ``` -If we're lagging sufficiently, we should go back to fast syncing, but +If we're lagging sufficiently, we should go back to block syncing, but this is an [open issue](https://github.com/tendermint/tendermint/issues/129). diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index 120716bfd..3925b21c8 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -36,10 +36,10 @@ proxy_app = "tcp://127.0.0.1:26658" # A custom human readable name for this node moniker = "anonymous" -# If this node is many blocks behind the tip of the chain, FastSync +# If this node is many blocks behind the tip of the chain, BlockSync # allows them to catchup quickly by downloading blocks in parallel # and verifying their commits -fast_sync = true +block_sync = true # Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb # * goleveldb (github.com/syndtr/goleveldb - most popular implementation) @@ -319,16 +319,16 @@ trust_period = "0s" temp_dir = "" ####################################################### -### Fast Sync Configuration Connections ### +### Block Sync Configuration Options ### ####################################################### -[fastsync] +[blocksync] -# Fast Sync version to use: -# -# In v0.37, the v1 and v2 fast sync protocols were deprecated. +# Block Sync version to use: +# +# In v0.37, v1 and v2 of the block sync protocols were deprecated. # Please use v0 instead. # -# 1) "v0" (default) - the legacy fast sync implementation +# 1) "v0" - the default block sync implementation version = "v0" ####################################################### diff --git a/docs/tendermint-core/metrics.md b/docs/tendermint-core/metrics.md index b0f7033ac..8fb0353a0 100644 --- a/docs/tendermint-core/metrics.md +++ b/docs/tendermint-core/metrics.md @@ -37,7 +37,7 @@ The following metrics are available: | consensus_total_txs | Gauge | | Total number of transactions committed | | consensus_block_parts | counter | peer_id | number of blockparts transmitted by peer | | consensus_latest_block_height | gauge | | /status sync_info number | -| consensus_fast_syncing | gauge | | either 0 (not fast syncing) or 1 (syncing) | +| consensus_block_syncing | gauge | | either 0 (not block syncing) or 1 (syncing) | | consensus_state_syncing | gauge | | either 0 (not state syncing) or 1 (syncing) | | consensus_block_size_bytes | Gauge | | Block size in bytes | | consensus_step_duration | Histogram | step | Histogram of durations for each step in the consensus protocol | diff --git a/docs/tendermint-core/state-sync.md b/docs/tendermint-core/state-sync.md index a6e314fe5..8400a6ef9 100644 --- a/docs/tendermint-core/state-sync.md +++ b/docs/tendermint-core/state-sync.md @@ -4,7 +4,7 @@ order: 11 # State Sync -With fast sync a node is downloading all of the data of an application from genesis and verifying it. +With block sync a node is downloading all of the data of an application from genesis and verifying it. With state sync your node will download data related to the head or near the head of the chain and verify the data. This leads to drastically shorter times for joining a network. diff --git a/node/node.go b/node/node.go index 51fe490d8..36c9acd67 100644 --- a/node/node.go +++ b/node/node.go @@ -16,7 +16,7 @@ import ( dbm "github.com/tendermint/tm-db" abci "github.com/tendermint/tendermint/abci/types" - bc "github.com/tendermint/tendermint/blockchain" + bc "github.com/tendermint/tendermint/blocksync" cfg "github.com/tendermint/tendermint/config" cs "github.com/tendermint/tendermint/consensus" "github.com/tendermint/tendermint/crypto" @@ -133,10 +133,10 @@ func DefaultMetricsProvider(config *cfg.InstrumentationConfig) MetricsProvider { // Option sets a parameter for the node. type Option func(*Node) -// Temporary interface for switching to fast sync, we should get rid of v0 and v1 reactors. +// Temporary interface for switching to block sync, we should get rid of v0 and v1 reactors. // See: https://github.com/tendermint/tendermint/issues/4595 -type fastSyncReactor interface { - SwitchToFastSync(sm.State) error +type blockSyncReactor interface { + SwitchToBlockSync(sm.State) error } // CustomReactors allows you to add custom reactors (name -> p2p.Reactor) to @@ -212,7 +212,7 @@ type Node struct { eventBus *types.EventBus // pub/sub for services stateStore sm.Store blockStore *store.BlockStore // store the blockchain to disk - bcReactor p2p.Reactor // for fast-syncing + bcReactor p2p.Reactor // for block-syncing mempoolReactor p2p.Reactor // for gossipping transactions mempool mempl.Mempool stateSync bool // whether the node should state sync on startup @@ -443,16 +443,16 @@ func createBlockchainReactor(config *cfg.Config, state sm.State, blockExec *sm.BlockExecutor, blockStore *store.BlockStore, - fastSync bool, + blockSync bool, logger log.Logger) (bcReactor p2p.Reactor, err error) { - switch config.FastSync.Version { + switch config.BlockSync.Version { case "v0": - bcReactor = bc.NewReactor(state.Copy(), blockExec, blockStore, fastSync) + bcReactor = bc.NewReactor(state.Copy(), blockExec, blockStore, blockSync) case "v1", "v2": - return nil, fmt.Errorf("fast sync version %s has been deprecated. Please use v0", config.FastSync.Version) + return nil, fmt.Errorf("block sync version %s has been deprecated. Please use v0", config.BlockSync.Version) default: - return nil, fmt.Errorf("unknown fastsync version %s", config.FastSync.Version) + return nil, fmt.Errorf("unknown fastsync version %s", config.BlockSync.Version) } bcReactor.SetLogger(logger.With("module", "blockchain")) @@ -642,9 +642,9 @@ func createPEXReactorAndAddToSwitch(addrBook pex.AddrBook, config *cfg.Config, return pexReactor } -// startStateSync starts an asynchronous state sync process, then switches to fast sync mode. -func startStateSync(ssR *statesync.Reactor, bcR fastSyncReactor, conR *cs.Reactor, - stateProvider statesync.StateProvider, config *cfg.StateSyncConfig, fastSync bool, +// startStateSync starts an asynchronous state sync process, then switches to block sync mode. +func startStateSync(ssR *statesync.Reactor, bcR blockSyncReactor, conR *cs.Reactor, + stateProvider statesync.StateProvider, config *cfg.StateSyncConfig, blockSync bool, stateStore sm.Store, blockStore *store.BlockStore, state sm.State) error { ssR.Logger.Info("Starting state sync") @@ -682,13 +682,13 @@ func startStateSync(ssR *statesync.Reactor, bcR fastSyncReactor, conR *cs.Reacto return } - if fastSync { + if blockSync { // FIXME Very ugly to have these metrics bleed through here. conR.Metrics.StateSyncing.Set(0) - conR.Metrics.FastSyncing.Set(1) - err = bcR.SwitchToFastSync(state) + conR.Metrics.BlockSyncing.Set(1) + err = bcR.SwitchToBlockSync(state) if err != nil { - ssR.Logger.Error("Failed to switch to fast sync", "err", err) + ssR.Logger.Error("Failed to switch to block sync", "err", err) return } } else { @@ -783,9 +783,9 @@ func NewNode(config *cfg.Config, } } - // Determine whether we should do fast sync. This must happen after the handshake, since the + // Determine whether we should do block sync. This must happen after the handshake, since the // app may modify the validator set, specifying ourself as the only validator. - fastSync := config.FastSyncMode && !onlyValidatorIsUs(state, pubKey) + blockSync := config.BlockSyncMode && !onlyValidatorIsUs(state, pubKey) logNodeStartupInfo(state, pubKey, logger, consensusLogger) @@ -808,26 +808,26 @@ func NewNode(config *cfg.Config, sm.BlockExecutorWithMetrics(smMetrics), ) - // Make BlockchainReactor. Don't start fast sync if we're doing a state sync first. - bcReactor, err := createBlockchainReactor(config, state, blockExec, blockStore, fastSync && !stateSync, logger) + // Make BlockchainReactor. Don't start block sync if we're doing a state sync first. + bcReactor, err := createBlockchainReactor(config, state, blockExec, blockStore, blockSync && !stateSync, logger) if err != nil { return nil, fmt.Errorf("could not create blockchain reactor: %w", err) } - // Make ConsensusReactor. Don't enable fully if doing a state sync and/or fast sync first. + // Make ConsensusReactor. Don't enable fully if doing a state sync and/or block sync first. // FIXME We need to update metrics here, since other reactors don't have access to them. if stateSync { csMetrics.StateSyncing.Set(1) - } else if fastSync { - csMetrics.FastSyncing.Set(1) + } else if blockSync { + csMetrics.BlockSyncing.Set(1) } consensusReactor, consensusState := createConsensusReactor( config, state, blockExec, blockStore, mempool, evidencePool, - privValidator, csMetrics, stateSync || fastSync, eventBus, consensusLogger, + privValidator, csMetrics, stateSync || blockSync, eventBus, consensusLogger, ) // Set up state sync reactor, and schedule a sync if requested. - // FIXME The way we do phased startups (e.g. replay -> fast sync -> consensus) is very messy, + // FIXME The way we do phased startups (e.g. replay -> block sync -> consensus) is very messy, // we should clean this whole thing up. See: // https://github.com/tendermint/tendermint/issues/4644 stateSyncReactor := statesync.NewReactor( @@ -982,12 +982,12 @@ func (n *Node) OnStart() error { // Run state sync if n.stateSync { - bcR, ok := n.bcReactor.(fastSyncReactor) + bcR, ok := n.bcReactor.(blockSyncReactor) if !ok { return fmt.Errorf("this blockchain reactor does not support switching from state sync") } err := startStateSync(n.stateSyncReactor, bcR, n.consensusReactor, n.stateSyncProvider, - n.config.StateSync, n.config.FastSyncMode, n.stateStore, n.blockStore, n.stateSyncGenesis) + n.config.StateSync, n.config.BlockSyncMode, n.stateStore, n.blockStore, n.stateSyncGenesis) if err != nil { return fmt.Errorf("failed to start state sync: %w", err) } @@ -1335,7 +1335,7 @@ func makeNodeInfo( Network: genDoc.ChainID, Version: version.TMCoreSemVer, Channels: []byte{ - bc.BlockchainChannel, + bc.BlocksyncChannel, cs.StateChannel, cs.DataChannel, cs.VoteChannel, cs.VoteSetBitsChannel, mempl.MempoolChannel, evidence.EvidenceChannel, diff --git a/proto/tendermint/blockchain/types.pb.go b/proto/tendermint/blocksync/types.pb.go similarity index 90% rename from proto/tendermint/blockchain/types.pb.go rename to proto/tendermint/blocksync/types.pb.go index bc160b230..fcbef7107 100644 --- a/proto/tendermint/blockchain/types.pb.go +++ b/proto/tendermint/blocksync/types.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: tendermint/blockchain/types.proto +// source: tendermint/blocksync/types.proto -package blockchain +package blocksync import ( fmt "fmt" @@ -32,7 +32,7 @@ func (m *BlockRequest) Reset() { *m = BlockRequest{} } func (m *BlockRequest) String() string { return proto.CompactTextString(m) } func (*BlockRequest) ProtoMessage() {} func (*BlockRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_2927480384e78499, []int{0} + return fileDescriptor_19b397c236e0fa07, []int{0} } func (m *BlockRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -77,7 +77,7 @@ func (m *NoBlockResponse) Reset() { *m = NoBlockResponse{} } func (m *NoBlockResponse) String() string { return proto.CompactTextString(m) } func (*NoBlockResponse) ProtoMessage() {} func (*NoBlockResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_2927480384e78499, []int{1} + return fileDescriptor_19b397c236e0fa07, []int{1} } func (m *NoBlockResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -122,7 +122,7 @@ func (m *BlockResponse) Reset() { *m = BlockResponse{} } func (m *BlockResponse) String() string { return proto.CompactTextString(m) } func (*BlockResponse) ProtoMessage() {} func (*BlockResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_2927480384e78499, []int{2} + return fileDescriptor_19b397c236e0fa07, []int{2} } func (m *BlockResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -166,7 +166,7 @@ func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} func (*StatusRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_2927480384e78499, []int{3} + return fileDescriptor_19b397c236e0fa07, []int{3} } func (m *StatusRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -205,7 +205,7 @@ func (m *StatusResponse) Reset() { *m = StatusResponse{} } func (m *StatusResponse) String() string { return proto.CompactTextString(m) } func (*StatusResponse) ProtoMessage() {} func (*StatusResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_2927480384e78499, []int{4} + return fileDescriptor_19b397c236e0fa07, []int{4} } func (m *StatusResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -262,7 +262,7 @@ func (m *Message) Reset() { *m = Message{} } func (m *Message) String() string { return proto.CompactTextString(m) } func (*Message) ProtoMessage() {} func (*Message) Descriptor() ([]byte, []int) { - return fileDescriptor_2927480384e78499, []int{5} + return fileDescriptor_19b397c236e0fa07, []int{5} } func (m *Message) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -373,42 +373,41 @@ func (*Message) XXX_OneofWrappers() []interface{} { } func init() { - proto.RegisterType((*BlockRequest)(nil), "tendermint.blockchain.BlockRequest") - proto.RegisterType((*NoBlockResponse)(nil), "tendermint.blockchain.NoBlockResponse") - proto.RegisterType((*BlockResponse)(nil), "tendermint.blockchain.BlockResponse") - proto.RegisterType((*StatusRequest)(nil), "tendermint.blockchain.StatusRequest") - proto.RegisterType((*StatusResponse)(nil), "tendermint.blockchain.StatusResponse") - proto.RegisterType((*Message)(nil), "tendermint.blockchain.Message") + proto.RegisterType((*BlockRequest)(nil), "tendermint.blocksync.BlockRequest") + proto.RegisterType((*NoBlockResponse)(nil), "tendermint.blocksync.NoBlockResponse") + proto.RegisterType((*BlockResponse)(nil), "tendermint.blocksync.BlockResponse") + proto.RegisterType((*StatusRequest)(nil), "tendermint.blocksync.StatusRequest") + proto.RegisterType((*StatusResponse)(nil), "tendermint.blocksync.StatusResponse") + proto.RegisterType((*Message)(nil), "tendermint.blocksync.Message") } -func init() { proto.RegisterFile("tendermint/blockchain/types.proto", fileDescriptor_2927480384e78499) } +func init() { proto.RegisterFile("tendermint/blocksync/types.proto", fileDescriptor_19b397c236e0fa07) } -var fileDescriptor_2927480384e78499 = []byte{ - // 370 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0xc1, 0x4e, 0xfa, 0x40, - 0x10, 0xc6, 0xdb, 0x7f, 0x81, 0x7f, 0x32, 0x50, 0x1a, 0x9b, 0xa8, 0xc4, 0x98, 0x46, 0xab, 0x12, - 0x3d, 0xd8, 0x26, 0x78, 0x25, 0x1e, 0x38, 0x11, 0x13, 0x8c, 0xa9, 0xc6, 0x83, 0x17, 0xd2, 0xe2, - 0x86, 0x36, 0x4a, 0x17, 0xd9, 0xed, 0xc1, 0xb7, 0xf0, 0x19, 0x7c, 0x1a, 0x8f, 0x1c, 0x3d, 0x1a, - 0x78, 0x11, 0xc3, 0x6c, 0x29, 0x4b, 0x03, 0xf5, 0xb6, 0x3b, 0xfd, 0xe6, 0x37, 0xdf, 0x7e, 0x99, - 0xc2, 0x31, 0x27, 0xf1, 0x33, 0x99, 0x8c, 0xa2, 0x98, 0xbb, 0xc1, 0x2b, 0x1d, 0xbc, 0x0c, 0x42, - 0x3f, 0x8a, 0x5d, 0xfe, 0x3e, 0x26, 0xcc, 0x19, 0x4f, 0x28, 0xa7, 0xe6, 0xee, 0x4a, 0xe2, 0xac, - 0x24, 0x07, 0x87, 0x52, 0x27, 0xca, 0x45, 0xbf, 0x68, 0xb2, 0x9b, 0x50, 0xeb, 0x2c, 0xae, 0x1e, - 0x79, 0x4b, 0x08, 0xe3, 0xe6, 0x1e, 0x54, 0x42, 0x12, 0x0d, 0x43, 0xde, 0x50, 0x8f, 0xd4, 0x73, - 0xcd, 0x4b, 0x6f, 0xf6, 0x05, 0x18, 0xb7, 0x34, 0x55, 0xb2, 0x31, 0x8d, 0x19, 0xd9, 0x2a, 0xbd, - 0x06, 0x7d, 0x5d, 0x78, 0x09, 0x65, 0x1c, 0x89, 0xba, 0x6a, 0x6b, 0xdf, 0x91, 0x8c, 0x8a, 0x07, - 0x08, 0xbd, 0x50, 0xd9, 0x06, 0xe8, 0xf7, 0xdc, 0xe7, 0x09, 0x4b, 0x3d, 0xd9, 0x6d, 0xa8, 0x2f, - 0x0b, 0xc5, 0xa3, 0x4d, 0x13, 0x4a, 0x81, 0xcf, 0x48, 0xe3, 0x1f, 0x56, 0xf1, 0x6c, 0x7f, 0x6a, - 0xf0, 0xbf, 0x47, 0x18, 0xf3, 0x87, 0xc4, 0xbc, 0x01, 0x1d, 0x67, 0xf4, 0x27, 0x02, 0x9d, 0x3a, - 0x3a, 0x71, 0x36, 0x46, 0xe7, 0xc8, 0xc9, 0x74, 0x15, 0xaf, 0x16, 0xc8, 0x49, 0x3d, 0xc0, 0x4e, - 0x4c, 0xfb, 0x4b, 0x9c, 0x30, 0x86, 0x83, 0xab, 0xad, 0xe6, 0x16, 0x5e, 0x2e, 0xc1, 0xae, 0xe2, - 0x19, 0x71, 0x2e, 0xd4, 0x1e, 0xd4, 0x73, 0x48, 0x0d, 0x91, 0xa7, 0xc5, 0x16, 0x33, 0xa0, 0x1e, - 0xe4, 0x71, 0x0c, 0xa3, 0xcb, 0x5e, 0x5c, 0x2a, 0xc4, 0xad, 0x05, 0xbf, 0xc0, 0x31, 0xb9, 0x60, - 0xde, 0x81, 0x91, 0xe1, 0x52, 0x7b, 0x65, 0xe4, 0x9d, 0xfd, 0xc1, 0xcb, 0xfc, 0xd5, 0xd9, 0x5a, - 0xa5, 0x53, 0x06, 0x8d, 0x25, 0xa3, 0xce, 0xe3, 0xd7, 0xcc, 0x52, 0xa7, 0x33, 0x4b, 0xfd, 0x99, - 0x59, 0xea, 0xc7, 0xdc, 0x52, 0xa6, 0x73, 0x4b, 0xf9, 0x9e, 0x5b, 0xca, 0x53, 0x7b, 0x18, 0xf1, - 0x30, 0x09, 0x9c, 0x01, 0x1d, 0xb9, 0xf2, 0x26, 0xaf, 0x8e, 0xb8, 0xc8, 0xee, 0xc6, 0xff, 0x23, - 0xa8, 0xe0, 0xc7, 0xab, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5e, 0x59, 0x07, 0xbd, 0x3f, 0x03, - 0x00, 0x00, +var fileDescriptor_19b397c236e0fa07 = []byte{ + // 368 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0x4d, 0x4f, 0xfa, 0x40, + 0x10, 0xc6, 0xdb, 0x7f, 0x81, 0x7f, 0x32, 0x50, 0x1a, 0x1b, 0xa3, 0xc4, 0x98, 0x86, 0xd4, 0x97, + 0xe8, 0xc1, 0x36, 0xc1, 0xa3, 0xc6, 0x03, 0x27, 0x4c, 0x7c, 0x49, 0x4a, 0xbc, 0x78, 0x21, 0x14, + 0x37, 0x40, 0x94, 0x2e, 0x32, 0xdb, 0x03, 0xdf, 0xc2, 0x2f, 0xe0, 0xf7, 0xf1, 0xc8, 0xd1, 0xa3, + 0x81, 0x2f, 0x62, 0x98, 0x2d, 0x65, 0x69, 0xb0, 0xb7, 0xdd, 0xe9, 0x33, 0xbf, 0x79, 0xfa, 0x64, + 0x16, 0xea, 0x82, 0x45, 0x2f, 0x6c, 0x32, 0x1a, 0x46, 0xc2, 0x0f, 0xdf, 0x78, 0xef, 0x15, 0xa7, + 0x51, 0xcf, 0x17, 0xd3, 0x31, 0x43, 0x6f, 0x3c, 0xe1, 0x82, 0xdb, 0xbb, 0x6b, 0x85, 0x97, 0x2a, + 0x0e, 0x0e, 0x95, 0x3e, 0x52, 0xcb, 0x6e, 0xd9, 0xe3, 0x9e, 0x42, 0xa5, 0xb9, 0xbc, 0x06, 0xec, + 0x3d, 0x66, 0x28, 0xec, 0x3d, 0x28, 0x0d, 0xd8, 0xb0, 0x3f, 0x10, 0x35, 0xbd, 0xae, 0x9f, 0x19, + 0x41, 0x72, 0x73, 0xcf, 0xc1, 0x7a, 0xe0, 0x89, 0x12, 0xc7, 0x3c, 0x42, 0xf6, 0xa7, 0xf4, 0x06, + 0xcc, 0x4d, 0xe1, 0x05, 0x14, 0x69, 0x24, 0xe9, 0xca, 0x8d, 0x7d, 0x4f, 0xf1, 0x29, 0xfd, 0x4b, + 0xbd, 0x54, 0xb9, 0x16, 0x98, 0x6d, 0xd1, 0x15, 0x31, 0x26, 0x9e, 0xdc, 0x6b, 0xa8, 0xae, 0x0a, + 0xf9, 0xa3, 0x6d, 0x1b, 0x0a, 0x61, 0x17, 0x59, 0xed, 0x1f, 0x55, 0xe9, 0xec, 0x7e, 0x1a, 0xf0, + 0xff, 0x9e, 0x21, 0x76, 0xfb, 0xcc, 0xbe, 0x05, 0x93, 0x66, 0x74, 0x26, 0x12, 0x9d, 0x38, 0x72, + 0xbd, 0x6d, 0xc9, 0x79, 0x6a, 0x30, 0x2d, 0x2d, 0xa8, 0x84, 0x6a, 0x50, 0x6d, 0xd8, 0x89, 0x78, + 0x67, 0x45, 0x93, 0xbe, 0x68, 0x6e, 0xb9, 0x71, 0xb2, 0x1d, 0x97, 0xc9, 0xaf, 0xa5, 0x05, 0x56, + 0x94, 0x89, 0xf4, 0x0e, 0xaa, 0x19, 0xa2, 0x41, 0xc4, 0xa3, 0x5c, 0x83, 0x29, 0xcf, 0x0c, 0xb3, + 0x34, 0xa4, 0xdc, 0xd2, 0xdf, 0x2d, 0xe4, 0xd1, 0x36, 0x42, 0x5f, 0xd2, 0x50, 0x2d, 0xd8, 0x8f, + 0x60, 0xa5, 0xb4, 0xc4, 0x5c, 0x91, 0x70, 0xc7, 0xf9, 0xb8, 0xd4, 0x5d, 0x15, 0x37, 0x2a, 0xcd, + 0x22, 0x18, 0x18, 0x8f, 0x9a, 0x4f, 0x5f, 0x73, 0x47, 0x9f, 0xcd, 0x1d, 0xfd, 0x67, 0xee, 0xe8, + 0x1f, 0x0b, 0x47, 0x9b, 0x2d, 0x1c, 0xed, 0x7b, 0xe1, 0x68, 0xcf, 0x57, 0xfd, 0xa1, 0x18, 0xc4, + 0xa1, 0xd7, 0xe3, 0x23, 0x5f, 0x5d, 0xe2, 0xf5, 0x91, 0x76, 0xd8, 0xdf, 0xf6, 0x30, 0xc2, 0x12, + 0x7d, 0xbb, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xf5, 0x1c, 0xa3, 0x45, 0x37, 0x03, 0x00, 0x00, } func (m *BlockRequest) Marshal() (dAtA []byte, err error) { diff --git a/proto/tendermint/blockchain/types.proto b/proto/tendermint/blocksync/types.proto similarity index 94% rename from proto/tendermint/blockchain/types.proto rename to proto/tendermint/blocksync/types.proto index f5c143cf5..8c187c793 100644 --- a/proto/tendermint/blockchain/types.proto +++ b/proto/tendermint/blocksync/types.proto @@ -1,7 +1,7 @@ syntax = "proto3"; -package tendermint.blockchain; +package tendermint.blocksync; -option go_package = "github.com/tendermint/tendermint/proto/tendermint/blockchain"; +option go_package = "github.com/tendermint/tendermint/proto/tendermint/blocksync"; import "tendermint/types/block.proto"; diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 634daec68..5ce004275 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -643,7 +643,7 @@ the blockchain's `AppHash` which is verified via [light client verification](../ `Snapshot.Metadata` and/or incrementally verifying contents against `AppHash`. * When all chunks have been accepted, Tendermint will make an ABCI `Info` call to verify that `LastBlockAppHash` and `LastBlockHeight` matches the expected values, and record the - `AppVersion` in the node state. It then switches to fast sync or consensus and joins the + `AppVersion` in the node state. It then switches to block sync or consensus and joins the network. * If Tendermint is unable to retrieve the next chunk after some time (e.g. because no suitable peers are available), it will reject the snapshot and try a different one via `OfferSnapshot`. diff --git a/spec/p2p/messages/block-sync.md b/spec/p2p/messages/block-sync.md index 48aa6155f..122702f4f 100644 --- a/spec/p2p/messages/block-sync.md +++ b/spec/p2p/messages/block-sync.md @@ -10,7 +10,7 @@ Block sync has one channel. | Name | Number | |-------------------|--------| -| BlockchainChannel | 64 | +| BlocksyncChannel | 64 | ## Message Types diff --git a/test/e2e/README.md b/test/e2e/README.md index 4f89d2885..70a63dac5 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -160,4 +160,4 @@ tendermint start ./build/node ./node.socket.toml ``` -Check `node/config.go` to see how the settings of the test application can be tweaked. \ No newline at end of file +Check `node/config.go` to see how the settings of the test application can be tweaked. diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index e203e4d6b..684acb426 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -28,7 +28,7 @@ var ( // FIXME: grpc disabled due to https://github.com/tendermint/tendermint/issues/5439 nodeABCIProtocols = uniformChoice{"unix", "tcp", "builtin"} // "grpc" nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp"} - nodeFastSyncs = uniformChoice{false, true} + nodeBlockSyncs = uniformChoice{"v0"} // "v2" nodeStateSyncs = uniformChoice{false, true} nodeMempools = uniformChoice{"v0", "v1"} nodePersistIntervals = uniformChoice{0, 1, 5} @@ -201,7 +201,7 @@ func generateNode( StartAt: startAt, Database: nodeDatabases.Choose(r).(string), PrivvalProtocol: nodePrivvalProtocols.Choose(r).(string), - FastSync: nodeFastSyncs.Choose(r).(bool), + BlockSync: nodeBlockSyncs.Choose(r).(string), Mempool: nodeMempools.Choose(r).(string), StateSync: nodeStateSyncs.Choose(r).(bool) && startAt > 0, PersistInterval: ptrUint64(uint64(nodePersistIntervals.Choose(r).(int))), diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 1fff574ca..e210db379 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -63,10 +63,10 @@ abci_protocol = "builtin" perturb = ["pause"] [node.validator05] +block_sync = "v0" start_at = 1005 # Becomes part of the validator set at 1010 seeds = ["seed02"] database = "cleveldb" -fast_sync = true mempool_version = "v1" # FIXME: should be grpc, disabled due to https://github.com/tendermint/tendermint/issues/5439 #abci_protocol = "grpc" @@ -76,7 +76,7 @@ perturb = ["kill", "pause", "disconnect", "restart"] [node.full01] start_at = 1010 mode = "full" -fast_sync = true +block_sync = "v0" persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"] retain_blocks = 1 perturb = ["restart"] @@ -84,7 +84,7 @@ perturb = ["restart"] [node.full02] start_at = 1015 mode = "full" -fast_sync = true +block_sync = "v0" state_sync = true seeds = ["seed01"] perturb = ["restart"] @@ -97,4 +97,4 @@ persistent_peers = ["validator01", "validator02", "validator03"] [node.light02] mode= "light" start_at= 1015 -persistent_peers = ["validator04", "full01", "validator05"] \ No newline at end of file +persistent_peers = ["validator04", "full01", "validator05"] diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 44c8e9118..6099ab492 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -88,8 +88,9 @@ type ManifestNode struct { // runner will wait for the network to reach at least this block height. StartAt int64 `toml:"start_at"` - // FastSync specifies whether to enable the fast sync protocol. - FastSync bool `toml:"fast_sync"` + // BlockSync specifies the block sync mode: "" (disable), "v0" or "v2". + // Defaults to disabled. + BlockSync string `toml:"block_sync"` // Mempool specifies which version of mempool to use. Either "v0" or "v1" // This defaults to v0. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 1123c26a9..3d213f381 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -72,7 +72,7 @@ type Node struct { IP net.IP ProxyPort uint32 StartAt int64 - FastSync bool + BlockSync string StateSync bool Mempool string Database string @@ -155,7 +155,7 @@ func LoadTestnet(file string) (*Testnet, error) { ABCIProtocol: Protocol(testnet.ABCIProtocol), PrivvalProtocol: ProtocolFile, StartAt: nodeManifest.StartAt, - FastSync: nodeManifest.FastSync, + BlockSync: nodeManifest.BlockSync, Mempool: nodeManifest.Mempool, StateSync: nodeManifest.StateSync, PersistInterval: 1, @@ -297,6 +297,11 @@ func (n Node) Validate(testnet Testnet) error { } } } + switch n.BlockSync { + case "", "v0": + default: + return fmt.Errorf("invalid block sync setting %q", n.BlockSync) + } switch n.Mempool { case "", "v0", "v1": default: diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index 2dd84eaf2..311b1dda2 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -284,7 +284,11 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) { cfg.Mempool.Version = node.Mempool } - cfg.FastSyncMode = node.FastSync + if node.BlockSync == "" { + cfg.BlockSyncMode = false + } else { + cfg.BlockSync.Version = node.BlockSync + } if node.StateSync { cfg.StateSync.Enable = true From 294468d0373178705618e0ce76fa28468128cb20 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Wed, 17 Aug 2022 17:36:44 +0200 Subject: [PATCH 6/7] e2e: add evidence tests --- consensus/reactor_test.go | 5 +- evidence/pool.go | 13 +- evidence/pool_test.go | 35 ++-- evidence/reactor_test.go | 13 +- evidence/verify_test.go | 9 +- internal/test/block.go | 92 +++++++++ internal/test/commit.go | 40 ++++ internal/test/doc.go | 6 + internal/test/factory_test.go | 15 ++ internal/test/genesis.go | 34 ++++ internal/test/tx.go | 11 ++ internal/test/validator.go | 41 ++++ internal/test/vote.go | 44 +++++ node/node_test.go | 3 +- rpc/client/evidence_test.go | 4 +- state/execution_test.go | 3 +- state/validation_test.go | 6 +- test/e2e/networks/ci.toml | 15 +- test/e2e/pkg/manifest.go | 4 + test/e2e/pkg/testnet.go | 10 + test/e2e/runner/evidence.go | 320 ++++++++++++++++++++++++++++++++ test/e2e/runner/main.go | 41 +++- test/e2e/tests/evidence_test.go | 22 +++ types/block_test.go | 18 +- types/event_bus_test.go | 3 +- types/evidence.go | 36 ++-- types/evidence_test.go | 9 +- 27 files changed, 788 insertions(+), 64 deletions(-) create mode 100644 internal/test/block.go create mode 100644 internal/test/commit.go create mode 100644 internal/test/doc.go create mode 100644 internal/test/factory_test.go create mode 100644 internal/test/genesis.go create mode 100644 internal/test/tx.go create mode 100644 internal/test/validator.go create mode 100644 internal/test/vote.go create mode 100644 test/e2e/runner/evidence.go create mode 100644 test/e2e/tests/evidence_test.go diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 1997bcbfa..51fd3d223 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -188,7 +188,8 @@ func TestReactorWithEvidence(t *testing.T) { // mock the evidence pool // everyone includes evidence of another double signing vIdx := (i + 1) % nValidators - ev := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID()) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID()) + require.NoError(t, err) evpool := &statemocks.EvidencePool{} evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil) evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return([]types.Evidence{ @@ -205,7 +206,7 @@ func TestReactorWithEvidence(t *testing.T) { eventBus := types.NewEventBus() eventBus.SetLogger(log.TestingLogger().With("module", "events")) - err := eventBus.Start() + err = eventBus.Start() require.NoError(t, err) cs.SetEventBus(eventBus) diff --git a/evidence/pool.go b/evidence/pool.go index dfeb7a717..6489dc758 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -463,10 +463,13 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { // Check the height of the conflicting votes and fetch the corresponding time and validator set // to produce the valid evidence - var dve *types.DuplicateVoteEvidence + var ( + dve *types.DuplicateVoteEvidence + err error + ) switch { case voteSet.VoteA.Height == state.LastBlockHeight: - dve = types.NewDuplicateVoteEvidence( + dve, err = types.NewDuplicateVoteEvidence( voteSet.VoteA, voteSet.VoteB, state.LastBlockTime, @@ -486,7 +489,7 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { evpool.logger.Error("failed to load block time for conflicting votes", "height", voteSet.VoteA.Height) continue } - dve = types.NewDuplicateVoteEvidence( + dve, err = types.NewDuplicateVoteEvidence( voteSet.VoteA, voteSet.VoteB, blockMeta.Header.Time, @@ -502,6 +505,10 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { "state.LastBlockHeight", state.LastBlockHeight) continue } + if err != nil { + evpool.logger.Error("error in generating evidence from votes", "err", err) + continue + } // check if we already have this evidence if evpool.isPending(dve) { diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 365a3be88..961de6e4a 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -61,7 +61,8 @@ func TestEvidencePoolBasic(t *testing.T) { assert.Equal(t, 0, len(evs)) assert.Zero(t, size) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[0], evidenceChainID) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[0], evidenceChainID) + require.NoError(t, err) // good evidence evAdded := make(chan struct{}) @@ -133,8 +134,9 @@ func TestAddExpiredEvidence(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.evDescription, func(t *testing.T) { - ev := types.NewMockDuplicateVoteEvidenceWithValidator(tc.evHeight, tc.evTime, val, evidenceChainID) - err := pool.AddEvidence(ev) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(tc.evHeight, tc.evTime, val, evidenceChainID) + require.NoError(t, err) + err = pool.AddEvidence(ev) if tc.expErr { assert.Error(t, err) } else { @@ -149,7 +151,8 @@ func TestReportConflictingVotes(t *testing.T) { pool, pv := defaultTestPool(height) val := types.NewValidator(pv.PrivKey.PubKey(), 10) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height+1, defaultEvidenceTime, pv, evidenceChainID) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height+1, defaultEvidenceTime, pv, evidenceChainID) + require.NoError(t, err) pool.ReportConflictingVotes(ev.VoteA, ev.VoteB) @@ -185,12 +188,14 @@ func TestEvidencePoolUpdate(t *testing.T) { state := pool.State() // create new block (no need to save it to blockStore) - prunedEv := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute), + prunedEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) - err := pool.AddEvidence(prunedEv) require.NoError(t, err) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute), + err = pool.AddEvidence(prunedEv) + require.NoError(t, err) + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute), val, evidenceChainID) + require.NoError(t, err) lastCommit := makeCommit(height, val.PrivKey.PubKey().Address()) block := types.MakeBlock(height+1, []types.Tx{}, lastCommit, []types.Evidence{ev}) // update state (partially) @@ -215,9 +220,10 @@ func TestEvidencePoolUpdate(t *testing.T) { func TestVerifyPendingEvidencePasses(t *testing.T) { var height int64 = 1 pool, val := defaultTestPool(height) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) - err := pool.AddEvidence(ev) + require.NoError(t, err) + err = pool.AddEvidence(ev) require.NoError(t, err) err = pool.CheckEvidence(types.EvidenceList{ev}) @@ -227,9 +233,10 @@ func TestVerifyPendingEvidencePasses(t *testing.T) { func TestVerifyDuplicatedEvidenceFails(t *testing.T) { var height int64 = 1 pool, val := defaultTestPool(height) - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) - err := pool.CheckEvidence(types.EvidenceList{ev, ev}) + require.NoError(t, err) + err = pool.CheckEvidence(types.EvidenceList{ev, ev}) if assert.Error(t, err) { assert.Equal(t, "duplicate evidence", err.(*types.ErrInvalidEvidence).Reason.Error()) } @@ -311,10 +318,12 @@ func TestRecoverPendingEvidence(t *testing.T) { pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore) require.NoError(t, err) pool.SetLogger(log.TestingLogger()) - goodEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(height, + goodEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(10*time.Minute), val, evidenceChainID) - expiredEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1), + require.NoError(t, err) + expiredEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1), defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID) + require.NoError(t, err) err = pool.AddEvidence(goodEvidence) require.NoError(t, err) err = pool.AddEvidence(expiredEvidence) diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index c0a22be26..4f8e73261 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -152,9 +152,10 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) { // the first reactor receives three more evidence evList = make([]types.Evidence, 3) for i := 0; i < 3; i++ { - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i), + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i), time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, state.ChainID) - err := pools[0].AddEvidence(ev) + require.NoError(t, err) + err = pools[0].AddEvidence(ev) require.NoError(t, err) evList[i] = ev } @@ -327,9 +328,10 @@ func _waitForEvidence( func sendEvidence(t *testing.T, evpool *evidence.Pool, val types.PrivValidator, n int) types.EvidenceList { evList := make([]types.Evidence, n) for i := 0; i < n; i++ { - ev := types.NewMockDuplicateVoteEvidenceWithValidator(int64(i+1), + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(int64(i+1), time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, evidenceChainID) - err := evpool.AddEvidence(ev) + require.NoError(t, err) + err = evpool.AddEvidence(ev) require.NoError(t, err) evList[i] = ev } @@ -377,12 +379,13 @@ func TestEvidenceVectors(t *testing.T) { valSet := types.NewValidatorSet([]*types.Validator{val}) - dupl := types.NewDuplicateVoteEvidence( + dupl, err := types.NewDuplicateVoteEvidence( exampleVote(1), exampleVote(2), defaultEvidenceTime, valSet, ) + require.NoError(t, err) testCases := []struct { testName string diff --git a/evidence/verify_test.go b/evidence/verify_test.go index 7825f9d43..f8021f436 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -412,11 +412,14 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) { } // create good evidence and correct validator power - goodEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) + goodEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) + require.NoError(t, err) goodEv.ValidatorPower = 1 goodEv.TotalVotingPower = 1 - badEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) - badTimeEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime.Add(1*time.Minute), val, chainID) + badEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID) + require.NoError(t, err) + badTimeEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime.Add(1*time.Minute), val, chainID) + require.NoError(t, err) badTimeEv.ValidatorPower = 1 badTimeEv.TotalVotingPower = 1 state := sm.State{ diff --git a/internal/test/block.go b/internal/test/block.go new file mode 100644 index 000000000..fe63730db --- /dev/null +++ b/internal/test/block.go @@ -0,0 +1,92 @@ +package test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" +) + +const ( + DefaultTestChainID = "test-chain" +) + +var ( + DefaultTestTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) +) + +func RandomAddress() []byte { + return crypto.CRandBytes(crypto.AddressSize) +} + +func RandomHash() []byte { + return crypto.CRandBytes(tmhash.Size) +} + +func MakeBlockID() types.BlockID { + return MakeBlockIDWithHash(RandomHash()) +} + +func MakeBlockIDWithHash(hash []byte) types.BlockID { + return types.BlockID{ + Hash: hash, + PartSetHeader: types.PartSetHeader{ + Total: 100, + Hash: RandomHash(), + }, + } +} + +// MakeHeader fills the rest of the contents of the header such that it passes +// validate basic +func MakeHeader(t *testing.T, h *types.Header) *types.Header { + t.Helper() + if h.Version.Block == 0 { + h.Version.Block = version.BlockProtocol + } + if h.Height == 0 { + h.Height = 1 + } + if h.LastBlockID.IsZero() { + h.LastBlockID = MakeBlockID() + } + if h.ChainID == "" { + h.ChainID = DefaultTestChainID + } + if len(h.LastCommitHash) == 0 { + h.LastCommitHash = RandomHash() + } + if len(h.DataHash) == 0 { + h.DataHash = RandomHash() + } + if len(h.ValidatorsHash) == 0 { + h.ValidatorsHash = RandomHash() + } + if len(h.NextValidatorsHash) == 0 { + h.NextValidatorsHash = RandomHash() + } + if len(h.ConsensusHash) == 0 { + h.ConsensusHash = RandomHash() + } + if len(h.AppHash) == 0 { + h.AppHash = RandomHash() + } + if len(h.LastResultsHash) == 0 { + h.LastResultsHash = RandomHash() + } + if len(h.EvidenceHash) == 0 { + h.EvidenceHash = RandomHash() + } + if len(h.ProposerAddress) == 0 { + h.ProposerAddress = RandomAddress() + } + + require.NoError(t, h.ValidateBasic()) + + return h +} diff --git a/internal/test/commit.go b/internal/test/commit.go new file mode 100644 index 000000000..4f933aca5 --- /dev/null +++ b/internal/test/commit.go @@ -0,0 +1,40 @@ +package test + +import ( + "context" + "time" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +func MakeCommit(ctx context.Context, blockID types.BlockID, height int64, round int32, voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time) (*types.Commit, error) { + // all sign + for i := 0; i < len(validators); i++ { + pubKey, err := validators[i].GetPubKey() + if err != nil { + return nil, err + } + vote := &types.Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: int32(i), + Height: height, + Round: round, + Type: tmproto.PrecommitType, + BlockID: blockID, + Timestamp: now, + } + + v := vote.ToProto() + + if err := validators[i].SignVote(voteSet.ChainID(), v); err != nil { + return nil, err + } + vote.Signature = v.Signature + if _, err := voteSet.AddVote(vote); err != nil { + return nil, err + } + } + + return voteSet.MakeCommit(), nil +} diff --git a/internal/test/doc.go b/internal/test/doc.go new file mode 100644 index 000000000..86438e290 --- /dev/null +++ b/internal/test/doc.go @@ -0,0 +1,6 @@ +/* +Package factory provides generation code for common structs in Tendermint. +It is used primarily for the testing of internal components such as statesync, +consensus, blocksync etc.. +*/ +package test diff --git a/internal/test/factory_test.go b/internal/test/factory_test.go new file mode 100644 index 000000000..6231cc7cc --- /dev/null +++ b/internal/test/factory_test.go @@ -0,0 +1,15 @@ +package test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/types" +) + +func TestMakeHeader(t *testing.T) { + header := MakeHeader(t, &types.Header{}) + require.NotNil(t, header) + + require.NoError(t, header.ValidateBasic()) +} diff --git a/internal/test/genesis.go b/internal/test/genesis.go new file mode 100644 index 000000000..66323894f --- /dev/null +++ b/internal/test/genesis.go @@ -0,0 +1,34 @@ +package test + +import ( + "time" + + cfg "github.com/tendermint/tendermint/config" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +func GenesisDoc( + config *cfg.Config, + time time.Time, + validators []*types.Validator, + consensusParams *tmproto.ConsensusParams, +) *types.GenesisDoc { + + genesisValidators := make([]types.GenesisValidator, len(validators)) + + for i := range validators { + genesisValidators[i] = types.GenesisValidator{ + Power: validators[i].VotingPower, + PubKey: validators[i].PubKey, + } + } + + return &types.GenesisDoc{ + GenesisTime: time, + InitialHeight: 1, + ChainID: config.ChainID(), + Validators: genesisValidators, + ConsensusParams: consensusParams, + } +} diff --git a/internal/test/tx.go b/internal/test/tx.go new file mode 100644 index 000000000..c61d0cfe0 --- /dev/null +++ b/internal/test/tx.go @@ -0,0 +1,11 @@ +package test + +import "github.com/tendermint/tendermint/types" + +func MakeNTxs(height, n int64) []types.Tx { + txs := make([]types.Tx, n) + for i := range txs { + txs[i] = types.Tx([]byte{byte(height), byte(i / 256), byte(i % 256)}) + } + return txs +} diff --git a/internal/test/validator.go b/internal/test/validator.go new file mode 100644 index 000000000..d0fdf8007 --- /dev/null +++ b/internal/test/validator.go @@ -0,0 +1,41 @@ +package test + +import ( + "context" + "sort" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/types" +) + +func Validator(ctx context.Context, votingPower int64) (*types.Validator, types.PrivValidator, error) { + privVal := types.NewMockPV() + pubKey, err := privVal.GetPubKey() + if err != nil { + return nil, nil, err + } + + val := types.NewValidator(pubKey, votingPower) + return val, privVal, nil +} + +func ValidatorSet(ctx context.Context, t *testing.T, numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) { + var ( + valz = make([]*types.Validator, numValidators) + privValidators = make([]types.PrivValidator, numValidators) + ) + t.Helper() + + for i := 0; i < numValidators; i++ { + val, privValidator, err := Validator(ctx, votingPower) + require.NoError(t, err) + valz[i] = val + privValidators[i] = privValidator + } + + sort.Sort(types.PrivValidatorsByAddress(privValidators)) + + return types.NewValidatorSet(valz), privValidators +} diff --git a/internal/test/vote.go b/internal/test/vote.go new file mode 100644 index 000000000..e2a8f15fa --- /dev/null +++ b/internal/test/vote.go @@ -0,0 +1,44 @@ +package test + +import ( + "context" + "time" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" +) + +func MakeVote( + ctx context.Context, + val types.PrivValidator, + chainID string, + valIndex int32, + height int64, + round int32, + step int, + blockID types.BlockID, + time time.Time, +) (*types.Vote, error) { + pubKey, err := val.GetPubKey() + if err != nil { + return nil, err + } + + v := &types.Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: valIndex, + Height: height, + Round: round, + Type: tmproto.SignedMsgType(step), + BlockID: blockID, + Timestamp: time, + } + + vpb := v.ToProto() + if err := val.SignVote(chainID, vpb); err != nil { + return nil, err + } + + v.Signature = vpb.Signature + return v, nil +} diff --git a/node/node_test.go b/node/node_test.go index e60897cb0..58b96ed13 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -277,7 +277,8 @@ func TestCreateProposalBlock(t *testing.T) { // than can fit in a block var currentBytes int64 for currentBytes <= maxEvidenceBytes { - ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain") + ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain") + require.NoError(t, err) currentBytes += int64(len(ev.Bytes())) evidencePool.ReportConflictingVotes(ev.VoteA, ev.VoteB) } diff --git a/rpc/client/evidence_test.go b/rpc/client/evidence_test.go index 527b8a9b5..a813d3912 100644 --- a/rpc/client/evidence_test.go +++ b/rpc/client/evidence_test.go @@ -45,7 +45,9 @@ func newEvidence(t *testing.T, val *privval.FilePV, validator := types.NewValidator(val.Key.PubKey, 10) valSet := types.NewValidatorSet([]*types.Validator{validator}) - return types.NewDuplicateVoteEvidence(vote, vote2, defaultTestTime, valSet) + ev, err := types.NewDuplicateVoteEvidence(vote, vote2, defaultTestTime, valSet) + require.NoError(t, err) + return ev } func makeEvidences( diff --git a/state/execution_test.go b/state/execution_test.go index 8b72dc952..7e5cf2124 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -153,7 +153,8 @@ func TestBeginBlockByzantineValidators(t *testing.T) { } // we don't need to worry about validating the evidence as long as they pass validate basic - dve := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID) + dve, err := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID) + require.NoError(t, err) dve.ValidatorPower = 1000 lcae := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ diff --git a/state/validation_test.go b/state/validation_test.go index afd47a650..ca7356cb9 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -243,8 +243,9 @@ func TestValidateBlockEvidence(t *testing.T) { var currentBytes int64 // more bytes than the maximum allowed for evidence for currentBytes <= maxBytesEvidence { - newEv := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), + newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[proposerAddr.String()], chainID) + require.NoError(t, err) evidence = append(evidence, newEv) currentBytes += int64(len(newEv.Bytes())) } @@ -263,8 +264,9 @@ func TestValidateBlockEvidence(t *testing.T) { var currentBytes int64 // precisely the amount of allowed evidence for { - newEv := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, + newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[proposerAddr.String()], chainID) + require.NoError(t, err) currentBytes += int64(len(newEv.Bytes())) if currentBytes >= maxBytesEvidence { break diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 1fff574ca..fad578bf7 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -3,6 +3,7 @@ ipv6 = true initial_height = 1000 +evidence = 5 initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" } [validators] @@ -27,11 +28,7 @@ validator05 = 50 [node.seed01] mode = "seed" -seeds = ["seed02"] - -[node.seed02] -mode = "seed" -seeds = ["seed01"] +perturb = ["restart"] [node.validator01] seeds = ["seed01"] @@ -39,7 +36,7 @@ snapshot_interval = 5 perturb = ["disconnect"] [node.validator02] -seeds = ["seed02"] +seeds = ["seed01"] database = "boltdb" abci_protocol = "tcp" privval_protocol = "tcp" @@ -53,7 +50,7 @@ database = "badgerdb" #abci_protocol = "grpc" privval_protocol = "unix" persist_interval = 3 -retain_blocks = 3 +retain_blocks = 10 perturb = ["kill"] [node.validator04] @@ -64,7 +61,7 @@ perturb = ["pause"] [node.validator05] start_at = 1005 # Becomes part of the validator set at 1010 -seeds = ["seed02"] +persistent_peers = ["validator01", "full01"] database = "cleveldb" fast_sync = true mempool_version = "v1" @@ -78,7 +75,7 @@ start_at = 1010 mode = "full" fast_sync = true persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"] -retain_blocks = 1 +retain_blocks = 10 perturb = ["restart"] [node.full02] diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 44c8e9118..5f00682d9 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -51,6 +51,10 @@ type Manifest struct { // Options are ed25519 & secp256k1 KeyType string `toml:"key_type"` + // Evidence indicates the amount of evidence that will be injected into the + // testnet via the RPC endpoint of a random node. Default is 0 + Evidence int `toml:"evidence"` + // ABCIProtocol specifies the protocol used to communicate with the ABCI // application: "unix", "tcp", "grpc", or "builtin". Defaults to builtin. // builtin will build a complete Tendermint node into the application and diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 1123c26a9..fb17acb46 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -11,6 +11,7 @@ import ( "sort" "strconv" "strings" + "time" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" @@ -45,6 +46,9 @@ const ( PerturbationKill Perturbation = "kill" PerturbationPause Perturbation = "pause" PerturbationRestart Perturbation = "restart" + + EvidenceAgeHeight int64 = 7 + EvidenceAgeTime time.Duration = 500 * time.Millisecond ) // Testnet represents a single testnet. @@ -59,6 +63,7 @@ type Testnet struct { ValidatorUpdates map[int64]map[*Node]int64 Nodes []*Node KeyType string + Evidence int ABCIProtocol string } @@ -122,6 +127,7 @@ func LoadTestnet(file string) (*Testnet, error) { Validators: map[*Node]int64{}, ValidatorUpdates: map[int64]map[*Node]int64{}, Nodes: []*Node{}, + Evidence: manifest.Evidence, ABCIProtocol: manifest.ABCIProtocol, } if len(manifest.KeyType) != 0 { @@ -328,6 +334,10 @@ func (n Node) Validate(testnet Testnet) error { if n.StateSync && n.StartAt == 0 { return errors.New("state synced nodes cannot start at the initial height") } + if n.RetainBlocks != 0 && n.RetainBlocks < uint64(EvidenceAgeHeight) { + return fmt.Errorf("retain_blocks must be greater or equal to max evidence age (%d)", + EvidenceAgeHeight) + } if n.PersistInterval == 0 && n.RetainBlocks > 0 { return errors.New("persist_interval=0 requires retain_blocks=0") } diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go new file mode 100644 index 000000000..f0a3838e9 --- /dev/null +++ b/test/e2e/runner/evidence.go @@ -0,0 +1,320 @@ +package main + +import ( + "bytes" + "context" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "path/filepath" + "time" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/internal/test" + tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/privval" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" + "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" +) + +// 1 in 4 evidence is light client evidence, the rest is duplicate vote evidence +const lightClientEvidenceRatio = 4 + +// InjectEvidence takes a running testnet and generates an amount of valid +// evidence and broadcasts it to a random node through the rpc endpoint `/broadcast_evidence`. +// Evidence is random and can be a mixture of LightClientAttackEvidence and +// DuplicateVoteEvidence. +func InjectEvidence(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet, amount int) error { + // select a random node + var targetNode *e2e.Node + + for _, idx := range r.Perm(len(testnet.Nodes)) { + targetNode = testnet.Nodes[idx] + + if targetNode.Mode == e2e.ModeSeed || targetNode.Mode == e2e.ModeLight { + targetNode = nil + continue + } + + break + } + + if targetNode == nil { + return errors.New("could not find node to inject evidence into") + } + + logger.Info(fmt.Sprintf("Injecting evidence through %v (amount: %d)...", targetNode.Name, amount)) + + client, err := targetNode.Client() + if err != nil { + return err + } + + // request the latest block and validator set from the node + blockRes, err := client.Block(ctx, nil) + if err != nil { + return err + } + evidenceHeight := blockRes.Block.Height + waitHeight := blockRes.Block.Height + 3 + + nValidators := 100 + valRes, err := client.Validators(ctx, &evidenceHeight, nil, &nValidators) + if err != nil { + return err + } + + valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators) + if err != nil { + return err + } + + // get the private keys of all the validators in the network + privVals, err := getPrivateValidatorKeys(testnet) + if err != nil { + return err + } + + // wait for the node to reach the height above the forged height so that + // it is able to validate the evidence + _, err = waitForNode(targetNode, waitHeight, time.Minute) + if err != nil { + return err + } + + var ev types.Evidence + for i := 1; i <= amount; i++ { + if i%lightClientEvidenceRatio == 0 { + ev, err = generateLightClientAttackEvidence( + ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, + ) + } else { + ev, err = generateDuplicateVoteEvidence( + ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, + ) + } + if err != nil { + return err + } + + _, err := client.BroadcastEvidence(ctx, ev) + if err != nil { + return err + } + } + + // wait for the node to reach the height above the forged height so that + // it is able to validate the evidence + _, err = waitForNode(targetNode, blockRes.Block.Height+2, 30*time.Second) + if err != nil { + return err + } + + logger.Info(fmt.Sprintf("Finished sending evidence (height %d)", blockRes.Block.Height+2)) + + return nil +} + +func getPrivateValidatorKeys(testnet *e2e.Testnet) ([]types.MockPV, error) { + privVals := []types.MockPV{} + + for _, node := range testnet.Nodes { + if node.Mode == e2e.ModeValidator { + privKeyPath := filepath.Join(testnet.Dir, node.Name, PrivvalKeyFile) + privKey, err := readPrivKey(privKeyPath) + if err != nil { + return nil, err + } + // Create mock private validators from the validators private key. MockPV is + // stateless which means we can double vote and do other funky stuff + privVals = append(privVals, types.NewMockPVWithParams(privKey, false, false)) + } + } + + return privVals, nil +} + +// creates evidence of a lunatic attack. The height provided is the common height. +// The forged height happens 2 blocks later. +func generateLightClientAttackEvidence( + ctx context.Context, + privVals []types.MockPV, + height int64, + vals *types.ValidatorSet, + chainID string, + evTime time.Time, +) (*types.LightClientAttackEvidence, error) { + // forge a random header + forgedHeight := height + 2 + forgedTime := evTime.Add(1 * time.Second) + header := makeHeaderRandom(chainID, forgedHeight) + header.Time = forgedTime + + // add a new bogus validator and remove an existing one to + // vary the validator set slightly + pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals) + if err != nil { + return nil, err + } + + header.ValidatorsHash = conflictingVals.Hash() + + // create a commit for the forged header + blockID := makeBlockID(header.Hash(), 1000, []byte("partshash")) + voteSet := types.NewVoteSet(chainID, forgedHeight, 0, tmproto.SignedMsgType(2), conflictingVals) + commit, err := test.MakeCommit(ctx, blockID, forgedHeight, 0, voteSet, pv, forgedTime) + if err != nil { + return nil, err + } + + ev := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: &types.SignedHeader{ + Header: header, + Commit: commit, + }, + ValidatorSet: conflictingVals, + }, + CommonHeight: height, + TotalVotingPower: vals.TotalVotingPower(), + Timestamp: evTime, + } + ev.ByzantineValidators = ev.GetByzantineValidators(vals, &types.SignedHeader{ + Header: makeHeaderRandom(chainID, forgedHeight), + }) + return ev, nil +} + +// generateDuplicateVoteEvidence picks a random validator from the val set and +// returns duplicate vote evidence against the validator +func generateDuplicateVoteEvidence( + ctx context.Context, + privVals []types.MockPV, + height int64, + vals *types.ValidatorSet, + chainID string, + time time.Time, +) (*types.DuplicateVoteEvidence, error) { + privVal, valIdx, err := getRandomValidatorIndex(privVals, vals) + if err != nil { + return nil, err + } + voteA, err := test.MakeVote(ctx, privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time) + if err != nil { + return nil, err + } + voteB, err := test.MakeVote(ctx, privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time) + if err != nil { + return nil, err + } + ev, err := types.NewDuplicateVoteEvidence(voteA, voteB, time, vals) + if err != nil { + return nil, fmt.Errorf("could not generate evidence: %w", err) + } + + return ev, nil +} + +// getRandomValidatorIndex picks a random validator from a slice of mock PrivVals that's +// also part of the validator set, returning the PrivVal and its index in the validator set +func getRandomValidatorIndex(privVals []types.MockPV, vals *types.ValidatorSet) (types.MockPV, int32, error) { + for _, idx := range rand.Perm(len(privVals)) { + pv := privVals[idx] + valIdx, _ := vals.GetByAddress(pv.PrivKey.PubKey().Address()) + if valIdx >= 0 { + return pv, valIdx, nil + } + } + return types.MockPV{}, -1, errors.New("no private validator found in validator set") +} + +func readPrivKey(keyFilePath string) (crypto.PrivKey, error) { + keyJSONBytes, err := ioutil.ReadFile(keyFilePath) + if err != nil { + return nil, err + } + pvKey := privval.FilePVKey{} + err = tmjson.Unmarshal(keyJSONBytes, &pvKey) + if err != nil { + return nil, fmt.Errorf("error reading PrivValidator key from %v: %w", keyFilePath, err) + } + + return pvKey.PrivKey, nil +} + +func makeHeaderRandom(chainID string, height int64) *types.Header { + return &types.Header{ + Version: tmversion.Consensus{Block: version.BlockProtocol, App: 1}, + ChainID: chainID, + Height: height, + Time: time.Now(), + LastBlockID: makeBlockID([]byte("headerhash"), 1000, []byte("partshash")), + LastCommitHash: crypto.CRandBytes(tmhash.Size), + DataHash: crypto.CRandBytes(tmhash.Size), + ValidatorsHash: crypto.CRandBytes(tmhash.Size), + NextValidatorsHash: crypto.CRandBytes(tmhash.Size), + ConsensusHash: crypto.CRandBytes(tmhash.Size), + AppHash: crypto.CRandBytes(tmhash.Size), + LastResultsHash: crypto.CRandBytes(tmhash.Size), + EvidenceHash: crypto.CRandBytes(tmhash.Size), + ProposerAddress: crypto.CRandBytes(crypto.AddressSize), + } +} + +func makeRandomBlockID() types.BlockID { + return makeBlockID(crypto.CRandBytes(tmhash.Size), 100, crypto.CRandBytes(tmhash.Size)) +} + +func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { + var ( + h = make([]byte, tmhash.Size) + psH = make([]byte, tmhash.Size) + ) + copy(h, hash) + copy(psH, partSetHash) + return types.BlockID{ + Hash: h, + PartSetHeader: types.PartSetHeader{ + Total: partSetSize, + Hash: psH, + }, + } +} + +func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet, +) ([]types.PrivValidator, *types.ValidatorSet, error) { + newVal, newPrivVal, err := test.Validator(ctx, 10) + if err != nil { + return nil, nil, err + } + + var newVals *types.ValidatorSet + if vals.Size() > 2 { + newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal)) + } else { + newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal)) + } + + // we need to sort the priv validators with the same index as the validator set + pv := make([]types.PrivValidator, newVals.Size()) + for idx, val := range newVals.Validators { + found := false + for _, p := range append(privVals, newPrivVal.(types.MockPV)) { + if bytes.Equal(p.PrivKey.PubKey().Address(), val.Address) { + pv[idx] = p + found = true + break + } + } + if !found { + return nil, nil, fmt.Errorf("missing priv validator for %v", val.Address) + } + } + + return pv, newVals, nil +} diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index 80048ee98..798bf12e4 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "math/rand" "os" "strconv" @@ -12,9 +13,9 @@ import ( e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) -var ( - logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) -) +const randomSeed = 2308084734268 + +var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) func main() { NewCLI().Run() @@ -56,6 +57,8 @@ func NewCLI() *CLI { return err } + r := rand.New(rand.NewSource(randomSeed)) // nolint: gosec + chLoadResult := make(chan error) ctx, loadCancel := context.WithCancel(context.Background()) defer loadCancel() @@ -84,6 +87,15 @@ func NewCLI() *CLI { } } + if cli.testnet.Evidence > 0 { + if err := InjectEvidence(ctx, r, cli.testnet, cli.testnet.Evidence); err != nil { + return err + } + if err := Wait(cli.testnet, 5); err != nil { // ensure chain progress + return err + } + } + loadCancel() if err := <-chLoadResult; err != nil { return err @@ -175,6 +187,29 @@ func NewCLI() *CLI { }, }) + cli.root.AddCommand(&cobra.Command{ + Use: "evidence [amount]", + Args: cobra.MaximumNArgs(1), + Short: "Generates and broadcasts evidence to a random node", + RunE: func(cmd *cobra.Command, args []string) (err error) { + amount := 1 + + if len(args) == 1 { + amount, err = strconv.Atoi(args[0]) + if err != nil { + return err + } + } + + return InjectEvidence( + cmd.Context(), + rand.New(rand.NewSource(randomSeed)), // nolint: gosec + cli.testnet, + amount, + ) + }, + }) + cli.root.AddCommand(&cobra.Command{ Use: "test", Short: "Runs test cases against a running testnet", diff --git a/test/e2e/tests/evidence_test.go b/test/e2e/tests/evidence_test.go new file mode 100644 index 000000000..f7f2ede79 --- /dev/null +++ b/test/e2e/tests/evidence_test.go @@ -0,0 +1,22 @@ +package e2e_test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// assert that all nodes that have blocks at the height of a misbehavior has evidence +// for that misbehavior +func TestEvidence_Misbehavior(t *testing.T) { + blocks := fetchBlockChain(t) + testnet := loadTestnet(t) + seenEvidence := 0 + for _, block := range blocks { + if len(block.Evidence.Evidence) != 0 { + seenEvidence += len(block.Evidence.Evidence) + } + } + require.Equal(t, testnet.Evidence, seenEvidence, + "difference between the amount of evidence produced and committed") +} diff --git a/types/block_test.go b/types/block_test.go index 2355cb0f1..2f477759b 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -41,7 +41,8 @@ func TestBlockAddEvidence(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + require.NoError(t, err) evList := []Evidence{ev} block := MakeBlock(h, txs, commit, evList) @@ -61,7 +62,8 @@ func TestBlockValidateBasic(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + require.NoError(t, err) evList := []Evidence{ev} testCases := []struct { @@ -127,7 +129,8 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + require.NoError(t, err) evList := []Evidence{ev} partSet := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(512) @@ -144,7 +147,8 @@ func TestBlockHashesTo(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now()) require.NoError(t, err) - ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain") + require.NoError(t, err) evList := []Evidence{ev} block := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList) @@ -635,7 +639,8 @@ func TestBlockProtoBuf(t *testing.T) { b2 := MakeBlock(h, []Tx{Tx([]byte{1})}, c1, []Evidence{}) b2.ProposerAddress = tmrand.Bytes(crypto.AddressSize) evidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) - evi := NewMockDuplicateVoteEvidence(h, evidenceTime, "block-test-chain") + evi, err := NewMockDuplicateVoteEvidence(h, evidenceTime, "block-test-chain") + require.NoError(t, err) b2.Evidence = EvidenceData{Evidence: EvidenceList{evi}} b2.EvidenceHash = b2.Evidence.Hash() @@ -699,7 +704,8 @@ func TestDataProtoBuf(t *testing.T) { // TestEvidenceDataProtoBuf ensures parity in converting to and from proto. func TestEvidenceDataProtoBuf(t *testing.T) { const chainID = "mychain" - ev := NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID) + ev, err := NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID) + require.NoError(t, err) data := &EvidenceData{Evidence: EvidenceList{ev}} _ = data.ByteSize() testCases := []struct { diff --git a/types/event_bus_test.go b/types/event_bus_test.go index a0a2e2e5f..30bd6b178 100644 --- a/types/event_bus_test.go +++ b/types/event_bus_test.go @@ -285,7 +285,8 @@ func TestEventBusPublishEventNewEvidence(t *testing.T) { } }) - ev := NewMockDuplicateVoteEvidence(1, time.Now(), "test-chain-id") + ev, err := NewMockDuplicateVoteEvidence(1, time.Now(), "test-chain-id") + require.NoError(t, err) query := "tm.event='NewEvidence'" evSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query)) diff --git a/types/evidence.go b/types/evidence.go index 35acaaed1..b6126bdc5 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -45,15 +45,20 @@ type DuplicateVoteEvidence struct { var _ Evidence = &DuplicateVoteEvidence{} // NewDuplicateVoteEvidence creates DuplicateVoteEvidence with right ordering given -// two conflicting votes. If one of the votes is nil, evidence returned is nil as well -func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *ValidatorSet) *DuplicateVoteEvidence { +// two conflicting votes. If either of the votes is nil, the val set is nil or the voter is +// not in the val set, an error is returned +func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *ValidatorSet, +) (*DuplicateVoteEvidence, error) { var voteA, voteB *Vote - if vote1 == nil || vote2 == nil || valSet == nil { - return nil + if vote1 == nil || vote2 == nil { + return nil, errors.New("missing vote") + } + if valSet == nil { + return nil, errors.New("missing validator set") } idx, val := valSet.GetByAddress(vote1.ValidatorAddress) if idx == -1 { - return nil + return nil, errors.New("validator not in validator set") } if strings.Compare(vote1.BlockID.Key(), vote2.BlockID.Key()) == -1 { @@ -69,7 +74,7 @@ func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *V TotalVotingPower: valSet.TotalVotingPower(), ValidatorPower: val.VotingPower, Timestamp: blockTime, - } + }, nil } // ABCI returns the application relevant representation of the evidence @@ -554,23 +559,32 @@ func (err *ErrEvidenceOverflow) Error() string { // unstable - use only for testing // assumes the round to be 0 and the validator index to be 0 -func NewMockDuplicateVoteEvidence(height int64, time time.Time, chainID string) *DuplicateVoteEvidence { +func NewMockDuplicateVoteEvidence(height int64, time time.Time, chainID string) (*DuplicateVoteEvidence, error) { val := NewMockPV() return NewMockDuplicateVoteEvidenceWithValidator(height, time, val, chainID) } // assumes voting power to be 10 and validator to be the only one in the set func NewMockDuplicateVoteEvidenceWithValidator(height int64, time time.Time, - pv PrivValidator, chainID string) *DuplicateVoteEvidence { - pubKey, _ := pv.GetPubKey() + pv PrivValidator, chainID string) (*DuplicateVoteEvidence, error) { + pubKey, err := pv.GetPubKey() + if err != nil { + return nil, err + } val := NewValidator(pubKey, 10) voteA := makeMockVote(height, 0, 0, pubKey.Address(), randBlockID(), time) vA := voteA.ToProto() - _ = pv.SignVote(chainID, vA) + err = pv.SignVote(chainID, vA) + if err != nil { + return nil, err + } voteA.Signature = vA.Signature voteB := makeMockVote(height, 0, 0, pubKey.Address(), randBlockID(), time) vB := voteB.ToProto() - _ = pv.SignVote(chainID, vB) + err = pv.SignVote(chainID, vB) + if err != nil { + return nil, err + } voteB.Signature = vB.Signature return NewDuplicateVoteEvidence(voteA, voteB, time, NewValidatorSet([]*Validator{val})) } diff --git a/types/evidence_test.go b/types/evidence_test.go index 946373aad..097ea85aa 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -43,7 +43,8 @@ func randomDuplicateVoteEvidence(t *testing.T) *DuplicateVoteEvidence { func TestDuplicateVoteEvidence(t *testing.T) { const height = int64(13) - ev := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id") + ev, err := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id") + require.NoError(t, err) assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes())) assert.NotNil(t, ev.String()) assert.Equal(t, ev.Height(), height) @@ -82,7 +83,8 @@ func TestDuplicateVoteEvidenceValidation(t *testing.T) { vote1 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime) vote2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime) valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(10)}) - ev := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) + ev, err := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet) + require.NoError(t, err) tc.malleateEvidence(ev) assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result") }) @@ -224,7 +226,8 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { } func TestMockEvidenceValidateBasic(t *testing.T) { - goodEvidence := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id") + goodEvidence, err := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id") + require.NoError(t, err) assert.Nil(t, goodEvidence.ValidateBasic()) } From 2b8d3956ad94e08d170483bfb9807b43bc755443 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 18 Aug 2022 10:11:41 +0200 Subject: [PATCH 7/7] lint --- evidence/pool.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/evidence/pool.go b/evidence/pool.go index 6489dc758..f2c514694 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -464,8 +464,9 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { // Check the height of the conflicting votes and fetch the corresponding time and validator set // to produce the valid evidence var ( - dve *types.DuplicateVoteEvidence - err error + dve *types.DuplicateVoteEvidence + valSet *types.ValidatorSet + err error ) switch { case voteSet.VoteA.Height == state.LastBlockHeight: @@ -477,7 +478,7 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { ) case voteSet.VoteA.Height < state.LastBlockHeight: - valSet, err := evpool.stateDB.LoadValidators(voteSet.VoteA.Height) + valSet, err = evpool.stateDB.LoadValidators(voteSet.VoteA.Height) if err != nil { evpool.logger.Error("failed to load validator set for conflicting votes", "height", voteSet.VoteA.Height, "err", err,