From 1f0cf7762bc414bf95b93afa773160f8467902e1 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 16 Aug 2022 17:07:21 +0200 Subject: [PATCH] spec: migrate v0.7.1. into v0.34 (#9262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit * Add three timeouts and align pseudocode better with existing algorithm * Align protocol with Tendermint code and add find valid value mechanism * Prepare to Nuke Develop (#47) * state -> step * vote -> v * New version of the algorithm and the proof * New version of the algorithm and the proofs * Added algorithm description * Add algorithm description * Add introduction * Add conclusion * Add conclusion file * fix warnings (caption was defined twice) - only the latter is used anyways (centers captions) - this makes it possible to autom. building the paper * Update grammar * s/state_p/step_p * Address Ismail's comments * intro: language fixes * definitions: language fixes * consensus: various fixes * proof: some fixes * try to improve reviewability * \eq -> = * textwrap to 79 * various minor fixes * proof: fix itemization * proof: more minor fixes * proof: timeouts are functions * proof: fixes to lemma6 * Intro changes and improve title page * Add Marko and Ming to acks * add readme * Format algorithm correctly Clarify condition semantic and timeouts Improve descriptions * patform -> platform * Ensure that rules are mutually exclusive - various clarifications and small improvements * Release v0.6 * small nits for smoother readability * This PR is to create signed commits to be able to merge (#50) Signed-off-by: Marko Baricevic * Add consesnus and blockchain specs, (#52) - Open questions - Do we want to split lite client work from consesnsus - From the blockchain spec, is encoding nessecary in the spec Signed-off-by: Marko Baricevic * Add ABCI SPEC (#51) - move the abci spec from tendermint to spec repo Signed-off-by: Marko Baricevic * spec/consensus/signing: add more details about nil and amnesia (#54) - Add more details about nil votes and about amnesia attacks Signed-off-by: Marko Baricevic * Add Section for P2P (#53) * Add Section for P2P - moved over the section on p2p Signed-off-by: Marko Baricevic * add some more files Signed-off-by: Marko Baricevic * Fix model section * Add non-recursive specification of Bisection algorithm - Fix timing issues by introducing Delta parameter * spec: update spec with tendermint updates (#62) * spec: update spec with tendermint updates - this in preperation of deleting the spec folder in docs in tendermint/tendermint Signed-off-by: Marko Baricevic * spec: added in reactors & p2p Signed-off-by: Marko Baricevic * spec: update readme in spec to comply with docs site Signed-off-by: Marko Baricevic * docs: addded more changes from tednermint/tendermint Signed-off-by: Marko Baricevic * reflect breaking changes made to Commit (#63) * reflect breaking changes made to Commit PR: https://github.com/tendermint/tendermint/pull/4146 Issue: https://github.com/tendermint/tendermint/issues/1648 * types: rename Commit#Precommits to Signatures * update BlockIDFlagAbsent comment * remove iota * Clean up error conditions and simplify pseudocode * Apply suggestions from code review Co-Authored-By: Anca Zamfir * Add spec doc about unconditional_peer, persistent_peers_max_dial of ADR-050 (#68) * Add spec doc about unconditional_peer_ids, persistent_peers_max_dial_period of ADR-050 * Add indefinitely dialing condition * Add sr25519 amino documentation (#67) * sr25519 amino * Update spec/blockchain/encoding.md Co-Authored-By: Marko * some suggestions for pseuodocode changes * Improved error handling * Add explanation on difference between trusted models * Address reviewer's comments * Addressing reviewer's comments * Separating algorithm from proofs * Intermediate commit (aligning spec with the code) * Removing Store from API and providing end-to-end timing guarantees * Address reviewer comment's. Intermediate commit * light client dir and readmes * titles * add redirects * add diagram * detection TODO * fix image * update readme * Aligh the correctness arguments with the pseudocode changes * lite->light * Fix link in readme ./light -> ./light-client * p2p: Merlin based malleability fixes (#72) * Update the secret connection spec with the use of merlin to eliminte handshake malleability * Update spec/p2p/peer.md Co-Authored-By: Anton Kaliaev * Update spec/p2p/peer.md Co-Authored-By: Anton Kaliaev * Update spec/p2p/peer.md Co-Authored-By: Anton Kaliaev Co-authored-by: Anton Kaliaev * docs: update specs to remove cmn (#77) - cmn was remvoed in favor of sub pkgs. cmn.kvpair is now kv.pair Signed-off-by: Marko Baricevic * evidence: Add time to evidence params (#69) * evidence: Add time to evidence params - this pr is grouped together with https://github.com/tendermint/tendermint/pull/4254, once that PR is merged then this one can be as well. Signed-off-by: Marko Baricevic * remove note Signed-off-by: Marko Baricevic * Apply suggestions from code review Co-Authored-By: Anton Kaliaev Co-authored-by: Anton Kaliaev * update link to the pex reactor * add markdown link checker * changed tab spacing * removed folder-path flag * first attempt at fixing all links * second attempt at fixing all links * codeowners: add code owners (#82) * codeowners: add code owners - added some codeowners please comment if youd like to be added as well. Signed-off-by: Marko Baricevic * remove comment of repo maintainers * remove .idea dir (#83) Signed-off-by: Marko Baricevic * RFC-001: configurable block retention (#84) * Added RFC for truncated block history coordination * Clarified minimum block retention * Added hard checks on block retention and snapshot interval, and made some minor tweaks * Genesis parameters are immutable * Use local config for snapshot interval * Reordered parameter descriptions * Clarified local config option for snapshot-interval * rewrite for ABCI commit response * Renamed RFC * add block retention diagram * Removed retain_blocks table * fix image numbers * resolved open questions * image quality * accept RFC-001 (#86) * abci: add basic description of ABCI Commit.ResponseHeight (#85) Documentation for block pruning, once it's merged: tendermint/tendermint#4588. Minimum documentation, for now - we probably shouldn't encourage using this feature too much until we release state sync. * abci: add MaxAgeNumBlocks/MaxAgeDuration to EvidenceParams (#87) * abci: update MaxAgeNumBlocks & MaxAgeDuration docs (#88) * document state sync ABCI interface and P2P protocol (#90) The corresponding Tendermint PRs are tendermint/tendermint#4704 and tendermint/tendermint#4705. * Revert "document state sync ABCI interface and P2P protocol (#90)" (#92) This reverts commit 9842b4b0fb703e609ad233af9683adc773bc95ef. * blockchain: change validator set sorting method (#91) * abci: specify sorting of RequestInitChain.Validators * blockchain: change validator sorting method Refs https://github.com/tendermint/tendermint/issues/2478 * reactors/pex: specify hash function (#94) https://github.com/tendermint/tendermint/pull/4810/files * document state sync ABCI interface and P2P protocol (#93) * Revert "Revert "document state sync ABCI interface and P2P protocol (#90)" (#92)" This reverts commit 90797cef90da762db7f7a14ce834c745140f7bcd. * update with new enum case * fix links Co-authored-by: Erik Grinaker * Update evidence params with MaxNum (#95) evidence params now includes maxNum which is the maximum number of evidence that can be committed on a single block * reactors/pex: masked IP is used as group key (#96) * spec: add ProofTrialPeriod to EvidenceParam (#99) * spec: modify Header.LastResultsHash (#97) Refs: https://github.com/tendermint/tendermint/issues/1007 PR: https://github.com/tendermint/tendermint/pull/4845 * spec: link to abci server implementations (#100) * spec: update evidence in blockchain.md (#108) now evidence reflects the actual evidence present in the tendermint repo * abci: add AppVersion to ConsensusParams (#106) * abci: tweak node sync estimate (#115) * spec/abci: expand on Validator#Address (#118) Refs https://github.com/tendermint/tendermint/issues/3732 * blockchain: rename to core (#123) * blockchain: remove duplicate evidence sections (#124) * spec/consensus: canonical vs subjective commit Refs https://github.com/tendermint/tendermint/issues/2769 * Apply suggestions from code review Co-authored-by: Igor Konnov * update spec with the removal of phantom validator evidence (#126) * bring blockchain back * add correct links * spec: revert event hashing (#132) * Evidence time is sourced from block time (#138) * RFC-002: non-zero genesis (#119) * abci: add ResponseInitChain.app_hash (#140) * update hashing of empty inputs, and initial block LastResultsHash (#141) * update evidence verification (#139) * accept RFC-002 (#142) * add description of arbitrary initial height (#135) * update ResponseInitChain.app_hash description (#143) * remove unused directories and update README (#145) This change removes unused directories (`papers` and `research`) and updates the README to reflect our strategy for merging the informalsystems/tendermint-rs specs into this repository. Partially addresses #121. * ci: add markdown linter (#146) * ci: add dependabot config (#148) * build(deps): bump gaurav-nelson/github-action-markdown-link-check from 0.6.0 to 1.0.7 (#149) Bumps [gaurav-nelson/github-action-markdown-link-check](https://github.com/gaurav-nelson/github-action-markdown-link-check) from 0.6.0 to 1.0.7. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * docs: add sections to abci (#150) * spec: update abci events (#151) * spec: extract light-client to its own directory (#152) Co-authored-by: Callum Waters * spec: remove evidences (#153) * add a stale bot (#134) * Current versions of light client specs from tendermint-rs (#158) * current versions of light client specs from tendermint-rs * markdown lint * linting * links * links * links Co-authored-by: Marko Baricevic * Fastsync spec from tendermint-rs (#157) * fastsync spec from tendermint-rs * fixed broken link * fixed linting * more fixes * markdown lint * move fast_sync to rust-spec Co-authored-by: Marko Baricevic * Update README.md (#160) * spec/reactors/mempool: batch txs per peer (#155) * spec/reactors/mempool: batch txs per peer Refs https://github.com/tendermint/tendermint/issues/625 * update * spec: Light client attack detector (#164) * start with new detection and evidence spec * more definitions at top * sketch of functions * pre post draft * evidence proof * typo * evidence theory polished * some TODOs resolved * more TODOs * links * second to last revision before PR * links * I will read once more and then make a PR * removed peer handling definitions * secondary * ready to review * detector ready for review * Update rust-spec/lightclient/detection/detection.md Co-authored-by: Zarko Milosevic * Update rust-spec/lightclient/detection/detection.md Co-authored-by: Zarko Milosevic * Update rust-spec/lightclient/detection/detection.md Co-authored-by: Zarko Milosevic * Update rust-spec/lightclient/detection/detection.md Co-authored-by: Zarko Milosevic * Update rust-spec/lightclient/detection/detection.md Co-authored-by: Zarko Milosevic * Update rust-spec/lightclient/detection/detection.md Co-authored-by: Zarko Milosevic * Update rust-spec/lightclient/detection/detection.md * skip-trace * PossibleCommit explained * Update rust-spec/lightclient/detection/detection.md Co-authored-by: Zarko Milosevic * comments by Zarko * renamed and changed link in README Co-authored-by: Zarko Milosevic * fixed an overlooked conflict (#167) * describe valset sorting according to v0.34 requirements (#169) * evidence: update data structures (#165) * fix markdown linter (#172) * TLA+ specs from MBT revision (#173) * remove setOption (#181) * spec: protobuf changes (#156) Co-authored-by: Anton Kaliaev * first check latest with secondary (#184) * Extending the blockchain specification (in the light client) to produce different ratios of faults (#183) * cleaning unused definitions * introduced the ratio of faulty processes * Update README.md (#185) * build(deps): bump gaurav-nelson/github-action-markdown-link-check from 1.0.7 to 1.0.8 (#188) Bumps [gaurav-nelson/github-action-markdown-link-check](https://github.com/gaurav-nelson/github-action-markdown-link-check) from 1.0.7 to 1.0.8. - [Release notes](https://github.com/gaurav-nelson/github-action-markdown-link-check/releases) - [Commits](https://github.com/gaurav-nelson/github-action-markdown-link-check/compare/1.0.7...e3c371c731b2f494f856dc5de7f61cea4d519907) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * spec: update light client verification to match supervisor (#171) * VDD renaming of verification spec + links fixed * latest() * backwards * added TODOs * link in old file to new name * better text * revision done. needs one more round of reading * renamed constants in 001 according to TLA+ and impl * ready for PR * forgot linting * Update rust-spec/lightclient/verification/verification_002_draft.md * Update rust-spec/lightclient/verification/verification_002_draft.md * added lightstore function needed for supervisor * added lightstore functions for supervisor * ident * Update rust-spec/lightclient/verification/verification_002_draft.md * github: issue template for proposals (#190) * Sequential Supervisor (#186) * move from tendermint-rs but needs discussion * markdown lint * TODO links replaced * links * links * links lint * Update rust-spec/lightclient/supervisor/supervisor.md * Update rust-spec/lightclient/supervisor/supervisor.md * Update rust-spec/lightclient/supervisor/supervisor.md * Update rust-spec/lightclient/supervisor/supervisor.md * moved peer handling definitions to supervisor * polishing * rename * Update rust-spec/lightclient/supervisor/supervisor_001_draft.md * Update rust-spec/lightclient/supervisor/supervisor_001_draft.md * changes to maintain StateVerified again * ready for changes in verification * start of supervisor * module name * fixed * more details * supevisor completed. Now I have to add function to verification * ready for review * tla comment * removed issues * Update rust-spec/lightclient/supervisor/supervisor_001_draft.md * intro text fixed * indentation * Update rust-spec/lightclient/supervisor/supervisor_001_draft.md * comment to entry points Co-authored-by: Marko Baricevic * RFC: adopt zip 215 (#144) Co-authored-by: Robert Zaremba * Core: move validation & data structures together (#176) Co-authored-by: Callum Waters * docs: make blockchain not viewable (#211) * evidence: update data structures to reflect added support of abci evidence (#213) * encoding: add secp, ref zip215, tables (#212) * Detector English Spec ready (#215) Add detector English spec * add Ivy proofs (#210) * add Ivy proofs * fix docker-compose command * Light client detector spec in TLA+ and refactoring of light client verification TLA+ spec (#216) Add light client detector spec in TLA+ * abci: lastcommitinfo.round extra sentence (#221) * abci: add abci_version to requestInfo (#223) * BFT requires _less than_ 1/3 faulty validators (#228) Thanks fo spotting the imprecision in the text, @shahankhatch ! * Draft of evidence handling for discussion (#225) * start with accountability deliverable * problem statement * draft function * quite complete draft. ready to discuss with Igor * Update isolate-attackers_001_draft.md * Update isolate-attackers_001_draft.md * Update isolate-attackers_001_draft.md * Update isolate-attackers_001_draft.md * Update isolate-attackers_001_draft.md * ready for TLA+ to take over * isolate * isolateamnesiatodos * Update isolate-attackers_001_draft.md * Update rust-spec/lightclient/attacks/isolate-attackers_001_draft.md Co-authored-by: Igor Konnov * Update rust-spec/lightclient/attacks/isolate-attackers_001_draft.md Co-authored-by: Igor Konnov * Update rust-spec/lightclient/attacks/isolate-attackers_001_draft.md Co-authored-by: Igor Konnov * Update rust-spec/lightclient/attacks/isolate-attackers_001_draft.md Co-authored-by: Igor Konnov * Update rust-spec/lightclient/attacks/isolate-attackers_001_draft.md Co-authored-by: Igor Konnov * Update rust-spec/lightclient/attacks/isolate-attackers_001_draft.md Co-authored-by: Igor Konnov * Update rust-spec/lightclient/attacks/isolate-attackers_001_draft.md Co-authored-by: Igor Konnov * Update rust-spec/lightclient/attacks/isolate-attackers_001_draft.md Co-authored-by: Igor Konnov * The TLA+ specification of the attackers detection (#231) * the working attackers isolation spec, needs more comments * the TLA+ spec of the attackers isolation * build(deps): bump gaurav-nelson/github-action-markdown-link-check (#233) Bumps [gaurav-nelson/github-action-markdown-link-check](https://github.com/gaurav-nelson/github-action-markdown-link-check) from 1.0.8 to 1.0.11. - [Release notes](https://github.com/gaurav-nelson/github-action-markdown-link-check/releases) - [Commits](https://github.com/gaurav-nelson/github-action-markdown-link-check/compare/1.0.8...2a60e0fe41b5361f446ccace6621a1a2a5c324cf) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Computing attack types (#232) Add light attack evidence handling * Update README.md (#234) * p2p: update frame size (#235) Reflect the change made in https://github.com/tendermint/tendermint/pull/5805 The MTU (Maximum Transmission Unit) for Ethernet is 1500 bytes. The IP header and the TCP header take up 20 bytes each at least (unless optional header fields are used) and thus the max for (non-Jumbo frame) Ethernet is 1500 - 20 -20 = 1460 Source: https://stackoverflow.com/a/3074427/820520 * build(deps): bump gaurav-nelson/github-action-markdown-link-check (#239) Bumps [gaurav-nelson/github-action-markdown-link-check](https://github.com/gaurav-nelson/github-action-markdown-link-check) from 1.0.11 to 1.0.12. - [Release notes](https://github.com/gaurav-nelson/github-action-markdown-link-check/releases) - [Commits](https://github.com/gaurav-nelson/github-action-markdown-link-check/compare/1.0.11...0fe4911067fa322422f325b002d2038ba5602170) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * layout: add section titles (#240) * reactors: remove bcv1 (#241) * abci: rewrite to proto interface (#237) * Update supervisor_001_draft.md (#243) * spec: remove reactor section (#242) Co-authored-by: Tess Rinearson * non-critical bugfix in the TLA+ spec (found by new version of apalache) (#244) * params: remove block timeiota (#248) * proto: add files (#246) Co-authored-by: Erik Grinaker * proto: modify height int64 to uint64 (#253) * abci: note on concurrency (#258) Co-authored-by: Marko * spec: merge rust-spec (#252) * Fix list of RFCs (#266) * readme: cleanup (#262) * modify readme * add rfc and proto * add rust=spec back to avoid breakage * lint readme * genesis: Explain fields in genesis file (#270) * describe the genesis * Update spec/core/genesis.md Co-authored-by: Dev Ojha * Apply suggestions from code review Co-authored-by: Callum Waters * add wording on app_state * Update spec/core/genesis.md Co-authored-by: Callum Waters Co-authored-by: Dev Ojha Co-authored-by: Callum Waters * p2p: links (#268) * fix links * fix more links * Proposer-based timestamp specification (#261) * added proposer-based timestamp spec * Update spec/consensus/proposer-based-timestamp/pbts_001_draft.md Co-authored-by: Aleksandr Bezobchuk * Update spec/consensus/proposer-based-timestamp/pbts_001_draft.md Co-authored-by: Aleksandr Bezobchuk * Update spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md Co-authored-by: Marko * Update spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md * Update spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md Co-authored-by: Callum Waters * fixes from PR Co-authored-by: Josef Widder <44643235+josef-widder@users.noreply.github.com> Co-authored-by: Aleksandr Bezobchuk Co-authored-by: Marko Co-authored-by: Callum Waters * abci: reorder sidebar (#282) * ABCI++ RFC (#254) * ABCI++ RFC This commit adds an RFC for ABCI++, which is a collection of three new phases of communication between the consensus engine and the application. Co-authored-by: Sunny Aggarwal * Fix bugs pointed out by @liamsi * Update rfc/004-abci++.md Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Fix markdown lints * Update rfc/004-abci++.md Co-authored-by: Ismail Khoffi * Update rfc/004-abci++.md Co-authored-by: Tess Rinearson * Update rfc/004-abci++.md Co-authored-by: Tess Rinearson * Add information about the rename in the context section * Bold RFC * Add example for self-authenticating vote data * More exposition of the term IPC * Update pros / negatives * Fix sentence fragment * Add desc for no-ops Co-authored-by: Sunny Aggarwal Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Ismail Khoffi Co-authored-by: Tess Rinearson * RFC: ReverseSync - fetching historical data (#224) * core: update a few sections (#284) * p2p: update state sync messages for reverse sync (#285) * Update README.md (#286) * rpc: define spec for RPC (#276) * add rpc spec and support outline * add json * add more routes remove unneeded ones * add rest of rpc endpoints * add jsonrpc calls * add more jsonrpc calls * fix blockchain * cleanup unused links and add links to repos * Update spec/rpc/README.md Co-authored-by: Callum Waters * add missing param from consensus param * Update spec/rpc/README.md Co-authored-by: Callum Waters * Update spec/rpc/README.md Co-authored-by: Callum Waters * fix cast and add doc to readme Co-authored-by: Callum Waters Co-authored-by: Marko Baricevic * A few improvements to the Ivy proof (#288) * Avoid quantifier alternation cycle The problematic quantifier alternation cycle arose because the definition of accountability_violation was unfolded. This commit also restructures the induction proof for clarity. * add count_lines.sh * fix typo and add forgotten complete=fo in comment Co-authored-by: Giuliano * Fixed a broken link (#291) * fix message type for block-sync (#298) * lint: fix lint errors (#301) * build(deps): bump actions/stale from 3 to 3.0.18 (#300) Bumps [actions/stale](https://github.com/actions/stale) from 3 to 3.0.18. - [Release notes](https://github.com/actions/stale/releases) - [Commits](https://github.com/actions/stale/compare/v3...v3.0.18) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump actions/stale from 3.0.18 to 3.0.19 (#302) Bumps [actions/stale](https://github.com/actions/stale) from 3.0.18 to 3.0.19. - [Release notes](https://github.com/actions/stale/releases) - [Commits](https://github.com/actions/stale/compare/v3.0.18...v3.0.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * rename HasVote to ReceivedVote (#289) * add a changelog to track changes (#303) * add a changelog to track changes * Update CHANGELOG.md Co-authored-by: Callum Waters Co-authored-by: Callum Waters * rpc: clarify timestamps (#304) * clarify timestamps * changelog entry * Update spec/rpc/README.md Co-authored-by: Callum Waters Co-authored-by: Callum Waters * rpc: add chunked genesis endpoint (#299) * rpc: add chunked genesis endpoint * fix lint * feedback * add info about error * fix lint Co-authored-by: marbar3778 * update ResponseCheckTx (#306) * rpc: Add totalGasUSed to block_results response (#308) * Add C++ code generation and test scenario (#310) * add parameters to byzantine send action * make net not trusted it's not necessary since for proofs Ivy will assume that the environment does not break action preconditions * use require instead of assume it seems that assume is not checked when other isolates call! * add comment * add comment * run with random seed * make domain model extractable to C++ * substitute require for assume assumes in an action are not checked when the action is called! I.e. they place no requirement on the caller; we're just assuming that the caller is going to do the right thing. This wasn't very important here but it leade to a minor inconsistency slipping through. * make the net isolate not trusted there was no need for it * add tendermint_test.ivy contains a simple test scenario that show that the specification is no vacuuous * update comment * add comments * throw if trying to parse nset value in the repl * add comment * minor refactoring * add new pex messages (#312) * build(deps): bump gaurav-nelson/github-action-markdown-link-check (#313) Bumps [gaurav-nelson/github-action-markdown-link-check](https://github.com/gaurav-nelson/github-action-markdown-link-check) from 1.0.12 to 1.0.13. - [Release notes](https://github.com/gaurav-nelson/github-action-markdown-link-check/releases) - [Commits](https://github.com/gaurav-nelson/github-action-markdown-link-check/compare/1.0.12...1.0.13) --- updated-dependencies: - dependency-name: gaurav-nelson/github-action-markdown-link-check dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * update spec to reference currently used timestamp type (#317) * build(deps): bump actions/stale from 3.0.19 to 4 (#319) Bumps [actions/stale](https://github.com/actions/stale) from 3.0.19 to 4. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v3.0.19...v4) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * address discrepancies between spec and implementation (#322) * update proto files for release (#318) * stale bot: ignore issues (#325) * evidence: add section explaining evidence (#324) * statesync: new messages for gossiping consensus params (#328) * rpc: update peer format in specification in NetInfo operation (#331) * Update supervisor_001_draft.md (#334) * core: text cleanup (#332) * abci: clarify what abci stands for (#336) * abci: clarify what abci stands for * link to abci type protos. * abci: clarify connection use in-process (#337) * abci: clarify connection use in-process * Update abci.md * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * invert abci explanations * lint++ * lint++ * lint++ * lint++ Co-authored-by: M. J. Fromberger * proto: move proto files under the correct directory related to their package name (#344) * abci.md fixup (#339) * abci: points of clarification ahead of v0.1.0 * lint++ * typo * lint++ * double word score * grammar * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * Update spec/abci/abci.md Co-authored-by: M. J. Fromberger * pr feedback * wip * update non-zero status code docs * fix event description * update CheckTx description Co-authored-by: M. J. Fromberger * Update supervisor_001_draft.md (#333) * Update supervisor_001_draft.md If the only node in the *FullNodes* set is the primary, that was just deemed faulty, we can't find honest primary. * Update supervisor_001_draft.md * light: update initialization description (#320) * apps.md fixups (#341) * wip * wip * wip * remove comments in favor of gh comments * wip * udpates to language, should must etc * Apply suggestions from code review Co-authored-by: M. J. Fromberger * remove tendermint cache description Co-authored-by: M. J. Fromberger * proto: add tendermint go changes (#349) * add missed proto files * add abci changes * rename blockchain to blocksync * Update proto/tendermint/abci/types.proto Co-authored-by: Callum Waters Co-authored-by: Callum Waters * fix mockery generation script (#9094) Signed-off-by: Marko Baricevic Co-authored-by: Milosevic, Zarko Co-authored-by: Milosevic, Zarko Co-authored-by: Zarko Milosevic Co-authored-by: Marko Co-authored-by: Zarko Milosevic Co-authored-by: Anton Kaliaev Co-authored-by: Anca Zamfir Co-authored-by: dongsamb Co-authored-by: Sunny Aggarwal Co-authored-by: Anca Zamfir Co-authored-by: Ethan Buchman Co-authored-by: Zarko Milosevic Co-authored-by: Ismail Khoffi Co-authored-by: Zaki Manian Co-authored-by: Erik Grinaker Co-authored-by: Tess Rinearson Co-authored-by: Alexander Simmerl Co-authored-by: Igor Konnov Co-authored-by: Sean Braithwaite Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Josef Widder <44643235+josef-widder@users.noreply.github.com> Co-authored-by: Andrey Kuprianov <59489470+andrey-kuprianov@users.noreply.github.com> Co-authored-by: Igor Konnov Co-authored-by: Sam Hart Co-authored-by: Robert Zaremba Co-authored-by: Giuliano Co-authored-by: Shahan Khatchadourian Co-authored-by: Dev Ojha Co-authored-by: istoilkovska Co-authored-by: Aleksandr Bezobchuk Co-authored-by: Sam Kleinman Co-authored-by: Sunny Aggarwal Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Marko Baricevic Co-authored-by: Giuliano Co-authored-by: Jordan Sexton Co-authored-by: MengXiangJian <805442788@qq.com> Co-authored-by: Yixin Luo <18810541851@163.com> Co-authored-by: crypto-facs <84574577+crypto-facs@users.noreply.github.com> Co-authored-by: Giuliano Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com> Co-authored-by: Mateusz Górski Co-authored-by: M. J. Fromberger Co-authored-by: Thane Thomson --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/proposal.md | 37 + .gitignore | 6 + .markdownlint.yml | 15 +- evidence/mocks/block_store.go | 4 +- light/rpc/mocks/light_client.go | 4 +- p2p/mocks/peer.go | 4 +- proto/README.md | 23 + proxy/mocks/app_conn_consensus.go | 4 +- proxy/mocks/app_conn_mempool.go | 4 +- proxy/mocks/app_conn_query.go | 4 +- proxy/mocks/app_conn_snapshot.go | 4 +- proxy/mocks/client_creator.go | 1 + spec/README.md | 93 + spec/abci/README.md | 27 + spec/abci/abci.md | 775 +++ spec/abci/apps.md | 671 +++ spec/abci/client-server.md | 113 + spec/blockchain/blockchain.md | 3 + spec/blockchain/encoding.md | 3 + spec/blockchain/readme.md | 14 + spec/blockchain/state.md | 3 + spec/consensus/bft-time.md | 55 + spec/consensus/consensus-paper/IEEEtran.bst | 2417 +++++++++ spec/consensus/consensus-paper/IEEEtran.cls | 4733 +++++++++++++++++ spec/consensus/consensus-paper/README.md | 24 + .../consensus-paper/algorithmicplus.sty | 195 + spec/consensus/consensus-paper/conclusion.tex | 16 + spec/consensus/consensus-paper/consensus.tex | 397 ++ .../consensus/consensus-paper/definitions.tex | 126 + spec/consensus/consensus-paper/homodel.sty | 32 + spec/consensus/consensus-paper/intro.tex | 138 + spec/consensus/consensus-paper/latex8.bst | 1124 ++++ spec/consensus/consensus-paper/latex8.sty | 168 + spec/consensus/consensus-paper/lit.bib | 1659 ++++++ spec/consensus/consensus-paper/paper.tex | 153 + spec/consensus/consensus-paper/proof.tex | 280 + spec/consensus/consensus-paper/rounddiag.sty | 62 + spec/consensus/consensus-paper/technote.sty | 118 + spec/consensus/consensus.md | 352 ++ spec/consensus/creating-proposal.md | 43 + spec/consensus/evidence.md | 199 + spec/consensus/light-client/README.md | 9 + spec/consensus/light-client/accountability.md | 3 + .../light-client/assets/light-node-image.png | Bin 0 -> 122270 bytes spec/consensus/light-client/detection.md | 3 + spec/consensus/light-client/verification.md | 3 + .../proposer-based-timestamp/README.md | 20 + .../pbts-algorithm_001_draft.md | 163 + .../pbts-sysmodel_001_draft.md | 198 + .../pbts_001_draft.md | 270 + .../tla/TendermintPBT_001_draft.tla | 597 +++ spec/consensus/proposer-selection.md | 323 ++ spec/consensus/readme.md | 32 + spec/consensus/signing.md | 229 + spec/consensus/wal.md | 32 + spec/core/data_structures.md | 456 ++ spec/core/encoding.md | 300 ++ spec/core/genesis.md | 34 + spec/core/readme.md | 13 + spec/core/state.md | 121 + spec/ivy-proofs/Dockerfile | 37 + spec/ivy-proofs/README.md | 33 + spec/ivy-proofs/abstract_tendermint.ivy | 178 + spec/ivy-proofs/accountable_safety_1.ivy | 143 + spec/ivy-proofs/accountable_safety_2.ivy | 52 + spec/ivy-proofs/check_proofs.sh | 39 + spec/ivy-proofs/classic_safety.ivy | 85 + spec/ivy-proofs/count_lines.sh | 13 + spec/ivy-proofs/docker-compose.yml | 8 + spec/ivy-proofs/domain_model.ivy | 143 + spec/ivy-proofs/network_shim.ivy | 133 + spec/ivy-proofs/output/.gitignore | 4 + spec/ivy-proofs/tendermint.ivy | 420 ++ spec/ivy-proofs/tendermint_test.ivy | 127 + spec/light-client/README.md | 205 + .../accountability/001indinv-apalache.csv | 13 + spec/light-client/accountability/MC_n4_f1.tla | 22 + spec/light-client/accountability/MC_n4_f2.tla | 22 + .../accountability/MC_n4_f2_amnesia.tla | 40 + spec/light-client/accountability/MC_n4_f3.tla | 22 + spec/light-client/accountability/MC_n5_f1.tla | 22 + spec/light-client/accountability/MC_n5_f2.tla | 22 + spec/light-client/accountability/MC_n6_f1.tla | 22 + spec/light-client/accountability/README.md | 308 ++ spec/light-client/accountability/Synopsis.md | 105 + .../TendermintAccDebug_004_draft.tla | 100 + .../TendermintAccInv_004_draft.tla | 370 ++ .../TendermintAccTrace_004_draft.tla | 33 + .../TendermintAcc_004_draft.tla | 474 ++ .../results/001indinv-apalache-mem-log.svg | 1063 ++++ .../results/001indinv-apalache-mem.svg | 1141 ++++ .../results/001indinv-apalache-ncells.svg | 1015 ++++ .../results/001indinv-apalache-nclauses.svg | 1133 ++++ .../results/001indinv-apalache-report.md | 61 + .../results/001indinv-apalache-time-log.svg | 1134 ++++ .../results/001indinv-apalache-time.svg | 957 ++++ .../results/001indinv-apalache-unstable.csv | 13 + spec/light-client/accountability/run.sh | 9 + spec/light-client/assets/light-node-image.png | Bin 0 -> 122270 bytes .../attacks/Blockchain_003_draft.tla | 166 + .../attacks/Isolation_001_draft.tla | 159 + .../attacks/LCVerificationApi_003_draft.tla | 192 + spec/light-client/attacks/MC_5_3.tla | 18 + .../attacks/isolate-attackers_001_draft.md | 221 + .../attacks/isolate-attackers_002_reviewed.md | 223 + .../attacks/notes-on-evidence-handling.md | 219 + .../detection/004bmc-apalache-ok.csv | 10 + .../detection/005bmc-apalache-error.csv | 4 + .../detection/Blockchain_003_draft.tla | 164 + .../detection/LCD_MC3_3_faulty.tla | 27 + .../detection/LCD_MC3_4_faulty.tla | 27 + .../detection/LCD_MC4_4_faulty.tla | 27 + .../detection/LCD_MC5_5_faulty.tla | 27 + .../detection/LCDetector_003_draft.tla | 373 ++ .../detection/LCVerificationApi_003_draft.tla | 192 + spec/light-client/detection/README.md | 75 + .../detection/detection_001_reviewed.md | 788 +++ .../detection/detection_003_reviewed.md | 839 +++ spec/light-client/detection/discussions.md | 178 + .../light-client/detection/draft-functions.md | 289 + .../detection/req-ibc-detection.md | 345 ++ spec/light-client/experiments.png | Bin 0 -> 83681 bytes .../supervisor/supervisor_001_draft.md | 637 +++ .../supervisor/supervisor_001_draft.tla | 71 + .../supervisor/supervisor_002_draft.md | 131 + .../verification/001bmc-apalache.csv | 49 + .../verification/002bmc-apalache-ok.csv | 55 + .../verification/003bmc-apalache-error.csv | 45 + .../verification/004bmc-apalache-ok.csv | 10 + .../verification/005bmc-apalache-error.csv | 4 + .../verification/Blockchain_002_draft.tla | 171 + .../verification/Blockchain_003_draft.tla | 164 + .../verification/Blockchain_A_1.tla | 171 + .../LCVerificationApi_003_draft.tla | 192 + .../verification/Lightclient_002_draft.tla | 465 ++ .../verification/Lightclient_003_draft.tla | 493 ++ .../verification/Lightclient_A_1.tla | 440 ++ .../verification/MC4_3_correct.tla | 26 + .../verification/MC4_3_faulty.tla | 26 + .../verification/MC4_4_correct.tla | 26 + .../verification/MC4_4_correct_drifted.tla | 26 + .../verification/MC4_4_faulty.tla | 26 + .../verification/MC4_4_faulty_drifted.tla | 26 + .../verification/MC4_5_correct.tla | 26 + .../verification/MC4_5_faulty.tla | 26 + .../verification/MC4_6_faulty.tla | 26 + .../verification/MC4_7_faulty.tla | 26 + .../verification/MC5_5_correct.tla | 26 + .../MC5_5_correct_peer_two_thirds_faulty.tla | 26 + .../verification/MC5_5_faulty.tla | 26 + .../MC5_5_faulty_peer_two_thirds_faulty.tla | 26 + .../verification/MC5_7_faulty.tla | 26 + .../verification/MC7_5_faulty.tla | 26 + .../verification/MC7_7_faulty.tla | 26 + spec/light-client/verification/README.md | 577 ++ .../verification_001_published.md | 1178 ++++ .../verification/verification_002_draft.md | 1061 ++++ .../verification/verification_003_draft.md | 76 + spec/p2p/config.md | 49 + spec/p2p/connection.md | 111 + spec/p2p/messages/README.md | 19 + spec/p2p/messages/block-sync.md | 68 + spec/p2p/messages/consensus.md | 149 + spec/p2p/messages/evidence.md | 23 + spec/p2p/messages/mempool.md | 33 + spec/p2p/messages/pex.md | 76 + spec/p2p/messages/state-sync.md | 133 + spec/p2p/node.md | 67 + spec/p2p/peer.md | 130 + spec/p2p/readme.md | 6 + spec/rpc/README.md | 1264 +++++ state/mocks/block_store.go | 4 +- state/mocks/evidence_pool.go | 4 +- state/mocks/store.go | 4 +- statesync/mocks/state_provider.go | 4 +- 176 files changed, 39742 insertions(+), 28 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/proposal.md create mode 100644 proto/README.md create mode 100644 spec/README.md create mode 100644 spec/abci/README.md create mode 100644 spec/abci/abci.md create mode 100644 spec/abci/apps.md create mode 100644 spec/abci/client-server.md create mode 100644 spec/blockchain/blockchain.md create mode 100644 spec/blockchain/encoding.md create mode 100644 spec/blockchain/readme.md create mode 100644 spec/blockchain/state.md create mode 100644 spec/consensus/bft-time.md create mode 100644 spec/consensus/consensus-paper/IEEEtran.bst create mode 100644 spec/consensus/consensus-paper/IEEEtran.cls create mode 100644 spec/consensus/consensus-paper/README.md create mode 100644 spec/consensus/consensus-paper/algorithmicplus.sty create mode 100644 spec/consensus/consensus-paper/conclusion.tex create mode 100644 spec/consensus/consensus-paper/consensus.tex create mode 100644 spec/consensus/consensus-paper/definitions.tex create mode 100644 spec/consensus/consensus-paper/homodel.sty create mode 100644 spec/consensus/consensus-paper/intro.tex create mode 100644 spec/consensus/consensus-paper/latex8.bst create mode 100644 spec/consensus/consensus-paper/latex8.sty create mode 100644 spec/consensus/consensus-paper/lit.bib create mode 100644 spec/consensus/consensus-paper/paper.tex create mode 100644 spec/consensus/consensus-paper/proof.tex create mode 100644 spec/consensus/consensus-paper/rounddiag.sty create mode 100644 spec/consensus/consensus-paper/technote.sty create mode 100644 spec/consensus/consensus.md create mode 100644 spec/consensus/creating-proposal.md create mode 100644 spec/consensus/evidence.md create mode 100644 spec/consensus/light-client/README.md create mode 100644 spec/consensus/light-client/accountability.md create mode 100644 spec/consensus/light-client/assets/light-node-image.png create mode 100644 spec/consensus/light-client/detection.md create mode 100644 spec/consensus/light-client/verification.md create mode 100644 spec/consensus/proposer-based-timestamp/README.md create mode 100644 spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md create mode 100644 spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md create mode 100644 spec/consensus/proposer-based-timestamp/pbts_001_draft.md create mode 100644 spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla create mode 100644 spec/consensus/proposer-selection.md create mode 100644 spec/consensus/readme.md create mode 100644 spec/consensus/signing.md create mode 100644 spec/consensus/wal.md create mode 100644 spec/core/data_structures.md create mode 100644 spec/core/encoding.md create mode 100644 spec/core/genesis.md create mode 100644 spec/core/readme.md create mode 100644 spec/core/state.md create mode 100644 spec/ivy-proofs/Dockerfile create mode 100644 spec/ivy-proofs/README.md create mode 100644 spec/ivy-proofs/abstract_tendermint.ivy create mode 100644 spec/ivy-proofs/accountable_safety_1.ivy create mode 100644 spec/ivy-proofs/accountable_safety_2.ivy create mode 100755 spec/ivy-proofs/check_proofs.sh create mode 100644 spec/ivy-proofs/classic_safety.ivy create mode 100755 spec/ivy-proofs/count_lines.sh create mode 100644 spec/ivy-proofs/docker-compose.yml create mode 100644 spec/ivy-proofs/domain_model.ivy create mode 100644 spec/ivy-proofs/network_shim.ivy create mode 100644 spec/ivy-proofs/output/.gitignore create mode 100644 spec/ivy-proofs/tendermint.ivy create mode 100644 spec/ivy-proofs/tendermint_test.ivy create mode 100644 spec/light-client/README.md create mode 100644 spec/light-client/accountability/001indinv-apalache.csv create mode 100644 spec/light-client/accountability/MC_n4_f1.tla create mode 100644 spec/light-client/accountability/MC_n4_f2.tla create mode 100644 spec/light-client/accountability/MC_n4_f2_amnesia.tla create mode 100644 spec/light-client/accountability/MC_n4_f3.tla create mode 100644 spec/light-client/accountability/MC_n5_f1.tla create mode 100644 spec/light-client/accountability/MC_n5_f2.tla create mode 100644 spec/light-client/accountability/MC_n6_f1.tla create mode 100644 spec/light-client/accountability/README.md create mode 100644 spec/light-client/accountability/Synopsis.md create mode 100644 spec/light-client/accountability/TendermintAccDebug_004_draft.tla create mode 100644 spec/light-client/accountability/TendermintAccInv_004_draft.tla create mode 100644 spec/light-client/accountability/TendermintAccTrace_004_draft.tla create mode 100644 spec/light-client/accountability/TendermintAcc_004_draft.tla create mode 100644 spec/light-client/accountability/results/001indinv-apalache-mem-log.svg create mode 100644 spec/light-client/accountability/results/001indinv-apalache-mem.svg create mode 100644 spec/light-client/accountability/results/001indinv-apalache-ncells.svg create mode 100644 spec/light-client/accountability/results/001indinv-apalache-nclauses.svg create mode 100644 spec/light-client/accountability/results/001indinv-apalache-report.md create mode 100644 spec/light-client/accountability/results/001indinv-apalache-time-log.svg create mode 100644 spec/light-client/accountability/results/001indinv-apalache-time.svg create mode 100644 spec/light-client/accountability/results/001indinv-apalache-unstable.csv create mode 100755 spec/light-client/accountability/run.sh create mode 100644 spec/light-client/assets/light-node-image.png create mode 100644 spec/light-client/attacks/Blockchain_003_draft.tla create mode 100644 spec/light-client/attacks/Isolation_001_draft.tla create mode 100644 spec/light-client/attacks/LCVerificationApi_003_draft.tla create mode 100644 spec/light-client/attacks/MC_5_3.tla create mode 100644 spec/light-client/attacks/isolate-attackers_001_draft.md create mode 100644 spec/light-client/attacks/isolate-attackers_002_reviewed.md create mode 100644 spec/light-client/attacks/notes-on-evidence-handling.md create mode 100644 spec/light-client/detection/004bmc-apalache-ok.csv create mode 100644 spec/light-client/detection/005bmc-apalache-error.csv create mode 100644 spec/light-client/detection/Blockchain_003_draft.tla create mode 100644 spec/light-client/detection/LCD_MC3_3_faulty.tla create mode 100644 spec/light-client/detection/LCD_MC3_4_faulty.tla create mode 100644 spec/light-client/detection/LCD_MC4_4_faulty.tla create mode 100644 spec/light-client/detection/LCD_MC5_5_faulty.tla create mode 100644 spec/light-client/detection/LCDetector_003_draft.tla create mode 100644 spec/light-client/detection/LCVerificationApi_003_draft.tla create mode 100644 spec/light-client/detection/README.md create mode 100644 spec/light-client/detection/detection_001_reviewed.md create mode 100644 spec/light-client/detection/detection_003_reviewed.md create mode 100644 spec/light-client/detection/discussions.md create mode 100644 spec/light-client/detection/draft-functions.md create mode 100644 spec/light-client/detection/req-ibc-detection.md create mode 100644 spec/light-client/experiments.png create mode 100644 spec/light-client/supervisor/supervisor_001_draft.md create mode 100644 spec/light-client/supervisor/supervisor_001_draft.tla create mode 100644 spec/light-client/supervisor/supervisor_002_draft.md create mode 100644 spec/light-client/verification/001bmc-apalache.csv create mode 100644 spec/light-client/verification/002bmc-apalache-ok.csv create mode 100644 spec/light-client/verification/003bmc-apalache-error.csv create mode 100644 spec/light-client/verification/004bmc-apalache-ok.csv create mode 100644 spec/light-client/verification/005bmc-apalache-error.csv create mode 100644 spec/light-client/verification/Blockchain_002_draft.tla create mode 100644 spec/light-client/verification/Blockchain_003_draft.tla create mode 100644 spec/light-client/verification/Blockchain_A_1.tla create mode 100644 spec/light-client/verification/LCVerificationApi_003_draft.tla create mode 100644 spec/light-client/verification/Lightclient_002_draft.tla create mode 100644 spec/light-client/verification/Lightclient_003_draft.tla create mode 100644 spec/light-client/verification/Lightclient_A_1.tla create mode 100644 spec/light-client/verification/MC4_3_correct.tla create mode 100644 spec/light-client/verification/MC4_3_faulty.tla create mode 100644 spec/light-client/verification/MC4_4_correct.tla create mode 100644 spec/light-client/verification/MC4_4_correct_drifted.tla create mode 100644 spec/light-client/verification/MC4_4_faulty.tla create mode 100644 spec/light-client/verification/MC4_4_faulty_drifted.tla create mode 100644 spec/light-client/verification/MC4_5_correct.tla create mode 100644 spec/light-client/verification/MC4_5_faulty.tla create mode 100644 spec/light-client/verification/MC4_6_faulty.tla create mode 100644 spec/light-client/verification/MC4_7_faulty.tla create mode 100644 spec/light-client/verification/MC5_5_correct.tla create mode 100644 spec/light-client/verification/MC5_5_correct_peer_two_thirds_faulty.tla create mode 100644 spec/light-client/verification/MC5_5_faulty.tla create mode 100644 spec/light-client/verification/MC5_5_faulty_peer_two_thirds_faulty.tla create mode 100644 spec/light-client/verification/MC5_7_faulty.tla create mode 100644 spec/light-client/verification/MC7_5_faulty.tla create mode 100644 spec/light-client/verification/MC7_7_faulty.tla create mode 100644 spec/light-client/verification/README.md create mode 100644 spec/light-client/verification/verification_001_published.md create mode 100644 spec/light-client/verification/verification_002_draft.md create mode 100644 spec/light-client/verification/verification_003_draft.md create mode 100644 spec/p2p/config.md create mode 100644 spec/p2p/connection.md create mode 100644 spec/p2p/messages/README.md create mode 100644 spec/p2p/messages/block-sync.md create mode 100644 spec/p2p/messages/consensus.md create mode 100644 spec/p2p/messages/evidence.md create mode 100644 spec/p2p/messages/mempool.md create mode 100644 spec/p2p/messages/pex.md create mode 100644 spec/p2p/messages/state-sync.md create mode 100644 spec/p2p/node.md create mode 100644 spec/p2p/peer.md create mode 100644 spec/p2p/readme.md create mode 100644 spec/rpc/README.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2fd57759129..7f9cdda6c20 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,3 +8,4 @@ # codeowner specified below. For this reason, the global codeowners # are often repeated in package-level definitions. * @ebuchman @tendermint/tendermint-engineering + diff --git a/.github/ISSUE_TEMPLATE/proposal.md b/.github/ISSUE_TEMPLATE/proposal.md new file mode 100644 index 00000000000..45f0bff42fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/proposal.md @@ -0,0 +1,37 @@ +--- +name: Protocol Change Proposal +about: Create a proposal to request a change to the protocol + +--- + + + +# Protocol Change Proposal + +## Summary + + + +## Problem Definition + + + +## Proposal + + + +____ + +#### For Admin Use + +- [ ] Not duplicate issue +- [ ] Appropriate labels applied +- [ ] Appropriate contributors tagged +- [ ] Contributor assigned/self-assigned diff --git a/.gitignore b/.gitignore index 1986b10c80d..ae00d966d86 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,9 @@ test/fuzz/**/corpus test/fuzz/**/crashers test/fuzz/**/suppressions test/fuzz/**/*.zip +*.aux +*.bbl +*.blg +*.pdf +*.gz +*.dvi diff --git a/.markdownlint.yml b/.markdownlint.yml index 1637001cc2f..80e3be4edbe 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,8 +1,11 @@ -default: true, -MD007: { "indent": 4 } +default: true +MD001: false +MD007: {indent: 4} MD013: false -MD024: { siblings_only: true } +MD024: {siblings_only: true} MD025: false -MD033: { no-inline-html: false } -no-hard-tabs: false -whitespace: false +MD033: false +MD036: false +MD010: false +MD012: false +MD028: false diff --git a/evidence/mocks/block_store.go b/evidence/mocks/block_store.go index e61c4e0aeb7..b0c67ff8740 100644 --- a/evidence/mocks/block_store.go +++ b/evidence/mocks/block_store.go @@ -58,13 +58,13 @@ func (_m *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta { return r0 } -type mockConstructorTestingTNewBlockStore interface { +type NewBlockStoreT interface { mock.TestingT Cleanup(func()) } // NewBlockStore creates a new instance of BlockStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewBlockStore(t mockConstructorTestingTNewBlockStore) *BlockStore { +func NewBlockStore(t NewBlockStoreT) *BlockStore { mock := &BlockStore{} mock.Mock.Test(t) diff --git a/light/rpc/mocks/light_client.go b/light/rpc/mocks/light_client.go index fabf73b01e8..25e101c86cc 100644 --- a/light/rpc/mocks/light_client.go +++ b/light/rpc/mocks/light_client.go @@ -100,13 +100,13 @@ func (_m *LightClient) VerifyLightBlockAtHeight(ctx context.Context, height int6 return r0, r1 } -type mockConstructorTestingTNewLightClient interface { +type NewLightClientT interface { mock.TestingT Cleanup(func()) } // NewLightClient creates a new instance of LightClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewLightClient(t mockConstructorTestingTNewLightClient) *LightClient { +func NewLightClient(t NewLightClientT) *LightClient { mock := &LightClient{} mock.Mock.Test(t) diff --git a/p2p/mocks/peer.go b/p2p/mocks/peer.go index e195c78bb1f..f739a0b2126 100644 --- a/p2p/mocks/peer.go +++ b/p2p/mocks/peer.go @@ -330,13 +330,13 @@ func (_m *Peer) TrySend(_a0 byte, _a1 []byte) bool { return r0 } -type mockConstructorTestingTNewPeer interface { +type NewPeerT interface { mock.TestingT Cleanup(func()) } // NewPeer creates a new instance of Peer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewPeer(t mockConstructorTestingTNewPeer) *Peer { +func NewPeer(t NewPeerT) *Peer { mock := &Peer{} mock.Mock.Test(t) diff --git a/proto/README.md b/proto/README.md new file mode 100644 index 00000000000..ebecd82d148 --- /dev/null +++ b/proto/README.md @@ -0,0 +1,23 @@ +# Protocol Buffers + +This sections defines the types and messages shared across implementations. The definition of the data structures are located in the [core/data_structures](../spec/core/data_structures.md) for the core data types and ABCI definitions are located in the [ABCI](../spec/abci/README.md) section. + +## Process of Updates + +The `.proto` files within this section are core to the protocol and updates must be treated as such. + +### Steps + +1. Make an issue with the proposed change. + - Within in the issue members from both the Tendermint-go and Tendermint-rs team will leave comments. If there is not consensus on the change an [RFC](../rfc/README.md) may be requested. + 1a. Submission of an RFC as a pull request should be made to facilitate further discussion. + 1b. Merge the RFC. +2. Make the necessary changes to the `.proto` file(s), [core data structures](../spec/core/data_structures.md) and/or [ABCI protocol](../spec/abci/apps.md). +3. Open issues within Tendermint-go and Tendermint-rs repos. This is used to notify the teams that a change occurred in the spec. + 1. Tag the issue with a spec version label. This will notify the team the changed has been made on master but has not entered a release. + +### Versioning + +The spec repo aims to be versioned. Once it has been versioned, updates to the protobuf files will live on master. After a certain amount of time, decided on by Tendermint-go and Tendermint-rs team leads, a release will be made on the spec repo. The spec may contain minor releases as well, depending on the implementation these changes may lead to a breaking change. If so, the implementation team should open an issue within the spec repo requiring a major release of the spec. + +If the steps above were followed each implementation should have issues tagged with a spec change label. Once all issues have been completed the team should signify their readiness for release. diff --git a/proxy/mocks/app_conn_consensus.go b/proxy/mocks/app_conn_consensus.go index d70cd7cc1eb..5acd8cac93f 100644 --- a/proxy/mocks/app_conn_consensus.go +++ b/proxy/mocks/app_conn_consensus.go @@ -141,13 +141,13 @@ func (_m *AppConnConsensus) SetResponseCallback(_a0 abcicli.Callback) { _m.Called(_a0) } -type mockConstructorTestingTNewAppConnConsensus interface { +type NewAppConnConsensusT interface { mock.TestingT Cleanup(func()) } // NewAppConnConsensus creates a new instance of AppConnConsensus. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewAppConnConsensus(t mockConstructorTestingTNewAppConnConsensus) *AppConnConsensus { +func NewAppConnConsensus(t NewAppConnConsensusT) *AppConnConsensus { mock := &AppConnConsensus{} mock.Mock.Test(t) diff --git a/proxy/mocks/app_conn_mempool.go b/proxy/mocks/app_conn_mempool.go index 05e23dd4337..87128ce85f9 100644 --- a/proxy/mocks/app_conn_mempool.go +++ b/proxy/mocks/app_conn_mempool.go @@ -102,13 +102,13 @@ func (_m *AppConnMempool) SetResponseCallback(_a0 abcicli.Callback) { _m.Called(_a0) } -type mockConstructorTestingTNewAppConnMempool interface { +type NewAppConnMempoolT interface { mock.TestingT Cleanup(func()) } // NewAppConnMempool creates a new instance of AppConnMempool. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewAppConnMempool(t mockConstructorTestingTNewAppConnMempool) *AppConnMempool { +func NewAppConnMempool(t NewAppConnMempoolT) *AppConnMempool { mock := &AppConnMempool{} mock.Mock.Test(t) diff --git a/proxy/mocks/app_conn_query.go b/proxy/mocks/app_conn_query.go index 544ab765ef7..6560ca0b5be 100644 --- a/proxy/mocks/app_conn_query.go +++ b/proxy/mocks/app_conn_query.go @@ -96,13 +96,13 @@ func (_m *AppConnQuery) QuerySync(_a0 types.RequestQuery) (*types.ResponseQuery, return r0, r1 } -type mockConstructorTestingTNewAppConnQuery interface { +type NewAppConnQueryT interface { mock.TestingT Cleanup(func()) } // NewAppConnQuery creates a new instance of AppConnQuery. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewAppConnQuery(t mockConstructorTestingTNewAppConnQuery) *AppConnQuery { +func NewAppConnQuery(t NewAppConnQueryT) *AppConnQuery { mock := &AppConnQuery{} mock.Mock.Test(t) diff --git a/proxy/mocks/app_conn_snapshot.go b/proxy/mocks/app_conn_snapshot.go index e3d5cb6cdaa..a90cd798a32 100644 --- a/proxy/mocks/app_conn_snapshot.go +++ b/proxy/mocks/app_conn_snapshot.go @@ -119,13 +119,13 @@ func (_m *AppConnSnapshot) OfferSnapshotSync(_a0 types.RequestOfferSnapshot) (*t return r0, r1 } -type mockConstructorTestingTNewAppConnSnapshot interface { +type NewAppConnSnapshotT interface { mock.TestingT Cleanup(func()) } // NewAppConnSnapshot creates a new instance of AppConnSnapshot. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewAppConnSnapshot(t mockConstructorTestingTNewAppConnSnapshot) *AppConnSnapshot { +func NewAppConnSnapshot(t NewAppConnSnapshotT) *AppConnSnapshot { mock := &AppConnSnapshot{} mock.Mock.Test(t) diff --git a/proxy/mocks/client_creator.go b/proxy/mocks/client_creator.go index eced0aeff60..fa0bf360593 100644 --- a/proxy/mocks/client_creator.go +++ b/proxy/mocks/client_creator.go @@ -35,6 +35,7 @@ func (_m *ClientCreator) NewABCIClient() (abcicli.Client, error) { return r0, r1 } + type mockConstructorTestingTNewClientCreator interface { mock.TestingT Cleanup(func()) diff --git a/spec/README.md b/spec/README.md new file mode 100644 index 00000000000..e7563175ce0 --- /dev/null +++ b/spec/README.md @@ -0,0 +1,93 @@ +--- +order: 1 +title: Overview +parent: + title: Spec + order: 7 +--- + +# Tendermint Spec + +This is a markdown specification of the Tendermint blockchain. +It defines the base data structures, how they are validated, +and how they are communicated over the network. + +If you find discrepancies between the spec and the code that +do not have an associated issue or pull request on github, +please submit them to our [bug bounty](https://tendermint.com/security)! + +## Contents + +- [Overview](#overview) + +### Data Structures + +- [Encoding and Digests](./core/encoding.md) +- [Blockchain](./core/data_structures.md) +- [State](./core/state.md) + +### Consensus Protocol + +- [Consensus Algorithm](./consensus/consensus.md) +- [Creating a proposal](./consensus/creating-proposal.md) +- [Time](./consensus/bft-time.md) +- [Light-Client](./consensus/light-client/README.md) + +### P2P and Network Protocols + +- [The Base P2P Layer](./p2p/node.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections +- [Peer Exchange (PEX)](./p2p/messages/pex.md): gossip known peer addresses so peers can find each other +- [Block Sync](./p2p/messages/block-sync.md): gossip blocks so peers can catch up quickly +- [Consensus](./p2p/messages/consensus.md): gossip votes and block parts so new blocks can be committed +- [Mempool](./p2p/messages/mempool.md): gossip transactions so they get included in blocks +- [Evidence](./p2p/messages/evidence.md): sending invalid evidence will stop the peer + +### RPC + +- [RPC SPEC](./rpc/README.md): Specification of the Tendermint remote procedure call interface. + +### Software + +- [ABCI](./abci/README.md): Details about interactions between the + application and consensus engine over ABCI +- [Write-Ahead Log](./consensus/wal.md): Details about how the consensus + engine preserves data and recovers from crash failures + +## Overview + +Tendermint provides Byzantine Fault Tolerant State Machine Replication using +hash-linked batches of transactions. Such transaction batches are called "blocks". +Hence, Tendermint defines a "blockchain". + +Each block in Tendermint has a unique index - its Height. +Height's in the blockchain are monotonic. +Each block is committed by a known set of weighted Validators. +Membership and weighting within this validator set may change over time. +Tendermint guarantees the safety and liveness of the blockchain +so long as less than 1/3 of the total weight of the Validator set +is malicious or faulty. + +A commit in Tendermint is a set of signed messages from more than 2/3 of +the total weight of the current Validator set. Validators take turns proposing +blocks and voting on them. Once enough votes are received, the block is considered +committed. These votes are included in the _next_ block as proof that the previous block +was committed - they cannot be included in the current block, as that block has already been +created. + +Once a block is committed, it can be executed against an application. +The application returns results for each of the transactions in the block. +The application can also return changes to be made to the validator set, +as well as a cryptographic digest of its latest state. + +Tendermint is designed to enable efficient verification and authentication +of the latest state of the blockchain. To achieve this, it embeds +cryptographic commitments to certain information in the block "header". +This information includes the contents of the block (eg. the transactions), +the validator set committing the block, as well as the various results returned by the application. +Note, however, that block execution only occurs _after_ a block is committed. +Thus, application results can only be included in the _next_ block. + +Also note that information like the transaction results and the validator set are never +directly included in the block - only their cryptographic digests (Merkle roots) are. +Hence, verification of a block requires a separate data structure to store this information. +We call this the `State`. Block verification also requires access to the previous block. diff --git a/spec/abci/README.md b/spec/abci/README.md new file mode 100644 index 00000000000..957742be7ee --- /dev/null +++ b/spec/abci/README.md @@ -0,0 +1,27 @@ +--- +order: 1 +parent: + title: ABCI + order: 2 +--- + +# ABCI + +ABCI stands for "**A**pplication **B**lock**c**hain **I**nterface". +ABCI is the interface between Tendermint (a state-machine replication engine) +and your application (the actual state machine). It consists of a set of +_methods_, each with a corresponding `Request` and `Response`message type. +To perform state-machine replication, Tendermint calls the ABCI methods on the +ABCI application by sending the `Request*` messages and receiving the `Response*` messages in return. + +All ABCI messages and methods are defined in [protocol buffers](https://github.com/tendermint/spec/blob/master/proto/abci/types.proto). +This allows Tendermint to run with applications written in many programming languages. + +This specification is split as follows: + +- [Methods and Types](./abci.md) - complete details on all ABCI methods and + message types +- [Applications](./apps.md) - how to manage ABCI application state and other + details about building ABCI applications +- [Client and Server](./client-server.md) - for those looking to implement their + own ABCI application servers diff --git a/spec/abci/abci.md b/spec/abci/abci.md new file mode 100644 index 00000000000..edcb4ff83fa --- /dev/null +++ b/spec/abci/abci.md @@ -0,0 +1,775 @@ +--- +order: 1 +title: Method and Types +--- + +# Methods and Types + +## Connections + +ABCI applications can run either within the _same_ process as the Tendermint +state-machine replication engine, or as a _separate_ process from the state-machine +replication engine. When run within the same process, Tendermint will call the ABCI +application methods directly as Go method calls. + +When Tendermint and the ABCI application are run as separate processes, Tendermint +opens four connections to the application for ABCI methods. The connections each +handle a subset of the ABCI method calls. These subsets are defined as follows: + +#### **Consensus** connection + +* Driven by a consensus protocol and is responsible for block execution. +* Handles the `InitChain`, `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit` method +calls. + +#### **Mempool** connection + +* For validating new transactions, before they're shared or included in a block. +* Handles the `CheckTx` calls. + +#### **Info** connection + +* For initialization and for queries from the user. +* Handles the `Info` and `Query` calls. + +#### **Snapshot** connection + +* For serving and restoring [state sync snapshots](apps.md#state-sync). +* Handles the `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk` calls. + +Additionally, there is a `Flush` method that is called on every connection, +and an `Echo` method that is just for debugging. + +More details on managing state across connections can be found in the section on +[ABCI Applications](apps.md). + +## Errors + +The `Query`, `CheckTx` and `DeliverTx` methods include a `Code` field in their `Response*`. +This field is meant to contain an application-specific response code. +A response code of `0` indicates no error. Any other response code +indicates to Tendermint that an error occurred. + +These methods also return a `Codespace` string to Tendermint. This field is +used to disambiguate `Code` values returned by different domains of the +application. The `Codespace` is a namespace for the `Code`. + +The `Echo`, `Info`, `InitChain`, `BeginBlock`, `EndBlock`, `Commit` methods +do not return errors. An error in any of these methods represents a critical +issue that Tendermint has no reasonable way to handle. If there is an error in one +of these methods, the application must crash to ensure that the error is safely +handled by an operator. + +The handling of non-zero response codes by Tendermint is described below + +### CheckTx + +The `CheckTx` ABCI method controls what transactions are considered for inclusion in a block. +When Tendermint receives a `ResponseCheckTx` with a non-zero `Code`, the associated +transaction will be not be added to Tendermint's mempool or it will be removed if +it is already included. + +### DeliverTx + +The `DeliverTx` ABCI method delivers transactions from Tendermint to the application. +When Tendermint recieves a `ResponseDeliverTx` with a non-zero `Code`, the response code is logged. +The transaction was already included in a block, so the `Code` does not influence +Tendermint consensus. + +### Query + +The `Query` ABCI method query queries the application for information about application state. +When Tendermint receives a `ResponseQuery` with a non-zero `Code`, this code is +returned directly to the client that initiated the query. + +## Events + +The `CheckTx`, `BeginBlock`, `DeliverTx`, `EndBlock` methods include an `Events` +field in their `Response*`. Applications may respond to these ABCI methods with a set of events. +Events allow applications to associate metadata about ABCI method execution with the +transactions and blocks this metadata relates to. +Events returned via these ABCI methods do not impact Tendermint consensus in any way +and instead exist to power subscriptions and queries of Tendermint state. + +An `Event` contains a `type` and a list of `EventAttributes`, which are key-value +string pairs denoting metadata about what happened during the method's execution. +`Event` values can be used to index transactions and blocks according to what happened +during their execution. Note that the set of events returned for a block from +`BeginBlock` and `EndBlock` are merged. In case both methods return the same +key, only the value defined in `EndBlock` is used. + +Each event has a `type` which is meant to categorize the event for a particular +`Response*` or `Tx`. A `Response*` or `Tx` may contain multiple events with duplicate +`type` values, where each distinct entry is meant to categorize attributes for a +particular event. Every key and value in an event's attributes must be UTF-8 +encoded strings along with the event type itself. + +```protobuf +message Event { + string type = 1; + repeated EventAttribute attributes = 2; +} +``` + +The attributes of an `Event` consist of a `key`, a `value`, and an `index` flag. The +index flag notifies the Tendermint indexer to index the attribute. The value of +the `index` flag is non-deterministic and may vary across different nodes in the network. + +```protobuf +message EventAttribute { + bytes key = 1; + bytes value = 2; + bool index = 3; // nondeterministic +} +``` + +Example: + +```go + abci.ResponseDeliverTx{ + // ... + Events: []abci.Event{ + { + Type: "validator.provisions", + Attributes: []abci.EventAttribute{ + abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true}, + abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true}, + abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: true}, + }, + }, + { + Type: "validator.provisions", + Attributes: []abci.EventAttribute{ + abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true}, + abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: false}, + abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: false}, + }, + }, + { + Type: "validator.slashed", + Attributes: []abci.EventAttribute{ + abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: false}, + abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true}, + abci.EventAttribute{Key: []byte("reason"), Value: []byte("..."), Index: true}, + }, + }, + // ... + }, +} +``` + +## EvidenceType + +Tendermint's security model relies on the use of "evidence". Evidence is proof of +malicious behaviour by a network participant. It is the responsibility of Tendermint +to detect such malicious behaviour. When malicious behavior is detected, Tendermint +will gossip evidence of the behavior to other nodes and commit the evidence to +the chain once it is verified by all validators. This evidence will then be +passed it on to the application through the ABCI. It is the responsibility of the +application to handle the evidence and exercise punishment. + +EvidenceType has the following protobuf format: + +```proto +enum EvidenceType { + UNKNOWN = 0; + DUPLICATE_VOTE = 1; + LIGHT_CLIENT_ATTACK = 2; +} +``` + +There are two forms of evidence: Duplicate Vote and Light Client Attack. More +information can be found in either [data structures](https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md) +or [accountability](https://github.com/tendermint/spec/blob/master/spec/light-client/accountability/) + +## Determinism + +ABCI applications must implement deterministic finite-state machines to be +securely replicated by the Tendermint consensus engine. This means block execution +over the Consensus Connection must be strictly deterministic: given the same +ordered set of requests, all nodes will compute identical responses, for all +BeginBlock, DeliverTx, EndBlock, and Commit. This is critical, because the +responses are included in the header of the next block, either via a Merkle root +or directly, so all nodes must agree on exactly what they are. + +For this reason, it is recommended that applications not be exposed to any +external user or process except via the ABCI connections to a consensus engine +like Tendermint Core. The application must only change its state based on input +from block execution (BeginBlock, DeliverTx, EndBlock, Commit), and not through +any other kind of request. This is the only way to ensure all nodes see the same +transactions and compute the same results. + +If there is some non-determinism in the state machine, consensus will eventually +fail as nodes disagree over the correct values for the block header. The +non-determinism must be fixed and the nodes restarted. + +Sources of non-determinism in applications may include: + +* Hardware failures + * Cosmic rays, overheating, etc. +* Node-dependent state + * Random numbers + * Time +* Underspecification + * Library version changes + * Race conditions + * Floating point numbers + * JSON serialization + * Iterating through hash-tables/maps/dictionaries +* External Sources + * Filesystem + * Network calls (eg. some external REST API service) + +See [#56](https://github.com/tendermint/abci/issues/56) for original discussion. + +Note that some methods (`Query, CheckTx, DeliverTx`) return +explicitly non-deterministic data in the form of `Info` and `Log` fields. The `Log` is +intended for the literal output from the application's logger, while the +`Info` is any additional info that should be returned. These are the only fields +that are not included in block header computations, so we don't need agreement +on them. All other fields in the `Response*` must be strictly deterministic. + +## Block Execution + +The first time a new blockchain is started, Tendermint calls +`InitChain`. From then on, the following sequence of methods is executed for each +block: + +`BeginBlock, [DeliverTx], EndBlock, Commit` + +where one `DeliverTx` is called for each transaction in the block. +The result is an updated application state. +Cryptographic commitments to the results of DeliverTx, EndBlock, and +Commit are included in the header of the next block. + +## State Sync + +State sync allows new nodes to rapidly bootstrap by discovering, fetching, and applying +state machine snapshots instead of replaying historical blocks. For more details, see the +[state sync section](../spec/p2p/messages/state-sync.md). + +New nodes will discover and request snapshots from other nodes in the P2P network. +A Tendermint node that receives a request for snapshots from a peer will call +`ListSnapshots` on its application to retrieve any local state snapshots. After receiving + snapshots from peers, the new node will offer each snapshot received from a peer +to its local application via the `OfferSnapshot` method. + +Snapshots may be quite large and are thus broken into smaller "chunks" that can be +assembled into the whole snapshot. Once the application accepts a snapshot and +begins restoring it, Tendermint will fetch snapshot "chunks" from existing nodes. +The node providing "chunks" will fetch them from its local application using +the `LoadSnapshotChunk` method. + +As the new node receives "chunks" it will apply them sequentially to the local +application with `ApplySnapshotChunk`. When all chunks have been applied, the application +`AppHash` is retrieved via an `Info` query. The `AppHash` is then compared to +the blockchain's `AppHash` which is verified via [light client verification](../spec/light-client/verification/README.md). + +## Messages + +### Echo + +* **Request**: + * `Message (string)`: A string to echo back +* **Response**: + * `Message (string)`: The input string +* **Usage**: + * Echo a string to test an abci client/server implementation + +### Flush + +* **Usage**: + * Signals that messages queued on the client should be flushed to + the server. It is called periodically by the client + implementation to ensure asynchronous requests are actually + sent, and is called immediately to make a synchronous request, + which returns when the Flush response comes back. + +### Info + +* **Request**: + + | Name | Type | Description | Field Number | + |---------------|--------|------------------------------------------|--------------| + | version | string | The Tendermint software semantic version | 1 | + | block_version | uint64 | The Tendermint Block Protocol version | 2 | + | p2p_version | uint64 | The Tendermint P2P Protocol version | 3 | + | abci_version | string | The Tendermint ABCI semantic version | 4 | + +* **Response**: + + | Name | Type | Description | Field Number | + |---------------------|--------|--------------------------------------------------|--------------| + | data | string | Some arbitrary information | 1 | + | version | string | The application software semantic version | 2 | + | app_version | uint64 | The application protocol version | 3 | + | last_block_height | int64 | Latest block for which the app has called Commit | 4 | + | last_block_app_hash | bytes | Latest result of Commit | 5 | + +* **Usage**: + * Return information about the application state. + * Used to sync Tendermint with the application during a handshake + that happens on startup. + * The returned `app_version` will be included in the Header of every block. + * Tendermint expects `last_block_app_hash` and `last_block_height` to + be updated during `Commit`, ensuring that `Commit` is never + called twice for the same block height. + +> Note: Semantic version is a reference to [semantic versioning](https://semver.org/). Semantic versions in info will be displayed as X.X.x. + +### InitChain + +* **Request**: + + | Name | Type | Description | Field Number | + |------------------|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|--------------| + | time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Genesis time | 1 | + | chain_id | string | ID of the blockchain. | 2 | + | consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters. | 3 | + | validators | repeated [ValidatorUpdate](#validatorupdate) | Initial genesis validators, sorted by voting power. | 4 | + | app_state_bytes | bytes | Serialized initial application state. JSON bytes. | 5 | + | initial_height | int64 | Height of the initial block (typically `1`). | 6 | + +* **Response**: + + | Name | Type | Description | Field Number | + |------------------|----------------------------------------------|-------------------------------------------------|--------------| + | consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters (optional | 1 | + | validators | repeated [ValidatorUpdate](#validatorupdate) | Initial validator set (optional). | 2 | + | app_hash | bytes | Initial application hash. | 3 | + +* **Usage**: + * Called once upon genesis. + * If ResponseInitChain.Validators is empty, the initial validator set will be the RequestInitChain.Validators + * If ResponseInitChain.Validators is not empty, it will be the initial + validator set (regardless of what is in RequestInitChain.Validators). + * This allows the app to decide if it wants to accept the initial validator + set proposed by tendermint (ie. in the genesis file), or if it wants to use + a different one (perhaps computed based on some application specific + information in the genesis file). + +### Query + +* **Request**: + + | Name | Type | Description | Field Number | + |--------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | data | bytes | Raw query bytes. Can be used with or in lieu of Path. | 1 | + | path | string | Path field of the request URI. Can be used with or in lieu of `data`. Apps MUST interpret `/store` as a query by key on the underlying store. The key SHOULD be specified in the `data` field. Apps SHOULD allow queries over specific types like `/accounts/...` or `/votes/...` | 2 | + | height | int64 | The block height for which you want the query (default=0 returns data for the latest committed block). Note that this is the height of the block containing the application's Merkle root hash, which represents the state as it was after committing the block at Height-1 | 3 | + | prove | bool | Return Merkle proof with response if possible | 4 | + +* **Response**: + + | Name | Type | Description | Field Number | + |-----------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | code | uint32 | Response code. | 1 | + | log | string | The output of the application's logger. **May be non-deterministic.** | 3 | + | info | string | Additional information. **May be non-deterministic.** | 4 | + | index | int64 | The index of the key in the tree. | 5 | + | key | bytes | The key of the matching data. | 6 | + | value | bytes | The value of the matching data. | 7 | + | proof_ops | [ProofOps](#proofops) | Serialized proof for the value data, if requested, to be verified against the `app_hash` for the given Height. | 8 | + | height | int64 | The block height from which data was derived. Note that this is the height of the block containing the application's Merkle root hash, which represents the state as it was after committing the block at Height-1 | 9 | + | codespace | string | Namespace for the `code`. | 10 | + +* **Usage**: + * Query for data from the application at current or past height. + * Optionally return Merkle proof. + * Merkle proof includes self-describing `type` field to support many types + of Merkle trees and encoding formats. + +### BeginBlock + +* **Request**: + + | Name | Type | Description | Field Number | + |----------------------|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------|--------------| + | hash | bytes | The block's hash. This can be derived from the block header. | 1 | + | header | [Header](../core/data_structures.md#header) | The block header. | 2 | + | last_commit_info | [LastCommitInfo](#lastcommitinfo) | Info about the last commit, including the round, and the list of validators and which ones signed the last block. | 3 | + | byzantine_validators | repeated [Evidence](#evidence) | List of evidence of validators that acted maliciously. | 4 | + +* **Response**: + + | Name | Type | Description | Field Number | + |--------|---------------------------|-------------------------------------|--------------| + | events | repeated [Event](#events) | type & Key-Value events for indexing | 1 | + +* **Usage**: + * Signals the beginning of a new block. + * Called prior to any `DeliverTx` method calls. + * The header contains the height, timestamp, and more - it exactly matches the + Tendermint block header. We may seek to generalize this in the future. + * The `LastCommitInfo` and `ByzantineValidators` can be used to determine + rewards and punishments for the validators. + +### CheckTx + +* **Request**: + + | Name | Type | Description | Field Number | + |------|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | tx | bytes | The request transaction bytes | 1 | + | type | CheckTxType | One of `CheckTx_New` or `CheckTx_Recheck`. `CheckTx_New` is the default and means that a full check of the tranasaction is required. `CheckTx_Recheck` types are used when the mempool is initiating a normal recheck of a transaction. | 2 | + +* **Response**: + + | Name | Type | Description | Field Number | + |------------|---------------------------|-----------------------------------------------------------------------|--------------| + | code | uint32 | Response code. | 1 | + | data | bytes | Result bytes, if any. | 2 | + | log | string | The output of the application's logger. **May be non-deterministic.** | 3 | + | info | string | Additional information. **May be non-deterministic.** | 4 | + | gas_wanted | int64 | Amount of gas requested for transaction. | 5 | + | gas_used | int64 | Amount of gas consumed by transaction. | 6 | + | events | repeated [Event](#events) | Type & Key-Value events for indexing transactions (eg. by account). | 7 | + | codespace | string | Namespace for the `code`. | 8 | + | sender | string | The transaction's sender (e.g. the signer) | 9 | + | priority | int64 | The transaction's priority (for mempool ordering) | 10 | + +* **Usage**: + + * Technically optional - not involved in processing blocks. + * Guardian of the mempool: every node runs `CheckTx` before letting a + transaction into its local mempool. + * The transaction may come from an external user or another node + * `CheckTx` validates the transaction against the current state of the application, + for example, checking signatures and account balances, but does not apply any + of the state changes described in the transaction. + not running code in a virtual machine. + * Transactions where `ResponseCheckTx.Code != 0` will be rejected - they will not be broadcast to + other nodes or included in a proposal block. + * Tendermint attributes no other value to the response code + +### DeliverTx + +* **Request**: + + | Name | Type | Description | Field Number | + |------|-------|--------------------------------|--------------| + | tx | bytes | The request transaction bytes. | 1 | + +* **Response**: + + | Name | Type | Description | Field Number | + |------------|---------------------------|-----------------------------------------------------------------------|--------------| + | code | uint32 | Response code. | 1 | + | data | bytes | Result bytes, if any. | 2 | + | log | string | The output of the application's logger. **May be non-deterministic.** | 3 | + | info | string | Additional information. **May be non-deterministic.** | 4 | + | gas_wanted | int64 | Amount of gas requested for transaction. | 5 | + | gas_used | int64 | Amount of gas consumed by transaction. | 6 | + | events | repeated [Event](#events) | Type & Key-Value events for indexing transactions (eg. by account). | 7 | + | codespace | string | Namespace for the `code`. | 8 | + +* **Usage**: + * [**Required**] The core method of the application. + * When `DeliverTx` is called, the application must execute the transaction in full before returning control to Tendermint. + * `ResponseDeliverTx.Code == 0` only if the transaction is fully valid. + +### EndBlock + +* **Request**: + + | Name | Type | Description | Field Number | + |--------|-------|------------------------------------|--------------| + | height | int64 | Height of the block just executed. | 1 | + +* **Response**: + + | Name | Type | Description | Field Number | + |-------------------------|----------------------------------------------|-----------------------------------------------------------------|--------------| + | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 1 | + | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical time, size, and other parameters. | 2 | + | events | repeated [Event](#events) | Type & Key-Value events for indexing | 3 | + +* **Usage**: + * Signals the end of a block. + * Called after all the transactions for the current block have been delivered, prior to the block's `Commit` message. + * Optional `validator_updates` triggered by block `H`. These updates affect validation + for blocks `H+1`, `H+2`, and `H+3`. + * Heights following a validator update are affected in the following way: + * `H+1`: `NextValidatorsHash` includes the new `validator_updates` value. + * `H+2`: The validator set change takes effect and `ValidatorsHash` is updated. + * `H+3`: `LastCommitInfo` is changed to include the altered validator set. + * `consensus_param_updates` returned for block `H` apply to the consensus + params for block `H+1`. For more information on the consensus parameters, + see the [application spec entry on consensus parameters](../spec/abci/apps.md#consensus-parameters). + +### Commit + +* **Request**: + + | Name | Type | Description | Field Number | + |--------|-------|------------------------------------|--------------| + + Commit signals the application to persist application state. It takes no parameters. +* **Response**: + + | Name | Type | Description | Field Number | + |---------------|-------|------------------------------------------------------------------------|--------------| + | data | bytes | The Merkle root hash of the application state. | 2 | + | retain_height | int64 | Blocks below this height may be removed. Defaults to `0` (retain all). | 3 | + +* **Usage**: + * Signal the application to persist the application state. + * Return an (optional) Merkle root hash of the application state + * `ResponseCommit.Data` is included as the `Header.AppHash` in the next block + * it may be empty + * Later calls to `Query` can return proofs about the application state anchored + in this Merkle root hash + * Note developers can return whatever they want here (could be nothing, or a + constant string, etc.), so long as it is deterministic - it must not be a + function of anything that did not come from the + BeginBlock/DeliverTx/EndBlock methods. + * Use `RetainHeight` with caution! If all nodes in the network remove historical + blocks then this data is permanently lost, and no new nodes will be able to + join the network and bootstrap. Historical blocks may also be required for + other purposes, e.g. auditing, replay of non-persisted heights, light client + verification, and so on. + +### ListSnapshots + +* **Request**: + + | Name | Type | Description | Field Number | + |--------|-------|------------------------------------|--------------| + + Empty request asking the application for a list of snapshots. + +* **Response**: + + | Name | Type | Description | Field Number | + |-----------|--------------------------------|--------------------------------|--------------| + | snapshots | repeated [Snapshot](#snapshot) | List of local state snapshots. | 1 | + +* **Usage**: + * Used during state sync to discover available snapshots on peers. + * See `Snapshot` data type for details. + +### LoadSnapshotChunk + +* **Request**: + + | Name | Type | Description | Field Number | + |--------|--------|-----------------------------------------------------------------------|--------------| + | height | uint64 | The height of the snapshot the chunks belongs to. | 1 | + | format | uint32 | The application-specific format of the snapshot the chunk belongs to. | 2 | + | chunk | uint32 | The chunk index, starting from `0` for the initial chunk. | 3 | + +* **Response**: + + | Name | Type | Description | Field Number | + |-------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | chunk | bytes | The binary chunk contents, in an arbitray format. Chunk messages cannot be larger than 16 MB _including metadata_, so 10 MB is a good starting point. | 1 | + +* **Usage**: + * Used during state sync to retrieve snapshot chunks from peers. + +### OfferSnapshot + +* **Request**: + + | Name | Type | Description | Field Number | + |----------|-----------------------|--------------------------------------------------------------------------|--------------| + | snapshot | [Snapshot](#snapshot) | The snapshot offered for restoration. | 1 | + | app_hash | bytes | The light client-verified app hash for this height, from the blockchain. | 2 | + +* **Response**: + + | Name | Type | Description | Field Number | + |--------|-------------------|-----------------------------------|--------------| + | result | [Result](#result) | The result of the snapshot offer. | 1 | + +#### Result + +```proto + enum Result { + UNKNOWN = 0; // Unknown result, abort all snapshot restoration + ACCEPT = 1; // Snapshot is accepted, start applying chunks. + ABORT = 2; // Abort snapshot restoration, and don't try any other snapshots. + REJECT = 3; // Reject this specific snapshot, try others. + REJECT_FORMAT = 4; // Reject all snapshots with this `format`, try others. + REJECT_SENDER = 5; // Reject all snapshots from all senders of this snapshot, try others. + } +``` + +* **Usage**: + * `OfferSnapshot` is called when bootstrapping a node using state sync. The application may + accept or reject snapshots as appropriate. Upon accepting, Tendermint will retrieve and + apply snapshot chunks via `ApplySnapshotChunk`. The application may also choose to reject a + snapshot in the chunk response, in which case it should be prepared to accept further + `OfferSnapshot` calls. + * Only `AppHash` can be trusted, as it has been verified by the light client. Any other data + can be spoofed by adversaries, so applications should employ additional verification schemes + to avoid denial-of-service attacks. The verified `AppHash` is automatically checked against + the restored application at the end of snapshot restoration. + * For more information, see the `Snapshot` data type or the [state sync section](../spec/p2p/messages/state-sync.md). + +### ApplySnapshotChunk + +* **Request**: + + | Name | Type | Description | Field Number | + |--------|--------|-----------------------------------------------------------------------------|--------------| + | index | uint32 | The chunk index, starting from `0`. Tendermint applies chunks sequentially. | 1 | + | chunk | bytes | The binary chunk contents, as returned by `LoadSnapshotChunk`. | 2 | + | sender | string | The P2P ID of the node who sent this chunk. | 3 | + +* **Response**: + + | Name | Type | Description | Field Number | + |----------------|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | result | Result (see below) | The result of applying this chunk. | 1 | + | refetch_chunks | repeated uint32 | Refetch and reapply the given chunks, regardless of `result`. Only the listed chunks will be refetched, and reapplied in sequential order. | 2 | + | reject_senders | repeated string | Reject the given P2P senders, regardless of `Result`. Any chunks already applied will not be refetched unless explicitly requested, but queued chunks from these senders will be discarded, and new chunks or other snapshots rejected. | 3 | + +```proto + enum Result { + UNKNOWN = 0; // Unknown result, abort all snapshot restoration + ACCEPT = 1; // The chunk was accepted. + ABORT = 2; // Abort snapshot restoration, and don't try any other snapshots. + RETRY = 3; // Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate. + RETRY_SNAPSHOT = 4; // Restart this snapshot from `OfferSnapshot`, reusing chunks unless instructed otherwise. + REJECT_SNAPSHOT = 5; // Reject this snapshot, try a different one. + } +``` + +* **Usage**: + * The application can choose to refetch chunks and/or ban P2P peers as appropriate. Tendermint + will not do this unless instructed by the application. + * The application may want to verify each chunk, e.g. by attaching chunk hashes in + `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 + 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`. + The application should be prepared to reset and accept it or abort as appropriate. + +## Data Types + +Most of the data structures used in ABCI are shared [common data structures](../spec/core/data_structures.md). In certain cases, ABCI uses different data structures which are documented here: + +### Validator + +* **Fields**: + + | Name | Type | Description | Field Number | + |---------|-------|---------------------------------------------------------------------|--------------| + | address | bytes | [Address](../core/data_structures.md#address) of validator | 1 | + | power | int64 | Voting power of the validator | 3 | + +* **Usage**: + * Validator identified by address + * Used in RequestBeginBlock as part of VoteInfo + * Does not include PubKey to avoid sending potentially large quantum pubkeys + over the ABCI + +### ValidatorUpdate + +* **Fields**: + + | Name | Type | Description | Field Number | + |---------|--------------------------------------------------|-------------------------------|--------------| + | pub_key | [Public Key](../core/data_structures.md#pub_key) | Public key of the validator | 1 | + | power | int64 | Voting power of the validator | 2 | + +* **Usage**: + * Validator identified by PubKey + * Used to tell Tendermint to update the validator set + +### VoteInfo + +* **Fields**: + + | Name | Type | Description | Field Number | + |-------------------|-------------------------|--------------------------------------------------------------|--------------| + | validator | [Validator](#validator) | A validator | 1 | + | signed_last_block | bool | Indicates whether or not the validator signed the last block | 2 | + +* **Usage**: + * Indicates whether a validator signed the last block, allowing for rewards + based on validator availability + +### Evidence + +* **Fields**: + + | Name | Type | Description | Field Number | + |--------------------|--------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|--------------| + | type | [EvidenceType](#evidencetype) | Type of the evidence. An enum of possible evidence's. | 1 | + | validator | [Validator](#validator) | The offending validator | 2 | + | height | int64 | Height when the offense occurred | 3 | + | time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Time of the block that was committed at the height that the offense occurred | 4 | + | total_voting_power | int64 | Total voting power of the validator set at height `Height` | 5 | + +#### EvidenceType + +* **Fields** + + EvidenceType is an enum with the listed fields: + + | Name | Field Number | + |---------------------|--------------| + | UNKNOWN | 0 | + | DUPLICATE_VOTE | 1 | + | LIGHT_CLIENT_ATTACK | 2 | + +### LastCommitInfo + +* **Fields**: + + | Name | Type | Description | Field Number | + |-------|--------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------| + | round | int32 | Commit round. Reflects the total amount of rounds it took to come to consensus for the current block. | 1 | + | votes | repeated [VoteInfo](#voteinfo) | List of validators addresses in the last validator set with their voting power and whether or not they signed a vote. | 2 | + +### ConsensusParams + +* **Fields**: + + | Name | Type | Description | Field Number | + |-----------|---------------------------------------------------------------|------------------------------------------------------------------------------|--------------| + | block | [BlockParams](../core/data_structures.md#blockparams) | Parameters limiting the size of a block and time between consecutive blocks. | 1 | + | evidence | [EvidenceParams](../core/data_structures.md#evidenceparams) | Parameters limiting the validity of evidence of byzantine behaviour. | 2 | + | validator | [ValidatorParams](../core/data_structures.md#validatorparams) | Parameters limiting the types of public keys validators can use. | 3 | + | version | [VersionsParams](../core/data_structures.md#versionparams) | The ABCI application version. | 4 | + +### ProofOps + +* **Fields**: + + | Name | Type | Description | Field Number | + |------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | ops | repeated [ProofOp](#proofop) | List of chained Merkle proofs, of possibly different types. The Merkle root of one op is the value being proven in the next op. The Merkle root of the final op should equal the ultimate root hash being verified against.. | 1 | + +### ProofOp + +* **Fields**: + + | Name | Type | Description | Field Number | + |------|--------|------------------------------------------------|--------------| + | type | string | Type of Merkle proof and how it's encoded. | 1 | + | key | bytes | Key in the Merkle tree that this proof is for. | 2 | + | data | bytes | Encoded Merkle proof for the key. | 3 | + +### Snapshot + +* **Fields**: + + | Name | Type | Description | Field Number | + |----------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | height | uint64 | The height at which the snapshot was taken (after commit). | 1 | + | format | uint32 | An application-specific snapshot format, allowing applications to version their snapshot data format and make backwards-incompatible changes. Tendermint does not interpret this. | 2 | + | chunks | uint32 | The number of chunks in the snapshot. Must be at least 1 (even if empty). | 3 | + | hash | bytes | TAn arbitrary snapshot hash. Must be equal only for identical snapshots across nodes. Tendermint does not interpret the hash, it only compares them. | 3 | + | metadata | bytes | Arbitrary application metadata, for example chunk hashes or other verification data. | 3 | + +* **Usage**: + * Used for state sync snapshots, see the [state sync section](../spec/p2p/messages/state-sync.md) for details. + * A snapshot is considered identical across nodes only if _all_ fields are equal (including + `Metadata`). Chunks may be retrieved from all nodes that have the same snapshot. + * When sent across the network, a snapshot message can be at most 4 MB. diff --git a/spec/abci/apps.md b/spec/abci/apps.md new file mode 100644 index 00000000000..d1b85a7406e --- /dev/null +++ b/spec/abci/apps.md @@ -0,0 +1,671 @@ +--- +order: 2 +title: Applications +--- + +# Applications + +Please ensure you've first read the spec for [ABCI Methods and Types](abci.md) + +Here we cover the following components of ABCI applications: + +- [Connection State](#connection-state) - the interplay between ABCI connections and application state + and the differences between `CheckTx` and `DeliverTx`. +- [Transaction Results](#transaction-results) - rules around transaction + results and validity +- [Validator Set Updates](#validator-updates) - how validator sets are + changed during `InitChain` and `EndBlock` +- [Query](#query) - standards for using the `Query` method and proofs about the + application state +- [Crash Recovery](#crash-recovery) - handshake protocol to synchronize + Tendermint and the application on startup. +- [State Sync](#state-sync) - rapid bootstrapping of new nodes by restoring state machine snapshots + +## Connection State + +Since Tendermint maintains four concurrent ABCI connections, it is typical +for an application to maintain a distinct state for each, and for the states to +be synchronized during `Commit`. + +### Concurrency + +In principle, each of the four ABCI connections operate concurrently with one +another. This means applications need to ensure access to state is +thread safe. In practice, both the +[default in-process ABCI client](https://github.com/tendermint/tendermint/blob/v0.34.4/abci/client/local_client.go#L18) +and the +[default Go ABCI +server](https://github.com/tendermint/tendermint/blob/v0.34.4/abci/server/socket_server.go#L32) +use global locks across all connections, so they are not +concurrent at all. This means if your app is written in Go, and compiled in-process with Tendermint +using the default `NewLocalClient`, or run out-of-process using the default `SocketServer`, +ABCI messages from all connections will be linearizable (received one at a +time). + +The existence of this global mutex means Go application developers can get +thread safety for application state by routing *all* reads and writes through the ABCI +system. Thus it may be *unsafe* to expose application state directly to an RPC +interface, and unless explicit measures are taken, all queries should be routed through the ABCI Query method. + +### BeginBlock + +The BeginBlock request can be used to run some code at the beginning of +every block. It also allows Tendermint to send the current block hash +and header to the application, before it sends any of the transactions. + +The app should remember the latest height and header (ie. from which it +has run a successful Commit) so that it can tell Tendermint where to +pick up from when it restarts. See information on the Handshake, below. + +### Commit + +Application state should only be persisted to disk during `Commit`. + +Before `Commit` is called, Tendermint locks and flushes the mempool so that no new messages will +be received on the mempool connection. This provides an opportunity to safely update all four connection +states to the latest committed state at once. + +When `Commit` completes, it unlocks the mempool. + +WARNING: if the ABCI app logic processing the `Commit` message sends a +`/broadcast_tx_sync` or `/broadcast_tx_commit` and waits for the response +before proceeding, it will deadlock. Executing those `broadcast_tx` calls +involves acquiring a lock that is held during the `Commit` call, so it's not +possible. If you make the call to the `broadcast_tx` endpoints concurrently, +that's no problem, it just can't be part of the sequential logic of the +`Commit` function. + +### Consensus Connection + +The Consensus Connection should maintain a `DeliverTxState` - the working state +for block execution. It should be updated by the calls to `BeginBlock`, `DeliverTx`, +and `EndBlock` during block execution and committed to disk as the "latest +committed state" during `Commit`. + +Updates made to the `DeliverTxState` by each method call must be readable by each subsequent method - +ie. the updates are linearizable. + +### Mempool Connection + +The mempool Connection should maintain a `CheckTxState` +to sequentially process pending transactions in the mempool that have +not yet been committed. It should be initialized to the latest committed state +at the end of every `Commit`. + +Before calling `Commit`, Tendermint will lock and flush the mempool connection, +ensuring that all existing CheckTx are responded to and no new ones can begin. +The `CheckTxState` may be updated concurrently with the `DeliverTxState`, as +messages may be sent concurrently on the Consensus and Mempool connections. + +After `Commit`, while still holding the mempool lock, CheckTx is run again on all transactions that remain in the +node's local mempool after filtering those included in the block. +An additional `Type` parameter is made available to the CheckTx function that +indicates whether an incoming transaction is new (`CheckTxType_New`), or a +recheck (`CheckTxType_Recheck`). + +Finally, after re-checking transactions in the mempool, Tendermint will unlock +the mempool connection. New transactions are once again able to be processed through CheckTx. + +Note that CheckTx is just a weak filter to keep invalid transactions out of the block chain. +CheckTx doesn't have to check everything that affects transaction validity; the +expensive things can be skipped. It's weak because a Byzantine node doesn't +care about CheckTx; it can propose a block full of invalid transactions if it wants. + +#### Replay Protection + +To prevent old transactions from being replayed, CheckTx must implement +replay protection. + +It is possible for old transactions to be sent to the application. So +it is important CheckTx implements some logic to handle them. + +### Query Connection + +The Info Connection should maintain a `QueryState` for answering queries from the user, +and for initialization when Tendermint first starts up (both described further +below). +It should always contain the latest committed state associated with the +latest committed block. + +`QueryState` should be set to the latest `DeliverTxState` at the end of every `Commit`, +after the full block has been processed and the state committed to disk. +Otherwise it should never be modified. + +Tendermint Core currently uses the Query connection to filter peers upon +connecting, according to IP address or node ID. For instance, +returning non-OK ABCI response to either of the following queries will +cause Tendermint to not connect to the corresponding peer: + +- `p2p/filter/addr/`, where `` is an IP address. +- `p2p/filter/id/`, where `` is the hex-encoded node ID (the hash of + the node's p2p pubkey). + +Note: these query formats are subject to change! + +### Snapshot Connection + +The Snapshot Connection is optional, and is only used to serve state sync snapshots for other nodes +and/or restore state sync snapshots to a local node being bootstrapped. + +For more information, see [the state sync section of this document](#state-sync). + +## Transaction Results + +The `Info` and `Log` fields are non-deterministic values for debugging/convenience purposes +that are otherwise ignored. + +The `Data` field must be strictly deterministic, but can be arbitrary data. + +### Gas + +Ethereum introduced the notion of `gas` as an abstract representation of the +cost of resources used by nodes when processing transactions. Every operation in the +Ethereum Virtual Machine uses some amount of gas, and gas can be accepted at a market-variable price. +Users propose a maximum amount of gas for their transaction; if the tx uses less, they get +the difference credited back. Tendermint adopts a similar abstraction, +though uses it only optionally and weakly, allowing applications to define +their own sense of the cost of execution. + +In Tendermint, the [ConsensusParams.Block.MaxGas](../proto/types/params.proto) limits the amount of `gas` that can be used in a block. +The default value is `-1`, meaning no limit, or that the concept of gas is +meaningless. + +Responses contain a `GasWanted` and `GasUsed` field. The former is the maximum +amount of gas the sender of a tx is willing to use, and the later is how much it actually +used. Applications should enforce that `GasUsed <= GasWanted` - ie. tx execution +should halt before it can use more resources than it requested. + +When `MaxGas > -1`, Tendermint enforces the following rules: + +- `GasWanted <= MaxGas` for all txs in the mempool +- `(sum of GasWanted in a block) <= MaxGas` when proposing a block + +If `MaxGas == -1`, no rules about gas are enforced. + +Note that Tendermint does not currently enforce anything about Gas in the consensus, only the mempool. +This means it does not guarantee that committed blocks satisfy these rules! +It is the application's responsibility to return non-zero response codes when gas limits are exceeded. + +The `GasUsed` field is ignored completely by Tendermint. That said, applications should enforce: + +- `GasUsed <= GasWanted` for any given transaction +- `(sum of GasUsed in a block) <= MaxGas` for every block + +In the future, we intend to add a `Priority` field to the responses that can be +used to explicitly prioritize txs in the mempool for inclusion in a block +proposal. See [#1861](https://github.com/tendermint/tendermint/issues/1861). + +### CheckTx + +If `Code != 0`, it will be rejected from the mempool and hence +not broadcasted to other peers and not included in a proposal block. + +`Data` contains the result of the CheckTx transaction execution, if any. It is +semantically meaningless to Tendermint. + +`Events` include any events for the execution, though since the transaction has not +been committed yet, they are effectively ignored by Tendermint. + +### DeliverTx + +DeliverTx is the workhorse of the blockchain. Tendermint sends the +DeliverTx requests asynchronously but in order, and relies on the +underlying socket protocol (ie. TCP) to ensure they are received by the +app in order. They have already been ordered in the global consensus by +the Tendermint protocol. + +If DeliverTx returns `Code != 0`, the transaction will be considered invalid, +though it is still included in the block. + +DeliverTx also returns a [Code, Data, and Log](../../proto/abci/types.proto#L189-L191). + +`Data` contains the result of the CheckTx transaction execution, if any. It is +semantically meaningless to Tendermint. + +Both the `Code` and `Data` are included in a structure that is hashed into the +`LastResultsHash` of the next block header. + +`Events` include any events for the execution, which Tendermint will use to index +the transaction by. This allows transactions to be queried according to what +events took place during their execution. + +## Updating the Validator Set + +The application may set the validator set during InitChain, and may update it during +EndBlock. + +Note that the maximum total power of the validator set is bounded by +`MaxTotalVotingPower = MaxInt64 / 8`. Applications are responsible for ensuring +they do not make changes to the validator set that cause it to exceed this +limit. + +Additionally, applications must ensure that a single set of updates does not contain any duplicates - +a given public key can only appear once within a given update. If an update includes +duplicates, the block execution will fail irrecoverably. + +### InitChain + +The `InitChain` method can return a list of validators. +If the list is empty, Tendermint will use the validators loaded in the genesis +file. +If the list returned by `InitChain` is not empty, Tendermint will use its contents as the validator set. +This way the application can set the initial validator set for the +blockchain. + +### EndBlock + +Updates to the Tendermint validator set can be made by returning +`ValidatorUpdate` objects in the `ResponseEndBlock`: + +```protobuf +message ValidatorUpdate { + tendermint.crypto.keys.PublicKey pub_key + int64 power +} + +message PublicKey { + oneof { + ed25519 bytes = 1; + } +``` + +The `pub_key` currently supports only one type: + +- `type = "ed25519"` + +The `power` is the new voting power for the validator, with the +following rules: + +- power must be non-negative +- if power is 0, the validator must already exist, and will be removed from the + validator set +- if power is non-0: + - if the validator does not already exist, it will be added to the validator + set with the given power + - if the validator does already exist, its power will be adjusted to the given power +- the total power of the new validator set must not exceed MaxTotalVotingPower + +Note the updates returned in block `H` will only take effect at block `H+2`. + +## Consensus Parameters + +ConsensusParams enforce certain limits in the blockchain, like the maximum size +of blocks, amount of gas used in a block, and the maximum acceptable age of +evidence. They can be set in InitChain and updated in EndBlock. + +### BlockParams.MaxBytes + +The maximum size of a complete Protobuf encoded block. +This is enforced by Tendermint consensus. + +This implies a maximum transaction size that is this MaxBytes, less the expected size of +the header, the validator set, and any included evidence in the block. + +Must have `0 < MaxBytes < 100 MB`. + +### BlockParams.MaxGas + +The maximum of the sum of `GasWanted` that will be allowed in a proposed block. +This is *not* enforced by Tendermint consensus. +It is left to the app to enforce (ie. if txs are included past the +limit, they should return non-zero codes). It is used by Tendermint to limit the +txs included in a proposed block. + +Must have `MaxGas >= -1`. +If `MaxGas == -1`, no limit is enforced. + +### EvidenceParams.MaxAgeDuration + +This is the maximum age of evidence in time units. +This is enforced by Tendermint consensus. + +If a block includes evidence older than this (AND the evidence was created more +than `MaxAgeNumBlocks` ago), the block will be rejected (validators won't vote +for it). + +Must have `MaxAgeDuration > 0`. + +### EvidenceParams.MaxAgeNumBlocks + +This is the maximum age of evidence in blocks. +This is enforced by Tendermint consensus. + +If a block includes evidence older than this (AND the evidence was created more +than `MaxAgeDuration` ago), the block will be rejected (validators won't vote +for it). + +Must have `MaxAgeNumBlocks > 0`. + +### EvidenceParams.MaxNum + +This is the maximum number of evidence that can be committed to a single block. + +The product of this and the `MaxEvidenceBytes` must not exceed the size of +a block minus it's overhead ( ~ `MaxBytes`). + +Must have `MaxNum > 0`. + +### Updates + +The application may set the ConsensusParams during InitChain, and update them during +EndBlock. If the ConsensusParams is empty, it will be ignored. Each field +that is not empty will be applied in full. For instance, if updating the +Block.MaxBytes, applications must also set the other Block fields (like +Block.MaxGas), even if they are unchanged, as they will otherwise cause the +value to be updated to 0. + +#### InitChain + +ResponseInitChain includes a ConsensusParams. +If ConsensusParams is nil, Tendermint will use the params loaded in the genesis +file. If ConsensusParams is not nil, Tendermint will use it. +This way the application can determine the initial consensus params for the +blockchain. + +#### EndBlock + +ResponseEndBlock includes a ConsensusParams. +If ConsensusParams nil, Tendermint will do nothing. +If ConsensusParam is not nil, Tendermint will use it. +This way the application can update the consensus params over time. + +Note the updates returned in block `H` will take effect right away for block +`H+1`. + +## Query + +Query is a generic method with lots of flexibility to enable diverse sets +of queries on application state. Tendermint makes use of Query to filter new peers +based on ID and IP, and exposes Query to the user over RPC. + +Note that calls to Query are not replicated across nodes, but rather query the +local node's state - hence they may return stale reads. For reads that require +consensus, use a transaction. + +The most important use of Query is to return Merkle proofs of the application state at some height +that can be used for efficient application-specific light-clients. + +Note Tendermint has technically no requirements from the Query +message for normal operation - that is, the ABCI app developer need not implement +Query functionality if they do not wish too. + +### Query Proofs + +The Tendermint block header includes a number of hashes, each providing an +anchor for some type of proof about the blockchain. The `ValidatorsHash` enables +quick verification of the validator set, the `DataHash` gives quick +verification of the transactions included in the block, etc. + +The `AppHash` is unique in that it is application specific, and allows for +application-specific Merkle proofs about the state of the application. +While some applications keep all relevant state in the transactions themselves +(like Bitcoin and its UTXOs), others maintain a separated state that is +computed deterministically *from* transactions, but is not contained directly in +the transactions themselves (like Ethereum contracts and accounts). +For such applications, the `AppHash` provides a much more efficient way to verify light-client proofs. + +ABCI applications can take advantage of more efficient light-client proofs for +their state as follows: + +- return the Merkle root of the deterministic application state in +`ResponseCommit.Data`. This Merkle root will be included as the `AppHash` in the next block. +- return efficient Merkle proofs about that application state in `ResponseQuery.Proof` + that can be verified using the `AppHash` of the corresponding block. + +For instance, this allows an application's light-client to verify proofs of +absence in the application state, something which is much less efficient to do using the block hash. + +Some applications (eg. Ethereum, Cosmos-SDK) have multiple "levels" of Merkle trees, +where the leaves of one tree are the root hashes of others. To support this, and +the general variability in Merkle proofs, the `ResponseQuery.Proof` has some minimal structure: + +```protobuf +message ProofOps { + repeated ProofOp ops +} + +message ProofOp { + string type = 1; + bytes key = 2; + bytes data = 3; +} +``` + +Each `ProofOp` contains a proof for a single key in a single Merkle tree, of the specified `type`. +This allows ABCI to support many different kinds of Merkle trees, encoding +formats, and proofs (eg. of presence and absence) just by varying the `type`. +The `data` contains the actual encoded proof, encoded according to the `type`. +When verifying the full proof, the root hash for one ProofOp is the value being +verified for the next ProofOp in the list. The root hash of the final ProofOp in +the list should match the `AppHash` being verified against. + +### Peer Filtering + +When Tendermint connects to a peer, it sends two queries to the ABCI application +using the following paths, with no additional data: + +- `/p2p/filter/addr/`, where `` denote the IP address and + the port of the connection +- `p2p/filter/id/`, where `` is the peer node ID (ie. the + pubkey.Address() for the peer's PubKey) + +If either of these queries return a non-zero ABCI code, Tendermint will refuse +to connect to the peer. + +### Paths + +Queries are directed at paths, and may optionally include additional data. + +The expectation is for there to be some number of high level paths +differentiating concerns, like `/p2p`, `/store`, and `/app`. Currently, +Tendermint only uses `/p2p`, for filtering peers. For more advanced use, see the +implementation of +[Query in the Cosmos-SDK](https://github.com/cosmos/cosmos-sdk/blob/v0.23.1/baseapp/baseapp.go#L333). + +## Crash Recovery + +On startup, Tendermint calls the `Info` method on the Info Connection to get the latest +committed state of the app. The app MUST return information consistent with the +last block it succesfully completed Commit for. + +If the app succesfully committed block H, then `last_block_height = H` and `last_block_app_hash = `. If the app +failed during the Commit of block H, then `last_block_height = H-1` and +`last_block_app_hash = `. + +We now distinguish three heights, and describe how Tendermint syncs itself with +the app. + +```md +storeBlockHeight = height of the last block Tendermint saw a commit for +stateBlockHeight = height of the last block for which Tendermint completed all + block processing and saved all ABCI results to disk +appBlockHeight = height of the last block for which ABCI app succesfully + completed Commit + +``` + +Note we always have `storeBlockHeight >= stateBlockHeight` and `storeBlockHeight >= appBlockHeight` +Note also Tendermint never calls Commit on an ABCI app twice for the same height. + +The procedure is as follows. + +First, some simple start conditions: + +If `appBlockHeight == 0`, then call InitChain. + +If `storeBlockHeight == 0`, we're done. + +Now, some sanity checks: + +If `storeBlockHeight < appBlockHeight`, error +If `storeBlockHeight < stateBlockHeight`, panic +If `storeBlockHeight > stateBlockHeight+1`, panic + +Now, the meat: + +If `storeBlockHeight == stateBlockHeight && appBlockHeight < storeBlockHeight`, +replay all blocks in full from `appBlockHeight` to `storeBlockHeight`. +This happens if we completed processing the block, but the app forgot its height. + +If `storeBlockHeight == stateBlockHeight && appBlockHeight == storeBlockHeight`, we're done. +This happens if we crashed at an opportune spot. + +If `storeBlockHeight == stateBlockHeight+1` +This happens if we started processing the block but didn't finish. + +If `appBlockHeight < stateBlockHeight` + replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`, + and replay the block at `storeBlockHeight` using the WAL. +This happens if the app forgot the last block it committed. + +If `appBlockHeight == stateBlockHeight`, + replay the last block (storeBlockHeight) in full. +This happens if we crashed before the app finished Commit + +If `appBlockHeight == storeBlockHeight` + update the state using the saved ABCI responses but dont run the block against the real app. +This happens if we crashed after the app finished Commit but before Tendermint saved the state. + +## State Sync + +A new node joining the network can simply join consensus at the genesis height and replay all +historical blocks until it is caught up. However, for large chains this can take a significant +amount of time, often on the order of days or weeks. + +State sync is an alternative mechanism for bootstrapping a new node, where it fetches a snapshot +of the state machine at a given height and restores it. Depending on the application, this can +be several orders of magnitude faster than replaying blocks. + +Note that state sync does not currently backfill historical blocks, so the node will have a +truncated block history - users are advised to consider the broader network implications of this in +terms of block availability and auditability. This functionality may be added in the future. + +For details on the specific ABCI calls and types, see the [methods and types section](abci.md). + +### Taking Snapshots + +Applications that want to support state syncing must take state snapshots at regular intervals. How +this is accomplished is entirely up to the application. A snapshot consists of some metadata and +a set of binary chunks in an arbitrary format: + +- `Height (uint64)`: The height at which the snapshot is taken. It must be taken after the given + height has been committed, and must not contain data from any later heights. + +- `Format (uint32)`: An arbitrary snapshot format identifier. This can be used to version snapshot + formats, e.g. to switch from Protobuf to MessagePack for serialization. The application can use + this when restoring to choose whether to accept or reject a snapshot. + +- `Chunks (uint32)`: The number of chunks in the snapshot. Each chunk contains arbitrary binary + data, and should be less than 16 MB; 10 MB is a good starting point. + +- `Hash ([]byte)`: An arbitrary hash of the snapshot. This is used to check whether a snapshot is + the same across nodes when downloading chunks. + +- `Metadata ([]byte)`: Arbitrary snapshot metadata, e.g. chunk hashes for verification or any other + necessary info. + +For a snapshot to be considered the same across nodes, all of these fields must be identical. When +sent across the network, snapshot metadata messages are limited to 4 MB. + +When a new node is running state sync and discovering snapshots, Tendermint will query an existing +application via the ABCI `ListSnapshots` method to discover available snapshots, and load binary +snapshot chunks via `LoadSnapshotChunk`. The application is free to choose how to implement this +and which formats to use, but must provide the following guarantees: + +- **Consistent:** A snapshot must be taken at a single isolated height, unaffected by + concurrent writes. This can be accomplished by using a data store that supports ACID + transactions with snapshot isolation. + +- **Asynchronous:** Taking a snapshot can be time-consuming, so it must not halt chain progress, + for example by running in a separate thread. + +- **Deterministic:** A snapshot taken at the same height in the same format must be identical + (at the byte level) across nodes, including all metadata. This ensures good availability of + chunks, and that they fit together across nodes. + +A very basic approach might be to use a datastore with MVCC transactions (such as RocksDB), +start a transaction immediately after block commit, and spawn a new thread which is passed the +transaction handle. This thread can then export all data items, serialize them using e.g. +Protobuf, hash the byte stream, split it into chunks, and store the chunks in the file system +along with some metadata - all while the blockchain is applying new blocks in parallel. + +A more advanced approach might include incremental verification of individual chunks against the +chain app hash, parallel or batched exports, compression, and so on. + +Old snapshots should be removed after some time - generally only the last two snapshots are needed +(to prevent the last one from being removed while a node is restoring it). + +### Bootstrapping a Node + +An empty node can be state synced by setting the configuration option `statesync.enabled = +true`. The node also needs the chain genesis file for basic chain info, and configuration for +light client verification of the restored snapshot: a set of Tendermint RPC servers, and a +trusted header hash and corresponding height from a trusted source, via the `statesync` +configuration section. + +Once started, the node will connect to the P2P network and begin discovering snapshots. These +will be offered to the local application via the `OfferSnapshot` ABCI method. Once a snapshot +is accepted Tendermint will fetch and apply the snapshot chunks. After all chunks have been +successfully applied, Tendermint verifies the app's `AppHash` against the chain using the light +client, then switches the node to normal consensus operation. + +#### Snapshot Discovery + +When the empty node join the P2P network, it asks all peers to report snapshots via the +`ListSnapshots` ABCI call (limited to 10 per node). After some time, the node picks the most +suitable snapshot (generally prioritized by height, format, and number of peers), and offers it +to the application via `OfferSnapshot`. The application can choose a number of responses, +including accepting or rejecting it, rejecting the offered format, rejecting the peer who sent +it, and so on. Tendermint will keep discovering and offering snapshots until one is accepted or +the application aborts. + +#### Snapshot Restoration + +Once a snapshot has been accepted via `OfferSnapshot`, Tendermint begins downloading chunks from +any peers that have the same snapshot (i.e. that have identical metadata fields). Chunks are +spooled in a temporary directory, and then given to the application in sequential order via +`ApplySnapshotChunk` until all chunks have been accepted. + +The method for restoring snapshot chunks is entirely up to the application. + +During restoration, the application can respond to `ApplySnapshotChunk` with instructions for how +to continue. This will typically be to accept the chunk and await the next one, but it can also +ask for chunks to be refetched (either the current one or any number of previous ones), P2P peers +to be banned, snapshots to be rejected or retried, and a number of other responses - see the ABCI +reference for details. + +If Tendermint fails to fetch a chunk after some time, it will reject the snapshot and try a +different one via `OfferSnapshot` - the application can choose whether it wants to support +restarting restoration, or simply abort with an error. + +#### Snapshot Verification + +Once all chunks have been accepted, Tendermint issues an `Info` ABCI call to retrieve the +`LastBlockAppHash`. This is compared with the trusted app hash from the chain, retrieved and +verified using the light client. Tendermint also checks that `LastBlockHeight` corresponds to the +height of the snapshot. + +This verification ensures that an application is valid before joining the network. However, the +snapshot restoration may take a long time to complete, so applications may want to employ additional +verification during the restore to detect failures early. This might e.g. include incremental +verification of each chunk against the app hash (using bundled Merkle proofs), checksums to +protect against data corruption by the disk or network, and so on. However, it is important to +note that the only trusted information available is the app hash, and all other snapshot metadata +can be spoofed by adversaries. + +Apps may also want to consider state sync denial-of-service vectors, where adversaries provide +invalid or harmful snapshots to prevent nodes from joining the network. The application can +counteract this by asking Tendermint to ban peers. As a last resort, node operators can use +P2P configuration options to whitelist a set of trusted peers that can provide valid snapshots. + +#### Transition to Consensus + +Once the snapshots have all been restored, Tendermint gathers additional information necessary for +bootstrapping the node (e.g. chain ID, consensus parameters, validator sets, and block headers) +from the genesis file and light client RPC servers. It also fetches and records the `AppVersion` +from the ABCI application. + +Once the state machine has been restored and Tendermint has gathered this additional +information, it transitions to block sync (if enabled) to fetch any remaining blocks up the chain +head, and then transitions to regular consensus operation. At this point the node operates like +any other node, apart from having a truncated block history at the height of the restored snapshot. diff --git a/spec/abci/client-server.md b/spec/abci/client-server.md new file mode 100644 index 00000000000..78c22674364 --- /dev/null +++ b/spec/abci/client-server.md @@ -0,0 +1,113 @@ +--- +order: 3 +title: Client and Server +--- + +# Client and Server + +This section is for those looking to implement their own ABCI Server, perhaps in +a new programming language. + +You are expected to have read [ABCI Methods and Types](./abci.md) and [ABCI +Applications](./apps.md). + +## Message Protocol + +The message protocol consists of pairs of requests and responses defined in the +[protobuf file](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/abci/types.proto). + +Some messages have no fields, while others may include byte-arrays, strings, integers, +or custom protobuf types. + +For more details on protobuf, see the [documentation](https://developers.google.com/protocol-buffers/docs/overview). + +For each request, a server should respond with the corresponding +response, where the order of requests is preserved in the order of +responses. + +## Server Implementations + +To use ABCI in your programming language of choice, there must be a ABCI +server in that language. Tendermint supports three implementations of the ABCI, written in Go: + +- In-process ([Golang](https://github.com/tendermint/tendermint/tree/master/abci), [Rust](https://github.com/tendermint/rust-abci)) +- ABCI-socket +- GRPC + +The latter two can be tested using the `abci-cli` by setting the `--abci` flag +appropriately (ie. to `socket` or `grpc`). + +See examples, in various stages of maintenance, in +[Go](https://github.com/tendermint/tendermint/tree/master/abci/server), +[JavaScript](https://github.com/tendermint/js-abci), +[C++](https://github.com/mdyring/cpp-tmsp), and +[Java](https://github.com/jTendermint/jabci). + +### In Process + +The simplest implementation uses function calls within Golang. +This means ABCI applications written in Golang can be compiled with Tendermint Core and run as a single binary. + +### GRPC + +If GRPC is available in your language, this is the easiest approach, +though it will have significant performance overhead. + +To get started with GRPC, copy in the [protobuf +file](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/abci/types.proto) +and compile it using the GRPC plugin for your language. For instance, +for golang, the command is `protoc --go_out=plugins=grpc:. types.proto`. +See the [grpc documentation for more details](http://www.grpc.io/docs/). +`protoc` will autogenerate all the necessary code for ABCI client and +server in your language, including whatever interface your application +must satisfy to be used by the ABCI server for handling requests. + +Note the length-prefixing used in the socket implementation (TSP) does not apply for GRPC. + +### TSP + +Tendermint Socket Protocol is an asynchronous, raw socket server which provides ordered message passing over unix or tcp. +Messages are serialized using Protobuf3 and length-prefixed with a [signed Varint](https://developers.google.com/protocol-buffers/docs/encoding?csw=1#signed-integers) + +If GRPC is not available in your language, or you require higher +performance, or otherwise enjoy programming, you may implement your own +ABCI server using the Tendermint Socket Protocol. The first step is still to auto-generate the relevant data +types and codec in your language using `protoc`. In addition to being proto3 encoded, messages coming over +the socket are length-prefixed to facilitate use as a streaming protocol. proto3 doesn't have an +official length-prefix standard, so we use our own. The first byte in +the prefix represents the length of the Big Endian encoded length. The +remaining bytes in the prefix are the Big Endian encoded length. + +For example, if the proto3 encoded ABCI message is 0xDEADBEEF (4 +bytes), the length-prefixed message is 0x0104DEADBEEF. If the proto3 +encoded ABCI message is 65535 bytes long, the length-prefixed message +would be like 0x02FFFF.... + +The benefit of using this `varint` encoding over the old version (where integers were encoded as `` is that +it is the standard way to encode integers in Protobuf. It is also generally shorter. + +As noted above, this prefixing does not apply for GRPC. + +An ABCI server must also be able to support multiple connections, as +Tendermint uses four connections. + +### Async vs Sync + +The main ABCI server (ie. non-GRPC) provides ordered asynchronous messages. +This is useful for DeliverTx and CheckTx, since it allows Tendermint to forward +transactions to the app before it's finished processing previous ones. + +Thus, DeliverTx and CheckTx messages are sent asynchronously, while all other +messages are sent synchronously. + +## Client + +There are currently two use-cases for an ABCI client. One is a testing +tool, as in the `abci-cli`, which allows ABCI requests to be sent via +command line. The other is a consensus engine, such as Tendermint Core, +which makes requests to the application every time a new transaction is +received or a block is committed. + +It is unlikely that you will need to implement a client. For details of +our client, see +[here](https://github.com/tendermint/tendermint/tree/master/abci/client). diff --git a/spec/blockchain/blockchain.md b/spec/blockchain/blockchain.md new file mode 100644 index 00000000000..fcc080ee72c --- /dev/null +++ b/spec/blockchain/blockchain.md @@ -0,0 +1,3 @@ +# Blockchain + +Deprecated see [core/data_structures.md](../core/data_structures.md) diff --git a/spec/blockchain/encoding.md b/spec/blockchain/encoding.md new file mode 100644 index 00000000000..aa2c9ab3f31 --- /dev/null +++ b/spec/blockchain/encoding.md @@ -0,0 +1,3 @@ +# Encoding + +Deprecated see [core/data_structures.md](../core/encoding.md) diff --git a/spec/blockchain/readme.md b/spec/blockchain/readme.md new file mode 100644 index 00000000000..10ad4669076 --- /dev/null +++ b/spec/blockchain/readme.md @@ -0,0 +1,14 @@ +--- +order: 1 +parent: + title: Blockchain + order: false +--- + +# Blockchain + +This section describes the core types and functionality of the Tendermint protocol implementation. + +[Core Data Structures](../core/data_structures.md) +[Encoding](../core/encoding.md) +[State](../core/state.md) diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md new file mode 100644 index 00000000000..f4f1d952516 --- /dev/null +++ b/spec/blockchain/state.md @@ -0,0 +1,3 @@ +# State + +Deprecated see [core/state.md](../core/state.md) diff --git a/spec/consensus/bft-time.md b/spec/consensus/bft-time.md new file mode 100644 index 00000000000..cec3b91ab9d --- /dev/null +++ b/spec/consensus/bft-time.md @@ -0,0 +1,55 @@ +--- +order: 2 +--- +# BFT Time + +Tendermint provides a deterministic, Byzantine fault-tolerant, source of time. +Time in Tendermint is defined with the Time field of the block header. + +It satisfies the following properties: + +- Time Monotonicity: Time is monotonically increasing, i.e., given +a header H1 for height h1 and a header H2 for height `h2 = h1 + 1`, `H1.Time < H2.Time`. +- Time Validity: Given a set of Commit votes that forms the `block.LastCommit` field, a range of +valid values for the Time field of the block header is defined only by +Precommit messages (from the LastCommit field) sent by correct processes, i.e., +a faulty process cannot arbitrarily increase the Time value. + +In the context of Tendermint, time is of type int64 and denotes UNIX time in milliseconds, i.e., +corresponds to the number of milliseconds since January 1, 1970. Before defining rules that need to be enforced by the +Tendermint consensus protocol, so the properties above holds, we introduce the following definition: + +- median of a Commit is equal to the median of `Vote.Time` fields of the `Vote` messages, +where the value of `Vote.Time` is counted number of times proportional to the process voting power. As in Tendermint +the voting power is not uniform (one process one vote), a vote message is actually an aggregator of the same votes whose +number is equal to the voting power of the process that has casted the corresponding votes message. + +Let's consider the following example: + +- we have four processes p1, p2, p3 and p4, with the following voting power distribution (p1, 23), (p2, 27), (p3, 10) +and (p4, 10). The total voting power is 70 (`N = 3f+1`, where `N` is the total voting power, and `f` is the maximum voting +power of the faulty processes), so we assume that the faulty processes have at most 23 of voting power. +Furthermore, we have the following vote messages in some LastCommit field (we ignore all fields except Time field): + - (p1, 100), (p2, 98), (p3, 1000), (p4, 500). We assume that p3 and p4 are faulty processes. Let's assume that the + `block.LastCommit` message contains votes of processes p2, p3 and p4. Median is then chosen the following way: + the value 98 is counted 27 times, the value 1000 is counted 10 times and the value 500 is counted also 10 times. + So the median value will be the value 98. No matter what set of messages with at least `2f+1` voting power we + choose, the median value will always be between the values sent by correct processes. + +We ensure Time Monotonicity and Time Validity properties by the following rules: + +- let rs denotes `RoundState` (consensus internal state) of some process. Then +`rs.ProposalBlock.Header.Time == median(rs.LastCommit) && +rs.Proposal.Timestamp == rs.ProposalBlock.Header.Time`. + +- Furthermore, when creating the `vote` message, the following rules for determining `vote.Time` field should hold: + + - if `rs.LockedBlock` is defined then + `vote.Time = max(rs.LockedBlock.Timestamp + time.Millisecond, time.Now())`, where `time.Now()` + denotes local Unix time in milliseconds + + - else if `rs.Proposal` is defined then + `vote.Time = max(rs.Proposal.Timestamp + time.Millisecond,, time.Now())`, + + - otherwise, `vote.Time = time.Now())`. In this case vote is for `nil` so it is not taken into account for + the timestamp of the next block. diff --git a/spec/consensus/consensus-paper/IEEEtran.bst b/spec/consensus/consensus-paper/IEEEtran.bst new file mode 100644 index 00000000000..53fbc030aae --- /dev/null +++ b/spec/consensus/consensus-paper/IEEEtran.bst @@ -0,0 +1,2417 @@ +%% +%% IEEEtran.bst +%% BibTeX Bibliography Style file for IEEE Journals and Conferences (unsorted) +%% Version 1.12 (2007/01/11) +%% +%% Copyright (c) 2003-2007 Michael Shell +%% +%% Original starting code base and algorithms obtained from the output of +%% Patrick W. Daly's makebst package as well as from prior versions of +%% IEEE BibTeX styles: +%% +%% 1. Howard Trickey and Oren Patashnik's ieeetr.bst (1985/1988) +%% 2. Silvano Balemi and Richard H. Roy's IEEEbib.bst (1993) +%% +%% Support sites: +%% http://www.michaelshell.org/tex/ieeetran/ +%% http://www.ctan.org/tex-archive/macros/latex/contrib/IEEEtran/ +%% and/or +%% http://www.ieee.org/ +%% +%% For use with BibTeX version 0.99a or later +%% +%% This is a numerical citation style. +%% +%%************************************************************************* +%% Legal Notice: +%% This code is offered as-is without any warranty either expressed or +%% implied; without even the implied warranty of MERCHANTABILITY or +%% FITNESS FOR A PARTICULAR PURPOSE! +%% User assumes all risk. +%% In no event shall IEEE or any contributor to this code be liable for +%% any damages or losses, including, but not limited to, incidental, +%% consequential, or any other damages, resulting from the use or misuse +%% of any information contained here. +%% +%% All comments are the opinions of their respective authors and are not +%% necessarily endorsed by the IEEE. +%% +%% This work is distributed under the LaTeX Project Public License (LPPL) +%% ( http://www.latex-project.org/ ) version 1.3, and may be freely used, +%% distributed and modified. A copy of the LPPL, version 1.3, is included +%% in the base LaTeX documentation of all distributions of LaTeX released +%% 2003/12/01 or later. +%% Retain all contribution notices and credits. +%% ** Modified files should be clearly indicated as such, including ** +%% ** renaming them and changing author support contact information. ** +%% +%% File list of work: IEEEabrv.bib, IEEEfull.bib, IEEEexample.bib, +%% IEEEtran.bst, IEEEtranS.bst, IEEEtranSA.bst, +%% IEEEtranN.bst, IEEEtranSN.bst, IEEEtran_bst_HOWTO.pdf +%%************************************************************************* +% +% +% Changelog: +% +% 1.00 (2002/08/13) Initial release +% +% 1.10 (2002/09/27) +% 1. Corrected minor bug for improperly formed warning message when a +% book was not given a title. Thanks to Ming Kin Lai for reporting this. +% 2. Added support for CTLname_format_string and CTLname_latex_cmd fields +% in the BST control entry type. +% +% 1.11 (2003/04/02) +% 1. Fixed bug with URLs containing underscores when using url.sty. Thanks +% to Ming Kin Lai for reporting this. +% +% 1.12 (2007/01/11) +% 1. Fixed bug with unwanted comma before "et al." when an entry contained +% more than two author names. Thanks to Pallav Gupta for reporting this. +% 2. Fixed bug with anomalous closing quote in tech reports that have a +% type, but without a number or address. Thanks to Mehrdad Mirreza for +% reporting this. +% 3. Use braces in \providecommand in begin.bib to better support +% latex2html. TeX style length assignments OK with recent versions +% of latex2html - 1.71 (2002/2/1) or later is strongly recommended. +% Use of the language field still causes trouble with latex2html. +% Thanks to Federico Beffa for reporting this. +% 4. Added IEEEtran.bst ID and version comment string to .bbl output. +% 5. Provide a \BIBdecl hook that allows the user to execute commands +% just prior to the first entry. +% 6. Use default urlstyle (is using url.sty) of "same" rather than rm to +% better work with a wider variety of bibliography styles. +% 7. Changed month abbreviations from Sept., July and June to Sep., Jul., +% and Jun., respectively, as IEEE now does. Thanks to Moritz Borgmann +% for reporting this. +% 8. Control entry types should not be considered when calculating longest +% label width. +% 9. Added alias www for electronic/online. +% 10. Added CTLname_url_prefix control entry type. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% DEFAULTS FOR THE CONTROLS OF THE BST STYLE %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% These are the defaults for the user adjustable controls. The values used +% here can be overridden by the user via IEEEtranBSTCTL entry type. + +% NOTE: The recommended LaTeX command to invoke a control entry type is: +% +%\makeatletter +%\def\bstctlcite{\@ifnextchar[{\@bstctlcite}{\@bstctlcite[@auxout]}} +%\def\@bstctlcite[#1]#2{\@bsphack +% \@for\@citeb:=#2\do{% +% \edef\@citeb{\expandafter\@firstofone\@citeb}% +% \if@filesw\immediate\write\csname #1\endcsname{\string\citation{\@citeb}}\fi}% +% \@esphack} +%\makeatother +% +% It is called at the start of the document, before the first \cite, like: +% \bstctlcite{IEEEexample:BSTcontrol} +% +% IEEEtran.cls V1.6 and later does provide this command. + + + +% #0 turns off the display of the number for articles. +% #1 enables +FUNCTION {default.is.use.number.for.article} { #1 } + + +% #0 turns off the display of the paper and type fields in @inproceedings. +% #1 enables +FUNCTION {default.is.use.paper} { #1 } + + +% #0 turns off the forced use of "et al." +% #1 enables +FUNCTION {default.is.forced.et.al} { #0 } + +% The maximum number of names that can be present beyond which an "et al." +% usage is forced. Be sure that num.names.shown.with.forced.et.al (below) +% is not greater than this value! +% Note: There are many instances of references in IEEE journals which have +% a very large number of authors as well as instances in which "et al." is +% used profusely. +FUNCTION {default.max.num.names.before.forced.et.al} { #10 } + +% The number of names that will be shown with a forced "et al.". +% Must be less than or equal to max.num.names.before.forced.et.al +FUNCTION {default.num.names.shown.with.forced.et.al} { #1 } + + +% #0 turns off the alternate interword spacing for entries with URLs. +% #1 enables +FUNCTION {default.is.use.alt.interword.spacing} { #1 } + +% If alternate interword spacing for entries with URLs is enabled, this is +% the interword spacing stretch factor that will be used. For example, the +% default "4" here means that the interword spacing in entries with URLs can +% stretch to four times normal. Does not have to be an integer. Note that +% the value specified here can be overridden by the user in their LaTeX +% code via a command such as: +% "\providecommand\BIBentryALTinterwordstretchfactor{1.5}" in addition to +% that via the IEEEtranBSTCTL entry type. +FUNCTION {default.ALTinterwordstretchfactor} { "4" } + + +% #0 turns off the "dashification" of repeated (i.e., identical to those +% of the previous entry) names. IEEE normally does this. +% #1 enables +FUNCTION {default.is.dash.repeated.names} { #1 } + + +% The default name format control string. +FUNCTION {default.name.format.string}{ "{f.~}{vv~}{ll}{, jj}" } + + +% The default LaTeX font command for the names. +FUNCTION {default.name.latex.cmd}{ "" } + + +% The default URL prefix. +FUNCTION {default.name.url.prefix}{ "[Online]. Available:" } + + +% Other controls that cannot be accessed via IEEEtranBSTCTL entry type. + +% #0 turns off the terminal startup banner/completed message so as to +% operate more quietly. +% #1 enables +FUNCTION {is.print.banners.to.terminal} { #1 } + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% FILE VERSION AND BANNER %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +FUNCTION{bst.file.version} { "1.12" } +FUNCTION{bst.file.date} { "2007/01/11" } +FUNCTION{bst.file.website} { "http://www.michaelshell.org/tex/ieeetran/bibtex/" } + +FUNCTION {banner.message} +{ is.print.banners.to.terminal + { "-- IEEEtran.bst version" " " * bst.file.version * + " (" * bst.file.date * ") " * "by Michael Shell." * + top$ + "-- " bst.file.website * + top$ + "-- See the " quote$ * "IEEEtran_bst_HOWTO.pdf" * quote$ * " manual for usage information." * + top$ + } + { skip$ } + if$ +} + +FUNCTION {completed.message} +{ is.print.banners.to.terminal + { "" + top$ + "Done." + top$ + } + { skip$ } + if$ +} + + + + +%%%%%%%%%%%%%%%%%%%%%% +%% STRING CONSTANTS %% +%%%%%%%%%%%%%%%%%%%%%% + +FUNCTION {bbl.and}{ "and" } +FUNCTION {bbl.etal}{ "et~al." } +FUNCTION {bbl.editors}{ "eds." } +FUNCTION {bbl.editor}{ "ed." } +FUNCTION {bbl.edition}{ "ed." } +FUNCTION {bbl.volume}{ "vol." } +FUNCTION {bbl.of}{ "of" } +FUNCTION {bbl.number}{ "no." } +FUNCTION {bbl.in}{ "in" } +FUNCTION {bbl.pages}{ "pp." } +FUNCTION {bbl.page}{ "p." } +FUNCTION {bbl.chapter}{ "ch." } +FUNCTION {bbl.paper}{ "paper" } +FUNCTION {bbl.part}{ "pt." } +FUNCTION {bbl.patent}{ "Patent" } +FUNCTION {bbl.patentUS}{ "U.S." } +FUNCTION {bbl.revision}{ "Rev." } +FUNCTION {bbl.series}{ "ser." } +FUNCTION {bbl.standard}{ "Std." } +FUNCTION {bbl.techrep}{ "Tech. Rep." } +FUNCTION {bbl.mthesis}{ "Master's thesis" } +FUNCTION {bbl.phdthesis}{ "Ph.D. dissertation" } +FUNCTION {bbl.st}{ "st" } +FUNCTION {bbl.nd}{ "nd" } +FUNCTION {bbl.rd}{ "rd" } +FUNCTION {bbl.th}{ "th" } + + +% This is the LaTeX spacer that is used when a larger than normal space +% is called for (such as just before the address:publisher). +FUNCTION {large.space} { "\hskip 1em plus 0.5em minus 0.4em\relax " } + +% The LaTeX code for dashes that are used to represent repeated names. +% Note: Some older IEEE journals used something like +% "\rule{0.275in}{0.5pt}\," which is fairly thick and runs right along +% the baseline. However, IEEE now uses a thinner, above baseline, +% six dash long sequence. +FUNCTION {repeated.name.dashes} { "------" } + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% PREDEFINED STRING MACROS %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +MACRO {jan} {"Jan."} +MACRO {feb} {"Feb."} +MACRO {mar} {"Mar."} +MACRO {apr} {"Apr."} +MACRO {may} {"May"} +MACRO {jun} {"Jun."} +MACRO {jul} {"Jul."} +MACRO {aug} {"Aug."} +MACRO {sep} {"Sep."} +MACRO {oct} {"Oct."} +MACRO {nov} {"Nov."} +MACRO {dec} {"Dec."} + + + +%%%%%%%%%%%%%%%%%% +%% ENTRY FIELDS %% +%%%%%%%%%%%%%%%%%% + +ENTRY + { address + assignee + author + booktitle + chapter + day + dayfiled + edition + editor + howpublished + institution + intype + journal + key + language + month + monthfiled + nationality + note + number + organization + pages + paper + publisher + school + series + revision + title + type + url + volume + year + yearfiled + CTLuse_article_number + CTLuse_paper + CTLuse_forced_etal + CTLmax_names_forced_etal + CTLnames_show_etal + CTLuse_alt_spacing + CTLalt_stretch_factor + CTLdash_repeated_names + CTLname_format_string + CTLname_latex_cmd + CTLname_url_prefix + } + {} + { label } + + + + +%%%%%%%%%%%%%%%%%%%%%%% +%% INTEGER VARIABLES %% +%%%%%%%%%%%%%%%%%%%%%%% + +INTEGERS { prev.status.punct this.status.punct punct.std + punct.no punct.comma punct.period + prev.status.space this.status.space space.std + space.no space.normal space.large + prev.status.quote this.status.quote quote.std + quote.no quote.close + prev.status.nline this.status.nline nline.std + nline.no nline.newblock + status.cap cap.std + cap.no cap.yes} + +INTEGERS { longest.label.width multiresult nameptr namesleft number.label numnames } + +INTEGERS { is.use.number.for.article + is.use.paper + is.forced.et.al + max.num.names.before.forced.et.al + num.names.shown.with.forced.et.al + is.use.alt.interword.spacing + is.dash.repeated.names} + + +%%%%%%%%%%%%%%%%%%%%%% +%% STRING VARIABLES %% +%%%%%%%%%%%%%%%%%%%%%% + +STRINGS { bibinfo + longest.label + oldname + s + t + ALTinterwordstretchfactor + name.format.string + name.latex.cmd + name.url.prefix} + + + + +%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOW LEVEL FUNCTIONS %% +%%%%%%%%%%%%%%%%%%%%%%%%% + +FUNCTION {initialize.controls} +{ default.is.use.number.for.article 'is.use.number.for.article := + default.is.use.paper 'is.use.paper := + default.is.forced.et.al 'is.forced.et.al := + default.max.num.names.before.forced.et.al 'max.num.names.before.forced.et.al := + default.num.names.shown.with.forced.et.al 'num.names.shown.with.forced.et.al := + default.is.use.alt.interword.spacing 'is.use.alt.interword.spacing := + default.is.dash.repeated.names 'is.dash.repeated.names := + default.ALTinterwordstretchfactor 'ALTinterwordstretchfactor := + default.name.format.string 'name.format.string := + default.name.latex.cmd 'name.latex.cmd := + default.name.url.prefix 'name.url.prefix := +} + + +% This IEEEtran.bst features a very powerful and flexible mechanism for +% controlling the capitalization, punctuation, spacing, quotation, and +% newlines of the formatted entry fields. (Note: IEEEtran.bst does not need +% or use the newline/newblock feature, but it has been implemented for +% possible future use.) The output states of IEEEtran.bst consist of +% multiple independent attributes and, as such, can be thought of as being +% vectors, rather than the simple scalar values ("before.all", +% "mid.sentence", etc.) used in most other .bst files. +% +% The more flexible and complex design used here was motivated in part by +% IEEE's rather unusual bibliography style. For example, IEEE ends the +% previous field item with a period and large space prior to the publisher +% address; the @electronic entry types use periods as inter-item punctuation +% rather than the commas used by the other entry types; and URLs are never +% followed by periods even though they are the last item in the entry. +% Although it is possible to accommodate these features with the conventional +% output state system, the seemingly endless exceptions make for convoluted, +% unreliable and difficult to maintain code. +% +% IEEEtran.bst's output state system can be easily understood via a simple +% illustration of two most recently formatted entry fields (on the stack): +% +% CURRENT_ITEM +% "PREVIOUS_ITEM +% +% which, in this example, is to eventually appear in the bibliography as: +% +% "PREVIOUS_ITEM," CURRENT_ITEM +% +% It is the job of the output routine to take the previous item off of the +% stack (while leaving the current item at the top of the stack), apply its +% trailing punctuation (including closing quote marks) and spacing, and then +% to write the result to BibTeX's output buffer: +% +% "PREVIOUS_ITEM," +% +% Punctuation (and spacing) between items is often determined by both of the +% items rather than just the first one. The presence of quotation marks +% further complicates the situation because, in standard English, trailing +% punctuation marks are supposed to be contained within the quotes. +% +% IEEEtran.bst maintains two output state (aka "status") vectors which +% correspond to the previous and current (aka "this") items. Each vector +% consists of several independent attributes which track punctuation, +% spacing, quotation, and newlines. Capitalization status is handled by a +% separate scalar because the format routines, not the output routine, +% handle capitalization and, therefore, there is no need to maintain the +% capitalization attribute for both the "previous" and "this" items. +% +% When a format routine adds a new item, it copies the current output status +% vector to the previous output status vector and (usually) resets the +% current (this) output status vector to a "standard status" vector. Using a +% "standard status" vector in this way allows us to redefine what we mean by +% "standard status" at the start of each entry handler and reuse the same +% format routines under the various inter-item separation schemes. For +% example, the standard status vector for the @book entry type may use +% commas for item separators, while the @electronic type may use periods, +% yet both entry handlers exploit many of the exact same format routines. +% +% Because format routines have write access to the output status vector of +% the previous item, they can override the punctuation choices of the +% previous format routine! Therefore, it becomes trivial to implement rules +% such as "Always use a period and a large space before the publisher." By +% pushing the generation of the closing quote mark to the output routine, we +% avoid all the problems caused by having to close a quote before having all +% the information required to determine what the punctuation should be. +% +% The IEEEtran.bst output state system can easily be expanded if needed. +% For instance, it is easy to add a "space.tie" attribute value if the +% bibliography rules mandate that two items have to be joined with an +% unbreakable space. + +FUNCTION {initialize.status.constants} +{ #0 'punct.no := + #1 'punct.comma := + #2 'punct.period := + #0 'space.no := + #1 'space.normal := + #2 'space.large := + #0 'quote.no := + #1 'quote.close := + #0 'cap.no := + #1 'cap.yes := + #0 'nline.no := + #1 'nline.newblock := +} + +FUNCTION {std.status.using.comma} +{ punct.comma 'punct.std := + space.normal 'space.std := + quote.no 'quote.std := + nline.no 'nline.std := + cap.no 'cap.std := +} + +FUNCTION {std.status.using.period} +{ punct.period 'punct.std := + space.normal 'space.std := + quote.no 'quote.std := + nline.no 'nline.std := + cap.yes 'cap.std := +} + +FUNCTION {initialize.prev.this.status} +{ punct.no 'prev.status.punct := + space.no 'prev.status.space := + quote.no 'prev.status.quote := + nline.no 'prev.status.nline := + punct.no 'this.status.punct := + space.no 'this.status.space := + quote.no 'this.status.quote := + nline.no 'this.status.nline := + cap.yes 'status.cap := +} + +FUNCTION {this.status.std} +{ punct.std 'this.status.punct := + space.std 'this.status.space := + quote.std 'this.status.quote := + nline.std 'this.status.nline := +} + +FUNCTION {cap.status.std}{ cap.std 'status.cap := } + +FUNCTION {this.to.prev.status} +{ this.status.punct 'prev.status.punct := + this.status.space 'prev.status.space := + this.status.quote 'prev.status.quote := + this.status.nline 'prev.status.nline := +} + + +FUNCTION {not} +{ { #0 } + { #1 } + if$ +} + +FUNCTION {and} +{ { skip$ } + { pop$ #0 } + if$ +} + +FUNCTION {or} +{ { pop$ #1 } + { skip$ } + if$ +} + + +% convert the strings "yes" or "no" to #1 or #0 respectively +FUNCTION {yes.no.to.int} +{ "l" change.case$ duplicate$ + "yes" = + { pop$ #1 } + { duplicate$ "no" = + { pop$ #0 } + { "unknown boolean " quote$ * swap$ * quote$ * + " in " * cite$ * warning$ + #0 + } + if$ + } + if$ +} + + +% pushes true if the single char string on the stack is in the +% range of "0" to "9" +FUNCTION {is.num} +{ chr.to.int$ + duplicate$ "0" chr.to.int$ < not + swap$ "9" chr.to.int$ > not and +} + +% multiplies the integer on the stack by a factor of 10 +FUNCTION {bump.int.mag} +{ #0 'multiresult := + { duplicate$ #0 > } + { #1 - + multiresult #10 + + 'multiresult := + } + while$ +pop$ +multiresult +} + +% converts a single character string on the stack to an integer +FUNCTION {char.to.integer} +{ duplicate$ + is.num + { chr.to.int$ "0" chr.to.int$ - } + {"noninteger character " quote$ * swap$ * quote$ * + " in integer field of " * cite$ * warning$ + #0 + } + if$ +} + +% converts a string on the stack to an integer +FUNCTION {string.to.integer} +{ duplicate$ text.length$ 'namesleft := + #1 'nameptr := + #0 'numnames := + { nameptr namesleft > not } + { duplicate$ nameptr #1 substring$ + char.to.integer numnames bump.int.mag + + 'numnames := + nameptr #1 + + 'nameptr := + } + while$ +pop$ +numnames +} + + + + +% The output routines write out the *next* to the top (previous) item on the +% stack, adding punctuation and such as needed. Since IEEEtran.bst maintains +% the output status for the top two items on the stack, these output +% routines have to consider the previous output status (which corresponds to +% the item that is being output). Full independent control of punctuation, +% closing quote marks, spacing, and newblock is provided. +% +% "output.nonnull" does not check for the presence of a previous empty +% item. +% +% "output" does check for the presence of a previous empty item and will +% remove an empty item rather than outputing it. +% +% "output.warn" is like "output", but will issue a warning if it detects +% an empty item. + +FUNCTION {output.nonnull} +{ swap$ + prev.status.punct punct.comma = + { "," * } + { skip$ } + if$ + prev.status.punct punct.period = + { add.period$ } + { skip$ } + if$ + prev.status.quote quote.close = + { "''" * } + { skip$ } + if$ + prev.status.space space.normal = + { " " * } + { skip$ } + if$ + prev.status.space space.large = + { large.space * } + { skip$ } + if$ + write$ + prev.status.nline nline.newblock = + { newline$ "\newblock " write$ } + { skip$ } + if$ +} + +FUNCTION {output} +{ duplicate$ empty$ + 'pop$ + 'output.nonnull + if$ +} + +FUNCTION {output.warn} +{ 't := + duplicate$ empty$ + { pop$ "empty " t * " in " * cite$ * warning$ } + 'output.nonnull + if$ +} + +% "fin.entry" is the output routine that handles the last item of the entry +% (which will be on the top of the stack when "fin.entry" is called). + +FUNCTION {fin.entry} +{ this.status.punct punct.no = + { skip$ } + { add.period$ } + if$ + this.status.quote quote.close = + { "''" * } + { skip$ } + if$ +write$ +newline$ +} + + +FUNCTION {is.last.char.not.punct} +{ duplicate$ + "}" * add.period$ + #-1 #1 substring$ "." = +} + +FUNCTION {is.multiple.pages} +{ 't := + #0 'multiresult := + { multiresult not + t empty$ not + and + } + { t #1 #1 substring$ + duplicate$ "-" = + swap$ duplicate$ "," = + swap$ "+" = + or or + { #1 'multiresult := } + { t #2 global.max$ substring$ 't := } + if$ + } + while$ + multiresult +} + +FUNCTION {capitalize}{ "u" change.case$ "t" change.case$ } + +FUNCTION {emphasize} +{ duplicate$ empty$ + { pop$ "" } + { "\emph{" swap$ * "}" * } + if$ +} + +FUNCTION {do.name.latex.cmd} +{ name.latex.cmd + empty$ + { skip$ } + { name.latex.cmd "{" * swap$ * "}" * } + if$ +} + +% IEEEtran.bst uses its own \BIBforeignlanguage command which directly +% invokes the TeX hyphenation patterns without the need of the Babel +% package. Babel does a lot more than switch hyphenation patterns and +% its loading can cause unintended effects in many class files (such as +% IEEEtran.cls). +FUNCTION {select.language} +{ duplicate$ empty$ 'pop$ + { language empty$ 'skip$ + { "\BIBforeignlanguage{" language * "}{" * swap$ * "}" * } + if$ + } + if$ +} + +FUNCTION {tie.or.space.prefix} +{ duplicate$ text.length$ #3 < + { "~" } + { " " } + if$ + swap$ +} + +FUNCTION {get.bbl.editor} +{ editor num.names$ #1 > 'bbl.editors 'bbl.editor if$ } + +FUNCTION {space.word}{ " " swap$ * " " * } + + +% Field Conditioners, Converters, Checkers and External Interfaces + +FUNCTION {empty.field.to.null.string} +{ duplicate$ empty$ + { pop$ "" } + { skip$ } + if$ +} + +FUNCTION {either.or.check} +{ empty$ + { pop$ } + { "can't use both " swap$ * " fields in " * cite$ * warning$ } + if$ +} + +FUNCTION {empty.entry.warn} +{ author empty$ title empty$ howpublished empty$ + month empty$ year empty$ note empty$ url empty$ + and and and and and and + { "all relevant fields are empty in " cite$ * warning$ } + 'skip$ + if$ +} + + +% The bibinfo system provides a way for the electronic parsing/acquisition +% of a bibliography's contents as is done by ReVTeX. For example, a field +% could be entered into the bibliography as: +% \bibinfo{volume}{2} +% Only the "2" would show up in the document, but the LaTeX \bibinfo command +% could do additional things with the information. IEEEtran.bst does provide +% a \bibinfo command via "\providecommand{\bibinfo}[2]{#2}". However, it is +% currently not used as the bogus bibinfo functions defined here output the +% entry values directly without the \bibinfo wrapper. The bibinfo functions +% themselves (and the calls to them) are retained for possible future use. +% +% bibinfo.check avoids acting on missing fields while bibinfo.warn will +% issue a warning message if a missing field is detected. Prior to calling +% the bibinfo functions, the user should push the field value and then its +% name string, in that order. + +FUNCTION {bibinfo.check} +{ swap$ duplicate$ missing$ + { pop$ pop$ "" } + { duplicate$ empty$ + { swap$ pop$ } + { swap$ pop$ } + if$ + } + if$ +} + +FUNCTION {bibinfo.warn} +{ swap$ duplicate$ missing$ + { swap$ "missing " swap$ * " in " * cite$ * warning$ pop$ "" } + { duplicate$ empty$ + { swap$ "empty " swap$ * " in " * cite$ * warning$ } + { swap$ pop$ } + if$ + } + if$ +} + + +% IEEE separates large numbers with more than 4 digits into groups of +% three. IEEE uses a small space to separate these number groups. +% Typical applications include patent and page numbers. + +% number of consecutive digits required to trigger the group separation. +FUNCTION {large.number.trigger}{ #5 } + +% For numbers longer than the trigger, this is the blocksize of the groups. +% The blocksize must be less than the trigger threshold, and 2 * blocksize +% must be greater than the trigger threshold (can't do more than one +% separation on the initial trigger). +FUNCTION {large.number.blocksize}{ #3 } + +% What is actually inserted between the number groups. +FUNCTION {large.number.separator}{ "\," } + +% So as to save on integer variables by reusing existing ones, numnames +% holds the current number of consecutive digits read and nameptr holds +% the number that will trigger an inserted space. +FUNCTION {large.number.separate} +{ 't := + "" + #0 'numnames := + large.number.trigger 'nameptr := + { t empty$ not } + { t #-1 #1 substring$ is.num + { numnames #1 + 'numnames := } + { #0 'numnames := + large.number.trigger 'nameptr := + } + if$ + t #-1 #1 substring$ swap$ * + t #-2 global.max$ substring$ 't := + numnames nameptr = + { duplicate$ #1 nameptr large.number.blocksize - substring$ swap$ + nameptr large.number.blocksize - #1 + global.max$ substring$ + large.number.separator swap$ * * + nameptr large.number.blocksize - 'numnames := + large.number.blocksize #1 + 'nameptr := + } + { skip$ } + if$ + } + while$ +} + +% Converts all single dashes "-" to double dashes "--". +FUNCTION {n.dashify} +{ large.number.separate + 't := + "" + { t empty$ not } + { t #1 #1 substring$ "-" = + { t #1 #2 substring$ "--" = not + { "--" * + t #2 global.max$ substring$ 't := + } + { { t #1 #1 substring$ "-" = } + { "-" * + t #2 global.max$ substring$ 't := + } + while$ + } + if$ + } + { t #1 #1 substring$ * + t #2 global.max$ substring$ 't := + } + if$ + } + while$ +} + + +% This function detects entries with names that are identical to that of +% the previous entry and replaces the repeated names with dashes (if the +% "is.dash.repeated.names" user control is nonzero). +FUNCTION {name.or.dash} +{ 's := + oldname empty$ + { s 'oldname := s } + { s oldname = + { is.dash.repeated.names + { repeated.name.dashes } + { s 'oldname := s } + if$ + } + { s 'oldname := s } + if$ + } + if$ +} + +% Converts the number string on the top of the stack to +% "numerical ordinal form" (e.g., "7" to "7th"). There is +% no artificial limit to the upper bound of the numbers as the +% least significant digit always determines the ordinal form. +FUNCTION {num.to.ordinal} +{ duplicate$ #-1 #1 substring$ "1" = + { bbl.st * } + { duplicate$ #-1 #1 substring$ "2" = + { bbl.nd * } + { duplicate$ #-1 #1 substring$ "3" = + { bbl.rd * } + { bbl.th * } + if$ + } + if$ + } + if$ +} + +% If the string on the top of the stack begins with a number, +% (e.g., 11th) then replace the string with the leading number +% it contains. Otherwise retain the string as-is. s holds the +% extracted number, t holds the part of the string that remains +% to be scanned. +FUNCTION {extract.num} +{ duplicate$ 't := + "" 's := + { t empty$ not } + { t #1 #1 substring$ + t #2 global.max$ substring$ 't := + duplicate$ is.num + { s swap$ * 's := } + { pop$ "" 't := } + if$ + } + while$ + s empty$ + 'skip$ + { pop$ s } + if$ +} + +% Converts the word number string on the top of the stack to +% Arabic string form. Will be successful up to "tenth". +FUNCTION {word.to.num} +{ duplicate$ "l" change.case$ 's := + s "first" = + { pop$ "1" } + { skip$ } + if$ + s "second" = + { pop$ "2" } + { skip$ } + if$ + s "third" = + { pop$ "3" } + { skip$ } + if$ + s "fourth" = + { pop$ "4" } + { skip$ } + if$ + s "fifth" = + { pop$ "5" } + { skip$ } + if$ + s "sixth" = + { pop$ "6" } + { skip$ } + if$ + s "seventh" = + { pop$ "7" } + { skip$ } + if$ + s "eighth" = + { pop$ "8" } + { skip$ } + if$ + s "ninth" = + { pop$ "9" } + { skip$ } + if$ + s "tenth" = + { pop$ "10" } + { skip$ } + if$ +} + + +% Converts the string on the top of the stack to numerical +% ordinal (e.g., "11th") form. +FUNCTION {convert.edition} +{ duplicate$ empty$ 'skip$ + { duplicate$ #1 #1 substring$ is.num + { extract.num + num.to.ordinal + } + { word.to.num + duplicate$ #1 #1 substring$ is.num + { num.to.ordinal } + { "edition ordinal word " quote$ * edition * quote$ * + " may be too high (or improper) for conversion" * " in " * cite$ * warning$ + } + if$ + } + if$ + } + if$ +} + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LATEX BIBLIOGRAPHY CODE %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +FUNCTION {start.entry} +{ newline$ + "\bibitem{" write$ + cite$ write$ + "}" write$ + newline$ + "" + initialize.prev.this.status +} + +% Here we write out all the LaTeX code that we will need. The most involved +% code sequences are those that control the alternate interword spacing and +% foreign language hyphenation patterns. The heavy use of \providecommand +% gives users a way to override the defaults. Special thanks to Javier Bezos, +% Johannes Braams, Robin Fairbairns, Heiko Oberdiek, Donald Arseneau and all +% the other gurus on comp.text.tex for their help and advice on the topic of +% \selectlanguage, Babel and BibTeX. +FUNCTION {begin.bib} +{ "% Generated by IEEEtran.bst, version: " bst.file.version * " (" * bst.file.date * ")" * + write$ newline$ + preamble$ empty$ 'skip$ + { preamble$ write$ newline$ } + if$ + "\begin{thebibliography}{" longest.label * "}" * + write$ newline$ + "\providecommand{\url}[1]{#1}" + write$ newline$ + "\csname url@samestyle\endcsname" + write$ newline$ + "\providecommand{\newblock}{\relax}" + write$ newline$ + "\providecommand{\bibinfo}[2]{#2}" + write$ newline$ + "\providecommand{\BIBentrySTDinterwordspacing}{\spaceskip=0pt\relax}" + write$ newline$ + "\providecommand{\BIBentryALTinterwordstretchfactor}{" + ALTinterwordstretchfactor * "}" * + write$ newline$ + "\providecommand{\BIBentryALTinterwordspacing}{\spaceskip=\fontdimen2\font plus " + write$ newline$ + "\BIBentryALTinterwordstretchfactor\fontdimen3\font minus \fontdimen4\font\relax}" + write$ newline$ + "\providecommand{\BIBforeignlanguage}[2]{{%" + write$ newline$ + "\expandafter\ifx\csname l@#1\endcsname\relax" + write$ newline$ + "\typeout{** WARNING: IEEEtran.bst: No hyphenation pattern has been}%" + write$ newline$ + "\typeout{** loaded for the language `#1'. Using the pattern for}%" + write$ newline$ + "\typeout{** the default language instead.}%" + write$ newline$ + "\else" + write$ newline$ + "\language=\csname l@#1\endcsname" + write$ newline$ + "\fi" + write$ newline$ + "#2}}" + write$ newline$ + "\providecommand{\BIBdecl}{\relax}" + write$ newline$ + "\BIBdecl" + write$ newline$ +} + +FUNCTION {end.bib} +{ newline$ "\end{thebibliography}" write$ newline$ } + +FUNCTION {if.url.alt.interword.spacing} +{ is.use.alt.interword.spacing + {url empty$ 'skip$ {"\BIBentryALTinterwordspacing" write$ newline$} if$} + { skip$ } + if$ +} + +FUNCTION {if.url.std.interword.spacing} +{ is.use.alt.interword.spacing + {url empty$ 'skip$ {"\BIBentrySTDinterwordspacing" write$ newline$} if$} + { skip$ } + if$ +} + + + + +%%%%%%%%%%%%%%%%%%%%%%%% +%% LONGEST LABEL PASS %% +%%%%%%%%%%%%%%%%%%%%%%%% + +FUNCTION {initialize.longest.label} +{ "" 'longest.label := + #1 'number.label := + #0 'longest.label.width := +} + +FUNCTION {longest.label.pass} +{ type$ "ieeetranbstctl" = + { skip$ } + { number.label int.to.str$ 'label := + number.label #1 + 'number.label := + label width$ longest.label.width > + { label 'longest.label := + label width$ 'longest.label.width := + } + { skip$ } + if$ + } + if$ +} + + + + +%%%%%%%%%%%%%%%%%%%%% +%% FORMAT HANDLERS %% +%%%%%%%%%%%%%%%%%%%%% + +%% Lower Level Formats (used by higher level formats) + +FUNCTION {format.address.org.or.pub.date} +{ 't := + "" + year empty$ + { "empty year in " cite$ * warning$ } + { skip$ } + if$ + address empty$ t empty$ and + year empty$ and month empty$ and + { skip$ } + { this.to.prev.status + this.status.std + cap.status.std + address "address" bibinfo.check * + t empty$ + { skip$ } + { punct.period 'prev.status.punct := + space.large 'prev.status.space := + address empty$ + { skip$ } + { ": " * } + if$ + t * + } + if$ + year empty$ month empty$ and + { skip$ } + { t empty$ address empty$ and + { skip$ } + { ", " * } + if$ + month empty$ + { year empty$ + { skip$ } + { year "year" bibinfo.check * } + if$ + } + { month "month" bibinfo.check * + year empty$ + { skip$ } + { " " * year "year" bibinfo.check * } + if$ + } + if$ + } + if$ + } + if$ +} + + +FUNCTION {format.names} +{ 'bibinfo := + duplicate$ empty$ 'skip$ { + this.to.prev.status + this.status.std + 's := + "" 't := + #1 'nameptr := + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { s nameptr + name.format.string + format.name$ + bibinfo bibinfo.check + 't := + nameptr #1 > + { nameptr num.names.shown.with.forced.et.al #1 + = + numnames max.num.names.before.forced.et.al > + is.forced.et.al and and + { "others" 't := + #1 'namesleft := + } + { skip$ } + if$ + namesleft #1 > + { ", " * t do.name.latex.cmd * } + { s nameptr "{ll}" format.name$ duplicate$ "others" = + { 't := } + { pop$ } + if$ + t "others" = + { " " * bbl.etal emphasize * } + { numnames #2 > + { "," * } + { skip$ } + if$ + bbl.and + space.word * t do.name.latex.cmd * + } + if$ + } + if$ + } + { t do.name.latex.cmd } + if$ + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ + cap.status.std + } if$ +} + + + + +%% Higher Level Formats + +%% addresses/locations + +FUNCTION {format.address} +{ address duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + } + if$ +} + + + +%% author/editor names + +FUNCTION {format.authors}{ author "author" format.names } + +FUNCTION {format.editors} +{ editor "editor" format.names duplicate$ empty$ 'skip$ + { ", " * + get.bbl.editor + capitalize + * + } + if$ +} + + + +%% date + +FUNCTION {format.date} +{ + month "month" bibinfo.check duplicate$ empty$ + year "year" bibinfo.check duplicate$ empty$ + { swap$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + "there's a month but no year in " cite$ * warning$ } + if$ + * + } + { this.to.prev.status + this.status.std + cap.status.std + swap$ 'skip$ + { + swap$ + " " * swap$ + } + if$ + * + } + if$ +} + +FUNCTION {format.date.electronic} +{ month "month" bibinfo.check duplicate$ empty$ + year "year" bibinfo.check duplicate$ empty$ + { swap$ + { pop$ } + { "there's a month but no year in " cite$ * warning$ + pop$ ")" * "(" swap$ * + this.to.prev.status + punct.no 'this.status.punct := + space.normal 'this.status.space := + quote.no 'this.status.quote := + cap.yes 'status.cap := + } + if$ + } + { swap$ + { swap$ pop$ ")" * "(" swap$ * } + { "(" swap$ * ", " * swap$ * ")" * } + if$ + this.to.prev.status + punct.no 'this.status.punct := + space.normal 'this.status.space := + quote.no 'this.status.quote := + cap.yes 'status.cap := + } + if$ +} + + + +%% edition/title + +% Note: IEEE considers the edition to be closely associated with +% the title of a book. So, in IEEEtran.bst the edition is normally handled +% within the formatting of the title. The format.edition function is +% retained here for possible future use. +FUNCTION {format.edition} +{ edition duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + convert.edition + status.cap + { "t" } + { "l" } + if$ change.case$ + "edition" bibinfo.check + "~" * bbl.edition * + cap.status.std + } + if$ +} + +% This is used to format the booktitle of a conference proceedings. +% Here we use the "intype" field to provide the user a way to +% override the word "in" (e.g., with things like "presented at") +% Use of intype stops the emphasis of the booktitle to indicate that +% we no longer mean the written conference proceedings, but the +% conference itself. +FUNCTION {format.in.booktitle} +{ booktitle "booktitle" bibinfo.check duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + select.language + intype missing$ + { emphasize + bbl.in " " * + } + { intype " " * } + if$ + swap$ * + cap.status.std + } + if$ +} + +% This is used to format the booktitle of collection. +% Here the "intype" field is not supported, but "edition" is. +FUNCTION {format.in.booktitle.edition} +{ booktitle "booktitle" bibinfo.check duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + select.language + emphasize + edition empty$ 'skip$ + { ", " * + edition + convert.edition + "l" change.case$ + * "~" * bbl.edition * + } + if$ + bbl.in " " * swap$ * + cap.status.std + } + if$ +} + +FUNCTION {format.article.title} +{ title duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + "t" change.case$ + } + if$ + "title" bibinfo.check + duplicate$ empty$ 'skip$ + { quote.close 'this.status.quote := + is.last.char.not.punct + { punct.std 'this.status.punct := } + { punct.no 'this.status.punct := } + if$ + select.language + "``" swap$ * + cap.status.std + } + if$ +} + +FUNCTION {format.article.title.electronic} +{ title duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + "t" change.case$ + } + if$ + "title" bibinfo.check + duplicate$ empty$ + { skip$ } + { select.language } + if$ +} + +FUNCTION {format.book.title.edition} +{ title "title" bibinfo.check + duplicate$ empty$ + { "empty title in " cite$ * warning$ } + { this.to.prev.status + this.status.std + select.language + emphasize + edition empty$ 'skip$ + { ", " * + edition + convert.edition + status.cap + { "t" } + { "l" } + if$ + change.case$ + * "~" * bbl.edition * + } + if$ + cap.status.std + } + if$ +} + +FUNCTION {format.book.title} +{ title "title" bibinfo.check + duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + select.language + emphasize + } + if$ +} + + + +%% journal + +FUNCTION {format.journal} +{ journal duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + select.language + emphasize + } + if$ +} + + + +%% how published + +FUNCTION {format.howpublished} +{ howpublished duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + } + if$ +} + + + +%% institutions/organization/publishers/school + +FUNCTION {format.institution} +{ institution duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + } + if$ +} + +FUNCTION {format.organization} +{ organization duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + } + if$ +} + +FUNCTION {format.address.publisher.date} +{ publisher "publisher" bibinfo.warn format.address.org.or.pub.date } + +FUNCTION {format.address.publisher.date.nowarn} +{ publisher "publisher" bibinfo.check format.address.org.or.pub.date } + +FUNCTION {format.address.organization.date} +{ organization "organization" bibinfo.check format.address.org.or.pub.date } + +FUNCTION {format.school} +{ school duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + cap.status.std + } + if$ +} + + + +%% volume/number/series/chapter/pages + +FUNCTION {format.volume} +{ volume empty.field.to.null.string + duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + bbl.volume + status.cap + { capitalize } + { skip$ } + if$ + swap$ tie.or.space.prefix + "volume" bibinfo.check + * * + cap.status.std + } + if$ +} + +FUNCTION {format.number} +{ number empty.field.to.null.string + duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + status.cap + { bbl.number capitalize } + { bbl.number } + if$ + swap$ tie.or.space.prefix + "number" bibinfo.check + * * + cap.status.std + } + if$ +} + +FUNCTION {format.number.if.use.for.article} +{ is.use.number.for.article + { format.number } + { "" } + if$ +} + +% IEEE does not seem to tie the series so closely with the volume +% and number as is done in other bibliography styles. Instead the +% series is treated somewhat like an extension of the title. +FUNCTION {format.series} +{ series empty$ + { "" } + { this.to.prev.status + this.status.std + bbl.series " " * + series "series" bibinfo.check * + cap.status.std + } + if$ +} + + +FUNCTION {format.chapter} +{ chapter empty$ + { "" } + { this.to.prev.status + this.status.std + type empty$ + { bbl.chapter } + { type "l" change.case$ + "type" bibinfo.check + } + if$ + chapter tie.or.space.prefix + "chapter" bibinfo.check + * * + cap.status.std + } + if$ +} + + +% The intended use of format.paper is for paper numbers of inproceedings. +% The paper type can be overridden via the type field. +% We allow the type to be displayed even if the paper number is absent +% for things like "postdeadline paper" +FUNCTION {format.paper} +{ is.use.paper + { paper empty$ + { type empty$ + { "" } + { this.to.prev.status + this.status.std + type "type" bibinfo.check + cap.status.std + } + if$ + } + { this.to.prev.status + this.status.std + type empty$ + { bbl.paper } + { type "type" bibinfo.check } + if$ + " " * paper + "paper" bibinfo.check + * + cap.status.std + } + if$ + } + { "" } + if$ +} + + +FUNCTION {format.pages} +{ pages duplicate$ empty$ 'skip$ + { this.to.prev.status + this.status.std + duplicate$ is.multiple.pages + { + bbl.pages swap$ + n.dashify + } + { + bbl.page swap$ + } + if$ + tie.or.space.prefix + "pages" bibinfo.check + * * + cap.status.std + } + if$ +} + + + +%% technical report number + +FUNCTION {format.tech.report.number} +{ number "number" bibinfo.check + this.to.prev.status + this.status.std + cap.status.std + type duplicate$ empty$ + { pop$ + bbl.techrep + } + { skip$ } + if$ + "type" bibinfo.check + swap$ duplicate$ empty$ + { pop$ } + { tie.or.space.prefix * * } + if$ +} + + + +%% note + +FUNCTION {format.note} +{ note empty$ + { "" } + { this.to.prev.status + this.status.std + punct.period 'this.status.punct := + note #1 #1 substring$ + duplicate$ "{" = + { skip$ } + { status.cap + { "u" } + { "l" } + if$ + change.case$ + } + if$ + note #2 global.max$ substring$ * "note" bibinfo.check + cap.yes 'status.cap := + } + if$ +} + + + +%% patent + +FUNCTION {format.patent.date} +{ this.to.prev.status + this.status.std + year empty$ + { monthfiled duplicate$ empty$ + { "monthfiled" bibinfo.check pop$ "" } + { "monthfiled" bibinfo.check } + if$ + dayfiled duplicate$ empty$ + { "dayfiled" bibinfo.check pop$ "" * } + { "dayfiled" bibinfo.check + monthfiled empty$ + { "dayfiled without a monthfiled in " cite$ * warning$ + * + } + { " " swap$ * * } + if$ + } + if$ + yearfiled empty$ + { "no year or yearfiled in " cite$ * warning$ } + { yearfiled "yearfiled" bibinfo.check + swap$ + duplicate$ empty$ + { pop$ } + { ", " * swap$ * } + if$ + } + if$ + } + { month duplicate$ empty$ + { "month" bibinfo.check pop$ "" } + { "month" bibinfo.check } + if$ + day duplicate$ empty$ + { "day" bibinfo.check pop$ "" * } + { "day" bibinfo.check + month empty$ + { "day without a month in " cite$ * warning$ + * + } + { " " swap$ * * } + if$ + } + if$ + year "year" bibinfo.check + swap$ + duplicate$ empty$ + { pop$ } + { ", " * swap$ * } + if$ + } + if$ + cap.status.std +} + +FUNCTION {format.patent.nationality.type.number} +{ this.to.prev.status + this.status.std + nationality duplicate$ empty$ + { "nationality" bibinfo.warn pop$ "" } + { "nationality" bibinfo.check + duplicate$ "l" change.case$ "united states" = + { pop$ bbl.patentUS } + { skip$ } + if$ + " " * + } + if$ + type empty$ + { bbl.patent "type" bibinfo.check } + { type "type" bibinfo.check } + if$ + * + number duplicate$ empty$ + { "number" bibinfo.warn pop$ } + { "number" bibinfo.check + large.number.separate + swap$ " " * swap$ * + } + if$ + cap.status.std +} + + + +%% standard + +FUNCTION {format.organization.institution.standard.type.number} +{ this.to.prev.status + this.status.std + organization duplicate$ empty$ + { pop$ + institution duplicate$ empty$ + { "institution" bibinfo.warn } + { "institution" bibinfo.warn " " * } + if$ + } + { "organization" bibinfo.warn " " * } + if$ + type empty$ + { bbl.standard "type" bibinfo.check } + { type "type" bibinfo.check } + if$ + * + number duplicate$ empty$ + { "number" bibinfo.check pop$ } + { "number" bibinfo.check + large.number.separate + swap$ " " * swap$ * + } + if$ + cap.status.std +} + +FUNCTION {format.revision} +{ revision empty$ + { "" } + { this.to.prev.status + this.status.std + bbl.revision + revision tie.or.space.prefix + "revision" bibinfo.check + * * + cap.status.std + } + if$ +} + + +%% thesis + +FUNCTION {format.master.thesis.type} +{ this.to.prev.status + this.status.std + type empty$ + { + bbl.mthesis + } + { + type "type" bibinfo.check + } + if$ +cap.status.std +} + +FUNCTION {format.phd.thesis.type} +{ this.to.prev.status + this.status.std + type empty$ + { + bbl.phdthesis + } + { + type "type" bibinfo.check + } + if$ +cap.status.std +} + + + +%% URL + +FUNCTION {format.url} +{ url empty$ + { "" } + { this.to.prev.status + this.status.std + cap.yes 'status.cap := + name.url.prefix " " * + "\url{" * url * "}" * + punct.no 'this.status.punct := + punct.period 'prev.status.punct := + space.normal 'this.status.space := + space.normal 'prev.status.space := + quote.no 'this.status.quote := + } + if$ +} + + + + +%%%%%%%%%%%%%%%%%%%% +%% ENTRY HANDLERS %% +%%%%%%%%%%%%%%%%%%%% + + +% Note: In many journals, IEEE (or the authors) tend not to show the number +% for articles, so the display of the number is controlled here by the +% switch "is.use.number.for.article" +FUNCTION {article} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors "author" output.warn + name.or.dash + format.article.title "title" output.warn + format.journal "journal" bibinfo.check "journal" output.warn + format.volume output + format.number.if.use.for.article output + format.pages output + format.date "year" output.warn + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {book} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + author empty$ + { format.editors "author and editor" output.warn } + { format.authors output.nonnull } + if$ + name.or.dash + format.book.title.edition output + format.series output + author empty$ + { skip$ } + { format.editors output } + if$ + format.address.publisher.date output + format.volume output + format.number output + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {booklet} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors output + name.or.dash + format.article.title "title" output.warn + format.howpublished "howpublished" bibinfo.check output + format.organization "organization" bibinfo.check output + format.address "address" bibinfo.check output + format.date output + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {electronic} +{ std.status.using.period + start.entry + if.url.alt.interword.spacing + format.authors output + name.or.dash + format.date.electronic output + format.article.title.electronic output + format.howpublished "howpublished" bibinfo.check output + format.organization "organization" bibinfo.check output + format.address "address" bibinfo.check output + format.note output + format.url output + fin.entry + empty.entry.warn + if.url.std.interword.spacing +} + +FUNCTION {inbook} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + author empty$ + { format.editors "author and editor" output.warn } + { format.authors output.nonnull } + if$ + name.or.dash + format.book.title.edition output + format.series output + format.address.publisher.date output + format.volume output + format.number output + format.chapter output + format.pages output + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {incollection} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors "author" output.warn + name.or.dash + format.article.title "title" output.warn + format.in.booktitle.edition "booktitle" output.warn + format.series output + format.editors output + format.address.publisher.date.nowarn output + format.volume output + format.number output + format.chapter output + format.pages output + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {inproceedings} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors "author" output.warn + name.or.dash + format.article.title "title" output.warn + format.in.booktitle "booktitle" output.warn + format.series output + format.editors output + format.volume output + format.number output + publisher empty$ + { format.address.organization.date output } + { format.organization "organization" bibinfo.check output + format.address.publisher.date output + } + if$ + format.paper output + format.pages output + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {manual} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors output + name.or.dash + format.book.title.edition "title" output.warn + format.howpublished "howpublished" bibinfo.check output + format.organization "organization" bibinfo.check output + format.address "address" bibinfo.check output + format.date output + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {mastersthesis} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors "author" output.warn + name.or.dash + format.article.title "title" output.warn + format.master.thesis.type output.nonnull + format.school "school" bibinfo.warn output + format.address "address" bibinfo.check output + format.date "year" output.warn + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {misc} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors output + name.or.dash + format.article.title output + format.howpublished "howpublished" bibinfo.check output + format.organization "organization" bibinfo.check output + format.address "address" bibinfo.check output + format.pages output + format.date output + format.note output + format.url output + fin.entry + empty.entry.warn + if.url.std.interword.spacing +} + +FUNCTION {patent} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors output + name.or.dash + format.article.title output + format.patent.nationality.type.number output + format.patent.date output + format.note output + format.url output + fin.entry + empty.entry.warn + if.url.std.interword.spacing +} + +FUNCTION {periodical} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.editors output + name.or.dash + format.book.title "title" output.warn + format.series output + format.volume output + format.number output + format.organization "organization" bibinfo.check output + format.date "year" output.warn + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {phdthesis} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors "author" output.warn + name.or.dash + format.article.title "title" output.warn + format.phd.thesis.type output.nonnull + format.school "school" bibinfo.warn output + format.address "address" bibinfo.check output + format.date "year" output.warn + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {proceedings} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.editors output + name.or.dash + format.book.title "title" output.warn + format.series output + format.volume output + format.number output + publisher empty$ + { format.address.organization.date output } + { format.organization "organization" bibinfo.check output + format.address.publisher.date output + } + if$ + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {standard} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors output + name.or.dash + format.book.title "title" output.warn + format.howpublished "howpublished" bibinfo.check output + format.organization.institution.standard.type.number output + format.revision output + format.date output + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {techreport} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors "author" output.warn + name.or.dash + format.article.title "title" output.warn + format.howpublished "howpublished" bibinfo.check output + format.institution "institution" bibinfo.warn output + format.address "address" bibinfo.check output + format.tech.report.number output.nonnull + format.date "year" output.warn + format.note output + format.url output + fin.entry + if.url.std.interword.spacing +} + +FUNCTION {unpublished} +{ std.status.using.comma + start.entry + if.url.alt.interword.spacing + format.authors "author" output.warn + name.or.dash + format.article.title "title" output.warn + format.date output + format.note "note" output.warn + format.url output + fin.entry + if.url.std.interword.spacing +} + + +% The special entry type which provides the user interface to the +% BST controls +FUNCTION {IEEEtranBSTCTL} +{ is.print.banners.to.terminal + { "** IEEEtran BST control entry " quote$ * cite$ * quote$ * " detected." * + top$ + } + { skip$ } + if$ + CTLuse_article_number + empty$ + { skip$ } + { CTLuse_article_number + yes.no.to.int + 'is.use.number.for.article := + } + if$ + CTLuse_paper + empty$ + { skip$ } + { CTLuse_paper + yes.no.to.int + 'is.use.paper := + } + if$ + CTLuse_forced_etal + empty$ + { skip$ } + { CTLuse_forced_etal + yes.no.to.int + 'is.forced.et.al := + } + if$ + CTLmax_names_forced_etal + empty$ + { skip$ } + { CTLmax_names_forced_etal + string.to.integer + 'max.num.names.before.forced.et.al := + } + if$ + CTLnames_show_etal + empty$ + { skip$ } + { CTLnames_show_etal + string.to.integer + 'num.names.shown.with.forced.et.al := + } + if$ + CTLuse_alt_spacing + empty$ + { skip$ } + { CTLuse_alt_spacing + yes.no.to.int + 'is.use.alt.interword.spacing := + } + if$ + CTLalt_stretch_factor + empty$ + { skip$ } + { CTLalt_stretch_factor + 'ALTinterwordstretchfactor := + "\renewcommand{\BIBentryALTinterwordstretchfactor}{" + ALTinterwordstretchfactor * "}" * + write$ newline$ + } + if$ + CTLdash_repeated_names + empty$ + { skip$ } + { CTLdash_repeated_names + yes.no.to.int + 'is.dash.repeated.names := + } + if$ + CTLname_format_string + empty$ + { skip$ } + { CTLname_format_string + 'name.format.string := + } + if$ + CTLname_latex_cmd + empty$ + { skip$ } + { CTLname_latex_cmd + 'name.latex.cmd := + } + if$ + CTLname_url_prefix + missing$ + { skip$ } + { CTLname_url_prefix + 'name.url.prefix := + } + if$ + + + num.names.shown.with.forced.et.al max.num.names.before.forced.et.al > + { "CTLnames_show_etal cannot be greater than CTLmax_names_forced_etal in " cite$ * warning$ + max.num.names.before.forced.et.al 'num.names.shown.with.forced.et.al := + } + { skip$ } + if$ +} + + +%%%%%%%%%%%%%%%%%%% +%% ENTRY ALIASES %% +%%%%%%%%%%%%%%%%%%% +FUNCTION {conference}{inproceedings} +FUNCTION {online}{electronic} +FUNCTION {internet}{electronic} +FUNCTION {webpage}{electronic} +FUNCTION {www}{electronic} +FUNCTION {default.type}{misc} + + + +%%%%%%%%%%%%%%%%%% +%% MAIN PROGRAM %% +%%%%%%%%%%%%%%%%%% + +READ + +EXECUTE {initialize.controls} +EXECUTE {initialize.status.constants} +EXECUTE {banner.message} + +EXECUTE {initialize.longest.label} +ITERATE {longest.label.pass} + +EXECUTE {begin.bib} +ITERATE {call.type$} +EXECUTE {end.bib} + +EXECUTE{completed.message} + + +%% That's all folks, mds. diff --git a/spec/consensus/consensus-paper/IEEEtran.cls b/spec/consensus/consensus-paper/IEEEtran.cls new file mode 100644 index 00000000000..9c967d555f0 --- /dev/null +++ b/spec/consensus/consensus-paper/IEEEtran.cls @@ -0,0 +1,4733 @@ +%% +%% IEEEtran.cls 2011/11/03 version V1.8 based on +%% IEEEtran.cls 2007/03/05 version V1.7a +%% The changes in V1.8 are made with a single goal in mind: +%% to change the look of the output using the [conference] option +%% and the default font size (10pt) to match the Word template more closely. +%% These changes may well have undesired side effects when other options +%% are in force! +%% +%% +%% This is the official IEEE LaTeX class for authors of the Institute of +%% Electrical and Electronics Engineers (IEEE) Transactions journals and +%% conferences. +%% +%% Support sites: +%% http://www.michaelshell.org/tex/ieeetran/ +%% http://www.ctan.org/tex-archive/macros/latex/contrib/IEEEtran/ +%% and +%% http://www.ieee.org/ +%% +%% Based on the original 1993 IEEEtran.cls, but with many bug fixes +%% and enhancements (from both JVH and MDS) over the 1996/7 version. +%% +%% +%% Contributors: +%% Gerry Murray (1993), Silvano Balemi (1993), +%% Jon Dixon (1996), Peter N"uchter (1996), +%% Juergen von Hagen (2000), and Michael Shell (2001-2007) +%% +%% +%% Copyright (c) 1993-2000 by Gerry Murray, Silvano Balemi, +%% Jon Dixon, Peter N"uchter, +%% Juergen von Hagen +%% and +%% Copyright (c) 2001-2007 by Michael Shell +%% +%% Current maintainer (V1.3 to V1.7): Michael Shell +%% See: +%% http://www.michaelshell.org/ +%% for current contact information. +%% +%% Special thanks to Peter Wilson (CUA) and Donald Arseneau +%% for allowing the inclusion of the \@ifmtarg command +%% from their ifmtarg LaTeX package. +%% +%%************************************************************************* +%% Legal Notice: +%% This code is offered as-is without any warranty either expressed or +%% implied; without even the implied warranty of MERCHANTABILITY or +%% FITNESS FOR A PARTICULAR PURPOSE! +%% User assumes all risk. +%% In no event shall IEEE or any contributor to this code be liable for +%% any damages or losses, including, but not limited to, incidental, +%% consequential, or any other damages, resulting from the use or misuse +%% of any information contained here. +%% +%% All comments are the opinions of their respective authors and are not +%% necessarily endorsed by the IEEE. +%% +%% This work is distributed under the LaTeX Project Public License (LPPL) +%% ( http://www.latex-project.org/ ) version 1.3, and may be freely used, +%% distributed and modified. A copy of the LPPL, version 1.3, is included +%% in the base LaTeX documentation of all distributions of LaTeX released +%% 2003/12/01 or later. +%% Retain all contribution notices and credits. +%% ** Modified files should be clearly indicated as such, including ** +%% ** renaming them and changing author support contact information. ** +%% +%% File list of work: IEEEtran.cls, IEEEtran_HOWTO.pdf, bare_adv.tex, +%% bare_conf.tex, bare_jrnl.tex, bare_jrnl_compsoc.tex +%% +%% Major changes to the user interface should be indicated by an +%% increase in the version numbers. If a version is a beta, it will +%% be indicated with a BETA suffix, i.e., 1.4 BETA. +%% Small changes can be indicated by appending letters to the version +%% such as "IEEEtran_v14a.cls". +%% In all cases, \Providesclass, any \typeout messages to the user, +%% \IEEEtransversionmajor and \IEEEtransversionminor must reflect the +%% correct version information. +%% The changes should also be documented via source comments. +%%************************************************************************* +%% +% +% Available class options +% e.g., \documentclass[10pt,conference]{IEEEtran} +% +% *** choose only one from each category *** +% +% 9pt, 10pt, 11pt, 12pt +% Sets normal font size. The default is 10pt. +% +% conference, journal, technote, peerreview, peerreviewca +% determines format mode - conference papers, journal papers, +% correspondence papers (technotes), or peer review papers. The user +% should also select 9pt when using technote. peerreview is like +% journal mode, but provides for a single-column "cover" title page for +% anonymous peer review. The paper title (without the author names) is +% repeated at the top of the page after the cover page. For peer review +% papers, the \IEEEpeerreviewmaketitle command must be executed (will +% automatically be ignored for non-peerreview modes) at the place the +% cover page is to end, usually just after the abstract (keywords are +% not normally used with peer review papers). peerreviewca is like +% peerreview, but allows the author names to be entered and formatted +% as with conference mode so that author affiliation and contact +% information can be easily seen on the cover page. +% The default is journal. +% +% draft, draftcls, draftclsnofoot, final +% determines if paper is formatted as a widely spaced draft (for +% handwritten editor comments) or as a properly typeset final version. +% draftcls restricts draft mode to the class file while all other LaTeX +% packages (i.e., \usepackage{graphicx}) will behave as final - allows +% for a draft paper with visible figures, etc. draftclsnofoot is like +% draftcls, but does not display the date and the word "DRAFT" at the foot +% of the pages. If using one of the draft modes, the user will probably +% also want to select onecolumn. +% The default is final. +% +% letterpaper, a4paper +% determines paper size: 8.5in X 11in or 210mm X 297mm. CHANGING THE PAPER +% SIZE WILL NOT ALTER THE TYPESETTING OF THE DOCUMENT - ONLY THE MARGINS +% WILL BE AFFECTED. In particular, documents using the a4paper option will +% have reduced side margins (A4 is narrower than US letter) and a longer +% bottom margin (A4 is longer than US letter). For both cases, the top +% margins will be the same and the text will be horizontally centered. +% For final submission to IEEE, authors should use US letter (8.5 X 11in) +% paper. Note that authors should ensure that all post-processing +% (ps, pdf, etc.) uses the same paper specificiation as the .tex document. +% Problems here are by far the number one reason for incorrect margins. +% IEEEtran will automatically set the default paper size under pdflatex +% (without requiring a change to pdftex.cfg), so this issue is more +% important to dvips users. Fix config.ps, config.pdf, or ~/.dvipsrc for +% dvips, or use the dvips -t papersize option instead as needed. See the +% testflow documentation +% http://www.ctan.org/tex-archive/macros/latex/contrib/IEEEtran/testflow +% for more details on dvips paper size configuration. +% The default is letterpaper. +% +% oneside, twoside +% determines if layout follows single sided or two sided (duplex) +% printing. The only notable change is with the headings at the top of +% the pages. +% The default is oneside. +% +% onecolumn, twocolumn +% determines if text is organized into one or two columns per page. One +% column mode is usually used only with draft papers. +% The default is twocolumn. +% +% compsoc +% Use the format of the IEEE Computer Society. +% +% romanappendices +% Use the "Appendix I" convention when numbering appendices. IEEEtran.cls +% now defaults to Alpha "Appendix A" convention - the opposite of what +% v1.6b and earlier did. +% +% captionsoff +% disables the display of the figure/table captions. Some IEEE journals +% request that captions be removed and figures/tables be put on pages +% of their own at the end of an initial paper submission. The endfloat +% package can be used with this class option to achieve this format. +% +% nofonttune +% turns off tuning of the font interword spacing. Maybe useful to those +% not using the standard Times fonts or for those who have already "tuned" +% their fonts. +% The default is to enable IEEEtran to tune font parameters. +% +% +%---------- +% Available CLASSINPUTs provided (all are macros unless otherwise noted): +% \CLASSINPUTbaselinestretch +% \CLASSINPUTinnersidemargin +% \CLASSINPUToutersidemargin +% \CLASSINPUTtoptextmargin +% \CLASSINPUTbottomtextmargin +% +% Available CLASSINFOs provided: +% \ifCLASSINFOpdf (TeX if conditional) +% \CLASSINFOpaperwidth (macro) +% \CLASSINFOpaperheight (macro) +% \CLASSINFOnormalsizebaselineskip (length) +% \CLASSINFOnormalsizeunitybaselineskip (length) +% +% Available CLASSOPTIONs provided: +% all class option flags (TeX if conditionals) unless otherwise noted, +% e.g., \ifCLASSOPTIONcaptionsoff +% point size options provided as a single macro: +% \CLASSOPTIONpt +% which will be defined as 9, 10, 11, or 12 depending on the document's +% normalsize point size. +% also, class option peerreviewca implies the use of class option peerreview +% and classoption draft implies the use of class option draftcls + + + + + +\ProvidesClass{IEEEtran}[2012/11/21 V1.8c by Harald Hanche-Olsen and Anders Christensen] +\typeout{-- Based on V1.7a by Michael Shell} +\typeout{-- See the "IEEEtran_HOWTO" manual for usage information.} +\typeout{-- http://www.michaelshell.org/tex/ieeetran/} +\NeedsTeXFormat{LaTeX2e} + +% IEEEtran.cls version numbers, provided as of V1.3 +% These values serve as a way a .tex file can +% determine if the new features are provided. +% The version number of this IEEEtrans.cls can be obtained from +% these values. i.e., V1.4 +% KEEP THESE AS INTEGERS! i.e., NO {4a} or anything like that- +% (no need to enumerate "a" minor changes here) +\def\IEEEtransversionmajor{1} +\def\IEEEtransversionminor{7} + +% These do nothing, but provide them like in article.cls +\newif\if@restonecol +\newif\if@titlepage + + +% class option conditionals +\newif\ifCLASSOPTIONonecolumn \CLASSOPTIONonecolumnfalse +\newif\ifCLASSOPTIONtwocolumn \CLASSOPTIONtwocolumntrue + +\newif\ifCLASSOPTIONoneside \CLASSOPTIONonesidetrue +\newif\ifCLASSOPTIONtwoside \CLASSOPTIONtwosidefalse + +\newif\ifCLASSOPTIONfinal \CLASSOPTIONfinaltrue +\newif\ifCLASSOPTIONdraft \CLASSOPTIONdraftfalse +\newif\ifCLASSOPTIONdraftcls \CLASSOPTIONdraftclsfalse +\newif\ifCLASSOPTIONdraftclsnofoot \CLASSOPTIONdraftclsnofootfalse + +\newif\ifCLASSOPTIONpeerreview \CLASSOPTIONpeerreviewfalse +\newif\ifCLASSOPTIONpeerreviewca \CLASSOPTIONpeerreviewcafalse + +\newif\ifCLASSOPTIONjournal \CLASSOPTIONjournaltrue +\newif\ifCLASSOPTIONconference \CLASSOPTIONconferencefalse +\newif\ifCLASSOPTIONtechnote \CLASSOPTIONtechnotefalse + +\newif\ifCLASSOPTIONnofonttune \CLASSOPTIONnofonttunefalse + +\newif\ifCLASSOPTIONcaptionsoff \CLASSOPTIONcaptionsofffalse + +\newif\ifCLASSOPTIONcompsoc \CLASSOPTIONcompsocfalse + +\newif\ifCLASSOPTIONromanappendices \CLASSOPTIONromanappendicesfalse + + +% class info conditionals + +% indicates if pdf (via pdflatex) output +\newif\ifCLASSINFOpdf \CLASSINFOpdffalse + + +% V1.6b internal flag to show if using a4paper +\newif\if@IEEEusingAfourpaper \@IEEEusingAfourpaperfalse + + + +% IEEEtran class scratch pad registers +% dimen +\newdimen\@IEEEtrantmpdimenA +\newdimen\@IEEEtrantmpdimenB +% count +\newcount\@IEEEtrantmpcountA +\newcount\@IEEEtrantmpcountB +% token list +\newtoks\@IEEEtrantmptoksA + +% we use \CLASSOPTIONpt so that we can ID the point size (even for 9pt docs) +% as well as LaTeX's \@ptsize to retain some compatability with some +% external packages +\def\@ptsize{0} +% LaTeX does not support 9pt, so we set \@ptsize to 0 - same as that of 10pt +\DeclareOption{9pt}{\def\CLASSOPTIONpt{9}\def\@ptsize{0}} +\DeclareOption{10pt}{\def\CLASSOPTIONpt{10}\def\@ptsize{0}} +\DeclareOption{11pt}{\def\CLASSOPTIONpt{11}\def\@ptsize{1}} +\DeclareOption{12pt}{\def\CLASSOPTIONpt{12}\def\@ptsize{2}} + + + +\DeclareOption{letterpaper}{\setlength{\paperheight}{11in}% + \setlength{\paperwidth}{8.5in}% + \@IEEEusingAfourpaperfalse + \def\CLASSOPTIONpaper{letter}% + \def\CLASSINFOpaperwidth{8.5in}% + \def\CLASSINFOpaperheight{11in}} + + +\DeclareOption{a4paper}{\setlength{\paperheight}{297mm}% + \setlength{\paperwidth}{210mm}% + \@IEEEusingAfourpapertrue + \def\CLASSOPTIONpaper{a4}% + \def\CLASSINFOpaperwidth{210mm}% + \def\CLASSINFOpaperheight{297mm}} + +\DeclareOption{oneside}{\@twosidefalse\@mparswitchfalse + \CLASSOPTIONonesidetrue\CLASSOPTIONtwosidefalse} +\DeclareOption{twoside}{\@twosidetrue\@mparswitchtrue + \CLASSOPTIONtwosidetrue\CLASSOPTIONonesidefalse} + +\DeclareOption{onecolumn}{\CLASSOPTIONonecolumntrue\CLASSOPTIONtwocolumnfalse} +\DeclareOption{twocolumn}{\CLASSOPTIONtwocolumntrue\CLASSOPTIONonecolumnfalse} + +% If the user selects draft, then this class AND any packages +% will go into draft mode. +\DeclareOption{draft}{\CLASSOPTIONdrafttrue\CLASSOPTIONdraftclstrue + \CLASSOPTIONdraftclsnofootfalse} +% draftcls is for a draft mode which will not affect any packages +% used by the document. +\DeclareOption{draftcls}{\CLASSOPTIONdraftfalse\CLASSOPTIONdraftclstrue + \CLASSOPTIONdraftclsnofootfalse} +% draftclsnofoot is like draftcls, but without the footer. +\DeclareOption{draftclsnofoot}{\CLASSOPTIONdraftfalse\CLASSOPTIONdraftclstrue + \CLASSOPTIONdraftclsnofoottrue} +\DeclareOption{final}{\CLASSOPTIONdraftfalse\CLASSOPTIONdraftclsfalse + \CLASSOPTIONdraftclsnofootfalse} + +\DeclareOption{journal}{\CLASSOPTIONpeerreviewfalse\CLASSOPTIONpeerreviewcafalse + \CLASSOPTIONjournaltrue\CLASSOPTIONconferencefalse\CLASSOPTIONtechnotefalse} + +\DeclareOption{conference}{\CLASSOPTIONpeerreviewfalse\CLASSOPTIONpeerreviewcafalse + \CLASSOPTIONjournalfalse\CLASSOPTIONconferencetrue\CLASSOPTIONtechnotefalse} + +\DeclareOption{technote}{\CLASSOPTIONpeerreviewfalse\CLASSOPTIONpeerreviewcafalse + \CLASSOPTIONjournalfalse\CLASSOPTIONconferencefalse\CLASSOPTIONtechnotetrue} + +\DeclareOption{peerreview}{\CLASSOPTIONpeerreviewtrue\CLASSOPTIONpeerreviewcafalse + \CLASSOPTIONjournalfalse\CLASSOPTIONconferencefalse\CLASSOPTIONtechnotefalse} + +\DeclareOption{peerreviewca}{\CLASSOPTIONpeerreviewtrue\CLASSOPTIONpeerreviewcatrue + \CLASSOPTIONjournalfalse\CLASSOPTIONconferencefalse\CLASSOPTIONtechnotefalse} + +\DeclareOption{nofonttune}{\CLASSOPTIONnofonttunetrue} + +\DeclareOption{captionsoff}{\CLASSOPTIONcaptionsofftrue} + +\DeclareOption{compsoc}{\CLASSOPTIONcompsoctrue} + +\DeclareOption{romanappendices}{\CLASSOPTIONromanappendicestrue} + + +% default to US letter paper, 10pt, twocolumn, one sided, final, journal +\ExecuteOptions{letterpaper,10pt,twocolumn,oneside,final,journal} +% overrride these defaults per user requests +\ProcessOptions + + + +% Computer Society conditional execution command +\long\def\@IEEEcompsoconly#1{\relax\ifCLASSOPTIONcompsoc\relax#1\relax\fi\relax} +% inverse +\long\def\@IEEEnotcompsoconly#1{\relax\ifCLASSOPTIONcompsoc\else\relax#1\relax\fi\relax} +% compsoc conference +\long\def\@IEEEcompsocconfonly#1{\relax\ifCLASSOPTIONcompsoc\ifCLASSOPTIONconference\relax#1\relax\fi\fi\relax} +% compsoc not conference +\long\def\@IEEEcompsocnotconfonly#1{\relax\ifCLASSOPTIONcompsoc\ifCLASSOPTIONconference\else\relax#1\relax\fi\fi\relax} + + +% IEEE uses Times Roman font, so we'll default to Times. +% These three commands make up the entire times.sty package. +\renewcommand{\sfdefault}{phv} +\renewcommand{\rmdefault}{ptm} +\renewcommand{\ttdefault}{pcr} + +\@IEEEcompsoconly{\typeout{-- Using IEEE Computer Society mode.}} + +% V1.7 compsoc nonconference papers, use Palatino/Palladio as the main text font, +% not Times Roman. +\@IEEEcompsocnotconfonly{\renewcommand{\rmdefault}{ppl}} + +% enable Times/Palatino main text font +\normalfont\selectfont + + + + + +% V1.7 conference notice message hook +\def\@IEEEconsolenoticeconference{\typeout{}% +\typeout{** Conference Paper **}% +\typeout{Before submitting the final camera ready copy, remember to:}% +\typeout{}% +\typeout{ 1. Manually equalize the lengths of two columns on the last page}% +\typeout{ of your paper;}% +\typeout{}% +\typeout{ 2. Ensure that any PostScript and/or PDF output post-processing}% +\typeout{ uses only Type 1 fonts and that every step in the generation}% +\typeout{ process uses the appropriate paper size.}% +\typeout{}} + + +% we can send console reminder messages to the user here +\AtEndDocument{\ifCLASSOPTIONconference\@IEEEconsolenoticeconference\fi} + + +% warn about the use of single column other than for draft mode +\ifCLASSOPTIONtwocolumn\else% + \ifCLASSOPTIONdraftcls\else% + \typeout{** ATTENTION: Single column mode is not typically used with IEEE publications.}% + \fi% +\fi + + +% V1.7 improved paper size setting code. +% Set pdfpage and dvips paper sizes. Conditional tests are similar to that +% of ifpdf.sty. Retain within {} to ensure tested macros are never altered, +% even if only effect is to set them to \relax. +% if \pdfoutput is undefined or equal to relax, output a dvips special +{\@ifundefined{pdfoutput}{\AtBeginDvi{\special{papersize=\CLASSINFOpaperwidth,\CLASSINFOpaperheight}}}{% +% pdfoutput is defined and not equal to \relax +% check for pdfpageheight existence just in case someone sets pdfoutput +% under non-pdflatex. If exists, set them regardless of value of \pdfoutput. +\@ifundefined{pdfpageheight}{\relax}{\global\pdfpagewidth\paperwidth +\global\pdfpageheight\paperheight}% +% if using \pdfoutput=0 under pdflatex, send dvips papersize special +\ifcase\pdfoutput +\AtBeginDvi{\special{papersize=\CLASSINFOpaperwidth,\CLASSINFOpaperheight}}% +\else +% we are using pdf output, set CLASSINFOpdf flag +\global\CLASSINFOpdftrue +\fi}} + +% let the user know the selected papersize +\typeout{-- Using \CLASSINFOpaperwidth\space x \CLASSINFOpaperheight\space +(\CLASSOPTIONpaper)\space paper.} + +\ifCLASSINFOpdf +\typeout{-- Using PDF output.} +\else +\typeout{-- Using DVI output.} +\fi + + +% The idea hinted here is for LaTeX to generate markleft{} and markright{} +% automatically for you after you enter \author{}, \journal{}, +% \journaldate{}, journalvol{}, \journalnum{}, etc. +% However, there may be some backward compatibility issues here as +% well as some special applications for IEEEtran.cls and special issues +% that may require the flexible \markleft{}, \markright{} and/or \markboth{}. +% We'll leave this as an open future suggestion. +%\newcommand{\journal}[1]{\def\@journal{#1}} +%\def\@journal{} + + + +% pointsize values +% used with ifx to determine the document's normal size +\def\@IEEEptsizenine{9} +\def\@IEEEptsizeten{10} +\def\@IEEEptsizeeleven{11} +\def\@IEEEptsizetwelve{12} + + + +% FONT DEFINITIONS (No sizexx.clo file needed) +% V1.6 revised font sizes, displayskip values and +% revised normalsize baselineskip to reduce underfull vbox problems +% on the 58pc = 696pt = 9.5in text height we want +% normalsize #lines/column baselineskip (aka leading) +% 9pt 63 11.0476pt (truncated down) +% 10pt 58 12pt (exact) +% 11pt 52 13.3846pt (truncated down) +% 12pt 50 13.92pt (exact) +% + +% we need to store the nominal baselineskip for the given font size +% in case baselinestretch ever changes. +% this is a dimen, so it will not hold stretch or shrink +\newdimen\@IEEEnormalsizeunitybaselineskip +\@IEEEnormalsizeunitybaselineskip\baselineskip + +\ifx\CLASSOPTIONpt\@IEEEptsizenine +\typeout{-- This is a 9 point document.} +\def\normalsize{\@setfontsize{\normalsize}{9}{11.0476pt}}% +\setlength{\@IEEEnormalsizeunitybaselineskip}{11.0476pt}% +\normalsize +\abovedisplayskip 1.5ex plus3pt minus1pt% +\belowdisplayskip \abovedisplayskip% +\abovedisplayshortskip 0pt plus3pt% +\belowdisplayshortskip 1.5ex plus3pt minus1pt +\def\small{\@setfontsize{\small}{8.5}{10pt}} +\def\footnotesize{\@setfontsize{\footnotesize}{8}{9pt}} +\def\scriptsize{\@setfontsize{\scriptsize}{7}{8pt}} +\def\tiny{\@setfontsize{\tiny}{5}{6pt}} +% sublargesize is the same as large - 10pt +\def\sublargesize{\@setfontsize{\sublargesize}{10}{12pt}} +\def\large{\@setfontsize{\large}{10}{12pt}} +\def\Large{\@setfontsize{\Large}{12}{14pt}} +\def\LARGE{\@setfontsize{\LARGE}{14}{17pt}} +\def\huge{\@setfontsize{\huge}{17}{20pt}} +\def\Huge{\@setfontsize{\Huge}{20}{24pt}} +\fi + + +% Check if we have selected 10 points +\ifx\CLASSOPTIONpt\@IEEEptsizeten +\typeout{-- This is a 10 point document.} +\def\normalsize{\@setfontsize{\normalsize}{10}{11}}% +\setlength{\@IEEEnormalsizeunitybaselineskip}{11pt}% +\normalsize +\abovedisplayskip 1.5ex plus4pt minus2pt% +\belowdisplayskip \abovedisplayskip% +\abovedisplayshortskip 0pt plus4pt% +\belowdisplayshortskip 1.5ex plus4pt minus2pt +\def\small{\@setfontsize{\small}{9}{10pt}} +\def\footnotesize{\@setfontsize{\footnotesize}{8}{9pt}} +\def\scriptsize{\@setfontsize{\scriptsize}{7}{8pt}} +\def\tiny{\@setfontsize{\tiny}{5}{6pt}} +% sublargesize is a tad smaller than large - 11pt +\def\sublargesize{\@setfontsize{\sublargesize}{11}{13.4pt}} +\def\large{\@setfontsize{\large}{12}{14pt}} +\def\Large{\@setfontsize{\Large}{14}{17pt}} +\def\LARGE{\@setfontsize{\LARGE}{17}{20pt}} +\def\huge{\@setfontsize{\huge}{20}{24pt}} +\def\Huge{\@setfontsize{\Huge}{24}{28pt}} +\fi + + +% Check if we have selected 11 points +\ifx\CLASSOPTIONpt\@IEEEptsizeeleven +\typeout{-- This is an 11 point document.} +\def\normalsize{\@setfontsize{\normalsize}{11}{13.3846pt}}% +\setlength{\@IEEEnormalsizeunitybaselineskip}{13.3846pt}% +\normalsize +\abovedisplayskip 1.5ex plus5pt minus3pt% +\belowdisplayskip \abovedisplayskip% +\abovedisplayshortskip 0pt plus5pt% +\belowdisplayshortskip 1.5ex plus5pt minus3pt +\def\small{\@setfontsize{\small}{10}{12pt}} +\def\footnotesize{\@setfontsize{\footnotesize}{9}{10.5pt}} +\def\scriptsize{\@setfontsize{\scriptsize}{8}{9pt}} +\def\tiny{\@setfontsize{\tiny}{6}{7pt}} +% sublargesize is the same as large - 12pt +\def\sublargesize{\@setfontsize{\sublargesize}{12}{14pt}} +\def\large{\@setfontsize{\large}{12}{14pt}} +\def\Large{\@setfontsize{\Large}{14}{17pt}} +\def\LARGE{\@setfontsize{\LARGE}{17}{20pt}} +\def\huge{\@setfontsize{\huge}{20}{24pt}} +\def\Huge{\@setfontsize{\Huge}{24}{28pt}} +\fi + + +% Check if we have selected 12 points +\ifx\CLASSOPTIONpt\@IEEEptsizetwelve +\typeout{-- This is a 12 point document.} +\def\normalsize{\@setfontsize{\normalsize}{12}{13.92pt}}% +\setlength{\@IEEEnormalsizeunitybaselineskip}{13.92pt}% +\normalsize +\abovedisplayskip 1.5ex plus6pt minus4pt% +\belowdisplayskip \abovedisplayskip% +\abovedisplayshortskip 0pt plus6pt% +\belowdisplayshortskip 1.5ex plus6pt minus4pt +\def\small{\@setfontsize{\small}{10}{12pt}} +\def\footnotesize{\@setfontsize{\footnotesize}{9}{10.5pt}} +\def\scriptsize{\@setfontsize{\scriptsize}{8}{9pt}} +\def\tiny{\@setfontsize{\tiny}{6}{7pt}} +% sublargesize is the same as large - 14pt +\def\sublargesize{\@setfontsize{\sublargesize}{14}{17pt}} +\def\large{\@setfontsize{\large}{14}{17pt}} +\def\Large{\@setfontsize{\Large}{17}{20pt}} +\def\LARGE{\@setfontsize{\LARGE}{20}{24pt}} +\def\huge{\@setfontsize{\huge}{22}{26pt}} +\def\Huge{\@setfontsize{\Huge}{24}{28pt}} +\fi + + +% V1.6 The Computer Modern Fonts will issue a substitution warning for +% 24pt titles (24.88pt is used instead) increase the substitution +% tolerance to turn off this warning +\def\fontsubfuzz{.9pt} +% However, the default (and correct) Times font will scale exactly as needed. + + +% warn the user in case they forget to use the 9pt option with +% technote +\ifCLASSOPTIONtechnote% + \ifx\CLASSOPTIONpt\@IEEEptsizenine\else% + \typeout{** ATTENTION: Technotes are normally 9pt documents.}% + \fi% +\fi + + +% V1.7 +% Improved \textunderscore to provide a much better fake _ when used with +% OT1 encoding. Under OT1, detect use of pcr or cmtt \ttfamily and use +% available true _ glyph for those two typewriter fonts. +\def\@IEEEstringptm{ptm} % Times Roman family +\def\@IEEEstringppl{ppl} % Palatino Roman family +\def\@IEEEstringphv{phv} % Helvetica Sans Serif family +\def\@IEEEstringpcr{pcr} % Courier typewriter family +\def\@IEEEstringcmtt{cmtt} % Computer Modern typewriter family +\DeclareTextCommandDefault{\textunderscore}{\leavevmode +\ifx\f@family\@IEEEstringpcr\string_\else +\ifx\f@family\@IEEEstringcmtt\string_\else +\ifx\f@family\@IEEEstringptm\kern 0em\vbox{\hrule\@width 0.5em\@height 0.5pt\kern -0.3ex}\else +\ifx\f@family\@IEEEstringppl\kern 0em\vbox{\hrule\@width 0.5em\@height 0.5pt\kern -0.3ex}\else +\ifx\f@family\@IEEEstringphv\kern -0.03em\vbox{\hrule\@width 0.62em\@height 0.52pt\kern -0.33ex}\kern -0.03em\else +\kern 0.09em\vbox{\hrule\@width 0.6em\@height 0.44pt\kern -0.63pt\kern -0.42ex}\kern 0.09em\fi\fi\fi\fi\fi\relax} + + + + +% set the default \baselinestretch +\def\baselinestretch{1} +\ifCLASSOPTIONdraftcls + \def\baselinestretch{1.5}% default baselinestretch for draft modes +\fi + + +% process CLASSINPUT baselinestretch +\ifx\CLASSINPUTbaselinestretch\@IEEEundefined +\else + \edef\baselinestretch{\CLASSINPUTbaselinestretch} % user CLASSINPUT override + \typeout{** ATTENTION: Overriding \string\baselinestretch\space to + \baselinestretch\space via \string\CLASSINPUT.} +\fi + +\normalsize % make \baselinestretch take affect + + + + +% store the normalsize baselineskip +\newdimen\CLASSINFOnormalsizebaselineskip +\CLASSINFOnormalsizebaselineskip=\baselineskip\relax +% and the normalsize unity (baselinestretch=1) baselineskip +% we could save a register by giving the user access to +% \@IEEEnormalsizeunitybaselineskip. However, let's protect +% its read only internal status +\newdimen\CLASSINFOnormalsizeunitybaselineskip +\CLASSINFOnormalsizeunitybaselineskip=\@IEEEnormalsizeunitybaselineskip\relax +% store the nominal value of jot +\newdimen\IEEEnormaljot +\IEEEnormaljot=0.25\baselineskip\relax + +% set \jot +\jot=\IEEEnormaljot\relax + + + + +% V1.6, we are now going to fine tune the interword spacing +% The default interword glue for Times under TeX appears to use a +% nominal interword spacing of 25% (relative to the font size, i.e., 1em) +% a maximum of 40% and a minimum of 19%. +% For example, 10pt text uses an interword glue of: +% +% 2.5pt plus 1.49998pt minus 0.59998pt +% +% However, IEEE allows for a more generous range which reduces the need +% for hyphenation, especially for two column text. Furthermore, IEEE +% tends to use a little bit more nominal space between the words. +% IEEE's interword spacing percentages appear to be: +% 35% nominal +% 23% minimum +% 50% maximum +% (They may even be using a tad more for the largest fonts such as 24pt.) +% +% for bold text, IEEE increases the spacing a little more: +% 37.5% nominal +% 23% minimum +% 55% maximum + +% here are the interword spacing ratios we'll use +% for medium (normal weight) +\def\@IEEEinterspaceratioM{0.35} +\def\@IEEEinterspaceMINratioM{0.23} +\def\@IEEEinterspaceMAXratioM{0.50} + +% for bold +\def\@IEEEinterspaceratioB{0.375} +\def\@IEEEinterspaceMINratioB{0.23} +\def\@IEEEinterspaceMAXratioB{0.55} + + +% command to revise the interword spacing for the current font under TeX: +% \fontdimen2 = nominal interword space +% \fontdimen3 = interword stretch +% \fontdimen4 = interword shrink +% since all changes to the \fontdimen are global, we can enclose these commands +% in braces to confine any font attribute or length changes +\def\@@@IEEEsetfontdimens#1#2#3{{% +\setlength{\@IEEEtrantmpdimenB}{\f@size pt}% grab the font size in pt, could use 1em instead. +\setlength{\@IEEEtrantmpdimenA}{#1\@IEEEtrantmpdimenB}% +\fontdimen2\font=\@IEEEtrantmpdimenA\relax +\addtolength{\@IEEEtrantmpdimenA}{-#2\@IEEEtrantmpdimenB}% +\fontdimen3\font=-\@IEEEtrantmpdimenA\relax +\setlength{\@IEEEtrantmpdimenA}{#1\@IEEEtrantmpdimenB}% +\addtolength{\@IEEEtrantmpdimenA}{-#3\@IEEEtrantmpdimenB}% +\fontdimen4\font=\@IEEEtrantmpdimenA\relax}} + +% revise the interword spacing for each font weight +\def\@@IEEEsetfontdimens{{% +\mdseries +\@@@IEEEsetfontdimens{\@IEEEinterspaceratioM}{\@IEEEinterspaceMAXratioM}{\@IEEEinterspaceMINratioM}% +\bfseries +\@@@IEEEsetfontdimens{\@IEEEinterspaceratioB}{\@IEEEinterspaceMAXratioB}{\@IEEEinterspaceMINratioB}% +}} + +% revise the interword spacing for each font shape +% \slshape is not often used for IEEE work and is not altered here. The \scshape caps are +% already a tad too large in the free LaTeX fonts (as compared to what IEEE uses) so we +% won't alter these either. +\def\@IEEEsetfontdimens{{% +\normalfont +\@@IEEEsetfontdimens +\normalfont\itshape +\@@IEEEsetfontdimens +}} + +% command to revise the interword spacing for each font size (and shape +% and weight). Only the \rmfamily is done here as \ttfamily uses a +% fixed spacing and \sffamily is not used as the main text of IEEE papers. +\def\@IEEEtunefonts{{\selectfont\rmfamily +\tiny\@IEEEsetfontdimens +\scriptsize\@IEEEsetfontdimens +\footnotesize\@IEEEsetfontdimens +\small\@IEEEsetfontdimens +\normalsize\@IEEEsetfontdimens +\sublargesize\@IEEEsetfontdimens +\large\@IEEEsetfontdimens +\LARGE\@IEEEsetfontdimens +\huge\@IEEEsetfontdimens +\Huge\@IEEEsetfontdimens}} + +% if the nofonttune class option is not given, revise the interword spacing +% now - in case IEEEtran makes any default length measurements, and make +% sure all the default fonts are loaded +\ifCLASSOPTIONnofonttune\else +\@IEEEtunefonts +\fi + +% and again at the start of the document in case the user loaded different fonts +\AtBeginDocument{\ifCLASSOPTIONnofonttune\else\@IEEEtunefonts\fi} + + + +% V1.6 +% LaTeX is a little to quick to use hyphenations +% So, we increase the penalty for their use and raise +% the badness level that triggers an underfull hbox +% warning. The author may still have to tweak things, +% but the appearance will be much better "right out +% of the box" than that under V1.5 and prior. +% TeX default is 50 +\hyphenpenalty=750 +% If we didn't adjust the interword spacing, 2200 might be better. +% The TeX default is 1000 +\hbadness=1350 +% IEEE does not use extra spacing after punctuation +\frenchspacing + +% V1.7 increase this a tad to discourage equation breaks +\binoppenalty=1000 % default 700 +\relpenalty=800 % default 500 + + +% margin note stuff +\marginparsep 10pt +\marginparwidth 20pt +\marginparpush 25pt + + +% if things get too close, go ahead and let them touch +\lineskip 0pt +\normallineskip 0pt +\lineskiplimit 0pt +\normallineskiplimit 0pt + +% The distance from the lower edge of the text body to the +% footline +\footskip 0.4in + +% normally zero, should be relative to font height. +% put in a little rubber to help stop some bad breaks (underfull vboxes) +\parskip 0ex plus 0.2ex minus 0.1ex +\ifCLASSOPTIONconference +\parskip 6pt plus 2pt minus 1pt +\fi + +\parindent 1.0em +\ifCLASSOPTIONconference +\parindent 14.45pt +\fi + +\topmargin -49.0pt +\headheight 12pt +\headsep 0.25in + +% use the normal font baselineskip +% so that \topskip is unaffected by changes in \baselinestretch +\topskip=\@IEEEnormalsizeunitybaselineskip +\textheight 58pc % 9.63in, 696pt +% Tweak textheight to a perfect integer number of lines/page. +% The normal baselineskip for each document point size is used +% to determine these values. +\ifx\CLASSOPTIONpt\@IEEEptsizenine\textheight=63\@IEEEnormalsizeunitybaselineskip\fi % 63 lines/page +\ifx\CLASSOPTIONpt\@IEEEptsizeten\textheight=58\@IEEEnormalsizeunitybaselineskip\fi % 58 lines/page +\ifx\CLASSOPTIONpt\@IEEEptsizeeleven\textheight=52\@IEEEnormalsizeunitybaselineskip\fi % 52 lines/page +\ifx\CLASSOPTIONpt\@IEEEptsizetwelve\textheight=50\@IEEEnormalsizeunitybaselineskip\fi % 50 lines/page + + +\columnsep 1.5pc +\textwidth 184.2mm + + +% the default side margins are equal +\if@IEEEusingAfourpaper +\oddsidemargin 14.32mm +\evensidemargin 14.32mm +\else +\oddsidemargin 0.680in +\evensidemargin 0.680in +\fi +% compensate for LaTeX's 1in offset +\addtolength{\oddsidemargin}{-1in} +\addtolength{\evensidemargin}{-1in} + + + +% adjust margins for conference mode +\ifCLASSOPTIONconference + \topmargin -0.25in + % we retain the reserved, but unused space for headers + \addtolength{\topmargin}{-\headheight} + \addtolength{\topmargin}{-\headsep} + \textheight 9.25in % The standard for conferences (668.4975pt) + % Tweak textheight to a perfect integer number of lines/page. + \ifx\CLASSOPTIONpt\@IEEEptsizenine\textheight=61\@IEEEnormalsizeunitybaselineskip\fi % 61 lines/page + \ifx\CLASSOPTIONpt\@IEEEptsizeten\textheight=62\@IEEEnormalsizeunitybaselineskip\fi % 62 lines/page + \ifx\CLASSOPTIONpt\@IEEEptsizeeleven\textheight=50\@IEEEnormalsizeunitybaselineskip\fi % 50 lines/page + \ifx\CLASSOPTIONpt\@IEEEptsizetwelve\textheight=48\@IEEEnormalsizeunitybaselineskip\fi % 48 lines/page +\fi + + +% compsoc conference +\ifCLASSOPTIONcompsoc +\ifCLASSOPTIONconference + % compsoc conference use a larger value for columnsep + \columnsep 0.375in + % compsoc conferences want 1in top margin, 1.125in bottom margin + \topmargin 0in + \addtolength{\topmargin}{-6pt}% we tweak this a tad to better comply with top of line stuff + % we retain the reserved, but unused space for headers + \addtolength{\topmargin}{-\headheight} + \addtolength{\topmargin}{-\headsep} + \textheight 8.875in % (641.39625pt) + % Tweak textheight to a perfect integer number of lines/page. + \ifx\CLASSOPTIONpt\@IEEEptsizenine\textheight=58\@IEEEnormalsizeunitybaselineskip\fi % 58 lines/page + \ifx\CLASSOPTIONpt\@IEEEptsizeten\textheight=53\@IEEEnormalsizeunitybaselineskip\fi % 53 lines/page + \ifx\CLASSOPTIONpt\@IEEEptsizeeleven\textheight=48\@IEEEnormalsizeunitybaselineskip\fi % 48 lines/page + \ifx\CLASSOPTIONpt\@IEEEptsizetwelve\textheight=46\@IEEEnormalsizeunitybaselineskip\fi % 46 lines/page + \textwidth 6.5in + % the default side margins are equal + \if@IEEEusingAfourpaper + \oddsidemargin 22.45mm + \evensidemargin 22.45mm + \else + \oddsidemargin 1in + \evensidemargin 1in + \fi + % compensate for LaTeX's 1in offset + \addtolength{\oddsidemargin}{-1in} + \addtolength{\evensidemargin}{-1in} +\fi\fi + + + +% draft mode settings override that of all other modes +% provides a nice 1in margin all around the paper and extra +% space between the lines for editor's comments +\ifCLASSOPTIONdraftcls + % want 1in from top of paper to text + \setlength{\topmargin}{-\headsep}% + \addtolength{\topmargin}{-\headheight}% + % we want 1in side margins regardless of paper type + \oddsidemargin 0in + \evensidemargin 0in + % set the text width + \setlength{\textwidth}{\paperwidth}% + \addtolength{\textwidth}{-2.0in}% + \setlength{\textheight}{\paperheight}% + \addtolength{\textheight}{-2.0in}% + % digitize textheight to be an integer number of lines. + % this may cause the bottom margin to be off a tad + \addtolength{\textheight}{-1\topskip}% + \divide\textheight by \baselineskip% + \multiply\textheight by \baselineskip% + \addtolength{\textheight}{\topskip}% +\fi + + + +% process CLASSINPUT inner/outer margin +% if inner margin defined, but outer margin not, set outer to inner. +\ifx\CLASSINPUTinnersidemargin\@IEEEundefined +\else + \ifx\CLASSINPUToutersidemargin\@IEEEundefined + \edef\CLASSINPUToutersidemargin{\CLASSINPUTinnersidemargin} + \fi +\fi + +\ifx\CLASSINPUToutersidemargin\@IEEEundefined +\else + % if outer margin defined, but inner margin not, set inner to outer. + \ifx\CLASSINPUTinnersidemargin\@IEEEundefined + \edef\CLASSINPUTinnersidemargin{\CLASSINPUToutersidemargin} + \fi + \setlength{\oddsidemargin}{\CLASSINPUTinnersidemargin} + \ifCLASSOPTIONtwoside + \setlength{\evensidemargin}{\CLASSINPUToutersidemargin} + \else + \setlength{\evensidemargin}{\CLASSINPUTinnersidemargin} + \fi + \addtolength{\oddsidemargin}{-1in} + \addtolength{\evensidemargin}{-1in} + \setlength{\textwidth}{\paperwidth} + \addtolength{\textwidth}{-\CLASSINPUTinnersidemargin} + \addtolength{\textwidth}{-\CLASSINPUToutersidemargin} + \typeout{** ATTENTION: Overriding inner side margin to \CLASSINPUTinnersidemargin\space and + outer side margin to \CLASSINPUToutersidemargin\space via \string\CLASSINPUT.} +\fi + + + +% process CLASSINPUT top/bottom text margin +% if toptext margin defined, but bottomtext margin not, set bottomtext to toptext margin +\ifx\CLASSINPUTtoptextmargin\@IEEEundefined +\else + \ifx\CLASSINPUTbottomtextmargin\@IEEEundefined + \edef\CLASSINPUTbottomtextmargin{\CLASSINPUTtoptextmargin} + \fi +\fi + +\ifx\CLASSINPUTbottomtextmargin\@IEEEundefined +\else + % if bottomtext margin defined, but toptext margin not, set toptext to bottomtext margin + \ifx\CLASSINPUTtoptextmargin\@IEEEundefined + \edef\CLASSINPUTtoptextmargin{\CLASSINPUTbottomtextmargin} + \fi + \setlength{\topmargin}{\CLASSINPUTtoptextmargin} + \addtolength{\topmargin}{-1in} + \addtolength{\topmargin}{-\headheight} + \addtolength{\topmargin}{-\headsep} + \setlength{\textheight}{\paperheight} + \addtolength{\textheight}{-\CLASSINPUTtoptextmargin} + \addtolength{\textheight}{-\CLASSINPUTbottomtextmargin} + % in the default format we use the normal baselineskip as topskip + % we only need 0.7 of this to clear typical top text and we need + % an extra 0.3 spacing at the bottom for descenders. This will + % correct for both. + \addtolength{\topmargin}{-0.3\@IEEEnormalsizeunitybaselineskip} + \typeout{** ATTENTION: Overriding top text margin to \CLASSINPUTtoptextmargin\space and + bottom text margin to \CLASSINPUTbottomtextmargin\space via \string\CLASSINPUT.} +\fi + + + + + + + +% LIST SPACING CONTROLS + +% Controls the amount of EXTRA spacing +% above and below \trivlist +% Both \list and IED lists override this. +% However, \trivlist will use this as will most +% things built from \trivlist like the \center +% environment. +\topsep 0.5\baselineskip + +% Controls the additional spacing around lists preceded +% or followed by blank lines. IEEE does not increase +% spacing before or after paragraphs so it is set to zero. +% \z@ is the same as zero, but faster. +\partopsep \z@ + +% Controls the spacing between paragraphs in lists. +% IEEE does not increase spacing before or after paragraphs +% so this is also zero. +% With IEEEtran.cls, global changes to +% this value DO affect lists (but not IED lists). +\parsep \z@ + +% Controls the extra spacing between list items. +% IEEE does not put extra spacing between items. +% With IEEEtran.cls, global changes to this value DO affect +% lists (but not IED lists). +\itemsep \z@ + +% \itemindent is the amount to indent the FIRST line of a list +% item. It is auto set to zero within the \list environment. To alter +% it, you have to do so when you call the \list. +% However, IEEE uses this for the theorem environment +% There is an alternative value for this near \leftmargini below +\itemindent -1em + +% \leftmargin, the spacing from the left margin of the main text to +% the left of the main body of a list item is set by \list. +% Hence this statement does nothing for lists. +% But, quote and verse do use it for indention. +\leftmargin 2em + +% we retain this stuff from the older IEEEtran.cls so that \list +% will work the same way as before. However, itemize, enumerate and +% description (IED) could care less about what these are as they +% all are overridden. +\leftmargini 2em +%\itemindent 2em % Alternative values: sometimes used. +%\leftmargini 0em +\leftmarginii 1em +\leftmarginiii 1.5em +\leftmarginiv 1.5em +\leftmarginv 1.0em +\leftmarginvi 1.0em +\labelsep 0.5em +\labelwidth \z@ + + +% The old IEEEtran.cls behavior of \list is retained. +% However, the new V1.3 IED list environments override all the +% @list stuff (\@listX is called within \list for the +% appropriate level just before the user's list_decl is called). +% \topsep is now 2pt as IEEE puts a little extra space around +% lists - used by those non-IED macros that depend on \list. +% Note that \parsep and \itemsep are not redefined as in +% the sizexx.clo \@listX (which article.cls uses) so global changes +% of these values DO affect \list +% +\def\@listi{\leftmargin\leftmargini \topsep 2pt plus 1pt minus 1pt} +\let\@listI\@listi +\def\@listii{\leftmargin\leftmarginii\labelwidth\leftmarginii% + \advance\labelwidth-\labelsep \topsep 2pt} +\def\@listiii{\leftmargin\leftmarginiii\labelwidth\leftmarginiii% + \advance\labelwidth-\labelsep \topsep 2pt} +\def\@listiv{\leftmargin\leftmarginiv\labelwidth\leftmarginiv% + \advance\labelwidth-\labelsep \topsep 2pt} +\def\@listv{\leftmargin\leftmarginv\labelwidth\leftmarginv% + \advance\labelwidth-\labelsep \topsep 2pt} +\def\@listvi{\leftmargin\leftmarginvi\labelwidth\leftmarginvi% + \advance\labelwidth-\labelsep \topsep 2pt} + + +% IEEE uses 5) not 5. +\def\labelenumi{\theenumi)} \def\theenumi{\arabic{enumi}} + +% IEEE uses a) not (a) +\def\labelenumii{\theenumii)} \def\theenumii{\alph{enumii}} + +% IEEE uses iii) not iii. +\def\labelenumiii{\theenumiii)} \def\theenumiii{\roman{enumiii}} + +% IEEE uses A) not A. +\def\labelenumiv{\theenumiv)} \def\theenumiv{\Alph{enumiv}} + +% exactly the same as in article.cls +\def\p@enumii{\theenumi} +\def\p@enumiii{\theenumi(\theenumii)} +\def\p@enumiv{\p@enumiii\theenumiii} + +% itemized list label styles +\def\labelitemi{$\bullet$} +\def\labelitemii{$\circ$} +\def\labelitemiii{\vrule height 0.8ex depth -0.2ex width 0.6ex} +\def\labelitemiv{$\ast$} + + + +% **** V1.3 ENHANCEMENTS **** +% Itemize, Enumerate and Description (IED) List Controls +% *************************** +% +% +% IEEE seems to use at least two different values by +% which ITEMIZED list labels are indented to the right +% For The Journal of Lightwave Technology (JLT) and The Journal +% on Selected Areas in Communications (JSAC), they tend to use +% an indention equal to \parindent. For Transactions on Communications +% they tend to indent ITEMIZED lists a little more--- 1.3\parindent. +% We'll provide both values here for you so that you can choose +% which one you like in your document using a command such as: +% setlength{\IEEEilabelindent}{\IEEEilabelindentB} +\newdimen\IEEEilabelindentA +\IEEEilabelindentA \parindent + +\newdimen\IEEEilabelindentB +\IEEEilabelindentB 1.3\parindent +% However, we'll default to using \parindent +% which makes more sense to me +\newdimen\IEEEilabelindent +\IEEEilabelindent \IEEEilabelindentA + + +% This controls the default amount the enumerated list labels +% are indented to the right. +% Normally, this is the same as the paragraph indention +\newdimen\IEEEelabelindent +\IEEEelabelindent \parindent + +% This controls the default amount the description list labels +% are indented to the right. +% Normally, this is the same as the paragraph indention +\newdimen\IEEEdlabelindent +\IEEEdlabelindent \parindent + +% This is the value actually used within the IED lists. +% The IED environments automatically set its value to +% one of the three values above, so global changes do +% not have any effect +\newdimen\IEEElabelindent +\IEEElabelindent \parindent + +% The actual amount labels will be indented is +% \IEEElabelindent multiplied by the factor below +% corresponding to the level of nesting depth +% This provides a means by which the user can +% alter the effective \IEEElabelindent for deeper +% levels +% There may not be such a thing as correct "standard IEEE" +% values. What IEEE actually does may depend on the specific +% circumstances. +% The first list level almost always has full indention. +% The second levels I've seen have only 75% of the normal indentation +% Three level or greater nestings are very rare. I am guessing +% that they don't use any indentation. +\def\IEEElabelindentfactori{1.0} % almost always one +\def\IEEElabelindentfactorii{0.75} % 0.0 or 1.0 may be used in some cases +\def\IEEElabelindentfactoriii{0.0} % 0.75? 0.5? 0.0? +\def\IEEElabelindentfactoriv{0.0} +\def\IEEElabelindentfactorv{0.0} +\def\IEEElabelindentfactorvi{0.0} + +% value actually used within IED lists, it is auto +% set to one of the 6 values above +% global changes here have no effect +\def\IEEElabelindentfactor{1.0} + +% This controls the default spacing between the end of the IED +% list labels and the list text, when normal text is used for +% the labels. +\newdimen\IEEEiednormlabelsep +\IEEEiednormlabelsep \parindent + +% This controls the default spacing between the end of the IED +% list labels and the list text, when math symbols are used for +% the labels (nomenclature lists). IEEE usually increases the +% spacing in these cases +\newdimen\IEEEiedmathlabelsep +\IEEEiedmathlabelsep 1.2em + +% This controls the extra vertical separation put above and +% below each IED list. IEEE usually puts a little extra spacing +% around each list. However, this spacing is barely noticeable. +\newskip\IEEEiedtopsep +\IEEEiedtopsep 2pt plus 1pt minus 1pt + + +% This command is executed within each IED list environment +% at the beginning of the list. You can use this to set the +% parameters for some/all your IED list(s) without disturbing +% global parameters that affect things other than lists. +% i.e., renewcommand{\IEEEiedlistdecl}{\setlength{\labelsep}{5em}} +% will alter the \labelsep for the next list(s) until +% \IEEEiedlistdecl is redefined. +\def\IEEEiedlistdecl{\relax} + +% This command provides an easy way to set \leftmargin based +% on the \labelwidth, \labelsep and the argument \IEEElabelindent +% Usage: \IEEEcalcleftmargin{width-to-indent-the-label} +% output is in the \leftmargin variable, i.e., effectively: +% \leftmargin = argument + \labelwidth + \labelsep +% Note controlled spacing here, shield end of lines with % +\def\IEEEcalcleftmargin#1{\setlength{\leftmargin}{#1}% +\addtolength{\leftmargin}{\labelwidth}% +\addtolength{\leftmargin}{\labelsep}} + +% This command provides an easy way to set \labelwidth to the +% width of the given text. It is the same as +% \settowidth{\labelwidth}{label-text} +% and useful as a shorter alternative. +% Typically used to set \labelwidth to be the width +% of the longest label in the list +\def\IEEEsetlabelwidth#1{\settowidth{\labelwidth}{#1}} + +% When this command is executed, IED lists will use the +% IEEEiedmathlabelsep label separation rather than the normal +% spacing. To have an effect, this command must be executed via +% the \IEEEiedlistdecl or within the option of the IED list +% environments. +\def\IEEEusemathlabelsep{\setlength{\labelsep}{\IEEEiedmathlabelsep}} + +% A flag which controls whether the IED lists automatically +% calculate \leftmargin from \IEEElabelindent, \labelwidth and \labelsep +% Useful if you want to specify your own \leftmargin +% This flag must be set (\IEEEnocalcleftmargintrue or \IEEEnocalcleftmarginfalse) +% via the \IEEEiedlistdecl or within the option of the IED list +% environments to have an effect. +\newif\ifIEEEnocalcleftmargin +\IEEEnocalcleftmarginfalse + +% A flag which controls whether \IEEElabelindent is multiplied by +% the \IEEElabelindentfactor for each list level. +% This flag must be set via the \IEEEiedlistdecl or within the option +% of the IED list environments to have an effect. +\newif\ifIEEEnolabelindentfactor +\IEEEnolabelindentfactorfalse + + +% internal variable to indicate type of IED label +% justification +% 0 - left; 1 - center; 2 - right +\def\@IEEEiedjustify{0} + + +% commands to allow the user to control IED +% label justifications. Use these commands within +% the IED environment option or in the \IEEEiedlistdecl +% Note that changing the normal list justifications +% is nonstandard and IEEE may not like it if you do so! +% I include these commands as they may be helpful to +% those who are using these enhanced list controls for +% other non-IEEE related LaTeX work. +% itemize and enumerate automatically default to right +% justification, description defaults to left. +\def\IEEEiedlabeljustifyl{\def\@IEEEiedjustify{0}}%left +\def\IEEEiedlabeljustifyc{\def\@IEEEiedjustify{1}}%center +\def\IEEEiedlabeljustifyr{\def\@IEEEiedjustify{2}}%right + + + + +% commands to save to and restore from the list parameter copies +% this allows us to set all the list parameters within +% the list_decl and prevent \list (and its \@list) +% from overriding any of our parameters +% V1.6 use \edefs instead of dimen's to conserve dimen registers +% Note controlled spacing here, shield end of lines with % +\def\@IEEEsavelistparams{\edef\@IEEEiedtopsep{\the\topsep}% +\edef\@IEEEiedlabelwidth{\the\labelwidth}% +\edef\@IEEEiedlabelsep{\the\labelsep}% +\edef\@IEEEiedleftmargin{\the\leftmargin}% +\edef\@IEEEiedpartopsep{\the\partopsep}% +\edef\@IEEEiedparsep{\the\parsep}% +\edef\@IEEEieditemsep{\the\itemsep}% +\edef\@IEEEiedrightmargin{\the\rightmargin}% +\edef\@IEEEiedlistparindent{\the\listparindent}% +\edef\@IEEEieditemindent{\the\itemindent}} + +% Note controlled spacing here +\def\@IEEErestorelistparams{\topsep\@IEEEiedtopsep\relax% +\labelwidth\@IEEEiedlabelwidth\relax% +\labelsep\@IEEEiedlabelsep\relax% +\leftmargin\@IEEEiedleftmargin\relax% +\partopsep\@IEEEiedpartopsep\relax% +\parsep\@IEEEiedparsep\relax% +\itemsep\@IEEEieditemsep\relax% +\rightmargin\@IEEEiedrightmargin\relax% +\listparindent\@IEEEiedlistparindent\relax% +\itemindent\@IEEEieditemindent\relax} + + +% v1.6b provide original LaTeX IED list environments +% note that latex.ltx defines \itemize and \enumerate, but not \description +% which must be created by the base classes +% save original LaTeX itemize and enumerate +\let\LaTeXitemize\itemize +\let\endLaTeXitemize\enditemize +\let\LaTeXenumerate\enumerate +\let\endLaTeXenumerate\endenumerate + +% provide original LaTeX description environment from article.cls +\newenvironment{LaTeXdescription} + {\list{}{\labelwidth\z@ \itemindent-\leftmargin + \let\makelabel\descriptionlabel}} + {\endlist} +\newcommand*\descriptionlabel[1]{\hspace\labelsep + \normalfont\bfseries #1} + + +% override LaTeX's default IED lists +\def\itemize{\@IEEEitemize} +\def\enditemize{\@endIEEEitemize} +\def\enumerate{\@IEEEenumerate} +\def\endenumerate{\@endIEEEenumerate} +\def\description{\@IEEEdescription} +\def\enddescription{\@endIEEEdescription} + +% provide the user with aliases - may help those using packages that +% override itemize, enumerate, or description +\def\IEEEitemize{\@IEEEitemize} +\def\endIEEEitemize{\@endIEEEitemize} +\def\IEEEenumerate{\@IEEEenumerate} +\def\endIEEEenumerate{\@endIEEEenumerate} +\def\IEEEdescription{\@IEEEdescription} +\def\endIEEEdescription{\@endIEEEdescription} + + +% V1.6 we want to keep the IEEEtran IED list definitions as our own internal +% commands so they are protected against redefinition +\def\@IEEEitemize{\@ifnextchar[{\@@IEEEitemize}{\@@IEEEitemize[\relax]}} +\def\@IEEEenumerate{\@ifnextchar[{\@@IEEEenumerate}{\@@IEEEenumerate[\relax]}} +\def\@IEEEdescription{\@ifnextchar[{\@@IEEEdescription}{\@@IEEEdescription[\relax]}} +\def\@endIEEEitemize{\endlist} +\def\@endIEEEenumerate{\endlist} +\def\@endIEEEdescription{\endlist} + + +% DO NOT ALLOW BLANK LINES TO BE IN THESE IED ENVIRONMENTS +% AS THIS WILL FORCE NEW PARAGRAPHS AFTER THE IED LISTS +% IEEEtran itemized list MDS 1/2001 +% Note controlled spacing here, shield end of lines with % +\def\@@IEEEitemize[#1]{% + \ifnum\@itemdepth>3\relax\@toodeep\else% + \ifnum\@listdepth>5\relax\@toodeep\else% + \advance\@itemdepth\@ne% + \edef\@itemitem{labelitem\romannumeral\the\@itemdepth}% + % get the labelindentfactor for this level + \advance\@listdepth\@ne% we need to know what the level WILL be + \edef\IEEElabelindentfactor{\csname IEEElabelindentfactor\romannumeral\the\@listdepth\endcsname}% + \advance\@listdepth-\@ne% undo our increment + \def\@IEEEiedjustify{2}% right justified labels are default + % set other defaults + \IEEEnocalcleftmarginfalse% + \IEEEnolabelindentfactorfalse% + \topsep\IEEEiedtopsep% + \IEEElabelindent\IEEEilabelindent% + \labelsep\IEEEiednormlabelsep% + \partopsep 0ex% + \parsep 0ex% + \itemsep \parskip% + \rightmargin 0em% + \listparindent 0em% + \itemindent 0em% + % calculate the label width + % the user can override this later if + % they specified a \labelwidth + \settowidth{\labelwidth}{\csname labelitem\romannumeral\the\@itemdepth\endcsname}% + \@IEEEsavelistparams% save our list parameters + \list{\csname\@itemitem\endcsname}{% + \@IEEErestorelistparams% override any list{} changes + % to our globals + \let\makelabel\@IEEEiedmakelabel% v1.6b setup \makelabel + \IEEEiedlistdecl% let user alter parameters + #1\relax% + % If the user has requested not to use the + % labelindent factor, don't revise \labelindent + \ifIEEEnolabelindentfactor\relax% + \else\IEEElabelindent=\IEEElabelindentfactor\labelindent% + \fi% + % Unless the user has requested otherwise, + % calculate our left margin based + % on \IEEElabelindent, \labelwidth and + % \labelsep + \ifIEEEnocalcleftmargin\relax% + \else\IEEEcalcleftmargin{\IEEElabelindent}% + \fi}\fi\fi}% + + +% DO NOT ALLOW BLANK LINES TO BE IN THESE IED ENVIRONMENTS +% AS THIS WILL FORCE NEW PARAGRAPHS AFTER THE IED LISTS +% IEEEtran enumerate list MDS 1/2001 +% Note controlled spacing here, shield end of lines with % +\def\@@IEEEenumerate[#1]{% + \ifnum\@enumdepth>3\relax\@toodeep\else% + \ifnum\@listdepth>5\relax\@toodeep\else% + \advance\@enumdepth\@ne% + \edef\@enumctr{enum\romannumeral\the\@enumdepth}% + % get the labelindentfactor for this level + \advance\@listdepth\@ne% we need to know what the level WILL be + \edef\IEEElabelindentfactor{\csname IEEElabelindentfactor\romannumeral\the\@listdepth\endcsname}% + \advance\@listdepth-\@ne% undo our increment + \def\@IEEEiedjustify{2}% right justified labels are default + % set other defaults + \IEEEnocalcleftmarginfalse% + \IEEEnolabelindentfactorfalse% + \topsep\IEEEiedtopsep% + \IEEElabelindent\IEEEelabelindent% + \labelsep\IEEEiednormlabelsep% + \partopsep 0ex% + \parsep 0ex% + \itemsep 0ex% + \rightmargin 0em% + \listparindent 0em% + \itemindent 0em% + % calculate the label width + % We'll set it to the width suitable for all labels using + % normalfont 1) to 9) + % The user can override this later + \settowidth{\labelwidth}{9)}% + \@IEEEsavelistparams% save our list parameters + \list{\csname label\@enumctr\endcsname}{\usecounter{\@enumctr}% + \@IEEErestorelistparams% override any list{} changes + % to our globals + \let\makelabel\@IEEEiedmakelabel% v1.6b setup \makelabel + \IEEEiedlistdecl% let user alter parameters + #1\relax% + % If the user has requested not to use the + % IEEElabelindent factor, don't revise \IEEElabelindent + \ifIEEEnolabelindentfactor\relax% + \else\IEEElabelindent=\IEEElabelindentfactor\IEEElabelindent% + \fi% + % Unless the user has requested otherwise, + % calculate our left margin based + % on \IEEElabelindent, \labelwidth and + % \labelsep + \ifIEEEnocalcleftmargin\relax% + \else\IEEEcalcleftmargin{\IEEElabelindent}% + \fi}\fi\fi}% + + +% DO NOT ALLOW BLANK LINES TO BE IN THESE IED ENVIRONMENTS +% AS THIS WILL FORCE NEW PARAGRAPHS AFTER THE IED LISTS +% IEEEtran description list MDS 1/2001 +% Note controlled spacing here, shield end of lines with % +\def\@@IEEEdescription[#1]{% + \ifnum\@listdepth>5\relax\@toodeep\else% + % get the labelindentfactor for this level + \advance\@listdepth\@ne% we need to know what the level WILL be + \edef\IEEElabelindentfactor{\csname IEEElabelindentfactor\romannumeral\the\@listdepth\endcsname}% + \advance\@listdepth-\@ne% undo our increment + \def\@IEEEiedjustify{0}% left justified labels are default + % set other defaults + \IEEEnocalcleftmarginfalse% + \IEEEnolabelindentfactorfalse% + \topsep\IEEEiedtopsep% + \IEEElabelindent\IEEEdlabelindent% + % assume normal labelsep + \labelsep\IEEEiednormlabelsep% + \partopsep 0ex% + \parsep 0ex% + \itemsep 0ex% + \rightmargin 0em% + \listparindent 0em% + \itemindent 0em% + % Bogus label width in case the user forgets + % to set it. + % TIP: If you want to see what a variable's width is you + % can use the TeX command \showthe\width-variable to + % display it on the screen during compilation + % (This might be helpful to know when you need to find out + % which label is the widest) + \settowidth{\labelwidth}{Hello}% + \@IEEEsavelistparams% save our list parameters + \list{}{\@IEEErestorelistparams% override any list{} changes + % to our globals + \let\makelabel\@IEEEiedmakelabel% v1.6b setup \makelabel + \IEEEiedlistdecl% let user alter parameters + #1\relax% + % If the user has requested not to use the + % labelindent factor, don't revise \IEEElabelindent + \ifIEEEnolabelindentfactor\relax% + \else\IEEElabelindent=\IEEElabelindentfactor\IEEElabelindent% + \fi% + % Unless the user has requested otherwise, + % calculate our left margin based + % on \IEEElabelindent, \labelwidth and + % \labelsep + \ifIEEEnocalcleftmargin\relax% + \else\IEEEcalcleftmargin{\IEEElabelindent}\relax% + \fi}\fi} + +% v1.6b we use one makelabel that does justification as needed. +\def\@IEEEiedmakelabel#1{\relax\if\@IEEEiedjustify 0\relax +\makebox[\labelwidth][l]{\normalfont #1}\else +\if\@IEEEiedjustify 1\relax +\makebox[\labelwidth][c]{\normalfont #1}\else +\makebox[\labelwidth][r]{\normalfont #1}\fi\fi} + + +% VERSE and QUOTE +% V1.7 define environments with newenvironment +\newenvironment{verse}{\let\\=\@centercr + \list{}{\itemsep\z@ \itemindent -1.5em \listparindent \itemindent + \rightmargin\leftmargin\advance\leftmargin 1.5em}\item\relax} + {\endlist} +\newenvironment{quotation}{\list{}{\listparindent 1.5em \itemindent\listparindent + \rightmargin\leftmargin \parsep 0pt plus 1pt}\item\relax} + {\endlist} +\newenvironment{quote}{\list{}{\rightmargin\leftmargin}\item\relax} + {\endlist} + + +% \titlepage +% provided only for backward compatibility. \maketitle is the correct +% way to create the title page. +\newif\if@restonecol +\def\titlepage{\@restonecolfalse\if@twocolumn\@restonecoltrue\onecolumn + \else \newpage \fi \thispagestyle{empty}\c@page\z@} +\def\endtitlepage{\if@restonecol\twocolumn \else \newpage \fi} + +% standard values from article.cls +\arraycolsep 5pt +\arrayrulewidth .4pt +\doublerulesep 2pt + +\tabcolsep 6pt +\tabbingsep 0.5em + + +%% FOOTNOTES +% +%\skip\footins 10pt plus 4pt minus 2pt +% V1.6 respond to changes in font size +% space added above the footnotes (if present) +\skip\footins 0.9\baselineskip plus 0.4\baselineskip minus 0.2\baselineskip + +% V1.6, we need to make \footnotesep responsive to changes +% in \baselineskip or strange spacings will result when in +% draft mode. Here is a little LaTeX secret - \footnotesep +% determines the height of an invisible strut that is placed +% *above* the baseline of footnotes after the first. Since +% LaTeX considers the space for characters to be 0.7/baselineskip +% above the baseline and 0.3/baselineskip below it, we need to +% use 0.7/baselineskip as a \footnotesep to maintain equal spacing +% between all the lines of the footnotes. IEEE often uses a tad +% more, so use 0.8\baselineskip. This slightly larger value also helps +% the text to clear the footnote marks. Note that \thanks in IEEEtran +% uses its own value of \footnotesep which is set in \maketitle. +{\footnotesize +\global\footnotesep 0.8\baselineskip} + +\def\unnumberedfootnote{\gdef\@thefnmark{\quad}\@footnotetext} + +\skip\@mpfootins 0.3\baselineskip +\fboxsep = 3pt +\fboxrule = .4pt +% V1.6 use 1em, then use LaTeX2e's \@makefnmark +% Note that IEEE normally *left* aligns the footnote marks, so we don't need +% box resizing tricks here. +%\long\def\@makefnmark{\scriptsize\normalfont\@thefnmark} +\long\def\@makefntext#1{\parindent 1em\indent\hbox{\@makefnmark}#1}% V1.6 use 1em +\long\def\@maketablefntext#1{\raggedleft\leavevmode\hbox{\@makefnmark}#1} +% V1.7 compsoc does not use superscipts for footnote marks +\ifCLASSOPTIONcompsoc +\def\@IEEEcompsocmakefnmark{\hbox{\normalfont\@thefnmark.\ }} +\long\def\@makefntext#1{\parindent 1em\indent\hbox{\@IEEEcompsocmakefnmark}#1} +\fi + +% IEEE does not use footnote rules. Or do they? +\def\footnoterule{\vskip-2pt \hrule height 0.6pt depth \z@ \vskip1.6pt\relax} +\toks@\expandafter{\@setminipage\let\footnoterule\relax\footnotesep\z@} +\edef\@setminipage{\the\toks@} + +% V1.7 for compsoc, IEEE uses a footnote rule only for \thanks. We devise a "one-shot" +% system to implement this. +\newif\if@IEEEenableoneshotfootnoterule +\@IEEEenableoneshotfootnoterulefalse +\ifCLASSOPTIONcompsoc +\def\footnoterule{\relax\if@IEEEenableoneshotfootnoterule +\kern-5pt +\hbox to \columnwidth{\hfill\vrule width 0.5\columnwidth height 0.4pt\hfill} +\kern4.6pt +\global\@IEEEenableoneshotfootnoterulefalse +\else +\relax +\fi} +\fi + +% V1.6 do not allow LaTeX to break a footnote across multiple pages +\interfootnotelinepenalty=10000 + +% V1.6 discourage breaks within equations +% Note that amsmath normally sets this to 10000, +% but LaTeX2e normally uses 100. +\interdisplaylinepenalty=2500 + +% default allows section depth up to /paragraph +\setcounter{secnumdepth}{4} + +% technotes do not allow /paragraph +\ifCLASSOPTIONtechnote + \setcounter{secnumdepth}{3} +\fi +% neither do compsoc conferences +\@IEEEcompsocconfonly{\setcounter{secnumdepth}{3}} + + +\newcounter{section} +\newcounter{subsection}[section] +\newcounter{subsubsection}[subsection] +\newcounter{paragraph}[subsubsection] + +% used only by IEEEtran's IEEEeqnarray as other packages may +% have their own, different, implementations +\newcounter{IEEEsubequation}[equation] + +% as shown when called by user from \ref, \label and in table of contents +\def\theequation{\arabic{equation}} % 1 +\def\theIEEEsubequation{\theequation\alph{IEEEsubequation}} % 1a (used only by IEEEtran's IEEEeqnarray) +\ifCLASSOPTIONcompsoc +% compsoc is all arabic +\def\thesection{\arabic{section}} +\def\thesubsection{\thesection.\arabic{subsection}} +\def\thesubsubsection{\thesubsection.\arabic{subsubsection}} +\def\theparagraph{\thesubsubsection.\arabic{paragraph}} +\else +\def\thesection{\Roman{section}} % I +% V1.7, \mbox prevents breaks around - +\def\thesubsection{\mbox{\thesection-\Alph{subsection}}} % I-A +% V1.7 use I-A1 format used by IEEE rather than I-A.1 +\def\thesubsubsection{\thesubsection\arabic{subsubsection}} % I-A1 +\def\theparagraph{\thesubsubsection\alph{paragraph}} % I-A1a +\fi + +% From Heiko Oberdiek. Because of the \mbox in \thesubsection, we need to +% tell hyperref to disable the \mbox command when making PDF bookmarks. +% This done already with hyperref.sty version 6.74o and later, but +% it will not hurt to do it here again for users of older versions. +\@ifundefined{pdfstringdefPreHook}{\let\pdfstringdefPreHook\@empty}{}% +\g@addto@macro\pdfstringdefPreHook{\let\mbox\relax} + + +% Main text forms (how shown in main text headings) +% V1.6, using \thesection in \thesectiondis allows changes +% in the former to automatically appear in the latter +\ifCLASSOPTIONcompsoc + \ifCLASSOPTIONconference% compsoc conference + \def\thesectiondis{\thesection.} + \def\thesubsectiondis{\thesectiondis\arabic{subsection}.} + \def\thesubsubsectiondis{\thesubsectiondis\arabic{subsubsection}.} + \def\theparagraphdis{\thesubsubsectiondis\arabic{paragraph}.} + \else% compsoc not conferencs + \def\thesectiondis{\thesection} + \def\thesubsectiondis{\thesectiondis.\arabic{subsection}} + \def\thesubsubsectiondis{\thesubsectiondis.\arabic{subsubsection}} + \def\theparagraphdis{\thesubsubsectiondis.\arabic{paragraph}} + \fi +\else% not compsoc + \def\thesectiondis{\thesection.} % I. + \def\thesubsectiondis{\Alph{subsection}.} % B. + \def\thesubsubsectiondis{\arabic{subsubsection})} % 3) + \def\theparagraphdis{\alph{paragraph})} % d) +\fi + +% just like LaTeX2e's \@eqnnum +\def\theequationdis{{\normalfont \normalcolor (\theequation)}}% (1) +% IEEEsubequation used only by IEEEtran's IEEEeqnarray +\def\theIEEEsubequationdis{{\normalfont \normalcolor (\theIEEEsubequation)}}% (1a) +% redirect LaTeX2e's equation number display and all that depend on +% it, through IEEEtran's \theequationdis +\def\@eqnnum{\theequationdis} + + + +% V1.7 provide string macros as article.cls does +\def\contentsname{Contents} +\def\listfigurename{List of Figures} +\def\listtablename{List of Tables} +\def\refname{References} +\def\indexname{Index} +\def\figurename{Fig.} +\def\tablename{TABLE} +\@IEEEcompsocconfonly{\def\figurename{Figure}\def\tablename{Table}} +\def\partname{Part} +\def\appendixname{Appendix} +\def\abstractname{Abstract} +% IEEE specific names +\def\IEEEkeywordsname{Keywords} +\def\IEEEproofname{Proof} + + +% LIST OF FIGURES AND TABLES AND TABLE OF CONTENTS +% +\def\@pnumwidth{1.55em} +\def\@tocrmarg{2.55em} +\def\@dotsep{4.5} +\setcounter{tocdepth}{3} + +% adjusted some spacings here so that section numbers will not easily +% collide with the section titles. +% VIII; VIII-A; and VIII-A.1 are usually the worst offenders. +% MDS 1/2001 +\def\tableofcontents{\section*{\contentsname}\@starttoc{toc}} +\def\l@section#1#2{\addpenalty{\@secpenalty}\addvspace{1.0em plus 1pt}% + \@tempdima 2.75em \begingroup \parindent \z@ \rightskip \@pnumwidth% + \parfillskip-\@pnumwidth {\bfseries\leavevmode #1}\hfil\hbox to\@pnumwidth{\hss #2}\par% + \endgroup} +% argument format #1:level, #2:labelindent,#3:labelsep +\def\l@subsection{\@dottedtocline{2}{2.75em}{3.75em}} +\def\l@subsubsection{\@dottedtocline{3}{6.5em}{4.5em}} +% must provide \l@ defs for ALL sublevels EVEN if tocdepth +% is such as they will not appear in the table of contents +% these defs are how TOC knows what level these things are! +\def\l@paragraph{\@dottedtocline{4}{6.5em}{5.5em}} +\def\l@subparagraph{\@dottedtocline{5}{6.5em}{6.5em}} +\def\listoffigures{\section*{\listfigurename}\@starttoc{lof}} +\def\l@figure{\@dottedtocline{1}{0em}{2.75em}} +\def\listoftables{\section*{\listtablename}\@starttoc{lot}} +\let\l@table\l@figure + + +%% Definitions for floats +%% +%% Normal Floats +\floatsep 1\baselineskip plus 0.2\baselineskip minus 0.2\baselineskip +\textfloatsep 1.7\baselineskip plus 0.2\baselineskip minus 0.4\baselineskip +\@fptop 0pt plus 1fil +\@fpsep 0.75\baselineskip plus 2fil +\@fpbot 0pt plus 1fil +\def\topfraction{0.9} +\def\bottomfraction{0.4} +\def\floatpagefraction{0.8} +% V1.7, let top floats approach 90% of page +\def\textfraction{0.1} + +%% Double Column Floats +\dblfloatsep 1\baselineskip plus 0.2\baselineskip minus 0.2\baselineskip + +\dbltextfloatsep 1.7\baselineskip plus 0.2\baselineskip minus 0.4\baselineskip +% Note that it would be nice if the rubber here actually worked in LaTeX2e. +% There is a long standing limitation in LaTeX, first discovered (to the best +% of my knowledge) by Alan Jeffrey in 1992. LaTeX ignores the stretchable +% portion of \dbltextfloatsep, and as a result, double column figures can and +% do result in an non-integer number of lines in the main text columns with +% underfull vbox errors as a consequence. A post to comp.text.tex +% by Donald Arseneau confirms that this had not yet been fixed in 1998. +% IEEEtran V1.6 will fix this problem for you in the titles, but it doesn't +% protect you from other double floats. Happy vspace'ing. + +\@dblfptop 0pt plus 1fil +\@dblfpsep 0.75\baselineskip plus 2fil +\@dblfpbot 0pt plus 1fil +\def\dbltopfraction{0.8} +\def\dblfloatpagefraction{0.8} +\setcounter{dbltopnumber}{4} + +\intextsep 1\baselineskip plus 0.2\baselineskip minus 0.2\baselineskip +\setcounter{topnumber}{2} +\setcounter{bottomnumber}{2} +\setcounter{totalnumber}{4} + + + +% article class provides these, we should too. +\newlength\abovecaptionskip +\newlength\belowcaptionskip +% but only \abovecaptionskip is used above figure captions and *below* table +% captions +\setlength\abovecaptionskip{0.65\baselineskip} +\setlength\belowcaptionskip{0.75\baselineskip} +% V1.6 create hooks in case the caption spacing ever needs to be +% overridden by a user +\def\@IEEEfigurecaptionsepspace{\vskip\abovecaptionskip\relax}% +\def\@IEEEtablecaptionsepspace{\vskip\belowcaptionskip\relax}% + + +% 1.6b revise caption system so that \@makecaption uses two arguments +% as with LaTeX2e. Otherwise, there will be problems when using hyperref. +\def\@IEEEtablestring{table} + +\ifCLASSOPTIONcompsoc +% V1.7 compsoc \@makecaption +\ifCLASSOPTIONconference% compsoc conference +\long\def\@makecaption#1#2{% +% test if is a for a figure or table +\ifx\@captype\@IEEEtablestring% +% if a table, do table caption +\normalsize\begin{center}{\normalfont\sffamily\normalsize {#1.}~ #2}\end{center}% +\@IEEEtablecaptionsepspace +% if not a table, format it as a figure +\else +\@IEEEfigurecaptionsepspace +\setbox\@tempboxa\hbox{\normalfont\sffamily\normalsize {#1.}~ #2}% +\ifdim \wd\@tempboxa >\hsize% +% if caption is longer than a line, let it wrap around +\setbox\@tempboxa\hbox{\normalfont\sffamily\normalsize {#1.}~ }% +\parbox[t]{\hsize}{\normalfont\sffamily\normalsize \noindent\unhbox\@tempboxa#2}% +% if caption is shorter than a line, center +\else% +\hbox to\hsize{\normalfont\sffamily\normalsize\hfil\box\@tempboxa\hfil}% +\fi\fi} +\else% nonconference compsoc +\long\def\@makecaption#1#2{% +% test if is a for a figure or table +\ifx\@captype\@IEEEtablestring% +% if a table, do table caption +\normalsize\begin{center}{\normalfont\sffamily\normalsize #1}\\{\normalfont\sffamily\normalsize #2}\end{center}% +\@IEEEtablecaptionsepspace +% if not a table, format it as a figure +\else +\@IEEEfigurecaptionsepspace +\setbox\@tempboxa\hbox{\normalfont\sffamily\normalsize {#1.}~ #2}% +\ifdim \wd\@tempboxa >\hsize% +% if caption is longer than a line, let it wrap around +\setbox\@tempboxa\hbox{\normalfont\sffamily\normalsize {#1.}~ }% +\parbox[t]{\hsize}{\normalfont\sffamily\normalsize \noindent\unhbox\@tempboxa#2}% +% if caption is shorter than a line, left justify +\else% +\hbox to\hsize{\normalfont\sffamily\normalsize\box\@tempboxa\hfil}% +\fi\fi} +\fi + +\else% traditional noncompsoc \@makecaption +\long\def\@makecaption#1#2{% +% test if is a for a figure or table +\ifx\@captype\@IEEEtablestring% +% if a table, do table caption +\footnotesize{\centering\normalfont\footnotesize#1.\qquad\scshape #2\par}% +\@IEEEtablecaptionsepspace +% if not a table, format it as a figure +\else +\@IEEEfigurecaptionsepspace +% 3/2001 use footnotesize, not small; use two nonbreaking spaces, not one +\setbox\@tempboxa\hbox{\normalfont\footnotesize {#1.}~~ #2}% +\ifdim \wd\@tempboxa >\hsize% +% if caption is longer than a line, let it wrap around +\setbox\@tempboxa\hbox{\normalfont\footnotesize {#1.}~~ }% +\parbox[t]{\hsize}{\normalfont\footnotesize\noindent\unhbox\@tempboxa#2}% +% if caption is shorter than a line, center if conference, left justify otherwise +\else% +\ifCLASSOPTIONconference \hbox to\hsize{\normalfont\footnotesize\box\@tempboxa\hfil}% +\else \hbox to\hsize{\normalfont\footnotesize\box\@tempboxa\hfil}% +\fi\fi\fi} +\fi + + + +% V1.7 disable captions class option, do so in a way that retains operation of \label +% within \caption +\ifCLASSOPTIONcaptionsoff +\long\def\@makecaption#1#2{\vspace*{2em}\footnotesize\begin{center}{\footnotesize #1}\end{center}% +\let\@IEEEtemporiglabeldefsave\label +\let\@IEEEtemplabelargsave\relax +\def\label##1{\gdef\@IEEEtemplabelargsave{##1}}% +\setbox\@tempboxa\hbox{#2}% +\let\label\@IEEEtemporiglabeldefsave +\ifx\@IEEEtemplabelargsave\relax\else\label{\@IEEEtemplabelargsave}\fi} +\fi + + +% V1.7 define end environments with \def not \let so as to work OK with +% preview-latex +\newcounter{figure} +\def\thefigure{\@arabic\c@figure} +\def\fps@figure{tbp} +\def\ftype@figure{1} +\def\ext@figure{lof} +\def\fnum@figure{\figurename~\thefigure} +\def\figure{\@float{figure}} +\def\endfigure{\end@float} +\@namedef{figure*}{\@dblfloat{figure}} +\@namedef{endfigure*}{\end@dblfloat} +\newcounter{table} +\ifCLASSOPTIONcompsoc +\def\thetable{\arabic{table}} +\else +\def\thetable{\@Roman\c@table} +\fi +\def\fps@table{tbp} +\def\ftype@table{2} +\def\ext@table{lot} +\def\fnum@table{\tablename~\thetable} +% V1.6 IEEE uses 8pt text for tables +% to default to footnotesize, we hack into LaTeX2e's \@floatboxreset and pray +\def\table{\def\@floatboxreset{\reset@font\scriptsize\@setminipage}% + \let\@makefntext\@maketablefntext + \@float{table}} +\def\endtable{\end@float} +% v1.6b double column tables need to default to footnotesize as well. +\@namedef{table*}{\def\@floatboxreset{\reset@font\scriptsize\@setminipage}\@dblfloat{table}} +\@namedef{endtable*}{\end@dblfloat} + + + + +%% +%% START OF IEEEeqnarry DEFINITIONS +%% +%% Inspired by the concepts, examples, and previous works of LaTeX +%% coders and developers such as Donald Arseneau, Fred Bartlett, +%% David Carlisle, Tony Liu, Frank Mittelbach, Piet van Oostrum, +%% Roland Winkler and Mark Wooding. +%% I don't make the claim that my work here is even near their calibre. ;) + + +% hook to allow easy changeover to IEEEtran.cls/tools.sty error reporting +\def\@IEEEclspkgerror{\ClassError{IEEEtran}} + +\newif\if@IEEEeqnarraystarform% flag to indicate if the environment was called as the star form +\@IEEEeqnarraystarformfalse + +\newif\if@advanceIEEEeqncolcnt% tracks if the environment should advance the col counter +% allows a way to make an \IEEEeqnarraybox that can be used within an \IEEEeqnarray +% used by IEEEeqnarraymulticol so that it can work properly in both +\@advanceIEEEeqncolcnttrue + +\newcount\@IEEEeqnnumcols % tracks how many IEEEeqnarray cols are defined +\newcount\@IEEEeqncolcnt % tracks how many IEEEeqnarray cols the user actually used + + +% The default math style used by the columns +\def\IEEEeqnarraymathstyle{\displaystyle} +% The default text style used by the columns +% default to using the current font +\def\IEEEeqnarraytextstyle{\relax} + +% like the iedlistdecl but for \IEEEeqnarray +\def\IEEEeqnarraydecl{\relax} +\def\IEEEeqnarrayboxdecl{\relax} + +% \yesnumber is the opposite of \nonumber +% a novel concept with the same def as the equationarray package +% However, we give IEEE versions too since some LaTeX packages such as +% the MDWtools mathenv.sty redefine \nonumber to something else. +\providecommand{\yesnumber}{\global\@eqnswtrue} +\def\IEEEyesnumber{\global\@eqnswtrue} +\def\IEEEnonumber{\global\@eqnswfalse} + + +\def\IEEEyessubnumber{\global\@IEEEissubequationtrue\global\@eqnswtrue% +\if@IEEEeqnarrayISinner% only do something inside an IEEEeqnarray +\if@IEEElastlinewassubequation\addtocounter{equation}{-1}\else\setcounter{IEEEsubequation}{1}\fi% +\def\@currentlabel{\p@IEEEsubequation\theIEEEsubequation}\fi} + +% flag to indicate that an equation is a sub equation +\newif\if@IEEEissubequation% +\@IEEEissubequationfalse + +% allows users to "push away" equations that get too close to the equation numbers +\def\IEEEeqnarraynumspace{\hphantom{\if@IEEEissubequation\theIEEEsubequationdis\else\theequationdis\fi}} + +% provides a way to span multiple columns within IEEEeqnarray environments +% will consider \if@advanceIEEEeqncolcnt before globally advancing the +% column counter - so as to work within \IEEEeqnarraybox +% usage: \IEEEeqnarraymulticol{number cols. to span}{col type}{cell text} +\long\def\IEEEeqnarraymulticol#1#2#3{\multispan{#1}% +% check if column is defined +\relax\expandafter\ifx\csname @IEEEeqnarraycolDEF#2\endcsname\@IEEEeqnarraycolisdefined% +\csname @IEEEeqnarraycolPRE#2\endcsname#3\relax\relax\relax\relax\relax% +\relax\relax\relax\relax\relax\csname @IEEEeqnarraycolPOST#2\endcsname% +\else% if not, error and use default type +\@IEEEclspkgerror{Invalid column type "#2" in \string\IEEEeqnarraymulticol.\MessageBreak +Using a default centering column instead}% +{You must define IEEEeqnarray column types before use.}% +\csname @IEEEeqnarraycolPRE@IEEEdefault\endcsname#3\relax\relax\relax\relax\relax% +\relax\relax\relax\relax\relax\csname @IEEEeqnarraycolPOST@IEEEdefault\endcsname% +\fi% +% advance column counter only if the IEEEeqnarray environment wants it +\if@advanceIEEEeqncolcnt\global\advance\@IEEEeqncolcnt by #1\relax\fi} + +% like \omit, but maintains track of the column counter for \IEEEeqnarray +\def\IEEEeqnarrayomit{\omit\if@advanceIEEEeqncolcnt\global\advance\@IEEEeqncolcnt by 1\relax\fi} + + +% provides a way to define a letter referenced column type +% usage: \IEEEeqnarraydefcol{col. type letter/name}{pre insertion text}{post insertion text} +\def\IEEEeqnarraydefcol#1#2#3{\expandafter\def\csname @IEEEeqnarraycolPRE#1\endcsname{#2}% +\expandafter\def\csname @IEEEeqnarraycolPOST#1\endcsname{#3}% +\expandafter\def\csname @IEEEeqnarraycolDEF#1\endcsname{1}} + + +% provides a way to define a numerically referenced inter-column glue types +% usage: \IEEEeqnarraydefcolsep{col. glue number}{glue definition} +\def\IEEEeqnarraydefcolsep#1#2{\expandafter\def\csname @IEEEeqnarraycolSEP\romannumeral #1\endcsname{#2}% +\expandafter\def\csname @IEEEeqnarraycolSEPDEF\romannumeral #1\endcsname{1}} + + +\def\@IEEEeqnarraycolisdefined{1}% just a macro for 1, used for checking undefined column types + + +% expands and appends the given argument to the \@IEEEtrantmptoksA token list +% used to build up the \halign preamble +\def\@IEEEappendtoksA#1{\edef\@@IEEEappendtoksA{\@IEEEtrantmptoksA={\the\@IEEEtrantmptoksA #1}}% +\@@IEEEappendtoksA} + +% also appends to \@IEEEtrantmptoksA, but does not expand the argument +% uses \toks8 as a scratchpad register +\def\@IEEEappendNOEXPANDtoksA#1{\toks8={#1}% +\edef\@@IEEEappendNOEXPANDtoksA{\@IEEEtrantmptoksA={\the\@IEEEtrantmptoksA\the\toks8}}% +\@@IEEEappendNOEXPANDtoksA} + +% define some common column types for the user +% math +\IEEEeqnarraydefcol{l}{$\IEEEeqnarraymathstyle}{$\hfil} +\IEEEeqnarraydefcol{c}{\hfil$\IEEEeqnarraymathstyle}{$\hfil} +\IEEEeqnarraydefcol{r}{\hfil$\IEEEeqnarraymathstyle}{$} +\IEEEeqnarraydefcol{L}{$\IEEEeqnarraymathstyle{}}{{}$\hfil} +\IEEEeqnarraydefcol{C}{\hfil$\IEEEeqnarraymathstyle{}}{{}$\hfil} +\IEEEeqnarraydefcol{R}{\hfil$\IEEEeqnarraymathstyle{}}{{}$} +% text +\IEEEeqnarraydefcol{s}{\IEEEeqnarraytextstyle}{\hfil} +\IEEEeqnarraydefcol{t}{\hfil\IEEEeqnarraytextstyle}{\hfil} +\IEEEeqnarraydefcol{u}{\hfil\IEEEeqnarraytextstyle}{} + +% vertical rules +\IEEEeqnarraydefcol{v}{}{\vrule width\arrayrulewidth} +\IEEEeqnarraydefcol{vv}{\vrule width\arrayrulewidth\hfil}{\hfil\vrule width\arrayrulewidth} +\IEEEeqnarraydefcol{V}{}{\vrule width\arrayrulewidth\hskip\doublerulesep\vrule width\arrayrulewidth} +\IEEEeqnarraydefcol{VV}{\vrule width\arrayrulewidth\hskip\doublerulesep\vrule width\arrayrulewidth\hfil}% +{\hfil\vrule width\arrayrulewidth\hskip\doublerulesep\vrule width\arrayrulewidth} + +% horizontal rules +\IEEEeqnarraydefcol{h}{}{\leaders\hrule height\arrayrulewidth\hfil} +\IEEEeqnarraydefcol{H}{}{\leaders\vbox{\hrule width\arrayrulewidth\vskip\doublerulesep\hrule width\arrayrulewidth}\hfil} + +% plain +\IEEEeqnarraydefcol{x}{}{} +\IEEEeqnarraydefcol{X}{$}{$} + +% the default column type to use in the event a column type is not defined +\IEEEeqnarraydefcol{@IEEEdefault}{\hfil$\IEEEeqnarraymathstyle}{$\hfil} + + +% a zero tabskip (used for "-" col types) +\def\@IEEEeqnarraycolSEPzero{0pt plus 0pt minus 0pt} +% a centering tabskip (used for "+" col types) +\def\@IEEEeqnarraycolSEPcenter{1000pt plus 0pt minus 1000pt} + +% top level default tabskip glues for the start, end, and inter-column +% may be reset within environments not always at the top level, e.g., \IEEEeqnarraybox +\edef\@IEEEeqnarraycolSEPdefaultstart{\@IEEEeqnarraycolSEPcenter}% default start glue +\edef\@IEEEeqnarraycolSEPdefaultend{\@IEEEeqnarraycolSEPcenter}% default end glue +\edef\@IEEEeqnarraycolSEPdefaultmid{\@IEEEeqnarraycolSEPzero}% default inter-column glue + + + +% creates a vertical rule that extends from the bottom to the top a a cell +% Provided in case other packages redefine \vline some other way. +% usage: \IEEEeqnarrayvrule[rule thickness] +% If no argument is provided, \arrayrulewidth will be used for the rule thickness. +\newcommand\IEEEeqnarrayvrule[1][\arrayrulewidth]{\vrule\@width#1\relax} + +% creates a blank separator row +% usage: \IEEEeqnarrayseprow[separation length][font size commands] +% default is \IEEEeqnarrayseprow[0.25\normalbaselineskip][\relax] +% blank arguments inherit the default values +% uses \skip5 as a scratch register - calls \@IEEEeqnarraystrutsize which uses more scratch registers +\def\IEEEeqnarrayseprow{\relax\@ifnextchar[{\@IEEEeqnarrayseprow}{\@IEEEeqnarrayseprow[0.25\normalbaselineskip]}} +\def\@IEEEeqnarrayseprow[#1]{\relax\@ifnextchar[{\@@IEEEeqnarrayseprow[#1]}{\@@IEEEeqnarrayseprow[#1][\relax]}} +\def\@@IEEEeqnarrayseprow[#1][#2]{\def\@IEEEeqnarrayseprowARGONE{#1}% +\ifx\@IEEEeqnarrayseprowARGONE\@empty% +% get the skip value, based on the font commands +% use skip5 because \IEEEeqnarraystrutsize uses \skip0, \skip2, \skip3 +% assign within a bogus box to confine the font changes +{\setbox0=\hbox{#2\relax\global\skip5=0.25\normalbaselineskip}}% +\else% +{\setbox0=\hbox{#2\relax\global\skip5=#1}}% +\fi% +\@IEEEeqnarrayhoptolastcolumn\IEEEeqnarraystrutsize{\skip5}{0pt}[\relax]\relax} + +% creates a blank separator row, but omits all the column templates +% usage: \IEEEeqnarrayseprowcut[separation length][font size commands] +% default is \IEEEeqnarrayseprowcut[0.25\normalbaselineskip][\relax] +% blank arguments inherit the default values +% uses \skip5 as a scratch register - calls \@IEEEeqnarraystrutsize which uses more scratch registers +\def\IEEEeqnarrayseprowcut{\multispan{\@IEEEeqnnumcols}\relax% span all the cols +% advance column counter only if the IEEEeqnarray environment wants it +\if@advanceIEEEeqncolcnt\global\advance\@IEEEeqncolcnt by \@IEEEeqnnumcols\relax\fi% +\@ifnextchar[{\@IEEEeqnarrayseprowcut}{\@IEEEeqnarrayseprowcut[0.25\normalbaselineskip]}} +\def\@IEEEeqnarrayseprowcut[#1]{\relax\@ifnextchar[{\@@IEEEeqnarrayseprowcut[#1]}{\@@IEEEeqnarrayseprowcut[#1][\relax]}} +\def\@@IEEEeqnarrayseprowcut[#1][#2]{\def\@IEEEeqnarrayseprowARGONE{#1}% +\ifx\@IEEEeqnarrayseprowARGONE\@empty% +% get the skip value, based on the font commands +% use skip5 because \IEEEeqnarraystrutsize uses \skip0, \skip2, \skip3 +% assign within a bogus box to confine the font changes +{\setbox0=\hbox{#2\relax\global\skip5=0.25\normalbaselineskip}}% +\else% +{\setbox0=\hbox{#2\relax\global\skip5=#1}}% +\fi% +\IEEEeqnarraystrutsize{\skip5}{0pt}[\relax]\relax} + + + +% draws a single rule across all the columns optional +% argument determines the rule width, \arrayrulewidth is the default +% updates column counter as needed and turns off struts +% usage: \IEEEeqnarrayrulerow[rule line thickness] +\def\IEEEeqnarrayrulerow{\multispan{\@IEEEeqnnumcols}\relax% span all the cols +% advance column counter only if the IEEEeqnarray environment wants it +\if@advanceIEEEeqncolcnt\global\advance\@IEEEeqncolcnt by \@IEEEeqnnumcols\relax\fi% +\@ifnextchar[{\@IEEEeqnarrayrulerow}{\@IEEEeqnarrayrulerow[\arrayrulewidth]}} +\def\@IEEEeqnarrayrulerow[#1]{\leaders\hrule height#1\hfil\relax% put in our rule +% turn off any struts +\IEEEeqnarraystrutsize{0pt}{0pt}[\relax]\relax} + + +% draws a double rule by using a single rule row, a separator row, and then +% another single rule row +% first optional argument determines the rule thicknesses, \arrayrulewidth is the default +% second optional argument determines the rule spacing, \doublerulesep is the default +% usage: \IEEEeqnarraydblrulerow[rule line thickness][rule spacing] +\def\IEEEeqnarraydblrulerow{\multispan{\@IEEEeqnnumcols}\relax% span all the cols +% advance column counter only if the IEEEeqnarray environment wants it +\if@advanceIEEEeqncolcnt\global\advance\@IEEEeqncolcnt by \@IEEEeqnnumcols\relax\fi% +\@ifnextchar[{\@IEEEeqnarraydblrulerow}{\@IEEEeqnarraydblrulerow[\arrayrulewidth]}} +\def\@IEEEeqnarraydblrulerow[#1]{\relax\@ifnextchar[{\@@IEEEeqnarraydblrulerow[#1]}% +{\@@IEEEeqnarraydblrulerow[#1][\doublerulesep]}} +\def\@@IEEEeqnarraydblrulerow[#1][#2]{\def\@IEEEeqnarraydblrulerowARG{#1}% +% we allow the user to say \IEEEeqnarraydblrulerow[][] +\ifx\@IEEEeqnarraydblrulerowARG\@empty% +\@IEEEeqnarrayrulerow[\arrayrulewidth]% +\else% +\@IEEEeqnarrayrulerow[#1]\relax% +\fi% +\def\@IEEEeqnarraydblrulerowARG{#2}% +\ifx\@IEEEeqnarraydblrulerowARG\@empty% +\\\IEEEeqnarrayseprow[\doublerulesep][\relax]% +\else% +\\\IEEEeqnarrayseprow[#2][\relax]% +\fi% +\\\multispan{\@IEEEeqnnumcols}% +% advance column counter only if the IEEEeqnarray environment wants it +\if@advanceIEEEeqncolcnt\global\advance\@IEEEeqncolcnt by \@IEEEeqnnumcols\relax\fi% +\def\@IEEEeqnarraydblrulerowARG{#1}% +\ifx\@IEEEeqnarraydblrulerowARG\@empty% +\@IEEEeqnarrayrulerow[\arrayrulewidth]% +\else% +\@IEEEeqnarrayrulerow[#1]% +\fi% +} + +% draws a double rule by using a single rule row, a separator (cutting) row, and then +% another single rule row +% first optional argument determines the rule thicknesses, \arrayrulewidth is the default +% second optional argument determines the rule spacing, \doublerulesep is the default +% usage: \IEEEeqnarraydblrulerow[rule line thickness][rule spacing] +\def\IEEEeqnarraydblrulerowcut{\multispan{\@IEEEeqnnumcols}\relax% span all the cols +% advance column counter only if the IEEEeqnarray environment wants it +\if@advanceIEEEeqncolcnt\global\advance\@IEEEeqncolcnt by \@IEEEeqnnumcols\relax\fi% +\@ifnextchar[{\@IEEEeqnarraydblrulerowcut}{\@IEEEeqnarraydblrulerowcut[\arrayrulewidth]}} +\def\@IEEEeqnarraydblrulerowcut[#1]{\relax\@ifnextchar[{\@@IEEEeqnarraydblrulerowcut[#1]}% +{\@@IEEEeqnarraydblrulerowcut[#1][\doublerulesep]}} +\def\@@IEEEeqnarraydblrulerowcut[#1][#2]{\def\@IEEEeqnarraydblrulerowARG{#1}% +% we allow the user to say \IEEEeqnarraydblrulerow[][] +\ifx\@IEEEeqnarraydblrulerowARG\@empty% +\@IEEEeqnarrayrulerow[\arrayrulewidth]% +\else% +\@IEEEeqnarrayrulerow[#1]% +\fi% +\def\@IEEEeqnarraydblrulerowARG{#2}% +\ifx\@IEEEeqnarraydblrulerowARG\@empty% +\\\IEEEeqnarrayseprowcut[\doublerulesep][\relax]% +\else% +\\\IEEEeqnarrayseprowcut[#2][\relax]% +\fi% +\\\multispan{\@IEEEeqnnumcols}% +% advance column counter only if the IEEEeqnarray environment wants it +\if@advanceIEEEeqncolcnt\global\advance\@IEEEeqncolcnt by \@IEEEeqnnumcols\relax\fi% +\def\@IEEEeqnarraydblrulerowARG{#1}% +\ifx\@IEEEeqnarraydblrulerowARG\@empty% +\@IEEEeqnarrayrulerow[\arrayrulewidth]% +\else% +\@IEEEeqnarrayrulerow[#1]% +\fi% +} + + + +% inserts a full row's worth of &'s +% relies on \@IEEEeqnnumcols to provide the correct number of columns +% uses \@IEEEtrantmptoksA, \count0 as scratch registers +\def\@IEEEeqnarrayhoptolastcolumn{\@IEEEtrantmptoksA={}\count0=1\relax% +\loop% add cols if the user did not use them all +\ifnum\count0<\@IEEEeqnnumcols\relax% +\@IEEEappendtoksA{&}% +\advance\count0 by 1\relax% update the col count +\repeat% +\the\@IEEEtrantmptoksA%execute the &'s +} + + + +\newif\if@IEEEeqnarrayISinner % flag to indicate if we are within the lines +\@IEEEeqnarrayISinnerfalse % of an IEEEeqnarray - after the IEEEeqnarraydecl + +\edef\@IEEEeqnarrayTHEstrutheight{0pt} % height and depth of IEEEeqnarray struts +\edef\@IEEEeqnarrayTHEstrutdepth{0pt} + +\edef\@IEEEeqnarrayTHEmasterstrutheight{0pt} % default height and depth of +\edef\@IEEEeqnarrayTHEmasterstrutdepth{0pt} % struts within an IEEEeqnarray + +\edef\@IEEEeqnarrayTHEmasterstrutHSAVE{0pt} % saved master strut height +\edef\@IEEEeqnarrayTHEmasterstrutDSAVE{0pt} % and depth + +\newif\if@IEEEeqnarrayusemasterstrut % flag to indicate that the master strut value +\@IEEEeqnarrayusemasterstruttrue % is to be used + + + +% saves the strut height and depth of the master strut +\def\@IEEEeqnarraymasterstrutsave{\relax% +\expandafter\skip0=\@IEEEeqnarrayTHEmasterstrutheight\relax% +\expandafter\skip2=\@IEEEeqnarrayTHEmasterstrutdepth\relax% +% remove stretchability +\dimen0\skip0\relax% +\dimen2\skip2\relax% +% save values +\edef\@IEEEeqnarrayTHEmasterstrutHSAVE{\the\dimen0}% +\edef\@IEEEeqnarrayTHEmasterstrutDSAVE{\the\dimen2}} + +% restores the strut height and depth of the master strut +\def\@IEEEeqnarraymasterstrutrestore{\relax% +\expandafter\skip0=\@IEEEeqnarrayTHEmasterstrutHSAVE\relax% +\expandafter\skip2=\@IEEEeqnarrayTHEmasterstrutDSAVE\relax% +% remove stretchability +\dimen0\skip0\relax% +\dimen2\skip2\relax% +% restore values +\edef\@IEEEeqnarrayTHEmasterstrutheight{\the\dimen0}% +\edef\@IEEEeqnarrayTHEmasterstrutdepth{\the\dimen2}} + + +% globally restores the strut height and depth to the +% master values and sets the master strut flag to true +\def\@IEEEeqnarraystrutreset{\relax% +\expandafter\skip0=\@IEEEeqnarrayTHEmasterstrutheight\relax% +\expandafter\skip2=\@IEEEeqnarrayTHEmasterstrutdepth\relax% +% remove stretchability +\dimen0\skip0\relax% +\dimen2\skip2\relax% +% restore values +\xdef\@IEEEeqnarrayTHEstrutheight{\the\dimen0}% +\xdef\@IEEEeqnarrayTHEstrutdepth{\the\dimen2}% +\global\@IEEEeqnarrayusemasterstruttrue} + + +% if the master strut is not to be used, make the current +% values of \@IEEEeqnarrayTHEstrutheight, \@IEEEeqnarrayTHEstrutdepth +% and the use master strut flag, global +% this allows user strut commands issued in the last column to be carried +% into the isolation/strut column +\def\@IEEEeqnarrayglobalizestrutstatus{\relax% +\if@IEEEeqnarrayusemasterstrut\else% +\xdef\@IEEEeqnarrayTHEstrutheight{\@IEEEeqnarrayTHEstrutheight}% +\xdef\@IEEEeqnarrayTHEstrutdepth{\@IEEEeqnarrayTHEstrutdepth}% +\global\@IEEEeqnarrayusemasterstrutfalse% +\fi} + + + +% usage: \IEEEeqnarraystrutsize{height}{depth}[font size commands] +% If called outside the lines of an IEEEeqnarray, sets the height +% and depth of both the master and local struts. If called inside +% an IEEEeqnarray line, sets the height and depth of the local strut +% only and sets the flag to indicate the use of the local strut +% values. If the height or depth is left blank, 0.7\normalbaselineskip +% and 0.3\normalbaselineskip will be used, respectively. +% The optional argument can be used to evaluate the lengths under +% a different font size and styles. If none is specified, the current +% font is used. +% uses scratch registers \skip0, \skip2, \skip3, \dimen0, \dimen2 +\def\IEEEeqnarraystrutsize#1#2{\relax\@ifnextchar[{\@IEEEeqnarraystrutsize{#1}{#2}}{\@IEEEeqnarraystrutsize{#1}{#2}[\relax]}} +\def\@IEEEeqnarraystrutsize#1#2[#3]{\def\@IEEEeqnarraystrutsizeARG{#1}% +\ifx\@IEEEeqnarraystrutsizeARG\@empty% +{\setbox0=\hbox{#3\relax\global\skip3=0.7\normalbaselineskip}}% +\skip0=\skip3\relax% +\else% arg one present +{\setbox0=\hbox{#3\relax\global\skip3=#1\relax}}% +\skip0=\skip3\relax% +\fi% if null arg +\def\@IEEEeqnarraystrutsizeARG{#2}% +\ifx\@IEEEeqnarraystrutsizeARG\@empty% +{\setbox0=\hbox{#3\relax\global\skip3=0.3\normalbaselineskip}}% +\skip2=\skip3\relax% +\else% arg two present +{\setbox0=\hbox{#3\relax\global\skip3=#2\relax}}% +\skip2=\skip3\relax% +\fi% if null arg +% remove stretchability, just to be safe +\dimen0\skip0\relax% +\dimen2\skip2\relax% +% dimen0 = height, dimen2 = depth +\if@IEEEeqnarrayISinner% inner does not touch master strut size +\edef\@IEEEeqnarrayTHEstrutheight{\the\dimen0}% +\edef\@IEEEeqnarrayTHEstrutdepth{\the\dimen2}% +\@IEEEeqnarrayusemasterstrutfalse% do not use master +\else% outer, have to set master strut too +\edef\@IEEEeqnarrayTHEmasterstrutheight{\the\dimen0}% +\edef\@IEEEeqnarrayTHEmasterstrutdepth{\the\dimen2}% +\edef\@IEEEeqnarrayTHEstrutheight{\the\dimen0}% +\edef\@IEEEeqnarrayTHEstrutdepth{\the\dimen2}% +\@IEEEeqnarrayusemasterstruttrue% use master strut +\fi} + + +% usage: \IEEEeqnarraystrutsizeadd{added height}{added depth}[font size commands] +% If called outside the lines of an IEEEeqnarray, adds the given height +% and depth to both the master and local struts. +% If called inside an IEEEeqnarray line, adds the given height and depth +% to the local strut only and sets the flag to indicate the use +% of the local strut values. +% In both cases, if a height or depth is left blank, 0pt is used instead. +% The optional argument can be used to evaluate the lengths under +% a different font size and styles. If none is specified, the current +% font is used. +% uses scratch registers \skip0, \skip2, \skip3, \dimen0, \dimen2 +\def\IEEEeqnarraystrutsizeadd#1#2{\relax\@ifnextchar[{\@IEEEeqnarraystrutsizeadd{#1}{#2}}{\@IEEEeqnarraystrutsizeadd{#1}{#2}[\relax]}} +\def\@IEEEeqnarraystrutsizeadd#1#2[#3]{\def\@IEEEeqnarraystrutsizearg{#1}% +\ifx\@IEEEeqnarraystrutsizearg\@empty% +\skip0=0pt\relax% +\else% arg one present +{\setbox0=\hbox{#3\relax\global\skip3=#1}}% +\skip0=\skip3\relax% +\fi% if null arg +\def\@IEEEeqnarraystrutsizearg{#2}% +\ifx\@IEEEeqnarraystrutsizearg\@empty% +\skip2=0pt\relax% +\else% arg two present +{\setbox0=\hbox{#3\relax\global\skip3=#2}}% +\skip2=\skip3\relax% +\fi% if null arg +% remove stretchability, just to be safe +\dimen0\skip0\relax% +\dimen2\skip2\relax% +% dimen0 = height, dimen2 = depth +\if@IEEEeqnarrayISinner% inner does not touch master strut size +% get local strut size +\expandafter\skip0=\@IEEEeqnarrayTHEstrutheight\relax% +\expandafter\skip2=\@IEEEeqnarrayTHEstrutdepth\relax% +% add it to the user supplied values +\advance\dimen0 by \skip0\relax% +\advance\dimen2 by \skip2\relax% +% update the local strut size +\edef\@IEEEeqnarrayTHEstrutheight{\the\dimen0}% +\edef\@IEEEeqnarrayTHEstrutdepth{\the\dimen2}% +\@IEEEeqnarrayusemasterstrutfalse% do not use master +\else% outer, have to set master strut too +% get master strut size +\expandafter\skip0=\@IEEEeqnarrayTHEmasterstrutheight\relax% +\expandafter\skip2=\@IEEEeqnarrayTHEmasterstrutdepth\relax% +% add it to the user supplied values +\advance\dimen0 by \skip0\relax% +\advance\dimen2 by \skip2\relax% +% update the local and master strut sizes +\edef\@IEEEeqnarrayTHEmasterstrutheight{\the\dimen0}% +\edef\@IEEEeqnarrayTHEmasterstrutdepth{\the\dimen2}% +\edef\@IEEEeqnarrayTHEstrutheight{\the\dimen0}% +\edef\@IEEEeqnarrayTHEstrutdepth{\the\dimen2}% +\@IEEEeqnarrayusemasterstruttrue% use master strut +\fi} + + +% allow user a way to see the struts +\newif\ifIEEEvisiblestruts +\IEEEvisiblestrutsfalse + +% inserts an invisible strut using the master or local strut values +% uses scratch registers \skip0, \skip2, \dimen0, \dimen2 +\def\@IEEEeqnarrayinsertstrut{\relax% +\if@IEEEeqnarrayusemasterstrut +% get master strut size +\expandafter\skip0=\@IEEEeqnarrayTHEmasterstrutheight\relax% +\expandafter\skip2=\@IEEEeqnarrayTHEmasterstrutdepth\relax% +\else% +% get local strut size +\expandafter\skip0=\@IEEEeqnarrayTHEstrutheight\relax% +\expandafter\skip2=\@IEEEeqnarrayTHEstrutdepth\relax% +\fi% +% remove stretchability, probably not needed +\dimen0\skip0\relax% +\dimen2\skip2\relax% +% dimen0 = height, dimen2 = depth +% allow user to see struts if desired +\ifIEEEvisiblestruts% +\vrule width0.2pt height\dimen0 depth\dimen2\relax% +\else% +\vrule width0pt height\dimen0 depth\dimen2\relax\fi} + + +% creates an invisible strut, useable even outside \IEEEeqnarray +% if \IEEEvisiblestrutstrue, the strut will be visible and 0.2pt wide. +% usage: \IEEEstrut[height][depth][font size commands] +% default is \IEEEstrut[0.7\normalbaselineskip][0.3\normalbaselineskip][\relax] +% blank arguments inherit the default values +% uses \dimen0, \dimen2, \skip0, \skip2 +\def\IEEEstrut{\relax\@ifnextchar[{\@IEEEstrut}{\@IEEEstrut[0.7\normalbaselineskip]}} +\def\@IEEEstrut[#1]{\relax\@ifnextchar[{\@@IEEEstrut[#1]}{\@@IEEEstrut[#1][0.3\normalbaselineskip]}} +\def\@@IEEEstrut[#1][#2]{\relax\@ifnextchar[{\@@@IEEEstrut[#1][#2]}{\@@@IEEEstrut[#1][#2][\relax]}} +\def\@@@IEEEstrut[#1][#2][#3]{\mbox{#3\relax% +\def\@IEEEstrutARG{#1}% +\ifx\@IEEEstrutARG\@empty% +\skip0=0.7\normalbaselineskip\relax% +\else% +\skip0=#1\relax% +\fi% +\def\@IEEEstrutARG{#2}% +\ifx\@IEEEstrutARG\@empty% +\skip2=0.3\normalbaselineskip\relax% +\else% +\skip2=#2\relax% +\fi% +% remove stretchability, probably not needed +\dimen0\skip0\relax% +\dimen2\skip2\relax% +\ifIEEEvisiblestruts% +\vrule width0.2pt height\dimen0 depth\dimen2\relax% +\else% +\vrule width0.0pt height\dimen0 depth\dimen2\relax\fi}} + + +% enables strut mode by setting a default strut size and then zeroing the +% \baselineskip, \lineskip, \lineskiplimit and \jot +\def\IEEEeqnarraystrutmode{\IEEEeqnarraystrutsize{0.7\normalbaselineskip}{0.3\normalbaselineskip}[\relax]% +\baselineskip=0pt\lineskip=0pt\lineskiplimit=0pt\jot=0pt} + + + +\def\IEEEeqnarray{\@IEEEeqnarraystarformfalse\@IEEEeqnarray} +\def\endIEEEeqnarray{\end@IEEEeqnarray} + +\@namedef{IEEEeqnarray*}{\@IEEEeqnarraystarformtrue\@IEEEeqnarray} +\@namedef{endIEEEeqnarray*}{\end@IEEEeqnarray} + + +% \IEEEeqnarray is an enhanced \eqnarray. +% The star form defaults to not putting equation numbers at the end of each row. +% usage: \IEEEeqnarray[decl]{cols} +\def\@IEEEeqnarray{\relax\@ifnextchar[{\@@IEEEeqnarray}{\@@IEEEeqnarray[\relax]}} +\def\@@IEEEeqnarray[#1]#2{% + % default to showing the equation number or not based on whether or not + % the star form was involked + \if@IEEEeqnarraystarform\global\@eqnswfalse + \else% not the star form + \global\@eqnswtrue + \fi% if star form + \@IEEEissubequationfalse% default to no subequations + \@IEEElastlinewassubequationfalse% assume last line is not a sub equation + \@IEEEeqnarrayISinnerfalse% not yet within the lines of the halign + \@IEEEeqnarraystrutsize{0pt}{0pt}[\relax]% turn off struts by default + \@IEEEeqnarrayusemasterstruttrue% use master strut till user asks otherwise + \IEEEvisiblestrutsfalse% diagnostic mode defaults to off + % no extra space unless the user specifically requests it + \lineskip=0pt\relax + \lineskiplimit=0pt\relax + \baselineskip=\normalbaselineskip\relax% + \jot=\IEEEnormaljot\relax% + \mathsurround\z@\relax% no extra spacing around math + \@advanceIEEEeqncolcnttrue% advance the col counter for each col the user uses, + % used in \IEEEeqnarraymulticol and in the preamble build + \stepcounter{equation}% advance equation counter before first line + \setcounter{IEEEsubequation}{0}% no subequation yet + \def\@currentlabel{\p@equation\theequation}% redefine the ref label + \IEEEeqnarraydecl\relax% allow a way for the user to make global overrides + #1\relax% allow user to override defaults + \let\\\@IEEEeqnarraycr% replace newline with one that can put in eqn. numbers + \global\@IEEEeqncolcnt\z@% col. count = 0 for first line + \@IEEEbuildpreamble #2\end\relax% build the preamble and put it into \@IEEEtrantmptoksA + % put in the column for the equation number + \ifnum\@IEEEeqnnumcols>0\relax\@IEEEappendtoksA{&}\fi% col separator for those after the first + \toks0={##}% + % advance the \@IEEEeqncolcnt for the isolation col, this helps with error checking + \@IEEEappendtoksA{\global\advance\@IEEEeqncolcnt by 1\relax}% + % add the isolation column + \@IEEEappendtoksA{\tabskip\z@skip\bgroup\the\toks0\egroup}% + % advance the \@IEEEeqncolcnt for the equation number col, this helps with error checking + \@IEEEappendtoksA{&\global\advance\@IEEEeqncolcnt by 1\relax}% + % add the equation number col to the preamble + \@IEEEappendtoksA{\tabskip\z@skip\hb@xt@\z@\bgroup\hss\the\toks0\egroup}% + % note \@IEEEeqnnumcols does not count the equation col or isolation col + % set the starting tabskip glue as determined by the preamble build + \tabskip=\@IEEEBPstartglue\relax + % begin the display alignment + \@IEEEeqnarrayISinnertrue% commands are now within the lines + $$\everycr{}\halign to\displaywidth\bgroup + % "exspand" the preamble + \span\the\@IEEEtrantmptoksA\cr} + +% enter isolation/strut column (or the next column if the user did not use +% every column), record the strut status, complete the columns, do the strut if needed, +% restore counters to correct values and exit +\def\end@IEEEeqnarray{\@IEEEeqnarrayglobalizestrutstatus&\@@IEEEeqnarraycr\egroup% +\if@IEEElastlinewassubequation\global\advance\c@IEEEsubequation\m@ne\fi% +\global\advance\c@equation\m@ne% +$$\@ignoretrue} + +% need a way to remember if last line is a subequation +\newif\if@IEEElastlinewassubequation% +\@IEEElastlinewassubequationfalse + +% IEEEeqnarray uses a modifed \\ instead of the plain \cr to +% end rows. This allows for things like \\*[vskip amount] +% This "cr" macros are modified versions those for LaTeX2e's eqnarray +% the {\ifnum0=`} braces must be kept away from the last column to avoid +% altering spacing of its math, so we use & to advance to the next column +% as there is an isolation/strut column after the user's columns +\def\@IEEEeqnarraycr{\@IEEEeqnarrayglobalizestrutstatus&% save strut status and advance to next column + {\ifnum0=`}\fi + \@ifstar{% + \global\@eqpen\@M\@IEEEeqnarrayYCR + }{% + \global\@eqpen\interdisplaylinepenalty \@IEEEeqnarrayYCR + }% +} + +\def\@IEEEeqnarrayYCR{\@testopt\@IEEEeqnarrayXCR\z@skip} + +\def\@IEEEeqnarrayXCR[#1]{% + \ifnum0=`{\fi}% + \@@IEEEeqnarraycr + \noalign{\penalty\@eqpen\vskip\jot\vskip #1\relax}}% + +\def\@@IEEEeqnarraycr{\@IEEEtrantmptoksA={}% clear token register + \advance\@IEEEeqncolcnt by -1\relax% adjust col count because of the isolation column + \ifnum\@IEEEeqncolcnt>\@IEEEeqnnumcols\relax + \@IEEEclspkgerror{Too many columns within the IEEEeqnarray\MessageBreak + environment}% + {Use fewer \string &'s or put more columns in the IEEEeqnarry column\MessageBreak + specifications.}\relax% + \else + \loop% add cols if the user did not use them all + \ifnum\@IEEEeqncolcnt<\@IEEEeqnnumcols\relax + \@IEEEappendtoksA{&}% + \advance\@IEEEeqncolcnt by 1\relax% update the col count + \repeat + % this number of &'s will take us the the isolation column + \fi + % execute the &'s + \the\@IEEEtrantmptoksA% + % handle the strut/isolation column + \@IEEEeqnarrayinsertstrut% do the strut if needed + \@IEEEeqnarraystrutreset% reset the strut system for next line or IEEEeqnarray + &% and enter the equation number column + % is this line needs an equation number, display it and advance the + % (sub)equation counters, record what type this line was + \if@eqnsw% + \if@IEEEissubequation\theIEEEsubequationdis\addtocounter{equation}{1}\stepcounter{IEEEsubequation}% + \global\@IEEElastlinewassubequationtrue% + \else% display a standard equation number, initialize the IEEEsubequation counter + \theequationdis\stepcounter{equation}\setcounter{IEEEsubequation}{0}% + \global\@IEEElastlinewassubequationfalse\fi% + \fi% + % reset the eqnsw flag to indicate default preference of the display of equation numbers + \if@IEEEeqnarraystarform\global\@eqnswfalse\else\global\@eqnswtrue\fi + \global\@IEEEissubequationfalse% reset the subequation flag + % reset the number of columns the user actually used + \global\@IEEEeqncolcnt\z@\relax + % the real end of the line + \cr} + + + + + +% \IEEEeqnarraybox is like \IEEEeqnarray except the box form puts everything +% inside a vtop, vbox, or vcenter box depending on the letter in the second +% optional argument (t,b,c). Vbox is the default. Unlike \IEEEeqnarray, +% equation numbers are not displayed and \IEEEeqnarraybox can be nested. +% \IEEEeqnarrayboxm is for math mode (like \array) and does not put the vbox +% within an hbox. +% \IEEEeqnarrayboxt is for text mode (like \tabular) and puts the vbox within +% a \hbox{$ $} construct. +% \IEEEeqnarraybox will auto detect whether to use \IEEEeqnarrayboxm or +% \IEEEeqnarrayboxt depending on the math mode. +% The third optional argument specifies the width this box is to be set to - +% natural width is the default. +% The * forms do not add \jot line spacing +% usage: \IEEEeqnarraybox[decl][pos][width]{cols} +\def\IEEEeqnarrayboxm{\@IEEEeqnarraystarformfalse\@IEEEeqnarrayboxHBOXSWfalse\@IEEEeqnarraybox} +\def\endIEEEeqnarrayboxm{\end@IEEEeqnarraybox} +\@namedef{IEEEeqnarrayboxm*}{\@IEEEeqnarraystarformtrue\@IEEEeqnarrayboxHBOXSWfalse\@IEEEeqnarraybox} +\@namedef{endIEEEeqnarrayboxm*}{\end@IEEEeqnarraybox} + +\def\IEEEeqnarrayboxt{\@IEEEeqnarraystarformfalse\@IEEEeqnarrayboxHBOXSWtrue\@IEEEeqnarraybox} +\def\endIEEEeqnarrayboxt{\end@IEEEeqnarraybox} +\@namedef{IEEEeqnarrayboxt*}{\@IEEEeqnarraystarformtrue\@IEEEeqnarrayboxHBOXSWtrue\@IEEEeqnarraybox} +\@namedef{endIEEEeqnarrayboxt*}{\end@IEEEeqnarraybox} + +\def\IEEEeqnarraybox{\@IEEEeqnarraystarformfalse\ifmmode\@IEEEeqnarrayboxHBOXSWfalse\else\@IEEEeqnarrayboxHBOXSWtrue\fi% +\@IEEEeqnarraybox} +\def\endIEEEeqnarraybox{\end@IEEEeqnarraybox} + +\@namedef{IEEEeqnarraybox*}{\@IEEEeqnarraystarformtrue\ifmmode\@IEEEeqnarrayboxHBOXSWfalse\else\@IEEEeqnarrayboxHBOXSWtrue\fi% +\@IEEEeqnarraybox} +\@namedef{endIEEEeqnarraybox*}{\end@IEEEeqnarraybox} + +% flag to indicate if the \IEEEeqnarraybox needs to put things into an hbox{$ $} +% for \vcenter in non-math mode +\newif\if@IEEEeqnarrayboxHBOXSW% +\@IEEEeqnarrayboxHBOXSWfalse + +\def\@IEEEeqnarraybox{\relax\@ifnextchar[{\@@IEEEeqnarraybox}{\@@IEEEeqnarraybox[\relax]}} +\def\@@IEEEeqnarraybox[#1]{\relax\@ifnextchar[{\@@@IEEEeqnarraybox[#1]}{\@@@IEEEeqnarraybox[#1][b]}} +\def\@@@IEEEeqnarraybox[#1][#2]{\relax\@ifnextchar[{\@@@@IEEEeqnarraybox[#1][#2]}{\@@@@IEEEeqnarraybox[#1][#2][\relax]}} + +% #1 = decl; #2 = t,b,c; #3 = width, #4 = col specs +\def\@@@@IEEEeqnarraybox[#1][#2][#3]#4{\@IEEEeqnarrayISinnerfalse % not yet within the lines of the halign + \@IEEEeqnarraymasterstrutsave% save current master strut values + \@IEEEeqnarraystrutsize{0pt}{0pt}[\relax]% turn off struts by default + \@IEEEeqnarrayusemasterstruttrue% use master strut till user asks otherwise + \IEEEvisiblestrutsfalse% diagnostic mode defaults to off + % no extra space unless the user specifically requests it + \lineskip=0pt\relax% + \lineskiplimit=0pt\relax% + \baselineskip=\normalbaselineskip\relax% + \jot=\IEEEnormaljot\relax% + \mathsurround\z@\relax% no extra spacing around math + % the default end glues are zero for an \IEEEeqnarraybox + \edef\@IEEEeqnarraycolSEPdefaultstart{\@IEEEeqnarraycolSEPzero}% default start glue + \edef\@IEEEeqnarraycolSEPdefaultend{\@IEEEeqnarraycolSEPzero}% default end glue + \edef\@IEEEeqnarraycolSEPdefaultmid{\@IEEEeqnarraycolSEPzero}% default inter-column glue + \@advanceIEEEeqncolcntfalse% do not advance the col counter for each col the user uses, + % used in \IEEEeqnarraymulticol and in the preamble build + \IEEEeqnarrayboxdecl\relax% allow a way for the user to make global overrides + #1\relax% allow user to override defaults + \let\\\@IEEEeqnarrayboxcr% replace newline with one that allows optional spacing + \@IEEEbuildpreamble #4\end\relax% build the preamble and put it into \@IEEEtrantmptoksA + % add an isolation column to the preamble to stop \\'s {} from getting into the last col + \ifnum\@IEEEeqnnumcols>0\relax\@IEEEappendtoksA{&}\fi% col separator for those after the first + \toks0={##}% + % add the isolation column to the preamble + \@IEEEappendtoksA{\tabskip\z@skip\bgroup\the\toks0\egroup}% + % set the starting tabskip glue as determined by the preamble build + \tabskip=\@IEEEBPstartglue\relax + % begin the alignment + \everycr{}% + % use only the very first token to determine the positioning + % this stops some problems when the user uses more than one letter, + % but is probably not worth the effort + % \noindent is used as a delimiter + \def\@IEEEgrabfirstoken##1##2\noindent{\let\@IEEEgrabbedfirstoken=##1}% + \@IEEEgrabfirstoken#2\relax\relax\noindent + % \@IEEEgrabbedfirstoken has the first token, the rest are discarded + % if we need to put things into and hbox and go into math mode, do so now + \if@IEEEeqnarrayboxHBOXSW \leavevmode \hbox \bgroup $\fi% + % use the appropriate vbox type + \if\@IEEEgrabbedfirstoken t\relax\vtop\else\if\@IEEEgrabbedfirstoken c\relax% + \vcenter\else\vbox\fi\fi\bgroup% + \@IEEEeqnarrayISinnertrue% commands are now within the lines + \ifx#3\relax\halign\else\halign to #3\relax\fi% + \bgroup + % "exspand" the preamble + \span\the\@IEEEtrantmptoksA\cr} + +% carry strut status and enter the isolation/strut column, +% exit from math mode if needed, and exit +\def\end@IEEEeqnarraybox{\@IEEEeqnarrayglobalizestrutstatus% carry strut status +&% enter isolation/strut column +\@IEEEeqnarrayinsertstrut% do strut if needed +\@IEEEeqnarraymasterstrutrestore% restore the previous master strut values +% reset the strut system for next IEEEeqnarray +% (sets local strut values back to previous master strut values) +\@IEEEeqnarraystrutreset% +% ensure last line, exit from halign, close vbox +\crcr\egroup\egroup% +% exit from math mode and close hbox if needed +\if@IEEEeqnarrayboxHBOXSW $\egroup\fi} + + + +% IEEEeqnarraybox uses a modifed \\ instead of the plain \cr to +% end rows. This allows for things like \\[vskip amount] +% This "cr" macros are modified versions those for LaTeX2e's eqnarray +% For IEEEeqnarraybox, \\* is the same as \\ +% the {\ifnum0=`} braces must be kept away from the last column to avoid +% altering spacing of its math, so we use & to advance to the isolation/strut column +% carry strut status into isolation/strut column +\def\@IEEEeqnarrayboxcr{\@IEEEeqnarrayglobalizestrutstatus% carry strut status +&% enter isolation/strut column +\@IEEEeqnarrayinsertstrut% do strut if needed +% reset the strut system for next line or IEEEeqnarray +\@IEEEeqnarraystrutreset% +{\ifnum0=`}\fi% +\@ifstar{\@IEEEeqnarrayboxYCR}{\@IEEEeqnarrayboxYCR}} + +% test and setup the optional argument to \\[] +\def\@IEEEeqnarrayboxYCR{\@testopt\@IEEEeqnarrayboxXCR\z@skip} + +% IEEEeqnarraybox does not automatically increase line spacing by \jot +\def\@IEEEeqnarrayboxXCR[#1]{\ifnum0=`{\fi}% +\cr\noalign{\if@IEEEeqnarraystarform\else\vskip\jot\fi\vskip#1\relax}} + + + +% starts the halign preamble build +\def\@IEEEbuildpreamble{\@IEEEtrantmptoksA={}% clear token register +\let\@IEEEBPcurtype=u%current column type is not yet known +\let\@IEEEBPprevtype=s%the previous column type was the start +\let\@IEEEBPnexttype=u%next column type is not yet known +% ensure these are valid +\def\@IEEEBPcurglue={0pt plus 0pt minus 0pt}% +\def\@IEEEBPcurcolname{@IEEEdefault}% name of current column definition +% currently acquired numerically referenced glue +% use a name that is easier to remember +\let\@IEEEBPcurnum=\@IEEEtrantmpcountA% +\@IEEEBPcurnum=0% +% tracks number of columns in the preamble +\@IEEEeqnnumcols=0% +% record the default end glues +\edef\@IEEEBPstartglue{\@IEEEeqnarraycolSEPdefaultstart}% +\edef\@IEEEBPendglue{\@IEEEeqnarraycolSEPdefaultend}% +% now parse the user's column specifications +\@@IEEEbuildpreamble} + + +% parses and builds the halign preamble +\def\@@IEEEbuildpreamble#1#2{\let\@@nextIEEEbuildpreamble=\@@IEEEbuildpreamble% +% use only the very first token to check the end +% \noindent is used as a delimiter as \end can be present here +\def\@IEEEgrabfirstoken##1##2\noindent{\let\@IEEEgrabbedfirstoken=##1}% +\@IEEEgrabfirstoken#1\relax\relax\noindent +\ifx\@IEEEgrabbedfirstoken\end\let\@@nextIEEEbuildpreamble=\@@IEEEfinishpreamble\else% +% identify current and next token type +\@IEEEgetcoltype{#1}{\@IEEEBPcurtype}{1}% current, error on invalid +\@IEEEgetcoltype{#2}{\@IEEEBPnexttype}{0}% next, no error on invalid next +% if curtype is a glue, get the glue def +\if\@IEEEBPcurtype g\@IEEEgetcurglue{#1}{\@IEEEBPcurglue}\fi% +% if curtype is a column, get the column def and set the current column name +\if\@IEEEBPcurtype c\@IEEEgetcurcol{#1}\fi% +% if curtype is a numeral, acquire the user defined glue +\if\@IEEEBPcurtype n\@IEEEprocessNcol{#1}\fi% +% process the acquired glue +\if\@IEEEBPcurtype g\@IEEEprocessGcol\fi% +% process the acquired col +\if\@IEEEBPcurtype c\@IEEEprocessCcol\fi% +% ready prevtype for next col spec. +\let\@IEEEBPprevtype=\@IEEEBPcurtype% +% be sure and put back the future token(s) as a group +\fi\@@nextIEEEbuildpreamble{#2}} + + +% executed just after preamble build is completed +% warn about zero cols, and if prevtype type = u, put in end tabskip glue +\def\@@IEEEfinishpreamble#1{\ifnum\@IEEEeqnnumcols<1\relax +\@IEEEclspkgerror{No column specifiers declared for IEEEeqnarray}% +{At least one column type must be declared for each IEEEeqnarray.}% +\fi%num cols less than 1 +%if last type undefined, set default end tabskip glue +\if\@IEEEBPprevtype u\@IEEEappendtoksA{\tabskip=\@IEEEBPendglue}\fi} + + +% Identify and return the column specifier's type code +\def\@IEEEgetcoltype#1#2#3{% +% use only the very first token to determine the type +% \noindent is used as a delimiter as \end can be present here +\def\@IEEEgrabfirstoken##1##2\noindent{\let\@IEEEgrabbedfirstoken=##1}% +\@IEEEgrabfirstoken#1\relax\relax\noindent +% \@IEEEgrabfirstoken has the first token, the rest are discarded +% n = number +% g = glue (any other char in catagory 12) +% c = letter +% e = \end +% u = undefined +% third argument: 0 = no error message, 1 = error on invalid char +\let#2=u\relax% assume invalid until know otherwise +\ifx\@IEEEgrabbedfirstoken\end\let#2=e\else +\ifcat\@IEEEgrabbedfirstoken\relax\else% screen out control sequences +\if0\@IEEEgrabbedfirstoken\let#2=n\else +\if1\@IEEEgrabbedfirstoken\let#2=n\else +\if2\@IEEEgrabbedfirstoken\let#2=n\else +\if3\@IEEEgrabbedfirstoken\let#2=n\else +\if4\@IEEEgrabbedfirstoken\let#2=n\else +\if5\@IEEEgrabbedfirstoken\let#2=n\else +\if6\@IEEEgrabbedfirstoken\let#2=n\else +\if7\@IEEEgrabbedfirstoken\let#2=n\else +\if8\@IEEEgrabbedfirstoken\let#2=n\else +\if9\@IEEEgrabbedfirstoken\let#2=n\else +\ifcat,\@IEEEgrabbedfirstoken\let#2=g\relax +\else\ifcat a\@IEEEgrabbedfirstoken\let#2=c\relax\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi +\if#2u\relax +\if0\noexpand#3\relax\else\@IEEEclspkgerror{Invalid character in column specifications}% +{Only letters, numerals and certain other symbols are allowed \MessageBreak +as IEEEeqnarray column specifiers.}\fi\fi} + + +% identify the current letter referenced column +% if invalid, use a default column +\def\@IEEEgetcurcol#1{\expandafter\ifx\csname @IEEEeqnarraycolDEF#1\endcsname\@IEEEeqnarraycolisdefined% +\def\@IEEEBPcurcolname{#1}\else% invalid column name +\@IEEEclspkgerror{Invalid column type "#1" in column specifications.\MessageBreak +Using a default centering column instead}% +{You must define IEEEeqnarray column types before use.}% +\def\@IEEEBPcurcolname{@IEEEdefault}\fi} + + +% identify and return the predefined (punctuation) glue value +\def\@IEEEgetcurglue#1#2{% +% ! = \! (neg small) -0.16667em (-3/18 em) +% , = \, (small) 0.16667em ( 3/18 em) +% : = \: (med) 0.22222em ( 4/18 em) +% ; = \; (large) 0.27778em ( 5/18 em) +% ' = \quad 1em +% " = \qquad 2em +% . = 0.5\arraycolsep +% / = \arraycolsep +% ? = 2\arraycolsep +% * = 1fil +% + = \@IEEEeqnarraycolSEPcenter +% - = \@IEEEeqnarraycolSEPzero +% Note that all em values are referenced to the math font (textfont2) fontdimen6 +% value for 1em. +% +% use only the very first token to determine the type +% this prevents errant tokens from getting in the main text +% \noindent is used as a delimiter here +\def\@IEEEgrabfirstoken##1##2\noindent{\let\@IEEEgrabbedfirstoken=##1}% +\@IEEEgrabfirstoken#1\relax\relax\noindent +% get the math font 1em value +% LaTeX2e's NFSS2 does not preload the fonts, but \IEEEeqnarray needs +% to gain access to the math (\textfont2) font's spacing parameters. +% So we create a bogus box here that uses the math font to ensure +% that \textfont2 is loaded and ready. If this is not done, +% the \textfont2 stuff here may not work. +% Thanks to Bernd Raichle for his 1997 post on this topic. +{\setbox0=\hbox{$\displaystyle\relax$}}% +% fontdimen6 has the width of 1em (a quad). +\@IEEEtrantmpdimenA=\fontdimen6\textfont2\relax% +% identify the glue value based on the first token +% we discard anything after the first +\if!\@IEEEgrabbedfirstoken\@IEEEtrantmpdimenA=-0.16667\@IEEEtrantmpdimenA\edef#2{\the\@IEEEtrantmpdimenA}\else +\if,\@IEEEgrabbedfirstoken\@IEEEtrantmpdimenA=0.16667\@IEEEtrantmpdimenA\edef#2{\the\@IEEEtrantmpdimenA}\else +\if:\@IEEEgrabbedfirstoken\@IEEEtrantmpdimenA=0.22222\@IEEEtrantmpdimenA\edef#2{\the\@IEEEtrantmpdimenA}\else +\if;\@IEEEgrabbedfirstoken\@IEEEtrantmpdimenA=0.27778\@IEEEtrantmpdimenA\edef#2{\the\@IEEEtrantmpdimenA}\else +\if'\@IEEEgrabbedfirstoken\@IEEEtrantmpdimenA=1\@IEEEtrantmpdimenA\edef#2{\the\@IEEEtrantmpdimenA}\else +\if"\@IEEEgrabbedfirstoken\@IEEEtrantmpdimenA=2\@IEEEtrantmpdimenA\edef#2{\the\@IEEEtrantmpdimenA}\else +\if.\@IEEEgrabbedfirstoken\@IEEEtrantmpdimenA=0.5\arraycolsep\edef#2{\the\@IEEEtrantmpdimenA}\else +\if/\@IEEEgrabbedfirstoken\edef#2{\the\arraycolsep}\else +\if?\@IEEEgrabbedfirstoken\@IEEEtrantmpdimenA=2\arraycolsep\edef#2{\the\@IEEEtrantmpdimenA}\else +\if *\@IEEEgrabbedfirstoken\edef#2{0pt plus 1fil minus 0pt}\else +\if+\@IEEEgrabbedfirstoken\edef#2{\@IEEEeqnarraycolSEPcenter}\else +\if-\@IEEEgrabbedfirstoken\edef#2{\@IEEEeqnarraycolSEPzero}\else +\edef#2{\@IEEEeqnarraycolSEPzero}% +\@IEEEclspkgerror{Invalid predefined inter-column glue type "#1" in\MessageBreak +column specifications. Using a default value of\MessageBreak +0pt instead}% +{Only !,:;'"./?*+ and - are valid predefined glue types in the\MessageBreak +IEEEeqnarray column specifications.}\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi} + + + +% process a numerical digit from the column specification +% and look up the corresponding user defined glue value +% can transform current type from n to g or a as the user defined glue is acquired +\def\@IEEEprocessNcol#1{\if\@IEEEBPprevtype g% +\@IEEEclspkgerror{Back-to-back inter-column glue specifiers in column\MessageBreak +specifications. Ignoring consecutive glue specifiers\MessageBreak +after the first}% +{You cannot have two or more glue types next to each other\MessageBreak +in the IEEEeqnarray column specifications.}% +\let\@IEEEBPcurtype=a% abort this glue, future digits will be discarded +\@IEEEBPcurnum=0\relax% +\else% if we previously aborted a glue +\if\@IEEEBPprevtype a\@IEEEBPcurnum=0\let\@IEEEBPcurtype=a%maintain digit abortion +\else%acquire this number +% save the previous type before the numerical digits started +\if\@IEEEBPprevtype n\else\let\@IEEEBPprevsavedtype=\@IEEEBPprevtype\fi% +\multiply\@IEEEBPcurnum by 10\relax% +\advance\@IEEEBPcurnum by #1\relax% add in number, \relax is needed to stop TeX's number scan +\if\@IEEEBPnexttype n\else%close acquisition +\expandafter\ifx\csname @IEEEeqnarraycolSEPDEF\expandafter\romannumeral\number\@IEEEBPcurnum\endcsname\@IEEEeqnarraycolisdefined% +\edef\@IEEEBPcurglue{\csname @IEEEeqnarraycolSEP\expandafter\romannumeral\number\@IEEEBPcurnum\endcsname}% +\else%user glue not defined +\@IEEEclspkgerror{Invalid user defined inter-column glue type "\number\@IEEEBPcurnum" in\MessageBreak +column specifications. Using a default value of\MessageBreak +0pt instead}% +{You must define all IEEEeqnarray numerical inter-column glue types via\MessageBreak +\string\IEEEeqnarraydefcolsep \space before they are used in column specifications.}% +\edef\@IEEEBPcurglue{\@IEEEeqnarraycolSEPzero}% +\fi% glue defined or not +\let\@IEEEBPcurtype=g% change the type to reflect the acquired glue +\let\@IEEEBPprevtype=\@IEEEBPprevsavedtype% restore the prev type before this number glue +\@IEEEBPcurnum=0\relax%ready for next acquisition +\fi%close acquisition, get glue +\fi%discard or acquire number +\fi%prevtype glue or not +} + + +% process an acquired glue +% add any acquired column/glue pair to the preamble +\def\@IEEEprocessGcol{\if\@IEEEBPprevtype a\let\@IEEEBPcurtype=a%maintain previous glue abortions +\else +% if this is the start glue, save it, but do nothing else +% as this is not used in the preamble, but before +\if\@IEEEBPprevtype s\edef\@IEEEBPstartglue{\@IEEEBPcurglue}% +\else%not the start glue +\if\@IEEEBPprevtype g%ignore if back to back glues +\@IEEEclspkgerror{Back-to-back inter-column glue specifiers in column\MessageBreak +specifications. Ignoring consecutive glue specifiers\MessageBreak +after the first}% +{You cannot have two or more glue types next to each other\MessageBreak +in the IEEEeqnarray column specifications.}% +\let\@IEEEBPcurtype=a% abort this glue +\else% not a back to back glue +\if\@IEEEBPprevtype c\relax% if the previoustype was a col, add column/glue pair to preamble +\ifnum\@IEEEeqnnumcols>0\relax\@IEEEappendtoksA{&}\fi +\toks0={##}% +% make preamble advance col counter if this environment needs this +\if@advanceIEEEeqncolcnt\@IEEEappendtoksA{\global\advance\@IEEEeqncolcnt by 1\relax}\fi +% insert the column defintion into the preamble, being careful not to expand +% the column definition +\@IEEEappendtoksA{\tabskip=\@IEEEBPcurglue}% +\@IEEEappendNOEXPANDtoksA{\begingroup\csname @IEEEeqnarraycolPRE}% +\@IEEEappendtoksA{\@IEEEBPcurcolname}% +\@IEEEappendNOEXPANDtoksA{\endcsname}% +\@IEEEappendtoksA{\the\toks0}% +\@IEEEappendNOEXPANDtoksA{\relax\relax\relax\relax\relax% +\relax\relax\relax\relax\relax\csname @IEEEeqnarraycolPOST}% +\@IEEEappendtoksA{\@IEEEBPcurcolname}% +\@IEEEappendNOEXPANDtoksA{\endcsname\relax\relax\relax\relax\relax% +\relax\relax\relax\relax\relax\endgroup}% +\advance\@IEEEeqnnumcols by 1\relax%one more column in the preamble +\else% error: non-start glue with no pending column +\@IEEEclspkgerror{Inter-column glue specifier without a prior column\MessageBreak +type in the column specifications. Ignoring this glue\MessageBreak +specifier}% +{Except for the first and last positions, glue can be placed only\MessageBreak +between column types.}% +\let\@IEEEBPcurtype=a% abort this glue +\fi% previous was a column +\fi% back-to-back glues +\fi% is start column glue +\fi% prev type not a +} + + +% process an acquired letter referenced column and, if necessary, add it to the preamble +\def\@IEEEprocessCcol{\if\@IEEEBPnexttype g\else +\if\@IEEEBPnexttype n\else +% we have a column followed by something other than a glue (or numeral glue) +% so we must add this column to the preamble now +\ifnum\@IEEEeqnnumcols>0\relax\@IEEEappendtoksA{&}\fi%col separator for those after the first +\if\@IEEEBPnexttype e\@IEEEappendtoksA{\tabskip=\@IEEEBPendglue\relax}\else%put in end glue +\@IEEEappendtoksA{\tabskip=\@IEEEeqnarraycolSEPdefaultmid\relax}\fi% or default mid glue +\toks0={##}% +% make preamble advance col counter if this environment needs this +\if@advanceIEEEeqncolcnt\@IEEEappendtoksA{\global\advance\@IEEEeqncolcnt by 1\relax}\fi +% insert the column definition into the preamble, being careful not to expand +% the column definition +\@IEEEappendNOEXPANDtoksA{\begingroup\csname @IEEEeqnarraycolPRE}% +\@IEEEappendtoksA{\@IEEEBPcurcolname}% +\@IEEEappendNOEXPANDtoksA{\endcsname}% +\@IEEEappendtoksA{\the\toks0}% +\@IEEEappendNOEXPANDtoksA{\relax\relax\relax\relax\relax% +\relax\relax\relax\relax\relax\csname @IEEEeqnarraycolPOST}% +\@IEEEappendtoksA{\@IEEEBPcurcolname}% +\@IEEEappendNOEXPANDtoksA{\endcsname\relax\relax\relax\relax\relax% +\relax\relax\relax\relax\relax\endgroup}% +\advance\@IEEEeqnnumcols by 1\relax%one more column in the preamble +\fi%next type not numeral +\fi%next type not glue +} + + +%% +%% END OF IEEEeqnarry DEFINITIONS +%% + + + + +% set up the running headings, this complex because of all the different +% modes IEEEtran supports +\if@twoside + \ifCLASSOPTIONtechnote + \def\ps@headings{% + \def\@oddhead{\hbox{}\scriptsize\leftmark \hfil \thepage} + \def\@evenhead{\scriptsize\thepage \hfil \leftmark\hbox{}} + \ifCLASSOPTIONdraftcls + \ifCLASSOPTIONdraftclsnofoot + \def\@oddfoot{}\def\@evenfoot{}% + \else + \def\@oddfoot{\scriptsize\@date\hfil DRAFT} + \def\@evenfoot{\scriptsize DRAFT\hfil\@date} + \fi + \else + \def\@oddfoot{}\def\@evenfoot{} + \fi} + \else % not a technote + \def\ps@headings{% + \ifCLASSOPTIONconference + \def\@oddhead{} + \def\@evenhead{} + \else + \def\@oddhead{\hbox{}\scriptsize\rightmark \hfil \thepage} + \def\@evenhead{\scriptsize\thepage \hfil \leftmark\hbox{}} + \fi + \ifCLASSOPTIONdraftcls + \def\@oddhead{\hbox{}\scriptsize\rightmark \hfil \thepage} + \def\@evenhead{\scriptsize\thepage \hfil \leftmark\hbox{}} + \ifCLASSOPTIONdraftclsnofoot + \def\@oddfoot{}\def\@evenfoot{}% + \else + \def\@oddfoot{\scriptsize\@date\hfil DRAFT} + \def\@evenfoot{\scriptsize DRAFT\hfil\@date} + \fi + \else + \def\@oddfoot{}\def\@evenfoot{}% + \fi} + \fi +\else % single side +\def\ps@headings{% + \ifCLASSOPTIONconference + \def\@oddhead{} + \def\@evenhead{} + \else + \def\@oddhead{\hbox{}\scriptsize\leftmark \hfil \thepage} + \def\@evenhead{} + \fi + \ifCLASSOPTIONdraftcls + \def\@oddhead{\hbox{}\scriptsize\leftmark \hfil \thepage} + \def\@evenhead{} + \ifCLASSOPTIONdraftclsnofoot + \def\@oddfoot{} + \else + \def\@oddfoot{\scriptsize \@date \hfil DRAFT} + \fi + \else + \def\@oddfoot{} + \fi + \def\@evenfoot{}} +\fi + + +% title page style +\def\ps@IEEEtitlepagestyle{\def\@oddfoot{}\def\@evenfoot{}% +\ifCLASSOPTIONconference + \def\@oddhead{}% + \def\@evenhead{}% +\else + \def\@oddhead{\hbox{}\scriptsize\leftmark \hfil \thepage}% + \def\@evenhead{\scriptsize\thepage \hfil \leftmark\hbox{}}% +\fi +\ifCLASSOPTIONdraftcls + \def\@oddhead{\hbox{}\scriptsize\leftmark \hfil \thepage}% + \def\@evenhead{\scriptsize\thepage \hfil \leftmark\hbox{}}% + \ifCLASSOPTIONdraftclsnofoot\else + \def\@oddfoot{\scriptsize \@date\hfil DRAFT}% + \def\@evenfoot{\scriptsize DRAFT\hfil \@date}% + \fi +\else + % all non-draft mode footers + \if@IEEEusingpubid + % for title pages that are using a pubid + % do not repeat pubid if using peer review option + \ifCLASSOPTIONpeerreview + \else + \footskip 0pt% + \ifCLASSOPTIONcompsoc + \def\@oddfoot{\hss\normalfont\scriptsize\raisebox{-1.5\@IEEEnormalsizeunitybaselineskip}[0ex][0ex]{\@IEEEpubid}\hss}% + \def\@evenfoot{\hss\normalfont\scriptsize\raisebox{-1.5\@IEEEnormalsizeunitybaselineskip}[0ex][0ex]{\@IEEEpubid}\hss}% + \else + \def\@oddfoot{\hss\normalfont\footnotesize\raisebox{1.5ex}[1.5ex]{\@IEEEpubid}\hss}% + \def\@evenfoot{\hss\normalfont\footnotesize\raisebox{1.5ex}[1.5ex]{\@IEEEpubid}\hss}% + \fi + \fi + \fi +\fi} + + +% peer review cover page style +\def\ps@IEEEpeerreviewcoverpagestyle{% +\def\@oddhead{}\def\@evenhead{}% +\def\@oddfoot{}\def\@evenfoot{}% +\ifCLASSOPTIONdraftcls + \ifCLASSOPTIONdraftclsnofoot\else + \def\@oddfoot{\scriptsize \@date\hfil DRAFT}% + \def\@evenfoot{\scriptsize DRAFT\hfil \@date}% + \fi +\else + % non-draft mode footers + \if@IEEEusingpubid + \footskip 0pt% + \ifCLASSOPTIONcompsoc + \def\@oddfoot{\hss\normalfont\scriptsize\raisebox{-1.5\@IEEEnormalsizeunitybaselineskip}[0ex][0ex]{\@IEEEpubid}\hss}% + \def\@evenfoot{\hss\normalfont\scriptsize\raisebox{-1.5\@IEEEnormalsizeunitybaselineskip}[0ex][0ex]{\@IEEEpubid}\hss}% + \else + \def\@oddfoot{\hss\normalfont\footnotesize\raisebox{1.5ex}[1.5ex]{\@IEEEpubid}\hss}% + \def\@evenfoot{\hss\normalfont\footnotesize\raisebox{1.5ex}[1.5ex]{\@IEEEpubid}\hss}% + \fi + \fi +\fi} + + +% start with empty headings +\def\rightmark{}\def\leftmark{} + + +%% Defines the command for putting the header. \footernote{TEXT} is the same +%% as \markboth{TEXT}{TEXT}. +%% Note that all the text is forced into uppercase, if you have some text +%% that needs to be in lower case, for instance et. al., then either manually +%% set \leftmark and \rightmark or use \MakeLowercase{et. al.} within the +%% arguments to \markboth. +\def\markboth#1#2{\def\leftmark{\@IEEEcompsoconly{\sffamily}\MakeUppercase{#1}}% +\def\rightmark{\@IEEEcompsoconly{\sffamily}\MakeUppercase{#2}}} +\def\footernote#1{\markboth{#1}{#1}} + +\def\today{\ifcase\month\or + January\or February\or March\or April\or May\or June\or + July\or August\or September\or October\or November\or December\fi + \space\number\day, \number\year} + + + + +%% CITATION AND BIBLIOGRAPHY COMMANDS +%% +%% V1.6 no longer supports the older, nonstandard \shortcite and \citename setup stuff +% +% +% Modify Latex2e \@citex to separate citations with "], [" +\def\@citex[#1]#2{% + \let\@citea\@empty + \@cite{\@for\@citeb:=#2\do + {\@citea\def\@citea{], [}% + \edef\@citeb{\expandafter\@firstofone\@citeb\@empty}% + \if@filesw\immediate\write\@auxout{\string\citation{\@citeb}}\fi + \@ifundefined{b@\@citeb}{\mbox{\reset@font\bfseries ?}% + \G@refundefinedtrue + \@latex@warning + {Citation `\@citeb' on page \thepage \space undefined}}% + {\hbox{\csname b@\@citeb\endcsname}}}}{#1}} + +% V1.6 we create hooks for the optional use of Donald Arseneau's +% cite.sty package. cite.sty is "smart" and will notice that the +% following format controls are already defined and will not +% redefine them. The result will be the proper sorting of the +% citation numbers and auto detection of 3 or more entry "ranges" - +% all in IEEE style: [1], [2], [5]--[7], [12] +% This also allows for an optional note, i.e., \cite[mynote]{..}. +% If the \cite with note has more than one reference, the note will +% be applied to the last of the listed references. It is generally +% desired that if a note is given, only one reference is listed in +% that \cite. +% Thanks to Mr. Arseneau for providing the required format arguments +% to produce the IEEE style. +\def\citepunct{], [} +\def\citedash{]--[} + +% V1.7 default to using same font for urls made by url.sty +\AtBeginDocument{\csname url@samestyle\endcsname} + +% V1.6 class files should always provide these +\def\newblock{\hskip .11em\@plus.33em\@minus.07em} +\let\@openbib@code\@empty + + +% Provide support for the control entries of IEEEtran.bst V1.00 and later. +% V1.7 optional argument allows for a different aux file to be specified in +% order to handle multiple bibliographies. For example, with multibib.sty: +% \newcites{sec}{Secondary Literature} +% \bstctlcite[@auxoutsec]{BSTcontrolhak} +\def\bstctlcite{\@ifnextchar[{\@bstctlcite}{\@bstctlcite[@auxout]}} +\def\@bstctlcite[#1]#2{\@bsphack + \@for\@citeb:=#2\do{% + \edef\@citeb{\expandafter\@firstofone\@citeb}% + \if@filesw\immediate\write\csname #1\endcsname{\string\citation{\@citeb}}\fi}% + \@esphack} + +% V1.6 provide a way for a user to execute a command just before +% a given reference number - used to insert a \newpage to balance +% the columns on the last page +\edef\@IEEEtriggerrefnum{0} % the default of zero means that + % the command is not executed +\def\@IEEEtriggercmd{\newpage} + +% allow the user to alter the triggered command +\long\def\IEEEtriggercmd#1{\long\def\@IEEEtriggercmd{#1}} + +% allow user a way to specify the reference number just before the +% command is executed +\def\IEEEtriggeratref#1{\@IEEEtrantmpcountA=#1% +\edef\@IEEEtriggerrefnum{\the\@IEEEtrantmpcountA}}% + +% trigger command at the given reference +\def\@IEEEbibitemprefix{\@IEEEtrantmpcountA=\@IEEEtriggerrefnum\relax% +\advance\@IEEEtrantmpcountA by -1\relax% +\ifnum\c@enumiv=\@IEEEtrantmpcountA\relax\@IEEEtriggercmd\relax\fi} + + +\def\@biblabel#1{[#1]} + +% compsoc journals left align the reference numbers +\@IEEEcompsocnotconfonly{\def\@biblabel#1{[#1]\hfill}} + +% controls bib item spacing +\def\IEEEbibitemsep{2.5pt plus .5pt} + +\@IEEEcompsocconfonly{\def\IEEEbibitemsep{1\baselineskip plus 0.25\baselineskip minus 0.25\baselineskip}} + + +\def\thebibliography#1{\section*{\refname}% + \addcontentsline{toc}{section}{\refname}% + % V1.6 add some rubber space here and provide a command trigger + \footnotesize\@IEEEcompsocconfonly{\small}\vskip 0.3\baselineskip plus 0.1\baselineskip minus 0.1\baselineskip% + \list{\@biblabel{\@arabic\c@enumiv}}% + {\settowidth\labelwidth{\@biblabel{#1}}% + \leftmargin\labelwidth + \labelsep 1em + \advance\leftmargin\labelsep\relax + \itemsep \IEEEbibitemsep\relax + \usecounter{enumiv}% + \let\p@enumiv\@empty + \renewcommand\theenumiv{\@arabic\c@enumiv}}% + \let\@IEEElatexbibitem\bibitem% + \def\bibitem{\@IEEEbibitemprefix\@IEEElatexbibitem}% +\def\newblock{\hskip .11em plus .33em minus .07em}% +% originally: +% \sloppy\clubpenalty4000\widowpenalty4000% +% by adding the \interlinepenalty here, we make it more +% difficult, but not impossible, for LaTeX to break within a reference. +% IEEE almost never breaks a reference (but they do it more often with +% technotes). You may get an underfull vbox warning around the bibliography, +% but the final result will be much more like what IEEE will publish. +% MDS 11/2000 +\ifCLASSOPTIONtechnote\sloppy\clubpenalty4000\widowpenalty4000\interlinepenalty100% +\else\sloppy\clubpenalty4000\widowpenalty4000\interlinepenalty500\fi% + \sfcode`\.=1000\relax} +\let\endthebibliography=\endlist + + + + +% TITLE PAGE COMMANDS +% +% +% \IEEEmembership is used to produce the sublargesize italic font used to indicate author +% IEEE membership. compsoc uses a large size sans slant font +\def\IEEEmembership#1{{\@IEEEnotcompsoconly{\sublargesize}\normalfont\@IEEEcompsoconly{\sffamily}\textit{#1}}} + + +% \IEEEauthorrefmark{} produces a footnote type symbol to indicate author affiliation. +% When given an argument of 1 to 9, \IEEEauthorrefmark{} follows the standard LaTeX footnote +% symbol sequence convention. However, for arguments 10 and above, \IEEEauthorrefmark{} +% reverts to using lower case roman numerals, so it cannot overflow. Do note that you +% cannot use \footnotemark[] in place of \IEEEauthorrefmark{} within \author as the footnote +% symbols will have been turned off to prevent \thanks from creating footnote marks. +% \IEEEauthorrefmark{} produces a symbol that appears to LaTeX as having zero vertical +% height - this allows for a more compact line packing, but the user must ensure that +% the interline spacing is large enough to prevent \IEEEauthorrefmark{} from colliding +% with the text above. +% V1.7 make this a robust command +\DeclareRobustCommand*{\IEEEauthorrefmark}[1]{\raisebox{0pt}[0pt][0pt]{\textsuperscript{\footnotesize\ensuremath{\ifcase#1\or *\or \dagger\or \ddagger\or% + \mathsection\or \mathparagraph\or \|\or **\or \dagger\dagger% + \or \ddagger\ddagger \else\textsuperscript{\expandafter\romannumeral#1}\fi}}}} + + +% FONT CONTROLS AND SPACINGS FOR CONFERENCE MODE AUTHOR NAME AND AFFILIATION BLOCKS +% +% The default font styles for the author name and affiliation blocks (confmode) +\def\@IEEEauthorblockNstyle{\normalfont\@IEEEcompsocnotconfonly{\sffamily}\sublargesize\@IEEEcompsocconfonly{\large}} +\def\@IEEEauthorblockAstyle{\normalfont\@IEEEcompsocnotconfonly{\sffamily}\@IEEEcompsocconfonly{\itshape}\normalsize\@IEEEcompsocconfonly{\large}} +% The default if the user does not use an author block +\def\@IEEEauthordefaulttextstyle{\normalfont\@IEEEcompsocnotconfonly{\sffamily}\sublargesize} + +% spacing from title (or special paper notice) to author name blocks (confmode) +% can be negative +\def\@IEEEauthorblockconfadjspace{-0.25em} +% compsoc conferences need more space here +\@IEEEcompsocconfonly{\def\@IEEEauthorblockconfadjspace{0.75\@IEEEnormalsizeunitybaselineskip}} +\ifCLASSOPTIONconference\def\@IEEEauthorblockconfadjspace{20pt}\fi + +% spacing between name and affiliation blocks (confmode) +% This can be negative. +% IEEE doesn't want any added spacing here, but I will leave these +% controls in place in case they ever change their mind. +% Personally, I like 0.75ex. +%\def\@IEEEauthorblockNtopspace{0.75ex} +%\def\@IEEEauthorblockAtopspace{0.75ex} +\def\@IEEEauthorblockNtopspace{0.0ex} +\def\@IEEEauthorblockAtopspace{0.0ex} +% baseline spacing within name and affiliation blocks (confmode) +% must be positive, spacings below certain values will make +% the position of line of text sensitive to the contents of the +% line above it i.e., whether or not the prior line has descenders, +% subscripts, etc. For this reason it is a good idea to keep +% these above 2.6ex +\def\@IEEEauthorblockNinterlinespace{2.6ex} +\def\@IEEEauthorblockAinterlinespace{2.75ex} + +% This tracks the required strut size. +% See the \@IEEEauthorhalign command for the actual default value used. +\def\@IEEEauthorblockXinterlinespace{2.7ex} + +% variables to retain font size and style across groups +% values given here have no effect as they will be overwritten later +\gdef\@IEEESAVESTATEfontsize{10} +\gdef\@IEEESAVESTATEfontbaselineskip{12} +\gdef\@IEEESAVESTATEfontencoding{OT1} +\gdef\@IEEESAVESTATEfontfamily{ptm} +\gdef\@IEEESAVESTATEfontseries{m} +\gdef\@IEEESAVESTATEfontshape{n} + +% saves the current font attributes +\def\@IEEEcurfontSAVE{\global\let\@IEEESAVESTATEfontsize\f@size% +\global\let\@IEEESAVESTATEfontbaselineskip\f@baselineskip% +\global\let\@IEEESAVESTATEfontencoding\f@encoding% +\global\let\@IEEESAVESTATEfontfamily\f@family% +\global\let\@IEEESAVESTATEfontseries\f@series% +\global\let\@IEEESAVESTATEfontshape\f@shape} + +% restores the saved font attributes +\def\@IEEEcurfontRESTORE{\fontsize{\@IEEESAVESTATEfontsize}{\@IEEESAVESTATEfontbaselineskip}% +\fontencoding{\@IEEESAVESTATEfontencoding}% +\fontfamily{\@IEEESAVESTATEfontfamily}% +\fontseries{\@IEEESAVESTATEfontseries}% +\fontshape{\@IEEESAVESTATEfontshape}% +\selectfont} + + +% variable to indicate if the current block is the first block in the column +\newif\if@IEEEprevauthorblockincol \@IEEEprevauthorblockincolfalse + + +% the command places a strut with height and depth = \@IEEEauthorblockXinterlinespace +% we use this technique to have complete manual control over the spacing of the lines +% within the halign environment. +% We set the below baseline portion at 30%, the above +% baseline portion at 70% of the total length. +% Responds to changes in the document's \baselinestretch +\def\@IEEEauthorstrutrule{\@IEEEtrantmpdimenA\@IEEEauthorblockXinterlinespace% +\@IEEEtrantmpdimenA=\baselinestretch\@IEEEtrantmpdimenA% +\rule[-0.3\@IEEEtrantmpdimenA]{0pt}{\@IEEEtrantmpdimenA}} + + +% blocks to hold the authors' names and affilations. +% Makes formatting easy for conferences +% +% use real definitions in conference mode +% name block +\def\IEEEauthorblockN#1{\relax\@IEEEauthorblockNstyle% set the default text style +\gdef\@IEEEauthorblockXinterlinespace{0pt}% disable strut for spacer row +% the \expandafter hides the \cr in conditional tex, see the array.sty docs +% for details, probably not needed here as the \cr is in a macro +% do a spacer row if needed +\if@IEEEprevauthorblockincol\expandafter\@IEEEauthorblockNtopspaceline\fi +\global\@IEEEprevauthorblockincoltrue% we now have a block in this column +%restore the correct strut value +\gdef\@IEEEauthorblockXinterlinespace{\@IEEEauthorblockNinterlinespace}% +% input the author names +#1% +% end the row if the user did not already +\crcr} +% spacer row for names +\def\@IEEEauthorblockNtopspaceline{\cr\noalign{\vskip\@IEEEauthorblockNtopspace}} +% +% affiliation block +\def\IEEEauthorblockA#1{\relax\@IEEEauthorblockAstyle% set the default text style +\gdef\@IEEEauthorblockXinterlinespace{0pt}%disable strut for spacer row +% the \expandafter hides the \cr in conditional tex, see the array.sty docs +% for details, probably not needed here as the \cr is in a macro +% do a spacer row if needed +\if@IEEEprevauthorblockincol\expandafter\@IEEEauthorblockAtopspaceline\fi +\global\@IEEEprevauthorblockincoltrue% we now have a block in this column +%restore the correct strut value +\gdef\@IEEEauthorblockXinterlinespace{\@IEEEauthorblockAinterlinespace}% +% input the author affiliations +#1% +% end the row if the user did not already +\crcr} +% spacer row for affiliations +\def\@IEEEauthorblockAtopspaceline{\cr\noalign{\vskip\@IEEEauthorblockAtopspace}} + + +% allow papers to compile even if author blocks are used in modes other +% than conference or peerreviewca. For such cases, we provide dummy blocks. +\ifCLASSOPTIONconference +\else + \ifCLASSOPTIONpeerreviewca\else + % not conference or peerreviewca mode + \def\IEEEauthorblockN#1{#1}% + \def\IEEEauthorblockA#1{#1}% + \fi +\fi + + + +% we provide our own halign so as not to have to depend on tabular +\def\@IEEEauthorhalign{\@IEEEauthordefaulttextstyle% default text style + \lineskip=0pt\relax% disable line spacing + \lineskiplimit=0pt\relax% + \baselineskip=0pt\relax% + \@IEEEcurfontSAVE% save the current font + \mathsurround\z@\relax% no extra spacing around math + \let\\\@IEEEauthorhaligncr% replace newline with halign friendly one + \tabskip=0pt\relax% no column spacing + \everycr{}% ensure no problems here + \@IEEEprevauthorblockincolfalse% no author blocks yet + \def\@IEEEauthorblockXinterlinespace{2.7ex}% default interline space + \vtop\bgroup%vtop box + \halign\bgroup&\relax\hfil\@IEEEcurfontRESTORE\relax ##\relax + \hfil\@IEEEcurfontSAVE\@IEEEauthorstrutrule\cr} + +% ensure last line, exit from halign, close vbox +\def\end@IEEEauthorhalign{\crcr\egroup\egroup} + +% handle bogus star form +\def\@IEEEauthorhaligncr{{\ifnum0=`}\fi\@ifstar{\@@IEEEauthorhaligncr}{\@@IEEEauthorhaligncr}} + +% test and setup the optional argument to \\[] +\def\@@IEEEauthorhaligncr{\@testopt\@@@IEEEauthorhaligncr\z@skip} + +% end the line and do the optional spacer +\def\@@@IEEEauthorhaligncr[#1]{\ifnum0=`{\fi}\cr\noalign{\vskip#1\relax}} + + + +% flag to prevent multiple \and warning messages +\newif\if@IEEEWARNand +\@IEEEWARNandtrue + +% if in conference or peerreviewca modes, we support the use of \and as \author is a +% tabular environment, otherwise we warn the user that \and is invalid +% outside of conference or peerreviewca modes. +\def\and{\relax} % provide a bogus \and that we will then override + +\renewcommand{\and}[1][\relax]{\if@IEEEWARNand\typeout{** WARNING: \noexpand\and is valid only + when in conference or peerreviewca}\typeout{modes (line \the\inputlineno).}\fi\global\@IEEEWARNandfalse} + +\ifCLASSOPTIONconference% +\renewcommand{\and}[1][\hfill]{\end{@IEEEauthorhalign}#1\begin{@IEEEauthorhalign}}% +\fi +\ifCLASSOPTIONpeerreviewca +\renewcommand{\and}[1][\hfill]{\end{@IEEEauthorhalign}#1\begin{@IEEEauthorhalign}}% +\fi + + +% page clearing command +% based on LaTeX2e's \cleardoublepage, but allows different page styles +% for the inserted blank pages +\def\@IEEEcleardoublepage#1{\clearpage\if@twoside\ifodd\c@page\else +\hbox{}\thispagestyle{#1}\newpage\if@twocolumn\hbox{}\thispagestyle{#1}\newpage\fi\fi\fi} + + +% user command to invoke the title page +\def\maketitle{\par% + \begingroup% + \normalfont% + \def\thefootnote{}% the \thanks{} mark type is empty + \def\footnotemark{}% and kill space from \thanks within author + \let\@makefnmark\relax% V1.7, must *really* kill footnotemark to remove all \textsuperscript spacing as well. + \footnotesize% equal spacing between thanks lines + \footnotesep 0.7\baselineskip%see global setting of \footnotesep for more info + % V1.7 disable \thanks note indention for compsoc + \@IEEEcompsoconly{\long\def\@makefntext##1{\parindent 1em\noindent\hbox{\@makefnmark}##1}}% + \normalsize% + \ifCLASSOPTIONpeerreview + \newpage\global\@topnum\z@ \@maketitle\@IEEEstatictitlevskip\@IEEEaftertitletext% + \thispagestyle{IEEEpeerreviewcoverpagestyle}\@thanks% + \else + \if@twocolumn% + \ifCLASSOPTIONtechnote% + \newpage\global\@topnum\z@ \@maketitle\@IEEEstatictitlevskip\@IEEEaftertitletext% + \else + \twocolumn[\@maketitle\@IEEEstatictitlevskip\@IEEEaftertitletext]% + \fi + \else + \newpage\global\@topnum\z@ \@maketitle\@IEEEstatictitlevskip\@IEEEaftertitletext% + \fi + \thispagestyle{IEEEtitlepagestyle}\@thanks% + \fi + % pullup page for pubid if used. + \if@IEEEusingpubid + \enlargethispage{-\@IEEEpubidpullup}% + \fi + \endgroup + \setcounter{footnote}{0}\let\maketitle\relax\let\@maketitle\relax + \gdef\@thanks{}% + % v1.6b do not clear these as we will need the title again for peer review papers + % \gdef\@author{}\gdef\@title{}% + \let\thanks\relax} + + + +% V1.7 parbox to format \@IEEEcompsoctitleabstractindextext +\long\def\@IEEEcompsoctitleabstractindextextbox#1{\parbox{0.915\textwidth}{#1}} + +% formats the Title, authors names, affiliations and special paper notice +% THIS IS A CONTROLLED SPACING COMMAND! Do not allow blank lines or unintentional +% spaces to enter the definition - use % at the end of each line +\def\@maketitle{\newpage +\begingroup\centering +\ifCLASSOPTIONtechnote% technotes + {\bfseries\large\@IEEEcompsoconly{\sffamily}\@title\par}\vskip 1.3em{\lineskip .5em\@IEEEcompsoconly{\sffamily}\@author + \@IEEEspecialpapernotice\par{\@IEEEcompsoconly{\vskip 1.5em\relax + \@IEEEcompsoctitleabstractindextextbox{\@IEEEcompsoctitleabstractindextext}\par + \hfill\@IEEEcompsocdiamondline\hfill\hbox{}\par}}}\relax +\else% not a technote + \vskip0.2em{\Huge\@IEEEcompsoconly{\sffamily}\@IEEEcompsocconfonly{\normalfont\normalsize\vskip 2\@IEEEnormalsizeunitybaselineskip + \bfseries\Large}\@title\par}\vskip1.0em\par% + % V1.6 handle \author differently if in conference mode + \ifCLASSOPTIONconference% + {\@IEEEspecialpapernotice\mbox{}\vskip\@IEEEauthorblockconfadjspace% + \mbox{}\hfill\begin{@IEEEauthorhalign}\@author\end{@IEEEauthorhalign}\hfill\mbox{}\par}\relax + \else% peerreviewca, peerreview or journal + \ifCLASSOPTIONpeerreviewca + % peerreviewca handles author names just like conference mode + {\@IEEEcompsoconly{\sffamily}\@IEEEspecialpapernotice\mbox{}\vskip\@IEEEauthorblockconfadjspace% + \mbox{}\hfill\begin{@IEEEauthorhalign}\@author\end{@IEEEauthorhalign}\hfill\mbox{}\par + {\@IEEEcompsoconly{\vskip 1.5em\relax + \@IEEEcompsoctitleabstractindextextbox{\@IEEEcompsoctitleabstractindextext}\par\hfill + \@IEEEcompsocdiamondline\hfill\hbox{}\par}}}\relax + \else% journal or peerreview + {\lineskip.5em\@IEEEcompsoconly{\sffamily}\sublargesize\@author\@IEEEspecialpapernotice\par + {\@IEEEcompsoconly{\vskip 1.5em\relax + \@IEEEcompsoctitleabstractindextextbox{\@IEEEcompsoctitleabstractindextext}\par\hfill + \@IEEEcompsocdiamondline\hfill\hbox{}\par}}}\relax + \fi + \fi +\fi\par\endgroup} + + + +% V1.7 Computer Society "diamond line" which follows index terms for nonconference papers +\def\@IEEEcompsocdiamondline{\vrule depth 0pt height 0.5pt width 4cm\hspace{7.5pt}% +\raisebox{-3.5pt}{\fontfamily{pzd}\fontencoding{U}\fontseries{m}\fontshape{n}\fontsize{11}{12}\selectfont\char70}% +\hspace{7.5pt}\vrule depth 0pt height 0.5pt width 4cm\relax} + +% V1.7 standard LateX2e \thanks, but with \itshape under compsoc. Also make it a \long\def +% We also need to trigger the one-shot footnote rule +\def\@IEEEtriggeroneshotfootnoterule{\global\@IEEEenableoneshotfootnoteruletrue} + + +\long\def\thanks#1{\footnotemark + \protected@xdef\@thanks{\@thanks + \protect\footnotetext[\the\c@footnote]{\@IEEEcompsoconly{\itshape + \protect\@IEEEtriggeroneshotfootnoterule\relax}\ignorespaces#1}}} +\let\@thanks\@empty + +% V1.7 allow \author to contain \par's. This is needed to allow \thanks to contain \par. +\long\def\author#1{\gdef\@author{#1}} + + +% in addition to setting up IEEEitemize, we need to remove a baselineskip space above and +% below it because \list's \pars introduce blank lines because of the footnote struts. +\def\@IEEEsetupcompsocitemizelist{\def\labelitemi{$\bullet$}% +\setlength{\IEEElabelindent}{0pt}\setlength{\parskip}{0pt}% +\setlength{\partopsep}{0pt}\setlength{\topsep}{0.5\baselineskip}\vspace{-1\baselineskip}\relax} + + +% flag for fake non-compsoc \IEEEcompsocthanksitem - prevents line break on very first item +\newif\if@IEEEbreakcompsocthanksitem \@IEEEbreakcompsocthanksitemfalse + +\ifCLASSOPTIONcompsoc +% V1.7 compsoc bullet item \thanks +% also, we need to redefine this to destroy the argument in \@IEEEdynamictitlevspace +\long\def\IEEEcompsocitemizethanks#1{\relax\@IEEEbreakcompsocthanksitemfalse\footnotemark + \protected@xdef\@thanks{\@thanks + \protect\footnotetext[\the\c@footnote]{\itshape\protect\@IEEEtriggeroneshotfootnoterule + {\let\IEEEiedlistdecl\relax\protect\begin{IEEEitemize}[\protect\@IEEEsetupcompsocitemizelist]\ignorespaces#1\relax + \protect\end{IEEEitemize}}\protect\vspace{-1\baselineskip}}}} +\DeclareRobustCommand*{\IEEEcompsocthanksitem}{\item} +\else +% non-compsoc, allow for dual compilation via rerouting to normal \thanks +\long\def\IEEEcompsocitemizethanks#1{\thanks{#1}} +% redirect to "pseudo-par" \hfil\break\indent after swallowing [] from \IEEEcompsocthanksitem[] +\DeclareRobustCommand{\IEEEcompsocthanksitem}{\@ifnextchar [{\@IEEEthanksswallowoptionalarg}% +{\@IEEEthanksswallowoptionalarg[\relax]}} +% be sure and break only after first item, be sure and ignore spaces after optional argument +\def\@IEEEthanksswallowoptionalarg[#1]{\relax\if@IEEEbreakcompsocthanksitem\hfil\break +\indent\fi\@IEEEbreakcompsocthanksitemtrue\ignorespaces} +\fi + + +% V1.6b define the \IEEEpeerreviewmaketitle as needed +\ifCLASSOPTIONpeerreview +\def\IEEEpeerreviewmaketitle{\@IEEEcleardoublepage{empty}% +\ifCLASSOPTIONtwocolumn +\twocolumn[\@IEEEpeerreviewmaketitle\@IEEEdynamictitlevspace] +\else +\newpage\@IEEEpeerreviewmaketitle\@IEEEstatictitlevskip +\fi +\thispagestyle{IEEEtitlepagestyle}} +\else +% \IEEEpeerreviewmaketitle does nothing if peer review option has not been selected +\def\IEEEpeerreviewmaketitle{\relax} +\fi + +% peerreview formats the repeated title like the title in journal papers. +\def\@IEEEpeerreviewmaketitle{\begin{center}\@IEEEcompsoconly{\sffamily}% +\normalfont\normalsize\vskip0.2em{\Huge\@title\par}\vskip1.0em\par +\end{center}} + + + +% V1.6 +% this is a static rubber spacer between the title/authors and the main text +% used for single column text, or when the title appears in the first column +% of two column text (technotes). +\def\@IEEEstatictitlevskip{{\normalfont\normalsize +% adjust spacing to next text +% v1.6b handle peer review papers +\ifCLASSOPTIONpeerreview +% for peer review papers, the same value is used for both title pages +% regardless of the other paper modes + \vskip 1\baselineskip plus 0.375\baselineskip minus 0.1875\baselineskip +\else + \ifCLASSOPTIONconference% conference + \vskip 0.6\baselineskip + \else% + \ifCLASSOPTIONtechnote% technote + \vskip 1\baselineskip plus 0.375\baselineskip minus 0.1875\baselineskip% + \else% journal uses more space + \vskip 2.5\baselineskip plus 0.75\baselineskip minus 0.375\baselineskip% + \fi + \fi +\fi}} + + +% V1.6 +% This is a dynamically determined rigid spacer between the title/authors +% and the main text. This is used only for single column titles over two +% column text (most common) +% This is bit tricky because we have to ensure that the textheight of the +% main text is an integer multiple of \baselineskip +% otherwise underfull vbox problems may develop in the second column of the +% text on the titlepage +% The possible use of \IEEEpubid must also be taken into account. +\def\@IEEEdynamictitlevspace{{% + % we run within a group so that all the macros can be forgotten when we are done + \long\def\thanks##1{\relax}%don't allow \thanks to run when we evaluate the vbox height + \long\def\IEEEcompsocitemizethanks##1{\relax}%don't allow \IEEEcompsocitemizethanks to run when we evaluate the vbox height + \normalfont\normalsize% we declare more descriptive variable names + \let\@IEEEmaintextheight=\@IEEEtrantmpdimenA%height of the main text columns + \let\@IEEEINTmaintextheight=\@IEEEtrantmpdimenB%height of the main text columns with integer # lines + % set the nominal and minimum values for the title spacer + % the dynamic algorithm will not allow the spacer size to + % become less than \@IEEEMINtitlevspace - instead it will be + % lengthened + % default to journal values + \def\@IEEENORMtitlevspace{2.5\baselineskip}% + \def\@IEEEMINtitlevspace{2\baselineskip}% + % conferences and technotes need tighter spacing + \ifCLASSOPTIONconference%conference + \def\@IEEENORMtitlevspace{1\baselineskip}% + \def\@IEEEMINtitlevspace{0.75\baselineskip}% + \fi + \ifCLASSOPTIONtechnote%technote + \def\@IEEENORMtitlevspace{1\baselineskip}% + \def\@IEEEMINtitlevspace{0.75\baselineskip}% + \fi% + % get the height that the title will take up + \ifCLASSOPTIONpeerreview + \settoheight{\@IEEEmaintextheight}{\vbox{\hsize\textwidth \@IEEEpeerreviewmaketitle}}% + \else + \settoheight{\@IEEEmaintextheight}{\vbox{\hsize\textwidth \@maketitle}}% + \fi + \@IEEEmaintextheight=-\@IEEEmaintextheight% title takes away from maintext, so reverse sign + % add the height of the page textheight + \advance\@IEEEmaintextheight by \textheight% + % correct for title pages using pubid + \ifCLASSOPTIONpeerreview\else + % peerreview papers use the pubid on the cover page only. + % And the cover page uses a static spacer. + \if@IEEEusingpubid\advance\@IEEEmaintextheight by -\@IEEEpubidpullup\fi + \fi% + % subtract off the nominal value of the title bottom spacer + \advance\@IEEEmaintextheight by -\@IEEENORMtitlevspace% + % \topskip takes away some too + \advance\@IEEEmaintextheight by -\topskip% + % calculate the column height of the main text for lines + % now we calculate the main text height as if holding + % an integer number of \normalsize lines after the first + % and discard any excess fractional remainder + % we subtracted the first line, because the first line + % is placed \topskip into the maintext, not \baselineskip like the + % rest of the lines. + \@IEEEINTmaintextheight=\@IEEEmaintextheight% + \divide\@IEEEINTmaintextheight by \baselineskip% + \multiply\@IEEEINTmaintextheight by \baselineskip% + % now we calculate how much the title spacer height will + % have to be reduced from nominal (\@IEEEREDUCEmaintextheight is always + % a positive value) so that the maintext area will contain an integer + % number of normal size lines + % we change variable names here (to avoid confusion) as we no longer + % need \@IEEEINTmaintextheight and can reuse its dimen register + \let\@IEEEREDUCEmaintextheight=\@IEEEINTmaintextheight% + \advance\@IEEEREDUCEmaintextheight by -\@IEEEmaintextheight% + \advance\@IEEEREDUCEmaintextheight by \baselineskip% + % this is the calculated height of the spacer + % we change variable names here (to avoid confusion) as we no longer + % need \@IEEEmaintextheight and can reuse its dimen register + \let\@IEEECOMPENSATElen=\@IEEEmaintextheight% + \@IEEECOMPENSATElen=\@IEEENORMtitlevspace% set the nominal value + % we go with the reduced length if it is smaller than an increase + \ifdim\@IEEEREDUCEmaintextheight < 0.5\baselineskip\relax% + \advance\@IEEECOMPENSATElen by -\@IEEEREDUCEmaintextheight% + % if the resulting spacer is too small back out and go with an increase instead + \ifdim\@IEEECOMPENSATElen<\@IEEEMINtitlevspace\relax% + \advance\@IEEECOMPENSATElen by \baselineskip% + \fi% + \else% + % go with an increase because it is closer to the nominal than a decrease + \advance\@IEEECOMPENSATElen by -\@IEEEREDUCEmaintextheight% + \advance\@IEEECOMPENSATElen by \baselineskip% + \fi% + % set the calculated rigid spacer + \vspace{\@IEEECOMPENSATElen}}} + + + +% V1.6 +% we allow the user access to the last part of the title area +% useful in emergencies such as when a different spacing is needed +% This text is NOT compensated for in the dynamic sizer. +\let\@IEEEaftertitletext=\relax +\long\def\IEEEaftertitletext#1{\def\@IEEEaftertitletext{#1}} + +% V1.7 provide a way for users to enter abstract and keywords +% into the onecolumn title are. This text is compensated for +% in the dynamic sizer. +\let\@IEEEcompsoctitleabstractindextext=\relax +\long\def\IEEEcompsoctitleabstractindextext#1{\def\@IEEEcompsoctitleabstractindextext{#1}} +% V1.7 provide a way for users to get the \@IEEEcompsoctitleabstractindextext if +% not in compsoc journal mode - this way abstract and keywords can be placed +% in their conventional position if not in compsoc mode. +\def\IEEEdisplaynotcompsoctitleabstractindextext{% +\ifCLASSOPTIONcompsoc% display if compsoc conf +\ifCLASSOPTIONconference\@IEEEcompsoctitleabstractindextext\fi +\else% or if not compsoc +\@IEEEcompsoctitleabstractindextext\fi} + + +% command to allow alteration of baselinestretch, but only if the current +% baselineskip is unity. Used to tweak the compsoc abstract and keywords line spacing. +\def\@IEEEtweakunitybaselinestretch#1{{\def\baselinestretch{1}\selectfont +\global\@tempskipa\baselineskip}\ifnum\@tempskipa=\baselineskip% +\def\baselinestretch{#1}\selectfont\fi\relax} + + +% abstract and keywords are in \small, except +% for 9pt docs in which they are in \footnotesize +% Because 9pt docs use an 8pt footnotesize, \small +% becomes a rather awkward 8.5pt +\def\@IEEEabskeysecsize{\small} +\ifx\CLASSOPTIONpt\@IEEEptsizenine + \def\@IEEEabskeysecsize{\footnotesize} +\fi + +% compsoc journals use \footnotesize, compsoc conferences use normalsize +\@IEEEcompsoconly{\def\@IEEEabskeysecsize{\footnotesize}} +\@IEEEcompsocconfonly{\def\@IEEEabskeysecsize{\normalsize}} + + + + +% V1.6 have abstract and keywords strip leading spaces, pars and newlines +% so that spacing is more tightly controlled. +\def\abstract{\normalfont + \if@twocolumn + \par\@IEEEabskeysecsize\bfseries\leavevmode\kern-1pt\textit{\abstractname}---\relax + \else + \begin{center}\vspace{-1.78ex}\@IEEEabskeysecsize\textbf{\abstractname}\end{center}\quotation\@IEEEabskeysecsize + \fi\@IEEEgobbleleadPARNLSP} +% V1.6 IEEE wants only 1 pica from end of abstract to introduction heading when in +% conference mode (the heading already has this much above it) +\def\endabstract{\relax\ifCLASSOPTIONconference\vspace{0ex}\else\vspace{1.34ex}\fi\par\if@twocolumn\else\endquotation\fi + \normalfont\normalsize} + +\def\IEEEkeywords{\normalfont + \if@twocolumn + \@IEEEabskeysecsize\bfseries\leavevmode\kern-1pt\textit{\IEEEkeywordsname}---\relax + \else + \begin{center}\@IEEEabskeysecsize\textbf{\IEEEkeywordsname}\end{center}\quotation\@IEEEabskeysecsize + \fi\itshape\@IEEEgobbleleadPARNLSP} +\def\endIEEEkeywords{\relax\ifCLASSOPTIONtechnote\vspace{1.34ex}\else\vspace{0.5ex}\fi + \par\if@twocolumn\else\endquotation\fi% + \normalfont\normalsize} + +% V1.7 compsoc keywords index terms +\ifCLASSOPTIONcompsoc + \ifCLASSOPTIONconference% compsoc conference +\def\abstract{\normalfont + \begin{center}\@IEEEabskeysecsize\textbf{\large\abstractname}\end{center}\vskip 0.5\baselineskip plus 0.1\baselineskip minus 0.1\baselineskip + \if@twocolumn\else\quotation\fi\itshape\@IEEEabskeysecsize% + \par\@IEEEgobbleleadPARNLSP} +\def\IEEEkeywords{\normalfont\vskip 1.5\baselineskip plus 0.25\baselineskip minus 0.25\baselineskip + \begin{center}\@IEEEabskeysecsize\textbf{\large\IEEEkeywordsname}\end{center}\vskip 0.5\baselineskip plus 0.1\baselineskip minus 0.1\baselineskip + \if@twocolumn\else\quotation\fi\itshape\@IEEEabskeysecsize% + \par\@IEEEgobbleleadPARNLSP} + \else% compsoc not conference +\def\abstract{\normalfont\@IEEEtweakunitybaselinestretch{1.15}\sffamily + \if@twocolumn + \@IEEEabskeysecsize\noindent\textbf{\abstractname}---\relax + \else + \begin{center}\vspace{-1.78ex}\@IEEEabskeysecsize\textbf{\abstractname}\end{center}\quotation\@IEEEabskeysecsize% + \fi\@IEEEgobbleleadPARNLSP} +\def\IEEEkeywords{\normalfont\@IEEEtweakunitybaselinestretch{1.15}\sffamily + \if@twocolumn + \@IEEEabskeysecsize\vskip 0.5\baselineskip plus 0.25\baselineskip minus 0.25\baselineskip\noindent + \textbf{\IEEEkeywordsname}---\relax + \else + \begin{center}\@IEEEabskeysecsize\textbf{\IEEEkeywordsname}\end{center}\quotation\@IEEEabskeysecsize% + \fi\@IEEEgobbleleadPARNLSP} + \fi +\fi + + + +% gobbles all leading \, \\ and \par, upon finding first token that +% is not a \ , \\ or a \par, it ceases and returns that token +% +% used to strip leading \, \\ and \par from the input +% so that such things in the beginning of an environment will not +% affect the formatting of the text +\long\def\@IEEEgobbleleadPARNLSP#1{\let\@IEEEswallowthistoken=0% +\let\@IEEEgobbleleadPARNLSPtoken#1% +\let\@IEEEgobbleleadPARtoken=\par% +\let\@IEEEgobbleleadNLtoken=\\% +\let\@IEEEgobbleleadSPtoken=\ % +\def\@IEEEgobbleleadSPMACRO{\ }% +\ifx\@IEEEgobbleleadPARNLSPtoken\@IEEEgobbleleadPARtoken% +\let\@IEEEswallowthistoken=1% +\fi% +\ifx\@IEEEgobbleleadPARNLSPtoken\@IEEEgobbleleadNLtoken% +\let\@IEEEswallowthistoken=1% +\fi% +\ifx\@IEEEgobbleleadPARNLSPtoken\@IEEEgobbleleadSPtoken% +\let\@IEEEswallowthistoken=1% +\fi% +% a control space will come in as a macro +% when it is the last one on a line +\ifx\@IEEEgobbleleadPARNLSPtoken\@IEEEgobbleleadSPMACRO% +\let\@IEEEswallowthistoken=1% +\fi% +% if we have to swallow this token, do so and taste the next one +% else spit it out and stop gobbling +\ifx\@IEEEswallowthistoken 1\let\@IEEEnextgobbleleadPARNLSP=\@IEEEgobbleleadPARNLSP\else% +\let\@IEEEnextgobbleleadPARNLSP=#1\fi% +\@IEEEnextgobbleleadPARNLSP}% + + + + +% TITLING OF SECTIONS +\def\@IEEEsectpunct{:\ \,} % Punctuation after run-in section heading (headings which are + % part of the paragraphs), need little bit more than a single space + % spacing from section number to title +% compsoc conferences use regular period/space punctuation +\ifCLASSOPTIONcompsoc +\ifCLASSOPTIONconference +\def\@IEEEsectpunct{.\ } +\fi\fi + +\def\@seccntformat#1{\hb@xt@ 1.4em{\csname the#1dis\endcsname\hss\relax}} +\def\@seccntformatinl#1{\hb@xt@ 1.1em{\csname the#1dis\endcsname\hss\relax}} +\def\@seccntformatch#1{\csname the#1dis\endcsname\hskip 1em\relax} + +\ifCLASSOPTIONcompsoc +% compsoc journals need extra spacing +\ifCLASSOPTIONconference\else +\def\@seccntformat#1{\csname the#1dis\endcsname\hskip 1em\relax} +\fi\fi + +%v1.7 put {} after #6 to allow for some types of user font control +%and use \@@par rather than \par +\def\@sect#1#2#3#4#5#6[#7]#8{% + \ifnum #2>\c@secnumdepth + \let\@svsec\@empty + \else + \refstepcounter{#1}% + % load section label and spacer into \@svsec + \ifnum #2=1 + \protected@edef\@svsec{\@seccntformatch{#1}\relax}% + \else + \ifnum #2>2 + \protected@edef\@svsec{\@seccntformatinl{#1}\relax}% + \else + \protected@edef\@svsec{\@seccntformat{#1}\relax}% + \fi + \fi + \fi% + \@tempskipa #5\relax + \ifdim \@tempskipa>\z@% tempskipa determines whether is treated as a high + \begingroup #6{\relax% or low level heading + \noindent % subsections are NOT indented + % print top level headings. \@svsec is label, #8 is heading title + % IEEE does not block indent the section title text, it flows like normal + {\hskip #3\relax\@svsec}{\interlinepenalty \@M #8\@@par}}% + \endgroup + \addcontentsline{toc}{#1}{\ifnum #2>\c@secnumdepth\relax\else + \protect\numberline{\csname the#1\endcsname}\fi#7}% + \else % printout low level headings + % svsechd seems to swallow the trailing space, protect it with \mbox{} + % got rid of sectionmark stuff + \def\@svsechd{#6{\hskip #3\relax\@svsec #8\@IEEEsectpunct\mbox{}}% + \addcontentsline{toc}{#1}{\ifnum #2>\c@secnumdepth\relax\else + \protect\numberline{\csname the#1\endcsname}\fi#7}}% + \fi%skip down + \@xsect{#5}} + + +% section* handler +%v1.7 put {} after #4 to allow for some types of user font control +%and use \@@par rather than \par +\def\@ssect#1#2#3#4#5{\@tempskipa #3\relax + \ifdim \@tempskipa>\z@ + %\begingroup #4\@hangfrom{\hskip #1}{\interlinepenalty \@M #5\par}\endgroup + % IEEE does not block indent the section title text, it flows like normal + \begingroup \noindent #4{\relax{\hskip #1}{\interlinepenalty \@M #5\@@par}}\endgroup + % svsechd swallows the trailing space, protect it with \mbox{} + \else \def\@svsechd{#4{\hskip #1\relax #5\@IEEEsectpunct\mbox{}}}\fi + \@xsect{#3}} + + +%% SECTION heading spacing and font +%% +% arguments are: #1 - sectiontype name +% (for \@sect) #2 - section level +% #3 - section heading indent +% #4 - top separation (absolute value used, neg indicates not to indent main text) +% If negative, make stretch parts negative too! +% #5 - (absolute value used) positive: bottom separation after heading, +% negative: amount to indent main text after heading +% Both #4 and #5 negative means to indent main text and use negative top separation +% #6 - font control +% You've got to have \normalfont\normalsize in the font specs below to prevent +% trouble when you do something like: +% \section{Note}{\ttfamily TT-TEXT} is known to ... +% IEEE sometimes REALLY stretches the area before a section +% heading by up to about 0.5in. However, it may not be a good +% idea to let LaTeX have quite this much rubber. +\ifCLASSOPTIONconference% +% IEEE wants section heading spacing to decrease for conference mode +\def\section{\@startsection{section}{1}{\z@}{1.5ex plus 1.5ex minus 0.5ex}% +{1sp}{\normalfont\normalsize\centering\scshape}}% +\def\subsection{\@startsection{subsection}{2}{\z@}{1.5ex plus 1.5ex minus 0.5ex}% +{1sp}{\normalfont\normalsize\itshape}}% +\else % for journals +\def\section{\@startsection{section}{1}{\z@}{3.0ex plus 1.5ex minus 1.5ex}% V1.6 3.0ex from 3.5ex +{0.7ex plus 1ex minus 0ex}{\normalfont\normalsize\centering\scshape}}% +\def\subsection{\@startsection{subsection}{2}{\z@}{3.5ex plus 1.5ex minus 1.5ex}% +{0.7ex plus .5ex minus 0ex}{\normalfont\normalsize\itshape}}% +\fi + +% for both journals and conferences +% decided to put in a little rubber above the section, might help somebody +\def\subsubsection{\@startsection{subsubsection}{3}{\parindent}{0ex plus 0.1ex minus 0.1ex}% +{0ex}{\normalfont\normalsize\itshape}}% +\def\paragraph{\@startsection{paragraph}{4}{2\parindent}{0ex plus 0.1ex minus 0.1ex}% +{0ex}{\normalfont\normalsize\itshape}}% + + +% compsoc +\ifCLASSOPTIONcompsoc +\ifCLASSOPTIONconference +% compsoc conference +\def\section{\@startsection{section}{1}{\z@}{1\baselineskip plus 0.25\baselineskip minus 0.25\baselineskip}% +{1\baselineskip plus 0.25\baselineskip minus 0.25\baselineskip}{\normalfont\large\bfseries}}% +\def\subsection{\@startsection{subsection}{2}{\z@}{1\baselineskip plus 0.25\baselineskip minus 0.25\baselineskip}% +{1\baselineskip plus 0.25\baselineskip minus 0.25\baselineskip}{\normalfont\sublargesize\bfseries}}% +\def\subsubsection{\@startsection{subsubsection}{3}{\z@}{1\baselineskip plus 0.25\baselineskip minus 0.25\baselineskip}% +{0ex}{\normalfont\normalsize\bfseries}}% +\def\paragraph{\@startsection{paragraph}{4}{2\parindent}{0ex plus 0.1ex minus 0.1ex}% +{0ex}{\normalfont\normalsize}}% +\else% compsoc journals +% use negative top separation as compsoc journals do not indent paragraphs after section titles +\def\section{\@startsection{section}{1}{\z@}{-3ex plus -2ex minus -1.5ex}% +{0.7ex plus 1ex minus 0ex}{\normalfont\large\sffamily\bfseries\scshape}}% +% Note that subsection and smaller may not be correct for the Computer Society, +% I have to look up an example. +\def\subsection{\@startsection{subsection}{2}{\z@}{-3.5ex plus -1.5ex minus -1.5ex}% +{0.7ex plus .5ex minus 0ex}{\normalfont\normalsize\sffamily\bfseries}}% +\def\subsubsection{\@startsection{subsubsection}{3}{\z@}{-2.5ex plus -1ex minus -1ex}% +{0.5ex plus 0.5ex minus 0ex}{\normalfont\normalsize\sffamily\itshape}}% +\def\paragraph{\@startsection{paragraph}{4}{2\parindent}{-0ex plus -0.1ex minus -0.1ex}% +{0ex}{\normalfont\normalsize}}% +\fi\fi + + + + +%% ENVIRONMENTS +% "box" symbols at end of proofs +\def\IEEEQEDclosed{\mbox{\rule[0pt]{1.3ex}{1.3ex}}} % for a filled box +% V1.6 some journals use an open box instead that will just fit around a closed one +\def\IEEEQEDopen{{\setlength{\fboxsep}{0pt}\setlength{\fboxrule}{0.2pt}\fbox{\rule[0pt]{0pt}{1.3ex}\rule[0pt]{1.3ex}{0pt}}}} +\ifCLASSOPTIONcompsoc +\def\IEEEQED{\IEEEQEDopen} % default to open for compsoc +\else +\def\IEEEQED{\IEEEQEDclosed} % otherwise default to closed +\fi + +% v1.7 name change to avoid namespace collision with amsthm. Also add support +% for an optional argument. +\def\IEEEproof{\@ifnextchar[{\@IEEEproof}{\@IEEEproof[\IEEEproofname]}} +\def\@IEEEproof[#1]{\par\noindent\hspace{2em}{\itshape #1: }} +\def\endIEEEproof{\hspace*{\fill}~\IEEEQED\par} + + +%\itemindent is set to \z@ by list, so define new temporary variable +\newdimen\@IEEEtmpitemindent +\def\@begintheorem#1#2{\@IEEEtmpitemindent\itemindent\topsep 0pt\rmfamily\trivlist% + \item[\hskip \labelsep{\indent\itshape #1\ #2:}]\itemindent\@IEEEtmpitemindent} +\def\@opargbegintheorem#1#2#3{\@IEEEtmpitemindent\itemindent\topsep 0pt\rmfamily \trivlist% +% V1.6 IEEE is back to using () around theorem names which are also in italics +% Thanks to Christian Peel for reporting this. + \item[\hskip\labelsep{\indent\itshape #1\ #2\ (#3):}]\itemindent\@IEEEtmpitemindent} +% V1.7 remove bogus \unskip that caused equations in theorems to collide with +% lines below. +\def\@endtheorem{\endtrivlist} + +% V1.6 +% display command for the section the theorem is in - so that \thesection +% is not used as this will be in Roman numerals when we want arabic. +% LaTeX2e uses \def\@thmcounter#1{\noexpand\arabic{#1}} for the theorem number +% (second part) display and \def\@thmcountersep{.} as a separator. +% V1.7 intercept calls to the section counter and reroute to \@IEEEthmcounterinsection +% to allow \appendix(ices} to override as needed. +% +% special handler for sections, allows appendix(ices) to override +\gdef\@IEEEthmcounterinsection#1{\arabic{#1}} +% string macro +\edef\@IEEEstringsection{section} + +% redefine the #1#2[#3] form of newtheorem to use a hook to \@IEEEthmcounterinsection +% if section in_counter is used +\def\@xnthm#1#2[#3]{% + \expandafter\@ifdefinable\csname #1\endcsname + {\@definecounter{#1}\@newctr{#1}[#3]% + \edef\@IEEEstringtmp{#3} + \ifx\@IEEEstringtmp\@IEEEstringsection + \expandafter\xdef\csname the#1\endcsname{% + \noexpand\@IEEEthmcounterinsection{#3}\@thmcountersep + \@thmcounter{#1}}% + \else + \expandafter\xdef\csname the#1\endcsname{% + \expandafter\noexpand\csname the#3\endcsname \@thmcountersep + \@thmcounter{#1}}% + \fi + \global\@namedef{#1}{\@thm{#1}{#2}}% + \global\@namedef{end#1}{\@endtheorem}}} + + + +%% SET UP THE DEFAULT PAGESTYLE +\ps@headings +\pagenumbering{arabic} + +% normally the page counter starts at 1 +\setcounter{page}{1} +% however, for peerreview the cover sheet is page 0 or page -1 +% (for duplex printing) +\ifCLASSOPTIONpeerreview + \if@twoside + \setcounter{page}{-1} + \else + \setcounter{page}{0} + \fi +\fi + +% standard book class behavior - let bottom line float up and down as +% needed when single sided +\ifCLASSOPTIONtwoside\else\raggedbottom\fi +% if two column - turn on twocolumn, allow word spacings to stretch more and +% enforce a rigid position for the last lines +\ifCLASSOPTIONtwocolumn +% the peer review option delays invoking twocolumn + \ifCLASSOPTIONpeerreview\else + \twocolumn + \fi +\sloppy +\flushbottom +\fi + + + + +% \APPENDIX and \APPENDICES definitions + +% This is the \@ifmtarg command from the LaTeX ifmtarg package +% by Peter Wilson (CUA) and Donald Arseneau +% \@ifmtarg is used to determine if an argument to a command +% is present or not. +% For instance: +% \@ifmtarg{#1}{\typeout{empty}}{\typeout{has something}} +% \@ifmtarg is used with our redefined \section command if +% \appendices is invoked. +% The command \section will behave slightly differently depending +% on whether the user specifies a title: +% \section{My appendix title} +% or not: +% \section{} +% This way, we can eliminate the blank lines where the title +% would be, and the unneeded : after Appendix in the table of +% contents +\begingroup +\catcode`\Q=3 +\long\gdef\@ifmtarg#1{\@xifmtarg#1QQ\@secondoftwo\@firstoftwo\@nil} +\long\gdef\@xifmtarg#1#2Q#3#4#5\@nil{#4} +\endgroup +% end of \@ifmtarg defs + + +% V1.7 +% command that allows the one time saving of the original definition +% of section to \@IEEEappendixsavesection for \appendix or \appendices +% we don't save \section here as it may be redefined later by other +% packages (hyperref.sty, etc.) +\def\@IEEEsaveoriginalsectiononce{\let\@IEEEappendixsavesection\section +\let\@IEEEsaveoriginalsectiononce\relax} + +% neat trick to grab and process the argument from \section{argument} +% we process differently if the user invoked \section{} with no +% argument (title) +% note we reroute the call to the old \section* +\def\@IEEEprocessthesectionargument#1{% +\@ifmtarg{#1}{% +\@IEEEappendixsavesection*{\appendixname~\thesectiondis}% +\addcontentsline{toc}{section}{\appendixname~\thesection}}{% +\@IEEEappendixsavesection*{\appendixname~\thesectiondis \\* #1}% +\addcontentsline{toc}{section}{\appendixname~\thesection: #1}}} + +% we use this if the user calls \section{} after +% \appendix-- which has no meaning. So, we ignore the +% command and its argument. Then, warn the user. +\def\@IEEEdestroythesectionargument#1{\typeout{** WARNING: Ignoring useless +\protect\section\space in Appendix (line \the\inputlineno).}} + + +% remember \thesection forms will be displayed in \ref calls +% and in the Table of Contents. +% The \sectiondis form is used in the actual heading itself + +% appendix command for one single appendix +% normally has no heading. However, if you want a +% heading, you can do so via the optional argument: +% \appendix[Optional Heading] +\def\appendix{\relax} +\renewcommand{\appendix}[1][]{\@IEEEsaveoriginalsectiononce\par + % v1.6 keep hyperref's identifiers unique + \gdef\theHsection{Appendix.A}% + % v1.6 adjust hyperref's string name for the section + \xdef\Hy@chapapp{appendix}% + \setcounter{section}{0}% + \setcounter{subsection}{0}% + \setcounter{subsubsection}{0}% + \setcounter{paragraph}{0}% + \gdef\thesection{A}% + \gdef\thesectiondis{}% + \gdef\thesubsection{\Alph{subsection}}% + \gdef\@IEEEthmcounterinsection##1{A} + \refstepcounter{section}% update the \ref counter + \@ifmtarg{#1}{\@IEEEappendixsavesection*{\appendixname}% + \addcontentsline{toc}{section}{\appendixname}}{% + \@IEEEappendixsavesection*{\appendixname~\\* #1}% + \addcontentsline{toc}{section}{\appendixname: #1}}% + % redefine \section command for appendix + % leave \section* as is + \def\section{\@ifstar{\@IEEEappendixsavesection*}{% + \@IEEEdestroythesectionargument}}% throw out the argument + % of the normal form +} + + + +% appendices command for multiple appendices +% user then calls \section with an argument (possibly empty) to +% declare the individual appendices +\def\appendices{\@IEEEsaveoriginalsectiononce\par + % v1.6 keep hyperref's identifiers unique + \gdef\theHsection{Appendix.\Alph{section}}% + % v1.6 adjust hyperref's string name for the section + \xdef\Hy@chapapp{appendix}% + \setcounter{section}{-1}% we want \refstepcounter to use section 0 + \setcounter{subsection}{0}% + \setcounter{subsubsection}{0}% + \setcounter{paragraph}{0}% + \ifCLASSOPTIONromanappendices% + \gdef\thesection{\Roman{section}}% + \gdef\thesectiondis{\Roman{section}}% + \@IEEEcompsocconfonly{\gdef\thesectiondis{\Roman{section}.}}% + \gdef\@IEEEthmcounterinsection##1{A\arabic{##1}} + \else% + \gdef\thesection{\Alph{section}}% + \gdef\thesectiondis{\Alph{section}}% + \@IEEEcompsocconfonly{\gdef\thesectiondis{\Alph{section}.}}% + \gdef\@IEEEthmcounterinsection##1{\Alph{##1}} + \fi% + \refstepcounter{section}% update the \ref counter + \setcounter{section}{0}% NEXT \section will be the FIRST appendix + % redefine \section command for appendices + % leave \section* as is + \def\section{\@ifstar{\@IEEEappendixsavesection*}{% process the *-form + \refstepcounter{section}% or is a new section so, + \@IEEEprocessthesectionargument}}% process the argument + % of the normal form +} + + + +% \IEEEPARstart +% Definition for the big two line drop cap letter at the beginning of the +% first paragraph of journal papers. The first argument is the first letter +% of the first word, the second argument is the remaining letters of the +% first word which will be rendered in upper case. +% In V1.6 this has been completely rewritten to: +% +% 1. no longer have problems when the user begins an environment +% within the paragraph that uses \IEEEPARstart. +% 2. auto-detect and use the current font family +% 3. revise handling of the space at the end of the first word so that +% interword glue will now work as normal. +% 4. produce correctly aligned edges for the (two) indented lines. +% +% We generalize things via control macros - playing with these is fun too. +% +% V1.7 added more control macros to make it easy for IEEEtrantools.sty users +% to change the font style. +% +% the number of lines that are indented to clear it +% may need to increase if using decenders +\def\@IEEEPARstartDROPLINES{2} +% minimum number of lines left on a page to allow a \@IEEEPARstart +% Does not take into consideration rubber shrink, so it tends to +% be overly cautious +\def\@IEEEPARstartMINPAGELINES{2} +% V1.7 the height of the drop cap is adjusted to match the height of this text +% in the current font (when \IEEEPARstart is called). +\def\@IEEEPARstartHEIGHTTEXT{T} +% the depth the letter is lowered below the baseline +% the height (and size) of the letter is determined by the sum +% of this value and the height of the \@IEEEPARstartHEIGHTTEXT in the current +% font. It is a good idea to set this value in terms of the baselineskip +% so that it can respond to changes therein. +\def\@IEEEPARstartDROPDEPTH{1.1\baselineskip} +% V1.7 the font the drop cap will be rendered in, +% can take zero or one argument. +\def\@IEEEPARstartFONTSTYLE{\bfseries} +% V1.7 any additional, non-font related commands needed to modify +% the drop cap letter, can take zero or one argument. +\def\@IEEEPARstartCAPSTYLE{\MakeUppercase} +% V1.7 the font that will be used to render the rest of the word, +% can take zero or one argument. +\def\@IEEEPARstartWORDFONTSTYLE{\relax} +% V1.7 any additional, non-font related commands needed to modify +% the rest of the word, can take zero or one argument. +\def\@IEEEPARstartWORDCAPSTYLE{\MakeUppercase} +% This is the horizontal separation distance from the drop letter to the main text. +% Lengths that depend on the font (e.g., ex, em, etc.) will be referenced +% to the font that is active when \IEEEPARstart is called. +\def\@IEEEPARstartSEP{0.15em} +% V1.7 horizontal offset applied to the left of the drop cap. +\def\@IEEEPARstartHOFFSET{0em} +% V1.7 Italic correction command applied at the end of the drop cap. +\def\@IEEEPARstartITLCORRECT{\/} + +% V1.7 compoc uses nonbold drop cap and small caps word style +\ifCLASSOPTIONcompsoc +\def\@IEEEPARstartFONTSTYLE{\mdseries} +\def\@IEEEPARstartWORDFONTSTYLE{\scshape} +\def\@IEEEPARstartWORDCAPSTYLE{\relax} +\fi + +% definition of \IEEEPARstart +% THIS IS A CONTROLLED SPACING AREA, DO NOT ALLOW SPACES WITHIN THESE LINES +% +% The token \@IEEEPARstartfont will be globally defined after the first use +% of \IEEEPARstart and will be a font command which creates the big letter +% The first argument is the first letter of the first word and the second +% argument is the rest of the first word(s). +\def\IEEEPARstart#1#2{\par{% +% if this page does not have enough space, break it and lets start +% on a new one +\@IEEEtranneedspace{\@IEEEPARstartMINPAGELINES\baselineskip}{\relax}% +% V1.7 move this up here in case user uses \textbf for \@IEEEPARstartFONTSTYLE +% which uses command \leavevmode which causes an unwanted \indent to be issued +\noindent +% calculate the desired height of the big letter +% it extends from the top of \@IEEEPARstartHEIGHTTEXT in the current font +% down to \@IEEEPARstartDROPDEPTH below the current baseline +\settoheight{\@IEEEtrantmpdimenA}{\@IEEEPARstartHEIGHTTEXT}% +\addtolength{\@IEEEtrantmpdimenA}{\@IEEEPARstartDROPDEPTH}% +% extract the name of the current font in bold +% and place it in \@IEEEPARstartFONTNAME +\def\@IEEEPARstartGETFIRSTWORD##1 ##2\relax{##1}% +{\@IEEEPARstartFONTSTYLE{\selectfont\edef\@IEEEPARstartFONTNAMESPACE{\fontname\font\space}% +\xdef\@IEEEPARstartFONTNAME{\expandafter\@IEEEPARstartGETFIRSTWORD\@IEEEPARstartFONTNAMESPACE\relax}}}% +% define a font based on this name with a point size equal to the desired +% height of the drop letter +\font\@IEEEPARstartsubfont\@IEEEPARstartFONTNAME\space at \@IEEEtrantmpdimenA\relax% +% save this value as a counter (integer) value (sp points) +\@IEEEtrantmpcountA=\@IEEEtrantmpdimenA% +% now get the height of the actual letter produced by this font size +\settoheight{\@IEEEtrantmpdimenB}{\@IEEEPARstartsubfont\@IEEEPARstartCAPSTYLE{#1}}% +% If something bogus happens like the first argument is empty or the +% current font is strange, do not allow a zero height. +\ifdim\@IEEEtrantmpdimenB=0pt\relax% +\typeout{** WARNING: IEEEPARstart drop letter has zero height! (line \the\inputlineno)}% +\typeout{ Forcing the drop letter font size to 10pt.}% +\@IEEEtrantmpdimenB=10pt% +\fi% +% and store it as a counter +\@IEEEtrantmpcountB=\@IEEEtrantmpdimenB% +% Since a font size doesn't exactly correspond to the height of the capital +% letters in that font, the actual height of the letter, \@IEEEtrantmpcountB, +% will be less than that desired, \@IEEEtrantmpcountA +% we need to raise the font size, \@IEEEtrantmpdimenA +% by \@IEEEtrantmpcountA / \@IEEEtrantmpcountB +% But, TeX doesn't have floating point division, so we have to use integer +% division. Hence the use of the counters. +% We need to reduce the denominator so that the loss of the remainder will +% have minimal affect on the accuracy of the result +\divide\@IEEEtrantmpcountB by 200% +\divide\@IEEEtrantmpcountA by \@IEEEtrantmpcountB% +% Then reequalize things when we use TeX's ability to multiply by +% floating point values +\@IEEEtrantmpdimenB=0.005\@IEEEtrantmpdimenA% +\multiply\@IEEEtrantmpdimenB by \@IEEEtrantmpcountA% +% \@IEEEPARstartfont is globaly set to the calculated font of the big letter +% We need to carry this out of the local calculation area to to create the +% big letter. +\global\font\@IEEEPARstartfont\@IEEEPARstartFONTNAME\space at \@IEEEtrantmpdimenB% +% Now set \@IEEEtrantmpdimenA to the width of the big letter +% We need to carry this out of the local calculation area to set the +% hanging indent +\settowidth{\global\@IEEEtrantmpdimenA}{\@IEEEPARstartfont +\@IEEEPARstartCAPSTYLE{#1\@IEEEPARstartITLCORRECT}}}% +% end of the isolated calculation environment +% add in the extra clearance we want +\advance\@IEEEtrantmpdimenA by \@IEEEPARstartSEP\relax% +% add in the optional offset +\advance\@IEEEtrantmpdimenA by \@IEEEPARstartHOFFSET\relax% +% V1.7 don't allow negative offsets to produce negative hanging indents +\@IEEEtrantmpdimenB\@IEEEtrantmpdimenA +\ifnum\@IEEEtrantmpdimenB < 0 \@IEEEtrantmpdimenB 0pt\fi +% \@IEEEtrantmpdimenA has the width of the big letter plus the +% separation space and \@IEEEPARstartfont is the font we need to use +% Now, we make the letter and issue the hanging indent command +% The letter is placed in a box of zero width and height so that other +% text won't be displaced by it. +\hangindent\@IEEEtrantmpdimenB\hangafter=-\@IEEEPARstartDROPLINES% +\makebox[0pt][l]{\hspace{-\@IEEEtrantmpdimenA}% +\raisebox{-\@IEEEPARstartDROPDEPTH}[0pt][0pt]{\hspace{\@IEEEPARstartHOFFSET}% +\@IEEEPARstartfont\@IEEEPARstartCAPSTYLE{#1\@IEEEPARstartITLCORRECT}% +\hspace{\@IEEEPARstartSEP}}}% +{\@IEEEPARstartWORDFONTSTYLE{\@IEEEPARstartWORDCAPSTYLE{\selectfont#2}}}} + + + + + + +% determines if the space remaining on a given page is equal to or greater +% than the specified space of argument one +% if not, execute argument two (only if the remaining space is greater than zero) +% and issue a \newpage +% +% example: \@IEEEtranneedspace{2in}{\vfill} +% +% Does not take into consideration rubber shrinkage, so it tends to +% be overly cautious +% Based on an example posted by Donald Arseneau +% Note this macro uses \@IEEEtrantmpdimenB internally for calculations, +% so DO NOT PASS \@IEEEtrantmpdimenB to this routine +% if you need a dimen register, import with \@IEEEtrantmpdimenA instead +\def\@IEEEtranneedspace#1#2{\penalty-100\begingroup%shield temp variable +\@IEEEtrantmpdimenB\pagegoal\advance\@IEEEtrantmpdimenB-\pagetotal% space left +\ifdim #1>\@IEEEtrantmpdimenB\relax% not enough space left +\ifdim\@IEEEtrantmpdimenB>\z@\relax #2\fi% +\newpage% +\fi\endgroup} + + + +% IEEEbiography ENVIRONMENT +% Allows user to enter biography leaving place for picture (adapts to font size) +% As of V1.5, a new optional argument allows you to have a real graphic! +% V1.5 and later also fixes the "colliding biographies" which could happen when a +% biography's text was shorter than the space for the photo. +% MDS 7/2001 +% V1.6 prevent multiple biographies from making multiple TOC entries +\newif\if@IEEEbiographyTOCentrynotmade +\global\@IEEEbiographyTOCentrynotmadetrue + +% biography counter so hyperref can jump directly to the biographies +% and not just the previous section +\newcounter{IEEEbiography} +\setcounter{IEEEbiography}{0} + +% photo area size +\def\@IEEEBIOphotowidth{1.0in} % width of the biography photo area +\def\@IEEEBIOphotodepth{1.25in} % depth (height) of the biography photo area +% area cleared for photo +\def\@IEEEBIOhangwidth{1.14in} % width cleared for the biography photo area +\def\@IEEEBIOhangdepth{1.25in} % depth cleared for the biography photo area + % actual depth will be a multiple of + % \baselineskip, rounded up +\def\@IEEEBIOskipN{4\baselineskip}% nominal value of the vskip above the biography + +\newenvironment{IEEEbiography}[2][]{\normalfont\@IEEEcompsoconly{\sffamily}\footnotesize% +\unitlength 1in\parskip=0pt\par\parindent 1em\interlinepenalty500% +% we need enough space to support the hanging indent +% the nominal value of the spacer +% and one extra line for good measure +\@IEEEtrantmpdimenA=\@IEEEBIOhangdepth% +\advance\@IEEEtrantmpdimenA by \@IEEEBIOskipN% +\advance\@IEEEtrantmpdimenA by 1\baselineskip% +% if this page does not have enough space, break it and lets start +% with a new one +\@IEEEtranneedspace{\@IEEEtrantmpdimenA}{\relax}% +% nominal spacer can strech, not shrink use 1fil so user can out stretch with \vfill +\vskip \@IEEEBIOskipN plus 1fil minus 0\baselineskip% +% the default box for where the photo goes +\def\@IEEEtempbiographybox{{\setlength{\fboxsep}{0pt}\framebox{% +\begin{minipage}[b][\@IEEEBIOphotodepth][c]{\@IEEEBIOphotowidth}\centering PLACE\\ PHOTO\\ HERE \end{minipage}}}}% +% +% detect if the optional argument was supplied, this requires the +% \@ifmtarg command as defined in the appendix section above +% and if so, override the default box with what they want +\@ifmtarg{#1}{\relax}{\def\@IEEEtempbiographybox{\mbox{\begin{minipage}[b][\@IEEEBIOphotodepth][c]{\@IEEEBIOphotowidth}% +\centering% +#1% +\end{minipage}}}}% end if optional argument supplied +% Make an entry into the table of contents only if we have not done so before +\if@IEEEbiographyTOCentrynotmade% +% link labels to the biography counter so hyperref will jump +% to the biography, not the previous section +\setcounter{IEEEbiography}{-1}% +\refstepcounter{IEEEbiography}% +\addcontentsline{toc}{section}{Biographies}% +\global\@IEEEbiographyTOCentrynotmadefalse% +\fi% +% one more biography +\refstepcounter{IEEEbiography}% +% Make an entry for this name into the table of contents +\addcontentsline{toc}{subsection}{#2}% +% V1.6 properly handle if a new paragraph should occur while the +% hanging indent is still active. Do this by redefining \par so +% that it will not start a new paragraph. (But it will appear to the +% user as if it did.) Also, strip any leading pars, newlines, or spaces. +\let\@IEEEBIOORGparCMD=\par% save the original \par command +\edef\par{\hfil\break\indent}% the new \par will not be a "real" \par +\settoheight{\@IEEEtrantmpdimenA}{\@IEEEtempbiographybox}% get height of biography box +\@IEEEtrantmpdimenB=\@IEEEBIOhangdepth% +\@IEEEtrantmpcountA=\@IEEEtrantmpdimenB% countA has the hang depth +\divide\@IEEEtrantmpcountA by \baselineskip% calculates lines needed to produce the hang depth +\advance\@IEEEtrantmpcountA by 1% ensure we overestimate +% set the hanging indent +\hangindent\@IEEEBIOhangwidth% +\hangafter-\@IEEEtrantmpcountA% +% reference the top of the photo area to the top of a capital T +\settoheight{\@IEEEtrantmpdimenB}{\mbox{T}}% +% set the photo box, give it zero width and height so as not to disturb anything +\noindent\makebox[0pt][l]{\hspace{-\@IEEEBIOhangwidth}\raisebox{\@IEEEtrantmpdimenB}[0pt][0pt]{% +\raisebox{-\@IEEEBIOphotodepth}[0pt][0pt]{\@IEEEtempbiographybox}}}% +% now place the author name and begin the bio text +\noindent\textbf{#2\ }\@IEEEgobbleleadPARNLSP}{\relax\let\par=\@IEEEBIOORGparCMD\par% +% 7/2001 V1.5 detect when the biography text is shorter than the photo area +% and pad the unused area - preventing a collision from the next biography entry +% MDS +\ifnum \prevgraf <\@IEEEtrantmpcountA\relax% detect when the biography text is shorter than the photo + \advance\@IEEEtrantmpcountA by -\prevgraf% calculate how many lines we need to pad + \advance\@IEEEtrantmpcountA by -1\relax% we compensate for the fact that we indented an extra line + \@IEEEtrantmpdimenA=\baselineskip% calculate the length of the padding + \multiply\@IEEEtrantmpdimenA by \@IEEEtrantmpcountA% + \noindent\rule{0pt}{\@IEEEtrantmpdimenA}% insert an invisible support strut +\fi% +\par\normalfont} + + + +% V1.6 +% added biography without a photo environment +\newenvironment{IEEEbiographynophoto}[1]{% +% Make an entry into the table of contents only if we have not done so before +\if@IEEEbiographyTOCentrynotmade% +% link labels to the biography counter so hyperref will jump +% to the biography, not the previous section +\setcounter{IEEEbiography}{-1}% +\refstepcounter{IEEEbiography}% +\addcontentsline{toc}{section}{Biographies}% +\global\@IEEEbiographyTOCentrynotmadefalse% +\fi% +% one more biography +\refstepcounter{IEEEbiography}% +% Make an entry for this name into the table of contents +\addcontentsline{toc}{subsection}{#1}% +\normalfont\@IEEEcompsoconly{\sffamily}\footnotesize\interlinepenalty500% +\vskip 4\baselineskip plus 1fil minus 0\baselineskip% +\parskip=0pt\par% +\noindent\textbf{#1\ }\@IEEEgobbleleadPARNLSP}{\relax\par\normalfont} + + +% provide the user with some old font commands +% got this from article.cls +\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm} +\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf} +\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt} +\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf} +\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit} +\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl} +\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc} +\DeclareRobustCommand*\cal{\@fontswitch\relax\mathcal} +\DeclareRobustCommand*\mit{\@fontswitch\relax\mathnormal} + + +% SPECIAL PAPER NOTICE COMMANDS +% +% holds the special notice text +\def\@IEEEspecialpapernotice{\relax} + +% for special papers, like invited papers, the user can do: +% \IEEEspecialpapernotice{(Invited Paper)} before \maketitle +\def\IEEEspecialpapernotice#1{\ifCLASSOPTIONconference% +\def\@IEEEspecialpapernotice{{\Large#1\vspace*{1em}}}% +\else% +\def\@IEEEspecialpapernotice{{\\*[1.5ex]\sublargesize\textit{#1}}\vspace*{-2ex}}% +\fi} + + + + +% PUBLISHER ID COMMANDS +% to insert a publisher's ID footer +% V1.6 \IEEEpubid has been changed so that the change in page size and style +% occurs in \maketitle. \IEEEpubid must now be issued prior to \maketitle +% use \IEEEpubidadjcol as before - in the second column of the title page +% These changes allow \maketitle to take the reduced page height into +% consideration when dynamically setting the space between the author +% names and the maintext. +% +% the amount the main text is pulled up to make room for the +% publisher's ID footer +% IEEE uses about 1.3\baselineskip for journals, +% dynamic title spacing will clean up the fraction +\def\@IEEEpubidpullup{1.3\baselineskip} +\ifCLASSOPTIONtechnote +% for technotes it must be an integer of baselineskip as there can be no +% dynamic title spacing for two column mode technotes (the title is in the +% in first column) and we should maintain an integer number of lines in the +% second column +% There are some examples (such as older issues of "Transactions on +% Information Theory") in which IEEE really pulls the text off the ID for +% technotes - about 0.55in (or 4\baselineskip). We'll use 2\baselineskip +% and call it even. +\def\@IEEEpubidpullup{2\baselineskip} +\fi + +% V1.7 compsoc does not use a pullup +\ifCLASSOPTIONcompsoc +\def\@IEEEpubidpullup{0pt} +\fi + +% holds the ID text +\def\@IEEEpubid{\relax} + +% flag so \maketitle can tell if \IEEEpubid was called +\newif\if@IEEEusingpubid +\global\@IEEEusingpubidfalse +% issue this command in the page to have the ID at the bottom +% V1.6 use before \maketitle +\def\IEEEpubid#1{\def\@IEEEpubid{#1}\global\@IEEEusingpubidtrue} + + +% command which will pull up (shorten) the column it is executed in +% to make room for the publisher ID. Place in the second column of +% the title page when using \IEEEpubid +% Is smart enough not to do anything when in single column text or +% if the user hasn't called \IEEEpubid +% currently needed in for the second column of a page with the +% publisher ID. If not needed in future releases, please provide this +% command and define it as \relax for backward compatibility +% v1.6b do not allow command to operate if the peer review option has been +% selected because \IEEEpubidadjcol will not be on the cover page. +% V1.7 do nothing if compsoc +\def\IEEEpubidadjcol{\ifCLASSOPTIONcompsoc\else\ifCLASSOPTIONpeerreview\else +\if@twocolumn\if@IEEEusingpubid\enlargethispage{-\@IEEEpubidpullup}\fi\fi\fi\fi} + +% Special thanks to Peter Wilson, Daniel Luecking, and the other +% gurus at comp.text.tex, for helping me to understand how best to +% implement the IEEEpubid command in LaTeX. + + + +%% Lockout some commands under various conditions + +% general purpose bit bucket +\newsavebox{\@IEEEtranrubishbin} + +% flags to prevent multiple warning messages +\newif\if@IEEEWARNthanks +\newif\if@IEEEWARNIEEEPARstart +\newif\if@IEEEWARNIEEEbiography +\newif\if@IEEEWARNIEEEbiographynophoto +\newif\if@IEEEWARNIEEEpubid +\newif\if@IEEEWARNIEEEpubidadjcol +\newif\if@IEEEWARNIEEEmembership +\newif\if@IEEEWARNIEEEaftertitletext +\@IEEEWARNthankstrue +\@IEEEWARNIEEEPARstarttrue +\@IEEEWARNIEEEbiographytrue +\@IEEEWARNIEEEbiographynophototrue +\@IEEEWARNIEEEpubidtrue +\@IEEEWARNIEEEpubidadjcoltrue +\@IEEEWARNIEEEmembershiptrue +\@IEEEWARNIEEEaftertitletexttrue + + +%% Lockout some commands when in various modes, but allow them to be restored if needed +%% +% save commands which might be locked out +% so that the user can later restore them if needed +\let\@IEEESAVECMDthanks\thanks +\let\@IEEESAVECMDIEEEPARstart\IEEEPARstart +\let\@IEEESAVECMDIEEEbiography\IEEEbiography +\let\@IEEESAVECMDendIEEEbiography\endIEEEbiography +\let\@IEEESAVECMDIEEEbiographynophoto\IEEEbiographynophoto +\let\@IEEESAVECMDendIEEEbiographynophoto\endIEEEbiographynophoto +\let\@IEEESAVECMDIEEEpubid\IEEEpubid +\let\@IEEESAVECMDIEEEpubidadjcol\IEEEpubidadjcol +\let\@IEEESAVECMDIEEEmembership\IEEEmembership +\let\@IEEESAVECMDIEEEaftertitletext\IEEEaftertitletext + + +% disable \IEEEPARstart when in draft mode +% This may have originally been done because the pre-V1.6 drop letter +% algorithm had problems with a non-unity baselinestretch +% At any rate, it seems too formal to have a drop letter in a draft +% paper. +\ifCLASSOPTIONdraftcls +\def\IEEEPARstart#1#2{#1#2\if@IEEEWARNIEEEPARstart\typeout{** ATTENTION: \noexpand\IEEEPARstart + is disabled in draft mode (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEPARstartfalse} +\fi +% and for technotes +\ifCLASSOPTIONtechnote +\def\IEEEPARstart#1#2{#1#2\if@IEEEWARNIEEEPARstart\typeout{** WARNING: \noexpand\IEEEPARstart + is locked out for technotes (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEPARstartfalse} +\fi + + +% lockout unneeded commands when in conference mode +\ifCLASSOPTIONconference +% when locked out, \thanks, \IEEEbiography, \IEEEbiographynophoto, \IEEEpubid, +% \IEEEmembership and \IEEEaftertitletext will all swallow their given text. +% \IEEEPARstart will output a normal character instead +% warn the user about these commands only once to prevent the console screen +% from filling up with redundant messages +\def\thanks#1{\if@IEEEWARNthanks\typeout{** WARNING: \noexpand\thanks + is locked out when in conference mode (line \the\inputlineno).}\fi\global\@IEEEWARNthanksfalse} +\def\IEEEPARstart#1#2{#1#2\if@IEEEWARNIEEEPARstart\typeout{** WARNING: \noexpand\IEEEPARstart + is locked out when in conference mode (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEPARstartfalse} + + +% LaTeX treats environments and commands with optional arguments differently. +% the actual ("internal") command is stored as \\commandname +% (accessed via \csname\string\commandname\endcsname ) +% the "external" command \commandname is a macro with code to determine +% whether or not the optional argument is presented and to provide the +% default if it is absent. So, in order to save and restore such a command +% we would have to save and restore \\commandname as well. But, if LaTeX +% ever changes the way it names the internal names, the trick would break. +% Instead let us just define a new environment so that the internal +% name can be left undisturbed. +\newenvironment{@IEEEbogusbiography}[2][]{\if@IEEEWARNIEEEbiography\typeout{** WARNING: \noexpand\IEEEbiography + is locked out when in conference mode (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEbiographyfalse% +\setbox\@IEEEtranrubishbin\vbox\bgroup}{\egroup\relax} +% and make biography point to our bogus biography +\let\IEEEbiography=\@IEEEbogusbiography +\let\endIEEEbiography=\end@IEEEbogusbiography + +\renewenvironment{IEEEbiographynophoto}[1]{\if@IEEEWARNIEEEbiographynophoto\typeout{** WARNING: \noexpand\IEEEbiographynophoto + is locked out when in conference mode (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEbiographynophotofalse% +\setbox\@IEEEtranrubishbin\vbox\bgroup}{\egroup\relax} + +\def\IEEEpubid#1{\if@IEEEWARNIEEEpubid\typeout{** WARNING: \noexpand\IEEEpubid + is locked out when in conference mode (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEpubidfalse} +\def\IEEEpubidadjcol{\if@IEEEWARNIEEEpubidadjcol\typeout{** WARNING: \noexpand\IEEEpubidadjcol + is locked out when in conference mode (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEpubidadjcolfalse} +\def\IEEEmembership#1{\if@IEEEWARNIEEEmembership\typeout{** WARNING: \noexpand\IEEEmembership + is locked out when in conference mode (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEmembershipfalse} +\def\IEEEaftertitletext#1{\if@IEEEWARNIEEEaftertitletext\typeout{** WARNING: \noexpand\IEEEaftertitletext + is locked out when in conference mode (line \the\inputlineno).}\fi\global\@IEEEWARNIEEEaftertitletextfalse} +\fi + + +% provide a way to restore the commands that are locked out +\def\IEEEoverridecommandlockouts{% +\typeout{** ATTENTION: Overriding command lockouts (line \the\inputlineno).}% +\let\thanks\@IEEESAVECMDthanks% +\let\IEEEPARstart\@IEEESAVECMDIEEEPARstart% +\let\IEEEbiography\@IEEESAVECMDIEEEbiography% +\let\endIEEEbiography\@IEEESAVECMDendIEEEbiography% +\let\IEEEbiographynophoto\@IEEESAVECMDIEEEbiographynophoto% +\let\endIEEEbiographynophoto\@IEEESAVECMDendIEEEbiographynophoto% +\let\IEEEpubid\@IEEESAVECMDIEEEpubid% +\let\IEEEpubidadjcol\@IEEESAVECMDIEEEpubidadjcol% +\let\IEEEmembership\@IEEESAVECMDIEEEmembership% +\let\IEEEaftertitletext\@IEEESAVECMDIEEEaftertitletext} + + + +% need a backslash character for typeout output +{\catcode`\|=0 \catcode`\\=12 +|xdef|@IEEEbackslash{\}} + + +% hook to allow easy disabling of all legacy warnings +\def\@IEEElegacywarn#1#2{\typeout{** ATTENTION: \@IEEEbackslash #1 is deprecated (line \the\inputlineno). +Use \@IEEEbackslash #2 instead.}} + + +% provide for legacy commands +\def\authorblockA{\@IEEElegacywarn{authorblockA}{IEEEauthorblockA}\IEEEauthorblockA} +\def\authorblockN{\@IEEElegacywarn{authorblockN}{IEEEauthorblockN}\IEEEauthorblockN} +\def\authorrefmark{\@IEEElegacywarn{authorrefmark}{IEEEauthorrefmark}\IEEEauthorrefmark} +\def\PARstart{\@IEEElegacywarn{PARstart}{IEEEPARstart}\IEEEPARstart} +\def\pubid{\@IEEElegacywarn{pubid}{IEEEpubid}\IEEEpubid} +\def\pubidadjcol{\@IEEElegacywarn{pubidadjcol}{IEEEpubidadjcol}\IEEEpubidadjcol} +\def\QED{\@IEEElegacywarn{QED}{IEEEQED}\IEEEQED} +\def\QEDclosed{\@IEEElegacywarn{QEDclosed}{IEEEQEDclosed}\IEEEQEDclosed} +\def\QEDopen{\@IEEElegacywarn{QEDopen}{IEEEQEDopen}\IEEEQEDopen} +\def\specialpapernotice{\@IEEElegacywarn{specialpapernotice}{IEEEspecialpapernotice}\IEEEspecialpapernotice} + + + +% provide for legacy environments +\def\biography{\@IEEElegacywarn{biography}{IEEEbiography}\IEEEbiography} +\def\biographynophoto{\@IEEElegacywarn{biographynophoto}{IEEEbiographynophoto}\IEEEbiographynophoto} +\def\keywords{\@IEEElegacywarn{keywords}{IEEEkeywords}\IEEEkeywords} +\def\endbiography{\endIEEEbiography} +\def\endbiographynophoto{\endIEEEbiographynophoto} +\def\endkeywords{\endIEEEkeywords} + + +% provide for legacy IED commands/lengths when possible +\let\labelindent\IEEElabelindent +\def\calcleftmargin{\@IEEElegacywarn{calcleftmargin}{IEEEcalcleftmargin}\IEEEcalcleftmargin} +\def\setlabelwidth{\@IEEElegacywarn{setlabelwidth}{IEEEsetlabelwidth}\IEEEsetlabelwidth} +\def\usemathlabelsep{\@IEEElegacywarn{usemathlabelsep}{IEEEusemathlabelsep}\IEEEusemathlabelsep} +\def\iedlabeljustifyc{\@IEEElegacywarn{iedlabeljustifyc}{IEEEiedlabeljustifyc}\IEEEiedlabeljustifyc} +\def\iedlabeljustifyl{\@IEEElegacywarn{iedlabeljustifyl}{IEEEiedlabeljustifyl}\IEEEiedlabeljustifyl} +\def\iedlabeljustifyr{\@IEEElegacywarn{iedlabeljustifyr}{IEEEiedlabeljustifyr}\IEEEiedlabeljustifyr} + + + +% let \proof use the IEEEtran version even after amsthm is loaded +% \proof is now deprecated in favor of \IEEEproof +\AtBeginDocument{\def\proof{\@IEEElegacywarn{proof}{IEEEproof}\IEEEproof}\def\endproof{\endIEEEproof}} + +% V1.7 \overrideIEEEmargins is no longer supported. +\def\overrideIEEEmargins{% +\typeout{** WARNING: \string\overrideIEEEmargins \space no longer supported (line \the\inputlineno).}% +\typeout{** Use the \string\CLASSINPUTinnersidemargin, \string\CLASSINPUToutersidemargin \space controls instead.}} + + +\endinput + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% End of IEEEtran.cls %%%%%%%%%%%%%%%%%%%%%%%%%%%% +% That's all folks! + diff --git a/spec/consensus/consensus-paper/README.md b/spec/consensus/consensus-paper/README.md new file mode 100644 index 00000000000..33e3958061a --- /dev/null +++ b/spec/consensus/consensus-paper/README.md @@ -0,0 +1,24 @@ +# Tendermint-spec + +The repository contains the specification (and the proofs) of the Tendermint +consensus protocol. + +## How to install Latex on Mac OS + +MacTex is Latex distribution for Mac OS. You can download it [here](http://www.tug.org/mactex/mactex-download.html). + +Popular IDE for Latex-based projects is TexStudio. It can be downloaded +[here](https://www.texstudio.org/). + +## How to build project + +In order to compile the latex files (and write bibliography), execute + +`$ pdflatex paper`
+`$ bibtex paper`
+`$ pdflatex paper`
+`$ pdflatex paper`
+ +The generated file is paper.pdf. You can open it with + +`$ open paper.pdf` diff --git a/spec/consensus/consensus-paper/algorithmicplus.sty b/spec/consensus/consensus-paper/algorithmicplus.sty new file mode 100644 index 00000000000..de7ca01ea26 --- /dev/null +++ b/spec/consensus/consensus-paper/algorithmicplus.sty @@ -0,0 +1,195 @@ +% ALGORITHMICPLUS STYLE +% for LaTeX version 2e +% Original ``algorithmic.sty'' by -- 1994 Peter Williams +% Bug fix (13 July 2004) by Arnaud Giersch +% Includes ideas from 'algorithmicext' by Martin Biely +% and 'distribalgo' by Xavier Defago +% Modifications: Martin Hutle +% +% This style file is free software; you can redistribute it and/or +% modify it under the terms of the GNU Lesser General Public +% License as published by the Free Software Foundation; either +% version 2 of the License, or (at your option) any later version. +% +% This style file is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% Lesser General Public License for more details. +% +% You should have received a copy of the GNU Lesser General Public +% License along with this style file; if not, write to the +% Free Software Foundation, Inc., 59 Temple Place - Suite 330, +% Boston, MA 02111-1307, USA. +% +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{algorithmicplus} +\typeout{Document Style `algorithmicplus' - environment, replaces `algorithmic'} +% +\RequirePackage{ifthen} +\RequirePackage{calc} +\newboolean{ALC@noend} +\setboolean{ALC@noend}{false} +\newcounter{ALC@line} +\newcounter{ALC@rem} +\newcounter{ALC@depth} +\newcounter{ALCPLUS@lastline} +\newlength{\ALC@tlm} +% +\DeclareOption{noend}{\setboolean{ALC@noend}{true}} +% +\ProcessOptions +% +% ALGORITHMIC +\newcommand{\algorithmiclnosize}{\small} +\newcommand{\algorithmiclnofont}{\tt} +\newcommand{\algorithmiclnodelimiter}{:} +% +\newcommand{\algorithmicrequire}{\textbf{Require:}} +\newcommand{\algorithmicensure}{\textbf{Ensure:}} +\newcommand{\algorithmiccomment}[1]{\{#1\}} +\newcommand{\algorithmicend}{\textbf{end}} +\newcommand{\algorithmicif}{\textbf{if}} +\newcommand{\algorithmicthen}{\textbf{then}} +\newcommand{\algorithmicelse}{\textbf{else}} +\newcommand{\algorithmicelsif}{\algorithmicelse\ \algorithmicif} +\newcommand{\algorithmicendif}{\algorithmicend\ \algorithmicif} +\newcommand{\algorithmicfor}{\textbf{for}} +\newcommand{\algorithmicforall}{\textbf{for all}} +\newcommand{\algorithmicdo}{\textbf{do}} +\newcommand{\algorithmicendfor}{\algorithmicend\ \algorithmicfor} +\newcommand{\algorithmicwhile}{\textbf{while}} +\newcommand{\algorithmicendwhile}{\algorithmicend\ \algorithmicwhile} +\newcommand{\algorithmicloop}{\textbf{loop}} +\newcommand{\algorithmicendloop}{\algorithmicend\ \algorithmicloop} +\newcommand{\algorithmicrepeat}{\textbf{repeat}} +\newcommand{\algorithmicuntil}{\textbf{until}} +\def\ALC@item[#1]{% +\if@noparitem \@donoparitem + \else \if@inlabel \indent \par \fi + \ifhmode \unskip\unskip \par \fi + \if@newlist \if@nobreak \@nbitem \else + \addpenalty\@beginparpenalty + \addvspace\@topsep \addvspace{-\parskip}\fi + \else \addpenalty\@itempenalty \addvspace\itemsep + \fi + \global\@inlabeltrue +\fi +\everypar{\global\@minipagefalse\global\@newlistfalse + \if@inlabel\global\@inlabelfalse \hskip -\parindent \box\@labels + \penalty\z@ \fi + \everypar{}}\global\@nobreakfalse +\if@noitemarg \@noitemargfalse \if@nmbrlist \refstepcounter{\@listctr}\fi \fi +\sbox\@tempboxa{\makelabel{#1}}% +\global\setbox\@labels + \hbox{\unhbox\@labels \hskip \itemindent + \hskip -\labelwidth \hskip -\ALC@tlm + \ifdim \wd\@tempboxa >\labelwidth + \box\@tempboxa + \else \hbox to\labelwidth {\unhbox\@tempboxa}\fi + \hskip \ALC@tlm}\ignorespaces} +% +\newenvironment{algorithmic}[1][0]{ +\setcounter{ALC@depth}{\@listdepth}% +\let\@listdepth\c@ALC@depth% +\let\@item\ALC@item + \newcommand{\ALC@lno}{% +\ifthenelse{\equal{\arabic{ALC@rem}}{0}} +{{\algorithmiclnosize\algorithmiclnofont \arabic{ALC@line}\algorithmiclnodelimiter}}{}% +} +\let\@listii\@listi +\let\@listiii\@listi +\let\@listiv\@listi +\let\@listv\@listi +\let\@listvi\@listi +\let\@listvii\@listi + \newenvironment{ALC@g}{ + \begin{list}{\ALC@lno}{ \itemsep\z@ \itemindent\z@ + \listparindent\z@ \rightmargin\z@ + \topsep\z@ \partopsep\z@ \parskip\z@\parsep\z@ + \leftmargin 1em + \addtolength{\ALC@tlm}{\leftmargin} + } + } + {\end{list}} + \newcommand{\ALC@it}{\refstepcounter{ALC@line}\addtocounter{ALC@rem}{1}\ifthenelse{\equal{\arabic{ALC@rem}}{#1}}{\setcounter{ALC@rem}{0}}{}\item} + \newcommand{\ALC@com}[1]{\ifthenelse{\equal{##1}{default}}% +{}{\ \algorithmiccomment{##1}}} + \newcommand{\REQUIRE}{\item[\algorithmicrequire]} + \newcommand{\ENSURE}{\item[\algorithmicensure]} + \newcommand{\STATE}{\ALC@it} + \newcommand{\COMMENT}[1]{\algorithmiccomment{##1}} + \newenvironment{ALC@if}{\begin{ALC@g}}{\end{ALC@g}} + \newenvironment{ALC@for}{\begin{ALC@g}}{\end{ALC@g}} + \newenvironment{ALC@whl}{\begin{ALC@g}}{\end{ALC@g}} + \newenvironment{ALC@loop}{\begin{ALC@g}}{\end{ALC@g}} + \newenvironment{ALC@rpt}{\begin{ALC@g}}{\end{ALC@g}} + \renewcommand{\\}{\@centercr} + \newcommand{\IF}[2][default]{\ALC@it\algorithmicif\ ##2\ \algorithmicthen% +\ALC@com{##1}\begin{ALC@if}} + \newcommand{\ELSE}[1][default]{\end{ALC@if}\ALC@it\algorithmicelse% +\ALC@com{##1}\begin{ALC@if}} + \newcommand{\ELSIF}[2][default]% +{\end{ALC@if}\ALC@it\algorithmicelsif\ ##2\ \algorithmicthen% +\ALC@com{##1}\begin{ALC@if}} + \newcommand{\FOR}[2][default]{\ALC@it\algorithmicfor\ ##2\ \algorithmicdo% +\ALC@com{##1}\begin{ALC@for}} + \newcommand{\FORALL}[2][default]{\ALC@it\algorithmicforall\ ##2\ % +\algorithmicdo% +\ALC@com{##1}\begin{ALC@for}} + \newcommand{\WHILE}[2][default]{\ALC@it\algorithmicwhile\ ##2\ % +\algorithmicdo% +\ALC@com{##1}\begin{ALC@whl}} + \newcommand{\LOOP}[1][default]{\ALC@it\algorithmicloop% +\ALC@com{##1}\begin{ALC@loop}} + \newcommand{\REPEAT}[1][default]{\ALC@it\algorithmicrepeat% +\ALC@com{##1}\begin{ALC@rpt}} + \newcommand{\UNTIL}[1]{\end{ALC@rpt}\ALC@it\algorithmicuntil\ ##1} + \ifthenelse{\boolean{ALC@noend}}{ + \newcommand{\ENDIF}{\end{ALC@if}} + \newcommand{\ENDFOR}{\end{ALC@for}} + \newcommand{\ENDWHILE}{\end{ALC@whl}} + \newcommand{\ENDLOOP}{\end{ALC@loop}} + }{ + \newcommand{\ENDIF}{\end{ALC@if}\ALC@it\algorithmicendif} + \newcommand{\ENDFOR}{\end{ALC@for}\ALC@it\algorithmicendfor} + \newcommand{\ENDWHILE}{\end{ALC@whl}\ALC@it\algorithmicendwhile} + \newcommand{\ENDLOOP}{\end{ALC@loop}\ALC@it\algorithmicendloop} + } + \renewcommand{\@toodeep}{} + \begin{list}{\ALC@lno}{\setcounter{ALC@line}{0}\setcounter{ALC@rem}{0}% + \itemsep\z@ \itemindent\z@ \listparindent\z@% + \partopsep\z@ \parskip\z@ \parsep\z@% + \labelsep 0.5em \topsep 0.2em% +\ifthenelse{\equal{#1}{0}} + {\labelwidth 0.5em } + {\labelwidth 1.2em } +\leftmargin\labelwidth \addtolength{\leftmargin}{\labelsep} + \ALC@tlm\labelsep + } +} +{% +\setcounter{ALCPLUS@lastline}{\value{ALC@line}}% +\end{list}} + +\newcommand{\continuecounting}{\setcounter{ALC@line}{\value{ALCPLUS@lastline}}} +\newcommand{\startcounting}[1]{\setcounter{ALC@line}{#1}\addtocounter{ALC@line}{-1}} + +\newcommand{\EMPTY}{\item[]} +\newcommand{\SPACE}{\vspace{3mm}} +\newcommand{\SHORTSPACE}{\vspace{1mm}} +\newcommand{\newlinetag}[3]{\newcommand{#1}[#2]{\item[#3]}} +\newcommand{\newconstruct}[5]{% + \newenvironment{ALC@\string#1}{\begin{ALC@g}}{\end{ALC@g}} + \newcommand{#1}[2][default]{\ALC@it#2\ ##2\ #3% + \ALC@com{##1}\begin{ALC@\string#1}} + \ifthenelse{\boolean{ALC@noend}}{ + \newcommand{#4}{\end{ALC@\string#1}} + }{ + \newcommand{#4}{\end{ALC@\string#1}\ALC@it#5} + } +} + +\newconstruct{\INDENT}{}{}{\ENDINDENT}{} + +\newcommand{\setlinenosize}[1]{\renewcommand{\algorithmiclnosize}{#1}} +\newcommand{\setlinenofont}[1]{\renewcommand{\algorithmiclnofont}{#1}} diff --git a/spec/consensus/consensus-paper/conclusion.tex b/spec/consensus/consensus-paper/conclusion.tex new file mode 100644 index 00000000000..dd17ccf44d4 --- /dev/null +++ b/spec/consensus/consensus-paper/conclusion.tex @@ -0,0 +1,16 @@ +\section{Conclusion} \label{sec:conclusion} + +We have proposed a new Byzantine-fault tolerant consensus algorithm that is the +core of the Tendermint BFT SMR platform. The algorithm is designed for the wide +area network with high number of mutually distrusted nodes that communicate +over gossip based peer-to-peer network. It has only a single mode of execution +and the communication pattern is very similar to the "normal" case of the +state-of-the art PBFT algorithm. The algorithm ensures termination with a novel +mechanism that takes advantage of the gossip based communication between nodes. +The proposed algorithm and the proofs are simple and elegant, and we believe +that this makes it easier to understand and implement correctly. + +\section*{Acknowledgment} + +We would like to thank Anton Kaliaev, Ismail Khoffi and Dahlia Malkhi for comments on an earlier version of the paper. We also want to thank Marko Vukolic, Ming Chuan Lin, Maria Potop-Butucaru, Sara Tucci, Antonella Del Pozzo and Yackolley Amoussou-Guenou for pointing out the liveness issues +in the previous version of the algorithm. Finally, we want to thank the Tendermint team members and all project contributors for making Tendermint such a great platform. diff --git a/spec/consensus/consensus-paper/consensus.tex b/spec/consensus/consensus-paper/consensus.tex new file mode 100644 index 00000000000..3265b61c753 --- /dev/null +++ b/spec/consensus/consensus-paper/consensus.tex @@ -0,0 +1,397 @@ + +\section{Tendermint consensus algorithm} \label{sec:tendermint} + +\newcommand\Disseminate{\textbf{Disseminate}} + +\newcommand\Proposal{\mathsf{PROPOSAL}} +\newcommand\ProposalPart{\mathsf{PROPOSAL\mbox{-}PART}} +\newcommand\PrePrepare{\mathsf{INIT}} \newcommand\Prevote{\mathsf{PREVOTE}} +\newcommand\Precommit{\mathsf{PRECOMMIT}} +\newcommand\Decision{\mathsf{DECISION}} + +\newcommand\ViewChange{\mathsf{VC}} +\newcommand\ViewChangeAck{\mathsf{VC\mbox{-}ACK}} +\newcommand\NewPrePrepare{\mathsf{VC\mbox{-}INIT}} +\newcommand\coord{\mathsf{proposer}} + +\newcommand\newHeight{newHeight} \newcommand\newRound{newRound} +\newcommand\nil{nil} \newcommand\id{id} \newcommand{\propose}{propose} +\newcommand\prevote{prevote} \newcommand\prevoteWait{prevoteWait} +\newcommand\precommit{precommit} \newcommand\precommitWait{precommitWait} +\newcommand\commit{commit} + +\newcommand\timeoutPropose{timeoutPropose} +\newcommand\timeoutPrevote{timeoutPrevote} +\newcommand\timeoutPrecommit{timeoutPrecommit} +\newcommand\proofOfLocking{proof\mbox{-}of\mbox{-}locking} + +\begin{algorithm}[htb!] \def\baselinestretch{1} \scriptsize\raggedright + \begin{algorithmic}[1] + \SHORTSPACE + \INIT{} + \STATE $h_p := 0$ + \COMMENT{current height, or consensus instance we are currently executing} + \STATE $round_p := 0$ \COMMENT{current round number} + \STATE $step_p \in \set{\propose, \prevote, \precommit}$ + \STATE $decision_p[] := nil$ + \STATE $lockedValue_p := nil$ + \STATE $lockedRound_p := -1$ + \STATE $validValue_p := nil$ + \STATE $validRound_p := -1$ + \ENDINIT + \SHORTSPACE + \STATE \textbf{upon} start \textbf{do} $StartRound(0)$ + \SHORTSPACE + \FUNCTION{$StartRound(round)$} \label{line:tab:startRound} + \STATE $round_p \assign round$ + \STATE $step_p \assign \propose$ + \IF{$\coord(h_p, round_p) = p$} + \IF{$validValue_p \neq \nil$} \label{line:tab:isThereLockedValue} + \STATE $proposal \assign validValue_p$ \ELSE \STATE $proposal \assign + getValue()$ + \label{line:tab:getValidValue} + \ENDIF + \STATE \Broadcast\ $\li{\Proposal,h_p, round_p, proposal, validRound_p}$ + \label{line:tab:send-proposal} + \ELSE + \STATE \textbf{schedule} $OnTimeoutPropose(h_p, + round_p)$ to be executed \textbf{after} $\timeoutPropose(round_p)$ + \ENDIF + \ENDFUNCTION + + \SPACE + \UPON{$\li{\Proposal,h_p,round_p, v, -1}$ \From\ $\coord(h_p,round_p)$ + \With\ $step_p = \propose$} \label{line:tab:recvProposal} + \IF{$valid(v) \wedge (lockedRound_p = -1 \vee lockedValue_p = v$)} + \label{line:tab:accept-proposal-2} + \STATE \Broadcast \ $\li{\Prevote,h_p,round_p,id(v)}$ + \label{line:tab:prevote-proposal} + \ELSE + \label{line:tab:acceptProposal1} + \STATE \Broadcast \ $\li{\Prevote,h_p,round_p,\nil}$ + \label{line:tab:prevote-nil} + \ENDIF + \STATE $step_p \assign \prevote$ \label{line:tab:setStateToPrevote1} + \ENDUPON + + \SPACE + \UPON{$\li{\Proposal,h_p,round_p, v, vr}$ \From\ $\coord(h_p,round_p)$ + \textbf{AND} $2f+1$ $\li{\Prevote,h_p, vr,id(v)}$ \With\ $step_p = \propose \wedge (vr \ge 0 \wedge vr < round_p)$} + \label{line:tab:acceptProposal} + \IF{$valid(v) \wedge (lockedRound_p \le vr + \vee lockedValue_p = v)$} \label{line:tab:cond-prevote-higher-proposal} + \STATE \Broadcast \ $\li{\Prevote,h_p,round_p,id(v)}$ + \label{line:tab:prevote-higher-proposal} + \ELSE + \label{line:tab:acceptProposal2} + \STATE \Broadcast \ $\li{\Prevote,h_p,round_p,\nil}$ + \label{line:tab:prevote-nil2} + \ENDIF + \STATE $step_p \assign \prevote$ \label{line:tab:setStateToPrevote3} + \ENDUPON + + \SPACE + \UPON{$2f+1$ $\li{\Prevote,h_p, round_p,*}$ \With\ $step_p = \prevote$ for the first time} + \label{line:tab:recvAny2/3Prevote} + \STATE \textbf{schedule} $OnTimeoutPrevote(h_p, round_p)$ to be executed \textbf{after} $\timeoutPrevote(round_p)$ \label{line:tab:timeoutPrevote} + \ENDUPON + + \SPACE + \UPON{$\li{\Proposal,h_p,round_p, v, *}$ \From\ $\coord(h_p,round_p)$ + \textbf{AND} $2f+1$ $\li{\Prevote,h_p, round_p,id(v)}$ \With\ $valid(v) \wedge step_p \ge \prevote$ for the first time} + \label{line:tab:recvPrevote} + \IF{$step_p = \prevote$} + \STATE $lockedValue_p \assign v$ \label{line:tab:setLockedValue} + \STATE $lockedRound_p \assign round_p$ \label{line:tab:setLockedRound} + \STATE \Broadcast \ $\li{\Precommit,h_p,round_p,id(v))}$ + \label{line:tab:precommit-v} + \STATE $step_p \assign \precommit$ \label{line:tab:setStateToCommit} + \ENDIF + \STATE $validValue_p \assign v$ \label{line:tab:setValidRound} + \STATE $validRound_p \assign round_p$ \label{line:tab:setValidValue} + \ENDUPON + + \SHORTSPACE + \UPON{$2f+1$ $\li{\Prevote,h_p,round_p, \nil}$ + \With\ $step_p = \prevote$} + \STATE \Broadcast \ $\li{\Precommit,h_p,round_p, \nil}$ + \label{line:tab:precommit-v-1} + \STATE $step_p \assign \precommit$ + \ENDUPON + + \SPACE + \UPON{$2f+1$ $\li{\Precommit,h_p,round_p,*}$ for the first time} + \label{line:tab:startTimeoutPrecommit} + \STATE \textbf{schedule} $OnTimeoutPrecommit(h_p, round_p)$ to be executed \textbf{after} $\timeoutPrecommit(round_p)$ + + \ENDUPON + + \SPACE + \UPON{$\li{\Proposal,h_p,r, v, *}$ \From\ $\coord(h_p,r)$ \textbf{AND} + $2f+1$ $\li{\Precommit,h_p,r,id(v)}$ \With\ $decision_p[h_p] = \nil$} + \label{line:tab:onDecideRule} + \IF{$valid(v)$} \label{line:tab:validDecisionValue} + \STATE $decision_p[h_p] = v$ \label{line:tab:decide} + \STATE$h_p \assign h_p + 1$ \label{line:tab:increaseHeight} + \STATE reset $lockedRound_p$, $lockedValue_p$, $validRound_p$ and $validValue_p$ to initial values + and empty message log + \STATE $StartRound(0)$ + \ENDIF + \ENDUPON + + \SHORTSPACE + \UPON{$f+1$ $\li{*,h_p,round, *, *}$ \textbf{with} $round > round_p$} + \label{line:tab:skipRounds} + \STATE $StartRound(round)$ \label{line:tab:nextRound2} + \ENDUPON + + \SHORTSPACE + \FUNCTION{$OnTimeoutPropose(height,round)$} \label{line:tab:onTimeoutPropose} + \IF{$height = h_p \wedge round = round_p \wedge step_p = \propose$} + \STATE \Broadcast \ $\li{\Prevote,h_p,round_p, \nil}$ + \label{line:tab:prevote-nil-on-timeout} + \STATE $step_p \assign \prevote$ + \ENDIF + \ENDFUNCTION + + \SHORTSPACE + \FUNCTION{$OnTimeoutPrevote(height,round)$} \label{line:tab:onTimeoutPrevote} + \IF{$height = h_p \wedge round = round_p \wedge step_p = \prevote$} + \STATE \Broadcast \ $\li{\Precommit,h_p,round_p,\nil}$ + \label{line:tab:precommit-nil-onTimeout} + \STATE $step_p \assign \precommit$ + \ENDIF + \ENDFUNCTION + + \SHORTSPACE + \FUNCTION{$OnTimeoutPrecommit(height,round)$} \label{line:tab:onTimeoutPrecommit} + \IF{$height = h_p \wedge round = round_p$} + \STATE $StartRound(round_p + 1)$ \label{line:tab:nextRound} + \ENDIF + \ENDFUNCTION + \end{algorithmic} \caption{Tendermint consensus algorithm} + \label{alg:tendermint} +\end{algorithm} + +In this section we present the Tendermint Byzantine fault-tolerant consensus +algorithm. The algorithm is specified by the pseudo-code shown in +Algorithm~\ref{alg:tendermint}. We present the algorithm as a set of \emph{upon +rules} that are executed atomically\footnote{In case several rules are active +at the same time, the first rule to be executed is picked randomly. The +correctness of the algorithm does not depend on the order in which rules are +executed.}. We assume that processes exchange protocol messages using a gossip +protocol and that both sent and received messages are stored in a local message +log for every process. An upon rule is triggered once the message log contains +messages such that the corresponding condition evaluates to $\tt{true}$. The +condition that assumes reception of $X$ messages of a particular type and +content denotes reception of messages whose senders have aggregate voting power at +least equal to $X$. For example, the condition $2f+1$ $\li{\Precommit,h_p,r,id(v)}$, +evaluates to true upon reception of $\Precommit$ messages for height $h_p$, +a round $r$ and with value equal to $id(v)$ whose senders have aggregate voting +power at least equal to $2f+1$. Some of the rules ends with "for the first time" constraint +to denote that it is triggered only the first time a corresponding condition evaluates +to $\tt{true}$. This is because those rules do not always change the state of algorithm +variables so without this constraint, the algorithm could keep +executing those rules forever. The variables with index $p$ are process local state +variables, while variables without index $p$ are value placeholders. The sign +$*$ denotes any value. + +We denote with $n$ the total voting power of processes in the system, and we +assume that the total voting power of faulty processes in the system is bounded +with a system parameter $f$. The algorithm assumes that $n > 3f$, i.e., it +requires that the total voting power of faulty processes is smaller than one +third of the total voting power. For simplicity we present the algorithm for +the case $n = 3f + 1$. + +The algorithm proceeds in rounds, where each round has a dedicated +\emph{proposer}. The mapping of rounds to proposers is known to all processes +and is given as a function $\coord(h, round)$, returning the proposer for +the round $round$ in the consensus instance $h$. We +assume that the proposer selection function is weighted round-robin, where +processes are rotated proportional to their voting power\footnote{A validator +with more voting power is selected more frequently, proportional to its power. +More precisely, during a sequence of rounds of size $n$, every process is +proposer in a number of rounds equal to its voting power.}. +The internal protocol state transitions are triggered by message reception and +by expiration of timeouts. There are three timeouts in Algorithm \ref{alg:tendermint}: +$\timeoutPropose$, $\timeoutPrevote$ and $\timeoutPrecommit$. +The timeouts prevent the algorithm from blocking and +waiting forever for some condition to be true, ensure that processes continuously +transition between rounds, and guarantee that eventually (after GST) communication +between correct processes is timely and reliable so they can decide. +The last role is achieved by increasing the timeouts with every new round $r$, +i.e, $timeoutX(r) = initTimeoutX + r*timeoutDelta$; +they are reset for every new height (consensus +instance). + +Processes exchange the following messages in Tendermint: $\Proposal$, +$\Prevote$ and $\Precommit$. The $\Proposal$ message is used by the proposer of +the current round to suggest a potential decision value, while $\Prevote$ and +$\Precommit$ are votes for a proposed value. According to the classification of +consensus algorithms from \cite{RMS10:dsn}, Tendermint, like PBFT +\cite{CL02:tcs} and DLS \cite{DLS88:jacm}, belongs to class 3, so it requires +two voting steps (three communication exchanges in total) to decide a value. +The Tendermint consensus algorithm is designed for the blockchain context where +the value to decide is a block of transactions (ie. it is potentially quite +large, consisting of many transactions). Therefore, in the Algorithm +\ref{alg:tendermint} (similar as in \cite{CL02:tcs}) we are explicit about +sending a value (block of transactions) and a small, constant size value id (a +unique value identifier, normally a hash of the value, i.e., if $\id(v) = +\id(v')$, then $v=v'$). The $\Proposal$ message is the only one carrying the +value; $\Prevote$ and $\Precommit$ messages carry the value id. A correct +process decides on a value $v$ in Tendermint upon receiving the $\Proposal$ for +$v$ and $2f+1$ voting-power equivalent $\Precommit$ messages for $\id(v)$ in +some round $r$. In order to send $\Precommit$ message for $v$ in a round $r$, a +correct process waits to receive the $\Proposal$ and $2f+1$ of the +corresponding $\Prevote$ messages in the round $r$. Otherwise, +it sends $\Precommit$ message with a special $\nil$ value. +This ensures that correct processes can $\Precommit$ only a +single value (or $\nil$) in a round. As +proposers may be faulty, the proposed value is treated by correct processes as +a suggestion (it is not blindly accepted), and a correct process tells others +if it accepted the $\Proposal$ for value $v$ by sending $\Prevote$ message for +$\id(v)$; otherwise it sends $\Prevote$ message with the special $\nil$ value. + +Every process maintains the following variables in the Algorithm +\ref{alg:tendermint}: $step$, $lockedValue$, $lockedRound$, $validValue$ and +$validRound$. The $step$ denotes the current state of the internal Tendermint +state machine, i.e., it reflects the stage of the algorithm execution in the +current round. The $lockedValue$ stores the most recent value (with respect to +a round number) for which a $\Precommit$ message has been sent. The +$lockedRound$ is the last round in which the process sent a $\Precommit$ +message that is not $\nil$. We also say that a correct process locks a value +$v$ in a round $r$ by setting $lockedValue = v$ and $lockedRound = r$ before +sending $\Precommit$ message for $\id(v)$. As a correct process can decide a +value $v$ only if $2f+1$ $\Precommit$ messages for $\id(v)$ are received, this +implies that a possible decision value is a value that is locked by at least +$f+1$ voting power equivalent of correct processes. Therefore, any value $v$ +for which $\Proposal$ and $2f+1$ of the corresponding $\Prevote$ messages are +received in some round $r$ is a \emph{possible decision} value. The role of the +$validValue$ variable is to store the most recent possible decision value; the +$validRound$ is the last round in which $validValue$ is updated. Apart from +those variables, a process also stores the current consensus instance ($h_p$, +called \emph{height} in Tendermint), and the current round number ($round_p$) +and attaches them to every message. Finally, a process also stores an array of +decisions, $decision_p$ (Tendermint assumes a sequence of consensus instances, +one for each height). + +Every round starts by a proposer suggesting a value with the $\Proposal$ +message (see line \ref{line:tab:send-proposal}). In the initial round of each +height, the proposer is free to chose the value to suggest. In the +Algorithm~\ref{alg:tendermint}, a correct process obtains a value to propose +using an external function $getValue()$ that returns a valid value to +propose. In the following rounds, a correct proposer will suggest a new value +only if $validValue = \nil$; otherwise $validValue$ is proposed (see +lines~\ref{line:tab:isThereLockedValue}-\ref{line:tab:getValidValue}). +In addition to the value proposed, the $\Proposal$ message also +contains the $validRound$ so other processes are informed about the last round +in which the proposer observed $validValue$ as a possible decision value. +Note that if a correct proposer $p$ sends $validValue$ with the $validRound$ in the +$\Proposal$, this implies that the process $p$ received $\Proposal$ and the +corresponding $2f+1$ $\Prevote$ messages for $validValue$ in the round +$validRound$. +If a correct process sends $\Proposal$ message with $validValue$ ($validRound > -1$) +at time $t > GST$, by the \emph{Gossip communication} property, the +corresponding $\Proposal$ and the $\Prevote$ messages will be received by all +correct processes before time $t+\Delta$. Therefore, all correct processes will +be able to verify the correctness of the suggested value as it is supported by +the $\Proposal$ and the corresponding $2f+1$ voting power equivalent $\Prevote$ +messages. + +A correct process $p$ accepts the proposal for a value $v$ (send $\Prevote$ +for $id(v)$) if an external \emph{valid} function returns $true$ for the value +$v$, and if $p$ hasn't locked any value ($lockedRound = -1$) or $p$ has locked +the value $v$ ($lockedValue = v$); see the line +\ref{line:tab:accept-proposal-2}. In case the proposed pair is $(v,vr \ge 0)$ and a +correct process $p$ has locked some value, it will accept +$v$ if it is a more recent possible decision value\footnote{As +explained above, the possible decision value in a round $r$ is the one for +which $\Proposal$ and the corresponding $2f+1$ $\Prevote$ messages are received +for the round $r$.}, $vr > lockedRound_p$, or if $lockedValue = v$ +(see line~\ref{line:tab:cond-prevote-higher-proposal}). Otherwise, a correct +process will reject the proposal by sending $\Prevote$ message with $\nil$ +value. A correct process will send $\Prevote$ message with $\nil$ value also in +case $\timeoutPropose$ expired (it is triggered when a correct process starts a +new round) and a process has not sent $\Prevote$ message in the current round +yet (see the line \ref{line:tab:onTimeoutPropose}). + +If a correct process receives $\Proposal$ message for some value $v$ and $2f+1$ +$\Prevote$ messages for $\id(v)$, then it sends $\Precommit$ message with +$\id(v)$. Otherwise, it sends $\Precommit$ $\nil$. A correct process will send +$\Precommit$ message with $\nil$ value also in case $\timeoutPrevote$ expired +(it is started when a correct process sent $\Prevote$ message and received any +$2f+1$ $\Prevote$ messages) and a process has not sent $\Precommit$ message in +the current round yet (see the line \ref{line:tab:onTimeoutPrecommit}). A +correct process decides on some value $v$ if it receives in some round $r$ +$\Proposal$ message for $v$ and $2f+1$ $\Precommit$ messages with $\id(v)$ (see +the line \ref{line:tab:decide}). To prevent the algorithm from blocking and +waiting forever for this condition to be true, the Algorithm +\ref{alg:tendermint} relies on $\timeoutPrecommit$. It is triggered after a +process receives any set of $2f+1$ $\Precommit$ messages for the current round. +If the $\timeoutPrecommit$ expires and a process has not decided yet, the +process starts the next round (see the line \ref{line:tab:onTimeoutPrecommit}). +When a correct process $p$ decides, it starts the next consensus instance +(for the next height). The \emph{Gossip communication} property ensures +that $\Proposal$ and $2f+1$ $\Prevote$ messages that led $p$ to decide +are eventually received by all correct processes, so they will also decide. + +\subsection{Termination mechanism} + +Tendermint ensures termination by a novel mechanism that benefits from the +gossip based nature of communication (see \emph{Gossip communication} +property). It requires managing two additional variables, $validValue$ and +$validRound$ that are then used by the proposer during the propose step as +explained above. The $validValue$ and $validRound$ are updated to $v$ and $r$ +by a correct process in a round $r$ when the process receives valid $\Proposal$ +message for the value $v$ and the corresponding $2f+1$ $\Prevote$ messages for +$id(v)$ in the round $r$ (see the rule at line~\ref{line:tab:recvPrevote}). + +We now give briefly the intuition how managing and proposing $validValue$ +and $validRound$ ensures termination. Formal treatment is left for +Section~\ref{sec:proof}. + +The first thing to note is that during good period, because of the +\emph{Gossip communication} property, if a correct process $p$ locks a value +$v$ in some round $r$, all correct processes will update $validValue$ to $v$ +and $validRound$ to $r$ before the end of the round $r$ (we prove this formally +in the Section~\ref{sec:proof}). The intuition is that messages that led to $p$ +locking a value $v$ in the round $r$ will be gossiped to all correct processes +before the end of the round $r$, so it will update $validValue$ and +$validRound$ (the line~\ref{line:tab:recvPrevote}). Therefore, if a correct +process locks some value during good period, $validValue$ and $validRound$ are +updated by all correct processes so that the value proposed in the following +rounds will be acceptable by all correct processes. Note +that it could happen that during good period, no correct process locks a value, +but some correct process $q$ updates $validValue$ and $validRound$ during some +round. As no correct process locks a value in this case, $validValue_q$ and +$validRound_q$ will also be acceptable by all correct processes as +$validRound_q > lockedRound_c$ for every correct process $c$ and as the +\emph{Gossip communication} property ensures that the corresponding $\Prevote$ +messages that $q$ received in the round $validRound_q$ are received by all +correct processes $\Delta$ time later. + +Finally, it could happen that after GST, there is a long sequence of rounds in which +no correct process neither locks a value nor update $validValue$ and $validRound$. +In this case, during this sequence of rounds, the proposed value suggested by correct +processes was not accepted by all correct processes. Note that this sequence of rounds +is always finite as at the beginning of every +round there is at least a single correct process $c$ such that $validValue_c$ +and $validRound_c$ are acceptable by every correct process. This is true as +there exists a correct process $c$ such that for every other correct process +$p$, $validRound_c > lockedRound_p$ or $validValue_c = lockedValue_p$. This is +true as $c$ is the process that has locked a value in the most recent round +among all correct processes (or no correct process locked any value). Therefore, +eventually $c$ will be the proper in some round and the proposed value will be accepted +by all correct processes, terminating therefore this sequence of +rounds. + +Therefore, updating $validValue$ and $validRound$ variables, and the +\emph{Gossip communication} property, together ensures that eventually, during +the good period, there exists a round with a correct proposer whose proposed +value will be accepted by all correct processes, and all correct processes will +terminate in that round. Note that this mechanism, contrary to the common +termination mechanism illustrated in the +Figure~\ref{ch3:fig:coordinator-change}, does not require exchanging any +additional information in addition to messages already sent as part of what is +normally being called "normal" case. + diff --git a/spec/consensus/consensus-paper/definitions.tex b/spec/consensus/consensus-paper/definitions.tex new file mode 100644 index 00000000000..454dd445dfd --- /dev/null +++ b/spec/consensus/consensus-paper/definitions.tex @@ -0,0 +1,126 @@ +\section{Definitions} \label{sec:definitions} + +\subsection{Model} + +We consider a system of processes that communicate by exchanging messages. +Processes can be correct or faulty, where a faulty process can behave in an +arbitrary way, i.e., we consider Byzantine faults. We assume that each process +has some amount of voting power (voting power of a process can be $0$). +Processes in our model are not part of a single administrative domain; +therefore we cannot enforce a direct network connectivity between all +processes. Instead, we assume that each process is connected to a subset of +processes called peers, such that there is an indirect communication channel +between all correct processes. Communication between processes is established +using a gossip protocol \cite{Dem1987:gossip}. + +Formally, we model the network communication using a variant of the \emph{partially +synchronous system model}~\cite{DLS88:jacm}: in all executions of the system +there is a bound $\Delta$ and an instant GST (Global Stabilization Time) such +that all communication among correct processes after GST is reliable and +$\Delta$-timely, i.e., if a correct process $p$ sends message $m$ at time $t +\ge GST$ to a correct process $q$, then $q$ will receive $m$ before $t + +\Delta$\footnote{Note that as we do not assume direct communication channels + among all correct processes, this implies that before the message $m$ + reaches $q$, it might pass through a number of correct processes that will +forward the message $m$ using gossip protocol towards $q$.}. +In addition to the standard \emph{partially + synchronous system model}~\cite{DLS88:jacm}, we assume an auxiliary property +that captures gossip-based nature of communication\footnote{The details of the Tendermint gossip protocol will be discussed in a separate + technical report. }: + + +\begin{itemize} \item \emph{Gossip communication:} If a correct process $p$ + sends some message $m$ at time $t$, all correct processes will receive + $m$ before $max\{t, GST\} + \Delta$. Furthermore, if a correct process $p$ + receives some message $m$ at time $t$, all correct processes will receive + $m$ before $max\{t, GST\} + \Delta$. \end{itemize} + + +The bound $\Delta$ and GST are system +parameters whose values are not required to be known for the safety of our +algorithm. Termination of the algorithm is guaranteed within a bounded duration +after GST. In practice, the algorithm will work correctly in the slightly +weaker variant of the model where the system alternates between (long enough) +good periods (corresponds to the \emph{after} GST period where system is +reliable and $\Delta$-timely) and bad periods (corresponds to the period +\emph{before} GST during which the system is asynchronous and messages can be +lost), but consideration of the GST model simplifies the discussion. + +We assume that process steps (which might include sending and receiving +messages) take zero time. Processes are equipped with clocks so they can +measure local timeouts. +Spoofing/impersonation attacks are assumed to be impossible at all times due to +the use of public-key cryptography, i.e., we assume that all protocol messages contains a digital signature. +Therefore, when a correct +process $q$ receives a signed message $m$ from its peer, the process $q$ can +verify who was the original sender of the message $m$ and if the message signature is valid. +We do not explicitly state a signature verification step in the pseudo-code of the algorithm to improve readability; +we assume that only messages with the valid signature are considered at that level (and messages with invalid signatures +are dropped). + + + +%Messages that are being gossiped are created by the consensus layer. We can + %think about consensus protocol as a content creator, which %defines what + %messages should be disseminated using the gossip protocol. A correct + %process creates the message for dissemination either i) %explicitly, by + %invoking \emph{send} function as part of the consensus protocol or ii) + %implicitly, by receiving a message from some other %process. Note that in + %the case ii) gossiping of messages is implicit, i.e., it happens without + %explicit send clause in the consensus algorithm %whenever a correct + %process receives some messages in the consensus algorithm\footnote{If a + %message is received by a correct process at %the consensus level then it + %is considered valid from the protocol point of view, i.e., it has a + %correct signature, a proper message structure %and a valid height and + %round number.}. + +%\item Processes keep resending messages (in case of failures or message loss) + %until all its peers get them. This ensures that every message %sent or + %received by a correct process is eventually received by all correct + %processes. + +\subsection{State Machine Replication} + +State machine replication (SMR) is a general approach for replicating services +modeled as a deterministic state machine~\cite{Lam78:cacm,Sch90:survey}. The +key idea of this approach is to guarantee that all replicas start in the same +state and then apply requests from clients in the same order, thereby +guaranteeing that the replicas' states will not diverge. Following +Schneider~\cite{Sch90:survey}, we note that the following is key for +implementing a replicated state machine tolerant to (Byzantine) faults: + +\begin{itemize} \item \emph{Replica Coordination.} All [non-faulty] replicas + receive and process the same sequence of requests. \end{itemize} + +Moreover, as Schneider also notes, this property can be decomposed into two +parts, \emph{Agreement} and \emph{Order}: Agreement requires all (non-faulty) +replicas to receive all requests, and Order requires that the order of received +requests is the same at all replicas. + +There is an additional requirement that needs to be ensured by Byzantine +tolerant state machine replication: only requests (called transactions in the +Tendermint terminology) proposed by clients are executed. In Tendermint, +transaction verification is the responsibility of the service that is being +replicated; upon receiving a transaction from the client, the Tendermint +process will ask the service if the request is valid, and only valid requests +will be processed. + + \subsection{Consensus} \label{sec:consensus} + +Tendermint solves state machine replication by sequentially executing consensus +instances to agree on each block of transactions that are +then executed by the service being replicated. We consider a variant of the +Byzantine consensus problem called Validity Predicate-based Byzantine consensus +that is motivated by blockchain systems~\cite{GLR17:red-belly-bc}. The problem +is defined by an agreement, a termination, and a validity property. + + \begin{itemize} \item \emph{Agreement:} No two correct processes decide on + different values. \item \emph{Termination:} All correct processes + eventually decide on a value. \item \emph{Validity:} A decided value + is valid, i.e., it satisfies the predefined predicate denoted + \emph{valid()}. \end{itemize} + + This variant of the Byzantine consensus problem has an application-specific + \emph{valid()} predicate to indicate whether a value is valid. In the context + of blockchain systems, for example, a value is not valid if it does not + contain an appropriate hash of the last value (block) added to the blockchain. diff --git a/spec/consensus/consensus-paper/homodel.sty b/spec/consensus/consensus-paper/homodel.sty new file mode 100644 index 00000000000..19f83e926e1 --- /dev/null +++ b/spec/consensus/consensus-paper/homodel.sty @@ -0,0 +1,32 @@ +\newcommand{\NC}{\mbox{\it NC}} +\newcommand{\HO}{\mbox{\it HO}} +\newcommand{\AS}{\mbox{\it AS}} +\newcommand{\SK}{\mbox{\it SK}} +\newcommand{\SHO}{\mbox{\it SHO}} +\newcommand{\AHO}{\mbox{\it AHO}} +\newcommand{\CONS}{\mbox{\it CONS}} +\newcommand{\K}{\mbox{\it K}} + +\newcommand{\Alg}{\mathcal{A}} +\newcommand{\Pred}{\mathcal{P}} +\newcommand{\Spr}{S_p^r} +\newcommand{\Tpr}{T_p^r} +\newcommand{\mupr}{\vec{\mu}_p^{\,r}} + +\newcommand{\MSpr}{S_p^{\rho}} +\newcommand{\MTpr}{T_p^{\rho}} + + + +\newconstruct{\SEND}{$\Spr$:}{}{\ENDSEND}{} +\newconstruct{\TRAN}{$\Tpr$:}{}{\ENDTRAN}{} +\newconstruct{\ROUND}{\textbf{Round}}{\!\textbf{:}}{\ENDROUND}{} +\newconstruct{\VARIABLES}{\textbf{Variables:}}{}{\ENDVARIABLES}{} +\newconstruct{\INIT}{\textbf{Initialization:}}{}{\ENDINIT}{} + +\newconstruct{\MSEND}{$\MSpr$:}{}{\ENDMSEND}{} +\newconstruct{\MTRAN}{$\MTpr$:}{}{\ENDMTRAN}{} + +\newconstruct{\SROUND}{\textbf{Selection Round}}{\!\textbf{:}}{\ENDSROUND}{} +\newconstruct{\VROUND}{\textbf{Validation Round}}{\!\textbf{:}}{\ENDVROUND}{} +\newconstruct{\DROUND}{\textbf{Decision Round}}{\!\textbf{:}}{\ENDDROUND}{} diff --git a/spec/consensus/consensus-paper/intro.tex b/spec/consensus/consensus-paper/intro.tex new file mode 100644 index 00000000000..493b509e912 --- /dev/null +++ b/spec/consensus/consensus-paper/intro.tex @@ -0,0 +1,138 @@ +\section{Introduction} \label{sec:tendermint} + +Consensus is a fundamental problem in distributed computing. It +is important because of it's role in State Machine Replication (SMR), a generic +approach for replicating services that can be modeled as a deterministic state +machine~\cite{Lam78:cacm, Sch90:survey}. The key idea of this approach is that +service replicas start in the same initial state, and then execute requests +(also called transactions) in the same order; thereby guaranteeing that +replicas stay in sync with each other. The role of consensus in the SMR +approach is ensuring that all replicas receive transactions in the same order. +Traditionally, deployments of SMR based systems are in data-center settings +(local area network), have a small number of replicas (three to seven) and are +typically part of a single administration domain (e.g., Chubby +\cite{Bur:osdi06}); therefore they handle benign (crash) failures only, as more +general forms of failure (in particular, malicious or Byzantine faults) are +considered to occur with only negligible probability. + +The success of cryptocurrencies and blockchain systems in recent years (e.g., +\cite{Nak2012:bitcoin, But2014:ethereum}) pose a whole new set of challenges on +the design and deployment of SMR based systems: reaching agreement over wide +area network, among large number of nodes (hundreds or thousands) that are not +part of the same administrative domain, and where a subset of nodes can behave +maliciously (Byzantine faults). Furthermore, contrary to the previous +data-center deployments where nodes are fully connected to each other, in +blockchain systems, a node is only connected to a subset of other nodes, so +communication is achieved by gossip-based peer-to-peer protocols. +The new requirements demand designs and algorithms that are not necessarily +present in the classical academic literature on Byzantine fault tolerant +consensus (or SMR) systems (e.g., \cite{DLS88:jacm, CL02:tcs}) as the primary +focus was different setup. + +In this paper we describe a novel Byzantine-fault tolerant consensus algorithm +that is the core of the BFT SMR platform called Tendermint\footnote{The + Tendermint platform is available open source at + https://github.com/tendermint/tendermint.}. The Tendermint platform consists of +a high-performance BFT SMR implementation written in Go, a flexible interface +for +building arbitrary deterministic applications above the consensus, and a suite +of tools for deployment and management. + +The Tendermint consensus algorithm is inspired by the PBFT SMR +algorithm~\cite{CL99:osdi} and the DLS algorithm for authenticated faults (the +Algorithm 2 from \cite{DLS88:jacm}). Similar to DLS algorithm, Tendermint +proceeds in +rounds\footnote{Tendermint is not presented in the basic round model of + \cite{DLS88:jacm}. Furthermore, we use the term round differently than in + \cite{DLS88:jacm}; in Tendermint a round denotes a sequence of communication + steps instead of a single communication step in \cite{DLS88:jacm}.}, where each +round has a dedicated proposer (also called coordinator or +leader) and a process proceeds to a new round as part of normal +processing (not only in case the proposer is faulty or suspected as being faulty +by enough processes as in PBFT). +The communication pattern of each round is very similar to the "normal" case +of PBFT. Therefore, in preferable conditions (correct proposer, timely and +reliable communication between correct processes), Tendermint decides in three +communication steps (the same as PBFT). + +The major novelty and contribution of the Tendermint consensus algorithm is a +new termination mechanism. As explained in \cite{MHS09:opodis, RMS10:dsn}, the +existing BFT consensus (and SMR) algorithms for the partially synchronous +system model (for example PBFT~\cite{CL99:osdi}, \cite{DLS88:jacm}, +\cite{MA06:tdsc}) typically relies on the communication pattern illustrated in +Figure~\ref{ch3:fig:coordinator-change} for termination. The +Figure~\ref{ch3:fig:coordinator-change} illustrates messages exchanged during +the proposer change when processes start a new round\footnote{There is no + consistent terminology in the distributed computing terminology on naming + sequence of communication steps that corresponds to a logical unit. It is + sometimes called a round, phase or a view.}. It guarantees that eventually (ie. +after some Global Stabilization Time, GST), there exists a round with a correct +proposer that will bring the system into a univalent configuration. +Intuitively, in a round in which the proposed value is accepted +by all correct processes, and communication between correct processes is +timely and reliable, all correct processes decide. + + +\begin{figure}[tbh!] \def\rdstretch{5} \def\ystretch{3} \centering + \begin{rounddiag}{4}{2} \round{1}{~} \rdmessage{1}{1}{$v_1$} + \rdmessage{2}{1}{$v_2$} \rdmessage{3}{1}{$v_3$} \rdmessage{4}{1}{$v_4$} + \round{2}{~} \rdmessage{1}{1}{$x, [v_{1..4}]$} + \rdmessage{1}{2}{$~~~~~~x, [v_{1..4}]$} \rdmessage{1}{3}{$~~~~~~~~x, + [v_{1..4}]$} \rdmessage{1}{4}{$~~~~~~~x, [v_{1..4}]$} \end{rounddiag} + \vspace{-5mm} \caption{\boldmath Proposer (coordinator) change: $p_1$ is the + new proposer.} \label{ch3:fig:coordinator-change} \end{figure} + +To ensure that a proposed value is accepted by all correct +processes\footnote{The proposed value is not blindly accepted by correct + processes in BFT algorithms. A correct process always verifies if the proposed + value is safe to be accepted so that safety properties of consensus are not + violated.} +a proposer will 1) build the global state by receiving messages from other +processes, 2) select the safe value to propose and 3) send the selected value +together with the signed messages +received in the first step to support it. The +value $v_i$ that a correct process sends to the next proposer normally +corresponds to a value the process considers as acceptable for a decision: + +\begin{itemize} \item in PBFT~\cite{CL99:osdi} and DLS~\cite{DLS88:jacm} it is + not the value itself but a set of $2f+1$ signed messages with the same + value id, \item in Fast Byzantine Paxos~\cite{MA06:tdsc} the value + itself is being sent. \end{itemize} + +In both cases, using this mechanism in our system model (ie. high +number of nodes over gossip based network) would have high communication +complexity that increases with the number of processes: in the first case as +the message sent depends on the total number of processes, and in the second +case as the value (block of transactions) is sent by each process. The set of +messages received in the first step are normally piggybacked on the proposal +message (in the Figure~\ref{ch3:fig:coordinator-change} denoted with +$[v_{1..4}]$) to justify the choice of the selected value $x$. Note that +sending this message also does not scale with the number of processes in the +system. + +We designed a novel termination mechanism for Tendermint that better suits the +system model we consider. It does not require additional communication (neither +sending new messages nor piggybacking information on the existing messages) and +it is fully based on the communication pattern that is very similar to the +normal case in PBFT \cite{CL99:osdi}. Therefore, there is only a single mode of +execution in Tendermint, i.e., there is no separation between the normal and +the recovery mode, which is the case in other PBFT-like protocols (e.g., +\cite{CL99:osdi}, \cite{Ver09:spinning} or \cite{Cle09:aardvark}). We believe +this makes Tendermint simpler to understand and implement correctly. + +Note that the orthogonal approach for reducing message complexity in order to +improve +scalability and decentralization (number of processes) of BFT consensus +algorithms is using advanced cryptography (for example Boneh-Lynn-Shacham (BLS) +signatures \cite{BLS2001:crypto}) as done for example in SBFT +\cite{Gue2018:sbft}. + +The remainder of the paper is as follows: Section~\ref{sec:definitions} defines +the system model and gives the problem definitions. Tendermint +consensus algorithm is presented in Section~\ref{sec:tendermint} and the +proofs are given in Section~\ref{sec:proof}. We conclude in +Section~\ref{sec:conclusion}. + + + + diff --git a/spec/consensus/consensus-paper/latex8.bst b/spec/consensus/consensus-paper/latex8.bst new file mode 100644 index 00000000000..2c7af56479d --- /dev/null +++ b/spec/consensus/consensus-paper/latex8.bst @@ -0,0 +1,1124 @@ + +% --------------------------------------------------------------- +% +% $Id: latex8.bst,v 1.1 1995/09/15 15:13:49 ienne Exp $ +% +% by Paolo.Ienne@di.epfl.ch +% + +% --------------------------------------------------------------- +% +% no guarantee is given that the format corresponds perfectly to +% IEEE 8.5" x 11" Proceedings, but most features should be ok. +% +% --------------------------------------------------------------- +% +% `latex8' from BibTeX standard bibliography style `abbrv' +% version 0.99a for BibTeX versions 0.99a or later, LaTeX version 2.09. +% Copyright (C) 1985, all rights reserved. +% Copying of this file is authorized only if either +% (1) you make absolutely no changes to your copy, including name, or +% (2) if you do make changes, you name it something other than +% btxbst.doc, plain.bst, unsrt.bst, alpha.bst, and abbrv.bst. +% This restriction helps ensure that all standard styles are identical. +% The file btxbst.doc has the documentation for this style. + +ENTRY + { address + author + booktitle + chapter + edition + editor + howpublished + institution + journal + key + month + note + number + organization + pages + publisher + school + series + title + type + volume + year + } + {} + { label } + +INTEGERS { output.state before.all mid.sentence after.sentence after.block } + +FUNCTION {init.state.consts} +{ #0 'before.all := + #1 'mid.sentence := + #2 'after.sentence := + #3 'after.block := +} + +STRINGS { s t } + +FUNCTION {output.nonnull} +{ 's := + output.state mid.sentence = + { ", " * write$ } + { output.state after.block = + { add.period$ write$ + newline$ + "\newblock " write$ + } + { output.state before.all = + 'write$ + { add.period$ " " * write$ } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION {output} +{ duplicate$ empty$ + 'pop$ + 'output.nonnull + if$ +} + +FUNCTION {output.check} +{ 't := + duplicate$ empty$ + { pop$ "empty " t * " in " * cite$ * warning$ } + 'output.nonnull + if$ +} + +FUNCTION {output.bibitem} +{ newline$ + "\bibitem{" write$ + cite$ write$ + "}" write$ + newline$ + "" + before.all 'output.state := +} + +FUNCTION {fin.entry} +{ add.period$ + write$ + newline$ +} + +FUNCTION {new.block} +{ output.state before.all = + 'skip$ + { after.block 'output.state := } + if$ +} + +FUNCTION {new.sentence} +{ output.state after.block = + 'skip$ + { output.state before.all = + 'skip$ + { after.sentence 'output.state := } + if$ + } + if$ +} + +FUNCTION {not} +{ { #0 } + { #1 } + if$ +} + +FUNCTION {and} +{ 'skip$ + { pop$ #0 } + if$ +} + +FUNCTION {or} +{ { pop$ #1 } + 'skip$ + if$ +} + +FUNCTION {new.block.checka} +{ empty$ + 'skip$ + 'new.block + if$ +} + +FUNCTION {new.block.checkb} +{ empty$ + swap$ empty$ + and + 'skip$ + 'new.block + if$ +} + +FUNCTION {new.sentence.checka} +{ empty$ + 'skip$ + 'new.sentence + if$ +} + +FUNCTION {new.sentence.checkb} +{ empty$ + swap$ empty$ + and + 'skip$ + 'new.sentence + if$ +} + +FUNCTION {field.or.null} +{ duplicate$ empty$ + { pop$ "" } + 'skip$ + if$ +} + +FUNCTION {emphasize} +{ duplicate$ empty$ + { pop$ "" } + { "{\em " swap$ * "}" * } + if$ +} + +INTEGERS { nameptr namesleft numnames } + +FUNCTION {format.names} +{ 's := + #1 'nameptr := + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { s nameptr "{f.~}{vv~}{ll}{, jj}" format.name$ 't := + nameptr #1 > + { namesleft #1 > + { ", " * t * } + { numnames #2 > + { "," * } + 'skip$ + if$ + t "others" = + { " et~al." * } + { " and " * t * } + if$ + } + if$ + } + 't + if$ + nameptr #1 + 'nameptr := + + namesleft #1 - 'namesleft := + } + while$ +} + +FUNCTION {format.authors} +{ author empty$ + { "" } + { author format.names } + if$ +} + +FUNCTION {format.editors} +{ editor empty$ + { "" } + { editor format.names + editor num.names$ #1 > + { ", editors" * } + { ", editor" * } + if$ + } + if$ +} + +FUNCTION {format.title} +{ title empty$ + { "" } + { title "t" change.case$ } + if$ +} + +FUNCTION {n.dashify} +{ 't := + "" + { t empty$ not } + { t #1 #1 substring$ "-" = + { t #1 #2 substring$ "--" = not + { "--" * + t #2 global.max$ substring$ 't := + } + { { t #1 #1 substring$ "-" = } + { "-" * + t #2 global.max$ substring$ 't := + } + while$ + } + if$ + } + { t #1 #1 substring$ * + t #2 global.max$ substring$ 't := + } + if$ + } + while$ +} + +FUNCTION {format.date} +{ year empty$ + { month empty$ + { "" } + { "there's a month but no year in " cite$ * warning$ + month + } + if$ + } + { month empty$ + 'year + { month " " * year * } + if$ + } + if$ +} + +FUNCTION {format.btitle} +{ title emphasize +} + +FUNCTION {tie.or.space.connect} +{ duplicate$ text.length$ #3 < + { "~" } + { " " } + if$ + swap$ * * +} + +FUNCTION {either.or.check} +{ empty$ + 'pop$ + { "can't use both " swap$ * " fields in " * cite$ * warning$ } + if$ +} + +FUNCTION {format.bvolume} +{ volume empty$ + { "" } + { "volume" volume tie.or.space.connect + series empty$ + 'skip$ + { " of " * series emphasize * } + if$ + "volume and number" number either.or.check + } + if$ +} + +FUNCTION {format.number.series} +{ volume empty$ + { number empty$ + { series field.or.null } + { output.state mid.sentence = + { "number" } + { "Number" } + if$ + number tie.or.space.connect + series empty$ + { "there's a number but no series in " cite$ * warning$ } + { " in " * series * } + if$ + } + if$ + } + { "" } + if$ +} + +FUNCTION {format.edition} +{ edition empty$ + { "" } + { output.state mid.sentence = + { edition "l" change.case$ " edition" * } + { edition "t" change.case$ " edition" * } + if$ + } + if$ +} + +INTEGERS { multiresult } + +FUNCTION {multi.page.check} +{ 't := + #0 'multiresult := + { multiresult not + t empty$ not + and + } + { t #1 #1 substring$ + duplicate$ "-" = + swap$ duplicate$ "," = + swap$ "+" = + or or + { #1 'multiresult := } + { t #2 global.max$ substring$ 't := } + if$ + } + while$ + multiresult +} + +FUNCTION {format.pages} +{ pages empty$ + { "" } + { pages multi.page.check + { "pages" pages n.dashify tie.or.space.connect } + { "page" pages tie.or.space.connect } + if$ + } + if$ +} + +FUNCTION {format.vol.num.pages} +{ volume field.or.null + number empty$ + 'skip$ + { "(" number * ")" * * + volume empty$ + { "there's a number but no volume in " cite$ * warning$ } + 'skip$ + if$ + } + if$ + pages empty$ + 'skip$ + { duplicate$ empty$ + { pop$ format.pages } + { ":" * pages n.dashify * } + if$ + } + if$ +} + +FUNCTION {format.chapter.pages} +{ chapter empty$ + 'format.pages + { type empty$ + { "chapter" } + { type "l" change.case$ } + if$ + chapter tie.or.space.connect + pages empty$ + 'skip$ + { ", " * format.pages * } + if$ + } + if$ +} + +FUNCTION {format.in.ed.booktitle} +{ booktitle empty$ + { "" } + { editor empty$ + { "In " booktitle emphasize * } + { "In " format.editors * ", " * booktitle emphasize * } + if$ + } + if$ +} + +FUNCTION {empty.misc.check} + +{ author empty$ title empty$ howpublished empty$ + month empty$ year empty$ note empty$ + and and and and and + key empty$ not and + { "all relevant fields are empty in " cite$ * warning$ } + 'skip$ + if$ +} + +FUNCTION {format.thesis.type} +{ type empty$ + 'skip$ + { pop$ + type "t" change.case$ + } + if$ +} + +FUNCTION {format.tr.number} +{ type empty$ + { "Technical Report" } + 'type + if$ + number empty$ + { "t" change.case$ } + { number tie.or.space.connect } + if$ +} + +FUNCTION {format.article.crossref} +{ key empty$ + { journal empty$ + { "need key or journal for " cite$ * " to crossref " * crossref * + warning$ + "" + } + { "In {\em " journal * "\/}" * } + if$ + } + { "In " key * } + if$ + " \cite{" * crossref * "}" * +} + +FUNCTION {format.crossref.editor} +{ editor #1 "{vv~}{ll}" format.name$ + editor num.names$ duplicate$ + #2 > + { pop$ " et~al." * } + { #2 < + 'skip$ + { editor #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" = + { " et~al." * } + { " and " * editor #2 "{vv~}{ll}" format.name$ * } + if$ + } + if$ + } + if$ +} + +FUNCTION {format.book.crossref} +{ volume empty$ + { "empty volume in " cite$ * "'s crossref of " * crossref * warning$ + "In " + } + { "Volume" volume tie.or.space.connect + " of " * + } + if$ + editor empty$ + editor field.or.null author field.or.null = + or + { key empty$ + { series empty$ + { "need editor, key, or series for " cite$ * " to crossref " * + crossref * warning$ + "" * + } + { "{\em " * series * "\/}" * } + if$ + } + { key * } + if$ + } + { format.crossref.editor * } + if$ + " \cite{" * crossref * "}" * +} + +FUNCTION {format.incoll.inproc.crossref} +{ editor empty$ + editor field.or.null author field.or.null = + or + { key empty$ + { booktitle empty$ + { "need editor, key, or booktitle for " cite$ * " to crossref " * + crossref * warning$ + "" + } + { "In {\em " booktitle * "\/}" * } + if$ + } + { "In " key * } + if$ + } + { "In " format.crossref.editor * } + if$ + " \cite{" * crossref * "}" * +} + +FUNCTION {article} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + crossref missing$ + { journal emphasize "journal" output.check + format.vol.num.pages output + format.date "year" output.check + } + { format.article.crossref output.nonnull + format.pages output + } + if$ + new.block + note output + fin.entry +} + +FUNCTION {book} +{ output.bibitem + author empty$ + { format.editors "author and editor" output.check } + { format.authors output.nonnull + crossref missing$ + { "author and editor" editor either.or.check } + 'skip$ + if$ + } + if$ + new.block + format.btitle "title" output.check + crossref missing$ + { format.bvolume output + new.block + format.number.series output + new.sentence + publisher "publisher" output.check + address output + } + { new.block + format.book.crossref output.nonnull + } + if$ + format.edition output + format.date "year" output.check + new.block + note output + fin.entry +} + +FUNCTION {booklet} +{ output.bibitem + format.authors output + new.block + format.title "title" output.check + howpublished address new.block.checkb + howpublished output + address output + format.date output + new.block + note output + fin.entry +} + +FUNCTION {inbook} +{ output.bibitem + author empty$ + { format.editors "author and editor" output.check } + { format.authors output.nonnull + + crossref missing$ + { "author and editor" editor either.or.check } + 'skip$ + if$ + } + if$ + new.block + format.btitle "title" output.check + crossref missing$ + { format.bvolume output + format.chapter.pages "chapter and pages" output.check + new.block + format.number.series output + new.sentence + publisher "publisher" output.check + address output + } + { format.chapter.pages "chapter and pages" output.check + new.block + format.book.crossref output.nonnull + } + if$ + format.edition output + format.date "year" output.check + new.block + note output + fin.entry +} + +FUNCTION {incollection} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + crossref missing$ + { format.in.ed.booktitle "booktitle" output.check + format.bvolume output + format.number.series output + format.chapter.pages output + new.sentence + publisher "publisher" output.check + address output + format.edition output + format.date "year" output.check + } + { format.incoll.inproc.crossref output.nonnull + format.chapter.pages output + } + if$ + new.block + note output + fin.entry +} + +FUNCTION {inproceedings} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + crossref missing$ + { format.in.ed.booktitle "booktitle" output.check + format.bvolume output + format.number.series output + format.pages output + address empty$ + { organization publisher new.sentence.checkb + organization output + publisher output + format.date "year" output.check + } + { address output.nonnull + format.date "year" output.check + new.sentence + organization output + publisher output + } + if$ + } + { format.incoll.inproc.crossref output.nonnull + format.pages output + } + if$ + new.block + note output + fin.entry +} + +FUNCTION {conference} { inproceedings } + +FUNCTION {manual} +{ output.bibitem + author empty$ + { organization empty$ + 'skip$ + { organization output.nonnull + address output + } + if$ + } + { format.authors output.nonnull } + if$ + new.block + format.btitle "title" output.check + author empty$ + { organization empty$ + { address new.block.checka + address output + } + 'skip$ + if$ + } + { organization address new.block.checkb + organization output + address output + } + if$ + format.edition output + format.date output + new.block + note output + fin.entry +} + +FUNCTION {mastersthesis} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + "Master's thesis" format.thesis.type output.nonnull + school "school" output.check + address output + format.date "year" output.check + new.block + note output + fin.entry +} + +FUNCTION {misc} +{ output.bibitem + format.authors output + title howpublished new.block.checkb + format.title output + howpublished new.block.checka + howpublished output + format.date output + new.block + note output + fin.entry + empty.misc.check +} + +FUNCTION {phdthesis} +{ output.bibitem + format.authors "author" output.check + new.block + format.btitle "title" output.check + new.block + "PhD thesis" format.thesis.type output.nonnull + school "school" output.check + address output + format.date "year" output.check + new.block + note output + fin.entry +} + +FUNCTION {proceedings} +{ output.bibitem + editor empty$ + { organization output } + { format.editors output.nonnull } + + if$ + new.block + format.btitle "title" output.check + format.bvolume output + format.number.series output + address empty$ + { editor empty$ + { publisher new.sentence.checka } + { organization publisher new.sentence.checkb + organization output + } + if$ + publisher output + format.date "year" output.check + } + { address output.nonnull + format.date "year" output.check + new.sentence + editor empty$ + 'skip$ + { organization output } + if$ + publisher output + } + if$ + new.block + note output + fin.entry +} + +FUNCTION {techreport} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + format.tr.number output.nonnull + institution "institution" output.check + address output + format.date "year" output.check + new.block + note output + fin.entry +} + +FUNCTION {unpublished} +{ output.bibitem + format.authors "author" output.check + new.block + format.title "title" output.check + new.block + note "note" output.check + format.date output + fin.entry +} + +FUNCTION {default.type} { misc } + +MACRO {jan} {"Jan."} + +MACRO {feb} {"Feb."} + +MACRO {mar} {"Mar."} + +MACRO {apr} {"Apr."} + +MACRO {may} {"May"} + +MACRO {jun} {"June"} + +MACRO {jul} {"July"} + +MACRO {aug} {"Aug."} + +MACRO {sep} {"Sept."} + +MACRO {oct} {"Oct."} + +MACRO {nov} {"Nov."} + +MACRO {dec} {"Dec."} + +MACRO {acmcs} {"ACM Comput. Surv."} + +MACRO {acta} {"Acta Inf."} + +MACRO {cacm} {"Commun. ACM"} + +MACRO {ibmjrd} {"IBM J. Res. Dev."} + +MACRO {ibmsj} {"IBM Syst.~J."} + +MACRO {ieeese} {"IEEE Trans. Softw. Eng."} + +MACRO {ieeetc} {"IEEE Trans. Comput."} + +MACRO {ieeetcad} + {"IEEE Trans. Comput.-Aided Design Integrated Circuits"} + +MACRO {ipl} {"Inf. Process. Lett."} + +MACRO {jacm} {"J.~ACM"} + +MACRO {jcss} {"J.~Comput. Syst. Sci."} + +MACRO {scp} {"Sci. Comput. Programming"} + +MACRO {sicomp} {"SIAM J. Comput."} + +MACRO {tocs} {"ACM Trans. Comput. Syst."} + +MACRO {tods} {"ACM Trans. Database Syst."} + +MACRO {tog} {"ACM Trans. Gr."} + +MACRO {toms} {"ACM Trans. Math. Softw."} + +MACRO {toois} {"ACM Trans. Office Inf. Syst."} + +MACRO {toplas} {"ACM Trans. Prog. Lang. Syst."} + +MACRO {tcs} {"Theoretical Comput. Sci."} + +READ + +FUNCTION {sortify} +{ purify$ + "l" change.case$ +} + +INTEGERS { len } + +FUNCTION {chop.word} +{ 's := + 'len := + s #1 len substring$ = + { s len #1 + global.max$ substring$ } + 's + if$ +} + +FUNCTION {sort.format.names} +{ 's := + #1 'nameptr := + "" + s num.names$ 'numnames := + numnames 'namesleft := + { namesleft #0 > } + { nameptr #1 > + { " " * } + 'skip$ + if$ + s nameptr "{vv{ } }{ll{ }}{ f{ }}{ jj{ }}" format.name$ 't := + nameptr numnames = t "others" = and + { "et al" * } + { t sortify * } + if$ + nameptr #1 + 'nameptr := + namesleft #1 - 'namesleft := + } + while$ +} + +FUNCTION {sort.format.title} +{ 't := + "A " #2 + "An " #3 + "The " #4 t chop.word + chop.word + chop.word + sortify + #1 global.max$ substring$ +} + +FUNCTION {author.sort} +{ author empty$ + { key empty$ + { "to sort, need author or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {author.editor.sort} +{ author empty$ + { editor empty$ + { key empty$ + { "to sort, need author, editor, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { editor sort.format.names } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {author.organization.sort} +{ author empty$ + + { organization empty$ + { key empty$ + { "to sort, need author, organization, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { "The " #4 organization chop.word sortify } + if$ + } + { author sort.format.names } + if$ +} + +FUNCTION {editor.organization.sort} +{ editor empty$ + { organization empty$ + { key empty$ + { "to sort, need editor, organization, or key in " cite$ * warning$ + "" + } + { key sortify } + if$ + } + { "The " #4 organization chop.word sortify } + if$ + } + { editor sort.format.names } + if$ +} + +FUNCTION {presort} +{ type$ "book" = + type$ "inbook" = + or + 'author.editor.sort + { type$ "proceedings" = + 'editor.organization.sort + { type$ "manual" = + 'author.organization.sort + 'author.sort + if$ + } + if$ + } + if$ + " " + * + year field.or.null sortify + * + " " + * + title field.or.null + sort.format.title + * + #1 entry.max$ substring$ + 'sort.key$ := +} + +ITERATE {presort} + +SORT + +STRINGS { longest.label } + +INTEGERS { number.label longest.label.width } + +FUNCTION {initialize.longest.label} +{ "" 'longest.label := + #1 'number.label := + #0 'longest.label.width := +} + +FUNCTION {longest.label.pass} +{ number.label int.to.str$ 'label := + number.label #1 + 'number.label := + label width$ longest.label.width > + { label 'longest.label := + label width$ 'longest.label.width := + } + 'skip$ + if$ +} + +EXECUTE {initialize.longest.label} + +ITERATE {longest.label.pass} + +FUNCTION {begin.bib} +{ preamble$ empty$ + 'skip$ + { preamble$ write$ newline$ } + if$ + "\begin{thebibliography}{" longest.label * + "}\setlength{\itemsep}{-1ex}\small" * write$ newline$ +} + +EXECUTE {begin.bib} + +EXECUTE {init.state.consts} + +ITERATE {call.type$} + +FUNCTION {end.bib} +{ newline$ + "\end{thebibliography}" write$ newline$ +} + +EXECUTE {end.bib} + +% end of file latex8.bst +% --------------------------------------------------------------- + + + diff --git a/spec/consensus/consensus-paper/latex8.sty b/spec/consensus/consensus-paper/latex8.sty new file mode 100644 index 00000000000..1e6b0dc7e6c --- /dev/null +++ b/spec/consensus/consensus-paper/latex8.sty @@ -0,0 +1,168 @@ +% --------------------------------------------------------------- +% +% $Id: latex8.sty,v 1.2 1995/09/15 15:31:13 ienne Exp $ +% +% by Paolo.Ienne@di.epfl.ch +% +% --------------------------------------------------------------- +% +% no guarantee is given that the format corresponds perfectly to +% IEEE 8.5" x 11" Proceedings, but most features should be ok. +% +% --------------------------------------------------------------- +% with LaTeX2e: +% ============= +% +% use as +% \documentclass[times,10pt,twocolumn]{article} +% \usepackage{latex8} +% \usepackage{times} +% +% --------------------------------------------------------------- + +% with LaTeX 2.09: +% ================ +% +% use as +% \documentstyle[times,art10,twocolumn,latex8]{article} +% +% --------------------------------------------------------------- +% with both versions: +% =================== +% +% specify \pagestyle{empty} to omit page numbers in the final +% version +% +% specify references as +% \bibliographystyle{latex8} +% \bibliography{...your files...} +% +% use Section{} and SubSection{} instead of standard section{} +% and subsection{} to obtain headings in the form +% "1.3. My heading" +% +% --------------------------------------------------------------- + +\typeout{IEEE 8.5 x 11-Inch Proceedings Style `latex8.sty'.} + +% ten point helvetica bold required for captions +% in some sites the name of the helvetica bold font may differ, +% change the name here: +\font\tenhv = phvb at 10pt +%\font\tenhv = phvb7t at 10pt + +% eleven point times bold required for second-order headings +% \font\elvbf = cmbx10 scaled 1100 +\font\elvbf = ptmb scaled 1100 + +% set dimensions of columns, gap between columns, and paragraph indent +\setlength{\textheight}{8.875in} +\setlength{\textwidth}{6.875in} +\setlength{\columnsep}{0.3125in} +\setlength{\topmargin}{0in} +\setlength{\headheight}{0in} +\setlength{\headsep}{0in} +\setlength{\parindent}{1pc} +\setlength{\oddsidemargin}{-.304in} +\setlength{\evensidemargin}{-.304in} + +% memento from size10.clo +% \normalsize{\@setfontsize\normalsize\@xpt\@xiipt} +% \small{\@setfontsize\small\@ixpt{11}} +% \footnotesize{\@setfontsize\footnotesize\@viiipt{9.5}} +% \scriptsize{\@setfontsize\scriptsize\@viipt\@viiipt} +% \tiny{\@setfontsize\tiny\@vpt\@vipt} +% \large{\@setfontsize\large\@xiipt{14}} +% \Large{\@setfontsize\Large\@xivpt{18}} +% \LARGE{\@setfontsize\LARGE\@xviipt{22}} +% \huge{\@setfontsize\huge\@xxpt{25}} +% \Huge{\@setfontsize\Huge\@xxvpt{30}} + +\def\@maketitle + { + \newpage + \null + \vskip .375in + \begin{center} + {\Large \bf \@title \par} + % additional two empty lines at the end of the title + \vspace*{24pt} + { + \large + \lineskip .5em + \begin{tabular}[t]{c} + \@author + \end{tabular} + \par + } + % additional small space at the end of the author name + \vskip .5em + { + \large + \begin{tabular}[t]{c} + \@affiliation + \end{tabular} + \par + \ifx \@empty \@email + \else + \begin{tabular}{r@{~}l} + E-mail: & {\tt \@email} + \end{tabular} + \par + \fi + } + % additional empty line at the end of the title block + \vspace*{12pt} + \end{center} + } + +\def\abstract + {% + \centerline{\large\bf Abstract}% + \vspace*{12pt}% + \it% + } + +\def\endabstract + { + % additional empty line at the end of the abstract + \vspace*{12pt} + } + +\def\affiliation#1{\gdef\@affiliation{#1}} \gdef\@affiliation{} + +\def\email#1{\gdef\@email{#1}} +\gdef\@email{} + +\newlength{\@ctmp} +\newlength{\@figindent} +\setlength{\@figindent}{1pc} + +\long\def\@makecaption#1#2{ + \vskip 10pt + \setbox\@tempboxa\hbox{\tenhv\noindent #1.~#2} + \setlength{\@ctmp}{\hsize} + \addtolength{\@ctmp}{-\@figindent}\addtolength{\@ctmp}{-\@figindent} + % IF longer than one indented paragraph line + \ifdim \wd\@tempboxa >\@ctmp + % THEN set as an indented paragraph + \begin{list}{}{\leftmargin\@figindent \rightmargin\leftmargin} + \item[]\tenhv #1.~#2\par + \end{list} + \else + % ELSE center + \hbox to\hsize{\hfil\box\@tempboxa\hfil} + \fi} + +% correct heading spacing and type +\def\section{\@startsection {section}{1}{\z@} + {14pt plus 2pt minus 2pt}{14pt plus 2pt minus 2pt} {\large\bf}} +\def\subsection{\@startsection {subsection}{2}{\z@} + {13pt plus 2pt minus 2pt}{13pt plus 2pt minus 2pt} {\elvbf}} + +% add the period after section numbers +\newcommand{\Section}[1]{\section{\hskip -1em.~#1}} +\newcommand{\SubSection}[1]{\subsection{\hskip -1em.~#1}} + +% end of file latex8.sty +% --------------------------------------------------------------- diff --git a/spec/consensus/consensus-paper/lit.bib b/spec/consensus/consensus-paper/lit.bib new file mode 100644 index 00000000000..4abc83e70cb --- /dev/null +++ b/spec/consensus/consensus-paper/lit.bib @@ -0,0 +1,1659 @@ +%--- conferences -------------------------------------------------- +@STRING{WDAG96 = "Proceedings of the 10th International Workshop + on Distributed Algorithms (WDAG'96)"} +@STRING{WDAG97 = "Proceedings of the 11th International Workshop + on Distributed Algorithms (WDAG'97)"} +@STRING{DISC98 = "Proceedings of the 12th International Conference + on Distributed Computing ({DISC}'98)"} +@STRING{DISC99 = "Proceedings of the 13th International Conference + on Distributed Computing ({DISC}'99)"} +@STRING{DISC98 = "Proceedings of the 13th International Conference + on Distributed Computing ({DISC}'98)"} +@STRING{DISC99 = "Proceedings of the 13th International Conference + on Distributed Computing ({DISC}'99)"} +@STRING{DISC00 = "Proceedings of the 14th International Conference + on Distributed Computing ({DISC}'00)"} +@STRING{DISC01 = "Proceedings of the 15th International Conference + on Distributed Computing ({DISC}'01)"} +@STRING{DISC02 = "Proceedings of the 16th International Conference + on Distributed Computing ({DISC}'02)"} +@STRING{DISC03 = "Proceedings of the 17th International Conference + on Distributed Computing ({DISC}'03)"} +@STRING{DISC04 = "Proceedings of the 18th International Conference + on Distributed Computing ({DISC}'04)"} +@STRING{DISC05 = "Proceedings of the 19th International Conference + on Distributed Computing ({DISC}'05)"} +@STRING{PODC83 = "Proceeding of the 1st Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'83)"} +@STRING{PODC91 = "Proceeding of the 9th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'91)"} +@STRING{PODC94 = "Proceeding of the 12th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'94)"} +@STRING{PODC95 = "Proceeding of the 13th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'95)"} +@STRING{PODC96 = "Proceeding of the 14th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'96)"} +@STRING{PODC97 = "Proceeding of the 15th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'97)"} +@STRING{PODC98 = "Proceeding of the 16th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'98)"} +@STRING{PODC99 = "Proceeding of the 17th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'99)"} +@STRING{PODC00 = "Proceeding of the 18th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'00)"} +@STRING{PODC01 = "Proceeding of the 19th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'01)"} +@STRING{PODC02 = "Proceeding of the 20th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'02)"} +@STRING{PODC03 = "Proceeding of the 21st Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'03)"} +@STRING{PODC03 = "Proceeding of the 22nd Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'03)"} +@STRING{PODC04 = "Proceeding of the 23rd Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'04)"} +@STRING{PODC05 = "Proceeding of the 24th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'05)"} +@STRING{PODC06 = "Proceedings of the 25th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'06)"} +@STRING{PODC07 = "Proceedings of the 26th Annual {ACM} Symposium on + Principles of Distributed Computing ({PODC}'07)"} +@STRING{STOC91 = "Proceedings of the 23rd Annual {ACM} Symposium on + Theory of Computing ({STOC}'91)"} +@STRING{WSS01 = "Proceedings of the 5th International Workshop on + Self-Stabilizing Systems ({WSS} '01)"} +@STRING{SSS06 = "Proceedings of the 8th International Symposium on + Stabilization, Safety, and Security of Distributed + Systems ({SSS} '06)"} +@STRING{DSN00 = "Dependable Systems and Networks ({DSN} 2000)"} +@STRING{DSN05 = "Dependable Systems and Networks ({DSN} 2005)"} +@STRING{DSN06 = "Dependable Systems and Networks ({DSN} 2006)"} +@STRING{DSN07 = "Dependable Systems and Networks ({DSN} 2007)"} + +%--- journals ----------------------------------------------------- +@STRING{PPL = "Parallel Processing Letters"} +@STRING{IPL = "Information Processing Letters"} +@STRING{DC = "Distributed Computing"} +@STRING{JACM = "Journal of the ACM"} +@STRING{IC = "Information and Control"} +@STRING{TCS = "Theoretical Computer Science"} +@STRING{ACMTCS = "ACM Transactions on Computer Systems"} +@STRING{TDSC = "Transactions on Dependable and Secure Computing"} +@STRING{TPLS = "ACM Trans. Program. Lang. Syst."} + +%--- publisher ---------------------------------------------------- +@STRING{ACM = "ACM Press"} +@STRING{IEEE = "IEEE"} +@STRING{SPR = "Springer-Verlag"} + +%--- institution -------------------------------------------------- +@STRING{TUAuto = {Technische Universit\"at Wien, Department of + Automation}} +@STRING{TUECS = {Technische Universit\"at Wien, Embedded Computing + Systems Group}} + + +%------------------------------------------------------------------ +@article{ABND+90:jacm, + author = {Hagit Attiya and Amotz Bar-Noy and Danny Dolev and + David Peleg and R{\"u}diger Reischuk}, + title = {Renaming in an asynchronous environment}, + journal = JACM, + volume = {37}, + number = {3}, + year = {1990}, + pages = {524--548}, + publisher = ACM, + address = {New York, NY, USA}, +} + +@article{ABND95:jacm, + author = {Hagit Attiya and Amotz Bar-Noy and Danny Dolev}, + title = {Sharing memory robustly in message-passing systems}, + journal = JACM, + volume = {42}, + number = {1}, + year = {1995}, + pages = {124--142}, + publisher = ACM, + address = {New York, NY, USA}, +} + +@inproceedings{ACKM04:podc, + author = {Ittai Abraham and Gregory Chockler and Idit Keidar + and Dahlia Malkhi}, + title = {Byzantine disk paxos: optimal resilience with + byzantine shared memory.}, + booktitle = PODC04, + year = {2004}, + pages = {226-235} +} + +@article{ACKM05:dc, + author = {Ittai Abraham and Gregory Chockler and Idit Keidar + and Dahlia Malkhi}, + title = {Byzantine disk paxos: optimal resilience with + byzantine shared memory.}, + journal = DC, + volume = {18}, + number = {5}, + year = {2006}, + pages = {387-408} +} + +@article{ACT00:dc, + author = "Marcos Kawazoe Aguilera and Wei Chen and Sam Toueg", + title = "Failure Detection and Consensus in the + Crash-Recovery Model", + journal = DC, + year = 2000, + month = apr, + volume = 13, + number = 2, + pages = "99--125", + url = + "http://www.cs.cornell.edu/home/sam/FDpapers/crash-recovery-finaldcversion.ps" +} + +@article{ACT00:siam, + author = "Marcos Kawazoe Aguilera and Wei Chen and Sam Toueg", + title = "On quiescent reliable communication", + journal = "SIAM Journal of Computing", + year = 2000, + volume = 29, + number = 6, + pages = "2040--2073", + month = apr +} + +@inproceedings{ACT97:wdag, + author = "Marcos Kawazoe Aguilera and Wei Chen and Sam Toueg", + title = "Heartbeat: A Timeout-Free Failure Detector for + Quiescent Reliable Communication", + booktitle = WDAG97, + year = 1997, + pages = "126--140", + url = + "http://simon.cs.cornell.edu/Info/People/weichen/research/mypapers/wdag97final.ps" +} + +@article{ACT98:disc, + author = "Marcos Kawazoe Aguilera and Wei Chen and Sam Toueg", + title = "Failure Detection and Consensus in the + Crash-Recovery Model", + journal = DISC98, + year = 1998, + pages = "231--245", + publisher = SPR +} + +@article{ACT99:tcs, + author = "Marcos Kawazoe Aguilera and Wei Chen and Sam Toueg", + title = "Using the Heartbeat Failure Detector for Quiescent + Reliable Communication and Consensus in + Partitionable Networks", + journal = "Theoretical Computer Science", + year = 1999, + month = jun, + volume = 220, + number = 1, + pages = "3--30", + url = + "http://www.cs.cornell.edu/home/sam/FDpapers/TCS98final.ps" +} + +@inproceedings{ADGF+04:ispdc, + author = {Anceaume, Emmanuelle and Delporte-Gallet, Carole and + Fauconnier, Hugues and Hurfin, Michel and Le Lann, + G{\'e}rard }, + title = {Designing Modular Services in the Scattered + Byzantine Failure Model.}, + booktitle = {ISPDC/HeteroPar}, + year = {2004}, + pages = {262-269} +} + +@inproceedings{ADGF+06:dsn, + author = {Marcos Kawazoe Aguilera and Carole Delporte-Gallet + and Hugues Fauconnier and Sam Toueg}, + title = {Consensus with Byzantine Failures and Little System + Synchrony.}, + booktitle = DSN06, + year = {2006}, + pages = {147-155} +} + +@inproceedings{ADGFT01:disc, + author = "Marcos Kawazoe Aguilera and Carole Delporte-Gallet + and Hugues Fauconnier and Sam Toueg", + title = "Stable Leader Election", + booktitle = DISC01, + year = 2001, + pages = "108--122", + publisher = SPR +} + +@inproceedings{ADGFT03:podc, + author = "Marcos K. Aguilera and Carole Delporte-Gallet and + Hugues Fauconnier and Sam Toueg", + title = "On implementing {O}mega with weak reliability and + synchrony assumptions", + booktitle = PODC03, + year = 2003, + publisher = ACM +} + +@inproceedings{ADGFT04:podc, + author = {Marcos K. Aguilera and Carole Delporte-Gallet and + Hugues Fauconnier and Sam Toueg}, + title = {Communication-efficient leader election and + consensus with limited link synchrony}, + booktitle = PODC04, + year = 2004, + pages = {328--337}, + address = {St. John's, Newfoundland, Canada}, + publisher = ACM +} + +@inproceedings{ADGFT06:dsn, + author = {Marcos Kawazoe Aguilera and Carole Delporte-Gallet + and Hugues Fauconnier and Sam Toueg}, + title = {Consensus with Byzantine Failures and Little System + Synchrony.}, + booktitle = DSN06, + year = 2006, + pages = {147-155}, + ee = + {http://doi.ieeecomputersociety.org/10.1109/DSN.2006.22}, + bibsource = {DBLP, http://dblp.uni-trier.de} +} + +@inproceedings{ADLS91:stoc, + author = "Hagit Attiya and Cynthia Dwork and Nancy A. Lynch + and Larry J. Stockmeyer", + title = "Bounds on the Time to Reach Agreement in the + Presence of Timing Uncertainty", + booktitle = STOC91, + year = 1991, + pages = "359--369", +} + +@article{AT99:ipl, + author = "Marcos Kawazoe Aguilera and Sam Toueg", + title = "A Simple Bivalency Proof that t -Resilient Consensus + Requires t + 1 Rounds", + journal = IPL, + volume = "71", + number = "3-4", + pages = "155--158", + year = "1999" +} + +@Book{AW04:book, + author = {Attiya, Hagit and Welch, Jennifer}, + title = {Distributed Computing}, + publisher = {John Wiley {\&} Sons}, + edition = {2nd}, + year = {2004} +} + +@Book{AW98:book, + author = {Hagit Attiya and Jennifer Welch}, + title = {Distributed Computing}, + publisher = {McGraw-Hill Publishing Company}, + year = {1998} +} + +@InBook{AW98:book:chap12, + author = {Hagit Attiya and Jennifer Welch}, + title = {Distributed Computing}, + publisher = {McGraw-Hill Publishing Company}, + year = {1998}, + chapter = {12, "Improving the fault-tolerance of algorithms"} +} + +@inproceedings{ABHMS11:disc, + author = {Hagit Attiya and + Fatemeh Borran and + Martin Hutle and + Zarko Milosevic and + Andr{\'e} Schiper}, + title = {Structured Derivation of Semi-Synchronous Algorithms}, + booktitle = {DISC}, + year = {2011}, + pages = {374-388} +} + +@inproceedings{BCBG+07:podc, + author = {Martin Biely and Bernadette Charron-Bost and Antoine + Gaillard and Martin Hutle and Andr{\'e} Schiper and + Josef Widder}, + title = {Tolerating Corrupted Communication}, + publisher = ACM, + booktitle = PODC07, + year = {2007} +} + +@InProceedings{BCBT96:wdag, + author = {Anindya Basu and Bernadette Charron-Bost and Sam + Toueg}, + title = {Simulating Reliable Links with Unreliable Links in + the Presence of Process Crashes}, + pages = {105--122}, + booktitle = {WDAG 1996}, + editor = {Babao{\u g}lu, {\"O}zalp}, + year = {1996}, + month = {Oct}, + volume = {1151}, + ISBN = {3-540-61769-8}, + pubisher = {Springer}, + series = {Lecture Notes in Computer Science}, +} + +@article{BDFG03:sigact, + author = "R. Boichat and P. Dutta and S. Frolund and + R. Guerraoui", + title = "Reconstructing {P}axos", + journal = "ACM SIGACT News", + year = "2003", + volume = "34", + number = "1", + pages = "47-67" +} + +@unpublished{BHR+06:note, + author = "Martin Biely and Martin Hutle and Sergio Rajsbaum + and Ulrich Schmid and Corentin Travers and Josef + Widder", + title = "Discussion note on moving timely links", + note = "Unpublished", + month = apr, + year = 2006 +} + +@article{BHRT03:jda, + author = {Roberto Baldoni and Jean-Michel H{\'e}lary and + Michel Raynal and L{\'e}naick Tanguy}, + title = {Consensus in Byzantine asynchronous systems.}, + journal = {J. Discrete Algorithms}, + volume = {1}, + number = {2}, + year = {2003}, + pages = {185-210}, + ee = {http://dx.doi.org/10.1016/S1570-8667(03)00025-X}, + bibsource = {DBLP, http://dblp.uni-trier.de} +} + +@unpublished{BHSS08:tdsc, + author = {Fatemeh Borran and Martin Hutle and Nuno Santos and + Andr{\'e} Schiper}, + title = {Solving Consensus with Communication Predicates: + A~Quantitative Approach}, + note = {Under submission}, + year = {2008} +} + +@inproceedings{Ben83:podc, + author = {Michael Ben-Or}, + title = {Another Advantage of Free Choice: Completely + Asynchronous Agreement Protocols}, + booktitle = {PODC}, + year = {1983}, +} + +@inproceedings{Bra04:podc, + author = {Bracha, Gabriel}, + title = {An asynchronous [(n - 1)/3]-resilient consensus protocol}, + booktitle = {PODC '84: Proceedings of the third annual ACM symposium on Principles of distributed computing}, + year = {1984}, + isbn = {0-89791-143-1}, + pages = {154--162}, + location = {Vancouver, British Columbia, Canada}, + doi = {http://doi.acm.org/10.1145/800222.806743}, + publisher = {ACM}, + address = {New York, NY, USA}, + } + + +@inproceedings{CBGS00:dsn, + author = "Bernadette Charron-Bost and Rachid Guerraoui and + Andr{\'{e}} Schiper", + title = "Synchronous System and Perfect Failure Detector: + {S}olvability and efficiency issues", + booktitle = DSN00, + publisher = "{IEEE} Computer Society", + address = "New York, {USA}", + pages = "523--532", + year = "2000" +} + +@inproceedings{CBS06:prdc, + author = {Bernadette Charron-Bost and Andr{\'e} Schiper}, + title = {Improving Fast Paxos: being optimistic with no + overhead}, + booktitle = {Pacific Rim Dependable Computing, Proceedings}, + year = {2006} +} + +@article{CBS09, + author = {B. Charron-Bost and A. Schiper}, + title = {The {H}eard-{O}f model: computing in distributed systems with benign failures}, + journal ={Distributed Computing}, + number = {1}, + volume = {22}, + pages = {49-71}, + year ={2009} + } + + +@article{CBS07:sigact, + author = {Bernadette Charron-Bost and Andr\'{e} Schiper}, + title = {Harmful dogmas in fault tolerant distributed + computing}, + journal = {SIGACT News}, + volume = {38}, + number = {1}, + year = {2007}, + pages = {53--61}, +} + +@techreport{CBS07:tr, + author = {Charron-Bost, Bernadette and Schiper, Andr{\'{e}}}, + title = {The Heard-Of Model: Unifying all Benign Failures}, + institution = {EPFL}, + year = 2007, + OPTnumber = {LSR-REPORT-2006-004} +} + +@article{CELT00:jacm, + author = {Soma Chaudhuri and Maurice Erlihy and Nancy A. Lynch + and Mark R. Tuttle}, + title = {Tight bounds for k-set agreement}, + journal = JACM, + volume = {47}, + number = {5}, + year = {2000}, + pages = {912--943}, + publisher = ACM, + address = {New York, NY, USA}, +} + +@article{CF99:tpds, + author = "Flaviu Cristian and Christof Fetzer", + title = "The Timed Asynchronous Distributed System Model", + journal = "IEEE Transactions on Parallel and Distributed + Systems", + volume = "10", + number = "6", + pages = "642--657", + year = "1999" +} + +@article{CHT96:jacm, + author = "Tushar Deepak Chandra and Vassos Hadzilacos and Sam + Toueg", + title = "The Weakest Failure Detector for Solving Consensus", + journal = {JACM}, + year = {1996}, +} + +@article{CL02:tcs, + author = {Miguel Castro and Barbara Liskov}, + title = {Practical byzantine fault tolerance and proactive + recovery}, + journal = {ACMTCS}, + year = {2002}, +} + +@inproceedings{CL99:osdi, + author = {Miguel Castro and Barbara Liskov}, + title = {Practical byzantine fault tolerance and proactive + recovery}, + booktitle = {Proceedings of the 3rd Symposium on Operating + Systems Design and Implementation}, + year = {1999}, + month = feb +} + +@inproceedings{CT91:podc, + author = {Tushar Deepak Chandra and Sam Toueg}, + title = {Unreliable Failure Detectors for Asynchronous + Systems (Preliminary Version)}, + booktitle = PODC91, + year = {1991}, + pages = {325-340} +} + +@article{CT96:jacm1, + author = "Tushar Deepak Chandra and Sam Toueg", + title = "Unreliable Failure Detectors for Reliable + Distributed Systems", + journal = {JACM}, + year = {1996}, +} + +@inproceedings{CTA00:dsn, + author = "Wei Chen and Sam Toueg and Marcos Kawazoe Aguilera", + title = "On the Quality of Service of Failure Detectors", + booktitle = "Proceedings IEEE International Conference on + Dependable Systems and Networks (DSN / FTCS'30)", + address = "New York City, USA", + year = 2000 +} + +@TechReport{DFKM96:tr, + author = {Danny Dolev and Roy Friedman and Idit Keidar and + Dahlia Malkhi}, + title = {Failure detectors in omission failure environments}, + institution = {Department of Computer Science, Cornell University}, + year = {1996}, + type = {Technical Report}, + number = {96-1608} +} + +@inproceedings{DG02:podc, + author = {Partha Dutta and Rachid Guerraoui}, + title = {The inherent price of indulgence}, + booktitle = PODC02, + year = 2002, + pages = {88--97}, + location = {Monterey, California}, + publisher = ACM, + address = {New York, NY, USA}, +} + +@inproceedings{DGFG+04:podc, + author = {Carole Delporte-Gallet and Hugues Fauconnier and + Rachid Guerraoui and Vassos Hadzilacos and Petr + Kouznetsov and Sam Toueg}, + title = {The weakest failure detectors to solve certain + fundamental problems in distributed computing}, + booktitle = PODC04, + year = 2004, + pages = {338--346}, + location = {St. John's, Newfoundland, Canada}, + publisher = ACM, + address = {New York, NY, USA} +} + +@inproceedings{DGL05:dsn, + author = {Partha Dutta and Rachid Guerraoui and Leslie + Lamport}, + title = {How Fast Can Eventual Synchrony Lead to Consensus?}, + booktitle = {Proceedings of the 2005 International Conference on + Dependable Systems and Networks (DSN'05)}, + pages = {22--27}, + year = {2005}, + address = {Los Alamitos, CA, USA} +} + +@article{DLS88:jacm, + author = "Cynthia Dwork and Nancy Lynch and Larry Stockmeyer", + title = "Consensus in the Presence of Partial Synchrony", + journal = {JACM}, + year = {1988}, +} + +@article{DPLL00:tcs, + author = "De Prisco, Roberto and Butler Lampson and Nancy + Lynch", + title = "Revisiting the {PAXOS} algorithm", + journal = TCS, + volume = "243", + number = "1--2", + pages = "35--91", + year = "2000" +} + +@techreport{DS97:tr, + author = {A. Doudou and A. Schiper}, + title = {Muteness Failure Detectors for Consensus with + {B}yzantine Processes}, + institution = {EPFL, Dept d'Informatique}, + year = {1997}, + type = {TR}, + month = {October}, + number = {97/230}, +} + +@inproceedings{DS98:podc, + author = {A. Doudou and A. Schiper}, + title = {Muteness Detectors for Consensus with {B}yzantine + Processes ({B}rief {A}nnouncement)}, + booktitle = {PODC}, + month = jul, + year = {1998} +} + +@article{DSU04:survey, + author = {D{\'e}fago, Xavier and Schiper, Andr{\'e} and Urb\'{a}n, P{\'e}ter}, + title = {Total order broadcast and multicast algorithms: Taxonomy and survey}, + journal = {ACM Comput. Surv.}, + issue_date = {December 2004}, + volume = {36}, + number = {4}, + month = dec, + year = {2004}, + issn = {0360-0300}, + pages = {372--421}, + numpages = {50}, + publisher = {ACM}, + address = {New York, NY, USA}, + keywords = {Distributed systems, agreement problems, atomic broadcast, atomic multicast, classification, distributed algorithms, fault-tolerance, global ordering, group communication, message passing, survey, taxonomy, total ordering}, +} + +@article{DeCandia07:dynamo, + author = {DeCandia, Giuseppe and Hastorun, Deniz and Jampani, Madan and Kakulapati, Gunavardhan and Lakshman, Avinash and Pilchin, Alex and Sivasubramanian, Swaminathan and Vosshall, Peter and Vogels, Werner}, + title = {Dynamo: amazon's highly available key-value store}, + journal = {SIGOPS Oper. Syst. Rev.}, + issue_date = {December 2007}, + volume = {41}, + number = {6}, + month = oct, + year = {2007}, + issn = {0163-5980}, + pages = {205--220}, + numpages = {16}, + publisher = {ACM}, + address = {New York, NY, USA}, + keywords = {performance, reliability, scalability}, +} + + +@book{Dol00:book, + author = {Shlomi Dolev}, + title = {Self-Stabilization}, + publisher = {The MIT Press}, + year = {2000} +} + +@inproceedings{FC95:podc, + author = "Christof Fetzer and Flaviu Cristian", + title = "Lower Bounds for Convergence Function Based Clock + Synchronization", + booktitle = PODC95, + year = 1995, + pages = "137--143" +} + +@article{FLP85:jacm, + author = "Michael J. Fischer and Nancy A. Lynch and + M. S. Paterson", + title = "Impossibility of Distributed Consensus with one + Faulty Process", + journal = {JACM}, + year = {1985}, +} + +@article{FMR05:tdsc, + author = {Roy Friedman and Achour Most{\'e}faoui and Michel + Raynal}, + title = {Simple and Efficient Oracle-Based Consensus + Protocols for Asynchronous Byzantine Systems.}, + journal = TDSC, + volume = {2}, + number = {1}, + year = {2005}, + pages = {46-56}, + ee = {http://dx.doi.org/10.1109/TDSC.2005.13}, + bibsource = {DBLP, http://dblp.uni-trier.de} +} + +@inproceedings{FS04:podc, + author = "Christof Fetzer and Ulrich Schmid", + title = "Brief announcement: on the possibility of consensus + in asynchronous systems with finite average response + times.", + booktitle = PODC04, + year = 2004, + pages = 402 +} + +@InProceedings{GL00:disc, + author = {Eli Gafni and Lesli Lamport}, + title = {Disk Paxos}, + booktitle = DISC00, + pages = {330--344}, + year = {2000}, +} + +@Article{GL03:dc, + author = {Eli Gafni and Lesli Lamport}, + title = {Disk Paxos}, + journal = DC, + year = 2003, + volume = {16}, + number = {1}, + pages = {1--20} +} + +@inproceedings{GP01:wss, + author = "Felix C. G{\"a}rtner and Stefan Pleisch", + title = "({I}m)Possibilities of Predicate Detection in + Crash-Affected Systems", + booktitle = WSS01, + year = 2001, + pages = "98--113" +} + +@inproceedings{GP02:disc, + author = "Felix C. G{\"a}rtner and Stefan Pleisch", + title = "Failure Detection Sequencers: Necessary and + Sufficient Information about Failures to Solve + Predicate Detection", + booktitle = DISC02, + year = 2002, + pages = "280--294" +} + +@inproceedings{GS96:wdag, + author = {Rachid Guerraoui and Andr{\'e} Schiper}, + title = {{``Gamma-Accurate''} Failure Detectors}, + booktitle = WDAG96, + year = {1996}, + pages = {269--286}, + publisher = SPR, + address = {London, UK} +} + +@inproceedings{Gaf98:podc, + author = {Eli Gafni}, + title = {Round-by-round fault detectors (extended abstract): + unifying synchrony and asynchrony}, + booktitle = PODC98, + year = {1998}, + pages = {143--152}, + address = {Puerto Vallarta, Mexico}, + publisher = ACM +} + +@incollection{Gra78:book, + author = {Jim N. Gray}, + title = {Notes on data base operating systems}, + booktitle = {Operating Systems: An Advanced Course}, + chapter = {3.F}, + publisher = {Springer}, + year = {1978}, + editor = {R. Bayer, R.M. Graham, G. Seegm\"uller}, + volume = {60}, + series = {Lecture Notes in Computer Science}, + address = {New York}, + pages = {465}, +} + +@InProceedings{HMR98:srds, + author = {Hurfin, M. and Mostefaoui, A. and Raynal, M.}, + title = {Consensus in asynchronous systems where processes + can crash and recover}, + booktitle = {Seventeenth IEEE Symposium on Reliable Distributed + Systems, Proceedings. }, + pages = { 280--286}, + year = {1998}, + address = {West Lafayette, IN}, + month = oct, + organization = {IEEE} +} + +@inproceedings{HMSZ06:sss, + author = "Martin Hutle and Dahlia Malkhi and Ulrich Schmid and + Lidong Zhou", + title = "Brief Announcement: Chasing the Weakest System Model + for Implementing {$\Omega$} and Consensus", + booktitle = SSS06, + year = 2006 +} + +@incollection{HT93:ds, + author = {Hadzilacos, Vassos and Toueg, Sam}, + title = {Fault-tolerant broadcasts and related problems}, + booktitle = {Distributed systems (2nd Ed.)}, + editor = {Mullender, Sape}, + year = {1993}, + isbn = {0-201-62427-3}, + pages = {97--145}, + numpages = {49} +} + + +@inproceedings{HS06:opodis, + author = {Heinrich Moser and Ulrich Schmid}, + title = {Optimal Clock Synchronization Revisited: Upper and + Lower Bounds in Real-Time Systems}, + booktitle = { Principles of Distributed Systems}, + pages = {94--109}, + year = {2006}, + volume = {4305}, + series = {Lecture Notes in Computer Science}, + publisher = SPR +} + +@techreport{HS06:tr, + author = {Martin Hutle and Andr{\'e} Schiper}, + title = { Communication predicates: A high-level abstraction + for coping with transient and dynamic faults}, + institution = {EPFL}, + number = { LSR-REPORT-2006-006 }, + year = {2006} +} + +@inproceedings{HS07:dsn, + author = {Martin Hutle and Andr{\'e} Schiper}, + title = { Communication predicates: A high-level abstraction + for coping with transient and dynamic faults}, + year = 2007, + booktitle = DSN07, + publisher = IEEE, + location = {Edinburgh,UK}, + pages = {92--10}, + month = jun +} + +@article{Her91:tpls, + author = {Maurice Herlihy}, + title = {Wait-free synchronization}, + journal = TPLS, + volume = {13}, + number = {1}, + year = {1991}, + pages = {124--149}, + publisher = ACM, + address = {New York, NY, USA}, +} + +@article{Kot09:zyzzyva, + author = {Kotla, Ramakrishna and Alvisi, Lorenzo and Dahlin, Mike and Clement, Allen and Wong, Edmund}, + title = {Zyzzyva: Speculative Byzantine fault tolerance}, + journal = {ACM Trans. Comput. Syst.}, + issue_date = {December 2009}, + volume = {27}, + number = {4}, + month = jan, + year = {2010}, + issn = {0734-2071}, + pages = {7:1--7:39}, + articleno = {7}, + numpages = {39}, + publisher = {ACM}, + address = {New York, NY, USA}, + keywords = {Byzantine fault tolerance, output commit, replication, speculative execution}, +} + + +@inproceedings{KMMS97:opodis, + author = "Kim Potter Kihlstrom and Louise E. Moser and + P. M. Melliar-Smith", + title = "Solving Consensus in a Byzantine Environment Using + an Unreliable Fault Detector", + booktitle = "Proceedings of the International Conference on + Principles of Distributed Systems (OPODIS)", + year = 1997, + month = dec, + address = "Chantilly, France", + pages = "61--75" +} + +@inproceedings{KS06:podc, + author = {Idit Keidar and Alexander Shraer}, + title = {Timeliness, failure-detectors, and consensus + performance}, + booktitle = PODC06, + year = {2006}, + pages = {169--178}, + location = {Denver, Colorado, USA}, + publisher = {ACM Press}, + address = {New York, NY, USA}, +} + +@InProceedings{LFA99:disc, + author = {Mikel Larrea and Antonio Fern\'andez and Sergio + Ar\'evalo}, + title = {Efficient algorithms to implement unreliable failure + detectors in partially synchronous systems}, + year = 1999, + month = sep, + pages = {34-48}, + series = "LNCS 1693", + booktitle = DISC99, + publisher = SPR, + address = {Bratislava, Slovaquia}, +} + +@article{LL84:ic, + author = "Jennifer Lundelius and Nancy A. Lynch", + title = "An Upper and Lower Bound for Clock Synchronization", + journal = IC, + volume = 62, + number = {2/3}, + year = 1984, + pages = {190--204} +} + +@techreport{LLS03:tr, + title = {How to Implement a Timer-free Perfect Failure + Detector in Partially Synchronous Systems}, + author = {Le Lann, G\'erard and Schmid, Ulrich}, + institution = TUAuto, + number = "183/1-127", + month = jan, + year = 2003 +} + +@article{LSP82:tpls, + author = {Leslie Lamport and Robert Shostak and Marshall + Pease}, + title = {The {B}yzantine Generals Problem}, + journal = {ACM Trans. Program. Lang. Syst.}, + year = {1982}, +} + +@inproceedings{Lam01:podc, + author = {Butler Lampson}, + title = {The ABCD's of Paxos}, + booktitle = {PODC}, + year = {2001}, + +} + +@inproceedings{Lam03:fddc, + author = {Leslie Lamport}, + title = {Lower Bounds for Asynchronous Consensus}, + booktitle = {Future Directions in Distributed Computing}, + pages = {22--23}, + year = {2003}, + editor = {Andr{\'e} Schiper and Alex A. Shvartsman and Hakim + Weatherspoon and Ben Y. Zhao}, + number = {2584}, + series = {Lecture Notes in Computer Science}, + publisher = SPR +} + +@techreport{Lam04:tr, + author = {Leslie Lamport}, + title = {Lower Bounds for Asynchronous Consensus}, + institution = {Microsoft Research}, + year = {2004}, + number = {MSR-TR-2004-72} +} + +@techreport{Lam05:tr, + author = {Leslie Lamport}, + title = {Fast Paxos}, + institution = {Microsoft Research}, + year = {2005}, + number = {MSR-TR-2005-12} +} + +@techreport{Lam05:tr-33, + author = {Leslie Lamport}, + title = {Generalized Consensus and Paxos}, + institution = {Microsoft Research}, + year = {2005}, + number = {MSR-TR-2005-33} +} + +@Misc{Lam06:slides, + author = {Leslie Lamport}, + title = {Byzantine Paxos}, + howpublished = {Unpublished slides}, + year = {2006} +} + +@Article{Lam86:dc, + author = {Lesli Lamport}, + title = {On Interprocess Communication--Part I: Basic + Formalism, Part II: Algorithms}, + journal = DC, + year = 1986, + volume = 1, + number = 2, + pages = {77--101} +} + +@Article {Lam98:tcs, + author = {Leslie Lamport}, + title = {The part-time parliament}, + journal = ACMTCS, + year = 1998, + volume = 16, + number = 2, + month = may, + pages = {133-169}, +} + +@book{Lyn96:book, + author = {Nancy Lynch}, + title = {Distributed Algorithms}, + publisher = {Morgan Kaufman}, + year = {1996}, +} + +@inproceedings{MA05:dsn, + author = {Martin, J.-P. and Alvisi, L. }, + title = {Fast Byzantine consensus}, + booktitle = DSN05, + pages = {402--411}, + year = {2005}, + month = jun, + organization = {IEEE}, +} + +@article{MA06:tdsc, + author = {Martin, J.-P. and Alvisi, L. }, + title = {Fast {B}yzantine Consensus}, + journal = {TDSC}, + year = {2006}, +} + +@InProceedings{MOZ05:dsn, + author = {Dahlia Malkhi and Florin Oprea and Lidong Zhou}, + title = {{$\Omega$} Meets Paxos: Leader Election and + Stability without Eventual Timely Links}, + booktitle = DSN05, + year = {2005} +} + +@inproceedings{MR00:podc, + author = "Achour Most{\'e}faoui and Michel Raynal", + title = "k-set agreement with limited accuracy failure + detectors", + booktitle = PODC00, + year = 2000, + pages = {143--152}, + location = {Portland, Oregon, United States}, + publisher = ACM +} + +@article{MR01:ppl, + author = "Achour Most{\'e}faoui and Michel Raynal", + title = "Leader-Based Consensus", + journal = PPL, + volume = 11, + number = 1, + year = 2001, + pages = {95--107} +} + +@techreport{OGS97:tr, + author = "Rui Oliveira and Rachid Guerraoui and {Andr\'e} + Schiper", + title = "Consensus in the crash-recover model", + number = "TR-97/239", + year = "1997" +} + +@article{PSL80:jacm, + author = {M. Pease and R. Shostak and L. Lamport}, + title = {Reaching Agreement in the Presence of Faults}, + journal = JACM, + volume = {27}, + number = {2}, + year = {1980}, + pages = {228--234}, + publisher = ACM, + address = ACMADDR, +} + +@article{ST87:jacm, + author = "T. K. Srikanth and Sam Toueg", + title = "Optimal clock synchronization", + journal = JACM, + volume = 34, + number = 3, + year = 1987, + pages = "626--645" +} + +@article{ST87:dc, + author = {T. K. Srikanth and Sam Toueg,}, + title = {Simulating authenticated broadcasts to derive simple fault-tolerant algorithms}, + journal = DC, + volume = {2}, + number = {2}, + year = {1987}, + pages = {80-94} +} + + +@inproceedings{SW89:stacs, + author = {Santoro, Nicola and Widmayer, Peter}, + title = {Time is not a healer}, + booktitle = {Proc.\ 6th Annual Symposium on Theor.\ Aspects of + Computer Science (STACS'89)}, + publisher = "Springer-Verlag", + series = {LNCS}, + volume = "349", + address = "Paderborn, Germany", + pages = "304-313", + year = "1989", + month = feb, +} + +@inproceedings{SW90:sigal, + author = {Nicola Santoro and Peter Widmayer}, + title = {Distributed Function Evaluation in the Presence of + Transmission Faults.}, + booktitle = {SIGAL International Symposium on Algorithms}, + year = {1990}, + pages = {358-367} +} + +@inproceedings{SWR02:icdcs, + author = {Ulrich Schmid and Bettina Weiss and John Rushby}, + title = {Formally Verified Byzantine Agreement in Presence of + Link Faults}, + booktitle = "22nd International Conference on Distributed + Computing Systems (ICDCS'02)", + year = 2002, + month = jul # " 2-5, ", + pages = "608--616", + address = "Vienna, Austria", +} + +@incollection{Sch93a:mullender, + Author = {F. B. Schneider}, + Title = {What Good are Models and What Models are Good}, + BookTitle = {Distributed Systems}, + Year = {1993}, + Editor = {Sape Mullender}, + Publisher = {ACM Press}, + Pages = {169-197}, +} + +@article{VL96:ic, + author = {George Varghese and Nancy A. Lynch}, + title = {A Tradeoff Between Safety and Liveness for + Randomized Coordinated Attack.}, + journal = {Inf. Comput.}, + volume = {128}, + number = {1}, + year = 1996, + pages = {57--71} +} + +@inproceedings{WGWB07:dsn, + title = {Synchronous Consensus with Mortal Byzantines}, + author = {Josef Widder and Günther Gridling and Bettina Weiss + and Jean-Paul Blanquart}, + year = {2007}, + booktitle = DSN07, + publisher = IEEE +} + +@inproceedings{Wid03:disc, + author = {Josef Widder}, + title = {Booting clock Synchronization in Partially + Synchronous Systems}, + booktitle = DISC03, + year = {2003}, + pages = {121--135} +} + +@techreport{Zie04:tr, + author = {Piotr Zieli{\'n}ski}, + title = {Paxos at War}, + institution = {University of Cambridge}, + year = {2004}, + number = {UCAM-CL-TR-593}, +} + +@article{Lam78:cacm, + author = {Leslie Lamport}, + title = {Time, clocks, and the ordering of events in a + distributed system}, + journal = {Commun. ACM}, + year = {1978}, +} + +@Article{Gue06:cj, + author = {Guerraoui, R. and Raynal, M.}, + journal = {The {C}omputer {J}ournal}, + title = {The {A}lpha of {I}ndulgent {C}onsensus}, + year = {2006} +} + +@Article{Gue03:toc, + affiliation = {EPFL}, + author = {Guerraoui, Rachid and Raynal, Michel}, + journal = {{IEEE} {T}rans. on {C}omputers}, + title = {The {I}nformation {S}tructure of {I}ndulgent {C}onsensus}, + year = {2004}, +} + +@techreport{Cas00, + author = {Castro, Miguel}, + title = {Practical {B}yzantine Fault-Tolerance. {PhD} thesis}, + institution = {MIT}, + year = 2000, +} + +@inproceedings{SongRSD08:icdcn, + author = {Yee Jiun Song and + Robbert van Renesse and + Fred B. Schneider and + Danny Dolev}, + title = {The Building Blocks of Consensus}, + booktitle = {ICDCN}, + year = {2008}, +} + + +@inproceedings{BS09:icdcn, + author = {Borran, Fatemeh and Schiper, Andr{\'e}}, + + title = {A {L}eader-free {B}yzantine {C}onsensus {A}lgorithm}, + note = {To appear in ICDCN, 2010}, +} + + +@inproceedings{MHS09:opodis, + author = {Zarko Milosevic and Martin Hutle and Andr{\'e} + Schiper}, + title = {Unifying {B}yzantine Consensus Algorithms with {W}eak + {I}nteractive {C}onsistency}, + note = {To appear in OPODIS 2009}, +} + +@inproceedings{MRR:dsn02, + author = {Most\'{e}faoui, Achour and Rajsbaum, Sergio and Raynal, Michel}, + title = {A Versatile and Modular Consensus Protocol}, + booktitle = {DSN}, + year = {2002}, + } + +@article{MR98:dc, + author = {Dahlia Malkhi and + Michael K. Reiter}, + title = {Byzantine Quorum Systems}, + journal = {Distributed Computing}, + year = {1998}, +} + +@inproceedings{Rei:ccs94, + author = {Reiter, Michael K.}, + title = {Secure agreement protocols: reliable and atomic group multicast in rampart}, + booktitle = {CCS}, + year = {1994}, + pages = {68--80}, + numpages = {13} +} + + +@techreport{RMS09-tr, + author = {Olivier R\"utti and Zarko Milosevic and Andr\'e Schiper}, + title = {{G}eneric construction of consensus algorithm for benign and {B}yzantine faults}, + institution = {EPFL-IC}, + number = {LSR-REPORT-2009-005}, + year = 2009, +} + +@inproceedings{Li:srds07, + author = {Li, Harry C. and Clement, Allen and Aiyer, Amitanand S. and Alvisi, Lorenzo}, + title = {The Paxos Register}, + booktitle = {SRDS}, + year = {2007}, + } + + @article{Amir11:prime, + author = {Amir, Yair and Coan, Brian and Kirsch, Jonathan and Lane, John}, + title = {Prime: Byzantine Replication under Attack}, + journal = {IEEE Trans. Dependable Secur. Comput.}, + issue_date = {July 2011}, + volume = {8}, + number = {4}, + month = jul, + year = {2011}, + issn = {1545-5971}, + pages = {564--577}, + numpages = {14}, + publisher = {IEEE Computer Society Press}, + address = {Los Alamitos, CA, USA}, + keywords = {Performance under attack, Byzantine fault tolerance, replicated state machines, distributed systems.}, +} + +@inproceedings{Mao08:mencius, + author = {Mao, Yanhua and Junqueira, Flavio P. and Marzullo, Keith}, + title = {Mencius: building efficient replicated state machines for WANs}, + booktitle = {OSDI}, + year = {2008}, + pages = {369--384}, + numpages = {16} +} + +@article{Sch90:survey, + author = {Schneider, Fred B.}, + title = {Implementing fault-tolerant services using the state machine approach: a tutorial}, + journal = {ACM Comput. Surv.}, + volume = {22}, + number = {4}, + month = dec, + year = {1990} +} + + +@techreport{HT94:TR, + author = {Hadzilacos, Vassos and Toueg, Sam}, + title = {A Modular Approach to Fault-Tolerant Broadcasts and Related Problems}, + year = {1994}, + source = {http://www.ncstrl.org:8900/ncstrl/servlet/search?formname=detail\&id=oai%3Ancstrlh%3Acornellcs%3ACORNELLCS%3ATR94-1425}, + publisher = {Cornell University}, + address = {Ithaca, NY, USA}, +} + +@inproceedings{Ver09:spinning, + author = {Veronese, Giuliana Santos and Correia, Miguel and Bessani, Alysson Neves and Lung, Lau Cheuk}, + title = {Spin One's Wheels? Byzantine Fault Tolerance with a Spinning Primary}, + booktitle = {SRDS}, + year = {2009}, + numpages = {10} +} + +@inproceedings{Cle09:aardvark, + author = {Clement, Allen and Wong, Edmund and Alvisi, Lorenzo and Dahlin, Mike and Marchetti, Mirco}, + title = {Making Byzantine fault tolerant systems tolerate Byzantine faults}, + booktitle = {NSDI}, + year = {2009}, + pages = {153--168}, + numpages = {16} +} + +@inproceedings{Aiyer05:barB, + author = {Aiyer, Amitanand S. and Alvisi, Lorenzo and Clement, Allen and Dahlin, Mike and Martin, Jean-Philippe and Porth, Carl}, + title = {BAR fault tolerance for cooperative services}, + booktitle = {SOSP}, + year = {2005}, + pages = {45--58}, + numpages = {14} +} + +@inproceedings{Cach01:crypto, + author = {Cachin, Christian and Kursawe, Klaus and Petzold, Frank and Shoup, Victor}, + title = {Secure and Efficient Asynchronous Broadcast Protocols}, + booktitle = {CRYPTO}, + year = {2001}, + pages = {524--541}, + numpages = {18} +} + +@article{Moniz11:ritas, + author = {Moniz, Henrique and Neves, Nuno Ferreria and Correia, Miguel and Verissimo, Paulo}, + title = {RITAS: Services for Randomized Intrusion Tolerance}, + journal = {IEEE Trans. Dependable Secur. Comput.}, + volume = {8}, + number = {1}, + month = jan, + year = {2011}, + pages = {122--136}, + numpages = {15} +} + +@inproceedings{MHS11:jabc, + author = {Milosevic, Zarko and Hutle, Martin and Schiper, Andre}, + title = {On the Reduction of Atomic Broadcast to Consensus with Byzantine Faults}, + booktitle = {SRDS}, + year = {2011}, + pages = {235--244}, + numpages = {10} +} + +@incollection{DHSZ03, + author={Driscoll, Kevin and Hall, Brendan and Sivencrona, Håkan and Zumsteg, Phil}, + title={Byzantine Fault Tolerance, from Theory to Reality}, + year={2003}, + booktitle={Computer Safety, Reliability, and Security}, + volume={2788}, + pages={235--248} +} + +@inproceedings{RMES:dsn07, + author = {Olivier R{\"u}tti and + Sergio Mena and + Richard Ekwall and + Andr{\'e} Schiper}, + title = {On the Cost of Modularity in Atomic Broadcast}, + booktitle = {DSN}, + year = {2007}, + pages = {635-644} +} + +@article{Ben:jc92, + author = {Charles H. Bennett and + Fran\c{c}ois Bessette and + Gilles Brassard and + Louis Salvail and + John A. Smolin}, + title = {Experimental Quantum Cryptography}, + journal = {J. Cryptology}, + volume = {5}, + number = {1}, + year = {1992}, + pages = {3-28} +} + +@inproceedings{Aiyer:disc08, + author = {Aiyer, Amitanand S. and Alvisi, Lorenzo and Bazzi, Rida A. and Clement, Allen}, + title = {Matrix Signatures: From MACs to Digital Signatures in Distributed Systems}, + booktitle = {DISC}, + year = {2008}, + pages = {16--31}, + numpages = {16} +} + +@inproceedings{Biel13:dsn, + author = {Biely, Martin and Delgado, Pamela and Milosevic, Zarko and Schiper, Andr{\'e}}, + title = {Distal: A Framework for Implementing Fault-tolerant Distributed Algorithms}, + note = {To appear in DSN, 2013}, + year = 2013 +} + +@inproceedings{BS10:icdcn, + author = {Borran, Fatemeh and Schiper, Andr{\'e}}, + title = {A leader-free Byzantine consensus algorithm}, + booktitle = {ICDCN}, + year = {2010}, + pages = {67--78}, + numpages = {12} +} + +@article{Cor06:cj, + author = {Correia, Miguel and Neves, Nuno Ferreira and Ver\'{\i}ssimo, Paulo}, + title = {From Consensus to Atomic Broadcast: Time-Free Byzantine-Resistant Protocols without Signatures}, + journal = {Comput. J.}, + volume = {49}, + number = {1}, + year = {2006}, + pages = {82--96}, + numpages = {15} +} + +@inproceedings{RMS10:dsn, + author = {Olivier R{\"u}tti and + Zarko Milosevic and + Andr{\'e} Schiper}, + title = {Generic construction of consensus algorithms for benign + and Byzantine faults}, + booktitle = {DSN}, + year = {2010}, + pages = {343-352} +} + + + +@inproceedings{HKJR:usenix10, + author = {Hunt, Patrick and Konar, Mahadev and Junqueira, Flavio P. and Reed, Benjamin}, + title = {ZooKeeper: wait-free coordination for internet-scale systems}, + OPTbooktitle = {Proceedings of the 2010 USENIX conference on USENIX annual technical conference}, + booktitle = {USENIXATC}, + year = {2010}, + OPTlocation = {Boston, MA}, + pages = {11}, + numpages = {1}, + OPTurl = {http://dl.acm.org/citation.cfm?id=1855840.1855851}, + acmid = {1855851}, + OPTpublisher = {USENIX Association}, + OPTaddress = {Berkeley, CA, USA}, +} + +@inproceedings{Bur:osdi06, + author = {Burrows, Mike}, + title = {The Chubby lock service for loosely-coupled distributed systems}, + booktitle = {OSDI}, + year = {2006}, + pages = {335--350}, + numpages = {16}, +} + +@INPROCEEDINGS{Mao09:hotdep, + author = {Yanhua Mao and Flavio P. Junqueira and Keith Marzullo}, + title = {Towards low latency state machine replication for uncivil wide-area networks}, + booktitle = {HotDep}, + year = {2009} +} + +@inproceedings{Chun07:a2m, + author = {Chun, Byung-Gon and Maniatis, Petros and Shenker, Scott and Kubiatowicz, John}, + title = {Attested append-only memory: making adversaries stick to their word}, + booktitle = {SOSP}, + year = {2007}, + pages = {189--204}, + numpages = {16} +} + +@TECHREPORT{MBS:epfltr, + author = {Zarko Milosevic and Martin Biely and Andr\'e Schiper}, + title = {Bounded {D}elay in {B}yzantine {T}olerant {S}tate {M}achine {R}eplication}, + year = 2013, + month = april, + institution = {EPFL}, + number = {185962}, +} + +@book{BH09:datacenter, + author = {Barroso, Luiz Andre and Hoelzle, Urs}, + title = {The Datacenter as a Computer: An Introduction to the Design of Warehouse-Scale Machines}, + year = {2009}, + isbn = {159829556X, 9781598295566}, + edition = {1st}, + publisher = {Morgan and Claypool Publishers}, +} + +@inproceedings{Kir11:csiirw, + author = {Kirsch, Jonathan and Goose, Stuart and Amir, Yair and Skare, Paul}, + title = {Toward survivable SCADA}, + booktitle = {CSIIRW}, + year = {2011}, + pages = {21:1--21:1}, + articleno = {21}, + numpages = {1} +} + +@inproceedings{Ongaro14:raft, + author = {Ongaro, Diego and Ousterhout, John}, + title = {In Search of an Understandable Consensus Algorithm}, + booktitle = {Proceedings of the 2014 USENIX Conference on USENIX Annual Technical Conference}, + series = {USENIX ATC'14}, + year = {2014}, + isbn = {978-1-931971-10-2}, + location = {Philadelphia, PA}, + pages = {305--320}, + numpages = {16}, + url = {http://dl.acm.org/citation.cfm?id=2643634.2643666}, + acmid = {2643666}, + publisher = {USENIX Association}, + address = {Berkeley, CA, USA}, +} + +@article{GLR17:red-belly-bc, + author = {Tyler Crain and + Vincent Gramoli and + Mikel Larrea and + Michel Raynal}, + title = {Leader/Randomization/Signature-free Byzantine Consensus for Consortium + Blockchains}, + journal = {CoRR}, + volume = {abs/1702.03068}, + year = {2017}, + url = {http://arxiv.org/abs/1702.03068}, + archivePrefix = {arXiv}, + eprint = {1702.03068}, + timestamp = {Wed, 07 Jun 2017 14:41:08 +0200}, + biburl = {http://dblp.org/rec/bib/journals/corr/CrainGLR17}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + + +@misc{Nak2012:bitcoin, + added-at = {2014-04-17T08:33:06.000+0200}, + author = {Nakamoto, Satoshi}, + biburl = {https://www.bibsonomy.org/bibtex/23db66df0fc9fa2b5033f096a901f1c36/ngnn}, + interhash = {423c2cdff70ba0cd0bca55ebb164d770}, + intrahash = {3db66df0fc9fa2b5033f096a901f1c36}, + keywords = {imported}, + timestamp = {2014-04-17T08:33:06.000+0200}, + title = {Bitcoin: A peer-to-peer electronic cash system}, + url = {http://www.bitcoin.org/bitcoin.pdf}, + year = 2009 +} + +@misc{But2014:ethereum, + author = {Vitalik Buterin}, + title = {Ethereum: A next-generation smart contract and decentralized application platform}, + year = {2014}, + howpublished = {\url{https://github.com/ethereum/wiki/wiki/White-Paper}}, + note = {Accessed: 2018-07-11}, + url = {https://github.com/ethereum/wiki/wiki/White-Paper}, +} + +@inproceedings{Dem1987:gossip, + author = {Demers, Alan and Greene, Dan and Hauser, Carl and Irish, Wes and Larson, John and Shenker, Scott and Sturgis, Howard and Swinehart, Dan and Terry, Doug}, + title = {Epidemic Algorithms for Replicated Database Maintenance}, + booktitle = {Proceedings of the Sixth Annual ACM Symposium on Principles of Distributed Computing}, + series = {PODC '87}, + year = {1987}, + isbn = {0-89791-239-X}, + location = {Vancouver, British Columbia, Canada}, + pages = {1--12}, + numpages = {12}, + url = {http://doi.acm.org/10.1145/41840.41841}, + doi = {10.1145/41840.41841}, + acmid = {41841}, + publisher = {ACM}, + address = {New York, NY, USA}, +} + +@article{Gue2018:sbft, + author = {Guy Golan{-}Gueta and + Ittai Abraham and + Shelly Grossman and + Dahlia Malkhi and + Benny Pinkas and + Michael K. Reiter and + Dragos{-}Adrian Seredinschi and + Orr Tamir and + Alin Tomescu}, + title = {{SBFT:} a Scalable Decentralized Trust Infrastructure for Blockchains}, + journal = {CoRR}, + volume = {abs/1804.01626}, + year = {2018}, + url = {http://arxiv.org/abs/1804.01626}, + archivePrefix = {arXiv}, + eprint = {1804.01626}, + timestamp = {Tue, 01 May 2018 19:46:29 +0200}, + biburl = {https://dblp.org/rec/bib/journals/corr/abs-1804-01626}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} + +@inproceedings{BLS2001:crypto, + author = {Boneh, Dan and Lynn, Ben and Shacham, Hovav}, + title = {Short Signatures from the Weil Pairing}, + booktitle = {Proceedings of the 7th International Conference on the Theory and Application of Cryptology and Information Security: Advances in Cryptology}, + series = {ASIACRYPT '01}, + year = {2001}, + isbn = {3-540-42987-5}, + pages = {514--532}, + numpages = {19}, + url = {http://dl.acm.org/citation.cfm?id=647097.717005}, + acmid = {717005}, + publisher = {Springer-Verlag}, + address = {Berlin, Heidelberg}, +} + + diff --git a/spec/consensus/consensus-paper/paper.tex b/spec/consensus/consensus-paper/paper.tex new file mode 100644 index 00000000000..22f8b405fc2 --- /dev/null +++ b/spec/consensus/consensus-paper/paper.tex @@ -0,0 +1,153 @@ +%\documentclass[conference]{IEEEtran} +\documentclass[conference,onecolumn,draft,a4paper]{IEEEtran} +% Add the compsoc option for Computer Society conferences. +% +% If IEEEtran.cls has not been installed into the LaTeX system files, +% manually specify the path to it like: +% \documentclass[conference]{../sty/IEEEtran} + + + +% *** GRAPHICS RELATED PACKAGES *** +% +\ifCLASSINFOpdf +\else +\fi + +% correct bad hyphenation here +\hyphenation{op-tical net-works semi-conduc-tor} + +%\usepackage[caption=false,font=footnotesize]{subfig} +\usepackage{tikz} +\usetikzlibrary{decorations,shapes,backgrounds,calc} +\tikzstyle{msg}=[->,black,>=latex] +\tikzstyle{rubber}=[|<->|] +\tikzstyle{announce}=[draw=blue,fill=blue,shape=diamond,right,minimum + height=2mm,minimum width=1.6667mm,inner sep=0pt] +\tikzstyle{decide}=[draw=red,fill=red,shape=isosceles triangle,right,minimum + height=2mm,minimum width=1.6667mm,inner sep=0pt,shape border rotate=90] +\tikzstyle{cast}=[draw=green!50!black,fill=green!50!black,shape=circle,left,minimum + height=2mm,minimum width=1.6667mm,inner sep=0pt] + + +\usepackage{multirow} +\usepackage{graphicx} +\usepackage{epstopdf} +\usepackage{amssymb} +\usepackage{rounddiag} +\graphicspath{{../}} + +\usepackage{technote} +\usepackage{homodel} +\usepackage{enumerate} +%%\usepackage{ulem}\normalem + +% to center caption +\usepackage{caption} + +\newcommand{\textstretch}{1.4} +\newcommand{\algostretch}{1} +\newcommand{\eqnstretch}{0.5} + +\newconstruct{\FOREACH}{\textbf{for each}}{\textbf{do}}{\ENDFOREACH}{} + +%\newconstruct{\ON}{\textbf{on}}{\textbf{do}}{\ENDON}{\textbf{end on}} +\newcommand\With{\textbf{while}} +\newcommand\From{\textbf{from}} +\newcommand\Broadcast{\textbf{broadcast}} +\newcommand\PBroadcast{send} +\newcommand\UpCall{\textbf{UpCall}} +\newcommand\DownCall{\textbf{DownCall}} +\newcommand \Call{\textbf{Call}} +\newident{noop} +\newconstruct{\UPON}{\textbf{upon}}{\textbf{do}}{\ENDUPON}{} + + + +\newcommand{\abcast}{\mathsf{to\mbox{\sf-}broadcast}} +\newcommand{\adeliver}{\mathsf{to\mbox{\sf-}deliver}} + +\newcommand{\ABCAgreement}{\emph{TO-Agreement}} +\newcommand{\ABCIntegrity}{\emph{TO-Integrity}} +\newcommand{\ABCValidity}{\emph{TO-Validity}} +\newcommand{\ABCTotalOrder}{\emph{TO-Order}} +\newcommand{\ABCBoundedDelivery}{\emph{TO-Bounded Delivery}} + + +\newcommand{\tabc}{\mathit{atab\mbox{\sf-}cast}} +\newcommand{\anno}{\mathit{atab\mbox{\sf-}announce}} +\newcommand{\abort}{\mathit{atab\mbox{\sf-}abort}} +\newcommand{\tadel}{\mathit{atab\mbox{\sf-}deliver}} + +\newcommand{\ATABAgreement}{\emph{ATAB-Agreement}} +\newcommand{\ATABAbort}{\emph{ATAB-Abort}} +\newcommand{\ATABIntegrity}{\emph{ATAB-Integrity}} +\newcommand{\ATABValidity}{\emph{ATAB-Validity}} +\newcommand{\ATABAnnounce}{\emph{ATAB-Announcement}} +\newcommand{\ATABTermination}{\emph{ATAB-Termination}} +%\newcommand{\ATABFastAnnounce}{\emph{ATAB-Fast-Announcement}} + +%% Command for observations. +\newtheorem{observation}{Observation} + + +%% HO ALGORITHM DEFINITIONS +\newconstruct{\FUNCTION}{\textbf{Function}}{\textbf{:}}{\ENDFUNCTION}{} + +%% Uncomment the following four lines to remove remarks and visible traces of +%% modifications in the document +%%\renewcommand{\sout}[1]{\relaxx} +%%\renewcommand{\uline}[1]{#1} +%% \renewcommand{\uwave}[1]{#1} + \renewcommand{\note}[2][default]{\relax} + + +%% The following commands can be used to generate TR or Conference version of the paper +\newcommand{\tr}[1]{} +\renewcommand{\tr}[1]{#1} +\newcommand{\onlypaper}[1]{#1} +%\renewcommand{\onlypaper}[1]{} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%\pagestyle{plain} +%\pagestyle{empty} + +%% IEEE tweaks +%\setlength{\IEEEilabelindent}{.5\parindent} +%\setlength{\IEEEiednormlabelsep}{.5\parindent} + +\begin{document} +% +% paper title +% can use linebreaks \\ within to get better formatting as desired +\title{The latest gossip on BFT consensus\vspace{-0.7\baselineskip}} + + + +\author{\IEEEauthorblockN{\large Ethan Buchman, Jae Kwon and Zarko Milosevic\\} + \IEEEauthorblockN{\large Tendermint}\\ + %\\\vspace{-0.5\baselineskip} + \IEEEauthorblockN{September 24, 2018} +} + +% make the title area +\maketitle +\vspace*{0.5em} + +\begin{abstract} +This paper presents Tendermint, a new protocol for ordering events in a distributed network under adversarial conditions. More commonly known as Byzantine Fault Tolerant (BFT) consensus or atomic broadcast, the problem has attracted significant attention in recent years due to the widespread success of blockchain-based digital currencies, such as Bitcoin and Ethereum, which successfully solved the problem in a public setting without a central authority. Tendermint modernizes classic academic work on the subject and simplifies the design of the BFT algorithm by relying on a peer-to-peer gossip protocol among nodes. +\end{abstract} + +%\noindent \textbf{Keywords:} Blockchain, Byzantine Fault Tolerance, State Machine %Replication + +\input{intro} +\input{definitions} +\input{consensus} +\input{proof} +\input{conclusion} + +\bibliographystyle{IEEEtran} +\bibliography{lit} + +%\appendix + +\end{document} diff --git a/spec/consensus/consensus-paper/proof.tex b/spec/consensus/consensus-paper/proof.tex new file mode 100644 index 00000000000..1c84d9b11e1 --- /dev/null +++ b/spec/consensus/consensus-paper/proof.tex @@ -0,0 +1,280 @@ +\section{Proof of Tendermint consensus algorithm} \label{sec:proof} + +\begin{lemma} \label{lemma:majority-intersection} For all $f\geq 0$, any two +sets of processes with voting power at least equal to $2f+1$ have at least one +correct process in common. \end{lemma} + +\begin{proof} As the total voting power is equal to $n=3f+1$, we have $2(2f+1) + = n+f+1$. This means that the intersection of two sets with the voting + power equal to $2f+1$ contains at least $f+1$ voting power in common, \ie, + at least one correct process (as the total voting power of faulty processes + is $f$). The result follows directly from this. \end{proof} + +\begin{lemma} \label{lemma:locked-decision_value-prevote-v} If $f+1$ correct +processes lock value $v$ in round $r_0$ ($lockedValue = v$ and $lockedRound = +r_0$), then in all rounds $r > r_0$, they send $\Prevote$ for $id(v)$ or +$\nil$. \end{lemma} + +\begin{proof} We prove the result by induction on $r$. + +\emph{Base step $r = r_0 + 1:$} Let's denote with $C$ the set of correct +processes with voting power equal to $f+1$. By the rules at +line~\ref{line:tab:recvProposal} and line~\ref{line:tab:acceptProposal}, the +processes from the set $C$ can't accept $\Proposal$ for any value different +from $v$ in round $r$, and therefore can't send a $\li{\Prevote,height_p, +r,id(v')}$ message, if $v' \neq v$. Therefore, the Lemma holds for the base +step. + +\emph{Induction step from $r_1$ to $r_1+1$:} We assume that no process from the +set $C$ has sent $\Prevote$ for values different than $id(v)$ or $\nil$ until +round $r_1 + 1$. We now prove that the Lemma also holds for round $r_1 + 1$. As +processes from the set $C$ send $\Prevote$ for $id(v)$ or $\nil$ in rounds $r_0 +\le r \le r_1$, by Lemma~\ref{lemma:majority-intersection} there is no value +$v' \neq v$ for which it is possible to receive $2f+1$ $\Prevote$ messages in +those rounds (i). Therefore, we have for all processes from the set $C$, +$lockedValue = v$ and $lockedRound \ge r_0$. Let's assume by a contradiction +that a process $q$ from the set $C$ sends $\Prevote$ in round $r_1 + 1$ for +value $id(v')$, where $v' \neq v$. This is possible only by +line~\ref{line:tab:prevote-higher-proposal}. Note that this implies that $q$ +received $2f+1$ $\li{\Prevote,h_q, r,id(v')}$ messages, where $r > r_0$ and $r +< r_1 +1$ (see line~\ref{line:tab:cond-prevote-higher-proposal}). A +contradiction with (i) and Lemma~\ref{lemma:majority-intersection}. +\end{proof} + +\begin{lemma} \label{lemma:agreement} Algorithm~\ref{alg:tendermint} satisfies +Agreement. \end{lemma} + +\begin{proof} Let round $r_0$ be the first round of height $h$ such that some + correct process $p$ decides $v$. We now prove that if some correct process + $q$ decides $v'$ in some round $r \ge r_0$, then $v = v'$. + +In case $r = r_0$, $q$ has received at least $2f+1$ +$\li{\Precommit,h_p,r_0,id(v')}$ messages at line~\ref{line:tab:onDecideRule}, +while $p$ has received at least $2f+1$ $\li{\Precommit,h_p,r_0,id(v)}$ +messages. By Lemma~\ref{lemma:majority-intersection} two sets of messages of +voting power $2f+1$ intersect in at least one correct process. As a correct +process sends a single $\Precommit$ message in a round, then $v=v'$. + +We prove the case $r > r_0$ by contradiction. By the +rule~\ref{line:tab:onDecideRule}, $p$ has received at least $2f+1$ voting-power +equivalent of $\li{\Precommit,h_p,r_0,id(v)}$ messages, i.e., at least $f+1$ +voting-power equivalent correct processes have locked value $v$ in round $r_0$ and have +sent those messages (i). Let denote this set of messages with $C$. On the +other side, $q$ has received at least $2f+1$ voting power equivalent of +$\li{\Precommit,h_q, r,id(v')}$ messages. As the voting power of all faulty +processes is at most $f$, some correct process $c$ has sent one of those +messages. By the rule at line~\ref{line:tab:recvPrevote}, $c$ has locked value +$v'$ in round $r$ before sending $\li{\Precommit,h_q, r,id(v')}$. Therefore $c$ +has received $2f+1$ $\Prevote$ messages for $id(v')$ in round $r > r_0$ (see +line~\ref{line:tab:recvPrevote}). By Lemma~\ref{lemma:majority-intersection}, a +process from the set $C$ has sent $\Prevote$ message for $id(v')$ in round $r$. +A contradiction with (i) and Lemma~\ref{lemma:locked-decision_value-prevote-v}. +\end{proof} + +\begin{lemma} \label{lemma:agreement} Algorithm~\ref{alg:tendermint} satisfies +Validity. \end{lemma} + +\begin{proof} Trivially follows from the rule at line +\ref{line:tab:validDecisionValue} which ensures that only valid values can be +decided. \end{proof} + +\begin{lemma} \label{lemma:round-synchronisation} If we assume that: +\begin{enumerate} + \item a correct process $p$ is the first correct process to + enter a round $r>0$ at time $t > GST$ (for every correct process + $c$, $round_c \le r$ at time $t$) + \item the proposer of round $r$ is + a correct process $q$ + \item for every correct process $c$, + $lockedRound_c \le validRound_q$ at time $t$ + \item $\timeoutPropose(r) + > 2\Delta + \timeoutPrecommit(r-1)$, $\timeoutPrevote(r) > 2\Delta$ and + $\timeoutPrecommit(r) > 2\Delta$, +\end{enumerate} +then all correct processes decide in round $r$ before $t + 4\Delta + + \timeoutPrecommit(r-1)$. +\end{lemma} + +\begin{proof} As $p$ is the first correct process to enter round $r$, it + executed the line~\ref{line:tab:nextRound} after $\timeoutPrecommit(r-1)$ + expired. Therefore, $p$ received $2f+1$ $\Precommit$ messages in the round + $r-1$ before time $t$. By the \emph{Gossip communication} property, all + correct processes will receive those messages the latest at time $t + + \Delta$. Correct processes that are in rounds $< r-1$ at time $t$ will + enter round $r-1$ (see the rule at line~\ref{line:tab:nextRound2}) and + trigger $\timeoutPrecommit(r-1)$ (see rule~\ref{line:tab:startTimeoutPrecommit}) + by time $t+\Delta$. Therefore, all correct processes will start round $r$ + by time $t+\Delta+\timeoutPrecommit(r-1)$ (i). + +In the worst case, the process $q$ is the last correct process to enter round +$r$, so $q$ starts round $r$ and sends $\Proposal$ message for some value $v$ +at time $t + \Delta + \timeoutPrecommit(r-1)$. Therefore, all correct processes +receive the $\Proposal$ message from $q$ the latest by time $t + 2\Delta + +\timeoutPrecommit(r-1)$. Therefore, if $\timeoutPropose(r) > 2\Delta + +\timeoutPrecommit(r-1)$, all correct processes will receive $\Proposal$ message +before $\timeoutPropose(r)$ expires. + +By (3) and the rules at line~\ref{line:tab:recvProposal} and +\ref{line:tab:acceptProposal}, all correct processes will accept the +$\Proposal$ message for value $v$ and will send a $\Prevote$ message for +$id(v)$ by time $t + 2\Delta + \timeoutPrecommit(r-1)$. Note that by the +\emph{Gossip communication} property, the $\Prevote$ messages needed to trigger +the rule at line~\ref{line:tab:acceptProposal} are received before time $t + +\Delta$. + +By time $t + 3\Delta + \timeoutPrecommit(r-1)$, all correct processes will receive +$\Proposal$ for $v$ and $2f+1$ corresponding $\Prevote$ messages for $id(v)$. +By the rule at line~\ref{line:tab:recvPrevote}, all correct processes will send +a $\Precommit$ message (see line~\ref{line:tab:precommit-v}) for $id(v)$ by +time $t + 3\Delta + \timeoutPrecommit(r-1)$. Therefore, by time $t + 4\Delta + +\timeoutPrecommit(r-1)$, all correct processes will have received the $\Proposal$ +for $v$ and $2f+1$ $\Precommit$ messages for $id(v)$, so they decide at +line~\ref{line:tab:decide} on $v$. + +This scenario holds if every correct process $q$ sends a $\Precommit$ message +before $\timeoutPrevote(r)$ expires, and if $\timeoutPrecommit(r)$ does not expire +before $t + 4\Delta + \timeoutPrecommit(r-1)$. Let's assume that a correct process +$c_1$ is the first correct process to trigger $\timeoutPrevote(r)$ (see the rule +at line~\ref{line:tab:recvAny2/3Prevote}) at time $t_1 > t$. This implies that +before time $t_1$, $c_1$ received a $\Proposal$ ($step_{c_1}$ must be +$\prevote$ by the rule at line~\ref{line:tab:recvAny2/3Prevote}) and a set of +$2f+1$ $\Prevote$ messages. By time $t_1 + \Delta$, all correct processes will +receive those messages. Note that even if some correct process was in the +smaller round before time $t_1$, at time $t_1 + \Delta$ it will start round $r$ +after receiving those messages (see the rule at +line~\ref{line:tab:skipRounds}). Therefore, all correct processes will send +their $\Prevote$ message for $id(v)$ by time $t_1 + \Delta$, and all correct +processes will receive those messages the by time $t_1 + 2\Delta$. Therefore, +as $\timeoutPrevote(r) > 2\Delta$, this ensures that all correct processes receive +$\Prevote$ messages from all correct processes before their respective local +$\timeoutPrevote(r)$ expire. + +On the other hand, $\timeoutPrecommit(r)$ is triggered in a correct process $c_2$ +after it receives any set of $2f+1$ $\Precommit$ messages for the first time. +Let's denote with $t_2 > t$ the earliest point in time $\timeoutPrecommit(r)$ is +triggered in some correct process $c_2$. This implies that $c_2$ has received +at least $f+1$ $\Precommit$ messages for $id(v)$ from correct processes, i.e., +those processes have received $\Proposal$ for $v$ and $2f+1$ $\Prevote$ +messages for $id(v)$ before time $t_2$. By the \emph{Gossip communication} +property, all correct processes will receive those messages by time $t_2 + +\Delta$, and will send $\Precommit$ messages for $id(v)$. Note that even if +some correct processes were at time $t_2$ in a round smaller than $r$, by the +rule at line~\ref{line:tab:skipRounds} they will enter round $r$ by time $t_2 + +\Delta$. Therefore, by time $t_2 + 2\Delta$, all correct processes will +receive $\Proposal$ for $v$ and $2f+1$ $\Precommit$ messages for $id(v)$. So if +$\timeoutPrecommit(r) > 2\Delta$, all correct processes will decide before the +timeout expires. \end{proof} + + +\begin{lemma} \label{lemma:validValue} If a correct process $p$ locks a value + $v$ at time $t_0 > GST$ in some round $r$ ($lockedValue = v$ and + $lockedRound = r$) and $\timeoutPrecommit(r) > 2\Delta$, then all correct + processes set $validValue$ to $v$ and $validRound$ to $r$ before starting + round $r+1$. \end{lemma} + +\begin{proof} In order to prove this Lemma, we need to prove that if the + process $p$ locks a value $v$ at time $t_0$, then no correct process will + leave round $r$ before time $t_0 + \Delta$ (unless it has already set + $validValue$ to $v$ and $validRound$ to $r$). It is sufficient to prove + this, since by the \emph{Gossip communication} property the messages that + $p$ received at time $t_0$ and that triggered rule at + line~\ref{line:tab:recvPrevote} will be received by time $t_0 + \Delta$ by + all correct processes, so all correct processes that are still in round $r$ + will set $validValue$ to $v$ and $validRound$ to $r$ (by the rule at + line~\ref{line:tab:recvPrevote}). To prove this, we need to compute the + earliest point in time a correct process could leave round $r$ without + updating $validValue$ to $v$ and $validRound$ to $r$ (we denote this time + with $t_1$). The Lemma is correct if $t_0 + \Delta < t_1$. + +If the process $p$ locks a value $v$ at time $t_0$, this implies that $p$ +received the valid $\Proposal$ message for $v$ and $2f+1$ +$\li{\Prevote,h,r,id(v)}$ at time $t_0$. At least $f+1$ of those messages are +sent by correct processes. Let's denote this set of correct processes as $C$. By +Lemma~\ref{lemma:majority-intersection} any set of $2f+1$ $\Prevote$ messages +in round $r$ contains at least a single message from the set $C$. + +Let's denote as time $t$ the earliest point in time a correct process, $c_1$, triggered +$\timeoutPrevote(r)$. This implies that $c_1$ received $2f+1$ $\Prevote$ messages +(see the rule at line \ref{line:tab:recvAny2/3Prevote}), where at least one of +those messages was sent by a process $c_2$ from the set $C$. Therefore, process +$c_2$ had received $\Proposal$ message before time $t$. By the \emph{Gossip +communication} property, all correct processes will receive $\Proposal$ and +$2f+1$ $\Prevote$ messages for round $r$ by time $t+\Delta$. The latest point +in time $p$ will trigger $\timeoutPrevote(r)$ is $t+\Delta$\footnote{Note that +even if $p$ was in smaller round at time $t$ it will start round $r$ by time +$t+\Delta$.}. So the latest point in time $p$ can lock the value $v$ in +round $r$ is $t_0 = t+\Delta+\timeoutPrevote(r)$ (as at this point +$\timeoutPrevote(r)$ expires, so a process sends $\Precommit$ $\nil$ and updates +$step$ to $\precommit$, see line \ref{line:tab:onTimeoutPrevote}). + +Note that according to the Algorithm \ref{alg:tendermint}, a correct process +can not send a $\Precommit$ message before receiving $2f+1$ $\Prevote$ +messages. Therefore, no correct process can send a $\Precommit$ message in +round $r$ before time $t$. If a correct process sends a $\Precommit$ message +for $\nil$, it implies that it has waited for the full duration of +$\timeoutPrevote(r)$ (see line +\ref{line:tab:precommit-nil-onTimeout})\footnote{The other case in which a +correct process $\Precommit$ for $\nil$ is after receiving $2f+1$ $Prevote$ for +$\nil$ messages, see the line \ref{line:tab:precommit-v-1}. By +Lemma~\ref{lemma:majority-intersection}, this is not possible in round $r$.}. +Therefore, no correct process can send $\Precommit$ for $\nil$ before time $t + +\timeoutPrevote(r)$ (*). + +A correct process $q$ that enters round $r+1$ must wait (i) $\timeoutPrecommit(r)$ +(see line \ref{line:tab:nextRound}) or (ii) receiving $f+1$ messages from the +round $r+1$ (see the line \ref{line:tab:skipRounds}). In the former case, $q$ +receives $2f+1$ $\Precommit$ messages before starting $\timeoutPrecommit(r)$. If +at least a single $\Precommit$ message from a correct process (at least $f+1$ +voting power equivalent of those messages is sent by correct processes) is for +$\nil$, then $q$ cannot start round $r+1$ before time $t_1 = t + +\timeoutPrevote(r) + \timeoutPrecommit(r)$ (see (*)). Therefore in this case we have: +$t_0 + \Delta < t_1$, i.e., $t+2\Delta+\timeoutPrevote(r) < t + \timeoutPrevote(r) + +\timeoutPrecommit(r)$, and this is true whenever $\timeoutPrecommit(r) > 2\Delta$, so +Lemma holds in this case. + +If in the set of $2f+1$ $\Precommit$ messages $q$ receives, there is at least a +single $\Precommit$ for $id(v)$ message from a correct process $c$, then $q$ +can start the round $r+1$ the earliest at time $t_1 = t+\timeoutPrecommit(r)$. In +this case, by the \emph{Gossip communication} property, all correct processes +will receive $\Proposal$ and $2f+1$ $\Prevote$ messages (that $c$ received +before time $t$) the latest at time $t+\Delta$. Therefore, $q$ will set +$validValue$ to $v$ and $validRound$ to $r$ the latest at time $t+\Delta$. As +$t+\Delta < t+\timeoutPrecommit(r)$, whenever $\timeoutPrecommit(r) > \Delta$, the +Lemma holds also in this case. + +In case (ii), $q$ received at least a single message from a correct process $c$ +from the round $r+1$. The earliest point in time $c$ could have started round +$r+1$ is $t+\timeoutPrecommit(r)$ in case it received a $\Precommit$ message for +$v$ from some correct process in the set of $2f+1$ $\Precommit$ messages it +received. The same reasoning as above holds also in this case, so $q$ set +$validValue$ to $v$ and $validRound$ to $r$ the latest by time $t+\Delta$. As +$t+\Delta < t+\timeoutPrecommit(r)$, whenever $\timeoutPrecommit(r) > \Delta$, the +Lemma holds also in this case. \end{proof} + +\begin{lemma} \label{lemma:agreement} Algorithm~\ref{alg:tendermint} satisfies +Termination. \end{lemma} + +\begin{proof} Lemma~\ref{lemma:round-synchronisation} defines a scenario in + which all correct processes decide. We now prove that within a bounded + duration after GST such a scenario will unfold. Let's assume that at time + $GST$ the highest round started by a correct process is $r_0$, and that + there exists a correct process $p$ such that the following holds: for every + correct process $c$, $lockedRound_c \le validRound_p$. Furthermore, we + assume that $p$ will be the proposer in some round $r_1 > r$ (this is + ensured by the $\coord$ function). + +We have two cases to consider. In the first case, for all rounds $r \ge r_0$ +and $r < r_1$, no correct process locks a value (set $lockedRound$ to $r$). So +in round $r_1$ we have the scenario from the +Lemma~\ref{lemma:round-synchronisation}, so all correct processes decides in +round $r_1$. + +In the second case, a correct process locks a value $v$ in round $r_2$, where +$r_2 \ge r_0$ and $r_2 < r_1$. Let's assume that $r_2$ is the highest round +before $r_1$ in which some correct process $q$ locks a value. By Lemma +\ref{lemma:validValue} at the end of round $r_2$ the following holds for all +correct processes $c$: $validValue_c = lockedValue_q$ and $validRound_c = r_2$. +Then in round $r_1$, the conditions for the +Lemma~\ref{lemma:round-synchronisation} holds, so all correct processes decide. +\end{proof} + diff --git a/spec/consensus/consensus-paper/rounddiag.sty b/spec/consensus/consensus-paper/rounddiag.sty new file mode 100644 index 00000000000..a6ca5d88352 --- /dev/null +++ b/spec/consensus/consensus-paper/rounddiag.sty @@ -0,0 +1,62 @@ +% ROUNDDIAG STYLE +% for LaTeX version 2e +% by -- 2008 Martin Hutle +% +% This style file is free software; you can redistribute it and/or +% modify it under the terms of the GNU Lesser General Public +% License as published by the Free Software Foundation; either +% version 2 of the License, or (at your option) any later version. +% +% This style file is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% Lesser General Public License for more details. +% +% You should have received a copy of the GNU Lesser General Public +% License along with this style file; if not, write to the +% Free Software Foundation, Inc., 59 Temple Place - Suite 330, +% Boston, MA 02111-1307, USA. +% +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{rounddiag} +\typeout{Document Style `rounddiag' - provides simple round diagrams} +% +\RequirePackage{ifthen} +\RequirePackage{calc} +\RequirePackage{tikz} + +\def\rdstretch{3} + +\tikzstyle{msg}=[->,thick,>=latex] +\tikzstyle{rndline}=[dotted] +\tikzstyle{procline}=[dotted] + +\newenvironment{rounddiag}[2]{ +\begin{center} +\begin{tikzpicture} +\foreach \i in {1,...,#1}{ + \draw[procline] (0,#1-\i) node[xshift=-1em]{$p_{\i}$} -- (#2*\rdstretch+1,#1-\i); +} +\foreach \i in {0,...,#2}{ + \draw[rndline] (\i*\rdstretch+0.5,0) -- (\i*\rdstretch+0.5,#1-1); +} +\newcommand{\rdat}[2]{ + (##2*\rdstretch+0.5,#1-##1) +}% +\newcommand{\round}[2]{% + \def\rdround{##1} + \ifthenelse{\equal{##2}{}}{}{ + \node[yshift=-1em] at ({##1*\rdstretch+0.5-0.5*\rdstretch},0) {##2}; + } +}% +\newcommand{\rdmessage}[3]{\draw[msg] + (\rdround*\rdstretch-\rdstretch+0.5,#1-##1) -- node[yshift=1.2ex]{##3} + (\rdround*\rdstretch+0.5,#1-##2);}% +\newcommand{\rdalltoall}{% + \foreach \i in {1,...,#1}{ + \foreach \j in {1,...,#1}{ + { \rdmessage{\i}{\j}{}}}}}% +}{% +\end{tikzpicture} +\end{center} +} diff --git a/spec/consensus/consensus-paper/technote.sty b/spec/consensus/consensus-paper/technote.sty new file mode 100644 index 00000000000..5353f13cd3a --- /dev/null +++ b/spec/consensus/consensus-paper/technote.sty @@ -0,0 +1,118 @@ +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{technote}[2007/11/09] +\typeout{Template for quick notes with some useful definitions} + +\RequirePackage{ifthen} +\RequirePackage{calc} +\RequirePackage{amsmath,amssymb,amsthm} +\RequirePackage{epsfig} +\RequirePackage{algorithm} +\RequirePackage[noend]{algorithmicplus} + +\newboolean{technote@noedit} +\setboolean{technote@noedit}{false} +\DeclareOption{noedit}{\setboolean{technote@noedit}{true}} + +\newcounter{technote@lang} +\setcounter{technote@lang}{0} +\DeclareOption{german}{\setcounter{technote@lang}{1}} +\DeclareOption{french}{\setcounter{technote@lang}{2}} + +\DeclareOption{fullpage}{ +\oddsidemargin -10mm % Margin on odd side pages (default=0mm) +\evensidemargin -10mm % Margin on even side pages (default=0mm) +\topmargin -10mm % Top margin space (default=16mm) +\headheight \baselineskip % Height of headers (default=0mm) +\headsep \baselineskip % Separation spc btw header and text (d=0mm) +\footskip 30pt % Separation spc btw text and footer (d=30pt) +\textheight 230mm % Total text height (default=200mm) +\textwidth 180mm % Total text width (default=160mm) +} + +\renewcommand{\algorithmiccomment}[1]{\hfill/* #1 */} +\renewcommand{\algorithmiclnosize}{\scriptsize} + +\newboolean{technote@truenumbers} +\setboolean{technote@truenumbers}{false} +\DeclareOption{truenumbers}{\setboolean{technote@truenumbers}{true}} + +\ProcessOptions + +\newcommand{\N}{\ifthenelse{\boolean{technote@truenumbers}}% + {\mbox{\rm I\hspace{-.5em}N}}% + {\mathbb{N}}} + +\newcommand{\R}{\ifthenelse{\boolean{technote@truenumbers}}% + {\mbox{\rm I\hspace{-.2em}R}}% + {\mathbb{R}}} + +\newcommand{\Z}{\mathbb{Z}} + +\newcommand{\set}[1]{\left\{#1\right\}} +\newcommand{\mathsc}[1]{\mbox{\sc #1}} +\newcommand{\li}[1]{\langle#1\rangle} +\newcommand{\st}{\;s.t.\;} +\newcommand{\Real}{\R} +\newcommand{\Natural}{\N} +\newcommand{\Integer}{\Z} + +% edit commands +\newcommand{\newedit}[2]{ + \newcommand{#1}[2][default]{% + \ifthenelse{\boolean{technote@noedit}}{}{ + \par\vspace{2mm} + \noindent + \begin{tabular}{|l|}\hline + \parbox{\linewidth-\tabcolsep*2}{{\bf #2:}\hfill\ifthenelse{\equal{##1}{default}}{}{##1}}\\\hline + \parbox{\linewidth-\tabcolsep*2}{\rule{0pt}{5mm}##2\rule[-2mm]{0pt}{2mm}}\\\hline + \end{tabular} + \par\vspace{2mm} + } + } +} + +\newedit{\note}{Note} +\newedit{\comment}{Comment} +\newedit{\question}{Question} +\newedit{\content}{Content} +\newedit{\problem}{Problem} + +\newcommand{\mnote}[1]{\marginpar{\scriptsize\it + \begin{minipage}[t]{0.8 in} + \raggedright #1 + \end{minipage}}} + +\newcommand{\Insert}[1]{\underline{#1}\marginpar{$|$}} + +\newcommand{\Delete}[1]{\marginpar{$|$} +} + +% lemma, theorem, etc. +\newtheorem{lemma}{Lemma} +\newtheorem{proposition}{Proposition} +\newtheorem{theorem}{Theorem} +\newtheorem{corollary}{Corollary} +\newtheorem{assumption}{Assumption} +\newtheorem{definition}{Definition} + +\gdef\op|{\,|\;} +\gdef\op:{\,:\;} +\newcommand{\assign}{\leftarrow} +\newcommand{\inc}[1]{#1 \assign #1 + 1} +\newcommand{\isdef}{:=} + +\newcommand{\ident}[1]{\mathit{#1}} +\def\newident#1{\expandafter\def\csname #1\endcsname{\ident{#1}}} + +\newcommand{\eg}{{\it e.g.}} +\newcommand{\ie}{{\it i.e.}} +\newcommand{\apriori}{{\it apriori}} +\newcommand{\etal}{{\it et al.}} + +\newcommand\ps@technote{% + \renewcommand\@oddhead{\theheader}% + \let\@evenhead\@oddhead + \renewcommand\@evenfoot + {\hfil\normalfont\textrm{\thepage}\hfil}% + \let\@oddfoot\@evenfoot +} diff --git a/spec/consensus/consensus.md b/spec/consensus/consensus.md new file mode 100644 index 00000000000..5d1526a1789 --- /dev/null +++ b/spec/consensus/consensus.md @@ -0,0 +1,352 @@ +--- +order: 1 +--- +# Byzantine Consensus Algorithm + +## Terms + +- The network is composed of optionally connected _nodes_. Nodes + directly connected to a particular node are called _peers_. +- The consensus process in deciding the next block (at some _height_ + `H`) is composed of one or many _rounds_. +- `NewHeight`, `Propose`, `Prevote`, `Precommit`, and `Commit` + represent state machine states of a round. (aka `RoundStep` or + just "step"). +- A node is said to be _at_ a given height, round, and step, or at + `(H,R,S)`, or at `(H,R)` in short to omit the step. +- To _prevote_ or _precommit_ something means to broadcast a [prevote + vote](https://godoc.org/github.com/tendermint/tendermint/types#Vote) + or [first precommit + vote](https://godoc.org/github.com/tendermint/tendermint/types#FirstPrecommit) + for something. +- A vote _at_ `(H,R)` is a vote signed with the bytes for `H` and `R` + included in its [sign-bytes](../core/data_structures.md#vote). +- _+2/3_ is short for "more than 2/3" +- _1/3+_ is short for "1/3 or more" +- A set of +2/3 of prevotes for a particular block or `` at + `(H,R)` is called a _proof-of-lock-change_ or _PoLC_ for short. + +## State Machine Overview + +At each height of the blockchain a round-based protocol is run to +determine the next block. Each round is composed of three _steps_ +(`Propose`, `Prevote`, and `Precommit`), along with two special steps +`Commit` and `NewHeight`. + +In the optimal scenario, the order of steps is: + +```md +NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->... +``` + +The sequence `(Propose -> Prevote -> Precommit)` is called a _round_. +There may be more than one round required to commit a block at a given +height. Examples for why more rounds may be required include: + +- The designated proposer was not online. +- The block proposed by the designated proposer was not valid. +- The block proposed by the designated proposer did not propagate + in time. +- The block proposed was valid, but +2/3 of prevotes for the proposed + block were not received in time for enough validator nodes by the + time they reached the `Precommit` step. Even though +2/3 of prevotes + are necessary to progress to the next step, at least one validator + may have voted `` or maliciously voted for something else. +- The block proposed was valid, and +2/3 of prevotes were received for + enough nodes, but +2/3 of precommits for the proposed block were not + received for enough validator nodes. + +Some of these problems are resolved by moving onto the next round & +proposer. Others are resolved by increasing certain round timeout +parameters over each successive round. + +## State Machine Diagram + +```md + +-------------------------------------+ + v |(Wait til `CommmitTime+timeoutCommit`) + +-----------+ +-----+-----+ + +----------> | Propose +--------------+ | NewHeight | + | +-----------+ | +-----------+ + | | ^ + |(Else, after timeoutPrecommit) v | ++-----+-----+ +-----------+ | +| Precommit | <------------------------+ Prevote | | ++-----+-----+ +-----------+ | + |(When +2/3 Precommits for block found) | + v | ++--------------------------------------------------------------------+ +| Commit | +| | +| * Set CommitTime = now; | +| * Wait for block, then stage/save/commit block; | ++--------------------------------------------------------------------+ +``` + +# Background Gossip + +A node may not have a corresponding validator private key, but it +nevertheless plays an active role in the consensus process by relaying +relevant meta-data, proposals, blocks, and votes to its peers. A node +that has the private keys of an active validator and is engaged in +signing votes is called a _validator-node_. All nodes (not just +validator-nodes) have an associated state (the current height, round, +and step) and work to make progress. + +Between two nodes there exists a `Connection`, and multiplexed on top of +this connection are fairly throttled `Channel`s of information. An +epidemic gossip protocol is implemented among some of these channels to +bring peers up to speed on the most recent state of consensus. For +example, + +- Nodes gossip `PartSet` parts of the current round's proposer's + proposed block. A LibSwift inspired algorithm is used to quickly + broadcast blocks across the gossip network. +- Nodes gossip prevote/precommit votes. A node `NODE_A` that is ahead + of `NODE_B` can send `NODE_B` prevotes or precommits for `NODE_B`'s + current (or future) round to enable it to progress forward. +- Nodes gossip prevotes for the proposed PoLC (proof-of-lock-change) + round if one is proposed. +- Nodes gossip to nodes lagging in blockchain height with block + [commits](https://godoc.org/github.com/tendermint/tendermint/types#Commit) + for older blocks. +- Nodes opportunistically gossip `ReceivedVote` messages to hint peers what + votes it already has. +- Nodes broadcast their current state to all neighboring peers. (but + is not gossiped further) + +There's more, but let's not get ahead of ourselves here. + +## Proposals + +A proposal is signed and published by the designated proposer at each +round. The proposer is chosen by a deterministic and non-choking round +robin selection algorithm that selects proposers in proportion to their +voting power (see +[implementation](https://github.com/tendermint/tendermint/blob/master/types/validator_set.go)). + +A proposal at `(H,R)` is composed of a block and an optional latest +`PoLC-Round < R` which is included iff the proposer knows of one. This +hints the network to allow nodes to unlock (when safe) to ensure the +liveness property. + +## State Machine Spec + +### Propose Step (height:H,round:R) + +Upon entering `Propose`: + +- The designated proposer proposes a block at `(H,R)`. + +The `Propose` step ends: + +- After `timeoutProposeR` after entering `Propose`. --> goto + `Prevote(H,R)` +- After receiving proposal block and all prevotes at `PoLC-Round`. --> + goto `Prevote(H,R)` +- After [common exit conditions](#common-exit-conditions) + +### Prevote Step (height:H,round:R) + +Upon entering `Prevote`, each validator broadcasts its prevote vote. + +- First, if the validator is locked on a block since `LastLockRound` + but now has a PoLC for something else at round `PoLC-Round` where + `LastLockRound < PoLC-Round < R`, then it unlocks. +- If the validator is still locked on a block, it prevotes that. +- Else, if the proposed block from `Propose(H,R)` is good, it + prevotes that. +- Else, if the proposal is invalid or wasn't received on time, it + prevotes ``. + +The `Prevote` step ends: + +- After +2/3 prevotes for a particular block or ``. -->; goto + `Precommit(H,R)` +- After `timeoutPrevote` after receiving any +2/3 prevotes. --> goto + `Precommit(H,R)` +- After [common exit conditions](#common-exit-conditions) + +### Precommit Step (height:H,round:R) + +Upon entering `Precommit`, each validator broadcasts its precommit vote. + +- If the validator has a PoLC at `(H,R)` for a particular block `B`, it + (re)locks (or changes lock to) and precommits `B` and sets + `LastLockRound = R`. +- Else, if the validator has a PoLC at `(H,R)` for ``, it unlocks + and precommits ``. +- Else, it keeps the lock unchanged and precommits ``. + +A precommit for `` means "I didn’t see a PoLC for this round, but I +did get +2/3 prevotes and waited a bit". + +The Precommit step ends: + +- After +2/3 precommits for ``. --> goto `Propose(H,R+1)` +- After `timeoutPrecommit` after receiving any +2/3 precommits. --> goto + `Propose(H,R+1)` +- After [common exit conditions](#common-exit-conditions) + +### Common exit conditions + +- After +2/3 precommits for a particular block. --> goto + `Commit(H)` +- After any +2/3 prevotes received at `(H,R+x)`. --> goto + `Prevote(H,R+x)` +- After any +2/3 precommits received at `(H,R+x)`. --> goto + `Precommit(H,R+x)` + +### Commit Step (height:H) + +- Set `CommitTime = now()` +- Wait until block is received. --> goto `NewHeight(H+1)` + +### NewHeight Step (height:H) + +- Move `Precommits` to `LastCommit` and increment height. +- Set `StartTime = CommitTime+timeoutCommit` +- Wait until `StartTime` to receive straggler commits. --> goto + `Propose(H,0)` + +## Proofs + +### Proof of Safety + +Assume that at most -1/3 of the voting power of validators is byzantine. +If a validator commits block `B` at round `R`, it's because it saw +2/3 +of precommits at round `R`. This implies that 1/3+ of honest nodes are +still locked at round `R' > R`. These locked validators will remain +locked until they see a PoLC at `R' > R`, but this won't happen because +1/3+ are locked and honest, so at most -2/3 are available to vote for +anything other than `B`. + +### Proof of Liveness + +If 1/3+ honest validators are locked on two different blocks from +different rounds, a proposers' `PoLC-Round` will eventually cause nodes +locked from the earlier round to unlock. Eventually, the designated +proposer will be one that is aware of a PoLC at the later round. Also, +`timeoutProposalR` increments with round `R`, while the size of a +proposal are capped, so eventually the network is able to "fully gossip" +the whole proposal (e.g. the block & PoLC). + +### Proof of Fork Accountability + +Define the JSet (justification-vote-set) at height `H` of a validator +`V1` to be all the votes signed by the validator at `H` along with +justification PoLC prevotes for each lock change. For example, if `V1` +signed the following precommits: `Precommit(B1 @ round 0)`, +`Precommit( @ round 1)`, `Precommit(B2 @ round 4)` (note that no +precommits were signed for rounds 2 and 3, and that's ok), +`Precommit(B1 @ round 0)` must be justified by a PoLC at round 0, and +`Precommit(B2 @ round 4)` must be justified by a PoLC at round 4; but +the precommit for `` at round 1 is not a lock-change by definition +so the JSet for `V1` need not include any prevotes at round 1, 2, or 3 +(unless `V1` happened to have prevoted for those rounds). + +Further, define the JSet at height `H` of a set of validators `VSet` to +be the union of the JSets for each validator in `VSet`. For a given +commit by honest validators at round `R` for block `B` we can construct +a JSet to justify the commit for `B` at `R`. We say that a JSet +_justifies_ a commit at `(H,R)` if all the committers (validators in the +commit-set) are each justified in the JSet with no duplicitous vote +signatures (by the committers). + +- **Lemma**: When a fork is detected by the existence of two + conflicting [commits](../core/data_structures.md#commit), the + union of the JSets for both commits (if they can be compiled) must + include double-signing by at least 1/3+ of the validator set. + **Proof**: The commit cannot be at the same round, because that + would immediately imply double-signing by 1/3+. Take the union of + the JSets of both commits. If there is no double-signing by at least + 1/3+ of the validator set in the union, then no honest validator + could have precommitted any different block after the first commit. + Yet, +2/3 did. Reductio ad absurdum. + +As a corollary, when there is a fork, an external process can determine +the blame by requiring each validator to justify all of its round votes. +Either we will find 1/3+ who cannot justify at least one of their votes, +and/or, we will find 1/3+ who had double-signed. + +### Alternative algorithm + +Alternatively, we can take the JSet of a commit to be the "full commit". +That is, if light clients and validators do not consider a block to be +committed unless the JSet of the commit is also known, then we get the +desirable property that if there ever is a fork (e.g. there are two +conflicting "full commits"), then 1/3+ of the validators are immediately +punishable for double-signing. + +There are many ways to ensure that the gossip network efficiently share +the JSet of a commit. One solution is to add a new message type that +tells peers that this node has (or does not have) a +2/3 majority for B +(or) at (H,R), and a bitarray of which votes contributed towards that +majority. Peers can react by responding with appropriate votes. + +We will implement such an algorithm for the next iteration of the +Tendermint consensus protocol. + +Other potential improvements include adding more data in votes such as +the last known PoLC round that caused a lock change, and the last voted +round/step (or, we may require that validators not skip any votes). This +may make JSet verification/gossip logic easier to implement. + +### Censorship Attacks + +Due to the definition of a block +[commit](https://github.com/tendermint/tendermint/blob/master/docs/tendermint-core/validators.md), any 1/3+ coalition of +validators can halt the blockchain by not broadcasting their votes. Such +a coalition can also censor particular transactions by rejecting blocks +that include these transactions, though this would result in a +significant proportion of block proposals to be rejected, which would +slow down the rate of block commits of the blockchain, reducing its +utility and value. The malicious coalition might also broadcast votes in +a trickle so as to grind blockchain block commits to a near halt, or +engage in any combination of these attacks. + +If a global active adversary were also involved, it can partition the +network in such a way that it may appear that the wrong subset of +validators were responsible for the slowdown. This is not just a +limitation of Tendermint, but rather a limitation of all consensus +protocols whose network is potentially controlled by an active +adversary. + +### Overcoming Forks and Censorship Attacks + +For these types of attacks, a subset of the validators through external +means should coordinate to sign a reorg-proposal that chooses a fork +(and any evidence thereof) and the initial subset of validators with +their signatures. Validators who sign such a reorg-proposal forego its +collateral on all other forks. Clients should verify the signatures on +the reorg-proposal, verify any evidence, and make a judgement or prompt +the end-user for a decision. For example, a phone wallet app may prompt +the user with a security warning, while a refrigerator may accept any +reorg-proposal signed by +1/2 of the original validators. + +No non-synchronous Byzantine fault-tolerant algorithm can come to +consensus when 1/3+ of validators are dishonest, yet a fork assumes that +1/3+ of validators have already been dishonest by double-signing or +lock-changing without justification. So, signing the reorg-proposal is a +coordination problem that cannot be solved by any non-synchronous +protocol (i.e. automatically, and without making assumptions about the +reliability of the underlying network). It must be provided by means +external to the weakly-synchronous Tendermint consensus algorithm. For +now, we leave the problem of reorg-proposal coordination to human +coordination via internet media. Validators must take care to ensure +that there are no significant network partitions, to avoid situations +where two conflicting reorg-proposals are signed. + +Assuming that the external coordination medium and protocol is robust, +it follows that forks are less of a concern than [censorship +attacks](#censorship-attacks). + +### Canonical vs subjective commit + +We distinguish between "canonical" and "subjective" commits. A subjective commit is what +each validator sees locally when they decide to commit a block. The canonical commit is +what is included by the proposer of the next block in the `LastCommit` field of +the block. This is what makes it canonical and ensures every validator agrees on the canonical commit, +even if it is different from the +2/3 votes a validator has seen, which caused the validator to +commit the respective block. Each block contains a canonical +2/3 commit for the previous +block. diff --git a/spec/consensus/creating-proposal.md b/spec/consensus/creating-proposal.md new file mode 100644 index 00000000000..cb43c8ebb41 --- /dev/null +++ b/spec/consensus/creating-proposal.md @@ -0,0 +1,43 @@ +--- +order: 2 +--- +# Creating a proposal + +A block consists of a header, transactions, votes (the commit), +and a list of evidence of malfeasance (ie. signing conflicting votes). + +We include no more than 1/10th of the maximum block size +(`ConsensusParams.Block.MaxBytes`) of evidence with each block. + +## Reaping transactions from the mempool + +When we reap transactions from the mempool, we calculate maximum data +size by subtracting maximum header size (`MaxHeaderBytes`), the maximum +amino overhead for a block (`MaxAminoOverheadForBlock`), the size of +the last commit (if present) and evidence (if present). While reaping +we account for amino overhead for each transaction. + +```go +func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 { + return maxBytes - + MaxOverheadForBlock - + MaxHeaderBytes - + int64(valsCount)*MaxVoteBytes - + int64(evidenceCount)*MaxEvidenceBytes +} +``` + +## Validating transactions in the mempool + +Before we accept a transaction in the mempool, we check if it's size is no more +than {MaxDataSize}. {MaxDataSize} is calculated using the same formula as +above, except we subtract the max number of evidence, {MaxNum} by the maximum size of evidence + +```go +func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 { + return maxBytes - + MaxOverheadForBlock - + MaxHeaderBytes - + (maxNumEvidence * MaxEvidenceBytes) +} +``` diff --git a/spec/consensus/evidence.md b/spec/consensus/evidence.md new file mode 100644 index 00000000000..84d51a01b57 --- /dev/null +++ b/spec/consensus/evidence.md @@ -0,0 +1,199 @@ +# Evidence + +Evidence is an important component of Tendermint's security model. Whilst the core +consensus protocol provides correctness gaurantees for state machine replication +that can tolerate less than 1/3 failures, the evidence system looks to detect and +gossip byzantine faults whose combined power is greater than or equal to 1/3. It is worth noting that +the evidence system is designed purely to detect possible attacks, gossip them, +commit them on chain and inform the application running on top of Tendermint. +Evidence in itself does not punish "bad actors", this is left to the discretion +of the application. A common form of punishment is slashing where the validators +that were caught violating the protocol have all or a portion of their voting +power removed. Evidence, given the assumption that 1/3+ of the network is still +byzantine, is susceptible to censorship and should therefore be considered added +security on a "best effort" basis. + +This document walks through the various forms of evidence, how they are detected, +gossiped, verified and committed. + +> NOTE: Evidence here is internal to tendermint and should not be confused with +> application evidence + +## Detection + +### Equivocation + +Equivocation is the most fundamental of byzantine faults. Simply put, to prevent +replication of state across all nodes, a validator tries to convince some subset +of nodes to commit one block whilst convincing another subset to commit a +different block. This is achieved by double voting (hence +`DuplicateVoteEvidence`). A successful duplicate vote attack requires greater +than 1/3 voting power and a (temporary) network partition between the aforementioned +subsets. This is because in consensus, votes are gossiped around. When a node +observes two conflicting votes from the same peer, it will use the two votes of +evidence and begin gossiping this evidence to other nodes. [Verification](#duplicatevoteevidence) is addressed further down. + +```go +type DuplicateVoteEvidence struct { + VoteA Vote + VoteB Vote + + // and abci specific fields +} +``` + +### Light Client Attacks + +Light clients also comply with the 1/3+ security model, however, by using a +different, more lightweight verification method they are subject to a +different kind of 1/3+ attack whereby the byzantine validators could sign an +alternative light block that the light client will think is valid. Detection, +explained in greater detail +[here](../light-client/detection/detection_003_reviewed.md), involves comparison +with multiple other nodes in the hope that at least one is "honest". An "honest" +node will return a challenging light block for the light client to validate. If +this challenging light block also meets the +[validation criteria](../light-client/verification/verification_001_published.md) +then the light client sends the "forged" light block to the node. +[Verification](#lightclientattackevidence) is addressed further down. + +```go +type LightClientAttackEvidence struct { + ConflictingBlock LightBlock + CommonHeight int64 + + // and abci specific fields +} +``` + +## Verification + +If a node receives evidence, it will first try to verify it, then persist it. +Evidence of byzantine behavior should only be committed once (uniqueness) and +should be committed within a certain period from the point that it occurred +(timely). Timelines is defined by the `EvidenceParams`: `MaxAgeNumBlocks` and +`MaxAgeDuration`. In Proof of Stake chains where validators are bonded, evidence +age should be less than the unbonding period so validators still can be +punished. Given these two propoerties the following initial checks are made. + +1. Has the evidence expired? This is done by taking the height of the `Vote` + within `DuplicateVoteEvidence` or `CommonHeight` within + `LightClientAttakEvidence`. The evidence height is then used to retrieve the + header and thus the time of the block that corresponds to the evidence. If + `CurrentHeight - MaxAgeNumBlocks > EvidenceHeight` && `CurrentTime - + MaxAgeDuration > EvidenceTime`, the evidence is considered expired and + ignored. + +2. Has the evidence already been committed? The evidence pool tracks the hash of + all committed evidence and uses this to determine uniqueness. If a new + evidence has the same hash as a committed one, the new evidence will be + ignored. + +### DuplicateVoteEvidence + +Valid `DuplicateVoteEvidence` must adhere to the following rules: + +- Validator Address, Height, Round and Type must be the same for both votes + +- BlockID must be different for both votes (BlockID can be for a nil block) + +- Validator must have been in the validator set at that height + +- Vote signature must be correctly signed. This also uses `ChainID` so we know + that the fault occurred on this chain + +### LightClientAttackEvidence + +Valid Light Client Attack Evidence must adhere to the following rules: + +- If the header of the light block is invalid, thus indicating a lunatic attack, + the node must check that they can use `verifySkipping` from their header at + the common height to the conflicting header + +- If the header is valid, then the validator sets are the same and this is + either a form of equivocation or amnesia. We therefore check that 2/3 of the + validator set also signed the conflicting header. + +- The nodes own header at the same height as the conflicting header must have a + different hash to the conflicting header. + +- If the nodes latest header is less in height to the conflicting header, then + the node must check that the conflicting block has a time that is less than + this latest header (This is a forward lunatic attack). + +## Gossiping + +If a node verifies evidence it then broadcasts it to all peers, continously sending +the same evidence once every 10 seconds until the evidence is seen on chain or +expires. + +## Commiting on Chain + +Evidence takes strict priority over regular transactions, thus a block is filled +with evidence first and transactions take up the remainder of the space. To +mitigate the threat of an already punished node from spamming the network with +more evidence, the size of the evidence in a block can be capped by +`EvidenceParams.MaxBytes`. Nodes receiving blocks with evidence will validate +the evidence before sending `Prevote` and `Precommit` votes. The evidence pool +will usually cache verifications so that this process is much quicker. + +## Sending Evidence to the Application + +After evidence is committed, the block is then processed by the block executor +which delivers the evidence to the application via `EndBlock`. Evidence is +stripped of the actual proof, split up per faulty validator and only the +validator, height, time and evidence type is sent. + +```proto +enum EvidenceType { + UNKNOWN = 0; + DUPLICATE_VOTE = 1; + LIGHT_CLIENT_ATTACK = 2; +} + +message Evidence { + EvidenceType type = 1; + // The offending validator + Validator validator = 2 [(gogoproto.nullable) = false]; + // The height when the offense occurred + int64 height = 3; + // The corresponding time where the offense occurred + google.protobuf.Timestamp time = 4 [ + (gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + // Total voting power of the validator set in case the ABCI application does + // not store historical validators. + // https://github.com/tendermint/tendermint/issues/4581 + int64 total_voting_power = 5; +} +``` + +`DuplicateVoteEvidence` and `LightClientAttackEvidence` are self-contained in +the sense that the evidence can be used to derive the `abci.Evidence` that is +sent to the application. Because of this, extra fields are necessary: + +```go +type DuplicateVoteEvidence struct { + VoteA *Vote + VoteB *Vote + + // abci specific information + TotalVotingPower int64 + ValidatorPower int64 + Timestamp time.Time +} + +type LightClientAttackEvidence struct { + ConflictingBlock *LightBlock + CommonHeight int64 + + // abci specific information + ByzantineValidators []*Validator + TotalVotingPower int64 + Timestamp time.Time +} +``` + +These ABCI specific fields don't affect validity of the evidence itself but must +be consistent amongst nodes and agreed upon on chain. If evidence with the +incorrect abci information is sent, a node will create new evidence from it and +replace the ABCI fields with the correct information. diff --git a/spec/consensus/light-client/README.md b/spec/consensus/light-client/README.md new file mode 100644 index 00000000000..44b9e0c7627 --- /dev/null +++ b/spec/consensus/light-client/README.md @@ -0,0 +1,9 @@ +--- +order: 1 +parent: + title: Light Client + order: false +--- +# Tendermint Light Client Protocol + +Deprecated, please see [light-client](../../light-client/README.md). diff --git a/spec/consensus/light-client/accountability.md b/spec/consensus/light-client/accountability.md new file mode 100644 index 00000000000..5cf46b0b437 --- /dev/null +++ b/spec/consensus/light-client/accountability.md @@ -0,0 +1,3 @@ +# Fork accountability + +Deprecated, please see [light-client/accountability](../../light-client/accountability.md). diff --git a/spec/consensus/light-client/assets/light-node-image.png b/spec/consensus/light-client/assets/light-node-image.png new file mode 100644 index 0000000000000000000000000000000000000000..f0b93c6e415ebc39bc2313c885faacb35aa7af9a GIT binary patch literal 122270 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ɸa2^db4^;{u=6(Fa8oE-0a}M)8Mf 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

#_$y479`m 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 yG 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 NF6my(J#Y2ke*p{XTJ-<` literal 0 HcmV?d00001 diff --git a/spec/consensus/light-client/detection.md b/spec/consensus/light-client/detection.md new file mode 100644 index 00000000000..5c87562ba5e --- /dev/null +++ b/spec/consensus/light-client/detection.md @@ -0,0 +1,3 @@ +# Detection + +Deprecated, please see [light-client/detection](../../light-client/detection.md). diff --git a/spec/consensus/light-client/verification.md b/spec/consensus/light-client/verification.md new file mode 100644 index 00000000000..1f0104a4027 --- /dev/null +++ b/spec/consensus/light-client/verification.md @@ -0,0 +1,3 @@ +# Core Verification + +Deprecated, please see [light-client/accountability](../../light-client/verification.md). diff --git a/spec/consensus/proposer-based-timestamp/README.md b/spec/consensus/proposer-based-timestamp/README.md new file mode 100644 index 00000000000..82421d99dad --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/README.md @@ -0,0 +1,20 @@ +# Proposer-Based Timestamps + +This section describes a version of the Tendermint consensus protocol, +which uses proposer-based timestamps. + +## Contents + +- [Proposer-Based Time][main] (entry point) +- [Part I - System Model and Properties][sysmodel] +- [Part II - Protocol Specification][algorithm] +- [TLA+ Specification][proposertla] + + +[algorithm]: ./pbts-algorithm_001_draft.md + +[sysmodel]: ./pbts-sysmodel_001_draft.md + +[main]: ./pbts_001_draft.md + +[proposertla]: ./tla/TendermintPBT_001_draft.tla diff --git a/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md b/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md new file mode 100644 index 00000000000..9791ff86bc8 --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md @@ -0,0 +1,163 @@ +# Proposer-Based Time - Part II + +## Updated Consensus Algorithm + +### Outline + +The algorithm in the [arXiv paper][arXiv] evaluates rules of the received messages without making explicit how these messages are received. In our solution, we will make some message filtering explicit. We will assume that there are message reception steps (where messages are received and possibly stored locally for later evaluation of rules) and processing steps (the latter roughly as described in a way similar to the pseudo code of the arXiv paper). + +In contrast to the original algorithm the field `proposal` in the `PROPOSE` message is a pair `(v, time)`, of the proposed consensus value `v` and the proposed time `time`. + +#### **[PBTS-RECEPTION-STEP.0]** + +In the reception step at process `p` at local time `now_p`, upon receiving a message `m`: + +- if the message `m` is of type `PROPOSE` and satisfies `now_p - PRECISION < m.time < now_p + PRECISION + MSGDELAY`, then mark the message as `timely` + +> if `m` does not satisfy the constraint consider it `untimely` + + +#### **[PBTS-PROCESSING-STEP.0]** + +In the processing step, based on the messages stored, the rules of the algorithms are +executed. Note that the processing step only operates on messages +for the current height. The consensus algorithm rules are defined by the following updates to arXiv paper. + +#### New `StartRound` + +There are two additions + +- in case the proposer's local time is smaller than the time of the previous block, the proposer waits until this is not the case anymore (to ensure the block time is monotonically increasing) +- the proposer sends its time `now_p` as part of its proposal + +We update the timeout for the `PROPOSE` step according to the following reasoning: + +- If a correct proposer needs to wait to make sure its proposed time is larger than the `blockTime` of the previous block, then it sends by realtime `blockTime + ACCURACY` (By this time, its local clock must exceed `blockTime`) +- the receiver will receive a `PROPOSE` message by `blockTime + ACCURACY + MSGDELAY` +- the receiver's local clock will be `<= blockTime + 2 * ACCURACY + MSGDELAY` +- thus when the receiver `p` enters this round it can set its timeout to a value `waitingTime => blockTime + 2 * ACCURACY + MSGDELAY - now_p` + +So we should set the timeout to `max(timeoutPropose(round_p), waitingTime)`. + +> If, in the future, a block delay parameter `BLOCKDELAY` is introduced, this means +that the proposer should wait for `now_p > blockTime + BLOCKDELAY` before sending a `PROPOSE` message. +Also, `BLOCKDELAY` needs to be added to `waitingTime`. + +#### **[PBTS-ALG-STARTROUND.0]** + +```go +function StartRound(round) { + blockTime ← block time of block h_p - 1 + waitingTime ← blockTime + 2 * ACCURACY + MSGDELAY - now_p + round_p ← round + step_p ← propose + if proposer(h_p, round_p) = p { + wait until now_p > blockTime // new wait condition + if validValue_p != nil { + proposal ← (validValue_p, now_p) // added "now_p" + } + else { + proposal ← (getValue(), now_p) // added "now_p" + } + broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩ + } + else { + schedule OnTimeoutPropose(h_p,round_p) to be executed after max(timeoutPropose(round_p), waitingTime) + } +} +``` + +#### New Rule Replacing Lines 22 - 27 + +- a validator prevotes for the consensus value `v` **and** the time `t` +- the code changes as the `PROPOSAL` message carries time (while `lockedValue` does not) + +#### **[PBTS-ALG-UPON-PROP.0]** + +```go +upon timely(⟨PROPOSAL, h_p, round_p, (v,t), −1⟩) from proposer(h_p, round_p) while step_p = propose do { + if valid(v) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) { + broadcast ⟨PREVOTE, h_p, round_p, id(v,t)⟩ + } + else { + broadcast ⟨PREVOTE, h_p, round_p, nil⟩ + } + step_p ← prevote +} +``` + +#### New Rule Replacing Lines 28 - 33 + +In case consensus is not reached in round 1, in `StartRound` the proposer of future rounds may propose the same value but with a different time. +Thus, the time `tprop` in the `PROPOSAL` message need not match the time `tvote` in the (old) `PREVOTE` messages. +A validator may send `PREVOTE` for the current round as long as the value `v` matches. +This gives the following rule: + +#### **[PBTS-ALG-OLD-PREVOTE.0]** + +```go +upon timely(⟨PROPOSAL, h_p, round_p, (v, tprop), vr⟩) from proposer(h_p, round_p) AND 2f + 1 ⟨PREVOTE, h_p, vr, id((v, tvote)⟩ +while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do { + if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) { + broadcast ⟨PREVOTE, h_p, roundp, id(v, tprop)⟩ + } + else { + broadcast ⟨PREVOTE, hp, roundp, nil⟩ + } + step_p ← prevote +} +``` + +#### New Rule Replacing Lines 36 - 43 + +- As above, in the following `(v,t)` is part of the message rather than `v` +- the stored values (i.e., `lockedValue`, `validValue`) do not contain the time + +#### **[PBTS-ALG-NEW-PREVOTE.0]** + +```go +upon timely(⟨PROPOSAL, h_p, round_p, (v,t), ∗⟩) from proposer(h_p, round_p) AND 2f + 1 ⟨PREVOTE, h_p, round_p, id(v,t)⟩ while valid(v) ∧ step_p ≥ prevote for the first time do { + if step_p = prevote { + lockedValue_p ← v + lockedRound_p ← round_p + broadcast ⟨PRECOMMIT, h_p, round_p, id(v,t))⟩ + step_p ← precommit + } + validValue_p ← v + validRound_p ← round_p +} +``` + +#### New Rule Replacing Lines 49 - 54 + +- we decide on `v` as well as on the time from the proposal message +- here we do not care whether the proposal was received timely. + +> In particular we need to take care of the case where the proposer is untimely to one correct validator only. We need to ensure that this validator decides if all decide. + +#### **[PBTS-ALG-DECIDE.0]** + +```go +upon ⟨PROPOSAL, h_p, r, (v,t), ∗⟩ from proposer(h_p, r) AND 2f + 1 ⟨PRECOMMIT, h_p, r, id(v,t)⟩ while decisionp[h_p] = nil do { + if valid(v) { + decision_p [h_p] = (v,t) // decide on time too + h_p ← h_p + 1 + reset lockedRound_p , lockedValue_p, validRound_p and validValue_p to initial values and empty message log + StartRound(0) + } +} +``` + +**All other rules remains unchanged.** + +Back to [main document][main]. + +[main]: ./pbts_001_draft.md + +[arXiv]: https://arxiv.org/abs/1807.04938 + +[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md + +[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md + +[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md diff --git a/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md new file mode 100644 index 00000000000..2eb32bc7df8 --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md @@ -0,0 +1,198 @@ +# Proposer-Based Time - Part I + +## System Model + +### Time and Clocks + +#### **[PBTS-CLOCK-NEWTON.0]** + +There is a reference Newtonian real-time `t` (UTC). + +Every correct validator `V` maintains a synchronized clock `C_V` that ensures: + +#### **[PBTS-CLOCK-PRECISION.0]** + +There exists a system parameter `PRECISION` such that for any two correct validators `V` and `W`, and at any real-time `t`, +`|C_V(t) - C_W(t)| < PRECISION` + + +### Message Delays + +We do not want to interfere with the Tendermint timing assumptions. We will postulate a timing restriction, which, if satisfied, ensures that liveness is preserved. + +In general the local clock may drift from the global time. (It may progress faster, e.g., one second of clock time might take 1.005 seconds of real-time). As a result the local clock and the global clock may be measured in different time units. Usually, the message delay is measured in global clock time units. To estimate the correct local timeout precisely, we would need to estimate the clock time duration of a message delay taking into account the clock drift. For simplicity we ignore this, and directly postulate the message delay assumption in terms of local time. + + +#### **[PBTS-MSG-D.0]** + +There exists a system parameter `MSGDELAY` for message end-to-end delays **counted in clock-time**. + +> Observe that [PBTS-MSG-D.0] imposes constraints on message delays as well as on the clock. + +#### **[PBTS-MSG-FAIR.0]** + +The message end-to-end delay between a correct proposer and a correct validator (for `PROPOSE` messages) is less than `MSGDELAY`. + + +## Problem Statement + +In this section we define the properties of Tendermint consensus (cf. the [arXiv paper][arXiv]) in this new system model. + +#### **[PBTS-PROPOSE.0]** + +A proposer proposes a pair `(v,t)` of consensus value `v` and time `t`. + +> We then restrict the allowed decisions along the following lines: + +#### **[PBTS-INV-AGREEMENT.0]** + +[Agreement] No two correct validators decide on different values `v`. + +#### **[PBTS-INV-TIME-VAL.0]** + +[Time-Validity] If a correct validator decides on `t` then `t` is "OK" (we will formalize this below), even if up to `2f` validators are faulty. + +However, the properties of Tendermint consensus are of more interest with respect to the blocks, that is, what is written into a block and when. We therefore, in the following, will give the safety and liveness properties from this block-centric viewpoint. +For this, observe that the time `t` decided at consensus height `k` will be written in the block of height `k+1`, and will be supported by `2f + 1` `PRECOMMIT` messages of the same consensus round `r`. The time written in the block, we will denote by `b.time` (to distinguish it from the term `bfttime` used for median-based time). For this, it is important to have the following consensus algorithm property: + +#### **[PBTS-INV-TIME-AGR.0]** + +[Time-Agreement] If two correct validators decide in the same round, then they decide on the same `t`. + +#### **[PBTS-DECISION-ROUND.0]** + +Note that the relation between consensus decisions, on the one hand, and blocks, on the other hand, is not immediate; in particular if we consider time: In the proposed solution, +as validators may decide in different rounds, they may decide on different times. +The proposer of the next block, may pick a commit (at least `2f + 1` `PRECOMMIT` messages from one round), and thus it picks a decision round that is going to become "canonic". +As a result, the proposer implicitly has a choice of one of the times that belong to rounds in which validators decided. Observe that this choice was implicitly the case already in the median-based `bfttime`. +However, as most consensus instances terminate within one round on the Cosmos hub, this is hardly ever observed in practice. + + + +Finally, observe that the agreement ([Agreement] and [Time-Agreement]) properties are based on the Tendermint security model [TMBC-FM-2THIRDS.0] of more than 2/3 correct validators, while [Time-Validity] is based on more than 1/3 correct validators. + +### SAFETY + +Here we will provide specifications that relate local time to block time. However, since we do not assume (by now) that local time is linked to real-time, these specifications also do not provide a relation between block time and real-time. Such properties are given [later](#REAL-TIME-SAFETY). + +For a correct validator `V`, let `beginConsensus(V,k)` be the local time when it sets its height to `k`, and let `endConsensus(V,k)` be the time when it sets its height to `k + 1`. + +Let + +- `beginConsensus(k)` be the minimum over `beginConsensus(V,k)`, and +- `last-beginConsensus(k)` be the maximum over `beginConsensus(V,k)`, and +- `endConsensus(k)` the maximum over `endConsensus(V,k)` + +for all correct validators `V`. + +> Observe that `beginConsensus(k) <= last-beginConsensus(k)` and if local clocks are monotonic, then `last-beginConsensus(k) <= endConsensus(k)`. + +#### **[PBTS-CLOCK-GROW.0]** + +We assume that during one consensus instance, local clocks are not set back, in particular for each correct validator `V` and each height `k`, we have `beginConsensus(V,k) < endConsensus(V,k)`. + + +#### **[PBTS-CONSENSUS-TIME-VALID.0]** + +If + +- there is a valid commit `c` for height `k`, and +- `c` contains a `PRECOMMIT` message by at least one correct validator, + +then the time `b.time` in the block `b` that is signed by `c` satisfies + +- `beginConsensus(k) - PRECISION <= b.time < endConsensus(k) + PRECISION + MSGDELAY`. + + +> [PBTS-CONSENSUS-TIME-VALID.0] is based on an analysis where the proposer is faulty (and does does not count towards `beginConsensus(k)` and `endConsensus(k)`), and we estimate the times at which correct validators receive and `accept` the `propose` message. If the proposer is correct we obtain + +#### **[PBTS-CONSENSUS-LIVE-VALID-CORR-PROP.0]** + +If the proposer of round 1 is correct, and + +- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and +- [PBTS-MSG-FAIR.0], and +- [PBTS-CLOCK-PRECISION.0], and +- [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?) + +then eventually (within bounded time) every correct validator decides in round 1. + +#### **[PBTS-CONSENSUS-SAFE-VALID-CORR-PROP.0]** + +If the proposer of round 1 is correct, and + +- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and +- [PBTS-MSG-FAIR.0], and +- [PBTS-CLOCK-PRECISION.0], and +- [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?) + +then `beginConsensus_k <= b.time <= last-beginConsensus_k`. + + +> For the above two properties we will assume that a correct proposer `v` sends its `PROPOSAL` at its local time `beginConsensus(v,k)`. + +### LIVENESS + +If + +- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and +- [PBTS-MSG-FAIR.0], +- [PBTS-CLOCK.0], and +- [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?) + +then eventually there is a valid commit `c` for height `k`. + + +### REAL-TIME SAFETY + +> We want to give a property that can be exploited from the outside, that is, given a block with some time stored in it, what is the estimate at which real-time the block was generated. To do so, we need to link clock-time to real-time; which is not the case with [PBTS-CLOCK.0]. For this, we introduce the following assumption on the clocks: + +#### **[PBTS-CLOCKSYNC-EXTERNAL.0]** + +There is a system parameter `ACCURACY`, such that for all real-times `t` and all correct validators `V`, + +- `| C_V(t) - t | < ACCURACY`. + +> `ACCURACY` is not necessarily visible at the code level. The properties below just show that the smaller +its value, the closer the block time will be to real-time + +#### **[PBTS-CONSENSUS-PTIME.0]** + +LET `m` be a propose message. We consider the following two real-times `proposalTime(m)` and `propRecvTime(m)`: + +- if the proposer is correct and sends `m` at time `t`, we write `proposalTime(m)` for real-time `t`. +- if first correct validator receives `m` at time `t`, we write `propRecvTime(m)` for real-time `t`. + + +#### **[PBTS-CONSENSUS-REALTIME-VALID.0]** + +Let `b` be a block with a valid commit that contains at least one `precommit` message by a correct validator (and `proposalTime` is the time for the height/round `propose` message `m` that triggered the `precommit`). Then: + +`propRecvTime(m) - ACCURACY - PRECISION < b.time < propRecvTime(m) + ACCURACY + PRECISION + MSGDELAY` + + +#### **[PBTS-CONSENSUS-REALTIME-VALID-CORR.0]** + +Let `b` be a block with a valid commit that contains at least one `precommit` message by a correct validator (and `proposalTime` is the time for the height/round `propose` message `m` that triggered the `precommit`). Then, if the proposer is correct: + +`proposalTime(m) - ACCURACY < b.time < proposalTime(m) + ACCURACY` + +> by the algorithm at time `proposalTime(m)` the proposer fixes `m.time <- now_p(proposalTime(m))` + +> "triggered the `PRECOMMIT`" implies that the data in `m` and `b` are "matching", that is, `m` proposed the values that are actually stored in `b`. + +Back to [main document][main]. + +[main]: ./pbts_001_draft.md + +[arXiv]: https://arxiv.org/abs/1807.04938 + +[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md + +[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md + +[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md + +[algorithm]: ./pbts-algorithm_001_draft.md + +[sysmodel]: ./pbts-sysmodel_001_draft.md diff --git a/spec/consensus/proposer-based-timestamp/pbts_001_draft.md b/spec/consensus/proposer-based-timestamp/pbts_001_draft.md new file mode 100644 index 00000000000..a4d876b28c3 --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/pbts_001_draft.md @@ -0,0 +1,270 @@ +# Proposer-Based Time + +## Current BFTTime + +### Description + +In Tendermint consensus, the first version of how time is computed and stored in a block works as follows: + +- validators send their current local time as part of `precommit` messages +- upon collecting the `precommit` messages that the proposer uses to build a commit to be put in the next block, the proposer computes the `time` of the next block as the median (weighted over voting power) of the times in the `precommit` messages. + +### Analysis + +1. **Fault tolerance.** The computed median time is called [`bfttime`][bfttime] as it is indeed fault-tolerant: if **less than a third** of the validators is faulty (counted in voting power), it is guaranteed that the computed time lies between the minimum and the maximum times sent by correct validators. +1. **Effect of faulty validators.** If more than `1/2` of the voting power (which is in fact more than one third and less than two thirds of the voting power) is held by faulty validators, then the time is under total control of the faulty validators. (This is particularly challenging in the context of [lightclient][lcspec] security.) +1. **Proposer influence on block time.** The proposer of the next block has a degree of freedom in choosing the `bfttime`, since it computes the median time based on the timestamps from `precommit` messages sent by + `2f + 1` correct validators. + 1. If there are `n` different timestamps in the `precommit` messages, the proposer can use any subset of timestamps that add up to `2f + 1` + of the voting power in order to compute the median. + 1. If the validators decide in different rounds, the proposer can decide on which round the median computation is based. +1. **Liveness.** The liveness of the protocol: + 1. does not depend on clock synchronization, + 1. depends on bounded message delays. +1. **Relation to real time.** There is no clock synchronizaton, which implies that there is **no relation** between the computed block `time` and real time. +1. **Aggregate signatures.** As the `precommit` messages contain the local times, all these `precommit` messages typically differ in the time field, which **prevents** the use of aggregate signatures. + +## Suggested Proposer-Based Time + +### Outline + +An alternative approach to time has been discussed: Rather than having the validators send the time in the `precommit` messages, the proposer in the consensus algorithm sends its time in the `propose` message, and the validators locally check whether the time is OK (by comparing to their local clock). + +This proposed solution adds the requirement of having synchronized clocks, and other implicit assumptions. + +### Comparison of the Suggested Method to the Old One + +1. **Fault tolerance.** Maintained in the suggested protocol. +1. **Effect of faulty validators.** Eliminated in the suggested protocol, + that is, the block `time` can be corrupted only in the extreme case when + `>2/3` of the validators are faulty. +1. **Proposer influence on block time.** The proposer of the next block + has less freedom when choosing the block time. + 1. This scenario is eliminated in the suggested protocol, provided that there are `<1/3` faulty validators. + 1. This scenario is still there. +1. **Liveness.** The liveness of the suggested protocol: + 1. depends on the introduced assumptions on synchronized clocks (see below), + 1. still depends on the message delays (unavoidable). +1. **Relation to real time.** We formalize clock synchronization, and obtain a **well-defined relation** between the block `time` and real time. +1. **Aggregate signatures.** The `precommit` messages free of time, which **allows** for aggregate signatures. + +### Protocol Overview + +#### Proposed Time + +We assume that the field `proposal` in the `PROPOSE` message is a pair `(v, time)`, of the proposed consensus value `v` and the proposed time `time`. + +#### Reception Step + +In the reception step at node `p` at local time `now_p`, upon receiving a message `m`: + +- **if** the message `m` is of type `PROPOSE` and satisfies `now_p - PRECISION < m.time < now_p + PRECISION + MSGDELAY`, then mark the message as `timely`. +(`PRECISION` and `MSGDELAY` being system parameters, see [below](#safety-and-liveness)) + +> after the presentation in the dev session, we realized that different semantics for the reception step is closer aligned to the implementation. Instead of dropping propose messages, we keep all of them, and mark timely ones. + +#### Processing Step + +- Start round + + + + + + + + + + + + +
arXiv paperProposer-based time
+ +```go +function StartRound(round) { + round_p ← round + step_p ← propose + if proposer(h_p, round_p) = p { + + + if validValue_p != nil { + + proposal ← validValue_p + } else { + + proposal ← getValue() + } + broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩ + } else { + schedule OnTimeoutPropose(h_p,round_p) to + be executed after timeoutPropose(round_p) + } +} +``` + + + +```go +function StartRound(round) { + round_p ← round + step_p ← propose + if proposer(h_p, round_p) = p { + // new wait condition + wait until now_p > block time of block h_p - 1 + if validValue_p != nil { + // add "now_p" + proposal ← (validValue_p, now_p) + } else { + // add "now_p" + proposal ← (getValue(), now_p) + } + broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩ + } else { + schedule OnTimeoutPropose(h_p,round_p) to + be executed after timeoutPropose(round_p) + } +} +``` + +
+ +- Rule on lines 28-35 + + + + + + + + + + + + +
arXiv paperProposer-based time
+ +```go +upon timely(⟨PROPOSAL, h_p, round_p, v, vr⟩) + from proposer(h_p, round_p) + AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩ +while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do { + if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) { + + broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩ + } else { + broadcast ⟨PREVOTE, hp, round_p, nil⟩ + } +} +``` + + + +```go +upon timely(⟨PROPOSAL, h_p, round_p, (v, tprop), vr⟩) + from proposer(h_p, round_p) + AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v, tvote)⟩ + while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do { + if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) { + // send hash of v and tprop in PREVOTE message + broadcast ⟨PREVOTE, h_p, round_p, id(v, tprop)⟩ + } else { + broadcast ⟨PREVOTE, hp, round_p, nil⟩ + } + } +``` + +
+ +- Rule on lines 49-54 + + + + + + + + + + + + +
arXiv paperProposer-based time
+ +```go +upon ⟨PROPOSAL, h_p, r, v, ∗⟩ from proposer(h_p, r) + AND 2f + 1 ⟨PRECOMMIT, h_p, r, id(v)⟩ + while decisionp[h_p] = nil do { + if valid(v) { + + decision_p [h_p] = v + h_p ← h_p + 1 + reset lockedRound_p , lockedValue_p, validRound_p and + validValue_p to initial values and empty message log + StartRound(0) + } + } +``` + + + +```go +upon ⟨PROPOSAL, h_p, r, (v,t), ∗⟩ from proposer(h_p, r) + AND 2f + 1 ⟨PRECOMMIT, h_p, r, id(v,t)⟩ + while decisionp[h_p] = nil do { + if valid(v) { + // decide on time too + decision_p [h_p] = (v,t) + h_p ← h_p + 1 + reset lockedRound_p , lockedValue_p, validRound_p and + validValue_p to initial values and empty message log + StartRound(0) + } + } +``` + +
+ +- Other rules are extended in a similar way, or remain unchanged + +### Property Overview + +#### Safety and Liveness + +For safety (Point 1, Point 2, Point 3i) and liveness (Point 4) we need +the following assumptions: + +- There exists a system parameter `PRECISION` such that for any two correct validators `V` and `W`, and at any real-time `t`, their local times `C_V(t)` and `C_W(t)` differ by less than `PRECISION` time units, +i.e., `|C_V(t) - C_W(t)| < PRECISION` +- The message end-to-end delay between a correct proposer and a correct validator (for `PROPOSE` messages) is less than `MSGDELAY`. + +#### Relation to Real-Time + +For analyzing real-time safety (Point 5), we use a system parameter `ACCURACY`, such that for all real-times `t` and all correct validators `V`, we have `| C_V(t) - t | < ACCURACY`. + +> `ACCURACY` is not necessarily visible at the code level. We might even view `ACCURACY` as variable over time. The smaller it is during a consensus instance, the closer the block time will be to real-time. +> +> Note that `PRECISION` and `MSGDELAY` show up in the code. + +### Detailed Specification + +This specification describes the changes needed to be done to the Tendermint consensus algorithm as described in the [arXiv paper][arXiv] and the simplified specification in [TLA+][tlatender], and makes precise the underlying assumptions and the required properties. + +- [Part I - System Model and Properties][sysmodel] +- [Part II - Protocol specification][algorithm] +- [TLA+ Specification][proposertla] + +[arXiv]: https://arxiv.org/abs/1807.04938 + +[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md + +[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md + +[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md + +[algorithm]: ./pbts-algorithm_001_draft.md + +[sysmodel]: ./pbts-sysmodel_001_draft.md + +[main]: ./pbts_001_draft.md + +[proposertla]: ./tla/TendermintPBT_001_draft.tla diff --git a/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla b/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla new file mode 100644 index 00000000000..2bcdecb27b0 --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla @@ -0,0 +1,597 @@ +-------------------- MODULE TendermintPBT_001_draft --------------------------- +(* + A TLA+ specification of a simplified Tendermint consensus, with added clocks + and proposer-based timestamps. This TLA+ specification extends and modifies + the Tendermint TLA+ specification for fork accountability: + https://github.com/tendermint/spec/blob/master/spec/light-client/accountability/TendermintAcc_004_draft.tla + + * Version 1. A preliminary specification. + + Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020. + Ilina Stoilkovska, Josef Widder, Informal Systems, 2021. + *) + +EXTENDS Integers, FiniteSets + +(********************* PROTOCOL PARAMETERS **********************************) +CONSTANTS + Corr, \* the set of correct processes + Faulty, \* the set of Byzantine processes, may be empty + N, \* the total number of processes: correct, defective, and Byzantine + T, \* an upper bound on the number of Byzantine processes + ValidValues, \* the set of valid values, proposed both by correct and faulty + InvalidValues, \* the set of invalid values, never proposed by the correct ones + MaxRound, \* the maximal round number + MaxTimestamp, \* the maximal value of the clock tick + Delay, \* message delay + Precision, \* clock precision: the maximal difference between two local clocks + Accuracy, \* clock accuracy: the maximal difference between a local clock and the real time + Proposer, \* the proposer function from 0..NRounds to 1..N + ClockDrift \* is there clock drift between the local clocks and the global clock + +ASSUME(N = Cardinality(Corr \union Faulty)) + +(*************************** DEFINITIONS ************************************) +AllProcs == Corr \union Faulty \* the set of all processes +Rounds == 0..MaxRound \* the set of potential rounds +Timestamps == 0..MaxTimestamp \* the set of clock ticks +NilRound == -1 \* a special value to denote a nil round, outside of Rounds +NilTimestamp == -1 \* a special value to denote a nil timestamp, outside of Ticks +RoundsOrNil == Rounds \union {NilRound} +Values == ValidValues \union InvalidValues \* the set of all values +NilValue == "None" \* a special value for a nil round, outside of Values +Proposals == Values \X Timestamps +NilProposal == <> +ValuesOrNil == Values \union {NilValue} +Decisions == Values \X Timestamps \X Rounds +NilDecision == <> + + +\* a value hash is modeled as identity +Id(v) == v + +\* The validity predicate +IsValid(v) == v \in ValidValues + +\* the two thresholds that are used in the algorithm +THRESHOLD1 == T + 1 \* at least one process is not faulty +THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T + +Min(S) == CHOOSE x \in S : \A y \in S : x <= y + +Max(S) == CHOOSE x \in S : \A y \in S : y <= x + +(********************* 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 |-> <>, validRound |-> Int, id |-> <>] + +RP == <> + +\* 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({}) + +SetOfRcvProp(S) == S <: {RP} +EmptyRcvProp == SetOfRcvProp({}) + +SetOfProc(S) == S <: {STRING} +EmptyProcSet == SetOfProc({}) + +(********************* PROTOCOL STATE VARIABLES ******************************) +VARIABLES + round, \* a process round number: Corr -> Rounds + localClock, \* a process local clock: Corr -> Ticks + realTime, \* a reference Newtonian real time + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + decision, \* process decision: Corr -> ValuesOrNil + lockedValue, \* a locked value: Corr -> ValuesOrNil + lockedRound, \* a locked round: Corr -> RoundsOrNil + validValue, \* a valid value: Corr -> ValuesOrNil + validRound \* a valid round: Corr -> RoundsOrNil + +\* book-keeping variables +VARIABLES + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + receivedTimelyProposal, \* used to keep track when a process receives a timely PROPOSAL message, {<>} + inspectedProposal, \* used to keep track when a process tries to receive a message, [Rounds -> <>] + evidence, \* the messages that were used by the correct processes to make transitions + action, \* we use this variable to see which action was taken + beginConsensus, \* the minimum of the local clocks in the initial state, Int + endConsensus, \* the local time when a decision is made, [Corr -> Int] + lastBeginConsensus, \* the maximum of the local clocks in the initial state, Int + proposalTime, \* the real time when a proposer proposes in a round, [Rounds -> Int] + proposalReceivedTime \* the real time when a correct process first receives a proposal message in a round, [Rounds -> Int] + +(* to see a type invariant, check TendermintAccInv3 *) + +\* a handy definition used in UNCHANGED +vars == <> + +(********************* PROTOCOL INITIALIZATION ******************************) +FaultyProposals(r) == + SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, + round: {r}, proposal: Proposals, validRound: RoundsOrNil]) + +AllFaultyProposals == + SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, + round: Rounds, proposal: Proposals, validRound: RoundsOrNil]) + +FaultyPrevotes(r) == + SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: {r}, id: Proposals]) + +AllFaultyPrevotes == + SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Proposals]) + +FaultyPrecommits(r) == + SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: {r}, id: Proposals]) + +AllFaultyPrecommits == + SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Proposals]) + +AllProposals == + SetOfMsgs([type: {"PROPOSAL"}, src: AllProcs, + round: Rounds, proposal: Proposals, validRound: RoundsOrNil]) + +RoundProposals(r) == + SetOfMsgs([type: {"PROPOSAL"}, src: AllProcs, + round: {r}, proposal: Proposals, validRound: RoundsOrNil]) + +BenignRoundsInMessages(msgfun) == + \* the message function never contains a message for a wrong round + \A r \in Rounds: + \A m \in msgfun[r]: + r = m.round + +\* The initial states of the protocol. Some faults can be in the system already. +Init == + /\ round = [p \in Corr |-> 0] + /\ \/ /\ ~ClockDrift + /\ localClock \in [Corr -> 0..Accuracy] + \/ /\ ClockDrift + /\ localClock = [p \in Corr |-> 0] + /\ realTime = 0 + /\ step = [p \in Corr |-> "PROPOSE"] + /\ decision = [p \in Corr |-> NilDecision] + /\ lockedValue = [p \in Corr |-> NilValue] + /\ lockedRound = [p \in Corr |-> NilRound] + /\ validValue = [p \in Corr |-> NilValue] + /\ validRound = [p \in Corr |-> NilRound] + /\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals] + /\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes] + /\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits] + /\ receivedTimelyProposal = EmptyRcvProp + /\ inspectedProposal = [r \in Rounds |-> EmptyProcSet] + /\ BenignRoundsInMessages(msgsPropose) + /\ BenignRoundsInMessages(msgsPrevote) + /\ BenignRoundsInMessages(msgsPrecommit) + /\ evidence = EmptyMsgSet + /\ action' = "Init" + /\ beginConsensus = Min({localClock[p] : p \in Corr}) + /\ endConsensus = [p \in Corr |-> NilTimestamp] + /\ lastBeginConsensus = Max({localClock[p] : p \in Corr}) + /\ proposalTime = [r \in Rounds |-> NilTimestamp] + /\ proposalReceivedTime = [r \in Rounds |-> NilTimestamp] + +(************************ MESSAGE PASSING ********************************) +BroadcastProposal(pSrc, pRound, pProposal, pValidRound) == + LET newMsg == + AsMsg([type |-> "PROPOSAL", src |-> pSrc, round |-> pRound, + proposal |-> pProposal, validRound |-> pValidRound]) + IN + msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}] + +BroadcastPrevote(pSrc, pRound, pId) == + LET newMsg == AsMsg([type |-> "PREVOTE", + src |-> pSrc, round |-> pRound, id |-> pId]) + IN + msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}] + +BroadcastPrecommit(pSrc, pRound, pId) == + LET newMsg == AsMsg([type |-> "PRECOMMIT", + src |-> pSrc, round |-> pRound, id |-> pId]) + IN + msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}] + + +(***************************** TIME **************************************) + +\* [PBTS-CLOCK-PRECISION.0] +SynchronizedLocalClocks == + \A p \in Corr : \A q \in Corr : + p /= q => + \/ /\ localClock[p] >= localClock[q] + /\ localClock[p] - localClock[q] < Precision + \/ /\ localClock[p] < localClock[q] + /\ localClock[q] - localClock[p] < Precision + +\* [PBTS-PROPOSE.0] +Proposal(v, t) == + <> + +\* [PBTS-DECISION-ROUND.0] +Decision(v, t, r) == + <> + +(**************** MESSAGE PROCESSING TRANSITIONS *************************) +\* lines 12-13 +StartRound(p, r) == + /\ step[p] /= "DECIDED" \* a decided process does not participate in consensus + /\ round' = [round EXCEPT ![p] = r] + /\ step' = [step EXCEPT ![p] = "PROPOSE"] + +\* lines 14-19, a proposal may be sent later +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 proposal == IF validValue[p] /= NilValue + THEN Proposal(validValue[p], localClock[p]) + ELSE Proposal(v, localClock[p]) IN + + /\ BroadcastProposal(p, round[p], proposal, validRound[p]) + /\ proposalTime' = [proposalTime EXCEPT ![r] = realTime] + /\ UNCHANGED <> + /\ action' = "InsertProposal" + +\* a new action used to filter messages that are not on time +\* [PBTS-RECEPTION-STEP.0] +ReceiveProposal(p) == + \E v \in Values, t \in Timestamps: + /\ LET r == round[p] IN + LET msg == + AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], + round |-> round[p], proposal |-> Proposal(v, t), validRound |-> NilRound]) IN + /\ msg \in msgsPropose[round[p]] + /\ p \notin inspectedProposal[r] + /\ <> \notin receivedTimelyProposal + /\ inspectedProposal' = [inspectedProposal EXCEPT ![r] = @ \union {p}] + /\ \/ /\ localClock[p] - Precision < t + /\ t < localClock[p] + Precision + Delay + /\ receivedTimelyProposal' = receivedTimelyProposal \union {<>} + /\ \/ /\ proposalReceivedTime[r] = NilTimestamp + /\ proposalReceivedTime' = [proposalReceivedTime EXCEPT ![r] = realTime] + \/ /\ proposalReceivedTime[r] /= NilTimestamp + /\ UNCHANGED proposalReceivedTime + \/ /\ \/ localClock[p] - Precision >= t + \/ t >= localClock[p] + Precision + Delay + /\ UNCHANGED <> + /\ UNCHANGED <> + /\ action' = "ReceiveProposal" + +\* lines 22-27 +UponProposalInPropose(p) == + \E v \in Values, t \in Timestamps: + /\ step[p] = "PROPOSE" (* line 22 *) + /\ LET msg == + AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], + round |-> round[p], proposal |-> Proposal(v, t), validRound |-> NilRound]) IN + /\ <> \in receivedTimelyProposal \* updated line 22 + /\ evidence' = {msg} \union evidence + /\ LET mid == (* line 23 *) + IF IsValid(v) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v) + THEN Id(Proposal(v, t)) + ELSE NilProposal + IN + BroadcastPrevote(p, round[p], mid) \* lines 24-26 + /\ step' = [step EXCEPT ![p] = "PREVOTE"] + /\ UNCHANGED <> + /\ action' = "UponProposalInPropose" + +\* lines 28-33 +\* [PBTS-ALG-OLD-PREVOTE.0] +UponProposalInProposeAndPrevote(p) == + \E v \in Values, t1 \in Timestamps, t2 \in Timestamps, 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 |-> Proposal(v, t1), validRound |-> vr]) + IN + /\ <> \in receivedTimelyProposal \* updated line 28 + /\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(Proposal(v, t2)) } IN + /\ Cardinality(PV) >= THRESHOLD2 \* line 28 + /\ evidence' = PV \union {msg} \union evidence + /\ LET mid == (* line 29 *) + IF IsValid(v) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v) + THEN Id(Proposal(v, t1)) + ELSE NilProposal + IN + BroadcastPrevote(p, round[p], mid) \* lines 24-26 + /\ step' = [step EXCEPT ![p] = "PREVOTE"] + /\ UNCHANGED <> + /\ action' = "UponProposalInProposeAndPrevote" + + \* lines 34-35 + lines 61-64 (onTimeoutPrevote) +UponQuorumOfPrevotesAny(p) == + /\ step[p] = "PREVOTE" \* line 34 and 61 + /\ \E MyEvidence \in SUBSET msgsPrevote[round[p]]: + \* find the unique voters in the evidence + 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 + /\ BroadcastPrecommit(p, round[p], NilProposal) + /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] + /\ UNCHANGED <> + /\ action' = "UponQuorumOfPrevotesAny" + +\* lines 36-46 +\* [PBTS-ALG-NEW-PREVOTE.0] +UponProposalInPrevoteOrCommitAndPrevote(p) == + \E v \in ValidValues, t \in Timestamps, vr \in RoundsOrNil: + /\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36 + /\ LET msg == + AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], + round |-> round[p], proposal |-> Proposal(v, t), validRound |-> vr]) IN + /\ <> \in receivedTimelyProposal \* updated line 36 + /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(Proposal(v, t)) } IN + /\ Cardinality(PV) >= THRESHOLD2 \* line 36 + /\ evidence' = PV \union {msg} \union evidence + /\ IF step[p] = "PREVOTE" + THEN \* lines 38-41: + /\ lockedValue' = [lockedValue EXCEPT ![p] = v] + /\ lockedRound' = [lockedRound EXCEPT ![p] = round[p]] + /\ BroadcastPrecommit(p, round[p], Id(Proposal(v, t))) + /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] + ELSE + UNCHANGED <> + \* lines 42-43 + /\ validValue' = [validValue EXCEPT ![p] = v] + /\ validRound' = [validRound EXCEPT ![p] = round[p]] + /\ UNCHANGED <> + /\ action' = "UponProposalInPrevoteOrCommitAndPrevote" + +\* lines 47-48 + 65-67 (onTimeoutPrecommit) +UponQuorumOfPrecommitsAny(p) == + /\ \E MyEvidence \in SUBSET msgsPrecommit[round[p]]: + \* find the unique committers in the evidence + 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 + /\ round[p] + 1 \in Rounds + /\ StartRound(p, round[p] + 1) + /\ UNCHANGED <> + /\ action' = "UponQuorumOfPrecommitsAny" + +\* lines 49-54 +\* [PBTS-ALG-DECIDE.0] +UponProposalInPrecommitNoDecision(p) == + /\ decision[p] = NilDecision \* line 49 + /\ \E v \in ValidValues, t \in Timestamps (* line 50*) , r \in Rounds, vr \in RoundsOrNil: + /\ LET msg == AsMsg([type |-> "PROPOSAL", src |-> Proposer[r], + round |-> r, proposal |-> Proposal(v, t), validRound |-> vr]) IN + /\ msg \in msgsPropose[r] \* line 49 + /\ p \in inspectedProposal[r] + /\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(Proposal(v, t)) } IN + /\ Cardinality(PV) >= THRESHOLD2 \* line 49 + /\ evidence' = PV \union {msg} \union evidence + /\ decision' = [decision EXCEPT ![p] = Decision(v, t, round[p])] \* 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. + /\ endConsensus' = [endConsensus EXCEPT ![p] = localClock[p]] + /\ step' = [step EXCEPT ![p] = "DECIDED"] + /\ UNCHANGED <> + /\ action' = "UponProposalInPrecommitNoDecision" + +\* the actions below are not essential for safety, but added for completeness + +\* lines 20-21 + 57-60 +OnTimeoutPropose(p) == + /\ step[p] = "PROPOSE" + /\ p /= Proposer[round[p]] + /\ BroadcastPrevote(p, round[p], NilProposal) + /\ step' = [step EXCEPT ![p] = "PREVOTE"] + /\ UNCHANGED <> + /\ action' = "OnTimeoutPropose" + +\* lines 44-46 +OnQuorumOfNilPrevotes(p) == + /\ step[p] = "PREVOTE" + /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilProposal) } IN + /\ Cardinality(PV) >= THRESHOLD2 \* line 36 + /\ evidence' = PV \union evidence + /\ BroadcastPrecommit(p, round[p], Id(NilProposal)) + /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] + /\ UNCHANGED <> + /\ 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 + /\ Cardinality(Faster) >= THRESHOLD1 + /\ evidence' = MyEvidence \union evidence + /\ StartRound(p, r) + /\ UNCHANGED <> + /\ action' = "OnRoundCatchup" + + +(********************* PROTOCOL TRANSITIONS ******************************) +\* advance the global clock +AdvanceRealTime == + /\ realTime < MaxTimestamp + /\ realTime' = realTime + 1 + /\ \/ /\ ~ClockDrift + /\ localClock' = [p \in Corr |-> localClock[p] + 1] + \/ /\ ClockDrift + /\ UNCHANGED localClock + /\ UNCHANGED <> + /\ action' = "AdvanceRealTime" + +\* advance the local clock of node p +AdvanceLocalClock(p) == + /\ localClock[p] < MaxTimestamp + /\ localClock' = [localClock EXCEPT ![p] = @ + 1] + /\ UNCHANGED <> + /\ action' = "AdvanceLocalClock" + +\* process timely messages +MessageProcessing(p) == + \* start round + \/ InsertProposal(p) + \* reception step + \/ ReceiveProposal(p) + \* processing step + \/ UponProposalInPropose(p) + \/ UponProposalInProposeAndPrevote(p) + \/ UponQuorumOfPrevotesAny(p) + \/ UponProposalInPrevoteOrCommitAndPrevote(p) + \/ UponQuorumOfPrecommitsAny(p) + \/ UponProposalInPrecommitNoDecision(p) + \* the actions below are not essential for safety, but added for completeness + \/ OnTimeoutPropose(p) + \/ OnQuorumOfNilPrevotes(p) + \/ OnRoundCatchup(p) + +(* + * A system transition. In this specificatiom, the system may eventually deadlock, + * e.g., when all processes decide. This is expected behavior, as we focus on safety. + *) +Next == + \/ AdvanceRealTime + \/ /\ ClockDrift + /\ \E p \in Corr: AdvanceLocalClock(p) + \/ /\ SynchronizedLocalClocks + /\ \E p \in Corr: MessageProcessing(p) + +----------------------------------------------------------------------------- + +(*************************** INVARIANTS *************************************) + +\* [PBTS-INV-AGREEMENT.0] +AgreementOnValue == + \A p, q \in Corr: + /\ decision[p] /= NilDecision + /\ decision[q] /= NilDecision + => \E v \in ValidValues, t1 \in Timestamps, t2 \in Timestamps, r1 \in Rounds, r2 \in Rounds : + /\ decision[p] = Decision(v, t1, r1) + /\ decision[q] = Decision(v, t2, r2) + +\* [PBTS-INV-TIME-AGR.0] +AgreementOnTime == + \A p, q \in Corr: + \A v1 \in ValidValues, v2 \in ValidValues, t1 \in Timestamps, t2 \in Timestamps, r \in Rounds : + /\ decision[p] = Decision(v1, t1, r) + /\ decision[q] = Decision(v2, t2, r) + => t1 = t2 + +\* [PBTS-CONSENSUS-TIME-VALID.0] +ConsensusTimeValid == + \A p \in Corr, t \in Timestamps : + \* if a process decides on v and t + (\E v \in ValidValues, r \in Rounds : decision[p] = Decision(v, t, r)) + \* then + => /\ beginConsensus - Precision <= t + /\ t < endConsensus[p] + Precision + Delay + +\* [PBTS-CONSENSUS-SAFE-VALID-CORR-PROP.0] +ConsensusSafeValidCorrProp == + \A v \in ValidValues, t \in Timestamps : + \* if the proposer in the first round is correct + (/\ Proposer[0] \in Corr + \* and there exists a process that decided on v, t + /\ \E p \in Corr, r \in Rounds : decision[p] = Decision(v, t, r)) + \* then t is between the minimal and maximal initial local time + => /\ beginConsensus <= t + /\ t <= lastBeginConsensus + +\* [PBTS-CONSENSUS-REALTIME-VALID-CORR.0] +ConsensusRealTimeValidCorr == + \A t \in Timestamps, r \in Rounds : + (/\ \E p \in Corr, v \in ValidValues : decision[p] = Decision(v, t, r) + /\ proposalTime[r] /= NilTimestamp) + => /\ proposalTime[r] - Accuracy < t + /\ t < proposalTime[r] + Accuracy + +\* [PBTS-CONSENSUS-REALTIME-VALID.0] +ConsensusRealTimeValid == + \A t \in Timestamps, r \in Rounds : + (\E p \in Corr, v \in ValidValues : decision[p] = Decision(v, t, r)) + => /\ proposalReceivedTime[r] - Accuracy - Precision < t + /\ t < proposalReceivedTime[r] + Accuracy + Precision + Delay + +\* [PBTS-MSG-FAIR.0] +BoundedDelay == + \A r \in Rounds : + (/\ proposalTime[r] /= NilTimestamp + /\ proposalTime[r] + Delay < realTime) + => inspectedProposal[r] = Corr + +\* [PBTS-CONSENSUS-TIME-LIVE.0] +ConsensusTimeLive == + \A r \in Rounds, p \in Corr : + (/\ proposalTime[r] /= NilTimestamp + /\ proposalTime[r] + Delay < realTime + /\ Proposer[r] \in Corr + /\ round[p] <= r) + => \E msg \in RoundProposals(r) : <> \in receivedTimelyProposal + +\* a conjunction of all invariants +Inv == + /\ AgreementOnValue + /\ AgreementOnTime + /\ ConsensusTimeValid + /\ ConsensusSafeValidCorrProp + /\ ConsensusRealTimeValid + /\ ConsensusRealTimeValidCorr + /\ BoundedDelay + +Liveness == + ConsensusTimeLive + +============================================================================= diff --git a/spec/consensus/proposer-selection.md b/spec/consensus/proposer-selection.md new file mode 100644 index 00000000000..3cea3d5cde1 --- /dev/null +++ b/spec/consensus/proposer-selection.md @@ -0,0 +1,323 @@ +--- +order: 3 +--- + +# Proposer Selection Procedure + +This document specifies the Proposer Selection Procedure that is used in Tendermint to choose a round proposer. +As Tendermint is “leader-based protocol”, the proposer selection is critical for its correct functioning. + +At a given block height, the proposer selection algorithm runs with the same validator set at each round . +Between heights, an updated validator set may be specified by the application as part of the ABCIResponses' EndBlock. + +## Requirements for Proposer Selection + +This sections covers the requirements with Rx being mandatory and Ox optional requirements. +The following requirements must be met by the Proposer Selection procedure: + +### R1: Determinism + +Given a validator set `V`, and two honest validators `p` and `q`, for each height `h` and each round `r` the following must hold: + + `proposer_p(h,r) = proposer_q(h,r)` + +where `proposer_p(h,r)` is the proposer returned by the Proposer Selection Procedure at process `p`, at height `h` and round `r`. + +### R2: Fairness + +Given a validator set with total voting power P and a sequence S of elections. In any sub-sequence of S with length C*P, a validator v must be elected as proposer P/VP(v) times, i.e. with frequency: + + f(v) ~ VP(v) / P + +where C is a tolerance factor for validator set changes with following values: + +- C == 1 if there are no validator set changes +- C ~ k when there are validator changes + +*[this needs more work]* + +## Basic Algorithm + +At its core, the proposer selection procedure uses a weighted round-robin algorithm. + +A model that gives a good intuition on how/ why the selection algorithm works and it is fair is that of a priority queue. The validators move ahead in this queue according to their voting power (the higher the voting power the faster a validator moves towards the head of the queue). When the algorithm runs the following happens: + +- all validators move "ahead" according to their powers: for each validator, increase the priority by the voting power +- first in the queue becomes the proposer: select the validator with highest priority +- move the proposer back in the queue: decrease the proposer's priority by the total voting power + +Notation: + +- vset - the validator set +- n - the number of validators +- VP(i) - voting power of validator i +- A(i) - accumulated priority for validator i +- P - total voting power of set +- avg - average of all validator priorities +- prop - proposer + +Simple view at the Selection Algorithm: + +```md + def ProposerSelection (vset): + + // compute priorities and elect proposer + for each validator i in vset: + A(i) += VP(i) + prop = max(A) + A(prop) -= P +``` + +## Stable Set + +Consider the validator set: + +Validator | p1 | p2 +----------|----|--- +VP | 1 | 3 + +Assuming no validator changes, the following table shows the proposer priority computation over a few runs. Four runs of the selection procedure are shown, starting with the 5th the same values are computed. +Each row shows the priority queue and the process place in it. The proposer is the closest to the head, the rightmost validator. As priorities are updated, the validators move right in the queue. The proposer moves left as its priority is reduced after election. + +| Priority Run | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | Alg step | +|----------------|----|----|-------|----|-------|----|----|----|------------------| +| | | | p1,p2 | | | | | | Initialized to 0 | +| run 1 | | | | p1 | | p2 | | | A(i)+=VP(i) | +| | | p2 | | p1 | | | | | A(p2)-= P | +| run 2 | | | | | p1,p2 | | | | A(i)+=VP(i) | +| | p1 | | | | p2 | | | | A(p1)-= P | +| run 3 | | p1 | | | | | | p2 | A(i)+=VP(i) | +| | | p1 | | p2 | | | | | A(p2)-= P | +| run 4 | | | p1 | | | | p2 | | A(i)+=VP(i) | +| | | | p1,p2 | | | | | | A(p2)-= P | + +It can be shown that: + +- At the end of each run k+1 the sum of the priorities is the same as at end of run k. If a new set's priorities are initialized to 0 then the sum of priorities will be 0 at each run while there are no changes. +- The max distance between priorites is (n-1) *P.*[formal proof not finished]* + +## Validator Set Changes + +Between proposer selection runs the validator set may change. Some changes have implications on the proposer election. + +### Voting Power Change + +Consider again the earlier example and assume that the voting power of p1 is changed to 4: + +Validator | p1 | p2 +----------|----|--- +VP | 4 | 3 + +Let's also assume that before this change the proposer priorites were as shown in first row (last run). As it can be seen, the selection could run again, without changes, as before. + +| Priority Run | -2 | -1 | 0 | 1 | 2 | Comment | +|----------------|----|----|---|----|----|-------------------| +| last run | | p2 | | p1 | | __update VP(p1)__ | +| next run | | | | | p2 | A(i)+=VP(i) | +| | p1 | | | | p2 | A(p1)-= P | + +However, when a validator changes power from a high to a low value, some other validator remain far back in the queue for a long time. This scenario is considered again in the Proposer Priority Range section. + +As before: + +- At the end of each run k+1 the sum of the priorities is the same as at run k. +- The max distance between priorites is (n-1) * P. + +### Validator Removal + +Consider a new example with set: + +Validator | p1 | p2 | p3 +----------|----|----|--- +VP | 1 | 2 | 3 + +Let's assume that after the last run the proposer priorities were as shown in first row with their sum being 0. After p2 is removed, at the end of next proposer selection run (penultimate row) the sum of priorities is -2 (minus the priority of the removed process). + +The procedure could continue without modifications. However, after a sufficiently large number of modifications in validator set, the priority values would migrate towards maximum or minimum allowed values causing truncations due to overflow detection. +For this reason, the selection procedure adds another __new step__ that centers the current priority values such that the priority sum remains close to 0. + +| Priority Run | -3 | -2 | -1 | 0 | 1 | 2 | 4 | Comment | +|----------------|----|----|----|---|----|----|---|-----------------------| +| last run | p3 | | | | p1 | p2 | | __remove p2__ | +| nextrun | | | | | | | | | +| __new step__ | | p3 | | | | p1 | | A(i) -= avg, avg = -1 | +| | | | | | p3 | p1 | | A(i)+=VP(i) | +| | | | p1 | | p3 | | | A(p1)-= P | + +The modified selection algorithm is: + +```md + def ProposerSelection (vset): + + // center priorities around zero + avg = sum(A(i) for i in vset)/len(vset) + for each validator i in vset: + A(i) -= avg + + // compute priorities and elect proposer + for each validator i in vset: + A(i) += VP(i) + prop = max(A) + A(prop) -= P +``` + +Observations: + +- The sum of priorities is now close to 0. Due to integer division the sum is an integer in (-n, n), where n is the number of validators. + +### New Validator + +When a new validator is added, same problem as the one described for removal appears, the sum of priorities in the new set is not zero. This is fixed with the centering step introduced above. + +One other issue that needs to be addressed is the following. A validator V that has just been elected is moved to the end of the queue. If the validator set is large and/ or other validators have significantly higher power, V will have to wait many runs to be elected. If V removes and re-adds itself to the set, it would make a significant (albeit unfair) "jump" ahead in the queue. + +In order to prevent this, when a new validator is added, its initial priority is set to: + +```md + A(V) = -1.125 * P +``` + +where P is the total voting power of the set including V. + +Curent implementation uses the penalty factor of 1.125 because it provides a small punishment that is efficient to calculate. See [here](https://github.com/tendermint/tendermint/pull/2785#discussion_r235038971) for more details. + +If we consider the validator set where p3 has just been added: + +Validator | p1 | p2 | p3 +----------|----|----|--- +VP | 1 | 3 | 8 + +then p3 will start with proposer priority: + +```md + A(p3) = -1.125 * (1 + 3 + 8) ~ -13 +``` + +Note that since current computation uses integer division there is penalty loss when sum of the voting power is less than 8. + +In the next run, p3 will still be ahead in the queue, elected as proposer and moved back in the queue. + +| Priority Run | -13 | -9 | -5 | -2 | -1 | 0 | 1 | 2 | 5 | 6 | 7 | Alg step | +|----------------|-----|----|----|----|----|---|---|----|----|----|----|-----------------------| +| last run | | | | p2 | | | | p1 | | | | __add p3__ | +| | p3 | | | p2 | | | | p1 | | | | A(p3) = -4 | +| next run | | p3 | | | | | | p2 | | p1 | | A(i) -= avg, avg = -4 | +| | | | | | p3 | | | | p2 | | p1 | A(i)+=VP(i) | +| | | | p1 | | p3 | | | | p2 | | | A(p1)-=P | + +## Proposer Priority Range + +With the introduction of centering, some interesting cases occur. Low power validators that bind early in a set that includes high power validator(s) benefit from subsequent additions to the set. This is because these early validators run through more right shift operations during centering, operations that increase their priority. + +As an example, consider the set where p2 is added after p1, with priority -1.125 * 80k = -90k. After the selection procedure runs once: + +Validator | p1 | p2 | Comment +----------|------|------|------------------ +VP | 80k | 10 | +A | 0 | -90k | __added p2__ +A | -45k | 45k | __run selection__ + +Then execute the following steps: + +1. Add a new validator p3: + + Validator | p1 | p2 | p3 + ----------|-----|----|--- + VP | 80k | 10 | 10 + +2. Run selection once. The notation '..p'/'p..' means very small deviations compared to column priority. + + | Priority Run | -90k.. | -60k | -45k | -15k | 0 | 45k | 75k | 155k | Comment | + |---------------|--------|------|------|------|---|-----|-----|------|--------------| + | last run | p3 | | p2 | | | p1 | | | __added p3__ | + | next run + | *right_shift*| | p3 | | p2 | | | p1 | | A(i) -= avg,avg=-30k + | | | ..p3| | ..p2| | | | p1 | A(i)+=VP(i) + | | | ..p3| | ..p2| | | p1.. | | A(p1)-=P, P=80k+20 + +3. Remove p1 and run selection once: + + Validator | p3 | p2 | Comment + ----------|--------|-------|------------------ + VP | 10 | 10 | + A | -60k | -15k | + A | -22.5k | 22.5k | __run selection__ + +At this point, while the total voting power is 20, the distance between priorities is 45k. It will take 4500 runs for p3 to catch up with p2. + +In order to prevent these types of scenarios, the selection algorithm performs scaling of priorities such that the difference between min and max values is smaller than two times the total voting power. + +The modified selection algorithm is: + +```md + def ProposerSelection (vset): + + // scale the priority values + diff = max(A)-min(A) + threshold = 2 * P + if diff > threshold: + scale = diff/threshold + for each validator i in vset: + A(i) = A(i)/scale + + // center priorities around zero + avg = sum(A(i) for i in vset)/len(vset) + for each validator i in vset: + A(i) -= avg + + // compute priorities and elect proposer + for each validator i in vset: + A(i) += VP(i) + prop = max(A) + A(prop) -= P +``` + +Observations: + +- With this modification, the maximum distance between priorites becomes 2 * P. + +Note also that even during steady state the priority range may increase beyond 2 * P. The scaling introduced here helps to keep the range bounded. + +## Wrinkles + +### Validator Power Overflow Conditions + +The validator voting power is a positive number stored as an int64. When a validator is added the `1.125 * P` computation must not overflow. As a consequence the code handling validator updates (add and update) checks for overflow conditions making sure the total voting power is never larger than the largest int64 `MAX`, with the property that `1.125 * MAX` is still in the bounds of int64. Fatal error is return when overflow condition is detected. + +### Proposer Priority Overflow/ Underflow Handling + +The proposer priority is stored as an int64. The selection algorithm performs additions and subtractions to these values and in the case of overflows and underflows it limits the values to: + +```go + MaxInt64 = 1 << 63 - 1 + MinInt64 = -1 << 63 +``` + +## Requirement Fulfillment Claims + +__[R1]__ + +The proposer algorithm is deterministic giving consistent results across executions with same transactions and validator set modifications. +[WIP - needs more detail] + +__[R2]__ + +Given a set of processes with the total voting power P, during a sequence of elections of length P, the number of times any process is selected as proposer is equal to its voting power. The sequence of the P proposers then repeats. If we consider the validator set: + +Validator | p1 | p2 +----------|----|--- +VP | 1 | 3 + +With no other changes to the validator set, the current implementation of proposer selection generates the sequence: +`p2, p1, p2, p2, p2, p1, p2, p2,...` or [`p2, p1, p2, p2`]* +A sequence that starts with any circular permutation of the [`p2, p1, p2, p2`] sub-sequence would also provide the same degree of fairness. In fact these circular permutations show in the sliding window (over the generated sequence) of size equal to the length of the sub-sequence. + +Assigning priorities to each validator based on the voting power and updating them at each run ensures the fairness of the proposer selection. In addition, every time a validator is elected as proposer its priority is decreased with the total voting power. + +Intuitively, a process v jumps ahead in the queue at most (max(A) - min(A))/VP(v) times until it reaches the head and is elected. The frequency is then: + +```md + f(v) ~ VP(v)/(max(A)-min(A)) = 1/k * VP(v)/P +``` + +For current implementation, this means v should be proposer at least VP(v) times out of k * P runs, with scaling factor k=2. diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md new file mode 100644 index 00000000000..aa79ba19298 --- /dev/null +++ b/spec/consensus/readme.md @@ -0,0 +1,32 @@ +--- +order: 1 +parent: + title: Consensus + order: 4 +--- + +# Consensus + +Specification of the Tendermint consensus protocol. + +## Contents + +- [Consensus Paper](./consensus-paper) - Latex paper on + [arxiv](https://arxiv.org/abs/1807.04938) describing the + core Tendermint consensus state machine with proofs of safety and termination. +- [BFT Time](./bft-time.md) - How the timestamp in a Tendermint + block header is computed in a Byzantine Fault Tolerant manner +- [Creating Proposal](./creating-proposal.md) - How a proposer + creates a block proposal for consensus +- [Light Client Protocol](./light-client) - A protocol for light weight consensus + verification and syncing to the latest state +- [Signing](./signing.md) - Rules for cryptographic signatures + produced by validators. +- [Write Ahead Log](./wal.md) - Write ahead log used by the + consensus state machine to recover from crashes. + +The protocol used to gossip consensus messages between peers, which is critical +for liveness, is described in the [reactors section](../reactors/consensus/consensus.md). + +There is also a [stale markdown description](consensus.md) of the consensus state machine +(TODO update this). diff --git a/spec/consensus/signing.md b/spec/consensus/signing.md new file mode 100644 index 00000000000..907a5a01af5 --- /dev/null +++ b/spec/consensus/signing.md @@ -0,0 +1,229 @@ +# Validator Signing + +Here we specify the rules for validating a proposal and vote before signing. +First we include some general notes on validating data structures common to both types. +We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining. + +## SignedMsgType + +The `SignedMsgType` is a single byte that refers to the type of the message +being signed. It is defined in Go as follows: + +```go +// SignedMsgType is a type of signed message in the consensus. +type SignedMsgType byte + +const ( + // Votes + PrevoteType SignedMsgType = 0x01 + PrecommitType SignedMsgType = 0x02 + + // Proposals + ProposalType SignedMsgType = 0x20 +) +``` + +All signed messages must correspond to one of these types. + +## Timestamp + +Timestamp validation is subtle and there are currently no bounds placed on the +timestamp included in a proposal or vote. It is expected that validators will honestly +report their local clock time. The median of all timestamps +included in a commit is used as the timestamp for the next block height. + +Timestamps are expected to be strictly monotonic for a given validator, though +this is not currently enforced. + +## ChainID + +ChainID is an unstructured string with a max length of 50-bytes. +In the future, the ChainID may become structured, and may take on longer lengths. +For now, it is recommended that signers be configured for a particular ChainID, +and to only sign votes and proposals corresponding to that ChainID. + +## BlockID + +BlockID is the structure used to represent the block: + +```go +type BlockID struct { + Hash []byte + PartsHeader PartSetHeader +} + +type PartSetHeader struct { + Hash []byte + Total int +} +``` + +To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one. +We introduce two methods, `BlockID.IsZero()` and `BlockID.IsComplete()` for these cases, respectively. + +`BlockID.IsZero()` returns true for BlockID `b` if each of the following +are true: + +```go +b.Hash == nil +b.PartsHeader.Total == 0 +b.PartsHeader.Hash == nil +``` + +`BlockID.IsComplete()` returns true for BlockID `b` if each of the following +are true: + +```go +len(b.Hash) == 32 +b.PartsHeader.Total > 0 +len(b.PartsHeader.Hash) == 32 +``` + +## Proposals + +The structure of a proposal for signing looks like: + +```go +type CanonicalProposal struct { + Type SignedMsgType // type alias for byte + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + POLRound int64 `binary:"fixed64"` + BlockID BlockID + Timestamp time.Time + ChainID string +} +``` + +A proposal is valid if each of the following lines evaluates to true for proposal `p`: + +```go +p.Type == 0x20 +p.Height > 0 +p.Round >= 0 +p.POLRound >= -1 +p.BlockID.IsComplete() +``` + +In other words, a proposal is valid for signing if it contains the type of a Proposal +(0x20), has a positive, non-zero height, a +non-negative round, a POLRound not less than -1, and a complete BlockID. + +## Votes + +The structure of a vote for signing looks like: + +```go +type CanonicalVote struct { + Type SignedMsgType // type alias for byte + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + BlockID BlockID + Timestamp time.Time + ChainID string +} +``` + +A vote is valid if each of the following lines evaluates to true for vote `v`: + +```go +v.Type == 0x1 || v.Type == 0x2 +v.Height > 0 +v.Round >= 0 +v.BlockID.IsZero() || v.BlockID.IsComplete() +``` + +In other words, a vote is valid for signing if it contains the type of a Prevote +or Precommit (0x1 or 0x2, respectively), has a positive, non-zero height, a +non-negative round, and an empty or valid BlockID. + +## Invalid Votes and Proposals + +Votes and proposals which do not satisfy the above rules are considered invalid. +Peers gossipping invalid votes and proposals may be disconnected from other peers on the network. +Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail +these basic validation rules. + +## Double Signing + +Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating". +Tendermint has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished +by the application. Note Tendermint does not currently handle evidence of conflciting proposals, though it may in the future. + +### State + +To prevent such double signing, signers must track the height, round, and type of the last message signed. +Assume the signer keeps the following state, `s`: + +```go +type LastSigned struct { + Height int64 + Round int64 + Type SignedMsgType // byte +} +``` + +After signing a vote or proposal `m`, the signer sets: + +```go +s.Height = m.Height +s.Round = m.Round +s.Type = m.Type +``` + +### Proposals + +A signer should only sign a proposal `p` if any of the following lines are true: + +```go +p.Height > s.Height +p.Height == s.Height && p.Round > s.Round +``` + +In other words, a proposal should only be signed if it's at a higher height, or a higher round for the same height. +Once a proposal or vote has been signed for a given height and round, a proposal should never be signed for the same height and round. + +### Votes + +A signer should only sign a vote `v` if any of the following lines are true: + +```go +v.Height > s.Height +v.Height == s.Height && v.Round > s.Round +v.Height == s.Height && v.Round == s.Round && v.Step == 0x1 && s.Step == 0x20 +v.Height == s.Height && v.Round == s.Round && v.Step == 0x2 && s.Step != 0x2 +``` + +In other words, a vote should only be signed if it's: + +- at a higher height +- at a higher round for the same height +- a prevote for the same height and round where we haven't signed a prevote or precommit (but have signed a proposal) +- a precommit for the same height and round where we haven't signed a precommit (but have signed a proposal and/or a prevote) + +This means that once a validator signs a prevote for a given height and round, the only other message it can sign for that height and round is a precommit. +And once a validator signs a precommit for a given height and round, it must not sign any other message for that same height and round. + +Note this includes votes for `nil`, ie. where `BlockID.IsZero()` is true. If a +signer has already signed a vote where `BlockID.IsZero()` is true, it cannot +sign another vote with the same type for the same height and round where +`BlockID.IsComplete()` is true. Thus only a single vote of a particular type +(ie. 0x01 or 0x02) can be signed for the same height and round. + +### Other Rules + +According to the rules of Tendermint consensus, once a validator precommits for +a block, they become "locked" on that block, which means they can't prevote for +another block unless they see sufficient justification (ie. a polka from a +higher round). For more details, see the [consensus +spec](https://arxiv.org/abs/1807.04938). + +Violating this rule is known as "amnesia". In contrast to equivocation, +which is easy to detect, amnesia is difficult to detect without access to votes +from all the validators, as this is what constitutes the justification for +"unlocking". Hence, amnesia is not punished within the protocol, and cannot +easily be prevented by a signer. If enough validators simultaneously commit an +amnesia attack, they may cause a fork of the blockchain, at which point an +off-chain protocol must be engaged to collect votes from all the validators and +determine who misbehaved. For more details, see [fork +detection](https://github.com/tendermint/tendermint/pull/3978). diff --git a/spec/consensus/wal.md b/spec/consensus/wal.md new file mode 100644 index 00000000000..95d1bad126e --- /dev/null +++ b/spec/consensus/wal.md @@ -0,0 +1,32 @@ +# WAL + +Consensus module writes every message to the WAL (write-ahead log). + +It also issues fsync syscall through +[File#Sync](https://golang.org/pkg/os/#File.Sync) for messages signed by this +node (to prevent double signing). + +Under the hood, it uses +[autofile.Group](https://godoc.org/github.com/tendermint/tmlibs/autofile#Group), +which rotates files when those get too big (> 10MB). + +The total maximum size is 1GB. We only need the latest block and the block before it, +but if the former is dragging on across many rounds, we want all those rounds. + +## Replay + +Consensus module will replay all the messages of the last height written to WAL +before a crash (if such occurs). + +The private validator may try to sign messages during replay because it runs +somewhat autonomously and does not know about replay process. + +For example, if we got all the way to precommit in the WAL and then crash, +after we replay the proposal message, the private validator will try to sign a +prevote. But it will fail. That's ok because we’ll see the prevote later in the +WAL. Then it will go to precommit, and that time it will work because the +private validator contains the `LastSignBytes` and then we’ll replay the +precommit from the WAL. + +Make sure to read about [WAL corruption](https://github.com/tendermint/tendermint/blob/master/docs/tendermint-core/running-in-production.md#wal-corruption) +and recovery strategies. diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md new file mode 100644 index 00000000000..87bea298f4d --- /dev/null +++ b/spec/core/data_structures.md @@ -0,0 +1,456 @@ +# Data Structures + +Here we describe the data structures in the Tendermint blockchain and the rules for validating them. + +The Tendermint blockchains consists of a short list of data types: + +- [Data Structures](#data-structures) + - [Block](#block) + - [Execution](#execution) + - [Header](#header) + - [Version](#version) + - [BlockID](#blockid) + - [PartSetHeader](#partsetheader) + - [Part](#part) + - [Time](#time) + - [Data](#data) + - [Commit](#commit) + - [CommitSig](#commitsig) + - [BlockIDFlag](#blockidflag) + - [Vote](#vote) + - [CanonicalVote](#canonicalvote) + - [Proposal](#proposal) + - [SignedMsgType](#signedmsgtype) + - [Signature](#signature) + - [EvidenceList](#evidencelist) + - [Evidence](#evidence) + - [DuplicateVoteEvidence](#duplicatevoteevidence) + - [LightClientAttackEvidence](#lightclientattackevidence) + - [LightBlock](#lightblock) + - [SignedHeader](#signedheader) + - [ValidatorSet](#validatorset) + - [Validator](#validator) + - [Address](#address) + - [ConsensusParams](#consensusparams) + - [BlockParams](#blockparams) + - [EvidenceParams](#evidenceparams) + - [ValidatorParams](#validatorparams) + - [VersionParams](#versionparams) + - [Proof](#proof) + + +## Block + +A block consists of a header, transactions, votes (the commit), +and a list of evidence of malfeasance (ie. signing conflicting votes). + +| Name | Type | Description | Validation | +|--------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| +| Header | [Header](#header) | Header corresponding to the block. This field contains information used throughout consensus and other areas of the protocol. To find out what it contains, visit [header] (#header) | Must adhere to the validation rules of [header](#header) | +| Data | [Data](#data) | Data contains a list of transactions. The contents of the transaction is unknown to Tendermint. | This field can be empty or populated, but no validation is performed. Applications can perform validation on individual transactions prior to block creation using [checkTx](../abci/abci.md#checktx). +| Evidence | [EvidenceList](#evidence_list) | Evidence contains a list of infractions committed by validators. | Can be empty, but when populated the validations rules from [evidenceList](#evidence_list) apply | +| LastCommit | [Commit](#commit) | `LastCommit` includes one vote for every validator. All votes must either be for the previous block, nil or absent. If a vote is for the previous block it must have a valid signature from the corresponding validator. The sum of the voting power of the validators that voted must be greater than 2/3 of the total voting power of the complete validator set. The number of votes in a commit is limited to 10000 (see `types.MaxVotesCount`). | Must be empty for the initial height and must adhere to the validation rules of [commit](#commit). | + +## Execution + +Once a block is validated, it can be executed against the state. + +The state follows this recursive equation: + +```go +state(initialHeight) = InitialState +state(h+1) <- Execute(state(h), ABCIApp, block(h)) +``` + +where `InitialState` includes the initial consensus parameters and validator set, +and `ABCIApp` is an ABCI application that can return results and changes to the validator +set (TODO). Execute is defined as: + +```go +func Execute(s State, app ABCIApp, block Block) State { + // Fuction ApplyBlock executes block of transactions against the app and returns the new root hash of the app state, + // modifications to the validator set and the changes of the consensus parameters. + AppHash, ValidatorChanges, ConsensusParamChanges := app.ApplyBlock(block) + + nextConsensusParams := UpdateConsensusParams(state.ConsensusParams, ConsensusParamChanges) + return State{ + ChainID: state.ChainID, + InitialHeight: state.InitialHeight, + LastResults: abciResponses.DeliverTxResults, + AppHash: AppHash, + InitialHeight: state.InitialHeight, + LastValidators: state.Validators, + Validators: state.NextValidators, + NextValidators: UpdateValidators(state.NextValidators, ValidatorChanges), + ConsensusParams: nextConsensusParams, + Version: { + Consensus: { + AppVersion: nextConsensusParams.Version.AppVersion, + }, + }, + } +} +``` + +Validating a new block is first done prior to the `prevote`, `precommit` & `finalizeCommit` stages. + +The steps to validate a new block are: + +- Check the validity rules of the block and its fields. +- Check the versions (Block & App) are the same as in local state. +- Check the chainID's match. +- Check the height is correct. +- Check the `LastBlockID` corresponds to BlockID currently in state. +- Check the hashes in the header match those in state. +- Verify the LastCommit against state, this step is skipped for the initial height. + - This is where checking the signatures correspond to the correct block will be made. +- Make sure the proposer is part of the validator set. +- Validate bock time. + - Make sure the new blocks time is after the previous blocks time. + - Calculate the medianTime and check it against the blocks time. + - If the blocks height is the initial height then check if it matches the genesis time. +- Validate the evidence in the block. Note: Evidence can be empty + +## Header + +A block header contains metadata about the block and about the consensus, as well as commitments to +the data in the current block, the previous block, and the results returned by the application: + +| Name | Type | Description | Validation | +|-------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Version | [Version](#version) | Version defines the application and protocol version being used. | Must adhere to the validation rules of [Version](#version) | +| ChainID | String | ChainID is the ID of the chain. This must be unique to your chain. | ChainID must be less than 50 bytes. | +| Height | uint64 | Height is the height for this header. | Must be > 0, >= initialHeight, and == previous Height+1 | +| Time | [Time](#time) | The timestamp is equal to the weighted median of validators present in the last commit. Read more on time in the [BFT-time section](../consensus/bft-time.md). Note: the timestamp of a vote must be greater by at least one millisecond than that of the block being voted on. | Time must be >= previous header timestamp + consensus parameters TimeIotaMs. The timestamp of the first block must be equal to the genesis time (since there's no votes to compute the median). | +| LastBlockID | [BlockID](#blockid) | BlockID of the previous block. | Must adhere to the validation rules of [blockID](#blockid). The first block has `block.Header.LastBlockID == BlockID{}`. | +| LastCommitHash | slice of bytes (`[]byte`) | MerkleRoot of the lastCommit's signatures. The signatures represent the validators that committed to the last block. The first block has an empty slices of bytes for the hash. | Must be of length 32 | +| DataHash | slice of bytes (`[]byte`) | MerkleRoot of the hash of transactions. **Note**: The transactions are hashed before being included in the merkle tree, the leaves of the Merkle tree are the hashes, not the transactions themselves. | Must be of length 32 | +| ValidatorHash | slice of bytes (`[]byte`) | MerkleRoot of the current validator set. The validators are first sorted by voting power (descending), then by address (ascending) prior to computing the MerkleRoot. | Must be of length 32 | +| NextValidatorHash | slice of bytes (`[]byte`) | MerkleRoot of the next validator set. The validators are first sorted by voting power (descending), then by address (ascending) prior to computing the MerkleRoot. | Must be of length 32 | +| ConsensusHash | slice of bytes (`[]byte`) | Hash of the protobuf encoded consensus parameters. | Must be of length 32 | +| AppHash | slice of bytes (`[]byte`) | Arbitrary byte array returned by the application after executing and commiting the previous block. It serves as the basis for validating any merkle proofs that comes from the ABCI application and represents the state of the actual application rather than the state of the blockchain itself. The first block's `block.Header.AppHash` is given by `ResponseInitChain.app_hash`. | This hash is determined by the application, Tendermint can not perform validation on it. | +| LastResultHash | slice of bytes (`[]byte`) | `LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`,`Info`, `Codespace` and `Events` fields are ignored). | Must be of length 32. The first block has `block.Header.ResultsHash == MerkleRoot(nil)`, i.e. the hash of an empty input, for RFC-6962 conformance. | +| EvidenceHash | slice of bytes (`[]byte`) | MerkleRoot of the evidence of Byzantine behaviour included in this block. | Must be of length 32 | +| ProposerAddress | slice of bytes (`[]byte`) | Address of the original proposer of the block. Validator must be in the current validatorSet. | Must be of length 20 | + +## Version + +NOTE: that this is more specifically the consensus version and doesn't include information like the +P2P Version. (TODO: we should write a comprehensive document about +versioning that this can refer to) + +| Name | type | Description | Validation | +|-------|--------|-----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| +| Block | uint64 | This number represents the version of the block protocol and must be the same throughout an operational network | Must be equal to protocol version being used in a network (`block.Version.Block == state.Version.Consensus.Block`) | +| App | uint64 | App version is decided on by the application. Read [here](../abci/abci.md#info) | `block.Version.App == state.Version.Consensus.App` | + +## BlockID + +The `BlockID` contains two distinct Merkle roots of the block. The `BlockID` includes these two hashes, as well as the number of parts (ie. `len(MakeParts(block))`) + +| Name | Type | Description | Validation | +|---------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------| +| Hash | slice of bytes (`[]byte`) | MerkleRoot of all the fields in the header (ie. `MerkleRoot(header)`. | hash must be of length 32 | +| PartSetHeader | [PartSetHeader](#PartSetHeader) | Used for secure gossiping of the block during consensus, is the MerkleRoot of the complete serialized block cut into parts (ie. `MerkleRoot(MakeParts(block))`). | Must adhere to the validation rules of [PartSetHeader](#PartSetHeader) | + +See [MerkleRoot](./encoding.md#MerkleRoot) for details. + +## PartSetHeader + +| Name | Type | Description | Validation | +|-------|---------------------------|-----------------------------------|----------------------| +| Total | int32 | Total amount of parts for a block | Must be > 0 | +| Hash | slice of bytes (`[]byte`) | MerkleRoot of a serialized block | Must be of length 32 | + +## Part + +Part defines a part of a block. In Tendermint blocks are broken into `parts` for gossip. + +| Name | Type | Description | Validation | +|-------|-----------------|-----------------------------------|----------------------| +| index | int32 | Total amount of parts for a block | Must be > 0 | +| bytes | bytes | MerkleRoot of a serialized block | Must be of length 32 | +| proof | [Proof](#proof) | MerkleRoot of a serialized block | Must be of length 32 | + +## Time + +Tendermint uses the [Google.Protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) +format, which uses two integers, one 64 bit integer for Seconds and a 32 bit integer for Nanoseconds. + +## Data + +Data is just a wrapper for a list of transactions, where transactions are arbitrary byte arrays: + +| Name | Type | Description | Validation | +|------|----------------------------|------------------------|-----------------------------------------------------------------------------| +| Txs | Matrix of bytes ([][]byte) | Slice of transactions. | Validation does not occur on this field, this data is unknown to Tendermint | + +## Commit + +Commit is a simple wrapper for a list of signatures, with one for each validator. It also contains the relevant BlockID, height and round: + +| Name | Type | Description | Validation | +|------------|----------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| Height | uint64 | Height at which this commit was created. | Must be > 0 | +| Round | int32 | Round that the commit corresponds to. | Must be > 0 | +| BlockID | [BlockID](#blockid) | The blockID of the corresponding block. | Must adhere to the validation rules of [BlockID](#blockid). | +| Signatures | Array of [CommitSig](#commitsig) | Array of commit signatures that correspond to current validator set. | Length of signatures must be > 0 and adhere to the validation of each individual [Commitsig](#commitsig) | + +## CommitSig + +`CommitSig` represents a signature of a validator, who has voted either for nil, +a particular `BlockID` or was absent. It's a part of the `Commit` and can be used +to reconstruct the vote set given the validator set. + +| Name | Type | Description | Validation | +|------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| +| BlockIDFlag | [BlockIDFlag](#blockidflag) | Represents the validators participation in consensus: Either voted for the block that received the majority, voted for another block, voted nil or did not vote | Must be one of the fields in the [BlockIDFlag](#blockidflag) enum | +| ValidatorAddress | [Address](#address) | Address of the validator | Must be of length 20 | +| Timestamp | [Time](#time) | This field will vary from `CommitSig` to `CommitSig`. It represents the timestamp of the validator. | [Time](#time) | +| Signature | [Signature](#signature) | Signature corresponding to the validators participation in consensus. | The length of the signature must be > 0 and < than 64 | + +NOTE: `ValidatorAddress` and `Timestamp` fields may be removed in the future +(see [ADR-25](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-025-commit.md)). + +## BlockIDFlag + +BlockIDFlag represents which BlockID the [signature](#commitsig) is for. + +```go +enum BlockIDFlag { + BLOCK_ID_FLAG_UNKNOWN = 0; + BLOCK_ID_FLAG_ABSENT = 1; // signatures for other blocks are also considered absent + BLOCK_ID_FLAG_COMMIT = 2; + BLOCK_ID_FLAG_NIL = 3; +} +``` + +## Vote + +A vote is a signed message from a validator for a particular block. +The vote includes information about the validator signing it. When stored in the blockchain or propagated over the network, votes are encoded in Protobuf. + +| Name | Type | Description | Validation | +|------------------|---------------------------------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| +| Type | [SignedMsgType](#signedmsgtype) | Either prevote or precommit. [SignedMsgType](#signedmsgtype) | A Vote is valid if its corresponding fields are included in the enum [signedMsgType](#signedmsgtype) | +| Height | uint64 | Height for which this vote was created for | Must be > 0 | +| Round | int32 | Round that the commit corresponds to. | Must be > 0 | +| BlockID | [BlockID](#blockid) | The blockID of the corresponding block. | [BlockID](#blockid) | +| Timestamp | [Time](#Time) | Timestamp represents the time at which a validator signed. | [Time](#time) | +| ValidatorAddress | slice of bytes (`[]byte`) | Address of the validator | Length must be equal to 20 | +| ValidatorIndex | int32 | Index at a specific block height that corresponds to the Index of the validator in the set. | must be > 0 | +| Signature | slice of bytes (`[]byte`) | Signature by the validator if they participated in consensus for the associated bock. | Length of signature must be > 0 and < 64 | + +## CanonicalVote + +CanonicalVote is for validator signing. This type will not be present in a block. Votes are represented via `CanonicalVote` and also encoded using protobuf via `type.SignBytes` which includes the `ChainID`, and uses a different ordering of +the fields. + +```proto +message CanonicalVote { + SignedMsgType type = 1; + fixed64 height = 2; + sfixed64 round = 3; + CanonicalBlockID block_id = 4; + google.protobuf.Timestamp timestamp = 5; + string chain_id = 6; +} +``` + +For signing, votes are represented via [`CanonicalVote`](#canonicalvote) and also encoded using protobuf via +`type.SignBytes` which includes the `ChainID`, and uses a different ordering of +the fields. + +We define a method `Verify` that returns `true` if the signature verifies against the pubkey for the `SignBytes` +using the given ChainID: + +```go +func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { + if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) { + return ErrVoteInvalidValidatorAddress + } + + if !pubKey.VerifyBytes(types.VoteSignBytes(chainID), vote.Signature) { + return ErrVoteInvalidSignature + } + return nil +} +``` + +## Proposal + +Proposal contains height and round for which this proposal is made, BlockID as a unique identifier +of proposed block, timestamp, and POLRound (a so-called Proof-of-Lock (POL) round) that is needed for +termination of the consensus. If POLRound >= 0, then BlockID corresponds to the block that +is locked in POLRound. The message is signed by the validator private key. + +| Name | Type | Description | Validation | +|-----------|---------------------------------|---------------------------------------------------------------------------------------|---------------------------------------------------------| +| Type | [SignedMsgType](#signedmsgtype) | Represents a Proposal [SignedMsgType](#signedmsgtype) | Must be `ProposalType` [signedMsgType](#signedmsgtype) | +| Height | uint64 | Height for which this vote was created for | Must be > 0 | +| Round | int32 | Round that the commit corresponds to. | Must be > 0 | +| POLRound | int64 | Proof of lock | Must be > 0 | +| BlockID | [BlockID](#blockid) | The blockID of the corresponding block. | [BlockID](#blockid) | +| Timestamp | [Time](#Time) | Timestamp represents the time at which a validator signed. | [Time](#time) | +| Signature | slice of bytes (`[]byte`) | Signature by the validator if they participated in consensus for the associated bock. | Length of signature must be > 0 and < 64 | + +## SignedMsgType + +Signed message type represents a signed messages in consensus. + +```proto +enum SignedMsgType { + + SIGNED_MSG_TYPE_UNKNOWN = 0; + // Votes + SIGNED_MSG_TYPE_PREVOTE = 1; + SIGNED_MSG_TYPE_PRECOMMIT = 2; + + // Proposal + SIGNED_MSG_TYPE_PROPOSAL = 32; +} +``` + +## Signature + +Signatures in Tendermint are raw bytes representing the underlying signature. + +See the [signature spec](./encoding.md#key-types) for more. + +## EvidenceList + +EvidenceList is a simple wrapper for a list of evidence: + +| Name | Type | Description | Validation | +|----------|--------------------------------|----------------------------------------|-----------------------------------------------------------------| +| Evidence | Array of [Evidence](#evidence) | List of verified [evidence](#evidence) | Validation adheres to individual types of [Evidence](#evidence) | + +## Evidence + +Evidence in Tendermint is used to indicate breaches in the consensus by a validator. + +More information on how evidence works in Tendermint can be found [here](../consensus/evidence.md) + +### DuplicateVoteEvidence + +`DuplicateVoteEvidence` represents a validator that has voted for two different blocks +in the same round of the same height. Votes are lexicographically sorted on `BlockID`. + +| Name | Type | Description | Validation | +|------------------|---------------|--------------------------------------------------------------------|-----------------------------------------------------| +| VoteA | [Vote](#vote) | One of the votes submitted by a validator when they equivocated | VoteA must adhere to [Vote](#vote) validation rules | +| VoteB | [Vote](#vote) | The second vote submitted by a validator when they equivocated | VoteB must adhere to [Vote](#vote) validation rules | +| TotalVotingPower | int64 | The total power of the validator set at the height of equivocation | Must be equal to nodes own copy of the data | +| ValidatorPower | int64 | Power of the equivocating validator at the height | Must be equal to the nodes own copy of the data | +| Timestamp | [Time](#Time) | Time of the block where the equivocation occurred | Must be equal to the nodes own copy of the data | + +### LightClientAttackEvidence + +`LightClientAttackEvidence` is a generalized evidence that captures all forms of known attacks on +a light client such that a full node can verify, propose and commit the evidence on-chain for +punishment of the malicious validators. There are three forms of attacks: Lunatic, Equivocation +and Amnesia. These attacks are exhaustive. You can find a more detailed overview of this [here](../light-client/accountability#the_misbehavior_of_faulty_validators) + +| Name | Type | Description | Validation | +|----------------------|------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------------| +| ConflictingBlock | [LightBlock](#LightBlock) | Read Below | Must adhere to the validation rules of [lightBlock](#lightblock) | +| CommonHeight | int64 | Read Below | must be > 0 | +| Byzantine Validators | Array of [Validators](#Validators) | validators that acted maliciously | Read Below | +| TotalVotingPower | int64 | The total power of the validator set at the height of the infraction | Must be equal to the nodes own copy of the data | +| Timestamp | [Time](#Time) | Time of the block where the infraction occurred | Must be equal to the nodes own copy of the data | + +## LightBlock + +LightBlock is the core data structure of the [light client](../light-client/README.md). It combines two data structures needed for verification ([signedHeader](#signedheader) & [validatorSet](#validatorset)). + +| Name | Type | Description | Validation | +|--------------|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| SignedHeader | [SignedHeader](#signedheader) | The header and commit, these are used for verification purposes. To find out more visit [light client docs](../light-client/README.md) | Must not be nil and adhere to the validation rules of [signedHeader](#signedheader) | +| ValidatorSet | [ValidatorSet](#validatorset) | The validatorSet is used to help with verify that the validators in that committed the infraction were truly in the validator set. | Must not be nil and adhere to the validation rules of [validatorSet](#validatorset) | + +The `SignedHeader` and `ValidatorSet` are linked by the hash of the validator set(`SignedHeader.ValidatorsHash == ValidatorSet.Hash()`. + +## SignedHeader + +The SignedhHeader is the [header](#header) accompanied by the commit to prove it. + +| Name | Type | Description | Validation | +|--------|-------------------|-------------------|-----------------------------------------------------------------------------------| +| Header | [Header](#Header) | [Header](#header) | Header cannot be nil and must adhere to the [Header](#Header) validation criteria | +| Commit | [Commit](#commit) | [Commit](#commit) | Commit cannot be nil and must adhere to the [Commit](#commit) criteria | + +## ValidatorSet + +| Name | Type | Description | Validation | +|------------|----------------------------------|----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| +| Validators | Array of [validator](#validator) | List of the active validators at a specific height | The list of validators can not be empty or nil and must adhere to the validation rules of [validator](#validator) | +| Proposer | [validator](#validator) | The block proposer for the corresponding block | The proposer cannot be nil and must adhere to the validation rules of [validator](#validator) | + +## Validator + +| Name | Type | Description | Validation | +|------------------|---------------------------|---------------------------------------------------------------------------------------------------|---------------------------------------------------| +| Address | [Address](#address) | Validators Address | Length must be of size 20 | +| Pubkey | slice of bytes (`[]byte`) | Validators Public Key | must be a length greater than 0 | +| VotingPower | int64 | Validators voting power | cannot be < 0 | +| ProposerPriority | int64 | Validators proposer priority. This is used to gauge when a validator is up next to propose blocks | No validation, value can be negative and positive | + +## Address + +Address is a type alias of a slice of bytes. The address is calculated by hashing the public key using sha256 and truncating it to only use the first 20 bytes of the slice. + +```go +const ( + TruncatedSize = 20 +) + +func SumTruncated(bz []byte) []byte { + hash := sha256.Sum256(bz) + return hash[:TruncatedSize] +} +``` + +## ConsensusParams + +| Name | Type | Description | Field Number | +|-----------|-------------------------------------|------------------------------------------------------------------------------|--------------| +| block | [BlockParams](#blockparams) | Parameters limiting the size of a block and time between consecutive blocks. | 1 | +| evidence | [EvidenceParams](#evidenceparams) | Parameters limiting the validity of evidence of byzantine behaviour. | 2 | +| validator | [ValidatorParams](#validatorparams) | Parameters limiting the types of public keys validators can use. | 3 | +| version | [BlockParams](#blockparams) | The ABCI application version. | 4 | + +### BlockParams + +| Name | Type | Description | Field Number | +|--------------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| +| max_bytes | int64 | Max size of a block, in bytes. | 1 | +| max_gas | int64 | Max sum of `GasWanted` in a proposed block. NOTE: blocks that violate this may be committed if there are Byzantine proposers. It's the application's responsibility to handle this when processing a block! | 2 | + +### EvidenceParams + +| Name | Type | Description | Field Number | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| +| max_age_num_blocks | int64 | Max age of evidence, in blocks. | 1 | +| max_age_duration | [google.protobuf.Duration](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration) | Max age of evidence, in time. It should correspond with an app's "unbonding period" or other similar mechanism for handling [Nothing-At-Stake attacks](https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed). | 2 | +| max_bytes | int64 | maximum size in bytes of total evidence allowed to be entered into a block | 3 | + +### ValidatorParams + +| Name | Type | Description | Field Number | +|---------------|-----------------|-----------------------------------------------------------------------|--------------| +| pub_key_types | repeated string | List of accepted public key types. Uses same naming as `PubKey.Type`. | 1 | + +### VersionParams + +| Name | Type | Description | Field Number | +|-------------|--------|-------------------------------|--------------| +| app_version | uint64 | The ABCI application version. | 1 | + +## Proof + +| Name | Type | Description | Field Number | +|-----------|----------------|-----------------------------------------------|--------------| +| total | int64 | Total number of items. | 1 | +| index | int64 | Index item to prove. | 2 | +| leaf_hash | bytes | Hash of item value. | 3 | +| aunts | repeated bytes | Hashes from leaf's sibling to a root's child. | 4 | diff --git a/spec/core/encoding.md b/spec/core/encoding.md new file mode 100644 index 00000000000..c137575d78b --- /dev/null +++ b/spec/core/encoding.md @@ -0,0 +1,300 @@ +# Encoding + +## Protocol Buffers + +Tendermint uses [Protocol Buffers](https://developers.google.com/protocol-buffers), specifically proto3, for all data structures. + +Please see the [Proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3) for more details. + +## Byte Arrays + +The encoding of a byte array is simply the raw-bytes prefixed with the length of +the array as a `UVarint` (what proto calls a `Varint`). + +For details on varints, see the [protobuf +spec](https://developers.google.com/protocol-buffers/docs/encoding#varints). + +For example, the byte-array `[0xA, 0xB]` would be encoded as `0x020A0B`, +while a byte-array containing 300 entires beginning with `[0xA, 0xB, ...]` would +be encoded as `0xAC020A0B...` where `0xAC02` is the UVarint encoding of 300. + +## Hashing + +Tendermint uses `SHA256` as its hash function. +Objects are always serialized before being hashed. +So `SHA256(obj)` is short for `SHA256(ProtoEncoding(obj))`. + +## Public Key Cryptography + +Tendermint uses Protobuf [Oneof](https://developers.google.com/protocol-buffers/docs/proto3#oneof) +to distinguish between different types public keys, and signatures. +Additionally, for each public key, Tendermint +defines an Address function that can be used as a more compact identifier in +place of the public key. Here we list the concrete types, their names, +and prefix bytes for public keys and signatures, as well as the address schemes +for each PubKey. Note for brevity we don't +include details of the private keys beyond their type and name. + +### Key Types + +Each type specifies it's own pubkey, address, and signature format. + +#### Ed25519 + +The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key: + +```go +address = SHA256(pubkey)[:20] +``` + +The signature is the raw 64-byte ED25519 signature. + +Tendermint adopted [zip215](https://zips.z.cash/zip-0215) for verification of ed25519 signatures. + +> Note: This change will be released in the next major release of Tendermint-Go (0.35). + +#### Secp256k1 + +The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key: + +```go +address = SHA256(pubkey)[:20] +``` + +## Other Common Types + +### BitArray + +The BitArray is used in some consensus messages to represent votes received from +validators, or parts received in a block. It is represented +with a struct containing the number of bits (`Bits`) and the bit-array itself +encoded in base64 (`Elems`). + +| Name | Type | +|-------|----------------------------| +| bits | int64 | +| elems | slice of int64 (`[]int64`) | + +Note BitArray receives a special JSON encoding in the form of `x` and `_` +representing `1` and `0`. Ie. the BitArray `10110` would be JSON encoded as +`"x_xx_"` + +### Part + +Part is used to break up blocks into pieces that can be gossiped in parallel +and securely verified using a Merkle tree of the parts. + +Part contains the index of the part (`Index`), the actual +underlying data of the part (`Bytes`), and a Merkle proof that the part is contained in +the set (`Proof`). + +| Name | Type | +|-------|---------------------------| +| index | uint32 | +| bytes | slice of bytes (`[]byte`) | +| proof | [proof](#merkle-proof) | + +See details of SimpleProof, below. + +### MakeParts + +Encode an object using Protobuf and slice it into parts. +Tendermint uses a part size of 65536 bytes, and allows a maximum of 1601 parts +(see `types.MaxBlockPartsCount`). This corresponds to the hard-coded block size +limit of 100MB. + +```go +func MakeParts(block Block) []Part +``` + +## Merkle Trees + +For an overview of Merkle trees, see +[wikipedia](https://en.wikipedia.org/wiki/Merkle_tree) + +We use the RFC 6962 specification of a merkle tree, with sha256 as the hash function. +Merkle trees are used throughout Tendermint to compute a cryptographic digest of a data structure. +The differences between RFC 6962 and the simplest form a merkle tree are that: + +1. leaf nodes and inner nodes have different hashes. + This is for "second pre-image resistance", to prevent the proof to an inner node being valid as the proof of a leaf. + The leaf nodes are `SHA256(0x00 || leaf_data)`, and inner nodes are `SHA256(0x01 || left_hash || right_hash)`. + +2. When the number of items isn't a power of two, the left half of the tree is as big as it could be. + (The largest power of two less than the number of items) This allows new leaves to be added with less + recomputation. For example: + +```md + Simple Tree with 6 items Simple Tree with 7 items + + * * + / \ / \ + / \ / \ + / \ / \ + / \ / \ + * * * * + / \ / \ / \ / \ + / \ / \ / \ / \ + / \ / \ / \ / \ + * * h4 h5 * * * h6 + / \ / \ / \ / \ / \ +h0 h1 h2 h3 h0 h1 h2 h3 h4 h5 +``` + +### MerkleRoot + +The function `MerkleRoot` is a simple recursive function defined as follows: + +```go +// SHA256([]byte{}) +func emptyHash() []byte { + return tmhash.Sum([]byte{}) +} + +// SHA256(0x00 || leaf) +func leafHash(leaf []byte) []byte { + return tmhash.Sum(append(0x00, leaf...)) +} + +// SHA256(0x01 || left || right) +func innerHash(left []byte, right []byte) []byte { + return tmhash.Sum(append(0x01, append(left, right...)...)) +} + +// largest power of 2 less than k +func getSplitPoint(k int) { ... } + +func MerkleRoot(items [][]byte) []byte{ + switch len(items) { + case 0: + return empthHash() + case 1: + return leafHash(items[0]) + default: + k := getSplitPoint(len(items)) + left := MerkleRoot(items[:k]) + right := MerkleRoot(items[k:]) + return innerHash(left, right) + } +} +``` + +Note: `MerkleRoot` operates on items which are arbitrary byte arrays, not +necessarily hashes. For items which need to be hashed first, we introduce the +`Hashes` function: + +```go +func Hashes(items [][]byte) [][]byte { + return SHA256 of each item +} +``` + +Note: we will abuse notion and invoke `MerkleRoot` with arguments of type `struct` or type `[]struct`. +For `struct` arguments, we compute a `[][]byte` containing the protobuf encoding of each +field in the struct, in the same order the fields appear in the struct. +For `[]struct` arguments, we compute a `[][]byte` by protobuf encoding the individual `struct` elements. + +### Merkle Proof + +Proof that a leaf is in a Merkle tree is composed as follows: + +| Name | Type | +|----------|----------------------------| +| total | int64 | +| index | int64 | +| leafHash | slice of bytes (`[]byte`) | +| aunts | Matrix of bytes ([][]byte) | + +Which is verified as follows: + +```golang +func (proof Proof) Verify(rootHash []byte, leaf []byte) bool { + assert(proof.LeafHash, leafHash(leaf) + + computedHash := computeHashFromAunts(proof.Index, proof.Total, proof.LeafHash, proof.Aunts) + return computedHash == rootHash +} + +func computeHashFromAunts(index, total int, leafHash []byte, innerHashes [][]byte) []byte{ + assert(index < total && index >= 0 && total > 0) + + if total == 1{ + assert(len(proof.Aunts) == 0) + return leafHash + } + + assert(len(innerHashes) > 0) + + numLeft := getSplitPoint(total) // largest power of 2 less than total + if index < numLeft { + leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1]) + assert(leftHash != nil) + return innerHash(leftHash, innerHashes[len(innerHashes)-1]) + } + rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1]) + assert(rightHash != nil) + return innerHash(innerHashes[len(innerHashes)-1], rightHash) +} +``` + +The number of aunts is limited to 100 (`MaxAunts`) to protect the node against DOS attacks. +This limits the tree size to 2^100 leaves, which should be sufficient for any +conceivable purpose. + +### IAVL+ Tree + +Because Tendermint only uses a Simple Merkle Tree, application developers are expect to use their own Merkle tree in their applications. For example, the IAVL+ Tree - an immutable self-balancing binary tree for persisting application state is used by the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/blob/ae77f0080a724b159233bd9b289b2e91c0de21b5/docs/interfaces/lite/specification.md) + +## JSON + +Tendermint has its own JSON encoding in order to keep backwards compatibility with the previous RPC layer. + +Registered types are encoded as: + +```json +{ + "type": "", + "value": +} +``` + +For instance, an ED25519 PubKey would look like: + +```json +{ + "type": "tendermint/PubKeyEd25519", + "value": "uZ4h63OFWuQ36ZZ4Bd6NF+/w9fWUwrOncrQsackrsTk=" +} +``` + +Where the `"value"` is the base64 encoding of the raw pubkey bytes, and the +`"type"` is the type name for Ed25519 pubkeys. + +### Signed Messages + +Signed messages (eg. votes, proposals) in the consensus are encoded using protobuf. + +When signing, the elements of a message are re-ordered so the fixed-length fields +are first, making it easy to quickly check the type, height, and round. +The `ChainID` is also appended to the end. +We call this encoding the SignBytes. For instance, SignBytes for a vote is the protobuf encoding of the following struct: + +```protobuf +message CanonicalVote { + SignedMsgType type = 1; + sfixed64 height = 2; // canonicalization requires fixed size encoding here + sfixed64 round = 3; // canonicalization requires fixed size encoding here + CanonicalBlockID block_id = 4; + google.protobuf.Timestamp timestamp = 5; + string chain_id = 6; +} +``` + +The field ordering and the fixed sized encoding for the first three fields is optimized to ease parsing of SignBytes +in HSMs. It creates fixed offsets for relevant fields that need to be read in this context. + +> Note: All canonical messages are length prefixed. + +For more details, see the [signing spec](../consensus/signing.md). +Also, see the motivating discussion in +[#1622](https://github.com/tendermint/tendermint/issues/1622). diff --git a/spec/core/genesis.md b/spec/core/genesis.md new file mode 100644 index 00000000000..1dc019e77e7 --- /dev/null +++ b/spec/core/genesis.md @@ -0,0 +1,34 @@ +# Genesis + +The genesis file is the starting point of a chain. An application will populate the `app_state` field in the genesis with their required fields. Tendermint is not able to validate this section because it is unaware what application state consists of. + +## Genesis Fields + +- `genesis_time`: The genesis time is the time the blockchain started or will start. If nodes are started before this time they will sit idle until the time specified. +- `chain_id`: The chainid is the chain identifier. Every chain should have a unique identifier. When conducting a fork based upgrade, we recommend changing the chainid to avoid network or consensus errors. +- `initial_height`: This field is the starting height of the blockchain. When conducting a chain restart to avoid restarting at height 1, the network is able to start at a specified height. +- `consensus_params` + - `block` + - `max_bytes`: The max amount of bytes a block can be. + - `max_gas`: The maximum amount of gas that a block can have. + - `time_iota_ms`: This parameter has no value anymore in Tendermint-core. + +- `evidence` + - `max_age_num_blocks`: After this preset amount of blocks has passed a single piece of evidence is considered invalid + - `max_age_duration`: After this preset amount of time has passed a single piece of evidence is considered invalid. + - `max_bytes`: The max amount of bytes of all evidence included in a block. + +> Note: For evidence to be considered invalid, evidence must be older than both `max_age_num_blocks` and `max_age_duration` + +- `validator` + - `pub_key_types`: Defines which curves are to be accepted as a valid validator consensus key. Tendermint supports ed25519, sr25519 and secp256k1. + +- `version` + - `app_version`: The version of the application. This is set by the application and is used to identify which version of the app a user should be using in order to operate a node. + +- `validators` + - This is an array of validators. This validator set is used as the starting validator set of the chain. This field can be empty, if the application sets the validator set in `InitChain`. + +- `app_hash`: The applications state root hash. This field does not need to be populated at the start of the chain, the application may provide the needed information via `Initchain`. + +- `app_state`: This section is filled in by the application and is unknown to Tendermint. diff --git a/spec/core/readme.md b/spec/core/readme.md new file mode 100644 index 00000000000..46f95f1b76f --- /dev/null +++ b/spec/core/readme.md @@ -0,0 +1,13 @@ +--- +order: 1 +parent: + title: Core + order: 3 +--- + +This section describes the core types and functionality of the Tendermint protocol implementation. + +- [Core Data Structures](./data_structures.md) +- [Encoding](./encoding.md) +- [Genesis](./genesis.md) +- [State](./state.md) diff --git a/spec/core/state.md b/spec/core/state.md new file mode 100644 index 00000000000..5138c095067 --- /dev/null +++ b/spec/core/state.md @@ -0,0 +1,121 @@ +# State + +The state contains information whose cryptographic digest is included in block headers, and thus is +necessary for validating new blocks. For instance, the validators set and the results of +transactions are never included in blocks, but their Merkle roots are: +the state keeps track of them. + +The `State` object itself is an implementation detail, since it is never +included in a block or gossiped over the network, and we never compute +its hash. The persistence or query interface of the `State` object +is an implementation detail and not included in the specification. +However, the types in the `State` object are part of the specification, since +the Merkle roots of the `State` objects are included in blocks and values are used during +validation. + +```go +type State struct { + ChainID string + InitialHeight int64 + + LastBlockHeight int64 + LastBlockID types.BlockID + LastBlockTime time.Time + + Version Version + LastResults []Result + AppHash []byte + + LastValidators ValidatorSet + Validators ValidatorSet + NextValidators ValidatorSet + + ConsensusParams ConsensusParams +} +``` + +The chain ID and initial height are taken from the genesis file, and not changed again. The +initial height will be `1` in the typical case, `0` is an invalid value. + +Note there is a hard-coded limit of 10000 validators. This is inherited from the +limit on the number of votes in a commit. + +Further information on [`Validator`'s](./data_structures.md#validator), +[`ValidatorSet`'s](./data_structures.md#validatorset) and +[`ConsensusParams`'s](./data_structures.md#consensusparams) can +be found in [data structures](./data_structures.md) + +## Execution + +State gets updated at the end of executing a block. Of specific interest is `ResponseEndBlock` and +`ResponseCommit` + +```go +type ResponseEndBlock struct { + ValidatorUpdates []ValidatorUpdate `protobuf:"bytes,1,rep,name=validator_updates,json=validatorUpdates,proto3" json:"validator_updates"` + ConsensusParamUpdates *types1.ConsensusParams `protobuf:"bytes,2,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3" json:"consensus_param_updates,omitempty"` + Events []Event `protobuf:"bytes,3,rep,name=events,proto3" json:"events,omitempty"` +} +``` + +where + +```go +type ValidatorUpdate struct { + PubKey crypto.PublicKey `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key"` + Power int64 `protobuf:"varint,2,opt,name=power,proto3" json:"power,omitempty"` +} +``` + +and + +```go +type ResponseCommit struct { + // reserve 1 + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + RetainHeight int64 `protobuf:"varint,3,opt,name=retain_height,json=retainHeight,proto3" json:"retain_height,omitempty"` +} +``` + +`ValidatorUpdates` are used to add and remove validators to the current set as well as update +validator power. Setting validator power to 0 in `ValidatorUpdate` will cause the validator to be +removed. `ConsensusParams` are safely copied across (i.e. if a field is nil it gets ignored) and the +`Data` from the `ResponseCommit` is used as the `AppHash` + +## Version + +```go +type Version struct { + consensus Consensus + software string +} +``` + +[`Consensus`](./data_structures.md#version) contains the protocol version for the blockchain and the +application. + +## Block + +The total size of a block is limited in bytes by the `ConsensusParams.Block.MaxBytes`. +Proposed blocks must be less than this size, and will be considered invalid +otherwise. + +Blocks should additionally be limited by the amount of "gas" consumed by the +transactions in the block, though this is not yet implemented. + +## Evidence + +For evidence in a block to be valid, it must satisfy: + +```go +block.Header.Time-evidence.Time < ConsensusParams.Evidence.MaxAgeDuration && + block.Header.Height-evidence.Height < ConsensusParams.Evidence.MaxAgeNumBlocks +``` + +A block must not contain more than `ConsensusParams.Evidence.MaxBytes` of evidence. This is +implemented to mitigate spam attacks. + +## Validator + +Validators from genesis file and `ResponseEndBlock` must have pubkeys of type ∈ +`ConsensusParams.Validator.PubKeyTypes`. diff --git a/spec/ivy-proofs/Dockerfile b/spec/ivy-proofs/Dockerfile new file mode 100644 index 00000000000..be60151fd2f --- /dev/null +++ b/spec/ivy-proofs/Dockerfile @@ -0,0 +1,37 @@ +# we need python2 support, which was dropped after buster: +FROM debian:buster + +RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections +RUN apt-get update +RUN apt-get install -y apt-utils + +# Install and configure locale `en_US.UTF-8` +RUN apt-get install -y locales && \ + sed -i -e "s/# $en_US.*/en_US.UTF-8 UTF-8/" /etc/locale.gen && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + update-locale LANG=en_US.UTF-8 +ENV LANG=en_US.UTF-8 + +RUN apt-get update +RUN apt-get install -y git python2 python-pip g++ cmake python-ply python-tk tix pkg-config libssl-dev python-setuptools + +# create a user: +RUN useradd -ms /bin/bash user +USER user +WORKDIR /home/user + +RUN git clone --recurse-submodules https://github.com/kenmcmil/ivy.git +WORKDIR /home/user/ivy/ +RUN git checkout 271ee38980699115508eb90a0dd01deeb750a94b + +RUN python2.7 build_submodules.py +RUN mkdir -p "/home/user/python/lib/python2.7/site-packages" +ENV PYTHONPATH="/home/user/python/lib/python2.7/site-packages" +# need to install pyparsing manually because otherwise wrong version found +RUN pip install pyparsing +RUN python2.7 setup.py install --prefix="/home/user/python/" +ENV PATH=$PATH:"/home/user/python/bin/" +WORKDIR /home/user/tendermint-proof/ + +ENTRYPOINT ["/home/user/tendermint-proof/check_proofs.sh"] + diff --git a/spec/ivy-proofs/README.md b/spec/ivy-proofs/README.md new file mode 100644 index 00000000000..00a4bed2594 --- /dev/null +++ b/spec/ivy-proofs/README.md @@ -0,0 +1,33 @@ +# Ivy Proofs + +```copyright +Copyright (c) 2020 Galois, Inc. +SPDX-License-Identifier: Apache-2.0 +``` + +## Contents + +This folder contains: + +* `tendermint.ivy`, a specification of Tendermint algorithm as described in *The latest gossip on BFT consensus* by E. Buchman, J. Kwon, Z. Milosevic. +* `abstract_tendermint.ivy`, a more abstract specification of Tendermint that is more verification-friendly. +* `classic_safety.ivy`, a proof that Tendermint satisfies the classic safety property of BFT consensus: if every two quorums have a well-behaved node in common, then no two well-behaved nodes ever disagree. +* `accountable_safety_1.ivy`, a proof that, assuming every quorum contains at least one well-behaved node, if two well-behaved nodes disagree, then there is evidence demonstrating at least f+1 nodes misbehaved. +* `accountable_safety_2.ivy`, a proof that, regardless of any assumption about quorums, well-behaved nodes cannot be framed by malicious nodes. In other words, malicious nodes can never construct evidence that incriminates a well-behaved node. +* `network_shim.ivy`, the network model and a convenience `shim` object to interface with the Tendermint specification. +* `domain_model.ivy`, a specification of the domain model underlying the Tendermint specification, i.e. rounds, value, quorums, etc. + +All specifications and proofs are written in [Ivy](https://github.com/kenmcmil/ivy). + +The license above applies to all files in this folder. + + +## Building and running + +The easiest way to check the proofs is to use [Docker](https://www.docker.com/). + +1. Install [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/). +2. Build a Docker image: `docker-compose build` +3. Run the proofs inside the Docker container: `docker-compose run +tendermint-proof`. This will check all the proofs with the `ivy_check` +command and write the output of `ivy_check` to a subdirectory of `./output/' diff --git a/spec/ivy-proofs/abstract_tendermint.ivy b/spec/ivy-proofs/abstract_tendermint.ivy new file mode 100644 index 00000000000..4a160be2a7b --- /dev/null +++ b/spec/ivy-proofs/abstract_tendermint.ivy @@ -0,0 +1,178 @@ +#lang ivy1.7 +# --- +# layout: page +# title: Abstract specification of Tendermint in Ivy +# --- + +# Here we define an abstract version of the Tendermint specification. We use +# two main forms of abstraction: a) We abstract over how information is +# transmitted (there is no network). b) We abstract functions using relations. +# For example, we abstract over a node's current round, instead only tracking +# with a relation which rounds the node has left. We do something similar for +# the `lockedRound` variable. This is in order to avoid using a function from +# node to round, and it allows us to emit verification conditions that are +# efficiently solvable by Z3. + +# This specification also defines the observations that are used to adjudicate +# misbehavior. Well-behaved nodes faithfully observe every message that they +# use to take a step, while Byzantine nodes can fake observations about +# themselves (including withholding observations). Misbehavior is defined using +# the collection of all observations made (in reality, those observations must +# be collected first, but we do not model this process). + +include domain_model + +module abstract_tendermint = { + +# Protocol state +# ############## + + relation left_round(N:node, R:round) + relation prevoted(N:node, R:round, V:value) + relation precommitted(N:node, R:round, V:value) + relation decided(N:node, R:round, V:value) + relation locked(N:node, R:round, V:value) + +# Accountability relations +# ######################## + + relation observed_prevoted(N:node, R:round, V:value) + relation observed_precommitted(N:node, R:round, V:value) + +# relations that are defined in terms of the previous two: + relation observed_equivocation(N:node) + relation observed_unlawful_prevote(N:node) + relation agreement + relation accountability_violation + + object defs = { # we hide those definitions and use them only when needed + private { + definition [observed_equivocation_def] observed_equivocation(N) = exists V1,V2,R . + V1 ~= V2 & (observed_precommitted(N,R,V1) & observed_precommitted(N,R,V2) | observed_prevoted(N,R,V1) & observed_prevoted(N,R,V2)) + + definition [observed_unlawful_prevote_def] observed_unlawful_prevote(N) = exists V1,V2,R1,R2 . + V1 ~= value.nil & V2 ~= value.nil & V1 ~= V2 & R1 < R2 & observed_precommitted(N,R1,V1) & observed_prevoted(N,R2,V2) + & forall Q,R . R1 <= R & R < R2 & nset.is_quorum(Q) -> exists N2 . nset.member(N2,Q) & ~observed_prevoted(N2,R,V2) + + definition [agreement_def] agreement = forall N1,N2,R1,R2,V1,V2 . well_behaved(N1) & well_behaved(N2) & decided(N1,R1,V1) & decided(N2,R2,V2) -> V1 = V2 + + definition [accountability_violation_def] accountability_violation = exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & (forall N . nset.member(N,Q1) & nset.member(N,Q2) -> observed_equivocation(N) | observed_unlawful_prevote(N)) + } + } + +# Protocol transitions +# #################### + + after init { + left_round(N,R) := R < 0; + prevoted(N,R,V) := false; + precommitted(N,R,V) := false; + decided(N,R,V) := false; + locked(N,R,V) := false; + + observed_prevoted(N,R,V) := false; + observed_precommitted(N,R,V) := false; + } + +# Actions are named after the corresponding line numbers in the Tendermint +# arXiv paper. + + action l_11(n:node, r:round) = { # start round r + require ~left_round(n,r); + left_round(n,R) := R < r; + } + + action l_22(n:node, rp:round, v:value) = { + require ~left_round(n,rp); + require ~prevoted(n,rp,V) & ~precommitted(n,rp,V); + require (forall R,V . locked(n,R,V) -> V = v) | v = value.nil; + prevoted(n, rp, v) := true; + left_round(n, R) := R < rp; # leave all lower rounds. + + observed_prevoted(n, rp, v) := observed_prevoted(n, rp, v) | well_behaved(n); # the node observes itself + } + + action l_28(n:node, rp:round, v:value, vr:round, q:nset) = { + require ~left_round(n,rp) & ~prevoted(n,rp,V); + require ~prevoted(n,rp,V) & ~precommitted(n,rp,V); + require vr < rp; + require nset.is_quorum(q) & (forall N . nset.member(N,q) -> (prevoted(N,vr,v) | ~well_behaved(N))); + var proposal:value; + if value.valid(v) & ((forall R0,V0 . locked(n,R0,V0) -> R0 <= vr) | (forall R,V . locked(n,R,V) -> V = v)) { + proposal := v; + } + else { + proposal := value.nil; + }; + prevoted(n, rp, proposal) := true; + left_round(n, R) := R < rp; # leave all lower rounds + + observed_prevoted(N, vr, v) := observed_prevoted(N, vr, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q + observed_prevoted(n, rp, proposal) := observed_prevoted(n, rp, proposal) | well_behaved(n); # the node observes itself + } + + action l_36(n:node, rp:round, v:value, q:nset) = { + require v ~= value.nil; + require ~left_round(n,rp); + require exists V . prevoted(n,rp,V); + require ~precommitted(n,rp,V); + require nset.is_quorum(q) & (forall N . nset.member(N,q) -> (prevoted(N,rp,v) | ~well_behaved(N))); + precommitted(n, rp, v) := true; + left_round(n, R) := R < rp; # leave all lower rounds + locked(n,R,V) := R <= rp & V = v; + + observed_prevoted(N, rp, v) := observed_prevoted(N, rp, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q + observed_precommitted(n, rp, v) := observed_precommitted(n, rp, v) | well_behaved(n); # the node observes itself + } + + action l_44(n:node, rp:round, q:nset) = { + require ~left_round(n,rp); + require ~precommitted(n,rp,V); + require nset.is_quorum(q) & (forall N .nset.member(N,q) -> (prevoted(N,rp,value.nil) | ~well_behaved(N))); + precommitted(n, rp, value.nil) := true; + left_round(n, R) := R < rp; # leave all lower rounds + + observed_prevoted(N, rp, value.nil) := observed_prevoted(N, rp, value.nil) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q + observed_precommitted(n, rp, value.nil) := observed_precommitted(n, rp, value.nil) | well_behaved(n); # the node observes itself + } + + action l_57(n:node, rp:round) = { + require ~left_round(n,rp); + require ~prevoted(n,rp,V); + prevoted(n, rp, value.nil) := true; + left_round(n, R) := R < rp; # leave all lower rounds + + observed_prevoted(n, rp, value.nil) := observed_prevoted(n, rp, value.nil) | well_behaved(n); # the node observes itself + } + + action l_61(n:node, rp:round) = { + require ~left_round(n,rp); + require ~precommitted(n,rp,V); + precommitted(n, rp, value.nil) := true; + left_round(n, R) := R < rp; # leave all lower rounds + + observed_precommitted(n, rp, value.nil) := observed_precommitted(n, rp, value.nil) | well_behaved(n); # the node observes itself + } + + action decide(n:node, r:round, v:value, q:nset) = { + require v ~= value.nil; + require nset.is_quorum(q) & (forall N . nset.member(N, q) -> (precommitted(N, r, v) | ~well_behaved(N))); + decided(n, r, v) := true; + + observed_precommitted(N, r, v) := observed_precommitted(N, r, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the precommits of quorum q + + } + + action misbehave = { +# Byzantine nodes can claim they observed whatever they want about themselves, +# but they cannot remove observations. Note that we use assume because we don't +# want those to be checked; we just want them to be true (that's the model of +# Byzantine behavior). + observed_prevoted(N,R,V) := *; + assume (old observed_prevoted(N,R,V)) -> observed_prevoted(N,R,V); + assume well_behaved(N) -> old observed_prevoted(N,R,V) = observed_prevoted(N,R,V); + observed_precommitted(N,R,V) := *; + assume (old observed_precommitted(N,R,V)) -> observed_precommitted(N,R,V); + assume well_behaved(N) -> old observed_precommitted(N,R,V) = observed_precommitted(N,R,V); + } +} diff --git a/spec/ivy-proofs/accountable_safety_1.ivy b/spec/ivy-proofs/accountable_safety_1.ivy new file mode 100644 index 00000000000..02bdf1add84 --- /dev/null +++ b/spec/ivy-proofs/accountable_safety_1.ivy @@ -0,0 +1,143 @@ +#lang ivy1.7 +# --- +# layout: page +# title: Proof of Classic Safety +# --- + +include tendermint +include abstract_tendermint + +# Here we prove the first accountability property: if two well-behaved nodes +# disagree, then there are two quorums Q1 and Q2 such that all members of the +# intersection of Q1 and Q2 have violated the accountability properties. + +# The proof is done in two steps: first we prove the abstract specification +# satisfies the property, and then we show by refinement that this property +# also holds in the concrete specification. + +# To see what is checked in the refinement proof, use `ivy_show isolate=accountable_safety_1 accountable_safety_1.ivy` +# To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_accountable_safety_1 accountable_safety_1.ivy` +# To check the whole proof, use `ivy_check accountable_safety_1.ivy`. + + +# Proof of the accountability property in the abstract specification +# ================================================================== + +# We prove with tactics (see `lemma_1` and `lemma_2`) that, if some basic +# invariants hold (see `invs` below), then the accountability property holds. + +isolate abstract_accountable_safety = { + + instantiate abstract_tendermint + +# The main property +# ----------------- + +# If there is disagreement, then there is evidence that a third of the nodes +# have violated the protocol: + invariant [accountability] agreement | accountability_violation + proof { + apply lemma_1.thm # this reduces to goal to three subgoals: p1, p2, and p3 (see their definition below) + proof [p1] { + assume invs.inv1 + } + proof [p2] { + assume invs.inv2 + } + proof [p3] { + assume invs.inv3 + } + } + +# The invariants +# -------------- + + isolate invs = { + + # well-behaved nodes observe their own actions faithfully: + invariant [inv1] well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V)) + # if a value is precommitted by a well-behaved node, then a quorum is observed to prevote it: + invariant [inv2] (exists N . well_behaved(N) & precommitted(N,R,V)) & V ~= value.nil -> exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V) + # if a value is decided by a well-behaved node, then a quorum is observed to precommit it: + invariant [inv3] (exists N . well_behaved(N) & decided(N,R,V)) -> 0 <= R & V ~= value.nil & exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_precommitted(N2,R,V) + private { + invariant (precommitted(N,R,V) | prevoted(N,R,V)) -> 0 <= R + invariant R < 0 -> left_round(N,R) + } + + } with this, nset, round, accountable_bft.max_2f_byzantine + +# The theorems proved with tactics +# -------------------------------- + +# Using complete induction on rounds, we prove that, assuming that the +# invariants inv1, inv2, and inv3 hold, the accountability property holds. + +# For technical reasons, we separate the proof in two steps + isolate lemma_1 = { + + specification { + theorem [thm] { + property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V)) + property [p2] forall R,V . (exists N . well_behaved(N) & precommitted(N,R,V)) & V ~= value.nil -> exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V) + property [p3] forall R,V. (exists N . well_behaved(N) & decided(N,R,V)) -> 0 <= R & V ~= value.nil & exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_precommitted(N2,R,V) + #------------------------------------------------------------------------------------------------------------------------------------------- + property agreement | accountability_violation + } + proof { + assume inductive_property # the theorem follows from what we prove by induction below + } + } + + implementation { + # complete induction is not built-in, so we introduce it with an axiom. Note that this only holds for a type where 0 is the smallest element + axiom [complete_induction] { + relation p(X:round) + { # base case + property p(0) + } + { # inductive step: show that if the property is true for all X lower or equal to x and y=x+1, then the property is true of y + individual a:round + individual b:round + property (forall X. 0 <= X & X <= a -> p(X)) & round.succ(a,b) -> p(b) + } + #-------------------------- + property forall X . 0 <= X -> p(X) + } + + # The main lemma: if inv1 and inv2 below hold and a quorum is observed to + # precommit V1 at R1 and another quorum is observed to precommit V2~=V1 at + # R2>=R1, then the intersection of two quorums (i.e. f+1 nodes) is observed to + # violate the protocol. We prove this by complete induction on R2. + theorem [inductive_property] { + property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V)) + property [p2] forall R,V . (exists N . well_behaved(N) & precommitted(N,R,V)) -> V = value.nil | exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V) + #----------------------------------------------------------------------------------------------------------------------- + property forall R2. 0 <= R2 -> ((exists V2,Q1,R1,V1,Q1 . V1 ~= value.nil & V2 ~= value.nil & V1 ~= V2 & 0 <= R1 & R1 <= R2 & nset.is_quorum(Q1) & (forall N . nset.member(N,Q1) -> observed_precommitted(N,R1,V1)) & (exists Q2 . nset.is_quorum(Q2) & forall N . nset.member(N,Q2) -> observed_prevoted(N,R2,V2))) -> accountability_violation) + } + proof { + apply complete_induction # the two subgoals (base case and inductive case) are then discharged automatically + # NOTE: this can take a long time depending on the SMT random seed (to try a different seed, use `ivy_check seed=$RANDOM` + } + } + } with this, round, nset, accountable_bft.max_2f_byzantine, defs.observed_equivocation_def, defs.observed_unlawful_prevote_def, defs.accountability_violation_def, defs.agreement_def + +} with round + +# The final proof +# =============== + +isolate accountable_safety_1 = { + +# First we instantiate the concrete protocol: + instantiate tendermint(abstract_accountable_safety) + +# We then define what we mean by agreement + relation agreement + definition [agreement_def] agreement = forall N1,N2. well_behaved(N1) & well_behaved(N2) & server.decision(N1) ~= value.nil & server.decision(N2) ~= value.nil -> server.decision(N1) = server.decision(N2) + + invariant abstract_accountable_safety.agreement -> agreement + + invariant [accountability] agreement | abstract_accountable_safety.accountability_violation + +} with value, round, proposers, shim, abstract_accountable_safety, abstract_accountable_safety.defs.agreement_def, accountable_safety_1.agreement_def diff --git a/spec/ivy-proofs/accountable_safety_2.ivy b/spec/ivy-proofs/accountable_safety_2.ivy new file mode 100644 index 00000000000..7fb928909a3 --- /dev/null +++ b/spec/ivy-proofs/accountable_safety_2.ivy @@ -0,0 +1,52 @@ +#lang ivy1.7 + +include tendermint +include abstract_tendermint + +# Here we prove the second accountability property: no well-behaved node is +# ever observed to violate the accountability properties. + +# The proof is done in two steps: first we prove the the abstract specification +# satisfies the property, and then we show by refinement that this property +# also holds in the concrete specification. + +# To see what is checked in the refinement proof, use `ivy_show isolate=accountable_safety_2 accountable_safety_2.ivy` +# To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_accountable_safety_2 accountable_safety_2.ivy` +# To check the whole proof, use `ivy_check complete=fo accountable_safety_2.ivy`. + +# Proof that the property holds in the abstract specification +# ============================================================ + +isolate abstract_accountable_safety_2 = { + + instantiate abstract_tendermint + +# the main property: + invariant [wb_never_punished] well_behaved(N) -> ~(observed_equivocation(N) | observed_unlawful_prevote(N)) + +# the main invariant for proving wb_not_punished: + invariant well_behaved(N) & precommitted(N,R,V) & ~locked(N,R,V) & V ~= value.nil -> exists R2,V2 . V2 ~= value.nil & R < R2 & precommitted(N,R2,V2) & locked(N,R2,V2) + + invariant (exists N . well_behaved(N) & precommitted(N,R,V) & V ~= value.nil) -> exists Q . nset.is_quorum(Q) & forall N . nset.member(N,Q) -> observed_prevoted(N,R,V) + + invariant well_behaved(N) -> (observed_prevoted(N,R,V) <-> prevoted(N,R,V)) + invariant well_behaved(N) -> (observed_precommitted(N,R,V) <-> precommitted(N,R,V)) + +# nodes stop prevoting or precommitting in lower rounds when doing so in a higher round: + invariant well_behaved(N) & prevoted(N,R2,V2) & R1 < R2 -> left_round(N,R1) + invariant well_behaved(N) & locked(N,R2,V2) & R1 < R2 -> left_round(N,R1) + + invariant [precommit_unique_per_round] well_behaved(N) & precommitted(N,R,V1) & precommitted(N,R,V2) -> V1 = V2 + +} with nset, round, abstract_accountable_safety_2.defs.observed_equivocation_def, abstract_accountable_safety_2.defs.observed_unlawful_prevote_def + +# Proof that the property holds in the concrete specification +# =========================================================== + +isolate accountable_safety_2 = { + + instantiate tendermint(abstract_accountable_safety_2) + + invariant well_behaved(N) -> ~(abstract_accountable_safety_2.observed_equivocation(N) | abstract_accountable_safety_2.observed_unlawful_prevote(N)) + +} with round, value, shim, abstract_accountable_safety_2, abstract_accountable_safety_2.defs.observed_equivocation_def, abstract_accountable_safety_2.defs.observed_unlawful_prevote_def diff --git a/spec/ivy-proofs/check_proofs.sh b/spec/ivy-proofs/check_proofs.sh new file mode 100755 index 00000000000..6afd1a962db --- /dev/null +++ b/spec/ivy-proofs/check_proofs.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# returns non-zero error code if any proof fails + +success=0 +log_dir=$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 6) +cmd="ivy_check seed=$RANDOM" +mkdir -p output/$log_dir + +echo "Checking classic safety:" +res=$($cmd classic_safety.ivy | tee "output/$log_dir/classic_safety.txt" | tail -n 1) +if [ "$res" = "OK" ]; then + echo "OK" +else + echo "FAILED" + success=1 +fi + +echo "Checking accountable safety 1:" +res=$($cmd accountable_safety_1.ivy | tee "output/$log_dir/accountable_safety_1.txt" | tail -n 1) +if [ "$res" = "OK" ]; then + echo "OK" +else + echo "FAILED" + success=1 +fi + +echo "Checking accountable safety 2:" +res=$($cmd complete=fo accountable_safety_2.ivy | tee "output/$log_dir/accountable_safety_2.txt" | tail -n 1) +if [ "$res" = "OK" ]; then + echo "OK" +else + echo "FAILED" + success=1 +fi + +echo +echo "See ivy_check output in the output/ folder" +exit $success diff --git a/spec/ivy-proofs/classic_safety.ivy b/spec/ivy-proofs/classic_safety.ivy new file mode 100644 index 00000000000..b422a2c175f --- /dev/null +++ b/spec/ivy-proofs/classic_safety.ivy @@ -0,0 +1,85 @@ +#lang ivy1.7 +# --- +# layout: page +# title: Proof of Classic Safety +# --- + +include tendermint +include abstract_tendermint + +# Here we prove the classic safety property: assuming that every two quorums +# have a well-behaved node in common, no two well-behaved nodes ever disagree. + +# The proof is done in two steps: first we prove the the abstract specification +# satisfies the property, and then we show by refinement that this property +# also holds in the concrete specification. + +# To see what is checked in the refinement proof, use `ivy_show isolate=classic_safety classic_safety.ivy` +# To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_classic_safety classic_safety.ivy` + +# To check the whole proof, use `ivy_check classic_safety.ivy`. + +# Note that all the verification conditions sent to Z3 for this proof are in +# EPR. + +# Classic safety in the abstract model +# ==================================== + +# We start by proving that classic safety holds in the abstract model. + +isolate abstract_classic_safety = { + + instantiate abstract_tendermint + + invariant [classic_safety] classic_bft.quorum_intersection & decided(N1,R1,V1) & decided(N2,R2,V2) -> V1 = V2 + +# The notion of choosable value +# ----------------------------- + + relation choosable(R:round, V:value) + definition choosable(R,V) = exists Q . nset.is_quorum(Q) & forall N . well_behaved(N) & nset.member(N,Q) -> ~left_round(N,R) | precommitted(N,R,V) + +# Main invariants +# --------------- + +# `classic_safety` is inductive relative to those invariants + + invariant [decision_is_quorum_precommit] (exists N1 . decided(N1,R,V)) -> exists Q. nset.is_quorum(Q) & forall N2. well_behaved(N2) & nset.member(N2, Q) -> precommitted(N2,R,V) + + invariant [precommitted_is_quorum_prevote] V ~= value.nil & (exists N1 . precommitted(N1,R,V)) -> exists Q. nset.is_quorum(Q) & forall N2. well_behaved(N2) & nset.member(N2, Q) -> prevoted(N2,R,V) + + invariant [prevote_unique_per_round] prevoted(N,R,V1) & prevoted(N,R,V2) -> V1 = V2 + +# This is the core invariant: as long as a precommitted value is still choosable, it remains protected by a lock and prevents any new value from being prevoted: + invariant [locks] classic_bft.quorum_intersection & V ~= value.nil & precommitted(N,R,V) & choosable(R,V) -> locked(N,R,V) & forall R2,V2 . R < R2 & prevoted(N,R2,V2) -> V2 = V | V2 = value.nil + +# Supporting invariants +# --------------------- + +# The main invariants are inductive relative to those + + invariant decided(N,R,V) -> V ~= value.nil + + invariant left_round(N,R2) & R1 < R2 -> left_round(N,R1) # if a node left round R2>R1, then it also left R1: + + invariant prevoted(N,R2,V2) & R1 < R2 -> left_round(N,R1) + invariant precommitted(N,R2,V2) & R1 < R2 -> left_round(N,R1) + +} with round, nset, classic_bft.quorum_intersection_def + +# The refinement proof +# ==================== + +# Now, thanks to the refinement relation that we establish in +# `concrete_tendermint.ivy`, we prove that classic safety transfers to the +# concrete specification: +isolate classic_safety = { + + # We instantiate the `tendermint` module providing `abstract_classic_safety` as abstract model. + instantiate tendermint(abstract_classic_safety) + + # We prove that if every two quorums have a well-behaved node in common, + # then well-behaved nodes never disagree: + invariant [classic_safety] classic_bft.quorum_intersection & server.decision(N1) ~= value.nil & server.decision(N2) ~= value.nil -> server.decision(N1) = server.decision(N2) + +} with value, round, proposers, shim, abstract_classic_safety # here we list all the specifications that we rely on for this proof diff --git a/spec/ivy-proofs/count_lines.sh b/spec/ivy-proofs/count_lines.sh new file mode 100755 index 00000000000..b2c457e21af --- /dev/null +++ b/spec/ivy-proofs/count_lines.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +r='^\s*$\|^\s*\#\|^\s*\}\s*$\|^\s*{\s*$' # removes comments and blank lines and lines that contain only { or } +N1=`cat tendermint.ivy domain_model.ivy network_shim.ivy | grep -v $r'\|.*invariant.*' | wc -l` +N2=`cat abstract_tendermint.ivy | grep "observed_" | wc -l` # the observed_* variables specify the observations of the nodes +SPEC_LINES=`expr $N1 + $N2` +echo "spec lines: $SPEC_LINES" +N3=`cat abstract_tendermint.ivy | grep -v $r'\|.*observed_.*' | wc -l` +N4=`cat accountable_safety_1.ivy | grep -v $r | wc -l` +PROOF_LINES=`expr $N3 + $N4` +echo "proof lines: $PROOF_LINES" +RATIO=`bc <<< "scale=2;$PROOF_LINES / $SPEC_LINES"` +echo "proof-to-code ratio for the accountable-safety property: $RATIO" diff --git a/spec/ivy-proofs/docker-compose.yml b/spec/ivy-proofs/docker-compose.yml new file mode 100644 index 00000000000..1d4a8ffe146 --- /dev/null +++ b/spec/ivy-proofs/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3' +services: + tendermint-proof: + build: . + volumes: + - ./:/home/user/tendermint-proof:ro + - ./output:/home/user/tendermint-proof/output:rw + diff --git a/spec/ivy-proofs/domain_model.ivy b/spec/ivy-proofs/domain_model.ivy new file mode 100644 index 00000000000..0f12f7288aa --- /dev/null +++ b/spec/ivy-proofs/domain_model.ivy @@ -0,0 +1,143 @@ +#lang ivy1.7 + +include order # this is a file from the standard library (`ivy/ivy/include/1.7/order.ivy`) + +isolate round = { + type this + individual minus_one:this + relation succ(R1:round, R2:round) + action incr(i:this) returns (j:this) + specification { +# to simplify verification, we treat rounds as an abstract totally ordered set with a successor relation. + instantiate totally_ordered(this) + property minus_one < 0 + property succ(X,Z) -> (X < Z & ~(X < Y & Y < Z)) + after incr { + ensure succ(i,j) + } + } + implementation { +# here we prove that the abstraction is sound. + interpret this -> int # rounds are integers in the Tendermint specification. + definition minus_one = 0-1 + definition succ(R1,R2) = R2 = R1 + 1 + implement incr { + j := i+1; + } + } +} + +instance node : iterable # nodes are a set with an order, that can be iterated over (see order.ivy in the standard library) + +relation well_behaved(N:node) # whether a node is well-behaved or not. NOTE: Used only in the proof and the Byzantine model; Nodes do know know who is well-behaved and who is not. + +isolate proposers = { + # each round has a unique proposer in Tendermint. In order to avoid a + # function from round to node (which makes verification more difficult), we + # abstract over this function using a relation. + relation is_proposer(N:node, R:round) + export action get_proposer(r:round) returns (n:node) + specification { + property is_proposer(N1,R) & is_proposer(N2,R) -> N1 = N2 + after get_proposer { + ensure is_proposer(n,r); + } + } + implementation { + function f(R:round):node + definition f(r:round) = <<>> + definition is_proposer(N,R) = N = f(R) + implement get_proposer { + n := f(r); + } + } +} + +isolate value = { # the type of values + type this + relation valid(V:value) + individual nil:value + specification { + property ~valid(nil) + } + implementation { + interpret value -> bv[2] + definition nil = <<< -1 >>> # let's say nil is -1 + definition valid(V) = V ~= nil + } +} + +object nset = { # the type of node sets + type this # a set of N=3f+i nodes for 0 + #include + namespace hash_space { + template + class hash > { + public: + size_t operator()(const std::set &s) const { + hash h; + size_t res = 0; + for (const T &e : s) + res += h(e); + return res; + } + }; + } + >>> + interpret nset -> <<< std::set<`node`> >>> + definition member(n:node, s:nset) = <<< `s`.find(`n`) != `s`.end() >>> + definition is_quorum(s:nset) = <<< 3*`s`.size() > 2*`node.size` >>> + definition is_blocking(s:nset) = <<< 3*`s`.size() > `node.size` >>> + implement empty { + <<< + >>> + } + implement insert { + <<< + `t` = `s`; + `t`.insert(`n`); + >>> + } + <<< encode `nset` + + std::ostream &operator <<(std::ostream &s, const `nset` &a) { + s << "{"; + for (auto iter = a.begin(); iter != a.end(); iter++) { + if (iter != a.begin()) s << ", "; + s << *iter; + } + s << "}"; + return s; + } + + template <> + `nset` _arg<`nset`>(std::vector &args, unsigned idx, long long bound) { + throw std::invalid_argument("Not implemented"); // no syntax for nset values in the REPL + } + + >>> + } +} + +object classic_bft = { + relation quorum_intersection + private { + definition [quorum_intersection_def] quorum_intersection = forall Q1,Q2. exists N. well_behaved(N) & nset.member(N, Q1) & nset.member(N, Q2) # every two quorums have a well-behaved node in common + } +} + +trusted isolate accountable_bft = { + # this is our baseline assumption about quorums: + private { + property [max_2f_byzantine] exists N . well_behaved(N) & nset.member(N,Q) # every quorum has a well-behaved member + } +} diff --git a/spec/ivy-proofs/network_shim.ivy b/spec/ivy-proofs/network_shim.ivy new file mode 100644 index 00000000000..ebc3a04fce1 --- /dev/null +++ b/spec/ivy-proofs/network_shim.ivy @@ -0,0 +1,133 @@ +#lang ivy1.7 +# --- +# layout: page +# title: Network model and network shim +# --- + +# Here we define a network module, which is our model of the network, and a +# shim module that sits on top of the network and which, upon receiving a +# message, calls the appropriate protocol handler. + +include domain_model + +# Here we define an enumeration type for identifying the 3 different types of +# messages that nodes send. +object msg_kind = { # TODO: merge with step_t + type this = {proposal, prevote, precommit} +} + +# Here we define the type of messages `msg`. Its members are structs with the fields described below. +object msg = { + type this = struct { + m_kind : msg_kind, + m_src : node, + m_round : round, + m_value : value, + m_vround : round + } +} + +# This is our model of the network: +isolate net = { + + export action recv(dst:node,v:msg) + action send(src:node,dst:node,v:msg) + # Note that the `recv` action is exported, meaning that it can be called + # non-deterministically by the environment any time it is enabled. In other + # words, a packet that is in flight can be received at any time. In this + # sense, the network is fully asynchronous. Moreover, there is no + # requirement that a given message will be received at all. + + # The state of the network consists of all the packets that have been + # sent so far, along with their destination. + relation sent(V:msg, N:node) + + after init { + sent(V, N) := false + } + + before send { + sent(v,dst) := true + } + + before recv { + require sent(v,dst) # only sent messages can be received. + } +} + +# The network shim sits on top of the network and, upon receiving a message, +# calls the appropriate protocol handler. It also exposes a `broadcast` action +# that sends to all nodes. + +isolate shim = { + + # In order not repeat the same code for each handler, we use a handler + # module parameterized by the type of message it will handle. Below we + # instantiate this module for the 3 types of messages of Tendermint + module handler(p_kind) = { + action handle(dst:node,m:msg) + object spec = { + before handle { + assert sent(m,dst) & m.m_kind = p_kind + } + } + } + + instance proposal_handler : handler(msg_kind.proposal) + instance prevote_handler : handler(msg_kind.prevote) + instance precommit_handler : handler(msg_kind.precommit) + + relation sent(M:msg,N:node) + + action broadcast(src:node,m:msg) + action send(src:node,dst:node,m:msg) + + specification { + after init { + sent(M,D) := false; + } + before broadcast { + sent(m,D) := true + } + before send { + sent(m,dst) := true + } + } + + # Here we give an implementation of it that satisfies its specification: + implementation { + + implement net.recv(dst:node,m:msg) { + + if m.m_kind = msg_kind.proposal { + call proposal_handler.handle(dst,m) + } + else if m.m_kind = msg_kind.prevote { + call prevote_handler.handle(dst,m) + } + else if m.m_kind = msg_kind.precommit { + call precommit_handler.handle(dst,m) + } + } + + implement broadcast { # broadcast sends to all nodes, including the sender. + var iter := node.iter.create(0); + while ~iter.is_end + invariant net.sent(M,D) -> sent(M,D) + { + var n := iter.val; + call net.send(src,n,m); + iter := iter.next; + } + } + + implement send { + call net.send(src,dst,m) + } + + private { + invariant net.sent(M,D) -> sent(M,D) + } + } + +} with net, node # to prove that the shim implementation satisfies the shim specification, we rely on the specification of net and node. diff --git a/spec/ivy-proofs/output/.gitignore b/spec/ivy-proofs/output/.gitignore new file mode 100644 index 00000000000..5e7d2734cfc --- /dev/null +++ b/spec/ivy-proofs/output/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/spec/ivy-proofs/tendermint.ivy b/spec/ivy-proofs/tendermint.ivy new file mode 100644 index 00000000000..b7678bef986 --- /dev/null +++ b/spec/ivy-proofs/tendermint.ivy @@ -0,0 +1,420 @@ +#lang ivy1.7 +# --- +# layout: page +# title: Specification of Tendermint in Ivy +# --- + +# This specification closely follows the pseudo-code given in "The latest +# gossip on BFT consensus" by E. Buchman, J. Kwon, Z. Milosevic +# + +include domain_model +include network_shim + +# We model the Tendermint protocol as an Ivy object. Like in Object-Oriented +# Programming, the basic structuring unit in Ivy is the object. Objects have +# internal state and actions (i.e. methods in OO parlance) that modify their +# state. We model Tendermint as an object whose actions represent steps taken +# by individual nodes in the protocol. Actions in Ivy can have preconditions, +# and a valid execution is a sequence of actions whose preconditions are all +# satisfied in the state in which they are called. + +# For technical reasons, we define below a `tendermint` module instead of an +# object. Ivy modules are a little bit like classes in OO programs, and like +# classes they can be instantiated to obtain objects. To instantiate the +# `tendermint` module, we must provide an abstract-protocol object. This allows +# us to use different abstract-protocol objects for different parts of the +# proof, and to do so without too much notational burden (we could have used +# Ivy monitors, but then we would need to prefix every variable name by the +# name of the object containing it, which clutters things a bit compared to the +# approach we took). + +# The abstract-protocol object is called by the resulting tendermint object so +# as to run the abstract protocol alongside the concrete protocol. This allows +# us to transfer properties proved of the abstract protocol to the concrete +# protocol, as follows. First, we prove that running the abstract protocol in +# this way results in a valid execution of the abstract protocol. This is done +# by checking that all preconditions of the abstract actions are satisfied at +# their call sites. Second, we establish a relation between abstract state and +# concrete state (in the form of invariants of the resulting, two-object +# transition system) that allow us to transfer properties proved in the +# abstract protocol to the concrete protocol (for example, we prove that any +# decision made in the Tendermint protocol is also made in the abstract +# protocol; if the abstract protocol satisfies the agreement property, this +# allows us to conclude that the Tendermint protocol also does). + +# The abstract protocol object that we will use is always the same, and only +# the abstract properties that we prove about it change in the different +# instantiations of the `tendermint` module. Thus we provide common invariants +# that a) allow to prove that the abstract preconditions are met, and b) +# provide a refinement relation (see end of the module) relating the state of +# Tendermint to the state of the abstract protocol. + +# In the model, Byzantine nodes can send whatever messages they want, except +# that they cannot forge sender identities. This reflects the fact that, in +# practice, nodes use public key cryptography to sign their messages. + +# Finally, note that the observations that serve to adjudicate misbehavior are +# defined only in the abstract protocol (they happen in the abstract actions). + +module tendermint(abstract_protocol) = { + + # the initial value of a node: + function init_val(N:node): value + + # the three type of steps + object step_t = { + type this = {propose, prevote, precommit} + } # refer to those e.g. as step_t.propose + + object server(n:node) = { + + # the current round of a node + individual round_p: round + + individual step: step_t + + individual decision: value + + individual lockedValue: value + individual lockedRound: round + + individual validValue: value + individual validRound: round + + + relation done_l34(R:round) + relation done_l36(R:round, V:value) + relation done_l47(R:round) + + # variables for scheduling request + relation propose_timer_scheduled(R:round) + relation prevote_timer_scheduled(R:round) + relation precommit_timer_scheduled(R:round) + + relation _recved_proposal(Sender:node, R:round, V:value, VR:round) + relation _recved_prevote(Sender:node, R:round, V:value) + relation _recved_precommit(Sender:node, R:round, V:value) + + relation _has_started + + after init { + round_p := 0; + step := step_t.propose; + decision := value.nil; + + lockedValue := value.nil; + lockedRound := round.minus_one; + + validValue := value.nil; + validRound := round.minus_one; + + done_l34(R) := false; + done_l36(R, V) := false; + done_l47(R) := false; + + propose_timer_scheduled(R) := false; + prevote_timer_scheduled(R) := false; + precommit_timer_scheduled(R) := false; + + _recved_proposal(Sender, R, V, VR) := false; + _recved_prevote(Sender, R, V) := false; + _recved_precommit(Sender, R, V) := false; + + _has_started := false; + } + + action getValue returns (v:value) = { + v := init_val(n) + } + + export action start = { + require ~_has_started; + _has_started := true; + # line 10 + call startRound(0); + } + + # line 11-21 + action startRound(r:round) = { + # line 12 + round_p := r; + + # line 13 + step := step_t.propose; + + var proposal : value; + + # line 14 + if (proposers.get_proposer(r) = n) { + if validValue ~= value.nil { # line 15 + proposal := validValue; # line 16 + } else { + proposal := getValue(); # line 18 + }; + call broadcast_proposal(r, proposal, validRound); # line 19 + } else { + propose_timer_scheduled(r) := true; # line 21 + }; + + call abstract_protocol.l_11(n, r); + } + + # This action, as not exported, can only be called at specific call sites. + action broadcast_proposal(r:round, v:value, vr:round) = { + var m: msg; + m.m_kind := msg_kind.proposal; + m.m_src := n; + m.m_round := r; + m.m_value := v; + m.m_vround := vr; + call shim.broadcast(n,m); + } + + implement shim.proposal_handler.handle(msg:msg) { + _recved_proposal(msg.m_src, msg.m_round, msg.m_value, msg.m_vround) := true; + } + + # line 22-27 + export action l_22(v:value) = { + require _has_started; + require _recved_proposal(proposers.get_proposer(round_p), round_p, v, round.minus_one); + require step = step_t.propose; + + if (value.valid(v) & (lockedRound = round.minus_one | lockedValue = v)) { + call broadcast_prevote(round_p, v); # line 24 + call abstract_protocol.l_22(n, round_p, v); + } else { + call broadcast_prevote(round_p, value.nil); # line 26 + call abstract_protocol.l_22(n, round_p, value.nil); + }; + + # line 27 + step := step_t.prevote; + } + + # line 28-33 + export action l_28(r:round, v:value, vr:round, q:nset) = { + require _has_started; + require r = round_p; + require _recved_proposal(proposers.get_proposer(r), r, v, vr); + require nset.is_quorum(q); + require nset.member(N,q) -> _recved_prevote(N,vr,v); + require step = step_t.propose; + require vr >= 0 & vr < r; + + # line 29 + if (value.valid(v) & (lockedRound <= vr | lockedValue = v)) { + call broadcast_prevote(r, v); + } else { + call broadcast_prevote(r, value.nil); + }; + + call abstract_protocol.l_28(n,r,v,vr,q); + step := step_t.prevote; + } + + action broadcast_prevote(r:round, v:value) = { + var m: msg; + m.m_kind := msg_kind.prevote; + m.m_src := n; + m.m_round := r; + m.m_value := v; + call shim.broadcast(n,m); + } + + implement shim.prevote_handler.handle(msg:msg) { + _recved_prevote(msg.m_src, msg.m_round, msg.m_value) := true; + } + + # line 34-35 + export action l_34(r:round, q:nset) = { + require _has_started; + require round_p = r; + require nset.is_quorum(q); + require exists V . nset.member(N,q) -> _recved_prevote(N,r,V); + require step = step_t.prevote; + require ~done_l34(r); + done_l34(r) := true; + + prevote_timer_scheduled(r) := true; + } + + + # line 36-43 + export action l_36(r:round, v:value, q:nset) = { + require _has_started; + require r = round_p; + require exists VR . round.minus_one <= VR & VR < r & _recved_proposal(proposers.get_proposer(r), r, v, VR); + require nset.is_quorum(q); + require nset.member(N,q) -> _recved_prevote(N,r,v); + require value.valid(v); + require step = step_t.prevote | step = step_t.precommit; + + require ~done_l36(r,v); + done_l36(r, v) := true; + + if step = step_t.prevote { + lockedValue := v; # line 38 + lockedRound := r; # line 39 + call broadcast_precommit(r, v); # line 40 + step := step_t.precommit; # line 41 + call abstract_protocol.l_36(n, r, v, q); + }; + + validValue := v; # line 42 + validRound := r; # line 43 + } + + # line 44-46 + export action l_44(r:round, q:nset) = { + require _has_started; + require r = round_p; + require nset.is_quorum(q); + require nset.member(N,q) -> _recved_prevote(N,r,value.nil); + require step = step_t.prevote; + + call broadcast_precommit(r, value.nil); # line 45 + step := step_t.precommit; # line 46 + + call abstract_protocol.l_44(n, r, q); + } + + action broadcast_precommit(r:round, v:value) = { + var m: msg; + m.m_kind := msg_kind.precommit; + m.m_src := n; + m.m_round := r; + m.m_value := v; + call shim.broadcast(n,m); + } + + implement shim.precommit_handler.handle(msg:msg) { + _recved_precommit(msg.m_src, msg.m_round, msg.m_value) := true; + } + + + # line 47-48 + export action l_47(r:round, q:nset) = { + require _has_started; + require round_p = r; + require nset.is_quorum(q); + require nset.member(N,q) -> exists V . _recved_precommit(N,r,V); + require ~done_l47(r); + done_l47(r) := true; + + precommit_timer_scheduled(r) := true; + } + + + # line 49-54 + export action l_49_decide(r:round, v:value, q:nset) = { + require _has_started; + require exists VR . round.minus_one <= VR & VR < r & _recved_proposal(proposers.get_proposer(r), r, v, VR); + require nset.is_quorum(q); + require nset.member(N,q) -> _recved_precommit(N,r,v); + require decision = value.nil; + + if value.valid(v) { + decision := v; + # MORE for next height + call abstract_protocol.decide(n, r, v, q); + } + } + + # line 55-56 + export action l_55(r:round, b:nset) = { + require _has_started; + require nset.is_blocking(b); + require nset.member(N,b) -> exists VR . round.minus_one <= VR & VR < r & exists V . _recved_proposal(N,r,V,VR) | _recved_prevote(N,r,V) | _recved_precommit(N,r,V); + require r > round_p; + call startRound(r); # line 56 + } + + # line 57-60 + export action onTimeoutPropose(r:round) = { + require _has_started; + require propose_timer_scheduled(r); + require r = round_p; + require step = step_t.propose; + call broadcast_prevote(r,value.nil); + step := step_t.prevote; + + call abstract_protocol.l_57(n,r); + + propose_timer_scheduled(r) := false; + } + + # line 61-64 + export action onTimeoutPrevote(r:round) = { + require _has_started; + require prevote_timer_scheduled(r); + require r = round_p; + require step = step_t.prevote; + call broadcast_precommit(r,value.nil); + step := step_t.precommit; + + call abstract_protocol.l_61(n,r); + + prevote_timer_scheduled(r) := false; + } + + # line 65-67 + export action onTimeoutPrecommit(r:round) = { + require _has_started; + require precommit_timer_scheduled(r); + require r = round_p; + call startRound(round.incr(r)); + + precommit_timer_scheduled(r) := false; + } + +# The Byzantine actions +# --------------------- + +# Byzantine nodes can send whatever they want, but they cannot send +# messages on behalf of well-behaved nodes. In practice this is implemented +# using cryptography (e.g. public-key cryptography). + + export action byzantine_send(m:msg, dst:node) = { + require ~well_behaved(n); + require ~well_behaved(m.m_src); # cannot forge the identity of well-behaved nodes + call shim.send(n,dst,m); + } + +# Byzantine nodes can also report fake observations, as defined in the abstract protocol. + export action fake_observations = { + call abstract_protocol.misbehave + } + +# Invariants +# ---------- + +# We provide common invariants that a) allow to prove that the abstract +# preconditions are met, and b) provide a refinement relation. + + + specification { + + invariant 0 <= round_p + invariant abstract_protocol.left_round(n,R) <-> R < round_p + + invariant lockedRound ~= round.minus_one -> forall R,V . abstract_protocol.locked(n,R,V) <-> R <= lockedRound & lockedValue = V + invariant lockedRound = round.minus_one -> forall R,V . ~abstract_protocol.locked(n,R,V) + + invariant forall M:msg . well_behaved(M.m_src) & M.m_kind = msg_kind.prevote & shim.sent(M,N) -> abstract_protocol.prevoted(M.m_src,M.m_round,M.m_value) + invariant well_behaved(N) & _recved_prevote(N,R,V) -> abstract_protocol.prevoted(N,R,V) + invariant forall M:msg . well_behaved(M.m_src) & M.m_kind = msg_kind.precommit & shim.sent(M,N) -> abstract_protocol.precommitted(M.m_src,M.m_round,M.m_value) + invariant well_behaved(N) & _recved_precommit(N,R,V) -> abstract_protocol.precommitted(N,R,V) + + invariant (step = step_t.prevote | step = step_t.propose) -> ~abstract_protocol.precommitted(n,round_p,V) + invariant step = step_t.propose -> ~abstract_protocol.prevoted(n,round_p,V) + invariant step = step_t.prevote -> exists V . abstract_protocol.prevoted(n,round_p,V) + + invariant round_p < R -> ~(abstract_protocol.prevoted(n,R,V) | abstract_protocol.precommitted(n,R,V)) + invariant ~_has_started -> step = step_t.propose & ~(abstract_protocol.prevoted(n,R,V) | abstract_protocol.precommitted(n,R,V)) & round_p = 0 + + invariant decision ~= value.nil -> exists R . abstract_protocol.decided(n,R,decision) + } + } +} diff --git a/spec/ivy-proofs/tendermint_test.ivy b/spec/ivy-proofs/tendermint_test.ivy new file mode 100644 index 00000000000..1299fc086d3 --- /dev/null +++ b/spec/ivy-proofs/tendermint_test.ivy @@ -0,0 +1,127 @@ +#lang ivy1.7 + +include tendermint +include abstract_tendermint + +isolate ghost_ = { + instantiate abstract_tendermint +} + +isolate protocol = { + instantiate tendermint(ghost_) # here we instantiate the parameter of the tendermint module with `ghost_`; however note that we don't extract any code for `ghost_` (it's not in the list of object in the extract, and it's thus sliced away). + implementation { + definition init_val(n:node) = <<< `n`%2 >>> + } + # attribute test = impl +} with ghost_, shim, value, round, proposers + +# Here we run a simple scenario that exhibits an execution in which nodes make +# a decision. We do this to rule out trivial modeling errors. + +# One option to check that this scenario is valid is to run it in Ivy's REPL. +# For this, first compile the scenario: +#```ivyc target=repl isolate=code trace=true tendermint_test.ivy +# Then, run the produced binary (e.g. for 4 nodes): +#``` ./tendermint_test 4 +# Finally, call the action: +#``` scenarios.scenario_1 +# Note that Ivy will check at runtime that all action preconditions are +# satisfied. For example, runing the scenario twice will cause a violation of +# the precondition of the `start` action, because a node cannot start twice +# (see `require ~_has_started` in action `start`). + +# Another possibility would be to run `ivy_check` on the scenario, but that +# does not seem to work at the moment. + +isolate scenarios = { + individual all:nset # will be used as parameter to actions requiring a quorum + + after init { + var iter := node.iter.create(0); + while ~iter.is_end + { + all := all.insert(iter.val); + iter := iter.next; + }; + assert nset.is_quorum(all); # we can also use asserts to make sure we are getting what we expect + } + + export action scenario_1 = { + # all nodes start: + var iter := node.iter.create(0); + while ~iter.is_end + { + call protocol.server.start(iter.val); + iter := iter.next; + }; + # all nodes receive the leader's proposal: + var m:msg; + m.m_kind := msg_kind.proposal; + m.m_src := 0; + m.m_round := 0; + m.m_value := 0; + m.m_vround := round.minus_one; + iter := node.iter.create(0); + while ~iter.is_end + { + call net.recv(iter.val,m); + iter := iter.next; + }; + # all nodes prevote: + iter := node.iter.create(0); + while ~iter.is_end + { + call protocol.server.l_22(iter.val,0); + iter := iter.next; + }; + # all nodes receive each other's prevote messages; + m.m_kind := msg_kind.prevote; + m.m_vround := 0; + iter := node.iter.create(0); + while ~iter.is_end + { + var iter2 := node.iter.create(0); # the sender + while ~iter2.is_end + { + m.m_src := iter2.val; + call net.recv(iter.val,m); + iter2 := iter2.next; + }; + iter := iter.next; + }; + # all nodes precommit: + iter := node.iter.create(0); + while ~iter.is_end + { + call protocol.server.l_36(iter.val,0,0,all); + iter := iter.next; + }; + # all nodes receive each other's pre-commits + m.m_kind := msg_kind.precommit; + iter := node.iter.create(0); + while ~iter.is_end + { + var iter2 := node.iter.create(0); # the sender + while ~iter2.is_end + { + m.m_src := iter2.val; + call net.recv(iter.val,m); + iter2 := iter2.next; + }; + iter := iter.next; + }; + # now all nodes can decide: + iter := node.iter.create(0); + while ~iter.is_end + { + call protocol.server.l_49_decide(iter.val,0,0,all); + iter := iter.next; + }; + } + + # TODO: add more scenarios + +} with round, node, proposers, value, nset, protocol, shim, net + +# extract code = protocol, shim, round, node +extract code = round, node, proposers, value, nset, protocol, shim, net, scenarios diff --git a/spec/light-client/README.md b/spec/light-client/README.md new file mode 100644 index 00000000000..42f20d46c19 --- /dev/null +++ b/spec/light-client/README.md @@ -0,0 +1,205 @@ +--- +order: 1 +parent: + title: Light Client + order: 5 +--- + +# Light Client Specification + +This directory contains work-in-progress English and TLA+ specifications for the Light Client +protocol. Implementations of the light client can be found in +[Rust](https://github.com/informalsystems/tendermint-rs/tree/master/light-client) and +[Go](https://github.com/tendermint/tendermint/tree/master/light). + +Light clients are assumed to be initialized once from a trusted source +with a trusted header and validator set. The light client +protocol allows a client to then securely update its trusted state by requesting and +verifying a minimal set of data from a network of full nodes (at least one of which is correct). + +The light client is decomposed into two main components: + +- [Commit Verification](#Commit-Verification) - verify signed headers and associated validator + set changes from a single full node, called primary +- [Attack Detection](#Attack-Detection) - verify commits across multiple full nodes (called secondaries) and detect conflicts (ie. the existence of a lightclient attack) + +In case a lightclient attack is detected, the lightclient submits evidence to a full node which is responsible for "accountability", that is, punishing attackers: + +- [Accountability](#Accountability) - given evidence for an attack, compute a set of validators that are responsible for it. + +## Commit Verification + +The [English specification](verification/verification_001_published.md) describes the light client +commit verification problem in terms of the temporal properties +[LCV-DIST-SAFE.1](https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification_001_published.md#lcv-dist-safe1) and +[LCV-DIST-LIVE.1](https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification_001_published.md#lcv-dist-live1). +Commit verification is assumed to operate within the Tendermint Failure Model, where +2/3 of validators are correct for some time period and +validator sets can change arbitrarily at each height. + +A light client protocol is also provided, including all checks that +need to be performed on headers, commits, and validator sets +to satisfy the temporal properties - so a light client can continuously +synchronize with a blockchain. Clients can skip possibly +many intermediate headers by exploiting overlap in trusted and untrusted validator sets. +When there is not enough overlap, a bisection routine can be used to find a +minimal set of headers that do provide the required overlap. + +The [TLA+ specification ver. 001](verification/Lightclient_A_1.tla) +is a formal description of the +commit verification protocol executed by a client, including the safety and +termination, which can be model checked with Apalache. + +A more detailed TLA+ specification of +[Light client verification ver. 003](verification/Lightclient_003_draft.tla) +is currently under peer review. + +The `MC*.tla` files contain concrete parameters for the +[TLA+ specification](verification/Lightclient_A_1.tla), in order to do model checking. +For instance, [MC4_3_faulty.tla](verification/MC4_3_faulty.tla) contains the following parameters +for the nodes, heights, the trusting period, the clock drifts, +correctness of the primary node, and the ratio of the faulty processes: + +```tla +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 3 +TRUSTING_PERIOD == 1400 \* the trusting period in some time units +CLOCK_DRIFT = 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT = 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators +``` + +To run a complete set of experiments, clone [apalache](https://github.com/informalsystems/apalache) and [apalache-tests](https://github.com/informalsystems/apalache-tests) into a directory `$DIR` and run the following commands: + +```sh +$DIR/apalache-tests/scripts/mk-run.py --memlimit 28 002bmc-apalache-ok.csv $DIR/apalache . out +./out/run-all.sh +``` + +After the experiments have finished, you can collect the logs by executing the following command: + +```sh +cd ./out +$DIR/apalache-tests/scripts/parse-logs.py --human . +``` + +All lines in `results.csv` should report `Deadlock`, which means that the algorithm +has terminated and no invariant violation was found. + +Similar to [002bmc-apalache-ok.csv](verification/002bmc-apalache-ok.csv), +file [003bmc-apalache-error.csv](verification/003bmc-apalache-error.csv) specifies +the set of experiments that should result in counterexamples: + +```sh +$DIR/apalache-tests/scripts/mk-run.py --memlimit 28 003bmc-apalache-error.csv $DIR/apalache . out +./out/run-all.sh +``` + +All lines in `results.csv` should report `Error`. + +The following table summarizes the experimental results for Light client verification +version 001. The TLA+ properties can be found in the +[TLA+ specification](verification/Lightclient_A_1.tla). + The experiments were run in an AWS instance equipped with 32GB +RAM and a 4-core Intel® Xeon® CPU E5-2686 v4 @ 2.30GHz CPU. +We write “✗=k” when a bug is reported at depth k, and “✓<=k” when +no bug is reported up to depth k. + +![Experimental results](experiments.png) + +The experimental results for version 003 are to be added. + +## Attack Detection + +The [English specification](detection/detection_003_reviewed.md) +defines light client attacks (and how they differ from blockchain +forks), and describes the problem of a light client detecting +these attacks by communicating with a network of full nodes, +where at least one is correct. + +The specification also contains a detection protocol that checks +whether the header obtained from the primary via the verification +protocol matches corresponding headers provided by the secondaries. +If this is not the case, the protocol analyses the verification traces +of the involved full nodes +and generates +[evidence](detection/detection_003_reviewed.md#tmbc-lc-evidence-data1) +of misbehavior that can be submitted to a full node so that +the faulty validators can be punished. + +The [TLA+ specification](detection/LCDetector_003_draft.tla) +is a formal description of the +detection protocol for two peers, including the safety and +termination, which can be model checked with Apalache. + +The `LCD_MC*.tla` files contain concrete parameters for the +[TLA+ specification](detection/LCDetector_003_draft.tla), +in order to run the model checker. +For instance, [LCD_MC4_4_faulty.tla](detection/MC4_4_faulty.tla) +contains the following parameters +for the nodes, heights, the trusting period, the clock drifts, +correctness of the nodes, and the ratio of the faulty processes: + +```tla +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 3 +TRUSTING_PERIOD == 1400 \* the trusting period in some time units +CLOCK_DRIFT = 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT = 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +IS_SECONDARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators +``` + +To run a complete set of experiments, clone [apalache](https://github.com/informalsystems/apalache) and [apalache-tests](https://github.com/informalsystems/apalache-tests) into a directory `$DIR` and run the following commands: + +```sh +$DIR/apalache-tests/scripts/mk-run.py --memlimit 28 004bmc-apalache-ok.csv $DIR/apalache . out +./out/run-all.sh +``` + +After the experiments have finished, you can collect the logs by executing the following command: + +```sh +cd ./out +$DIR/apalache-tests/scripts/parse-logs.py --human . +``` + +All lines in `results.csv` should report `Deadlock`, which means that the algorithm +has terminated and no invariant violation was found. + +Similar to [004bmc-apalache-ok.csv](verification/004bmc-apalache-ok.csv), +file [005bmc-apalache-error.csv](verification/005bmc-apalache-error.csv) specifies +the set of experiments that should result in counterexamples: + +```sh +$DIR/apalache-tests/scripts/mk-run.py --memlimit 28 005bmc-apalache-error.csv $DIR/apalache . out +./out/run-all.sh +``` + +All lines in `results.csv` should report `Error`. + +The detailed experimental results are to be added soon. + +## Accountability + +The [English specification](attacks/isolate-attackers_002_reviewed.md) +defines the protocol that is executed on a full node upon receiving attack [evidence](detection/detection_003_reviewed.md#tmbc-lc-evidence-data1) from a lightclient. In particular, the protocol handles three types of attacks + +- lunatic +- equivocation +- amnesia + +We discussed in the [last part](attacks/isolate-attackers_002_reviewed.md#Part-III---Completeness) of the English specification +that the non-lunatic cases are defined by having the same validator set in the conflicting blocks. For these cases, +computer-aided analysis of [Tendermint Consensus in TLA+](./accountability/README.md) shows that equivocation and amnesia capture all non-lunatic attacks. + +The [TLA+ specification](attacks/Isolation_001_draft.tla) +is a formal description of the +protocol, including the safety property, which can be model checked with Apalache. + +Similar to the other specifications, [MC_5_3.tla](attacks/MC_5_3.tla) contains concrete parameters to run the model checker. The specification can be checked within seconds. + +[tendermint-accountability](./accountability/README.md) diff --git a/spec/light-client/accountability/001indinv-apalache.csv b/spec/light-client/accountability/001indinv-apalache.csv new file mode 100644 index 00000000000..37c6aeda259 --- /dev/null +++ b/spec/light-client/accountability/001indinv-apalache.csv @@ -0,0 +1,13 @@ +no,filename,tool,timeout,init,inv,next,args +1,MC_n4_f1.tla,apalache,10h,TypedInv,TypedInv,,--length=1 --cinit=ConstInit +2,MC_n4_f2.tla,apalache,10h,TypedInv,TypedInv,,--length=1 --cinit=ConstInit +3,MC_n5_f1.tla,apalache,10h,TypedInv,TypedInv,,--length=1 --cinit=ConstInit +4,MC_n5_f2.tla,apalache,10h,TypedInv,TypedInv,,--length=1 --cinit=ConstInit +5,MC_n4_f1.tla,apalache,20h,Init,TypedInv,,--length=0 --cinit=ConstInit +6,MC_n4_f2.tla,apalache,20h,Init,TypedInv,,--length=0 --cinit=ConstInit +7,MC_n5_f1.tla,apalache,20h,Init,TypedInv,,--length=0 --cinit=ConstInit +8,MC_n5_f2.tla,apalache,20h,Init,TypedInv,,--length=0 --cinit=ConstInit +9,MC_n4_f1.tla,apalache,20h,TypedInv,Agreement,,--length=0 --cinit=ConstInit +10,MC_n4_f2.tla,apalache,20h,TypedInv,Accountability,,--length=0 --cinit=ConstInit +11,MC_n5_f1.tla,apalache,20h,TypedInv,Agreement,,--length=0 --cinit=ConstInit +12,MC_n5_f2.tla,apalache,20h,TypedInv,Accountability,,--length=0 --cinit=ConstInit diff --git a/spec/light-client/accountability/MC_n4_f1.tla b/spec/light-client/accountability/MC_n4_f1.tla new file mode 100644 index 00000000000..7a828b49864 --- /dev/null +++ b/spec/light-client/accountability/MC_n4_f1.tla @@ -0,0 +1,22 @@ +----------------------------- MODULE MC_n4_f1 ------------------------------- +CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N + +\* the variables declared in TendermintAcc3 +VARIABLES + round, step, decision, lockedValue, lockedRound, validValue, validRound, + msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + +INSTANCE TendermintAccDebug_004_draft WITH + Corr <- {"c1", "c2", "c3"}, + Faulty <- {"f1"}, + N <- 4, + T <- 1, + ValidValues <- { "v0", "v1" }, + InvalidValues <- {"v2"}, + MaxRound <- 2 + +\* run Apalache with --cinit=ConstInit +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 new file mode 100644 index 00000000000..893f18db634 --- /dev/null +++ b/spec/light-client/accountability/MC_n4_f2.tla @@ -0,0 +1,22 @@ +----------------------------- MODULE MC_n4_f2 ------------------------------- +CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N + +\* the variables declared in TendermintAcc3 +VARIABLES + round, step, decision, lockedValue, lockedRound, validValue, validRound, + msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + +INSTANCE TendermintAccDebug_004_draft WITH + Corr <- {"c1", "c2"}, + Faulty <- {"f3", "f4"}, + N <- 4, + T <- 1, + ValidValues <- { "v0", "v1" }, + InvalidValues <- {"v2"}, + MaxRound <- 2 + +\* run Apalache with --cinit=ConstInit +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 new file mode 100644 index 00000000000..434fffaeb4e --- /dev/null +++ b/spec/light-client/accountability/MC_n4_f2_amnesia.tla @@ -0,0 +1,40 @@ +---------------------- MODULE MC_n4_f2_amnesia ------------------------------- +EXTENDS Sequences + +CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N + +\* the variables declared in TendermintAcc3 +VARIABLES + round, step, decision, lockedValue, lockedRound, validValue, validRound, + msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + +\* the variable declared in TendermintAccTrace3 +VARIABLE + toReplay + +\* old apalache annotations, fix with the new release +a <: b == a + +INSTANCE TendermintAccTrace_004_draft WITH + Corr <- {"c1", "c2"}, + Faulty <- {"f3", "f4"}, + N <- 4, + T <- 1, + ValidValues <- { "v0", "v1" }, + InvalidValues <- {"v2"}, + MaxRound <- 2, + Trace <- << + "UponProposalInPropose", + "UponProposalInPrevoteOrCommitAndPrevote", + "UponProposalInPrecommitNoDecision", + "OnRoundCatchup", + "UponProposalInPropose", + "UponProposalInPrevoteOrCommitAndPrevote", + "UponProposalInPrecommitNoDecision" + >> <: Seq(STRING) + +\* run Apalache with --cinit=ConstInit +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 new file mode 100644 index 00000000000..b794fff5ed1 --- /dev/null +++ b/spec/light-client/accountability/MC_n4_f3.tla @@ -0,0 +1,22 @@ +----------------------------- MODULE MC_n4_f3 ------------------------------- +CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N + +\* the variables declared in TendermintAcc3 +VARIABLES + round, step, decision, lockedValue, lockedRound, validValue, validRound, + msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + +INSTANCE TendermintAccDebug_004_draft WITH + Corr <- {"c1"}, + Faulty <- {"f2", "f3", "f4"}, + N <- 4, + T <- 1, + ValidValues <- { "v0", "v1" }, + InvalidValues <- {"v2"}, + MaxRound <- 2 + +\* run Apalache with --cinit=ConstInit +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 new file mode 100644 index 00000000000..d65673a58d7 --- /dev/null +++ b/spec/light-client/accountability/MC_n5_f1.tla @@ -0,0 +1,22 @@ +----------------------------- MODULE MC_n5_f1 ------------------------------- +CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N + +\* the variables declared in TendermintAcc3 +VARIABLES + round, step, decision, lockedValue, lockedRound, validValue, validRound, + msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + +INSTANCE TendermintAccDebug_004_draft WITH + Corr <- {"c1", "c2", "c3", "c4"}, + Faulty <- {"f5"}, + N <- 5, + T <- 1, + ValidValues <- { "v0", "v1" }, + InvalidValues <- {"v2"}, + MaxRound <- 2 + +\* run Apalache with --cinit=ConstInit +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 new file mode 100644 index 00000000000..c19aa98cc67 --- /dev/null +++ b/spec/light-client/accountability/MC_n5_f2.tla @@ -0,0 +1,22 @@ +----------------------------- MODULE MC_n5_f2 ------------------------------- +CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N + +\* the variables declared in TendermintAcc3 +VARIABLES + round, step, decision, lockedValue, lockedRound, validValue, validRound, + msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + +INSTANCE TendermintAccDebug_004_draft WITH + Corr <- {"c1", "c2", "c3"}, + Faulty <- {"f4", "f5"}, + N <- 5, + T <- 1, + ValidValues <- { "v0", "v1" }, + InvalidValues <- {"v2"}, + MaxRound <- 2 + +\* run Apalache with --cinit=ConstInit +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 new file mode 100644 index 00000000000..2e992974f51 --- /dev/null +++ b/spec/light-client/accountability/MC_n6_f1.tla @@ -0,0 +1,22 @@ +----------------------------- MODULE MC_n6_f1 ------------------------------- +CONSTANT Proposer \* the proposer function from 0..NRounds to 1..N + +\* the variables declared in TendermintAcc3 +VARIABLES + round, step, decision, lockedValue, lockedRound, validValue, validRound, + msgsPropose, msgsPrevote, msgsPrecommit, evidence, action + +INSTANCE TendermintAccDebug_004_draft WITH + Corr <- {"c1", "c2", "c3", "c4", "c5"}, + Faulty <- {"f6"}, + N <- 4, + T <- 1, + ValidValues <- { "v0", "v1" }, + InvalidValues <- {"v2"}, + MaxRound <- 2 + +\* run Apalache with --cinit=ConstInit +ConstInit == \* the proposer is arbitrary -- works for safety + Proposer \in [Rounds -> AllProcs] + +============================================================================= diff --git a/spec/light-client/accountability/README.md b/spec/light-client/accountability/README.md new file mode 100644 index 00000000000..bb872649bb9 --- /dev/null +++ b/spec/light-client/accountability/README.md @@ -0,0 +1,308 @@ +--- +order: 1 +parent: + title: Accountability + order: 4 +--- + +# Fork accountability + +## Problem Statement + +Tendermint consensus guarantees the following specifications for all heights: + +* agreement -- no two correct full nodes decide differently. +* validity -- the decided block satisfies the predefined predicate *valid()*. +* termination -- all correct full nodes eventually decide, + +If the faulty validators have less than 1/3 of voting power in the current validator set. In the case where this assumption +does not hold, each of the specification may be violated. + +The agreement property says that for a given height, any two correct validators that decide on a block for that height decide on the same block. That the block was indeed generated by the blockchain, can be verified starting from a trusted (genesis) block, and checking that all subsequent blocks are properly signed. + +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. + +**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. + +**Q:** Should we distinguish agreement for validators and full nodes for agreement? The case where all correct validators agree on a block, but a correct full node decides on a different block seems to be slightly less severe that the case where two correct validators decide on different blocks. Still, if a contaminated full node becomes validator that may be problematic later on. Also it is not clear how gossiping is impaired if a contaminated full node is on a different branch. + +*Remark.* In the case 1/3 or more of the voting power belongs to faulty validators, also validity and termination can be broken. Termination can be broken if faulty processes just do not send the messages that are needed to make progress. Due to asynchrony, this is not punishable, because faulty validators can always claim they never received the messages that would have forced them to send messages. + +## The Misbehavior of Faulty Validators + +Forks are the result of faulty validators deviating from the protocol. In principle several such deviations can be detected without a fork actually occurring: + +1. double proposal: A faulty proposer proposes two different values (blocks) for the same height and the same round in Tendermint consensus. + +2. double signing: Tendermint consensus forces correct validators to prevote and precommit for at most one value per round. In case a faulty validator sends multiple prevote and/or precommit messages for different values for the same height/round, this is a misbehavior. + +3. lunatic validator: Tendermint consensus forces correct validators to prevote and precommit only for values *v* that satisfy *valid(v)*. If faulty validators prevote and precommit for *v* although *valid(v)=false* this is misbehavior. + +*Remark.* In isolation, Point 3 is an attack on validity (rather than agreement). However, the prevotes and precommits can then also be used to forge blocks. + +1. amnesia: Tendermint consensus has a locking mechanism. If a validator has some value v locked, then it can only prevote/precommit for v or nil. Sending prevote/precomit message for a different value v' (that is not nil) while holding lock on value v is misbehavior. + +2. spurious messages: In Tendermint consensus most of the message send instructions are guarded by threshold guards, e.g., one needs to receive *2f + 1* prevote messages to send precommit. Faulty validators may send precommit without having received the prevote messages. + +Independently of a fork happening, punishing this behavior might be important to prevent forks altogether. This should keep attackers from misbehaving: if less than 1/3 of the voting power is faulty, this misbehavior is detectable but will not lead to a safety violation. Thus, unless they have 1/3 or more (or in some cases more than 2/3) of the voting power attackers have the incentive to not misbehave. If attackers control too much voting power, we have to deal with forks, as discussed in this document. + +## Two types of forks + +* Fork-Full. Two correct validators decide on different blocks for the same height. Since also the next validator sets are decided upon, the correct validators may be partitioned to participate in two distinct branches of the forked chain. + +As in this case we have two different blocks (both having the same right/no right to exist), a central system invariant (one block per height decided by correct validators) is violated. As full nodes are contaminated in this case, the contamination can spread also to light clients. However, even without breaking this system invariant, light clients can be subject to a fork: + +* Fork-Light. All correct validators decide on the same block for height *h*, but faulty processes (validators or not), forge a different block for that height, in order to fool users (who use the light client). + +# Attack scenarios + +## On-chain attacks + +### Equivocation (one round) + +There are several scenarios in which forks might happen. The first is double signing within a round. + +* F1. Equivocation: faulty validators sign multiple vote messages (prevote and/or precommit) for different values *during the same round r* at a given height h. + +### Flip-flopping + +Tendermint consensus implements a locking mechanism: If a correct validator *p* receives proposal for value v and *2f + 1* prevotes for a value *id(v)* in round *r*, it locks *v* and remembers *r*. In this case, *p* also sends a precommit message for *id(v)*, which later may serve as proof that *p* locked *v*. +In subsequent rounds, *p* only sends prevote messages for a value it had previously locked. However, it is possible to change the locked value if in a future round *r' > r*, if the process receives proposal and *2f + 1* prevotes for a different value *v'*. In this case, *p* could send a prevote/precommit for *id(v')*. This algorithmic feature can be exploited in two ways: + +* F2. Faulty Flip-flopping (Amnesia): faulty validators precommit some value *id(v)* in round *r* (value *v* is locked in round *r*) and then prevote for different value *id(v')* in higher round *r' > r* without previously correctly unlocking value *v*. In this case faulty processes "forget" that they have locked value *v* and prevote some other value in the following rounds. +Some correct validators might have decided on *v* in *r*, and other correct validators decide on *v'* in *r'*. Here we can have branching on the main chain (Fork-Full). + +* F3. Correct Flip-flopping (Back to the past): There are some precommit messages signed by (correct) validators for value *id(v)* in round *r*. Still, *v* is not decided upon, and all processes move on to the next round. Then correct validators (correctly) lock and decide a different value *v'* in some round *r' > r*. And the correct validators continue; there is no branching on the main chain. +However, faulty validators may use the correct precommit messages from round *r* together with a posteriori generated faulty precommit messages for round *r* to forge a block for a value that was not decided on the main chain (Fork-Light). + +## Off-chain attacks + +F1-F3 may contaminate the state of full nodes (and even validators). Contaminated (but otherwise correct) full nodes may thus communicate faulty blocks to light clients. +Similarly, without actually interfering with the main chain, we can have the following: + +* F4. Phantom validators: faulty validators vote (sign prevote and precommit messages) in heights in which they are not part of the validator sets (at the main chain). + +* F5. Lunatic validator: faulty validator that sign vote messages to support (arbitrary) application state that is different from the application state that resulted from valid state transitions. + +## Types of victims + +We consider three types of potential attack victims: + +* FN: full node +* LCS: light client with sequential header verification +* LCB: light client with bisection based header verification + +F1 and F2 can be used by faulty validators to actually create multiple branches on the blockchain. That means that correctly operating full nodes decide on different blocks for the same height. Until a fork is detected locally by a full node (by receiving evidence from others or by some other local check that fails), the full node can spread corrupted blocks to light clients. + +*Remark.* If full nodes take a branch different from the one taken by the validators, it may be that the liveness of the gossip protocol may be affected. We should eventually look at this more closely. However, as it does not influence safety it is not a primary concern. + +F3 is similar to F1, except that no two correct validators decide on different blocks. It may still be the case that full nodes become affected. + +In addition, without creating a fork on the main chain, light clients can be contaminated by more than a third of validators that are faulty and sign a forged header +F4 cannot fool correct full nodes as they know the current validator set. Similarly, LCS know who the validators are. Hence, F4 is an attack against LCB that do not necessarily know the complete prefix of headers (Fork-Light), as they trust a header that is signed by at least one correct validator (trusting period method). + +The following table gives an overview of how the different attacks may affect different nodes. F1-F3 are *on-chain* attacks so they can corrupt the state of full nodes. Then if a light client (LCS or LCB) contacts a full node to obtain headers (or blocks), the corrupted state may propagate to the light client. + +F4 and F5 are *off-chain*, that is, these attacks cannot be used to corrupt the state of full nodes (which have sufficient knowledge on the state of the chain to not be fooled). + +| Attack | FN | LCS | LCB | +|:------:|:------:|:------:|:------:| +| F1 | direct | FN | FN | +| F2 | direct | FN | FN | +| F3 | direct | FN | FN | +| F4 | | | direct | +| F5 | | | direct | + +**Q:** Light clients are more vulnerable than full nodes, because the former do only verify headers but do not execute transactions. What kind of certainty is gained by a full node that executes a transaction? + +As a full node verifies all transactions, it can only be +contaminated by an attack if the blockchain itself violates its invariant (one block per height), that is, in case of a fork that leads to branching. + +## Detailed Attack Scenarios + +### Equivocation based attacks + +In case of equivocation based attacks, faulty validators sign multiple votes (prevote and/or precommit) in the same +round of some height. This attack can be executed on both full nodes and light clients. It requires 1/3 or more of voting power to be executed. + +#### Scenario 1: Equivocation on the main chain + +Validators: + +* CA - a set of correct validators with less than 1/3 of the voting power +* CB - a set of correct validators with less than 1/3 of the voting power +* CA and CB are disjoint +* F - a set of faulty validators with 1/3 or more voting power + +Observe that this setting violates the Tendermint failure model. + +Execution: + +* A faulty proposer proposes block A to CA +* A faulty proposer proposes block B to CB +* Validators from the set CA and CB prevote for A and B, respectively. +* Faulty validators from the set F prevote both for A and B. +* The faulty prevote messages + * for A arrive at CA long before the B messages + * for B arrive at CB long before the A messages +* Therefore correct validators from set CA and CB will observe +more than 2/3 of prevotes for A and B and precommit for A and B, respectively. +* Faulty validators from the set F precommit both values A and B. +* Thus, we have more than 2/3 commits for both A and B. + +Consequences: + +* Creating evidence of misbehavior is simple in this case as we have multiple messages signed by the same faulty processes for different values in the same round. + +* We have to ensure that these different messages reach a correct process (full node, monitor?), which can submit evidence. + +* This is an attack on the full node level (Fork-Full). +* It extends also to the light clients, +* For both we need a detection and recovery mechanism. + +#### Scenario 2: Equivocation to a light client (LCS) + +Validators: + +* a set F of faulty validators with more than 2/3 of the voting power. + +Execution: + +* for the main chain F behaves nicely +* F coordinates to sign a block B that is different from the one on the main chain. +* the light clients obtains B and trusts at as it is signed by more than 2/3 of the voting power. + +Consequences: + +Once equivocation is used to attack light client it opens space +for different kind of attacks as application state can be diverged in any direction. For example, it can modify validator set such that it contains only validators that do not have any stake bonded. Note that after a light client is fooled by a fork, that means that an attacker can change application state and validator set arbitrarily. + +In order to detect such (equivocation-based attack), the light client would need to cross check its state with some correct validator (or to obtain a hash of the state from the main chain using out of band channels). + +*Remark.* The light client would be able to create evidence of misbehavior, but this would require to pull potentially a lot of data from correct full nodes. Maybe we need to figure out different architecture where a light client that is attacked will push all its data for the current unbonding period to a correct node that will inspect this data and submit corresponding evidence. There are also architectures that assumes a special role (sometimes called fisherman) whose goal is to collect as much as possible useful data from the network, to do analysis and create evidence transactions. That functionality is outside the scope of this document. + +*Remark.* The difference between LCS and LCB might only be in the amount of voting power needed to convince light client about arbitrary state. In case of LCB where security threshold is at minimum, an attacker can arbitrarily modify application state with 1/3 or more of voting power, while in case of LCS it requires more than 2/3 of the voting power. + +### Flip-flopping: Amnesia based attacks + +In case of amnesia, faulty validators lock some value *v* in some round *r*, and then vote for different value *v'* in higher rounds without correctly unlocking value *v*. This attack can be used both on full nodes and light clients. + +#### Scenario 3: At most 2/3 of faults + +Validators: + +* a set F of faulty validators with 1/3 or more but at most 2/3 of the voting power +* a set C of correct validators + +Execution: + +* Faulty validators commit (without exposing it on the main chain) a block A in round *r* by collecting more than 2/3 of the + voting power (containing correct and faulty validators). +* All validators (correct and faulty) reach a round *r' > r*. +* Some correct validators in C do not lock any value before round *r'*. +* The faulty validators in F deviate from Tendermint consensus by ignoring that they locked A in *r*, and propose a different block B in *r'*. +* As the validators in C that have not locked any value find B acceptable, they accept the proposal for B and commit a block B. + +*Remark.* In this case, the more than 1/3 of faulty validators do not need to commit an equivocation (F1) as they only vote once per round in the execution. + +Detecting faulty validators in the case of such an attack can be done by the fork accountability mechanism described in: . + +If a light client is attacked using this attack with 1/3 or more of voting power (and less than 2/3), the attacker cannot change the application state arbitrarily. Rather, the attacker is limited to a state a correct validator finds acceptable: In the execution above, correct validators still find the value acceptable, however, the block the light client trusts deviates from the one on the main chain. + +#### Scenario 4: More than 2/3 of faults + +In case there is an attack with more than 2/3 of the voting power, an attacker can arbitrarily change application state. + +Validators: + +* a set F1 of faulty validators with 1/3 or more of the voting power +* a set F2 of faulty validators with less than 1/3 of the voting power + +Execution + +* Similar to Scenario 3 (however, messages by correct validators are not needed) +* The faulty validators in F1 lock value A in round *r* +* They sign a different value in follow-up rounds +* F2 does not lock A in round *r* + +Consequences: + +* The validators in F1 will be detectable by the the fork accountability mechanisms. +* The validators in F2 cannot be detected using this mechanism. +Only in case they signed something which conflicts with the application this can be used against them. Otherwise they do not do anything incorrect. +* This case is not covered by the report as it only assumes at most 2/3 of faulty validators. + +**Q:** do we need to define a special kind of attack for the case where a validator sign arbitrarily state? It seems that detecting such attack requires a different mechanism that would require as an evidence a sequence of blocks that led to that state. This might be very tricky to implement. + +### Back to the past + +In this kind of attack, faulty validators take advantage of the fact that they did not sign messages in some of the past rounds. Due to the asynchronous network in which Tendermint operates, we cannot easily differentiate between such an attack and delayed message. This kind of attack can be used at both full nodes and light clients. + +#### Scenario 5 + +Validators: + +* C1 - a set of correct validators with over 1/3 of the voting power +* C2 - a set of correct validators with 1/3 of the voting power +* C1 and C2 are disjoint +* F - a set of faulty validators with less than 1/3 voting power +* one additional faulty process *q* +* F and *q* violate the Tendermint failure model. + +Execution: + +* in a round *r* of height *h* we have C1 precommitting a value A, +* C2 precommits nil, +* F does not send any message +* *q* precommits nil. +* In some round *r' > r*, F and *q* and C2 commit some other value B different from A. +* F and *fp* "go back to the past" and sign precommit message for value A in round *r*. +* Together with precomit messages of C1 this is sufficient for a commit for value A. + +Consequences: + +* Only a single faulty validator that previously precommited nil did equivocation, while the other 1/3 of faulty validators actually executed an attack that has exactly the same sequence of messages as part of amnesia attack. Detecting this kind of attack boil down to mechanisms for equivocation and amnesia. + +**Q:** should we keep this as a separate kind of attack? It seems that equivocation, amnesia and phantom validators are the only kind of attack we need to support and this gives us security also in other cases. This would not be surprising as equivocation and amnesia are attacks that followed from the protocol and phantom attack is not really an attack to Tendermint but more to the Proof of Stake module. + +### Phantom validators + +In case of phantom validators, processes that are not part of the current validator set but are still bonded (as attack happen during their unbonding period) can be part of the attack by signing vote messages. This attack can be executed against both full nodes and light clients. + +#### Scenario 6 + +Validators: + +* F -- a set of faulty validators that are not part of the validator set on the main chain at height *h + k* + +Execution: + +* There is a fork, and there exist two different headers for height *h + k*, with different validator sets: + * VS2 on the main chain + * forged header VS2', signed by F (and others) + +* a light client has a trust in a header for height *h* (and the corresponding validator set VS1). +* As part of bisection header verification, it verifies the header at height *h + k* with new validator set VS2'. + +Consequences: + +* To detect this, a node needs to see both, the forged header and the canonical header from the chain. +* If this is the case, detecting these kind of attacks is easy as it just requires verifying if processes are signing messages in heights in which they are not part of the validator set. + +**Remark.** We can have phantom-validator-based attacks as a follow up of equivocation or amnesia based attack where forked state contains validators that are not part of the validator set at the main chain. In this case, they keep signing messages contributed to a forked chain (the wrong branch) although they are not part of the validator set on the main chain. This attack can also be used to attack full node during a period of time it is eclipsed. + +**Remark.** Phantom validator evidence has been removed from implementation as it was deemed, although possibly a plausible form of evidence, not relevant. Any attack on +the light client involving a phantom validator will have needed to be initiated by 1/3+ lunatic +validators that can forge a new validator set that includes the phantom validator. Only in +that case will the light client accept the phantom validators vote. We need only worry about +punishing the 1/3+ lunatic cabal, that is the root cause of the attack. + +### Lunatic validator + +Lunatic validator agrees to sign commit messages for arbitrary application state. It is used to attack light clients. +Note that detecting this behavior require application knowledge. Detecting this behavior can probably be done by +referring to the block before the one in which height happen. + +**Q:** can we say that in this case a validator declines to check if a proposed value is valid before voting for it? diff --git a/spec/light-client/accountability/Synopsis.md b/spec/light-client/accountability/Synopsis.md new file mode 100644 index 00000000000..76da3868c72 --- /dev/null +++ b/spec/light-client/accountability/Synopsis.md @@ -0,0 +1,105 @@ + +# Synopsis + + A TLA+ specification of a simplified Tendermint consensus, tuned for + fork accountability. The simplifications are as follows: + +- the procotol runs for one height, that is, one-shot consensus + +- this specification focuses on safety, so timeouts are modelled with + with non-determinism + +- the proposer function is non-determinstic, no fairness is assumed + +- the messages by the faulty processes are injected right in the initial states + +- every process has the voting power of 1 + +- hashes are modelled as identity + + Having the above assumptions in mind, the specification follows the pseudo-code + of the Tendermint paper: + + Byzantine processes can demonstrate arbitrary behavior, including + no communication. However, we have to show that under the collective evidence + collected by the correct processes, at least `f+1` Byzantine processes demonstrate + one of the following behaviors: + +- Equivocation: a Byzantine process sends two different values + in the same round. + +- Amnesia: a Byzantine process locks a value, although it has locked + another value in the past. + +# TLA+ modules + +- [TendermintAcc_004_draft](TendermintAcc_004_draft.tla) is the protocol + specification, + +- [TendermintAccInv_004_draft](TendermintAccInv_004_draft.tla) contains an + inductive invariant for establishing the protocol safety as well as the + forking cases, + +- `MC_n_f`, e.g., [MC_n4_f1](MC_n4_f1.tla), contains fixed constants for + model checking with the [Apalache model + checker](https://github.com/informalsystems/apalache), + +- [TendermintAccTrace_004_draft](TendermintAccTrace_004_draft.tla) shows how + to restrict the execution space to a fixed sequence of actions (e.g., to + instantiate a counterexample), + +- [TendermintAccDebug_004_draft](TendermintAccDebug_004_draft.tla) contains + the useful definitions for debugging the protocol specification with TLC and + Apalache. + +# Reasoning about fork scenarios + +The theorem statements can be found in +[TendermintAccInv_004_draft.tla](TendermintAccInv_004_draft.tla). + +First, we would like to show that `TypedInv` is an inductive invariant. +Formally, the statement looks as follows: + +```tla +THEOREM TypedInvIsInductive == + \/ FaultyQuorum + \//\ Init => TypedInv + /\ TypedInv /\ [Next]_vars => TypedInv' +``` + +When over two-thirds of processes are faulty, `TypedInv` is not inductive. +However, there is no hope to repair the protocol in this case. We run +[Apalache](https://github.com/informalsystems/apalache) to prove this theorem +only for fixed instances of 4 to 5 validators. Apalache does not parse theorem +statements at the moment, so we ran Apalache using a shell script. To find a +parameterized argument, one has to use a theorem prover, e.g., TLAPS. + +Second, we would like to show that the invariant implies `Agreement`, that is, +no fork, provided that less than one third of processes is faulty. By combining +this theorem with the previous theorem, we conclude that the protocol indeed +satisfies Agreement under the condition `LessThanThirdFaulty`. + +```tla +THEOREM AgreementWhenLessThanThirdFaulty == + LessThanThirdFaulty /\ TypedInv => Agreement +``` + +Third, in the general case, we either have no fork, or two fork scenarios: + +```tla +THEOREM AgreementOrFork == + ~FaultyQuorum /\ TypedInv => Accountability +``` + +# Model checking results + +Check the report on [model checking with Apalache](./results/001indinv-apalache-report.md). + +To run the model checking experiments, use the script: + +```console +./run.sh +``` + +This script assumes that the apalache build is available in +`~/devl/apalache-unstable`. diff --git a/spec/light-client/accountability/TendermintAccDebug_004_draft.tla b/spec/light-client/accountability/TendermintAccDebug_004_draft.tla new file mode 100644 index 00000000000..deaa990ea39 --- /dev/null +++ b/spec/light-client/accountability/TendermintAccDebug_004_draft.tla @@ -0,0 +1,100 @@ +------------------ MODULE TendermintAccDebug_004_draft ------------------------- +(* + A few definitions that we use for debugging TendermintAcc3, which do not belong + to the specification itself. + + * Version 3. Modular and parameterized definitions. + + Igor Konnov, 2020. + *) + +EXTENDS TendermintAccInv_004_draft + +\* make them parameters? +NFaultyProposals == 0 \* the number of injected faulty PROPOSE messages +NFaultyPrevotes == 6 \* the number of injected faulty PREVOTE messages +NFaultyPrecommits == 6 \* the number of injected faulty PRECOMMIT messages + +\* Given a set of allowed messages Msgs, this operator produces a function from +\* 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. +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}] + +\* As TLC explodes with faults, we may have initial states without faults +InitNoFaults == + /\ round = [p \in Corr |-> 0] + /\ step = [p \in Corr |-> "PROPOSE"] + /\ decision = [p \in Corr |-> NilValue] + /\ lockedValue = [p \in Corr |-> NilValue] + /\ 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 + +(* + A specialized version of Init that injects NFaultyProposals proposals, + NFaultyPrevotes prevotes, NFaultyPrecommits precommits by the faulty processes + *) +InitFewFaults == + /\ round = [p \in Corr |-> 0] + /\ step = [p \in Corr |-> "PROPOSE"] + /\ decision = [p \in Corr |-> NilValue] + /\ lockedValue = [p \in Corr |-> NilValue] + /\ lockedRound = [p \in Corr |-> NilRound] + /\ validValue = [p \in Corr |-> NilValue] + /\ validRound = [p \in Corr |-> NilRound] + /\ ProduceFaults(msgsPrevote', + SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Values]), + NFaultyPrevotes) + /\ ProduceFaults(msgsPrecommit', + SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Values]), + NFaultyPrecommits) + /\ ProduceFaults(msgsPropose', + SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, round: Rounds, + proposal: Values, validRound: Rounds \cup {NilRound}]), + NFaultyProposals) + /\ evidence = EmptyMsgSet + +\* Add faults incrementally +NextWithFaults == + \* either the protocol makes a step + \/ Next + \* or a faulty process sends a message + \//\ UNCHANGED <> + /\ \E p \in Faulty: + \E r \in Rounds: + \//\ UNCHANGED <> + /\ \E proposal \in ValidValues \union {NilValue}: + \E vr \in RoundsOrNil: + BroadcastProposal(p, r, proposal, vr) + \//\ UNCHANGED <> + /\ \E id \in ValidValues \union {NilValue}: + BroadcastPrevote(p, r, id) + \//\ UNCHANGED <> + /\ \E id \in ValidValues \union {NilValue}: + BroadcastPrecommit(p, r, id) + +(******************************** PROPERTIES ***************************************) +\* simple reachability properties to see that the spec is progressing +NoPrevote == \A p \in Corr: step[p] /= "PREVOTE" + +NoPrecommit == \A p \in Corr: step[p] /= "PRECOMMIT" + +NoValidPrecommit == + \A r \in Rounds: + \A m \in msgsPrecommit[r]: + m.id = NilValue \/ m.src \in Faulty + +NoHigherRounds == \A p \in Corr: round[p] < 1 + +NoDecision == \A p \in Corr: decision[p] = NilValue + +============================================================================= + diff --git a/spec/light-client/accountability/TendermintAccInv_004_draft.tla b/spec/light-client/accountability/TendermintAccInv_004_draft.tla new file mode 100644 index 00000000000..5dd15396ded --- /dev/null +++ b/spec/light-client/accountability/TendermintAccInv_004_draft.tla @@ -0,0 +1,370 @@ +------------------- MODULE TendermintAccInv_004_draft -------------------------- +(* + An inductive invariant for TendermintAcc3, which capture the forked + and non-forked cases. + + * Version 3. Modular and parameterized definitions. + * Version 2. Bugfixes in the spec and an inductive invariant. + + Igor Konnov, 2020. + *) + +EXTENDS TendermintAcc_004_draft + +(************************** TYPE INVARIANT ***********************************) +(* first, we define the sets of all potential messages *) +AllProposals == + SetOfMsgs([type: {"PROPOSAL"}, + src: AllProcs, + round: Rounds, + proposal: ValuesOrNil, + validRound: RoundsOrNil]) + +AllPrevotes == + SetOfMsgs([type: {"PREVOTE"}, + src: AllProcs, + round: Rounds, + id: ValuesOrNil]) + +AllPrecommits == + SetOfMsgs([type: {"PRECOMMIT"}, + src: AllProcs, + round: Rounds, + id: ValuesOrNil]) + +(* the standard type invariant -- importantly, it is inductive *) +TypeOK == + /\ round \in [Corr -> Rounds] + /\ step \in [Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" }] + /\ decision \in [Corr -> ValidValues \union {NilValue}] + /\ lockedValue \in [Corr -> ValidValues \union {NilValue}] + /\ lockedRound \in [Corr -> RoundsOrNil] + /\ validValue \in [Corr -> ValidValues \union {NilValue}] + /\ validRound \in [Corr -> RoundsOrNil] + /\ msgsPropose \in [Rounds -> SUBSET AllProposals] + /\ BenignRoundsInMessages(msgsPropose) + /\ msgsPrevote \in [Rounds -> SUBSET AllPrevotes] + /\ BenignRoundsInMessages(msgsPrevote) + /\ msgsPrecommit \in [Rounds -> SUBSET AllPrecommits] + /\ BenignRoundsInMessages(msgsPrecommit) + /\ evidence \in SUBSET (AllProposals \union AllPrevotes \union AllPrecommits) + /\ action \in { + "Init", + "InsertProposal", + "UponProposalInPropose", + "UponProposalInProposeAndPrevote", + "UponQuorumOfPrevotesAny", + "UponProposalInPrevoteOrCommitAndPrevote", + "UponQuorumOfPrecommitsAny", + "UponProposalInPrecommitNoDecision", + "OnTimeoutPropose", + "OnQuorumOfNilPrevotes", + "OnRoundCatchup" + } + +(************************** INDUCTIVE INVARIANT *******************************) +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] + +NoFutureMessagesForLargerRounds(p) == + \* a correct process does not send messages for the future rounds + \A r \in { rr \in Rounds: rr > round[p] }: + /\ \A m \in msgsPropose[r]: m.src /= p + /\ \A m \in msgsPrevote[r]: m.src /= p + /\ \A m \in msgsPrecommit[r]: m.src /= p + +NoFutureMessagesForCurrentRound(p) == + \* a correct process does not send messages in the future + 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 + /\ \/ step[p] \in {"PRECOMMIT", "DECIDED"} + \/ \A m \in msgsPrecommit[r]: m.src /= p + +\* the correct processes never send future messages +AllNoFutureMessagesSent == + \A p \in Corr: + /\ NoFutureMessagesForCurrentRound(p) + /\ NoFutureMessagesForLargerRounds(p) + +\* a correct process in the PREVOTE state has sent a PREVOTE message +IfInPrevoteThenSentPrevote(p) == + step[p] = "PREVOTE" => + \E m \in msgsPrevote[round[p]]: + /\ m.id \in ValidValues \cup { NilValue } + /\ m.src = p + +AllIfInPrevoteThenSentPrevote == + \A p \in Corr: IfInPrevoteThenSentPrevote(p) + +\* a correct process in the PRECOMMIT state has sent a PRECOMMIT message +IfInPrecommitThenSentPrecommit(p) == + step[p] = "PRECOMMIT" => + \E m \in msgsPrecommit[round[p]]: + /\ m.id \in ValidValues \cup { NilValue } + /\ m.src = p + +AllIfInPrecommitThenSentPrecommit == + \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 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: + /\ m.src = Proposer[r] + /\ m.proposal = decision[p] + \* not inductive: /\ m.src \in Corr => (m.validRound <= r) + +AllIfInDecidedThenReceivedProposal == + \A p \in Corr: + 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] } + IN + Cardinality(PV) >= THRESHOLD2 + +AllIfInDecidedThenReceivedTwoThirds == + \A p \in Corr: + IfInDecidedThenReceivedTwoThirds(p) + +\* for a round r, there is proposal by the round proposer for a valid round vr +ProposalInRound(r, proposedVal, vr) == + \E m \in msgsPropose[r]: + /\ m.src = Proposer[r] + /\ m.proposal = proposedVal + /\ m.validRound = vr + +TwoThirdsPrevotes(vr, v) == + LET PV == { mm \in msgsPrevote[vr] \intersect evidence: mm.id = v } IN + Cardinality(PV) >= THRESHOLD2 + +\* if a process sends a PREVOTE, then there are three possibilities: +\* 1) the process is faulty, 2) the PREVOTE cotains Nil, +\* 3) there is a proposal in an earlier (valid) round and two thirds of PREVOTES +IfSentPrevoteThenReceivedProposalOrTwoThirds(r) == + \A mpv \in msgsPrevote[r]: + \/ mpv.src \in Faulty + \* lockedRound and lockedValue is beyond my comprehension + \/ mpv.id = NilValue + \//\ mpv.src \in Corr + /\ mpv.id /= NilValue + /\ \/ ProposalInRound(r, mpv.id, NilRound) + \/ \E vr \in { rr \in Rounds: rr < r }: + /\ ProposalInRound(r, mpv.id, vr) + /\ TwoThirdsPrevotes(vr, mpv.id) + +AllIfSentPrevoteThenReceivedProposalOrTwoThirds == + \A r \in Rounds: + IfSentPrevoteThenReceivedProposalOrTwoThirds(r) + +\* if a correct process has sent a PRECOMMIT, then there are two thirds, +\* either on a valid value, or a nil value +IfSentPrecommitThenReceivedTwoThirds == + \A r \in Rounds: + \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 } + IN + Cardinality(PV) >= THRESHOLD2 + \/ /\ mpc.id = NilValue + /\ Cardinality(msgsPrevote[r]) >= THRESHOLD2 + +\* if a correct process has sent a precommit message in a round, it should +\* have sent a prevote +IfSentPrecommitThenSentPrevote == + \A r \in Rounds: + \A mpc \in msgsPrecommit[r]: + mpc.src \in Corr => + \E m \in msgsPrevote[r]: + m.src = mpc.src + +\* 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 == + {mm \in UNION { msgsPrecommit[r]: r \in Rounds }: mm.src = p /\ mm.id /= NilValue } + IN + pPrecommits /= {} <: {MT} + => LET latest == + CHOOSE m \in pPrecommits: + \A m2 \in pPrecommits: + m2.round <= m.round + IN + /\ lockedRound[p] = latest.round + /\ lockedValue[p] = latest.id + +AllLatestPrecommitHasLockedRound == + \A p \in Corr: + LatestPrecommitHasLockedRound(p) + +\* 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. +NoEquivocationByCorrect(r, msgs) == + \A p \in Corr: + \E v \in ValidValues \union {NilValue}: + \A m \in msgs[r]: + \/ m.src /= p + \/ m.id = v + +\* a proposer nevers sends two values +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) + +\* construct the set of the message senders +Senders(M) == { m.src: m \in M } + +\* The final piece by Josef Widder: +\* if T + 1 processes precommit on the same value in a round, +\* then in the future rounds there are less than 2T + 1 prevotes for another value +PrecommitsLockValue == + \A r \in Rounds: + \A v \in ValidValues \union {NilValue}: + \/ LET Precommits == {m \in msgsPrecommit[r]: m.id = v} + IN + Cardinality(Senders(Precommits)) < THRESHOLD1 + \/ \A fr \in { rr \in Rounds: rr > r }: \* future rounds + \A w \in (ValuesOrNil) \ {v}: + 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 + /\ AllIfInDecidedThenValidDecision + /\ AllLockedRoundIffLockedValue + /\ AllIfLockedRoundThenSentCommit + /\ AllLatestPrecommitHasLockedRound + /\ AllIfSentPrevoteThenReceivedProposalOrTwoThirds + /\ IfSentPrecommitThenSentPrevote + /\ IfSentPrecommitThenReceivedTwoThirds + /\ AllNoEquivocationByCorrect + /\ PrecommitsLockValue + +\* this is the inductive invariant we like to check +TypedInv == TypeOK /\ Inv + +\* UNUSED FOR SAFETY +ValidRoundNotSmallerThanLockedRound(p) == + validRound[p] >= lockedRound[p] + +\* UNUSED FOR SAFETY +ValidRoundIffValidValue(p) == + (validRound[p] = NilRound) <=> (validValue[p] = NilValue) + +\* UNUSED FOR SAFETY +AllValidRoundIffValidValue == + \A p \in Corr: ValidRoundIffValidValue(p) + +\* if validRound is defined, then there are two-thirds of PREVOTEs +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 valid round can be only set to a valid value that was proposed earlier +IfValidRoundThenProposal(p) == + \/ validRound[p] = NilRound + \/ \E m \in msgsPropose[validRound[p]]: + m.proposal = validValue[p] + +\* UNUSED FOR SAFETY +AllIfValidRoundThenProposal == + \A p \in Corr: IfValidRoundThenProposal(p) + +(******************************** THEOREMS ***************************************) +(* Under this condition, the faulty processes can decide alone *) +FaultyQuorum == Cardinality(Faulty) >= THRESHOLD2 + +(* The standard condition of the Tendermint security model *) +LessThanThirdFaulty == N > 3 * T /\ Cardinality(Faulty) <= T + +(* + TypedInv is an inductive invariant, provided that there is no faulty quorum. + We run Apalache to prove this theorem only for fixed instances of 4 to 10 processes. + (We run Apalache manually, as it does not parse theorem statements at the moment.) + To get a parameterized argument, one has to use a theorem prover, e.g., TLAPS. + *) +THEOREM TypedInvIsInductive == + \/ FaultyQuorum \* if there are 2 * T + 1 faulty processes, we give up + \//\ Init => TypedInv + /\ TypedInv /\ [Next]_vars => TypedInv' + +(* + There should be no fork, when there are less than 1/3 faulty processes. + *) +THEOREM AgreementWhenLessThanThirdFaulty == + LessThanThirdFaulty /\ TypedInv => Agreement + +(* + In a more general case, when there are less than 2/3 faulty processes, + there is either Agreement (no fork), or two scenarios exist: + equivocation by Faulty, or amnesia by Faulty. + *) +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 new file mode 100644 index 00000000000..436c2275a65 --- /dev/null +++ b/spec/light-client/accountability/TendermintAccTrace_004_draft.tla @@ -0,0 +1,33 @@ +------------------ MODULE TendermintAccTrace_004_draft ------------------------- +(* + 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 + +\* a sequence of action names that should appear in the given order, +\* excluding "Init" +CONSTANT Trace + +VARIABLE toReplay + +TraceInit == + /\ toReplay = Trace + /\ action' := "Init" + /\ Init + +TraceNext == + /\ Len(toReplay) > 0 + /\ toReplay' = Tail(toReplay) + \* Here is the trick. We restrict the action to the expected one, + \* so the other actions will be pruned + /\ action' := Head(toReplay) + /\ Next + +================================================================================ diff --git a/spec/light-client/accountability/TendermintAcc_004_draft.tla b/spec/light-client/accountability/TendermintAcc_004_draft.tla new file mode 100644 index 00000000000..9d3a543d4ef --- /dev/null +++ b/spec/light-client/accountability/TendermintAcc_004_draft.tla @@ -0,0 +1,474 @@ +-------------------- MODULE TendermintAcc_004_draft --------------------------- +(* + A TLA+ specification of a simplified Tendermint consensus, tuned for + fork accountability. The simplifications are as follows: + + - the protocol runs for one height, that is, it is one-shot consensus + + - this specification focuses on safety, so timeouts are modelled + with non-determinism + + - the proposer function is non-determinstic, no fairness is assumed + + - the messages by the faulty processes are injected right in the initial states + + - every process has the voting power of 1 + + - hashes are modelled as identity + + Having the above assumptions in mind, the specification follows the pseudo-code + of the Tendermint paper: https://arxiv.org/abs/1807.04938 + + 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 behaviours: + + - Equivocation: a Byzantine process may send two different values + in the same round. + + - Amnesia: a Byzantine process may lock a value without unlocking + the previous value that it has locked in the past. + + * 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. + * Version 1. A preliminary specification. + + Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020. + *) + +EXTENDS Integers, FiniteSets + +(********************* PROTOCOL PARAMETERS **********************************) +CONSTANTS + Corr, \* the set of correct processes + Faulty, \* the set of Byzantine processes, may be empty + N, \* the total number of processes: correct, defective, and Byzantine + T, \* an upper bound on the number of Byzantine processes + ValidValues, \* the set of valid values, proposed both by correct and faulty + InvalidValues, \* the set of invalid values, never proposed by the correct ones + MaxRound, \* the maximal round number + Proposer \* the proposer function from 0..NRounds to 1..N + +ASSUME(N = Cardinality(Corr \union Faulty)) + +(*************************** DEFINITIONS ************************************) +AllProcs == Corr \union Faulty \* the set of all processes +Rounds == 0..MaxRound \* the set of potential rounds +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 +NilValue == "None" \* a special value for a nil round, outside of Values +ValuesOrNil == Values \union {NilValue} + +\* a value hash is modeled as identity +Id(v) == v + +\* The validity predicate +IsValid(v) == v \in ValidValues + +\* the two thresholds that are used in the algorithm +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({}) + +(********************* PROTOCOL STATE VARIABLES ******************************) +VARIABLES + round, \* a process round number: Corr -> Rounds + step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } + decision, \* process decision: Corr -> ValuesOrNil + lockedValue, \* a locked value: Corr -> ValuesOrNil + lockedRound, \* a locked round: Corr -> RoundsOrNil + validValue, \* a valid value: Corr -> ValuesOrNil + validRound \* a valid round: Corr -> RoundsOrNil + +\* book-keeping variables +VARIABLES + msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages + msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages + msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages + evidence, \* the messages that were used by the correct processes to make transitions + action \* we use this variable to see which action was taken + +(* to see a type invariant, check TendermintAccInv3 *) + +\* a handy definition used in UNCHANGED +vars == <> + +(********************* PROTOCOL INITIALIZATION ******************************) +FaultyProposals(r) == + SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, + round: {r}, proposal: Values, validRound: RoundsOrNil]) + +AllFaultyProposals == + SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, + round: Rounds, proposal: Values, validRound: RoundsOrNil]) + +FaultyPrevotes(r) == + SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: {r}, id: Values]) + +AllFaultyPrevotes == + SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Values]) + +FaultyPrecommits(r) == + SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: {r}, id: Values]) + +AllFaultyPrecommits == + SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Values]) + +BenignRoundsInMessages(msgfun) == + \* the message function never contains a message for a wrong round + \A r \in Rounds: + \A m \in msgfun[r]: + r = m.round + +\* The initial states of the protocol. Some faults can be in the system already. +Init == + /\ round = [p \in Corr |-> 0] + /\ step = [p \in Corr |-> "PROPOSE"] + /\ decision = [p \in Corr |-> NilValue] + /\ lockedValue = [p \in Corr |-> NilValue] + /\ lockedRound = [p \in Corr |-> NilRound] + /\ validValue = [p \in Corr |-> NilValue] + /\ validRound = [p \in Corr |-> NilRound] + /\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals] + /\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes] + /\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits] + /\ BenignRoundsInMessages(msgsPropose) + /\ BenignRoundsInMessages(msgsPrevote) + /\ BenignRoundsInMessages(msgsPrecommit) + /\ evidence = EmptyMsgSet + /\ action' = "Init" + +(************************ MESSAGE PASSING ********************************) +BroadcastProposal(pSrc, pRound, pProposal, pValidRound) == + LET newMsg == + AsMsg([type |-> "PROPOSAL", src |-> pSrc, round |-> pRound, + proposal |-> pProposal, validRound |-> pValidRound]) + IN + msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}] + +BroadcastPrevote(pSrc, pRound, pId) == + LET newMsg == AsMsg([type |-> "PREVOTE", + src |-> pSrc, round |-> pRound, id |-> pId]) + IN + msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}] + +BroadcastPrecommit(pSrc, pRound, pId) == + LET newMsg == AsMsg([type |-> "PRECOMMIT", + src |-> pSrc, round |-> pRound, id |-> pId]) + IN + msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}] + + +(********************* PROTOCOL TRANSITIONS ******************************) +\* lines 12-13 +StartRound(p, r) == + /\ step[p] /= "DECIDED" \* a decided process does not participate in consensus + /\ round' = [round EXCEPT ![p] = r] + /\ step' = [step EXCEPT ![p] = "PROPOSE"] + +\* lines 14-19, a proposal may be sent later +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 proposal == IF validValue[p] /= NilValue THEN validValue[p] ELSE v IN + BroadcastProposal(p, round[p], proposal, validRound[p]) + /\ UNCHANGED <> + /\ action' = "InsertProposal" + +\* lines 22-27 +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 + /\ msg \in msgsPropose[round[p]] \* line 22 + /\ evidence' = {msg} \union evidence + /\ LET mid == (* line 23 *) + IF IsValid(v) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v) + THEN Id(v) + ELSE NilValue + IN + BroadcastPrevote(p, round[p], mid) \* lines 24-26 + /\ step' = [step EXCEPT ![p] = "PREVOTE"] + /\ UNCHANGED <> + /\ action' = "UponProposalInPropose" + +\* 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 msg == + AsMsg([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 + /\ Cardinality(PV) >= THRESHOLD2 \* line 28 + /\ evidence' = PV \union {msg} \union evidence + /\ LET mid == (* line 29 *) + IF IsValid(v) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v) + THEN Id(v) + ELSE NilValue + IN + BroadcastPrevote(p, round[p], mid) \* lines 24-26 + /\ step' = [step EXCEPT ![p] = "PREVOTE"] + /\ UNCHANGED <> + /\ action' = "UponProposalInProposeAndPrevote" + + \* lines 34-35 + lines 61-64 (onTimeoutPrevote) +UponQuorumOfPrevotesAny(p) == + /\ step[p] = "PREVOTE" \* line 34 and 61 + /\ \E MyEvidence \in SUBSET msgsPrevote[round[p]]: + \* find the unique voters in the evidence + 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 + /\ BroadcastPrecommit(p, round[p], NilValue) + /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] + /\ UNCHANGED <> + /\ action' = "UponQuorumOfPrevotesAny" + +\* lines 36-46 +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 + /\ 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 + /\ IF step[p] = "PREVOTE" + THEN \* lines 38-41: + /\ lockedValue' = [lockedValue EXCEPT ![p] = v] + /\ lockedRound' = [lockedRound EXCEPT ![p] = round[p]] + /\ BroadcastPrecommit(p, round[p], Id(v)) + /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] + ELSE + UNCHANGED <> + \* lines 42-43 + /\ validValue' = [validValue EXCEPT ![p] = v] + /\ validRound' = [validRound EXCEPT ![p] = round[p]] + /\ UNCHANGED <> + /\ action' = "UponProposalInPrevoteOrCommitAndPrevote" + +\* lines 47-48 + 65-67 (onTimeoutPrecommit) +UponQuorumOfPrecommitsAny(p) == + /\ \E MyEvidence \in SUBSET msgsPrecommit[round[p]]: + \* find the unique committers in the evidence + 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 + /\ round[p] + 1 \in Rounds + /\ StartRound(p, round[p] + 1) + /\ UNCHANGED <> + /\ action' = "UponQuorumOfPrecommitsAny" + +\* lines 49-54 +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 + /\ 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 + /\ 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 <> + /\ action' = "UponProposalInPrecommitNoDecision" + +\* the actions below are not essential for safety, but added for completeness + +\* lines 20-21 + 57-60 +OnTimeoutPropose(p) == + /\ step[p] = "PROPOSE" + /\ p /= Proposer[round[p]] + /\ BroadcastPrevote(p, round[p], NilValue) + /\ step' = [step EXCEPT ![p] = "PREVOTE"] + /\ UNCHANGED <> + /\ action' = "OnTimeoutPropose" + +\* lines 44-46 +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 + /\ BroadcastPrecommit(p, round[p], Id(NilValue)) + /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] + /\ UNCHANGED <> + /\ 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 + /\ Cardinality(Faster) >= THRESHOLD1 + /\ evidence' = MyEvidence \union evidence + /\ StartRound(p, r) + /\ UNCHANGED <> + /\ action' = "OnRoundCatchup" + +(* + * A system transition. In this specificatiom, the system may eventually deadlock, + * e.g., when all processes decide. This is expected behavior, as we focus on safety. + *) +Next == + \E p \in Corr: + \/ InsertProposal(p) + \/ UponProposalInPropose(p) + \/ UponProposalInProposeAndPrevote(p) + \/ UponQuorumOfPrevotesAny(p) + \/ UponProposalInPrevoteOrCommitAndPrevote(p) + \/ UponQuorumOfPrecommitsAny(p) + \/ UponProposalInPrecommitNoDecision(p) + \* the actions below are not essential for safety, but added for completeness + \/ OnTimeoutPropose(p) + \/ 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 + +\* amnesic behavior by a process p +AmnesiaBy(p) == + \E r1, r2 \in Rounds: + /\ 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 + /\ \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) } + IN + Cardinality(prevotes) < THRESHOLD2 + +(******************************** PROPERTIES ***************************************) + +\* the safety property -- agreement +Agreement == + \A p, q \in Corr: + \/ decision[p] = NilValue + \/ decision[q] = NilValue + \/ decision[p] = decision[q] + +\* the protocol validity +Validity == + \A p \in Corr: decision[p] \in ValidValues \union {NilValue} + +(* + The protocol safety. Two cases are possible: + 1. There is no fork, that is, Agreement holds true. + 2. A subset of faulty processes demonstrates equivocation or amnesia. + *) +Accountability == + \/ Agreement + \/ \E Detectable \in SUBSET Faulty: + /\ Cardinality(Detectable) >= THRESHOLD1 + /\ \A p \in Detectable: + 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 == + \A p \in Faulty: ~AmnesiaBy(p) + +\* This property is violated. You can check it to see an example of equivocation. +NoEquivocation == + \A p \in Faulty: ~EquivocationBy(p) + +\* This property is violated. You can check it to see an example of agreement. +\* It is not exactly ~Agreement, as we do not want to see the states where +\* decision[p] = NilValue +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 +\* is a tough constraint for Apalache. It has not reported a counterexample +\* for n=4,f=2, length <= 5. +ShowMeAmnesiaWithoutEquivocation == + (~Agreement /\ \E p \in Faulty: ~EquivocationBy(p)) + => \A p \in Faulty: ~AmnesiaBy(p) + +\* This property is violated on n=4,f=2, length=4 in less than 10 min. +\* Two faulty processes may demonstrate amnesia without equivocation. +AmnesiaImpliesEquivocation == + (\E p \in Faulty: AmnesiaBy(p)) => (\E q \in Faulty: EquivocationBy(q)) + +(* + This property is violated. You can check it to see that all correct processes + may reach MaxRound without making a decision. + *) +NeverUndecidedInMaxRound == + LET AllInMax == \A p \in Corr: round[p] = MaxRound + AllDecided == \A p \in Corr: decision[p] /= NilValue + IN + AllInMax => AllDecided + +============================================================================= + diff --git a/spec/light-client/accountability/results/001indinv-apalache-mem-log.svg b/spec/light-client/accountability/results/001indinv-apalache-mem-log.svg new file mode 100644 index 00000000000..5821418da40 --- /dev/null +++ b/spec/light-client/accountability/results/001indinv-apalache-mem-log.svg @@ -0,0 +1,1063 @@ + + + + + + + + + 2020-12-11T20:07:39.617177 + image/svg+xml + + + Matplotlib v3.3.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/light-client/accountability/results/001indinv-apalache-mem.svg b/spec/light-client/accountability/results/001indinv-apalache-mem.svg new file mode 100644 index 00000000000..dc7213eaed0 --- /dev/null +++ b/spec/light-client/accountability/results/001indinv-apalache-mem.svg @@ -0,0 +1,1141 @@ + + + + + + + + + 2020-12-11T20:07:40.321995 + image/svg+xml + + + Matplotlib v3.3.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/light-client/accountability/results/001indinv-apalache-ncells.svg b/spec/light-client/accountability/results/001indinv-apalache-ncells.svg new file mode 100644 index 00000000000..20c49f4f19e --- /dev/null +++ b/spec/light-client/accountability/results/001indinv-apalache-ncells.svg @@ -0,0 +1,1015 @@ + + + + + + + + + 2020-12-11T20:07:40.804886 + image/svg+xml + + + Matplotlib v3.3.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/light-client/accountability/results/001indinv-apalache-nclauses.svg b/spec/light-client/accountability/results/001indinv-apalache-nclauses.svg new file mode 100644 index 00000000000..86d19143bf1 --- /dev/null +++ b/spec/light-client/accountability/results/001indinv-apalache-nclauses.svg @@ -0,0 +1,1133 @@ + + + + + + + + + 2020-12-11T20:07:41.276750 + image/svg+xml + + + Matplotlib v3.3.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/light-client/accountability/results/001indinv-apalache-report.md b/spec/light-client/accountability/results/001indinv-apalache-report.md new file mode 100644 index 00000000000..0c14742c536 --- /dev/null +++ b/spec/light-client/accountability/results/001indinv-apalache-report.md @@ -0,0 +1,61 @@ +# Results of 001indinv-apalache + +## 1. Awesome plots + +### 1.1. Time (logarithmic scale) + +![time-log](001indinv-apalache-time-log.svg "Time Log") + +### 1.2. Time (linear) + +![time-log](001indinv-apalache-time.svg "Time Log") + +### 1.3. Memory (logarithmic scale) + +![mem-log](001indinv-apalache-mem-log.svg "Memory Log") + +### 1.4. Memory (linear) + +![mem](001indinv-apalache-mem.svg "Memory Log") + +### 1.5. Number of arena cells (linear) + +![ncells](001indinv-apalache-ncells.svg "Number of arena cells") + +### 1.6. Number of SMT clauses (linear) + +![nclauses](001indinv-apalache-nclauses.svg "Number of SMT clauses") + +## 2. Input parameters + +no | filename | tool | timeout | init | inv | next | args +----|----------------|------------|-----------|------------|------------------|--------|------------------------------ +1 | MC_n4_f1.tla | apalache | 10h | TypedInv | TypedInv | | --length=1 --cinit=ConstInit +2 | MC_n4_f2.tla | apalache | 10h | TypedInv | TypedInv | | --length=1 --cinit=ConstInit +3 | MC_n5_f1.tla | apalache | 10h | TypedInv | TypedInv | | --length=1 --cinit=ConstInit +4 | MC_n5_f2.tla | apalache | 10h | TypedInv | TypedInv | | --length=1 --cinit=ConstInit +5 | MC_n4_f1.tla | apalache | 20h | Init | TypedInv | | --length=0 --cinit=ConstInit +6 | MC_n4_f2.tla | apalache | 20h | Init | TypedInv | | --length=0 --cinit=ConstInit +7 | MC_n5_f1.tla | apalache | 20h | Init | TypedInv | | --length=0 --cinit=ConstInit +8 | MC_n5_f2.tla | apalache | 20h | Init | TypedInv | | --length=0 --cinit=ConstInit +9 | MC_n4_f1.tla | apalache | 20h | TypedInv | Agreement | | --length=0 --cinit=ConstInit +10 | MC_n4_f2.tla | apalache | 20h | TypedInv | Accountability | | --length=0 --cinit=ConstInit +11 | MC_n5_f1.tla | apalache | 20h | TypedInv | Agreement | | --length=0 --cinit=ConstInit +12 | MC_n5_f2.tla | apalache | 20h | TypedInv | Accountability | | --length=0 --cinit=ConstInit + +## 3. Detailed results: 001indinv-apalache-unstable.csv + +01:no | 02:tool | 03:status | 04:time_sec | 05:depth | 05:mem_kb | 10:ninit_trans | 11:ninit_trans | 12:ncells | 13:nclauses | 14:navg_clause_len +-------|------------|-------------|---------------|------------|-------------|------------------|------------------|-------------|---------------|-------------------- +1 | apalache | NoError | 11m | 1 | 3.0GB | 0 | 0 | 217K | 1.0M | 89 +2 | apalache | NoError | 11m | 1 | 3.0GB | 0 | 0 | 207K | 1.0M | 88 +3 | apalache | NoError | 16m | 1 | 4.0GB | 0 | 0 | 311K | 2.0M | 101 +4 | apalache | NoError | 14m | 1 | 3.0GB | 0 | 0 | 290K | 1.0M | 103 +5 | apalache | NoError | 9s | 0 | 563MB | 0 | 0 | 2.0K | 14K | 42 +6 | apalache | NoError | 10s | 0 | 657MB | 0 | 0 | 2.0K | 28K | 43 +7 | apalache | NoError | 8s | 0 | 635MB | 0 | 0 | 2.0K | 17K | 44 +8 | apalache | NoError | 10s | 0 | 667MB | 0 | 0 | 3.0K | 32K | 45 +9 | apalache | NoError | 5m05s | 0 | 2.0GB | 0 | 0 | 196K | 889K | 108 +10 | apalache | NoError | 8m08s | 0 | 6.0GB | 0 | 0 | 2.0M | 3.0M | 34 +11 | apalache | NoError | 9m09s | 0 | 3.0GB | 0 | 0 | 284K | 1.0M | 128 +12 | apalache | NoError | 14m | 0 | 7.0GB | 0 | 0 | 4.0M | 5.0M | 38 diff --git a/spec/light-client/accountability/results/001indinv-apalache-time-log.svg b/spec/light-client/accountability/results/001indinv-apalache-time-log.svg new file mode 100644 index 00000000000..458d67c6c3b --- /dev/null +++ b/spec/light-client/accountability/results/001indinv-apalache-time-log.svg @@ -0,0 +1,1134 @@ + + + + + + + + + 2020-12-11T20:07:38.347583 + image/svg+xml + + + Matplotlib v3.3.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/light-client/accountability/results/001indinv-apalache-time.svg b/spec/light-client/accountability/results/001indinv-apalache-time.svg new file mode 100644 index 00000000000..a5db5a8b590 --- /dev/null +++ b/spec/light-client/accountability/results/001indinv-apalache-time.svg @@ -0,0 +1,957 @@ + + + + + + + + + 2020-12-11T20:07:39.136767 + image/svg+xml + + + Matplotlib v3.3.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/light-client/accountability/results/001indinv-apalache-unstable.csv b/spec/light-client/accountability/results/001indinv-apalache-unstable.csv new file mode 100644 index 00000000000..db1a0609383 --- /dev/null +++ b/spec/light-client/accountability/results/001indinv-apalache-unstable.csv @@ -0,0 +1,13 @@ +01:no,02:tool,03:status,04:time_sec,05:depth,05:mem_kb,10:ninit_trans,11:ninit_trans,12:ncells,13:nclauses,14:navg_clause_len +1,apalache,NoError,704,1,3215424,0,0,217385,1305718,89 +2,apalache,NoError,699,1,3195020,0,0,207969,1341979,88 +3,apalache,NoError,1018,1,4277060,0,0,311798,2028544,101 +4,apalache,NoError,889,1,4080012,0,0,290989,1951616,103 +5,apalache,NoError,9,0,577100,0,0,2045,14655,42 +6,apalache,NoError,10,0,673772,0,0,2913,28213,43 +7,apalache,NoError,8,0,651008,0,0,2214,17077,44 +8,apalache,NoError,10,0,683188,0,0,3082,32651,45 +9,apalache,NoError,340,0,3053848,0,0,196943,889859,108 +10,apalache,NoError,517,0,6424536,0,0,2856378,3802779,34 +11,apalache,NoError,587,0,4028516,0,0,284369,1343296,128 +12,apalache,NoError,880,0,7881148,0,0,4382556,5778072,38 diff --git a/spec/light-client/accountability/run.sh b/spec/light-client/accountability/run.sh new file mode 100755 index 00000000000..75e57a5f86f --- /dev/null +++ b/spec/light-client/accountability/run.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# +# The script to run all experiments at once + +export SCRIPTS_DIR=~/devl/apalache-tests/scripts +export BUILDS="unstable" +export BENCHMARK=001indinv-apalache +export RUN_SCRIPT=./run-all.sh # alternatively, use ./run-parallel.sh +make -e -f ~/devl/apalache-tests/Makefile.common diff --git a/spec/light-client/assets/light-node-image.png b/spec/light-client/assets/light-node-image.png new file mode 100644 index 0000000000000000000000000000000000000000..f0b93c6e415ebc39bc2313c885faacb35aa7af9a GIT binary patch literal 122270 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ɸa2^db4^;{u=6(Fa8oE-0a}M)8Mf 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 NF6my(J#Y2ke*p{XTJ-<` literal 0 HcmV?d00001 diff --git a/spec/light-client/attacks/Blockchain_003_draft.tla b/spec/light-client/attacks/Blockchain_003_draft.tla new file mode 100644 index 00000000000..fb6e6e8e875 --- /dev/null +++ b/spec/light-client/attacks/Blockchain_003_draft.tla @@ -0,0 +1,166 @@ +------------------------ MODULE Blockchain_003_draft ----------------------------- +(* + This is a high-level specification of Tendermint blockchain + that is designed specifically for the light client. + Validators have the voting power of one. If you like to model various + voting powers, introduce multiple copies of the same validator + (do not forget to give them unique names though). + *) +EXTENDS Integers, FiniteSets, Apalache + +Min(a, b) == IF a < b THEN a ELSE b + +CONSTANT + AllNodes, + (* a set of all nodes that can act as validators (correct and faulty) *) + ULTIMATE_HEIGHT, + (* a maximal height that can be ever reached (modelling artifact) *) + TRUSTING_PERIOD + (* the period within which the validators are trusted *) + +Heights == 1..ULTIMATE_HEIGHT (* possible heights *) + +(* A commit is just a set of nodes who have committed the block *) +Commits == SUBSET AllNodes + +(* The set of all block headers that can be on the blockchain. + This is a simplified version of the Block data structure in the actual implementation. *) +BlockHeaders == [ + height: Heights, + \* the block height + time: Int, + \* the block timestamp in some integer units + lastCommit: Commits, + \* the nodes who have voted on the previous block, the set itself instead of a hash + (* in the implementation, only the hashes of V and NextV are stored in a block, + as V and NextV are stored in the application state *) + VS: SUBSET AllNodes, + \* the validators of this bloc. We store the validators instead of the hash. + NextVS: SUBSET AllNodes + \* the validators of the next block. We store the next validators instead of the hash. +] + +(* A signed header is just a header together with a set of commits *) +LightBlocks == [header: BlockHeaders, Commits: Commits] + +VARIABLES + refClock, + (* the current global time in integer units as perceived by the reference chain *) + blockchain, + (* A sequence of BlockHeaders, which gives us a bird view of the blockchain. *) + Faulty + (* A set of faulty nodes, which can act as validators. We assume that the set + of faulty processes is non-decreasing. If a process has recovered, it should + connect using a different id. *) + +(* all variables, to be used with UNCHANGED *) +vars == <> + +(* The set of all correct nodes in a state *) +Corr == AllNodes \ Faulty + +(* APALACHE annotations *) +a <: b == a \* type annotation + +NT == STRING +NodeSet(S) == S <: {NT} +EmptyNodeSet == NodeSet({}) + +BT == [height |-> Int, time |-> Int, lastCommit |-> {NT}, VS |-> {NT}, NextVS |-> {NT}] + +LBT == [header |-> BT, Commits |-> {NT}] +(* end of APALACHE annotations *) + +(****************************** BLOCKCHAIN ************************************) + +(* the header is still within the trusting period *) +InTrustingPeriod(header) == + refClock < header.time + TRUSTING_PERIOD + +(* + Given a function pVotingPower \in D -> Powers for some D \subseteq AllNodes + and pNodes \subseteq D, test whether the set pNodes \subseteq AllNodes has + more than 2/3 of voting power among the nodes in D. + *) +TwoThirds(pVS, pNodes) == + LET TP == Cardinality(pVS) + SP == Cardinality(pVS \intersect pNodes) + IN + 3 * SP > 2 * TP \* when thinking in real numbers, not integers: SP > 2.0 / 3.0 * TP + +(* + Given a set of FaultyNodes, test whether the voting power of the correct nodes in D + is more than 2/3 of the voting power of the faulty nodes in D. + + Parameters: + - pFaultyNodes is a set of nodes that are considered faulty + - pVS is a set of all validators, maybe including Faulty, intersecting with it, etc. + - pMaxFaultRatio is a pair <> that limits the ratio a / b of the faulty + validators from above (exclusive) + *) +FaultyValidatorsFewerThan(pFaultyNodes, pVS, maxRatio) == + LET FN == pFaultyNodes \intersect pVS \* faulty nodes in pNodes + CN == pVS \ pFaultyNodes \* correct nodes in pNodes + CP == Cardinality(CN) \* power of the correct nodes + FP == Cardinality(FN) \* power of the faulty nodes + IN + \* CP + FP = TP is the total voting power + LET TP == CP + FP IN + FP * maxRatio[2] < TP * maxRatio[1] + +(* Can a block be produced by a correct peer, or an authenticated Byzantine peer *) +IsLightBlockAllowedByDigitalSignatures(ht, block) == + \/ block.header = blockchain[ht] \* signed by correct and faulty (maybe) + \/ /\ block.Commits \subseteq Faulty + /\ block.header.height = ht + /\ block.header.time >= 0 \* signed only by faulty + +(* + Initialize the blockchain to the ultimate height right in the initial states. + We pick the faulty validators statically, but that should not affect the light client. + + Parameters: + - pMaxFaultyRatioExclusive is a pair <> that bound the number of + faulty validators in each block by the ratio a / b (exclusive) + *) +InitToHeight(pMaxFaultyRatioExclusive) == + /\ \E Nodes \in SUBSET AllNodes: + Faulty := Nodes \* pick a subset of nodes to be faulty + \* pick the validator sets and last commits + /\ \E vs, lastCommit \in [Heights -> SUBSET AllNodes]: + \E timestamp \in [Heights -> Int]: + \* refClock is at least as early as the timestamp in the last block + /\ \E tm \in Int: + refClock := tm /\ tm >= timestamp[ULTIMATE_HEIGHT] + \* the genesis starts on day 1 + /\ timestamp[1] = 1 + /\ vs[1] = AllNodes + /\ lastCommit[1] = EmptyNodeSet + /\ \A h \in Heights \ {1}: + /\ lastCommit[h] \subseteq vs[h - 1] \* the non-validators cannot commit + /\ TwoThirds(vs[h - 1], lastCommit[h]) \* the commit has >2/3 of validator votes + \* the faulty validators have the power below the threshold + /\ FaultyValidatorsFewerThan(Faulty, vs[h], pMaxFaultyRatioExclusive) + /\ timestamp[h] > timestamp[h - 1] \* the time grows monotonically + /\ timestamp[h] < timestamp[h - 1] + TRUSTING_PERIOD \* but not too fast + \* form the block chain out of validator sets and commits (this makes apalache faster) + /\ blockchain := [h \in Heights |-> + [height |-> h, + time |-> timestamp[h], + VS |-> vs[h], + NextVS |-> IF h < ULTIMATE_HEIGHT THEN vs[h + 1] ELSE AllNodes, + lastCommit |-> lastCommit[h]] + ] \****** + +(********************* BLOCKCHAIN ACTIONS ********************************) +(* + Advance the clock by zero or more time units. + *) +AdvanceTime == + /\ \E tm \in Int: tm >= refClock /\ refClock' = tm + /\ UNCHANGED <> + +============================================================================= +\* Modification History +\* Last modified Wed Jun 10 14:10:54 CEST 2020 by igor +\* Created Fri Oct 11 15:45:11 CEST 2019 by igor diff --git a/spec/light-client/attacks/Isolation_001_draft.tla b/spec/light-client/attacks/Isolation_001_draft.tla new file mode 100644 index 00000000000..7406b89422c --- /dev/null +++ b/spec/light-client/attacks/Isolation_001_draft.tla @@ -0,0 +1,159 @@ +----------------------- MODULE Isolation_001_draft ---------------------------- +(** + * The specification of the attackers isolation at full node, + * when it has received an evidence from the light client. + * We check that the isolation spec produces a set of validators + * that have more than 1/3 of the voting power. + * + * It follows the English specification: + * + * https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/attacks/isolate-attackers_001_draft.md + * + * The assumptions made in this specification: + * + * - the voting power of every validator is 1 + * (add more validators, if you need more validators) + * + * - Tendermint security model is violated + * (there are Byzantine validators who signed a conflicting block) + * + * Igor Konnov, Zarko Milosevic, Josef Widder, Informal Systems, 2020 + *) + + +EXTENDS Integers, FiniteSets, Apalache + +\* algorithm parameters +CONSTANTS + AllNodes, + (* a set of all nodes that can act as validators (correct and faulty) *) + COMMON_HEIGHT, + (* an index of the block header that two peers agree upon *) + CONFLICT_HEIGHT, + (* an index of the block header that two peers disagree upon *) + TRUSTING_PERIOD, + (* the period within which the validators are trusted *) + FAULTY_RATIO + (* a pair <> that limits that ratio of faulty validator in the blockchain + from above (exclusive). Tendermint security model prescribes 1 / 3. *) + +VARIABLES + blockchain, (* the chain at the full node *) + refClock, (* the reference clock at the full node *) + Faulty, (* the set of faulty validators *) + conflictingBlock, (* an evidence that two peers reported conflicting blocks *) + state, (* the state of the attack isolation machine at the full node *) + attackers (* the set of the identified attackers *) + +vars == <> + +\* instantiate the chain at the full node +ULTIMATE_HEIGHT == CONFLICT_HEIGHT + 1 +BC == INSTANCE Blockchain_003_draft + +\* use the light client API +TRUSTING_HEIGHT == COMMON_HEIGHT +TARGET_HEIGHT == CONFLICT_HEIGHT + +LC == INSTANCE LCVerificationApi_003_draft + WITH localClock <- refClock, REAL_CLOCK_DRIFT <- 0, CLOCK_DRIFT <- 0 + +\* old-style type annotations in apalache +a <: b == a + +\* [LCAI-NONVALID-OUTPUT.1::TLA.1] +ViolatesValidity(header1, header2) == + \/ header1.VS /= header2.VS + \/ header1.NextVS /= header2.NextVS + \/ header1.height /= header2.height + \/ header1.time /= header2.time + (* The English specification also checks the fields that we do not have + at this level of abstraction: + - header1.ConsensusHash != header2.ConsensusHash or + - header1.AppHash != header2.AppHash or + - header1.LastResultsHash header2 != ev.LastResultsHash + *) + +Init == + /\ state := "init" + \* Pick an arbitrary blockchain from 1 to COMMON_HEIGHT + 1. + /\ BC!InitToHeight(FAULTY_RATIO) \* initializes blockchain, Faulty, and refClock + /\ attackers := {} <: {STRING} \* attackers are unknown + \* Receive an arbitrary evidence. + \* Instantiate the light block fields one by one, + \* to avoid combinatorial explosion of records. + /\ \E time \in Int: + \E VS, NextVS, lastCommit, Commits \in SUBSET AllNodes: + LET conflicting == + [ Commits |-> Commits, + header |-> + [height |-> CONFLICT_HEIGHT, + time |-> time, + VS |-> VS, + NextVS |-> NextVS, + lastCommit |-> lastCommit] ] + IN + LET refBlock == [ header |-> blockchain[COMMON_HEIGHT], + Commits |-> blockchain[COMMON_HEIGHT + 1].lastCommit ] + IN + /\ "SUCCESS" = LC!ValidAndVerifiedUntimed(refBlock, conflicting) + \* More than third of next validators in the common reference block + \* is faulty. That is a precondition for a fork. + /\ 3 * Cardinality(Faulty \intersect refBlock.header.NextVS) + > Cardinality(refBlock.header.NextVS) + \* correct validators cannot sign an invalid block + /\ ViolatesValidity(conflicting.header, refBlock.header) + => conflicting.Commits \subseteq Faulty + /\ conflictingBlock := conflicting + + +\* This is a specification of isolateMisbehavingProcesses. +\* +\* [LCAI-FUNC-MAIN.1::TLA.1] +Next == + /\ state = "init" + \* Extract the rounds from the reference block and the conflicting block. + \* In this specification, we just pick rounds non-deterministically. + \* The English specification calls RoundOf on the blocks. + /\ \E referenceRound, evidenceRound \in Int: + /\ referenceRound >= 0 /\ evidenceRound >= 0 + /\ LET reference == blockchain[CONFLICT_HEIGHT] + referenceCommit == blockchain[CONFLICT_HEIGHT + 1].lastCommit + evidenceHeader == conflictingBlock.header + evidenceCommit == conflictingBlock.Commits + IN + IF ViolatesValidity(reference, evidenceHeader) + THEN /\ attackers' := blockchain[COMMON_HEIGHT].NextVS \intersect evidenceCommit + /\ state' := "Lunatic" + ELSE IF referenceRound = evidenceRound + THEN /\ attackers' := referenceCommit \intersect evidenceCommit + /\ state' := "Equivocation" + ELSE + \* This property is shown in property + \* Accountability of TendermintAcc3.tla + /\ state' := "Amnesia" + /\ \E Attackers \in SUBSET (Faulty \intersect reference.VS): + /\ 3 * Cardinality(Attackers) > Cardinality(reference.VS) + /\ attackers' := Attackers + /\ blockchain' := blockchain + /\ refClock' := refClock + /\ Faulty' := Faulty + /\ conflictingBlock' := conflictingBlock + +(********************************** INVARIANTS *******************************) + +\* This invariant ensure that the attackers have +\* more than 1/3 of the voting power +\* +\* [LCAI-INV-Output.1::TLA-DETECTION-COMPLETENESS.1] +DetectionCompleteness == + state /= "init" => + 3 * Cardinality(attackers) > Cardinality(blockchain[CONFLICT_HEIGHT].VS) + +\* This invariant ensures that only the faulty validators are detected +\* +\* [LCAI-INV-Output.1::TLA-DETECTION-ACCURACY.1] +DetectionAccuracy == + attackers \subseteq Faulty + +============================================================================== diff --git a/spec/light-client/attacks/LCVerificationApi_003_draft.tla b/spec/light-client/attacks/LCVerificationApi_003_draft.tla new file mode 100644 index 00000000000..909eab92b8a --- /dev/null +++ b/spec/light-client/attacks/LCVerificationApi_003_draft.tla @@ -0,0 +1,192 @@ +-------------------- MODULE LCVerificationApi_003_draft -------------------------- +(** + * The common interface of the light client verification and detection. + *) +EXTENDS Integers, FiniteSets + +\* the parameters of Light Client +CONSTANTS + TRUSTING_PERIOD, + (* the period within which the validators are trusted *) + CLOCK_DRIFT, + (* the assumed precision of the clock *) + REAL_CLOCK_DRIFT, + (* the actual clock drift, which under normal circumstances should not + be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) + FAULTY_RATIO + (* a pair <> that limits that ratio of faulty validator in the blockchain + from above (exclusive). Tendermint security model prescribes 1 / 3. *) + +VARIABLES + localClock (* current time as measured by the light client *) + +(* the header is still within the trusting period *) +InTrustingPeriodLocal(header) == + \* note that the assumption about the drift reduces the period of trust + localClock < header.time + TRUSTING_PERIOD - CLOCK_DRIFT + +(* the header is still within the trusting period, even if the clock can go backwards *) +InTrustingPeriodLocalSurely(header) == + \* note that the assumption about the drift reduces the period of trust + localClock < header.time + TRUSTING_PERIOD - 2 * CLOCK_DRIFT + +(* ensure that the local clock does not drift far away from the global clock *) +IsLocalClockWithinDrift(local, global) == + /\ global - REAL_CLOCK_DRIFT <= local + /\ local <= global + REAL_CLOCK_DRIFT + +(** + * Check that the commits in an untrusted block form 1/3 of the next validators + * in a trusted header. + *) +SignedByOneThirdOfTrusted(trusted, untrusted) == + LET TP == Cardinality(trusted.header.NextVS) + SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) + IN + 3 * SP > TP + +(** + The first part of the precondition of ValidAndVerified, which does not take + the current time into account. + + [LCV-FUNC-VALID.1::TLA-PRE-UNTIMED.1] + *) +ValidAndVerifiedPreUntimed(trusted, untrusted) == + LET thdr == trusted.header + uhdr == untrusted.header + IN + /\ thdr.height < uhdr.height + \* the trusted block has been created earlier + /\ thdr.time < uhdr.time + /\ untrusted.Commits \subseteq uhdr.VS + /\ LET TP == Cardinality(uhdr.VS) + SP == Cardinality(untrusted.Commits) + IN + 3 * SP > 2 * TP + /\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS + (* As we do not have explicit hashes we ignore these three checks of the English spec: + + 1. "trusted.Commit is a commit is for the header trusted.Header, + i.e. it contains the correct hash of the header". + 2. untrusted.Validators = hash(untrusted.Header.Validators) + 3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) + *) + +(** + Check the precondition of ValidAndVerified, including the time checks. + + [LCV-FUNC-VALID.1::TLA-PRE.1] + *) +ValidAndVerifiedPre(trusted, untrusted, checkFuture) == + LET thdr == trusted.header + uhdr == untrusted.header + IN + /\ InTrustingPeriodLocal(thdr) + \* The untrusted block is not from the future (modulo clock drift). + \* Do the check, if it is required. + /\ checkFuture => uhdr.time < localClock + CLOCK_DRIFT + /\ ValidAndVerifiedPreUntimed(trusted, untrusted) + + +(** + Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. + This test does take current time into account, but only looks at the block structure. + + [LCV-FUNC-VALID.1::TLA-UNTIMED.1] + *) +ValidAndVerifiedUntimed(trusted, untrusted) == + IF ~ValidAndVerifiedPreUntimed(trusted, untrusted) + THEN "INVALID" + ELSE IF untrusted.header.height = trusted.header.height + 1 + \/ SignedByOneThirdOfTrusted(trusted, untrusted) + THEN "SUCCESS" + ELSE "NOT_ENOUGH_TRUST" + +(** + Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. + + [LCV-FUNC-VALID.1::TLA.1] + *) +ValidAndVerified(trusted, untrusted, checkFuture) == + IF ~ValidAndVerifiedPre(trusted, untrusted, checkFuture) + THEN "INVALID" + ELSE IF ~InTrustingPeriodLocal(untrusted.header) + (* We leave the following test for the documentation purposes. + The implementation should do this test, as signature verification may be slow. + In the TLA+ specification, ValidAndVerified happens in no time. + *) + THEN "FAILED_TRUSTING_PERIOD" + ELSE IF untrusted.header.height = trusted.header.height + 1 + \/ SignedByOneThirdOfTrusted(trusted, untrusted) + THEN "SUCCESS" + ELSE "NOT_ENOUGH_TRUST" + + +(** + The invariant of the light store that is not related to the blockchain + *) +LightStoreInv(fetchedLightBlocks, lightBlockStatus) == + \A lh, rh \in DOMAIN fetchedLightBlocks: + \* for every pair of stored headers that have been verified + \/ lh >= rh + \/ lightBlockStatus[lh] /= "StateVerified" + \/ lightBlockStatus[rh] /= "StateVerified" + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" + \* or the left header is outside the trusting period, so no guarantees + \/ LET lhdr == fetchedLightBlocks[lh] + rhdr == fetchedLightBlocks[rh] + IN + \* we can verify the right one using the left one + "SUCCESS" = ValidAndVerifiedUntimed(lhdr, rhdr) + +(** + Correctness states that all the obtained headers are exactly like in the blockchain. + + It is always the case that every verified header in LightStore was generated by + an instance of Tendermint consensus. + + [LCV-DIST-SAFE.1::CORRECTNESS-INV.1] + *) +CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" => + fetchedLightBlocks[h].header = blockchain[h] + +(** + * When the light client terminates, there are no failed blocks. + * (Otherwise, someone lied to us.) + *) +NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] /= "StateFailed" + +(** + The expected post-condition of VerifyToTarget. + *) +VerifyToTargetPost(blockchain, isPeerCorrect, + fetchedLightBlocks, lightBlockStatus, + trustedHeight, targetHeight, finalState) == + LET trustedHeader == fetchedLightBlocks[trustedHeight].header IN + \* The light client is not lying us on the trusted block. + \* It is straightforward to detect. + /\ lightBlockStatus[trustedHeight] = "StateVerified" + /\ trustedHeight \in DOMAIN fetchedLightBlocks + /\ trustedHeader = blockchain[trustedHeight] + \* the invariants we have found in the light client verification + \* there is a problem with trusting period + /\ isPeerCorrect + => CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) + \* a correct peer should fail the light client, + \* if the trusted block is in the trusting period + /\ isPeerCorrect /\ InTrustingPeriodLocalSurely(trustedHeader) + => finalState = "finishedSuccess" + /\ finalState = "finishedSuccess" => + /\ lightBlockStatus[targetHeight] = "StateVerified" + /\ targetHeight \in DOMAIN fetchedLightBlocks + /\ NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) + /\ LightStoreInv(fetchedLightBlocks, lightBlockStatus) + + +================================================================================== diff --git a/spec/light-client/attacks/MC_5_3.tla b/spec/light-client/attacks/MC_5_3.tla new file mode 100644 index 00000000000..552de49aee8 --- /dev/null +++ b/spec/light-client/attacks/MC_5_3.tla @@ -0,0 +1,18 @@ +------------------------- MODULE MC_5_3 ------------------------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5"} +COMMON_HEIGHT == 1 +CONFLICT_HEIGHT == 3 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +FAULTY_RATIO == <<1, 2>> \* < 1 / 2 faulty validators + +VARIABLES + blockchain, \* the reference blockchain + refClock, \* current time in the reference blockchain + Faulty, \* the set of faulty validators + state, \* the state of the light client detector + conflictingBlock, \* an evidence that two peers reported conflicting blocks + attackers + +INSTANCE Isolation_001_draft +============================================================================ diff --git a/spec/light-client/attacks/isolate-attackers_001_draft.md b/spec/light-client/attacks/isolate-attackers_001_draft.md new file mode 100644 index 00000000000..e4f585f4a8f --- /dev/null +++ b/spec/light-client/attacks/isolate-attackers_001_draft.md @@ -0,0 +1,221 @@ + +# Lightclient Attackers Isolation + +> Warning: This is the beginning of an unfinished draft. Don't continue reading! + +Adversarial nodes may have the incentive to lie to a lightclient about the state of a Tendermint blockchain. An attempt to do so is called attack. Light client [verification][verification] checks incoming data by checking a so-called "commit", which is a forwarded set of signed messages that is (supposedly) produced during executing Tendermint consensus. Thus, an attack boils down to creating and signing Tendermint consensus messages in deviation from the Tendermint consensus algorithm rules. + +As Tendermint consensus and light client verification is safe under the assumption of more than 2/3 of correct voting power per block [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link], this implies that if there was an attack then [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] was violated, that is, there is a block such that + +- validators deviated from the protocol, and +- these validators represent more than 1/3 of the voting power in that block. + +In the case of an [attack][node-based-attack-characterization], the lightclient [attack detection mechanism][detection] computes data, so called evidence [[LC-DATA-EVIDENCE.1]][LC-DATA-EVIDENCE-link], that can be used + +- to proof that there has been attack [[TMBC-LC-EVIDENCE-DATA.1]][TMBC-LC-EVIDENCE-DATA-link] and +- as basis to find the actual nodes that deviated from the Tendermint protocol. + +This specification considers how a full node in a Tendermint blockchain can isolate a set of attackers that launched the attack. The set should satisfy + +- the set does not contain a correct validator +- the set contains validators that represent more than 1/3 of the voting power of a block that is still within the unbonding period + +# Outline + +**TODO** when preparing a version for broader review. + +# Part I - Basics + +For definitions of data structures used here, in particular LightBlocks [[LCV-DATA-LIGHTBLOCK.1]](https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-data-lightblock1), cf. [Light Client Verification][verification]. + +# Part II - Definition of the Problem + +The specification of the [detection mechanism][detection] describes + +- what is a light client attack, +- conditions under which the detector will detect a light client attack, +- and the format of the output data, called evidence, in the case an attack is detected. The format is defined in +[[LC-DATA-EVIDENCE.1]][LC-DATA-EVIDENCE-link] and looks as follows + +```go +type LightClientAttackEvidence struct { + ConflictingBlock LightBlock + CommonHeight int64 +} +``` + +The isolator is a function that gets as input evidence `ev` +and a prefix of the blockchain `bc` at least up to height `ev.ConflictingBlock.Header.Height + 1`. The output is a set of *peerIDs* of validators. + +We assume that the full node is synchronized with the blockchain and has reached the height `ev.ConflictingBlock.Header.Height + 1`. + +#### **[FN-INV-Output.1]** + +When an output is generated it satisfies the following properties: + +- If + - `bc[CommonHeight].bfttime` is within the unbonding period w.r.t. the time at the full node, + - `ev.ConflictingBlock.Header != bc[ev.ConflictingBlock.Header.Height]` + - Validators in `ev.ConflictingBlock.Commit` represent more than 1/3 of the voting power in `bc[ev.CommonHeight].NextValidators` +- Then: A set of validators in `bc[CommonHeight].NextValidators` that + - represent more than 1/3 of the voting power in `bc[ev.commonHeight].NextValidators` + - signed Tendermint consensus messages for height `ev.ConflictingBlock.Header.Height` by violating the Tendermint consensus protocol. +- Else: the empty set. + +# Part IV - Protocol + +Here we discuss how to solve the problem of isolating misbehaving processes. We describe the function `isolateMisbehavingProcesses` as well as all the helping functions below. In [Part V](#part-v---Completeness), we discuss why the solution is complete based on result from analysis with automated tools. + +## Isolation + +### Outline + +> Describe solution (in English), decomposition into functions, where communication to other components happens. + +#### **[LCAI-FUNC-MAIN.1]** + +```go +func isolateMisbehavingProcesses(ev LightClientAttackEvidence, bc Blockchain) []ValidatorAddress { + + reference := bc[ev.conflictingBlock.Header.Height].Header + ev_header := ev.conflictingBlock.Header + + ref_commit := bc[ev.conflictingBlock.Header.Height + 1].Header.LastCommit // + 1 !! + ev_commit := ev.conflictingBlock.Commit + + if violatesTMValidity(reference, ev_header) { + // lunatic light client attack + signatories := Signers(ev.ConflictingBlock.Commit) + bonded_vals := Addresses(bc[ev.CommonHeight].NextValidators) + return intersection(signatories,bonded_vals) + + } + // If this point is reached the validator sets in reference and ev_header are identical + else if RoundOf(ref_commit) == RoundOf(ev_commit) { + // equivocation light client attack + return intersection(Signers(ref_commit), Signers(ev_commit)) + } + else { + // amnesia light client attack + return IsolateAmnesiaAttacker(ev, bc) + } +} +``` + +- Implementation comment + - If the full node has only reached height `ev.conflictingBlock.Header.Height` then `bc[ev.conflictingBlock.Header.Height + 1].Header.LastCommit` refers to the locally stored commit for this height. (This commit must be present by the precondition on `length(bc)`.) + - We check in the precondition that the unbonding period is not expired. However, since time moves on, before handing the validators over Cosmos SDK, the time needs to be checked again to satisfy the contract which requires that only bonded validators are reported. This passing of validators to the SDK is out of scope of this specification. +- Expected precondition + - `length(bc) >= ev.conflictingBlock.Header.Height` + - `ValidAndVerifiedUnbonding(bc[ev.CommonHeight], ev.ConflictingBlock) == SUCCESS` + - `ev.ConflictingBlock.Header != bc[ev.ConflictingBlock.Header.Height]` + - TODO: input light blocks pass basic validation +- Expected postcondition + - [[FN-INV-Output.1]](#FN-INV-Output1) holds +- Error condition + - returns an error if precondition is violated. + +### Details of the Functions + +#### **[LCAI-FUNC-VVU.1]** + +```go +func ValidAndVerifiedUnbonding(trusted LightBlock, untrusted LightBlock) Result +``` + +- Conditions are identical to [[LCV-FUNC-VALID.2]][LCV-FUNC-VALID.link] except the precondition "*trusted.Header.Time > now - trustingPeriod*" is substituted with + - `trusted.Header.Time > now - UnbondingPeriod` + +#### **[LCAI-FUNC-NONVALID.1]** + +```go +func violatesTMValidity(ref Header, ev Header) boolean +``` + +- Implementation remarks + - checks whether the evidence header `ev` violates the validity property of Tendermint Consensus, by checking agains a reference header +- Expected precondition + - `ref.Height == ev.Height` +- Expected postcondition + - returns evaluation of the following disjunction + **[[LCAI-NONVALID-OUTPUT.1]]** == + `ref.ValidatorsHash != ev.ValidatorsHash` or + `ref.NextValidatorsHash != ev.NextValidatorsHash` or + `ref.ConsensusHash != ev.ConsensusHash` or + `ref.AppHash != ev.AppHash` or + `ref.LastResultsHash != ev.LastResultsHash` + +```go +func IsolateAmnesiaAttacker(ev LightClientAttackEvidence, bc Blockchain) []ValidatorAddress +``` + +- Implementation remarks + **TODO:** What should we do here? Refer to the accountability doc? +- Expected postcondition + **TODO:** What should we do here? Refer to the accountability doc? + +```go +func RoundOf(commit Commit) []ValidatorAddress +``` + +- Expected precondition + - `commit` is well-formed. In particular all votes are from the same round `r`. +- Expected postcondition + - returns round `r` that is encoded in all the votes of the commit + +```go +func Signers(commit Commit) []ValidatorAddress +``` + +- Expected postcondition + - returns all validator addresses in `commit` + +```go +func Addresses(vals Validator[]) ValidatorAddress[] +``` + +- Expected postcondition + - returns all validator addresses in `vals` + +# Part V - Completeness + +As discussed in the beginning of this document, an attack boils down to creating and signing Tendermint consensus messages in deviation from the Tendermint consensus algorithm rules. +The main function `isolateMisbehavingProcesses` distinguishes three kinds of wrongly signing messages, namely, + +- lunatic: signing invalid blocks +- equivocation: double-signing valid blocks in the same consensus round +- amnesia: signing conflicting blocks in different consensus rounds, without having seen a quorum of messages that would have allowed to do so. + +The question is whether this captures all attacks. +First observe that the first checking in `isolateMisbehavingProcesses` is `violatesTMValidity`. It takes care of lunatic attacks. If this check passes, that is, if `violatesTMValidity` returns `FALSE` this means that [FN-NONVALID-OUTPUT] evaluates to false, which implies that `ref.ValidatorsHash = ev.ValidatorsHash`. Hence after `violatesTMValidity`, all the involved validators are the ones from the blockchain. It is thus sufficient to analyze one instance of Tendermint consensus with a fixed group membership (set of validators). Also it is sufficient to consider two different valid consensus values, that is, binary consensus. + +**TODO** we have analyzed Tendermint consensus with TLA+ and have accompanied Galois in an independent study of the protocol based on [Ivy proofs](https://github.com/tendermint/spec/tree/master/ivy-proofs). + +# References + +[[supervisor]] The specification of the light client supervisor. + +[[verification]] The specification of the light client verification protocol + +[[detection]] The specification of the light client attack detection mechanism. + +[supervisor]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor_001_draft.md + +[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md + +[detection]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md + +[LC-DATA-EVIDENCE-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#lc-data-evidence1 + +[TMBC-LC-EVIDENCE-DATA-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#tmbc-lc-evidence-data1 + +[node-based-attack-characterization]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#node-based-characterization-of-attacks + +[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-fm-2thirds1 + +[LCV-FUNC-VALID.link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-func-valid2 diff --git a/spec/light-client/attacks/isolate-attackers_002_reviewed.md b/spec/light-client/attacks/isolate-attackers_002_reviewed.md new file mode 100644 index 00000000000..febcc10a827 --- /dev/null +++ b/spec/light-client/attacks/isolate-attackers_002_reviewed.md @@ -0,0 +1,223 @@ +# Lightclient Attackers Isolation + +Adversarial nodes may have the incentive to lie to a lightclient about the state of a Tendermint blockchain. An attempt to do so is called attack. Light client [verification][verification] checks incoming data by checking a so-called "commit", which is a forwarded set of signed messages that is (supposedly) produced during executing Tendermint consensus. Thus, an attack boils down to creating and signing Tendermint consensus messages in deviation from the Tendermint consensus algorithm rules. + +As Tendermint consensus and light client verification is safe under the assumption of more than 2/3 of correct voting power per block [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link], this implies that if there was an attack then [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] was violated, that is, there is a block such that + +- validators deviated from the protocol, and +- these validators represent more than 1/3 of the voting power in that block. + +In the case of an [attack][node-based-attack-characterization], the lightclient [attack detection mechanism][detection] computes data, so called evidence [[LC-DATA-EVIDENCE.1]][LC-DATA-EVIDENCE-link], that can be used + +- to proof that there has been attack [[TMBC-LC-EVIDENCE-DATA.1]][TMBC-LC-EVIDENCE-DATA-link] and +- as basis to find the actual nodes that deviated from the Tendermint protocol. + +This specification considers how a full node in a Tendermint blockchain can isolate a set of attackers that launched the attack. The set should satisfy + +- the set does not contain a correct validator +- the set contains validators that represent more than 1/3 of the voting power of a block that is still within the unbonding period + +# Outline + +After providing the [problem statement](#Part-I---Basics-and-Definition-of-the-Problem), we specify the [isolator function](#Part-II---Protocol) and close with the discussion about its [correctness](#Part-III---Completeness) which is based on computer-aided analysis of Tendermint Consensus. + +# Part I - Basics and Definition of the Problem + +For definitions of data structures used here, in particular LightBlocks [[LCV-DATA-LIGHTBLOCK.1]](https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-data-lightblock1), we refer to the specification of [Light Client Verification][verification]. + +The specification of the [detection mechanism][detection] describes + +- what is a light client attack, +- conditions under which the detector will detect a light client attack, +- and the format of the output data, called evidence, in the case an attack is detected. The format is defined in +[[LC-DATA-EVIDENCE.1]][LC-DATA-EVIDENCE-link] and looks as follows + +```go +type LightClientAttackEvidence struct { + ConflictingBlock LightBlock + CommonHeight int64 +} +``` + +The isolator is a function that gets as input evidence `ev` +and a prefix of the blockchain `bc` at least up to height `ev.ConflictingBlock.Header.Height + 1`. The output is a set of *peerIDs* of validators. + +We assume that the full node is synchronized with the blockchain and has reached the height `ev.ConflictingBlock.Header.Height + 1`. + +#### **[LCAI-INV-Output.1]** + +When an output is generated it satisfies the following properties: + +- If + - `bc[CommonHeight].bfttime` is within the unbonding period w.r.t. the time at the full node, + - `ev.ConflictingBlock.Header != bc[ev.ConflictingBlock.Header.Height]` + - Validators in `ev.ConflictingBlock.Commit` represent more than 1/3 of the voting power in `bc[ev.CommonHeight].NextValidators` +- Then: The output is a set of validators in `bc[CommonHeight].NextValidators` that + - represent more than 1/3 of the voting power in `bc[ev.commonHeight].NextValidators` + - signed Tendermint consensus messages for height `ev.ConflictingBlock.Header.Height` by violating the Tendermint consensus protocol. +- Else: the empty set. + +# Part II - Protocol + +Here we discuss how to solve the problem of isolating misbehaving processes. We describe the function `isolateMisbehavingProcesses` as well as all the helping functions below. In [Part III](#part-III---Completeness), we discuss why the solution is complete based on result from analysis with automated tools. + +## Isolation + +### Outline + +We first check whether the conflicting block can indeed be verified from the common height. We then first check whether it was a lunatic attack (violating validity). If this is not the case, we check for equivocation. If this also is not the case, we start the on-chain [accountability protocol](https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit). + +#### **[LCAI-FUNC-MAIN.1]** + +```go +func isolateMisbehavingProcesses(ev LightClientAttackEvidence, bc Blockchain) []ValidatorAddress { + + reference := bc[ev.conflictingBlock.Header.Height].Header + ev_header := ev.conflictingBlock.Header + + ref_commit := bc[ev.conflictingBlock.Header.Height + 1].Header.LastCommit // + 1 !! + ev_commit := ev.conflictingBlock.Commit + + if violatesTMValidity(reference, ev_header) { + // lunatic light client attack + signatories := Signers(ev.ConflictingBlock.Commit) + bonded_vals := Addresses(bc[ev.CommonHeight].NextValidators) + return intersection(signatories,bonded_vals) + + } + // If this point is reached the validator sets in reference and ev_header are identical + else if RoundOf(ref_commit) == RoundOf(ev_commit) { + // equivocation light client attack + return intersection(Signers(ref_commit), Signers(ev_commit)) + } + else { + // amnesia light client attack + return IsolateAmnesiaAttacker(ev, bc) + } +} +``` + +- Implementation comment + - If the full node has only reached height `ev.conflictingBlock.Header.Height` then `bc[ev.conflictingBlock.Header.Height + 1].Header.LastCommit` refers to the locally stored commit for this height. (This commit must be present by the precondition on `length(bc)`.) + - We check in the precondition that the unbonding period is not expired. However, since time moves on, before handing the validators over Cosmos SDK, the time needs to be checked again to satisfy the contract which requires that only bonded validators are reported. This passing of validators to the SDK is out of scope of this specification. +- Expected precondition + - `length(bc) >= ev.conflictingBlock.Header.Height` + - `ValidAndVerifiedUnbonding(bc[ev.CommonHeight], ev.ConflictingBlock) == SUCCESS` + - `ev.ConflictingBlock.Header != bc[ev.ConflictingBlock.Header.Height]` + - `ev.conflictingBlock` satisfies basic validation (in particular all signed messages in the Commit are from the same round) +- Expected postcondition + - [[FN-INV-Output.1]](#FN-INV-Output1) holds +- Error condition + - returns an error if precondition is violated. + +### Details of the Functions + +#### **[LCAI-FUNC-VVU.1]** + +```go +func ValidAndVerifiedUnbonding(trusted LightBlock, untrusted LightBlock) Result +``` + +- Conditions are identical to [[LCV-FUNC-VALID.2]][LCV-FUNC-VALID.link] except the precondition "*trusted.Header.Time > now - trustingPeriod*" is substituted with + - `trusted.Header.Time > now - UnbondingPeriod` + +#### **[LCAI-FUNC-NONVALID.1]** + +```go +func violatesTMValidity(ref Header, ev Header) boolean +``` + +- Implementation remarks + - checks whether the evidence header `ev` violates the validity property of Tendermint Consensus, by checking against a reference header +- Expected precondition + - `ref.Height == ev.Height` +- Expected postcondition + - returns evaluation of the following disjunction + **[LCAI-NONVALID-OUTPUT.1]** == + `ref.ValidatorsHash != ev.ValidatorsHash` or + `ref.NextValidatorsHash != ev.NextValidatorsHash` or + `ref.ConsensusHash != ev.ConsensusHash` or + `ref.AppHash != ev.AppHash` or + `ref.LastResultsHash != ev.LastResultsHash` + +```go +func IsolateAmnesiaAttacker(ev LightClientAttackEvidence, bc Blockchain) []ValidatorAddress +``` + +- Implementation remarks + - This triggers the [query/response protocol](https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit). +- Expected postcondition + - returns attackers according to [LCAI-INV-Output.1]. + +```go +func RoundOf(commit Commit) []ValidatorAddress +``` + +- Expected precondition + - `commit` is well-formed. In particular all votes are from the same round `r`. +- Expected postcondition + - returns round `r` that is encoded in all the votes of the commit +- Error condition + - reports error if precondition is violated + +```go +func Signers(commit Commit) []ValidatorAddress +``` + +- Expected postcondition + - returns all validator addresses in `commit` + +```go +func Addresses(vals Validator[]) ValidatorAddress[] +``` + +- Expected postcondition + - returns all validator addresses in `vals` + +# Part III - Completeness + +As discussed in the beginning of this document, an attack boils down to creating and signing Tendermint consensus messages in deviation from the Tendermint consensus algorithm rules. +The main function `isolateMisbehavingProcesses` distinguishes three kinds of wrongly signed messages, namely, + +- lunatic: signing invalid blocks +- equivocation: double-signing valid blocks in the same consensus round +- amnesia: signing conflicting blocks in different consensus rounds, without having seen a quorum of messages that would have allowed to do so. + +The question is whether this captures all attacks. +First observe that the first check in `isolateMisbehavingProcesses` is `violatesTMValidity`. It takes care of lunatic attacks. If this check passes, that is, if `violatesTMValidity` returns `FALSE` this means that [[LCAI-NONVALID-OUTPUT.1]](#LCAI-FUNC-NONVALID1]) evaluates to false, which implies that `ref.ValidatorsHash = ev.ValidatorsHash`. Hence, after `violatesTMValidity`, all the involved validators are the ones from the blockchain. It is thus sufficient to analyze one instance of Tendermint consensus with a fixed group membership (set of validators). Also, as we have two different blocks for the same height, it is sufficient to consider two different valid consensus values, that is, binary consensus. + +For this fixed group membership, we have analyzed the attacks using the TLA+ specification of [Tendermint Consensus in TLA+][tendermint-accountability]. We checked that indeed the only possible scenarios that can lead to violation of agreement are **equivocation** and **amnesia**. An independent study by Galois of the protocol based on [Ivy proofs](https://github.com/tendermint/spec/tree/master/ivy-proofs) led to the same conclusion. + +# References + +[[supervisor]] The specification of the light client supervisor. + +[[verification]] The specification of the light client verification protocol. + +[[detection]] The specification of the light client attack detection mechanism. + +[[tendermint-accountability]]: TLA+ specification to check the types of attacks + +[tendermint-accountability]: +https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md + +[supervisor]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor_001_draft.md + +[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md + +[detection]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md + +[LC-DATA-EVIDENCE-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#lc-data-evidence1 + +[TMBC-LC-EVIDENCE-DATA-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#tmbc-lc-evidence-data1 + +[node-based-attack-characterization]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#node-based-characterization-of-attacks + +[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-fm-2thirds1 + +[LCV-FUNC-VALID.link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-func-valid2 diff --git a/spec/light-client/attacks/notes-on-evidence-handling.md b/spec/light-client/attacks/notes-on-evidence-handling.md new file mode 100644 index 00000000000..4b7d819191b --- /dev/null +++ b/spec/light-client/attacks/notes-on-evidence-handling.md @@ -0,0 +1,219 @@ + +# Light client attacks + +We define a light client attack as detection of conflicting headers for a given height that can be verified +starting from the trusted light block. A light client attack is defined in the context of interactions of +light client with two peers. One of the peers (called primary) defines a trace of verified light blocks +(primary trace) that are being checked against trace of the other peer (called witness) that we call +witness trace. + +A light client attack is defined by the primary and witness traces +that have a common root (the same trusted light block for a common height) but forms +conflicting branches (end of traces is for the same height but with different headers). +Note that conflicting branches could be arbitrarily big as branches continue to diverge after +a bifurcation point. We propose an approach that allows us to define a valid light client attack +only with a common light block and a single conflicting light block. We rely on the fact that +we assume that the primary is under suspicion (therefore not trusted) and that the witness plays +support role to detect and process an attack (therefore trusted). Therefore, once a light client +detects an attack, it needs to send to a witness only missing data (common height +and conflicting light block) as it has its trace. Keeping light client attack data of constant size +saves bandwidth and reduces an attack surface. As we will explain below, although in the context of +light client core +[verification](https://github.com/informalsystems/tendermint-rs/tree/master/docs/spec/lightclient/verification) +the roles of primary and witness are clearly defined, +in case of the attack, we run the same attack detection procedure twice where the roles are swapped. +The rationale is that the light client does not know what peer is correct (on a right main branch) +so it tries to create and submit an attack evidence to both peers. + +Light client attack evidence consists of a conflicting light block and a common height. + +```go +type LightClientAttackEvidence struct { + ConflictingBlock LightBlock + CommonHeight int64 +} +``` + +Full node can validate a light client attack evidence by executing the following procedure: + +```go +func IsValid(lcaEvidence LightClientAttackEvidence, bc Blockchain) boolean { + commonBlock = GetLightBlock(bc, lcaEvidence.CommonHeight) + if commonBlock == nil return false + + // Note that trustingPeriod in ValidAndVerified is set to UNBONDING_PERIOD + verdict = ValidAndVerified(commonBlock, lcaEvidence.ConflictingBlock) + conflictingHeight = lcaEvidence.ConflictingBlock.Header.Height + + return verdict == OK and bc[conflictingHeight].Header != lcaEvidence.ConflictingBlock.Header +} +``` + +## Light client attack creation + +Given a trusted light block `trusted`, a light node executes the bisection algorithm to verify header +`untrusted` at some height `h`. If the bisection algorithm succeeds, then the header `untrusted` is verified. +Headers that are downloaded as part of the bisection algorithm are stored in a store and they are also in +the verified state. Therefore, after the bisection algorithm successfully terminates we have a trace of +the light blocks ([] LightBlock) we obtained from the primary that we call primary trace. + +### Primary trace + +The following invariant holds for the primary trace: + +- Given a `trusted` light block, target height `h`, and `primary_trace` ([] LightBlock): + *primary_trace[0] == trusted* and *primary_trace[len(primary_trace)-1].Height == h* and + successive light blocks are passing light client verification logic. + +### Witness with a conflicting header + +The verified header at height `h` is cross-checked with every witness as part of +[detection](https://github.com/informalsystems/tendermint-rs/tree/master/docs/spec/lightclient/detection). +If a witness returns the conflicting header at the height `h` the following procedure is executed to verify +if the conflicting header comes from the valid trace and if that's the case to create an attack evidence: + +#### Helper functions + +We assume the following helper functions: + +```go +// Returns trace of verified light blocks starting from rootHeight and ending with targetHeight. +Trace(lightStore LightStore, rootHeight int64, targetHeight int64) LightBlock[] + +// Returns validator set for the given height +GetValidators(bc Blockchain, height int64) Validator[] + +// Returns validator set for the given height +GetValidators(bc Blockchain, height int64) Validator[] + +// Return validator addresses for the given validators +GetAddresses(vals Validator[]) ValidatorAddress[] +``` + +```go +func DetectLightClientAttacks(primary PeerID, + primary_trace []LightBlock, + witness PeerID) (LightClientAttackEvidence, LightClientAttackEvidence) { + primary_lca_evidence, witness_trace = DetectLightClientAttack(primary_trace, witness) + + witness_lca_evidence = nil + if witness_trace != nil { + witness_lca_evidence, _ = DetectLightClientAttack(witness_trace, primary) + } + return primary_lca_evidence, witness_lca_evidence +} + +func DetectLightClientAttack(trace []LightBlock, peer PeerID) (LightClientAttackEvidence, []LightBlock) { + + lightStore = new LightStore().Update(trace[0], StateTrusted) + + for i in 1..len(trace)-1 { + lightStore, result = VerifyToTarget(peer, lightStore, trace[i].Header.Height) + + if result == ResultFailure then return (nil, nil) + + current = lightStore.Get(trace[i].Header.Height) + + // if obtained header is the same as in the trace we continue with a next height + if current.Header == trace[i].Header continue + + // we have identified a conflicting header + commonBlock = trace[i-1] + conflictingBlock = trace[i] + + return (LightClientAttackEvidence { conflictingBlock, commonBlock.Header.Height }, + Trace(lightStore, trace[i-1].Header.Height, trace[i].Header.Height)) + } + return (nil, nil) +} +``` + +## Evidence handling + +As part of on chain evidence handling, full nodes identifies misbehaving processes and informs +the application, so they can be slashed. Note that only bonded validators should +be reported to the application. There are three types of attacks that can be executed against +Tendermint light client: + +- lunatic attack +- equivocation attack and +- amnesia attack. + +We now specify the evidence handling logic. + +```go +func detectMisbehavingProcesses(lcAttackEvidence LightClientAttackEvidence, bc Blockchain) []ValidatorAddress { + assume IsValid(lcaEvidence, bc) + + // lunatic light client attack + if !isValidBlock(current.Header, conflictingBlock.Header) { + conflictingCommit = lcAttackEvidence.ConflictingBlock.Commit + bondedValidators = GetNextValidators(bc, lcAttackEvidence.CommonHeight) + + return getSigners(conflictingCommit) intersection GetAddresses(bondedValidators) + + // equivocation light client attack + } else if current.Header.Round == conflictingBlock.Header.Round { + conflictingCommit = lcAttackEvidence.ConflictingBlock.Commit + trustedCommit = bc[conflictingBlock.Header.Height+1].LastCommit + + return getSigners(trustedCommit) intersection getSigners(conflictingCommit) + + // amnesia light client attack + } else { + HandleAmnesiaAttackEvidence(lcAttackEvidence, bc) + } +} + +// Block validity in this context is defined by the trusted header. +func isValidBlock(trusted Header, conflicting Header) boolean { + return trusted.ValidatorsHash == conflicting.ValidatorsHash and + trusted.NextValidatorsHash == conflicting.NextValidatorsHash and + trusted.ConsensusHash == conflicting.ConsensusHash and + trusted.AppHash == conflicting.AppHash and + trusted.LastResultsHash == conflicting.LastResultsHash +} + +func getSigners(commit Commit) []ValidatorAddress { + signers = []ValidatorAddress + for (i, commitSig) in commit.Signatures { + if commitSig.BlockIDFlag == BlockIDFlagCommit { + signers.append(commitSig.ValidatorAddress) + } + } + return signers +} +``` + +Note that amnesia attack evidence handling involves more complex processing, i.e., cannot be +defined simply on amnesia attack evidence. We explain in the following section a protocol +for handling amnesia attack evidence. + +### Amnesia attack evidence handling + +Detecting faulty processes in case of the amnesia attack is more complex and cannot be inferred +purely based on attack evidence data. In this case, in order to detect misbehaving processes we need +access to votes processes sent/received during the conflicting height. Therefore, amnesia handling assumes that +validators persist all votes received and sent during multi-round heights (as amnesia attack +is only possible in heights that executes over multiple rounds, i.e., commit round > 0). + +To simplify description of the algorithm we assume existence of the trusted oracle called monitor that will +drive the algorithm and output faulty processes at the end. Monitor can be implemented in a +distributed setting as on-chain module. The algorithm works as follows: + 1) Monitor sends votesets request to validators of the conflicting height. Validators + are expected to send their votesets within predefined timeout. + 2) Upon receiving votesets request, validators send their votesets to a monitor. + 2) Validators which have not sent its votesets within timeout are considered faulty. + 3) The preprocessing of the votesets is done. That means that the received votesets are analyzed + and each vote (valid) sent by process p is added to the voteset of the sender p. This phase ensures that + votes sent by faulty processes observed by at least one correct validator cannot be excluded from the analysis. + 4) Votesets of every validator are analyzed independently to decide whether the validator is correct or faulty. + A faulty validators is the one where at least one of those invalid transitions is found: + - More than one PREVOTE message is sent in a round + - More than one PRECOMMIT message is sent in a round + - PRECOMMIT message is sent without receiving +2/3 of voting-power equivalent + appropriate PREVOTE messages + - PREVOTE message is sent for the value V’ in round r’ and the PRECOMMIT message had + been sent for the value V in round r by the same process (r’ > r) and there are no + +2/3 of voting-power equivalent PREVOTE(vr, V’) messages (vr ≥ 0 and vr > r and vr < r’) + as the justification for sending PREVOTE(r’, V’) diff --git a/spec/light-client/detection/004bmc-apalache-ok.csv b/spec/light-client/detection/004bmc-apalache-ok.csv new file mode 100644 index 00000000000..bf4f53ea2a3 --- /dev/null +++ b/spec/light-client/detection/004bmc-apalache-ok.csv @@ -0,0 +1,10 @@ +no;filename;tool;timeout;init;inv;next;args +1;LCD_MC3_3_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 +2;LCD_MC3_3_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 +3;LCD_MC3_3_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 +4;LCD_MC3_4_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 +5;LCD_MC3_4_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 +6;LCD_MC3_4_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 +7;LCD_MC4_4_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 +8;LCD_MC4_4_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 +9;LCD_MC4_4_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 diff --git a/spec/light-client/detection/005bmc-apalache-error.csv b/spec/light-client/detection/005bmc-apalache-error.csv new file mode 100644 index 00000000000..1b9dd05ca9b --- /dev/null +++ b/spec/light-client/detection/005bmc-apalache-error.csv @@ -0,0 +1,4 @@ +no;filename;tool;timeout;init;inv;next;args +1;LCD_MC3_3_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 +2;LCD_MC3_4_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 +3;LCD_MC4_4_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 diff --git a/spec/light-client/detection/Blockchain_003_draft.tla b/spec/light-client/detection/Blockchain_003_draft.tla new file mode 100644 index 00000000000..2b37c1b1818 --- /dev/null +++ b/spec/light-client/detection/Blockchain_003_draft.tla @@ -0,0 +1,164 @@ +------------------------ MODULE Blockchain_003_draft ----------------------------- +(* + This is a high-level specification of Tendermint blockchain + that is designed specifically for the light client. + Validators have the voting power of one. If you like to model various + voting powers, introduce multiple copies of the same validator + (do not forget to give them unique names though). + *) +EXTENDS Integers, FiniteSets + +Min(a, b) == IF a < b THEN a ELSE b + +CONSTANT + AllNodes, + (* a set of all nodes that can act as validators (correct and faulty) *) + ULTIMATE_HEIGHT, + (* a maximal height that can be ever reached (modelling artifact) *) + TRUSTING_PERIOD + (* the period within which the validators are trusted *) + +Heights == 1..ULTIMATE_HEIGHT (* possible heights *) + +(* A commit is just a set of nodes who have committed the block *) +Commits == SUBSET AllNodes + +(* The set of all block headers that can be on the blockchain. + This is a simplified version of the Block data structure in the actual implementation. *) +BlockHeaders == [ + height: Heights, + \* the block height + time: Int, + \* the block timestamp in some integer units + lastCommit: Commits, + \* the nodes who have voted on the previous block, the set itself instead of a hash + (* in the implementation, only the hashes of V and NextV are stored in a block, + as V and NextV are stored in the application state *) + VS: SUBSET AllNodes, + \* the validators of this bloc. We store the validators instead of the hash. + NextVS: SUBSET AllNodes + \* the validators of the next block. We store the next validators instead of the hash. +] + +(* A signed header is just a header together with a set of commits *) +LightBlocks == [header: BlockHeaders, Commits: Commits] + +VARIABLES + refClock, + (* the current global time in integer units as perceived by the reference chain *) + blockchain, + (* A sequence of BlockHeaders, which gives us a bird view of the blockchain. *) + Faulty + (* A set of faulty nodes, which can act as validators. We assume that the set + of faulty processes is non-decreasing. If a process has recovered, it should + connect using a different id. *) + +(* all variables, to be used with UNCHANGED *) +vars == <> + +(* The set of all correct nodes in a state *) +Corr == AllNodes \ Faulty + +(* APALACHE annotations *) +a <: b == a \* type annotation + +NT == STRING +NodeSet(S) == S <: {NT} +EmptyNodeSet == NodeSet({}) + +BT == [height |-> Int, time |-> Int, lastCommit |-> {NT}, VS |-> {NT}, NextVS |-> {NT}] + +LBT == [header |-> BT, Commits |-> {NT}] +(* end of APALACHE annotations *) + +(****************************** BLOCKCHAIN ************************************) + +(* the header is still within the trusting period *) +InTrustingPeriod(header) == + refClock < header.time + TRUSTING_PERIOD + +(* + Given a function pVotingPower \in D -> Powers for some D \subseteq AllNodes + and pNodes \subseteq D, test whether the set pNodes \subseteq AllNodes has + more than 2/3 of voting power among the nodes in D. + *) +TwoThirds(pVS, pNodes) == + LET TP == Cardinality(pVS) + SP == Cardinality(pVS \intersect pNodes) + IN + 3 * SP > 2 * TP \* when thinking in real numbers, not integers: SP > 2.0 / 3.0 * TP + +(* + Given a set of FaultyNodes, test whether the voting power of the correct nodes in D + is more than 2/3 of the voting power of the faulty nodes in D. + + Parameters: + - pFaultyNodes is a set of nodes that are considered faulty + - pVS is a set of all validators, maybe including Faulty, intersecting with it, etc. + - pMaxFaultRatio is a pair <> that limits the ratio a / b of the faulty + validators from above (exclusive) + *) +FaultyValidatorsFewerThan(pFaultyNodes, pVS, maxRatio) == + LET FN == pFaultyNodes \intersect pVS \* faulty nodes in pNodes + CN == pVS \ pFaultyNodes \* correct nodes in pNodes + CP == Cardinality(CN) \* power of the correct nodes + FP == Cardinality(FN) \* power of the faulty nodes + IN + \* CP + FP = TP is the total voting power + LET TP == CP + FP IN + FP * maxRatio[2] < TP * maxRatio[1] + +(* Can a block be produced by a correct peer, or an authenticated Byzantine peer *) +IsLightBlockAllowedByDigitalSignatures(ht, block) == + \/ block.header = blockchain[ht] \* signed by correct and faulty (maybe) + \/ /\ block.Commits \subseteq Faulty + /\ block.header.height = ht + /\ block.header.time >= 0 \* signed only by faulty + +(* + Initialize the blockchain to the ultimate height right in the initial states. + We pick the faulty validators statically, but that should not affect the light client. + + Parameters: + - pMaxFaultyRatioExclusive is a pair <> that bound the number of + faulty validators in each block by the ratio a / b (exclusive) + *) +InitToHeight(pMaxFaultyRatioExclusive) == + /\ Faulty \in SUBSET AllNodes \* some nodes may fail + \* pick the validator sets and last commits + /\ \E vs, lastCommit \in [Heights -> SUBSET AllNodes]: + \E timestamp \in [Heights -> Int]: + \* refClock is at least as early as the timestamp in the last block + /\ \E tm \in Int: refClock = tm /\ tm >= timestamp[ULTIMATE_HEIGHT] + \* the genesis starts on day 1 + /\ timestamp[1] = 1 + /\ vs[1] = AllNodes + /\ lastCommit[1] = EmptyNodeSet + /\ \A h \in Heights \ {1}: + /\ lastCommit[h] \subseteq vs[h - 1] \* the non-validators cannot commit + /\ TwoThirds(vs[h - 1], lastCommit[h]) \* the commit has >2/3 of validator votes + \* the faulty validators have the power below the threshold + /\ FaultyValidatorsFewerThan(Faulty, vs[h], pMaxFaultyRatioExclusive) + /\ timestamp[h] > timestamp[h - 1] \* the time grows monotonically + /\ timestamp[h] < timestamp[h - 1] + TRUSTING_PERIOD \* but not too fast + \* form the block chain out of validator sets and commits (this makes apalache faster) + /\ blockchain = [h \in Heights |-> + [height |-> h, + time |-> timestamp[h], + VS |-> vs[h], + NextVS |-> IF h < ULTIMATE_HEIGHT THEN vs[h + 1] ELSE AllNodes, + lastCommit |-> lastCommit[h]] + ] \****** + +(********************* BLOCKCHAIN ACTIONS ********************************) +(* + Advance the clock by zero or more time units. + *) +AdvanceTime == + /\ \E tm \in Int: tm >= refClock /\ refClock' = tm + /\ UNCHANGED <> + +============================================================================= +\* Modification History +\* Last modified Wed Jun 10 14:10:54 CEST 2020 by igor +\* Created Fri Oct 11 15:45:11 CEST 2019 by igor diff --git a/spec/light-client/detection/LCD_MC3_3_faulty.tla b/spec/light-client/detection/LCD_MC3_3_faulty.tla new file mode 100644 index 00000000000..cef1df4d37e --- /dev/null +++ b/spec/light-client/detection/LCD_MC3_3_faulty.tla @@ -0,0 +1,27 @@ +------------------------- MODULE LCD_MC3_3_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 3 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +IS_SECONDARY_CORRECT == TRUE +FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators + +VARIABLES + blockchain, (* the reference blockchain *) + localClock, (* current time in the light client *) + refClock, (* current time in the reference blockchain *) + Faulty, (* the set of faulty validators *) + state, (* the state of the light client detector *) + fetchedLightBlocks1, (* a function from heights to LightBlocks *) + fetchedLightBlocks2, (* a function from heights to LightBlocks *) + fetchedLightBlocks1b, (* a function from heights to LightBlocks *) + commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) + nextHeightToTry, (* the index in CreateEvidenceForPeer *) + evidences + +INSTANCE LCDetector_003_draft +============================================================================ diff --git a/spec/light-client/detection/LCD_MC3_4_faulty.tla b/spec/light-client/detection/LCD_MC3_4_faulty.tla new file mode 100644 index 00000000000..06bcdee13a0 --- /dev/null +++ b/spec/light-client/detection/LCD_MC3_4_faulty.tla @@ -0,0 +1,27 @@ +------------------------- MODULE LCD_MC3_4_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 4 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +IS_SECONDARY_CORRECT == TRUE +FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators + +VARIABLES + blockchain, (* the reference blockchain *) + localClock, (* current time in the light client *) + refClock, (* current time in the reference blockchain *) + Faulty, (* the set of faulty validators *) + state, (* the state of the light client detector *) + fetchedLightBlocks1, (* a function from heights to LightBlocks *) + fetchedLightBlocks2, (* a function from heights to LightBlocks *) + fetchedLightBlocks1b, (* a function from heights to LightBlocks *) + commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) + nextHeightToTry, (* the index in CreateEvidenceForPeer *) + evidences + +INSTANCE LCDetector_003_draft +============================================================================ diff --git a/spec/light-client/detection/LCD_MC4_4_faulty.tla b/spec/light-client/detection/LCD_MC4_4_faulty.tla new file mode 100644 index 00000000000..fdb97d9616e --- /dev/null +++ b/spec/light-client/detection/LCD_MC4_4_faulty.tla @@ -0,0 +1,27 @@ +------------------------- MODULE LCD_MC4_4_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 4 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +IS_SECONDARY_CORRECT == TRUE +FAULTY_RATIO == <<2, 3>> \* < 2 / 3 faulty validators + +VARIABLES + blockchain, (* the reference blockchain *) + localClock, (* current time in the light client *) + refClock, (* current time in the reference blockchain *) + Faulty, (* the set of faulty validators *) + state, (* the state of the light client detector *) + fetchedLightBlocks1, (* a function from heights to LightBlocks *) + fetchedLightBlocks2, (* a function from heights to LightBlocks *) + fetchedLightBlocks1b, (* a function from heights to LightBlocks *) + commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) + nextHeightToTry, (* the index in CreateEvidenceForPeer *) + evidences + +INSTANCE LCDetector_003_draft +============================================================================ diff --git a/spec/light-client/detection/LCD_MC5_5_faulty.tla b/spec/light-client/detection/LCD_MC5_5_faulty.tla new file mode 100644 index 00000000000..fdbd87b8b9a --- /dev/null +++ b/spec/light-client/detection/LCD_MC5_5_faulty.tla @@ -0,0 +1,27 @@ +------------------------- MODULE LCD_MC5_5_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 5 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +IS_SECONDARY_CORRECT == TRUE +FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators + +VARIABLES + blockchain, (* the reference blockchain *) + localClock, (* current time in the light client *) + refClock, (* current time in the reference blockchain *) + Faulty, (* the set of faulty validators *) + state, (* the state of the light client detector *) + fetchedLightBlocks1, (* a function from heights to LightBlocks *) + fetchedLightBlocks2, (* a function from heights to LightBlocks *) + fetchedLightBlocks1b, (* a function from heights to LightBlocks *) + commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) + nextHeightToTry, (* the index in CreateEvidenceForPeer *) + evidences + +INSTANCE LCDetector_003_draft +============================================================================ diff --git a/spec/light-client/detection/LCDetector_003_draft.tla b/spec/light-client/detection/LCDetector_003_draft.tla new file mode 100644 index 00000000000..e2d32e996f2 --- /dev/null +++ b/spec/light-client/detection/LCDetector_003_draft.tla @@ -0,0 +1,373 @@ +-------------------------- MODULE LCDetector_003_draft ----------------------------- +(** + * This is a specification of the light client detector module. + * It follows the English specification: + * + * https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md + * + * The assumptions made in this specification: + * + * - light client connects to one primary and one secondary peer + * + * - the light client has its own local clock that can drift from the reference clock + * within the envelope [refClock - CLOCK_DRIFT, refClock + CLOCK_DRIFT]. + * The local clock may increase as well as decrease in the the envelope + * (similar to clock synchronization). + * + * - the ratio of the faulty validators is set as the parameter. + * + * Igor Konnov, Josef Widder, 2020 + *) + +EXTENDS Integers + +\* the parameters of Light Client +CONSTANTS + AllNodes, + (* a set of all nodes that can act as validators (correct and faulty) *) + TRUSTED_HEIGHT, + (* an index of the block header that the light client trusts by social consensus *) + TARGET_HEIGHT, + (* an index of the block header that the light client tries to verify *) + TRUSTING_PERIOD, + (* the period within which the validators are trusted *) + CLOCK_DRIFT, + (* the assumed precision of the clock *) + REAL_CLOCK_DRIFT, + (* the actual clock drift, which under normal circumstances should not + be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) + FAULTY_RATIO, + (* a pair <> that limits that ratio of faulty validator in the blockchain + from above (exclusive). Tendermint security model prescribes 1 / 3. *) + IS_PRIMARY_CORRECT, + IS_SECONDARY_CORRECT + +VARIABLES + blockchain, (* the reference blockchain *) + localClock, (* the local clock of the light client *) + refClock, (* the reference clock in the reference blockchain *) + Faulty, (* the set of faulty validators *) + state, (* the state of the light client detector *) + fetchedLightBlocks1, (* a function from heights to LightBlocks *) + fetchedLightBlocks2, (* a function from heights to LightBlocks *) + fetchedLightBlocks1b, (* a function from heights to LightBlocks *) + commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) + nextHeightToTry, (* the index in CreateEvidenceForPeer *) + evidences (* a set of evidences *) + +vars == <> + +\* (old) type annotations in Apalache +a <: b == a + + +\* instantiate a reference chain +ULTIMATE_HEIGHT == TARGET_HEIGHT + 1 +BC == INSTANCE Blockchain_003_draft + WITH ULTIMATE_HEIGHT <- (TARGET_HEIGHT + 1) + +\* use the light client API +LC == INSTANCE LCVerificationApi_003_draft + +\* evidence type +ET == [peer |-> STRING, conflictingBlock |-> BC!LBT, commonHeight |-> Int] + +\* is the algorithm in the terminating state +IsTerminated == + state \in { <<"NoEvidence", "PRIMARY">>, + <<"NoEvidence", "SECONDARY">>, + <<"FaultyPeer", "PRIMARY">>, + <<"FaultyPeer", "SECONDARY">>, + <<"FoundEvidence", "PRIMARY">> } + + +(********************************* Initialization ******************************) + +\* initialization for the light blocks data structure +InitLightBlocks(lb, Heights) == + \* BC!LightBlocks is an infinite set, as time is not restricted. + \* Hence, we initialize the light blocks by picking the sets inside. + \E vs, nextVS, lastCommit, commit \in [Heights -> SUBSET AllNodes]: + \* although [Heights -> Int] is an infinite set, + \* Apalache needs just one instance of this set, so it does not complain. + \E timestamp \in [Heights -> Int]: + LET hdr(h) == + [height |-> h, + time |-> timestamp[h], + VS |-> vs[h], + NextVS |-> nextVS[h], + lastCommit |-> lastCommit[h]] + IN + LET lightHdr(h) == + [header |-> hdr(h), Commits |-> commit[h]] + IN + lb = [ h \in Heights |-> lightHdr(h) ] + +\* initialize the detector algorithm +Init == + \* initialize the blockchain to TARGET_HEIGHT + 1 + /\ BC!InitToHeight(FAULTY_RATIO) + /\ \E tm \in Int: + tm >= 0 /\ LC!IsLocalClockWithinDrift(tm, refClock) /\ localClock = tm + \* start with the secondary looking for evidence + /\ state = <<"Init", "SECONDARY">> /\ commonHeight = 0 /\ nextHeightToTry = 0 + /\ evidences = {} <: {ET} + \* Precompute a possible result of light client verification for the primary. + \* It is the input to the detection algorithm. + /\ \E Heights1 \in SUBSET(TRUSTED_HEIGHT..TARGET_HEIGHT): + /\ TRUSTED_HEIGHT \in Heights1 + /\ TARGET_HEIGHT \in Heights1 + /\ InitLightBlocks(fetchedLightBlocks1, Heights1) + \* As we have a non-deterministic scheduler, for every trace that has + \* an unverified block, there is a filtered trace that only has verified + \* blocks. This is a deep observation. + /\ LET status == [h \in Heights1 |-> "StateVerified"] IN + LC!VerifyToTargetPost(blockchain, IS_PRIMARY_CORRECT, + fetchedLightBlocks1, status, + TRUSTED_HEIGHT, TARGET_HEIGHT, "finishedSuccess") + \* initialize the other data structures to the default values + /\ LET trustedBlock == blockchain[TRUSTED_HEIGHT] + trustedLightBlock == [header |-> trustedBlock, Commits |-> AllNodes] + IN + /\ fetchedLightBlocks2 = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock] + /\ fetchedLightBlocks1b = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock] + + +(********************************* Transitions ******************************) + +\* a block should contain a copy of the block from the reference chain, +\* with a matching commit +CopyLightBlockFromChain(block, height) == + LET ref == blockchain[height] + lastCommit == + IF height < ULTIMATE_HEIGHT + THEN blockchain[height + 1].lastCommit + \* for the ultimate block, which we never use, + \* as ULTIMATE_HEIGHT = TARGET_HEIGHT + 1 + ELSE blockchain[height].VS + IN + block = [header |-> ref, Commits |-> lastCommit] + +\* Either the primary is correct and the block comes from the reference chain, +\* or the block is produced by a faulty primary. +\* +\* [LCV-FUNC-FETCH.1::TLA.1] +FetchLightBlockInto(isPeerCorrect, block, height) == + IF isPeerCorrect + THEN CopyLightBlockFromChain(block, height) + ELSE BC!IsLightBlockAllowedByDigitalSignatures(height, block) + + +(** + * Pick the next height, for which there is a block. + *) +PickNextHeight(fetchedBlocks, height) == + LET largerHeights == { h \in DOMAIN fetchedBlocks: h > height } IN + IF largerHeights = ({} <: {Int}) + THEN -1 + ELSE CHOOSE h \in largerHeights: + \A h2 \in largerHeights: h <= h2 + + +(** + * Check, whether the target header matches at the secondary and primary. + *) +CompareLast == + /\ state = <<"Init", "SECONDARY">> + \* fetch a block from the secondary: + \* non-deterministically pick a block that matches the constraints + /\ \E latest \in BC!LightBlocks: + \* for the moment, we ignore the possibility of a timeout when fetching a block + /\ FetchLightBlockInto(IS_SECONDARY_CORRECT, latest, TARGET_HEIGHT) + /\ IF latest.header = fetchedLightBlocks1[TARGET_HEIGHT].header + THEN \* if the headers match, CreateEvidence is not called + /\ state' = <<"NoEvidence", "SECONDARY">> + \* save the retrieved block for further analysis + /\ fetchedLightBlocks2' = + [h \in (DOMAIN fetchedLightBlocks2) \union {TARGET_HEIGHT} |-> + IF h = TARGET_HEIGHT THEN latest ELSE fetchedLightBlocks2[h]] + /\ UNCHANGED <> + ELSE \* prepare the parameters for CreateEvidence + /\ commonHeight' = TRUSTED_HEIGHT + /\ nextHeightToTry' = PickNextHeight(fetchedLightBlocks1, TRUSTED_HEIGHT) + /\ state' = IF nextHeightToTry' >= 0 + THEN <<"CreateEvidence", "SECONDARY">> + ELSE <<"FaultyPeer", "SECONDARY">> + /\ UNCHANGED fetchedLightBlocks2 + + /\ UNCHANGED <> + + +\* the actual loop in CreateEvidence +CreateEvidence(peer, isPeerCorrect, refBlocks, targetBlocks) == + /\ state = <<"CreateEvidence", peer>> + \* precompute a possible result of light client verification for the secondary + \* we have to introduce HeightRange, because Apalache can only handle a..b + \* for constant a and b + /\ LET HeightRange == { h \in TRUSTED_HEIGHT..TARGET_HEIGHT: + commonHeight <= h /\ h <= nextHeightToTry } IN + \E HeightsRange \in SUBSET(HeightRange): + /\ commonHeight \in HeightsRange /\ nextHeightToTry \in HeightsRange + /\ InitLightBlocks(targetBlocks, HeightsRange) + \* As we have a non-deterministic scheduler, for every trace that has + \* an unverified block, there is a filtered trace that only has verified + \* blocks. This is a deep observation. + /\ \E result \in {"finishedSuccess", "finishedFailure"}: + LET targetStatus == [h \in HeightsRange |-> "StateVerified"] IN + \* call VerifyToTarget for (commonHeight, nextHeightToTry). + /\ LC!VerifyToTargetPost(blockchain, isPeerCorrect, + targetBlocks, targetStatus, + commonHeight, nextHeightToTry, result) + \* case 1: the peer has failed (or the trusting period has expired) + /\ \/ /\ result /= "finishedSuccess" + /\ state' = <<"FaultyPeer", peer>> + /\ UNCHANGED <> + \* case 2: success + \/ /\ result = "finishedSuccess" + /\ LET block1 == refBlocks[nextHeightToTry] IN + LET block2 == targetBlocks[nextHeightToTry] IN + IF block1.header /= block2.header + THEN \* the target blocks do not match + /\ state' = <<"FoundEvidence", peer>> + /\ evidences' = evidences \union + {[peer |-> peer, + conflictingBlock |-> block1, + commonHeight |-> commonHeight]} + /\ UNCHANGED <> + ELSE \* the target blocks match + /\ nextHeightToTry' = PickNextHeight(refBlocks, nextHeightToTry) + /\ commonHeight' = nextHeightToTry + /\ state' = IF nextHeightToTry' >= 0 + THEN state + ELSE <<"NoEvidence", peer>> + /\ UNCHANGED evidences + +SwitchToPrimary == + /\ state = <<"FoundEvidence", "SECONDARY">> + /\ nextHeightToTry' = PickNextHeight(fetchedLightBlocks2, commonHeight) + /\ state' = <<"CreateEvidence", "PRIMARY">> + /\ UNCHANGED <> + + +CreateEvidenceForSecondary == + /\ CreateEvidence("SECONDARY", IS_SECONDARY_CORRECT, + fetchedLightBlocks1, fetchedLightBlocks2') + /\ UNCHANGED <> + +CreateEvidenceForPrimary == + /\ CreateEvidence("PRIMARY", IS_PRIMARY_CORRECT, + fetchedLightBlocks2, + fetchedLightBlocks1b') + /\ UNCHANGED <> + +(* + The local and global clocks can be updated. They can also drift from each other. + Note that the local clock can actually go backwards in time. + However, it still stays in the drift envelope + of [refClock - REAL_CLOCK_DRIFT, refClock + REAL_CLOCK_DRIFT]. + *) +AdvanceClocks == + /\ \E tm \in Int: + tm >= refClock /\ refClock' = tm + /\ \E tm \in Int: + /\ tm >= localClock + /\ LC!IsLocalClockWithinDrift(tm, refClock') + /\ localClock' = tm + +(** + Execute AttackDetector for one secondary. + + [LCD-FUNC-DETECTOR.2::LOOP.1] + *) +Next == + /\ AdvanceClocks + /\ \/ CompareLast + \/ CreateEvidenceForSecondary + \/ SwitchToPrimary + \/ CreateEvidenceForPrimary + + +\* simple invariants to see the progress of the detector +NeverNoEvidence == state[1] /= "NoEvidence" +NeverFoundEvidence == state[1] /= "FoundEvidence" +NeverFaultyPeer == state[1] /= "FaultyPeer" +NeverCreateEvidence == state[1] /= "CreateEvidence" + +NeverFoundEvidencePrimary == state /= <<"FoundEvidence", "PRIMARY">> + +NeverReachTargetHeight == nextHeightToTry < TARGET_HEIGHT + +EvidenceWhenFaultyInv == + (state[1] = "FoundEvidence") => (~IS_PRIMARY_CORRECT \/ ~IS_SECONDARY_CORRECT) + +NoEvidenceForCorrectInv == + IS_PRIMARY_CORRECT /\ IS_SECONDARY_CORRECT => evidences = {} <: {ET} + +(** + * If we find an evidence by peer A, peer B has ineded given us a corrupted + * header following the common height. Also, we have a verification trace by peer A. + *) +CommonHeightOnEvidenceInv == + \A e \in evidences: + LET conflicting == e.conflictingBlock IN + LET conflictingHeader == conflicting.header IN + \* the evidence by suspectingPeer can be verified by suspectingPeer in one step + LET SoundEvidence(suspectingPeer, peerBlocks) == + \/ e.peer /= suspectingPeer + \* the conflicting block from another peer verifies against the common height + \/ /\ "SUCCESS" = + LC!ValidAndVerifiedUntimed(peerBlocks[e.commonHeight], conflicting) + \* and the headers of the same height by the two peers do not match + /\ peerBlocks[conflictingHeader.height].header /= conflictingHeader + IN + /\ SoundEvidence("PRIMARY", fetchedLightBlocks1b) + /\ SoundEvidence("SECONDARY", fetchedLightBlocks2) + +(** + * If the light client does not find an evidence, + * then there is no attack on the light client. + *) +AccuracyInv == + (LC!InTrustingPeriodLocal(fetchedLightBlocks1[TARGET_HEIGHT].header) + /\ state = <<"NoEvidence", "SECONDARY">>) + => + (fetchedLightBlocks1[TARGET_HEIGHT].header = blockchain[TARGET_HEIGHT] + /\ fetchedLightBlocks2[TARGET_HEIGHT].header = blockchain[TARGET_HEIGHT]) + +(** + * The primary reports a corrupted block at the target height. If the secondary is + * correct and the algorithm has terminated, we should get the evidence. + * This property is violated due to clock drift. VerifyToTarget may fail with + * the correct secondary within the trusting period (due to clock drift, locally + * we think that we are outside of the trusting period). + *) +PrecisionInvGrayZone == + (/\ fetchedLightBlocks1[TARGET_HEIGHT].header /= blockchain[TARGET_HEIGHT] + /\ BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + /\ IS_SECONDARY_CORRECT + /\ IsTerminated) + => + evidences /= {} <: {ET} + +(** + * The primary reports a corrupted block at the target height. If the secondary is + * correct and the algorithm has terminated, we should get the evidence. + * This invariant does not fail, as we are using the local clock to check the trusting + * period. + *) +PrecisionInvLocal == + (/\ fetchedLightBlocks1[TARGET_HEIGHT].header /= blockchain[TARGET_HEIGHT] + /\ LC!InTrustingPeriodLocalSurely(blockchain[TRUSTED_HEIGHT]) + /\ IS_SECONDARY_CORRECT + /\ IsTerminated) + => + evidences /= {} <: {ET} + +==================================================================================== diff --git a/spec/light-client/detection/LCVerificationApi_003_draft.tla b/spec/light-client/detection/LCVerificationApi_003_draft.tla new file mode 100644 index 00000000000..909eab92b8a --- /dev/null +++ b/spec/light-client/detection/LCVerificationApi_003_draft.tla @@ -0,0 +1,192 @@ +-------------------- MODULE LCVerificationApi_003_draft -------------------------- +(** + * The common interface of the light client verification and detection. + *) +EXTENDS Integers, FiniteSets + +\* the parameters of Light Client +CONSTANTS + TRUSTING_PERIOD, + (* the period within which the validators are trusted *) + CLOCK_DRIFT, + (* the assumed precision of the clock *) + REAL_CLOCK_DRIFT, + (* the actual clock drift, which under normal circumstances should not + be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) + FAULTY_RATIO + (* a pair <> that limits that ratio of faulty validator in the blockchain + from above (exclusive). Tendermint security model prescribes 1 / 3. *) + +VARIABLES + localClock (* current time as measured by the light client *) + +(* the header is still within the trusting period *) +InTrustingPeriodLocal(header) == + \* note that the assumption about the drift reduces the period of trust + localClock < header.time + TRUSTING_PERIOD - CLOCK_DRIFT + +(* the header is still within the trusting period, even if the clock can go backwards *) +InTrustingPeriodLocalSurely(header) == + \* note that the assumption about the drift reduces the period of trust + localClock < header.time + TRUSTING_PERIOD - 2 * CLOCK_DRIFT + +(* ensure that the local clock does not drift far away from the global clock *) +IsLocalClockWithinDrift(local, global) == + /\ global - REAL_CLOCK_DRIFT <= local + /\ local <= global + REAL_CLOCK_DRIFT + +(** + * Check that the commits in an untrusted block form 1/3 of the next validators + * in a trusted header. + *) +SignedByOneThirdOfTrusted(trusted, untrusted) == + LET TP == Cardinality(trusted.header.NextVS) + SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) + IN + 3 * SP > TP + +(** + The first part of the precondition of ValidAndVerified, which does not take + the current time into account. + + [LCV-FUNC-VALID.1::TLA-PRE-UNTIMED.1] + *) +ValidAndVerifiedPreUntimed(trusted, untrusted) == + LET thdr == trusted.header + uhdr == untrusted.header + IN + /\ thdr.height < uhdr.height + \* the trusted block has been created earlier + /\ thdr.time < uhdr.time + /\ untrusted.Commits \subseteq uhdr.VS + /\ LET TP == Cardinality(uhdr.VS) + SP == Cardinality(untrusted.Commits) + IN + 3 * SP > 2 * TP + /\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS + (* As we do not have explicit hashes we ignore these three checks of the English spec: + + 1. "trusted.Commit is a commit is for the header trusted.Header, + i.e. it contains the correct hash of the header". + 2. untrusted.Validators = hash(untrusted.Header.Validators) + 3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) + *) + +(** + Check the precondition of ValidAndVerified, including the time checks. + + [LCV-FUNC-VALID.1::TLA-PRE.1] + *) +ValidAndVerifiedPre(trusted, untrusted, checkFuture) == + LET thdr == trusted.header + uhdr == untrusted.header + IN + /\ InTrustingPeriodLocal(thdr) + \* The untrusted block is not from the future (modulo clock drift). + \* Do the check, if it is required. + /\ checkFuture => uhdr.time < localClock + CLOCK_DRIFT + /\ ValidAndVerifiedPreUntimed(trusted, untrusted) + + +(** + Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. + This test does take current time into account, but only looks at the block structure. + + [LCV-FUNC-VALID.1::TLA-UNTIMED.1] + *) +ValidAndVerifiedUntimed(trusted, untrusted) == + IF ~ValidAndVerifiedPreUntimed(trusted, untrusted) + THEN "INVALID" + ELSE IF untrusted.header.height = trusted.header.height + 1 + \/ SignedByOneThirdOfTrusted(trusted, untrusted) + THEN "SUCCESS" + ELSE "NOT_ENOUGH_TRUST" + +(** + Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. + + [LCV-FUNC-VALID.1::TLA.1] + *) +ValidAndVerified(trusted, untrusted, checkFuture) == + IF ~ValidAndVerifiedPre(trusted, untrusted, checkFuture) + THEN "INVALID" + ELSE IF ~InTrustingPeriodLocal(untrusted.header) + (* We leave the following test for the documentation purposes. + The implementation should do this test, as signature verification may be slow. + In the TLA+ specification, ValidAndVerified happens in no time. + *) + THEN "FAILED_TRUSTING_PERIOD" + ELSE IF untrusted.header.height = trusted.header.height + 1 + \/ SignedByOneThirdOfTrusted(trusted, untrusted) + THEN "SUCCESS" + ELSE "NOT_ENOUGH_TRUST" + + +(** + The invariant of the light store that is not related to the blockchain + *) +LightStoreInv(fetchedLightBlocks, lightBlockStatus) == + \A lh, rh \in DOMAIN fetchedLightBlocks: + \* for every pair of stored headers that have been verified + \/ lh >= rh + \/ lightBlockStatus[lh] /= "StateVerified" + \/ lightBlockStatus[rh] /= "StateVerified" + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" + \* or the left header is outside the trusting period, so no guarantees + \/ LET lhdr == fetchedLightBlocks[lh] + rhdr == fetchedLightBlocks[rh] + IN + \* we can verify the right one using the left one + "SUCCESS" = ValidAndVerifiedUntimed(lhdr, rhdr) + +(** + Correctness states that all the obtained headers are exactly like in the blockchain. + + It is always the case that every verified header in LightStore was generated by + an instance of Tendermint consensus. + + [LCV-DIST-SAFE.1::CORRECTNESS-INV.1] + *) +CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" => + fetchedLightBlocks[h].header = blockchain[h] + +(** + * When the light client terminates, there are no failed blocks. + * (Otherwise, someone lied to us.) + *) +NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] /= "StateFailed" + +(** + The expected post-condition of VerifyToTarget. + *) +VerifyToTargetPost(blockchain, isPeerCorrect, + fetchedLightBlocks, lightBlockStatus, + trustedHeight, targetHeight, finalState) == + LET trustedHeader == fetchedLightBlocks[trustedHeight].header IN + \* The light client is not lying us on the trusted block. + \* It is straightforward to detect. + /\ lightBlockStatus[trustedHeight] = "StateVerified" + /\ trustedHeight \in DOMAIN fetchedLightBlocks + /\ trustedHeader = blockchain[trustedHeight] + \* the invariants we have found in the light client verification + \* there is a problem with trusting period + /\ isPeerCorrect + => CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) + \* a correct peer should fail the light client, + \* if the trusted block is in the trusting period + /\ isPeerCorrect /\ InTrustingPeriodLocalSurely(trustedHeader) + => finalState = "finishedSuccess" + /\ finalState = "finishedSuccess" => + /\ lightBlockStatus[targetHeight] = "StateVerified" + /\ targetHeight \in DOMAIN fetchedLightBlocks + /\ NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) + /\ LightStoreInv(fetchedLightBlocks, lightBlockStatus) + + +================================================================================== diff --git a/spec/light-client/detection/README.md b/spec/light-client/detection/README.md new file mode 100644 index 00000000000..50d8ab6fe1b --- /dev/null +++ b/spec/light-client/detection/README.md @@ -0,0 +1,75 @@ +--- +order: 1 +parent: + title: Fork Detection + order: 2 +--- + +# Tendermint fork detection and IBC fork detection + +## Status + +This is a work in progress. +This directory captures the ongoing work and discussion on fork +detection both in the context of a Tendermint light node and in the +context of IBC. It contains the following files + +### [detection.md](./detection.md) + +a draft of the light node fork detection including "proof of fork" + definition, that is, the data structure to submit evidence to full + nodes. + +### [discussions.md](./discussions.md) + +A collection of ideas and intuitions from recent discussions + +- the outcome of recent discussion +- a sketch of the light client supervisor to provide the context in + which fork detection happens +- a discussion about lightstore semantics + +### [req-ibc-detection.md](./req-ibc-detection.md) + +- a collection of requirements for fork detection in the IBC + context. In particular it contains a section "Required Changes in + ICS 007" with necessary updates to ICS 007 to support Tendermint + fork detection + +### [draft-functions.md](./draft-functions.md) + +In order to address the collected requirements, we started to sketch +some functions that we will need in the future when we specify in more +detail the + +- fork detections +- proof of fork generation +- proof of fork verification + +on the following components. + +- IBC on-chain components +- Relayer + +### TODOs + +We decided to merge the files while there are still open points to +address to record the current state an move forward. In particular, +the following points need to be addressed: + +- + +- + +- + +- + +Most likely we will write a specification on the light client +supervisor along the outcomes of + +- + +that also addresses initialization + +- diff --git a/spec/light-client/detection/detection_001_reviewed.md b/spec/light-client/detection/detection_001_reviewed.md new file mode 100644 index 00000000000..db8c29a143f --- /dev/null +++ b/spec/light-client/detection/detection_001_reviewed.md @@ -0,0 +1,788 @@ +# ***This an unfinished draft. Comments are welcome!*** + +**TODO:** We will need to do small adaptations to the verification +spec to reflect the semantics in the LightStore (verified, trusted, +untrusted, etc. not needed anymore). In more detail: + +- The state of the Lightstore needs to go. Functions like `LatestVerified` can +keep the name but will ignore state as it will not exist anymore. + +- verification spec should be adapted to the second parameter of +`VerifyToTarget` +being a lightblock; new version number of function tag; + +- We should clarify what is the expectation of VerifyToTarget +so if it returns TimeoutError it can be assumed faulty. I guess that +VerifyToTarget with correct full node should never terminate with +TimeoutError. + +- We need to introduce a new version number for the new +specification. So we should decide how + to handle that. + +# Light Client Attack Detector + +In this specification, we strengthen the light client to be resistant +against so-called light client attacks. In a light client attack, all +the correct Tendermint full nodes agree on the sequence of generated +blocks (no fork), but a set of faulty full nodes attack a light client +by generating (signing) a block that deviates from the block of the +same height on the blockchain. In order to do so, some of these faulty +full nodes must have been validators before and violate +[[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link), as otherwise, if +[[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link) would hold, +[verification](verification) would satisfy +[[LCV-SEQ-SAFE.1]](LCV-SEQ-SAFE-link). + +An attack detector (or detector for short) is a mechanism that is used +by the light client [supervisor](supervisor) after +[verification](verification) of a new light block +with the primary, to cross-check the newly learned light block with +other peers (secondaries). It expects as input a light block with some +height *root* (that serves as a root of trust), and a verification +trace (a sequence of lightblocks) that the primary provided. + +In case the detector observes a light client attack, it computes +evidence data that can be used by Tendermint full nodes to isolate a +set of faulty full nodes that are still within the unbonding period +(more than 1/3 of the voting power of the validator set at some block of the chain), +and report them via ABCI to the application of a Tendermint blockchain +in order to punish faulty nodes. + +## Context of this document + +The light client [verification](verification) specification is +designed for the Tendermint failure model (1/3 assumption) +[[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link). It is safe under this +assumption, and live if it can reliably (that is, no message loss, no +duplication, and eventually delivered) and timely communicate with a +correct full node. If [[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link) assumption is violated, the light client +can be fooled to trust a light block that was not generated by +Tendermint consensus. + +This specification, the attack detector, is a "second line of +defense", in case the 1/3 assumption is violated. Its goal is to +detect a light client attack (conflicting light blocks) and collect +evidence. However, it is impractical to probe all full nodes. At this +time we consider a simple scheme of maintaining an address book of +known full nodes from which a small subset (e.g., 4) are chosen +initially to communicate with. More involved book keeping with +probabilistic guarantees can be considered at later stages of the +project. + +The light client maintains a simple address book containing addresses +of full nodes that it can pick as primary and secondaries. To obtain +a new light block, the light client first does +[verification](verification) with the primary, and then cross-checks +the light block (and the trace of light blocks that led to it) with +the secondaries using this specification. + +## Tendermint Consensus and Light Client Attacks + +In this section we will give some mathematical definitions of what we +mean by light client attacks (that are considered in this +specification) and how they differ from main-chain forks. To this end +we start by defining some properties of the sequence of blocks that is +decided upon by Tendermint consensus in normal operation (if the +Tendermint failure model holds +[[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link)), +and then define different +deviations that correspond to attack scenarios. + +#### **[TMBC-GENESIS.1]** + +Let *Genesis* be the agreed-upon initial block (file). + +#### **[TMBC-FUNC-SIGN.1]** + +Let *b* and *c* be two light blocks with *b.Header.Height + 1 = +c.Header.Height*. We define the predicate **signs(b,c)** to hold +iff *c.Header.LastCommit* is in *PossibleCommit(b)*. +[[TMBC-SOUND-DISTR-POSS-COMMIT.1]](TMBC-SOUND-DISTR-POSS-COMMIT-link). + +> The above encodes sequential verification, that is, intuitively, +> b.Header.NextValidators = c.Header.Validators and 2/3 of +> these Validators signed c? + +#### **[TMBC-FUNC-SUPPORT.1]** + +Let *b* and *c* be two light blocks. We define the predicate +**supports(b,c,t)** to hold iff + +- *t - trustingPeriod < b.Header.Time < t* +- the voting power in *b.NextValidators* of nodes in *c.Commit* + is more than 1/3 of *TotalVotingPower(b.Header.NextValidators)* + +> That is, if the [Tendermint failure model](TMBC-FM-2THIRDS-link) +> holds, then *c* has been signed by at least one correct full node, cf. +> [[TMBC-VAL-CONTAINS-CORR.1]](TMBC-VAL-CONTAINS-CORR-link). +> The following formalizes that *b* was properly generated by +> Tendermint; *b* can be traced back to genesis + +#### **[TMBC-SEQ-ROOTED.1]** + +Let *b* be a light block. +We define *sequ-rooted(b)* iff for all *i*, *1 <= i < h = b.Header.Height*, +there exist light blocks *a(i)* s.t. + +- *a(1) = Genesis* and +- *a(h) = b* and +- *signs( a(i) , a(i+1) )*. + +> The following formalizes that *c* is trusted based on *b* in +> skipping verification. Observe that we do not require here (yet) +> that *b* was properly generated. + +#### **[TMBC-SKIP-TRACE.1]** + +Let *b* and *c* be light blocks. We define *skip-trace(b,c,t)* if at +time t there exists an *h* and a sequence *a(1)*, ... *a(h)* s.t. + +- *a(1) = b* and +- *a(h) = c* and +- *supports( a(i), a(i+1), t)*, for all i, *1 <= i < h*. + +We call such a sequence *a(1)*, ... *a(h)* a **verification trace**. + +> The following formalizes that two light blocks of the same height +> should agree on the content of the header. Observe that *b* and *c* +> may disagree on the Commit. This is a special case if the canonical +> commit has not been decided on, that is, if b.Header.Height is the +> maximum height of all blocks decided upon by Tendermint at this +> moment. + +#### **[TMBC-SIGN-SKIP-MATCH.1]** + +Let *a*, *b*, *c*, be light blocks and *t* a time, we define +*sign-skip-match(a,b,c,t) = true* iff the following implication +evaluates to true: + +- *sequ-rooted(a)* and +- *b.Header.Height = c.Header.Height* and +- *skip-trace(a,b,t)* +- *skip-trace(a,c,t)* + +implies *b.Header = c.Header*. + +> Observe that *sign-skip-match* is defined via an implication. If it +> evaluates to false this means that the left-hand-side of the +> implication evaluates to true, and the right-hand-side evaluates to +> false. In particular, there are two **different** headers *b* and +> *c* that both can be verified from a common block *a* from the +> chain. Thus, the following describes an attack. + +#### **[TMBC-ATTACK.1]** + +If there exists three light blocks a, b, and c, with +*sign-skip-match(a,b,c,t) = false* then we have an *attack*. We say +we have **an attack at height** *b.Header.Height* and write +*attack(a,b,c,t)*. + +> The lightblock *a* need not be unique, that is, there may be +> several blocks that satisfy the above requirement for the same +> blocks *b* and *c*. + +[[TMBC-ATTACK.1]](#TMBC-ATTACK1) is a formalization of the violation +of the agreement property based on the result of consensus, that is, +the generated blocks. + +**Remark.** +Violation of agreement is only possible if more than 1/3 of the validators (or +next validators) of some previous block deviated from the protocol. The +upcoming "accountability" specification will describe how to compute +a set of at least 1/3 faulty nodes from two conflicting blocks. [] + +There are different ways to characterize forks +and attack scenarios. This specification uses the "node-based +characterization of attacks" which focuses on what kinds of nodes are +affected (light nodes vs. full nodes). For future reference and +discussion we also provide a +"block-based characterization of attacks" below. + +### Node-based characterization of attacks + +#### **[TMBC-MC-FORK.1]** + +We say there is a (main chain) fork at time *t* if + +- there are two correct full nodes *i* and *j* and +- *i* is different from *j* and +- *i* has decided on *b* and +- *j* has decided on *c* and +- there exist *a* such that *attack(a,b,c,t)*. + +#### **[TMBC-LC-ATTACK.1]** + +We say there is a light client attack at time *t*, if + +- there is **no** (main chain) fork [[TMBC-MC-FORK.1]](#TMBC-MC-FORK1), and +- there exist nodes that have computed light blocks *b* and *c* and +- there exist *a* such that *attack(a,b,c,t)*. + +We say the attack is at height *a.Header.Height*. + +> In this specification we consider detection of light client +> attacks. Intuitively, the case we consider is that +> light block *b* is the one from the +> blockchain, and some attacker has computed *c* and tries to wrongly +> convince +> the light client that *c* is the block from the chain. + +#### **[TMBC-LC-ATTACK-EVIDENCE.1]** + +We consider the following case of a light client attack +[[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1): + +- *attack(a,b,c,t)* +- there is a peer p1 that has a sequence *chain* of blocks from *a* to *b* +- *skip-trace(a,c,t)*: by [[TMBC-SKIP-TRACE.1]](#TMBC-SKIP-TRACE1) there is a + verification trace *v* of the form *a = v(1)*, ... *v(h) = c* + +Evidence for p1 (that proves an attack) consists for index i +of v(i) and v(i+1) such that + +- E1(i). v(i) is equal to the block of *chain* at height v(i).Height, and +- E2(i). v(i+1) that is different from the block of *chain* at + height v(i+1).height + +> Observe p1 can +> +> - check that v(i+1) differs from its block at that height, and +> - verify v(i+1) in one step from v(i) as v is a verification trace. + +**Proposition.** In the case of attack, evidence exists. +*Proof.* First observe that + +- (A). (NOT E2(i)) implies E1(i+1) + +Now by contradiction assume there is no evidence. Thus + +- for all i, we have NOT E1(i) or NOT E2(i) +- for i = 1 we have E1(1) and thus NOT E2(1) + thus by induction on i, by (A) we have for all i that **E1(i)** +- from attack we have E2(h-1), and as there is no evidence for + i = h - 1 we get **NOT E1(h-1)**. Contradiction. +QED. + +#### **[TMBC-LC-EVIDENCE-DATA.1]** + +To prove the attack to p1, because of Point E1, it is sufficient to +submit + +- v(i).Height (rather than v(i)). +- v(i+1) + +This information is *evidence for height v(i).Height*. + +### Block-based characterization of attacks + +In this section we provide a different characterization of attacks. It +is not defined on the nodes that are affected but purely on the +content of the blocks. In that sense these definitions are less +operational. + +> They might be relevant for a closer analysis of fork scenarios on the +> chain, which is out of the scope of this specification. + +#### **[TMBC-SIGN-UNIQUE.1]** + +Let *b* and *c* be light blocks, we define the predicate +*sign-unique(b,c)* to evaluate to true iff the following implication +evaluates to true: + +- *b.Header.Height = c.Header.Height* and +- *sequ-rooted(b)* and +- *sequ-rooted(c)* + +implies *b = c*. + +#### **[TMBC-BLOCKS-MCFORK.1]** + +If there exists two light blocks b and c, with *sign-unique(b,c) = +false* then we have a *fork*. + +> The difference of the above definition to +> [[TMBC-MC-FORK.1]](#TMBC-MC-FORK1) is subtle. The latter requires a +> full node being affected by a bad block while +> [[TMBC-BLOCKS-MCFORK.1]](#TMBC-BLOCKS-MCFORK1) just requires that a +> bad block exists, possibly in memory of an attacker. +> The following captures a light client fork. There is no fork up to +> the height of block b. However, c is of that height, is different, +> and passes skipping verification. It is a stricter property than +> [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1), as +> [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1) requires that no correct full +> node is affected. + +#### **[TMBC-BLOCKS-LCFORK.1]** + +Let *a*, *b*, *c*, be light blocks and *t* a time. We define +*light-client-fork(a,b,c,t)* iff + +- *sign-skip-match(a,b,c,t) = false* and +- *sequ-rooted(b)* and +- *b* is "unique", that is, for all *d*, *sequ-rooted(d)* and + *d.Header.Height = b.Header.Height* implies *d = b* + +> Finally, let us also define bogus blocks that have no support. +> Observe that bogus is even defined if there is a fork. +> Also, for the definition it would be sufficient to restrict *a* to +> *a.height < b.height* (which is implied by the definitions which +> unfold until *supports()*). + +#### **[TMBC-BOGUS.1]** + +Let *b* be a light block and *t* a time. We define *bogus(b,t)* iff + +- *sequ-rooted(b) = false* and +- for all *a*, *sequ-rooted(a)* implies *skip-trace(a,b,t) = false* + +### Informal Problem statement + +There is no sequential specification: the detector only makes sense +in a distributed systems where some nodes misbehave. + +We work under the assumption that full nodes and validators are +responsible for detecting attacks on the main chain, and the evidence +reactor takes care of broadcasting evidence to communicate +misbehaving nodes via ABCI to the application, and halt the chain in +case of a fork. The point of this specification is to shield a light +clients against attacks that cannot be detected by full nodes, and +are fully addressed at light clients (and consequently IBC relayers, +which use the light client protocols to observe the state of a +blockchain). In order to provide full nodes the incentive to follow +the protocols when communicating with the light client, this +specification also considers the generation of evidence that will +also be processed by the Tendermint blockchain. + +#### **[LCD-IP-MODEL.1]** + +The detector is designed under the assumption that + +- [[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link) may be violated +- there is no fork on the main chain. + +> As a result some faulty full nodes may launch an attack on a light +> client. + +The following requirements are operational in that they describe how +things should be done, rather than what should be done. However, they +do not constitute temporal logic verification conditions. For those, +see [LCD-DIST-*] below. + +The detector is called in the [supervisor](supervisor) as follows + +```go +Evidences := AttackDetector(root_of_trust, verifiedLS);` +``` + +where + +- `root-of-trust` is a light block that is trusted (that is, +except upon initialization, the primary and the secondaries +agreed on in the past), and +- `verifiedLS` is a lightstore that contains a verification trace that + starts from a lightblock that can be verified with the + `root-of-trust` in one step and ends with a lightblock of the height + requested by the user +- `Evidences` is a list of evidences for misbehavior + +#### **[LCD-IP-STATEMENT.1]** + +Whenever AttackDetector is called, the detector should for each +secondary try to replay the verification trace `verifiedLS` with the +secondary + +- in case replaying leads to detection of a light client attack + (one of the lightblocks differ from the one in verifiedLS with + the same height), we should return evidence +- if the secondary cannot provide a verification trace, we have no + proof for an attack. Block *b* may be bogus. In this case the + secondary is faulty and it should be replaced. + +## Assumptions/Incentives/Environment + +It is not in the interest of faulty full nodes to talk to the +detector as long as the detector is connected to at least one +correct full node. This would only increase the likelihood of +misbehavior being detected. Also we cannot punish them easily +(cheaply). The absence of a response need not be the fault of the full +node. + +Correct full nodes have the incentive to respond, because the +detector may help them to understand whether their header is a good +one. We can thus base liveness arguments of the detector on +the assumptions that correct full nodes reliably talk to the +detector. + +### Assumptions + +#### **[LCD-A-CorrFull.1]** + +At all times there is at least one correct full +node among the primary and the secondaries. + +> For this version of the detection we take this assumption. It +> allows us to establish the invariant that the lightblock +> `root-of-trust` is always the one from the blockchain, and we can +> use it as starting point for the evidence computation. Moreover, it +> allows us to establish the invariant at the supervisor that any +> lightblock in the (top-level) lightstore is from the blockchain. +> In the future we might design a lightclient based on the assumption +> that at least in regular intervals the lightclient is connected to a +> correct full node. This will require the detector to reconsider +> `root-of-trust`, and remove lightblocks from the top-level +> lightstore. + +#### **[LCD-A-RelComm.1]** + +Communication between the detector and a correct full node is +reliable and bounded in time. Reliable communication means that +messages are not lost, not duplicated, and eventually delivered. There +is a (known) end-to-end delay *Delta*, such that if a message is sent +at time *t* then it is received and processed by time *t + Delta*. +This implies that we need a timeout of at least *2 Delta* for remote +procedure calls to ensure that the response of a correct peer arrives +before the timeout expires. + +## Definitions + +### Evidence + +Following the definition of +[[TMBC-LC-ATTACK-EVIDENCE.1]](#TMBC-LC-ATTACK-EVIDENCE1), by evidence +we refer to a variable of the following type + +#### **[LC-DATA-EVIDENCE.1]** + +```go +type LightClientAttackEvidence struct { + ConflictingBlock LightBlock + CommonHeight int64 +} +``` + +As the above data is computed for a specific peer, the following +data structure wraps the evidence and adds the peerID. + +#### **[LC-DATA-EVIDENCE-INT.1]** + +```go +type InternalEvidence struct { + Evidence LightClientAttackEvidence + Peer PeerID +} +``` + +#### **[LC-SUMBIT-EVIDENCE.1]** + +```go +func submitEvidence(Evidences []InternalEvidence) +``` + +- Expected postcondition + - for each `ev` in `Evidences`: submit `ev.Evidence` to `ev.Peer` + +--- + +### LightStore + +Lightblocks and LightStores are defined in the verification +specification [LCV-DATA-LIGHTBLOCK.1] and [LCV-DATA-LIGHTSTORE.1]. See +the [verification specification][verification] for details. + +## (Distributed) Problem statement + +> As the attack detector is there to reduce the impact of faulty +> nodes, and faulty nodes imply that there is a distributed system, +> there is no sequential specification to which this distributed +> problem statement may refer to. + +The detector gets as input a trusted lightblock called *root* and an +auxiliary lightstore called *primary_trace* with lightblocks that have +been verified before, and that were provided by the primary. + +#### **[LCD-DIST-INV-ATTACK.1]** + +If the detector returns evidence for height *h* +[[TMBC-LC-EVIDENCE-DATA.1]](#TMBC-LC-EVIDENCE-DATA1), then there is an +attack at height *h*. [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1) + +#### **[LCD-DIST-INV-STORE.1]** + +If the detector does not return evidence, then *primary_trace* +contains only blocks from the blockchain. + +#### **[LCD-DIST-LIVE.1]** + +The detector eventually terminates. + +#### **[LCD-DIST-TERM-NORMAL.1]** + +If + +- the *primary_trace* contains only blocks from the blockchain, and +- there is no attack, and +- *Secondaries* is always non-empty, and +- the age of *root* is always less than the trusting period, + +then the detector does not return evidence. + +#### **[LCD-DIST-TERM-ATTACK.1]** + +If + +- there is an attack, and +- a secondary reports a block that conflicts + with one of the blocks in *primary_trace*, and +- *Secondaries* is always non-empty, and +- the age of *root* is always less than the trusting period, + +then the detector returns evidence. + +> Observe that above we require that "a secondary reports a block that +> conflicts". If there is an attack, but no secondary tries to launch +> it against the detector (or the message from the secondary is lost +> by the network), then there is nothing to detect for us. + +#### **[LCD-DIST-SAFE-SECONDARY.1]** + +No correct secondary is ever replaced. + +#### **[LCD-DIST-SAFE-BOGUS.1]** + +If + +- a secondary reports a bogus lightblock, +- the age of *root* is always less than the trusting period, + +then the secondary is replaced before the detector terminates. + +> The above property is quite operational ("reports"), but it captures +> quite closely the requirement. As the +> detector only makes sense in a distributed setting, and does +> not have a sequential specification, less "pure" +> specification are acceptable. + +# Protocol + +## Functions and Data defined in other Specifications + +### From the supervisor + +```go +Replace_Secondary(addr Address, root-of-trust LightBlock) +``` + +### From the verifier + +```go +func VerifyToTarget(primary PeerID, root LightBlock, + targetHeight Height) (LightStore, Result) +``` + +> Note: the above differs from the current version in the second +> parameter. verification will be revised. + +Observe that `VerifyToTarget` does communication with the secondaries +via the function [FetchLightBlock](fetch). + +### Shared data of the light client + +- a pool of full nodes *FullNodes* that have not been contacted before +- peer set called *Secondaries* +- primary + +> Note that the lightStore is not needed to be shared. + +## Outline + +The problem laid out is solved by calling the function `AttackDetector` +with a lightstore that contains a light block that has just been +verified by the verifier. + +Then `AttackDetector` downloads headers from the secondaries. In case +a conflicting header is downloaded from a secondary, +`CreateEvidenceForPeer` which computes evidence in the case that +indeed an attack is confirmed. It could be that the secondary reports +a bogus block, which means that there need not be an attack, and the +secondary is replaced. + +## Details of the functions + +#### **[LCD-FUNC-DETECTOR.1]:** + +```go +func AttackDetector(root LightBlock, primary_trace []LightBlock) + ([]InternalEvidence) { + + Evidences := new []InternalEvidence; + + for each secondary in Secondaries { + // we replay the primary trace with the secondary, in + // order to generate evidence that we can submit to the + // secodary. We return the evidence + the trace the + // secondary told us that spans the evidence at its local store + + EvidenceForSecondary, newroot, secondary_trace, result := + CreateEvidenceForPeer(secondary, + root, + primary_trace); + if result == FaultyPeer { + Replace_Secondary(root); + } + else if result == FoundEvidence { + // the conflict is not bogus + Evidences.Add(EvidenceForSecondary); + // we replay the secondary trace with the primary, ... + EvidenceForPrimary, _, result := + CreateEvidenceForPeer(primary, + newroot, + secondary_trace); + if result == FoundEvidence { + Evidences.Add(EvidenceForPrimary); + } + // At this point we do not care about the other error + // codes. We already have generated evidence for an + // attack and need to stop the lightclient. It does not + // help to call replace_primary. Also we will use the + // same primary to check with other secondaries in + // later iterations of the loop + } + // In the case where the secondary reports NoEvidence + // we do nothing + } + return Evidences; +} +``` + +- Expected precondition + - root and primary trace are a verification trace +- Expected postcondition + - solves the problem statement (if attack found, then evidence is reported) +- Error condition + - `ErrorTrustExpired`: fails if root expires (outside trusting + period) [[LCV-INV-TP.1]](LCV-INV-TP1-link) + - `ErrorNoPeers`: if no peers are left to replace secondaries, and + no evidence was found before that happened + +--- + +```go +func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) + (Evidence, LightBlock, LightStore, result) { + + common := root; + + for i in 1 .. len(trace) { + auxLS, result := VerifyToTarget(peer, common, trace[i].Header.Height) + + if result != ResultSuccess { + // something went wrong; peer did not provide a verifyable block + return (nil, nil, nil, FaultyPeer) + } + else { + if auxLS.LatestVerified().Header != trace[i].Header { + // the header reported by the peer differs from the + // reference header in trace but both could be + // verified from common in one step. + // we can create evidence for submission to the secondary + ev := new InternalEvidence; + ev.Evidence.ConflictingBlock := trace[i]; + ev.Evidence.CommonHeight := common.Height; + ev.Peer := peer + return (ev, common, auxLS, FoundEvidence) + } + else { + // the peer agrees with the trace, we move common forward + // we could delete auxLS as it will be overwritten in + // the next iteration + common := trace[i] + } + } + } + return (nil, nil, nil, NoEvidence) +} +``` + +- Expected precondition + - root and trace are a verification trace +- Expected postcondition + - finds evidence where trace and peer diverge +- Error condition + - `ErrorTrustExpired`: fails if root expires (outside trusting + period) [[LCV-INV-TP.1]](LCV-INV-TP1-link) + - If `VerifyToTarget` returns error but root is not expired then return + `FaultyPeer` + +--- + +## Correctness arguments + +#### Argument for [[LCD-DIST-INV-ATTACK.1]](#LCD-DIST-INV-ATTACK1) + +Under the assumption that root and trace are a verification trace, +when in `CreateEvidenceForPeer` the detector the detector creates +evidence, then the lightclient has seen two different headers (one via +`trace` and one via `VerifyToTarget` for the same height that can both +be verified in one step. + +#### Argument for [[LCD-DIST-INV-STORE.1]](#LCD-DIST-INV-STORE1) + +We assume that there is at least one correct peer, and there is no +fork. As a result the correct peer has the correct sequence of +blocks. Since the primary_trace is checked block-by-block also against +each secondary, and at no point evidence was generated that means at +no point there were conflicting blocks. + +#### Argument for [[LCD-DIST-LIVE.1]](#LCD-DIST-LIVE1) + +At the latest when [[LCV-INV-TP.1]](LCV-INV-TP1-link) is violated, +`AttackDetector` terminates. + +#### Argument for [[LCD-DIST-TERM-NORMAL.1]](#LCD-DIST-TERM-NORMAL1) + +As there are finitely many peers, eventually the main loop +terminates. As there is no attack no evidence can be generated. + +#### Argument for [[LCD-DIST-TERM-ATTACK.1]](#LCD-DIST-TERM-ATTACK1) + +Argument similar to [[LCD-DIST-TERM-NORMAL.1]](#LCD-DIST-TERM-NORMAL1) + +#### Argument for [[LCD-DIST-SAFE-SECONDARY.1]](#LCD-DIST-SAFE-SECONDARY1) + +Secondaries are only replaced if they time-out or if they report bogus +blocks. The former is ruled out by the timing assumption, the latter +by correct peers only reporting blocks from the chain. + +#### Argument for [[LCD-DIST-SAFE-BOGUS.1]](#LCD-DIST-SAFE-BOGUS1) + +Once a bogus block is recognized as such the secondary is removed. + +# References + +> links to other specifications/ADRs this document refers to + +[[verification]] The specification of the light client verification. + +[[supervisor]] The specification of the light client supervisor. + +[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md + +[supervisor]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor.md + +[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md + +[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#tmbc-fm-2thirds1 + +[TMBC-SOUND-DISTR-POSS-COMMIT-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#tmbc-sound-distr-poss-commit1 + +[LCV-SEQ-SAFE-link]:https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#lcv-seq-safe1 + +[TMBC-VAL-CONTAINS-CORR-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#tmbc-val-contains-corr1 + +[fetch]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#lcv-func-fetch1 + +[LCV-INV-TP1-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#lcv-inv-tp1 diff --git a/spec/light-client/detection/detection_003_reviewed.md b/spec/light-client/detection/detection_003_reviewed.md new file mode 100644 index 00000000000..13f6e9716a9 --- /dev/null +++ b/spec/light-client/detection/detection_003_reviewed.md @@ -0,0 +1,839 @@ +# Light Client Attack Detector + +In this specification, we strengthen the light client to be resistant +against so-called light client attacks. In a light client attack, all +the correct Tendermint full nodes agree on the sequence of generated +blocks (no fork), but a set of faulty full nodes attack a light client +by generating (signing) a block that deviates from the block of the +same height on the blockchain. In order to do so, some of these faulty +full nodes must have been validators before and violate the assumption +of more than two thirds of "correct voting power" +[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link], as otherwise, if +[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] would hold, +[verification][verification] would satisfy +[[LCV-SEQ-SAFE.1]][LCV-SEQ-SAFE-link]. + +An attack detector (or detector for short) is a mechanism that is used +by the light client [supervisor][supervisor] after +[verification][verification] of a new light block +with the primary, to cross-check the newly learned light block with +other peers (secondaries). It expects as input a light block with some +height *root* (that serves as a root of trust), and a verification +trace (a sequence of lightblocks) that the primary provided. + +In case the detector observes a light client attack, it computes +evidence data that can be used by Tendermint full nodes to isolate a +set of faulty full nodes that are still within the unbonding period +(more than 1/3 of the voting power of the validator set at some block +of the chain), and report them via ABCI (application/blockchain +interface) +to the application of a +Tendermint blockchain in order to punish faulty nodes. + +## Context of this document + +The light client [verification][verification] specification is +designed for the Tendermint failure model (1/3 assumption) +[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link]. It is safe under this +assumption, and live if it can reliably (that is, no message loss, no +duplication, and eventually delivered) and timely communicate with a +correct full node. If [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] +assumption is violated, the light client can be fooled to trust a +light block that was not generated by Tendermint consensus. + +This specification, the attack detector, is a "second line of +defense", in case the 1/3 assumption is violated. Its goal is to +detect a light client attack (conflicting light blocks) and collect +evidence. However, it is impractical to probe all full nodes. At this +time we consider a simple scheme of maintaining an address book of +known full nodes from which a small subset (e.g., 4) are chosen +initially to communicate with. More involved book keeping with +probabilistic guarantees can be considered at later stages of the +project. + +The light client maintains a simple address book containing addresses +of full nodes that it can pick as primary and secondaries. To obtain +a new light block, the light client first does +[verification][verification] with the primary, and then cross-checks +the light block (and the trace of light blocks that led to it) with +the secondaries using this specification. + +# Outline + +- [Part I](#part-i---Tendermint-Consensus-and-Light-Client-Attacks): + Formal definitions of lightclient attacks, based on basic + properties of Tendermint consensus. + - [Node-based characterization of + attacks](#Node-based-characterization-of-attacks). The + definition of attacks used in the problem statement of + this specification. + + - [Block-based characterization of attacks](#Block-based-characterization-of-attacks). Alternative definitions + provided for future reference. + +- [Part II](#part-ii---problem-statement): Problem statement of + lightclient attack detection + + - [Informal Problem Statement](#informal-problem-statement) + - [Assumptions](#Assumptions) + - [Definitions](#definitions) + - [Distributed Problem statement](#Distributed-Problem-statement) + +- [Part III](#part-iii---protocol): The protocol + + - [Functions and Data defined in other Specifications](#Functions-and-Data-defined-in-other-Specifications) + - [Outline of Solution](#Outline-of-solution) + - [Details of the functions](#Details-of-the-functions) + - [Correctness arguments](#Correctness-arguments) + +# Part I - Tendermint Consensus and Light Client Attacks + +In this section we will give some mathematical definitions of what we +mean by light client attacks (that are considered in this +specification) and how they differ from main-chain forks. To this end, +we start by defining some properties of the sequence of blocks that is +decided upon by Tendermint consensus in normal operation (if the +Tendermint failure model holds +[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link]), +and then define different +deviations that correspond to attack scenarios. We consider the notion +of [light blocks][LCV-LB-link] and [headers][LVC-HD-link]. + +#### **[TMBC-GENESIS.1]** + +Let *Genesis* be the agreed-upon initial block (file). + +#### **[TMBC-FUNC-SIGN.1]** + +Let *b* and *c* be two light blocks with *b.Header.Height + 1 = +c.Header.Height*. We define the predicate **signs(b,c)** to hold +iff *c.Header.LastCommit* is in *PossibleCommit(b)*. +[[TMBC-SOUND-DISTR-POSS-COMMIT.1]][TMBC-SOUND-DISTR-POSS-COMMIT-link]. + +> The above encodes sequential verification, that is, intuitively, +> b.Header.NextValidators = c.Header.Validators and 2/3 of +> these Validators signed c. + +#### **[TMBC-FUNC-SUPPORT.1]** + +Let *b* and *c* be two light blocks. We define the predicate +**supports(b,c,t)** to hold iff + +- *t - trustingPeriod < b.Header.Time < t* +- the voting power in *b.NextValidators* of nodes in *c.Commit* + is more than 1/3 of *TotalVotingPower(b.Header.NextValidators)* + +> That is, if the [Tendermint failure model][TMBC-FM-2THIRDS-link] +> holds, then *c* has been signed by at least one correct full node, cf. +> [[TMBC-VAL-CONTAINS-CORR.1]][TMBC-VAL-CONTAINS-CORR-link]. +> The following formalizes that *b* was properly generated by +> Tendermint; *b* can be traced back to genesis. + +#### **[TMBC-SEQ-ROOTED.1]** + +Let *b* be a light block. +We define *sequ-rooted(b)* iff for all *i*, *1 <= i < h = b.Header.Height*, +there exist light blocks *a(i)* s.t. + +- *a(1) = Genesis* and +- *a(h) = b* and +- *signs( a(i) , a(i+1) )*. + +> The following formalizes that *c* is trusted based on *b* in +> skipping verification. Observe that we do not require here (yet) +> that *b* was properly generated. + +#### **[TMBC-SKIP-TRACE.1]** + +Let *b* and *c* be light blocks. We define *skip-trace(b,c,t)* if at +time t there exists an integer *h* and a sequence *a(1)*, ... *a(h)* s.t. + +- *a(1) = b* and +- *a(h) = c* and +- *supports( a(i), a(i+1), t)*, for all i, *1 <= i < h*. + +We call such a sequence *a(1)*, ... *a(h)* a **verification trace**. + +> The following formalizes that two light blocks of the same height +> should agree on the content of the header. Observe that *b* and *c* +> may disagree on the Commit. This is a special case if the canonical +> commit has not been decided on yet, that is, if b.Header.Height is the +> maximum height of all blocks decided upon by Tendermint at this +> moment. + +#### **[TMBC-SIGN-SKIP-MATCH.1]** + +Let *a*, *b*, *c*, be light blocks and *t* a time, we define +*sign-skip-match(a,b,c,t) = true* iff the following implication +evaluates to true: + +- *sequ-rooted(a)* and +- *b.Header.Height = c.Header.Height* and +- *skip-trace(a,b,t)* +- *skip-trace(a,c,t)* + +implies *b.Header = c.Header*. + +> Observe that *sign-skip-match* is defined via an implication. If it +> evaluates to false this means that the left-hand-side of the +> implication evaluates to true, and the right-hand-side evaluates to +> false. In particular, there are two **different** headers *b* and +> *c* that both can be verified from a common block *a* from the +> chain. Thus, the following describes an attack. + +#### **[TMBC-ATTACK.1]** + +If there exists three light blocks a, b, and c, with +*sign-skip-match(a,b,c,t) = false* then we have an *attack*. We say +we have **an attack at height** *b.Header.Height* and write +*attack(a,b,c,t)*. + +> The lightblock *a* need not be unique, that is, there may be +> several blocks that satisfy the above requirement for the same +> blocks *b* and *c*. + +[[TMBC-ATTACK.1]](#TMBC-ATTACK1) is a formalization of the violation +of the agreement property based on the result of consensus, that is, +the generated blocks. + +**Remark.** +Violation of agreement is only possible if more than 1/3 of the validators (or +next validators) of some previous block deviated from the protocol. The +upcoming "accountability" specification will describe how to compute +a set of at least 1/3 faulty nodes from two conflicting blocks. [] + +There are different ways to characterize forks +and attack scenarios. This specification uses the "node-based +characterization of attacks" which focuses on what kinds of nodes are +affected (light nodes vs. full nodes). For future reference and +discussion we also provide a +"block-based characterization of attacks" below. + +## Node-based characterization of attacks + +#### **[TMBC-MC-FORK.1]** + +We say there is a (main chain) fork at time *t* if + +- there are two correct full nodes *i* and *j* and +- *i* is different from *j* and +- *i* has decided on *b* and +- *j* has decided on *c* and +- there exist *a* such that *attack(a,b,c,t)*. + +#### **[TMBC-LC-ATTACK.1]** + +We say there is a light client attack at time *t*, if + +- there is **no** (main chain) fork [[TMBC-MC-FORK.1]](#TMBC-MC-FORK1), and +- there exist nodes that have computed light blocks *b* and *c* and +- there exist *a* such that *attack(a,b,c,t)*. + +We say the attack is at height *a.Header.Height*. + +> In this specification we consider detection of light client +> attacks. Intuitively, the case we consider is that +> light block *b* is the one from the +> blockchain, and some attacker has computed *c* and tries to wrongly +> convince +> the light client that *c* is the block from the chain. + +#### **[TMBC-LC-ATTACK-EVIDENCE.1]** + +We consider the following case of a light client attack +[[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1): + +- *attack(a,b,c,t)* +- there is a peer p1 that has a sequence *chain* of blocks from *a* to *b* +- *skip-trace(a,c,t)*: by [[TMBC-SKIP-TRACE.1]](#TMBC-SKIP-TRACE1) there is a + verification trace *v* of the form *a = v(1)*, ... *v(h) = c* + +Evidence for p1 (that proves an attack to p1) consists for index i +of v(i) and v(i+1) such that + +- E1(i). v(i) is equal to the block of *chain* at height v(i).Height, and +- E2(i). v(i+1) that is different from the block of *chain* at + height v(i+1).height + +> Observe p1 can +> +> - check that v(i+1) differs from its block at that height, and +> - verify v(i+1) in one step from v(i) as v is a verification trace. + +#### **[TMBC-LC-EVIDENCE-DATA.1]** + +To prove the attack to p1, because of Point E1, it is sufficient to +submit + +- v(i).Height (rather than v(i)). +- v(i+1) + +This information is *evidence for height v(i).Height*. + +## Block-based characterization of attacks + +In this section we provide a different characterization of attacks. It +is not defined on the nodes that are affected but purely on the +content of the blocks. In that sense these definitions are less +operational. + +> They might be relevant for a closer analysis of fork scenarios on the +> chain, which is out of the scope of this specification. + +#### **[TMBC-SIGN-UNIQUE.1]** + +Let *b* and *c* be light blocks, we define the predicate +*sign-unique(b,c)* to evaluate to true iff the following implication +evaluates to true: + +- *b.Header.Height = c.Header.Height* and +- *sequ-rooted(b)* and +- *sequ-rooted(c)* + +implies *b = c*. + +#### **[TMBC-BLOCKS-MCFORK.1]** + +If there exists two light blocks b and c, with *sign-unique(b,c) = +false* then we have a *fork*. + +> The difference of the above definition to +> [[TMBC-MC-FORK.1]](#TMBC-MC-FORK1) is subtle. The latter requires a +> full node being affected by a bad block while +> [[TMBC-BLOCKS-MCFORK.1]](#TMBC-BLOCKS-MCFORK1) just requires that a +> bad block exists, possibly in memory of an attacker. +> The following captures a light client fork. There is no fork up to +> the height of block b. However, c is of that height, is different, +> and passes skipping verification. It is a stricter property than +> [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1), as +> [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1) requires that no correct full +> node is affected. + +#### **[TMBC-BLOCKS-LCFORK.1]** + +Let *a*, *b*, *c*, be light blocks and *t* a time. We define +*light-client-fork(a,b,c,t)* iff + +- *sign-skip-match(a,b,c,t) = false* and +- *sequ-rooted(b)* and +- *b* is "unique", that is, for all *d*, *sequ-rooted(d)* and + *d.Header.Height = b.Header.Height* implies *d = b* + +> Finally, let us also define bogus blocks that have no support. +> Observe that bogus is even defined if there is a fork. +> Also, for the definition it would be sufficient to restrict *a* to +> *a.height < b.height* (which is implied by the definitions which +> unfold until *supports()*). + +#### **[TMBC-BOGUS.1]** + +Let *b* be a light block and *t* a time. We define *bogus(b,t)* iff + +- *sequ-rooted(b) = false* and +- for all *a*, *sequ-rooted(a)* implies *skip-trace(a,b,t) = false* + +# Part II - Problem Statement + +## Informal Problem statement + +There is no sequential specification: the detector only makes sense +in a distributed systems where some nodes misbehave. + +We work under the assumption that full nodes and validators are +responsible for detecting attacks on the main chain, and the evidence +reactor takes care of broadcasting evidence to communicate +misbehaving nodes via ABCI to the application, and halt the chain in +case of a fork. The point of this specification is to shield a light +clients against attacks that cannot be detected by full nodes, and +are fully addressed at light clients (and consequently IBC relayers, +which use the light client protocols to observe the state of a +blockchain). In order to provide full nodes the incentive to follow +the protocols when communicating with the light client, this +specification also considers the generation of evidence that will +also be processed by the Tendermint blockchain. + +#### **[LCD-IP-MODEL.1]** + +The detector is designed under the assumption that + +- [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] may be violated +- there is no fork on the main chain. + +> As a result some faulty full nodes may launch an attack on a light +> client. + +The following requirements are operational in that they describe how +things should be done, rather than what should be done. However, they +do not constitute temporal logic verification conditions. For those, +see [LCD-DIST-*] below. + +The detector is called in the [supervisor][supervisor] as follows + +```go +Evidences := AttackDetector(root_of_trust, verifiedLS);` +``` + +where + +- `root-of-trust` is a light block that is trusted (that is, +except upon initialization, the primary and the secondaries +agreed on in the past), and +- `verifiedLS` is a lightstore that contains a verification trace that + starts from a lightblock that can be verified with the + `root-of-trust` in one step and ends with a lightblock of the height + requested by the user +- `Evidences` is a list of evidences for misbehavior + +#### **[LCD-IP-STATEMENT.1]** + +Whenever AttackDetector is called, the detector should for each +secondary cross check the largest header in verifiedLS with the +corresponding header of the same height provided by the secondary. If +there is a deviation, the detector should +try to replay the verification trace `verifiedLS` with the +secondary + +- in case replaying leads to detection of a light client attack + (one of the lightblocks differ from the one in verifiedLS with + the same height), we should return evidence +- if the secondary cannot provide a verification trace, we have no + proof for an attack. Block *b* may be bogus. In this case the + secondary is faulty and it should be replaced. + +## Assumptions + +It is not in the interest of faulty full nodes to talk to the +detector as long as the detector is connected to at least one +correct full node. This would only increase the likelihood of +misbehavior being detected. Also we cannot punish them easily +(cheaply). The absence of a response need not be the fault of the full +node. + +Correct full nodes have the incentive to respond, because the +detector may help them to understand whether their header is a good +one. We can thus base liveness arguments of the detector on +the assumptions that correct full nodes reliably talk to the +detector. + +#### **[LCD-A-CorrFull.1]** + +At all times there is at least one correct full +node among the primary and the secondaries. + +> For this version of the detection we take this assumption. It +> allows us to establish the invariant that the lightblock +> `root-of-trust` is always the one from the blockchain, and we can +> use it as starting point for the evidence computation. Moreover, it +> allows us to establish the invariant at the supervisor that any +> lightblock in the (top-level) lightstore is from the blockchain. +> In the future we might design a lightclient based on the assumption +> that at least in regular intervals the lightclient is connected to a +> correct full node. This will require the detector to reconsider +> `root-of-trust`, and remove lightblocks from the top-level +> lightstore. + +#### **[LCD-A-RelComm.1]** + +Communication between the detector and a correct full node is +reliable and bounded in time. Reliable communication means that +messages are not lost, not duplicated, and eventually delivered. There +is a (known) end-to-end delay *Delta*, such that if a message is sent +at time *t* then it is received and processed by time *t + Delta*. +This implies that we need a timeout of at least *2 Delta* for remote +procedure calls to ensure that the response of a correct peer arrives +before the timeout expires. + +## Definitions + +### Evidence + +Following the definition of +[[TMBC-LC-ATTACK-EVIDENCE.1]](#TMBC-LC-ATTACK-EVIDENCE1), by evidence +we refer to a variable of the following type + +#### **[LC-DATA-EVIDENCE.1]** + +```go +type LightClientAttackEvidence struct { + ConflictingBlock LightBlock + CommonHeight int64 + + // Evidence also includes application specific data which is not + // part of verification but is sent to the application once the + // evidence gets committed on chain. +} +``` + +As the above data is computed for a specific peer, the following +data structure wraps the evidence and adds the peerID. + +#### **[LC-DATA-EVIDENCE-INT.1]** + +```go +type InternalEvidence struct { + Evidence LightClientAttackEvidence + Peer PeerID +} +``` + +#### **[LC-SUMBIT-EVIDENCE.1]** + +```go +func submitEvidence(Evidences []InternalEvidence) +``` + +- Expected postcondition + - for each `ev` in `Evidences`: submit `ev.Evidence` to `ev.Peer` + +--- + +### LightStore + +Lightblocks and LightStores are defined in the verification +specification [[LCV-DATA-LIGHTBLOCK.1]][LCV-LB-link] +and [[LCV-DATA-LIGHTSTORE.2]][LCV-LS-link]. See +the [verification specification][verification] for details. + +## Distributed Problem statement + +> As the attack detector is there to reduce the impact of faulty +> nodes, and faulty nodes imply that there is a distributed system, +> there is no sequential specification to which this distributed +> problem statement may refer to. + +The detector gets as input a trusted lightblock called *root* and an +auxiliary lightstore called *primary_trace* with lightblocks that have +been verified before, and that were provided by the primary. + +#### **[LCD-DIST-INV-ATTACK.1]** + +If the detector returns evidence for height *h* +[[TMBC-LC-EVIDENCE-DATA.1]](#TMBC-LC-EVIDENCE-DATA1), then there is an +attack at height *h*. [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1) + +#### **[LCD-DIST-INV-STORE.1]** + +If the detector does not return evidence, then *primary_trace* +contains only blocks from the blockchain. + +#### **[LCD-DIST-LIVE.1]** + +The detector eventually terminates. + +#### **[LCD-DIST-TERM-NORMAL.1]** + +If + +- the *primary_trace* contains only blocks from the blockchain, and +- there is no attack, and +- *Secondaries* is always non-empty, and +- the age of *root* is always less than the trusting period, + +then the detector does not return evidence. + +#### **[LCD-DIST-TERM-ATTACK.1]** + +If + +- there is an attack, and +- a secondary reports a block that conflicts + with one of the blocks in *primary_trace*, and +- *Secondaries* is always non-empty, and +- the age of *root* is always less than the trusting period, + +then the detector returns evidence. + +> Observe that above we require that "a secondary reports a block that +> conflicts". If there is an attack, but no secondary tries to launch +> it against the detector (or the message from the secondary is lost +> by the network), then there is nothing to detect for us. + +#### **[LCD-DIST-SAFE-SECONDARY.1]** + +No correct secondary is ever replaced. + +#### **[LCD-DIST-SAFE-BOGUS.1]** + +If + +- a secondary reports a bogus lightblock, +- the age of *root* is always less than the trusting period, + +then the secondary is replaced before the detector terminates. + +> The above property is quite operational (e.g., the usage of +> "reports"), but it captures closely the requirement. As the +> detector only makes sense in a distributed setting, and does not +> have a sequential specification, a less "pure" specification are +> acceptable. + +# Part III - Protocol + +## Functions and Data defined in other Specifications + +### From the [supervisor][supervisor] + +[[LC-FUNC-REPLACE-SECONDARY.1]][repl] + +```go +Replace_Secondary(addr Address, root-of-trust LightBlock) +``` + +### From the [verifier][verification] + +[[LCV-FUNC-MAIN.2]][vtt] + +```go +func VerifyToTarget(primary PeerID, root LightBlock, + targetHeight Height) (LightStore, Result) +``` + +Observe that `VerifyToTarget` does communication with the secondaries +via the function [FetchLightBlock][fetch]. + +### Shared data of the light client + +- a pool of full nodes *FullNodes* that have not been contacted before +- peer set called *Secondaries* +- primary + +> Note that the lightStore is not needed to be shared. + +## Outline of solution + +The problem laid out is solved by calling the function `AttackDetector` +with a lightstore that contains a light block that has just been +verified by the verifier. + +Then `AttackDetector` downloads headers from the secondaries. In case +a conflicting header is downloaded from a secondary, it calls +`CreateEvidenceForPeer` which computes evidence in the case that +indeed an attack is confirmed. It could be that the secondary reports +a bogus block, which means that there need not be an attack, and the +secondary is replaced. + +## Details of the functions + +#### **[LCD-FUNC-DETECTOR.2]:** + +```go +func AttackDetector(root LightBlock, primary_trace []LightBlock) + ([]InternalEvidence) { + + Evidences := new []InternalEvidence; + + for each secondary in Secondaries { + lb, result := FetchLightBlock(secondary,primary_trace.Latest().Header.Height); + if result != ResultSuccess { + Replace_Secondary(root); + } + else if lb.Header != primary_trace.Latest().Header { + + // we replay the primary trace with the secondary, in + // order to generate evidence that we can submit to the + // secondary. We return the evidence + the trace the + // secondary told us that spans the evidence at its local store + + EvidenceForSecondary, newroot, secondary_trace, result := + CreateEvidenceForPeer(secondary, + root, + primary_trace); + if result == FaultyPeer { + Replace_Secondary(root); + } + else if result == FoundEvidence { + // the conflict is not bogus + Evidences.Add(EvidenceForSecondary); + // we replay the secondary trace with the primary, ... + EvidenceForPrimary, _, result := + CreateEvidenceForPeer(primary, + newroot, + secondary_trace); + if result == FoundEvidence { + Evidences.Add(EvidenceForPrimary); + } + // At this point we do not care about the other error + // codes. We already have generated evidence for an + // attack and need to stop the lightclient. It does not + // help to call replace_primary. Also we will use the + // same primary to check with other secondaries in + // later iterations of the loop + } + // In the case where the secondary reports NoEvidence + // after initially it reported a conflicting header. + // secondary is faulty + Replace_Secondary(root); + } + } + return Evidences; +} +``` + +- Expected precondition + - root and primary trace are a verification trace +- Expected postcondition + - solves the problem statement (if attack found, then evidence is reported) +- Error condition + - `ErrorTrustExpired`: fails if root expires (outside trusting + period) [[LCV-INV-TP.1]][LCV-INV-TP1-link] + - `ErrorNoPeers`: if no peers are left to replace secondaries, and + no evidence was found before that happened + +--- + +```go +func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) + (Evidence, LightBlock, LightStore, result) { + + common := root; + + for i in 1 .. len(trace) { + auxLS, result := VerifyToTarget(peer, common, trace[i].Header.Height) + + if result != ResultSuccess { + // something went wrong; peer did not provide a verifiable block + return (nil, nil, nil, FaultyPeer) + } + else { + if auxLS.LatestVerified().Header != trace[i].Header { + // the header reported by the peer differs from the + // reference header in trace but both could be + // verified from common in one step. + // we can create evidence for submission to the secondary + ev := new InternalEvidence; + ev.Evidence.ConflictingBlock := trace[i]; + // CommonHeight is used to indicate the type of attack + // if the CommonHeight != ConflictingBlock.Height this + // is by definition a lunatic attack else it is an + // equivocation attack + ev.Evidence.CommonHeight := common.Height; + ev.Peer := peer + return (ev, common, auxLS, FoundEvidence) + } + else { + // the peer agrees with the trace, we move common forward. + // we could delete auxLS as it will be overwritten in + // the next iteration + common := trace[i] + } + } + } + return (nil, nil, nil, NoEvidence) +} +``` + +- Expected precondition + - root and trace are a verification trace +- Expected postcondition + - finds evidence where trace and peer diverge +- Error condition + - `ErrorTrustExpired`: fails if root expires (outside trusting + period) [[LCV-INV-TP.1]][LCV-INV-TP1-link] + - If `VerifyToTarget` returns error but root is not expired then return + `FaultyPeer` + +--- + +## Correctness arguments + +#### On the existence of evidence + +**Proposition.** In the case of attack, +evidence [[TMBC-LC-ATTACK-EVIDENCE.1]](#TMBC-LC-ATTACK-EVIDENCE1) + exists. +*Proof.* First observe that + +- (A). (NOT E2(i)) implies E1(i+1) + +Now by contradiction assume there is no evidence. Thus + +- for all i, we have NOT E1(i) or NOT E2(i) +- for i = 1 we have E1(1) and thus NOT E2(1) + thus by induction on i, by (A) we have for all i that **E1(i)** +- from attack we have E2(h-1), and as there is no evidence for + i = h - 1 we get **NOT E1(h-1)**. Contradiction. +QED. + +#### Argument for [[LCD-DIST-INV-ATTACK.1]](#LCD-DIST-INV-ATTACK1) + +Under the assumption that root and trace are a verification trace, +when in `CreateEvidenceForPeer` the detector creates +evidence, then the lightclient has seen two different headers (one via +`trace` and one via `VerifyToTarget`) for the same height that can both +be verified in one step. + +#### Argument for [[LCD-DIST-INV-STORE.1]](#LCD-DIST-INV-STORE1) + +We assume that there is at least one correct peer, and there is no +fork. As a result, the correct peer has the correct sequence of +blocks. Since the primary_trace is checked block-by-block also against +each secondary, and at no point evidence was generated that means at +no point there were conflicting blocks. + +#### Argument for [[LCD-DIST-LIVE.1]](#LCD-DIST-LIVE1) + +At the latest when [[LCV-INV-TP.1]][LCV-INV-TP1-link] is violated, +`AttackDetector` terminates. + +#### Argument for [[LCD-DIST-TERM-NORMAL.1]](#LCD-DIST-TERM-NORMAL1) + +As there are finitely many peers, eventually the main loop +terminates. As there is no attack no evidence can be generated. + +#### Argument for [[LCD-DIST-TERM-ATTACK.1]](#LCD-DIST-TERM-ATTACK1) + +Argument similar to [[LCD-DIST-TERM-NORMAL.1]](#LCD-DIST-TERM-NORMAL1) + +#### Argument for [[LCD-DIST-SAFE-SECONDARY.1]](#LCD-DIST-SAFE-SECONDARY1) + +Secondaries are only replaced if they time-out or if they report bogus +blocks. The former is ruled out by the timing assumption, the latter +by correct peers only reporting blocks from the chain. + +#### Argument for [[LCD-DIST-SAFE-BOGUS.1]](#LCD-DIST-SAFE-BOGUS1) + +Once a bogus block is recognized as such the secondary is removed. + +# References + +> links to other specifications/ADRs this document refers to + +[[verification]] The specification of the light client verification. + +[[supervisor]] The specification of the light client supervisor. + +[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md + +[supervisor]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor_001_draft.md + +[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md + +[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-fm-2thirds1 + +[TMBC-SOUND-DISTR-POSS-COMMIT-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-sound-distr-poss-commit1 + +[LCV-SEQ-SAFE-link]:https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-seq-safe1 + +[TMBC-VAL-CONTAINS-CORR-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-val-contains-corr1 + +[fetch]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-func-fetch1 + +[LCV-INV-TP1-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-inv-tp1 + +[LCV-LB-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-data-lightblock1 + +[LCV-LS-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-data-lightstore2 + +[LVC-HD-link]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-header-fields2 + +[repl]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor_001_draft.md#lc-func-replace-secondary1 + +[vtt]: +https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-func-main2 diff --git a/spec/light-client/detection/discussions.md b/spec/light-client/detection/discussions.md new file mode 100644 index 00000000000..82702dd69d1 --- /dev/null +++ b/spec/light-client/detection/discussions.md @@ -0,0 +1,178 @@ +# Results of Discussions and Decisions + +- Generating a minimal proof of fork (as suggested in [Issue #5083](https://github.com/tendermint/tendermint/issues/5083)) is too costly at the light client + - we do not know all lightblocks from the primary + - therefore there are many scenarios. we might even need to ask + the primary again for additional lightblocks to isolate the + branch. + +> For instance, the light node starts with block at height 1 and the +> primary provides a block of height 10 that the light node can +> verify immediately. In cross-checking, a secondary now provides a +> conflicting header b10 of height 10 that needs another header b5 +> of height 5 to +> verify. Now, in order for the light node to convince the primary: +> +> - The light node cannot just sent b5, as it is not clear whether +> the fork happened before or after 5 +> - The light node cannot just send b10, as the primary would also +> need b5 for verification +> - In order to minimize the evidence, the light node may try to +> figure out where the branch happens, e.g., by asking the primary +> for height 5 (it might be that more queries are required, also +> to the secondary. However, assuming that in this scenario the +> primary is faulty it may not respond. + + As the main goal is to catch misbehavior of the primary, + evidence generation and punishment must not depend on their + cooperation. So the moment we have proof of fork (even if it + contains several light blocks) we should submit right away. + +- decision: "full" proof of fork consists of two traces that originate in the + same lightblock and lead to conflicting headers of the same height. + +- For submission of proof of fork, we may do some optimizations, for + instance, we might just submit a trace of lightblocks that verifies a block + different from the one the full node knows (we do not send the trace + the primary gave us back to the primary) + +- The light client attack is via the primary. Thus we try to + catch if the primary installs a bad light block + - We do not check secondary against secondary + - For each secondary, we check the primary against one secondary + +- Observe that just two blocks for the same height are not +sufficient proof of fork. +One of the blocks may be bogus [TMBC-BOGUS.1] which does +not constitute slashable behavior. +Which leads to the question whether the light node should try to do +fork detection on its initial block (from subjective +initialization). This could be done by doing backwards verification +(with the hashes) until a bifurcation block is found. +While there are scenarios where a +fork could be found, there is also the scenario where a faulty full +node feeds the light node with bogus light blocks and forces the light +node to check hashes until a bogus chain is out of the trusting period. +As a result, the light client +should not try to detect a fork for its initial header. **The initial +header must be trusted as is.** + +# Light Client Sequential Supervisor + +**TODO:** decide where (into which specification) to put the +following: + +We describe the context on which the fork detector is called by giving +a sequential version of the supervisor function. +Roughly, it alternates two phases namely: + +- Light Client Verification. As a result, a header of the required + height has been downloaded from and verified with the primary. +- Light Client Fork Detections. As a result the header has been + cross-checked with the secondaries. In case there is a fork we + submit "proof of fork" and exit. + +#### **[LC-FUNC-SUPERVISOR.1]:** + +```go +func Sequential-Supervisor () (Error) { + loop { + // get the next height + nextHeight := input(); + + // Verify + result := NoResult; + while result != ResultSuccess { + lightStore,result := VerifyToTarget(primary, lightStore, nextHeight); + if result == ResultFailure { + // pick new primary (promote a secondary to primary) + /// and delete all lightblocks above + // LastTrusted (they have not been cross-checked) + Replace_Primary(); + } + } + + // Cross-check + PoFs := Forkdetector(lightStore, PoFs); + if PoFs.Empty { + // no fork detected with secondaries, we trust the new + // lightblock + LightStore.Update(testedLB, StateTrusted); + } + else { + // there is a fork, we submit the proofs and exit + for i, p range PoFs { + SubmitProofOfFork(p); + } + return(ErrorFork); + } + } +} +``` + +**TODO:** finish conditions + +- Implementation remark +- Expected precondition + - *lightStore* initialized with trusted header + - *PoFs* empty +- Expected postcondition + - runs forever, or + - is terminated by user and satisfies LightStore invariant, or **TODO** + - has submitted proof of fork upon detecting a fork +- Error condition + - none + +---- + +# Semantics of the LightStore + +Currently, a lightblock in the lightstore can be in one of the +following states: + +- StateUnverified +- StateVerified +- StateFailed +- StateTrusted + +The intuition is that `StateVerified` captures that the lightblock has +been verified with the primary, and `StateTrusted` is the state after +successful cross-checking with the secondaries. + +Assuming there is **always one correct node among primary and +secondaries**, and there is no fork on the blockchain, lightblocks that +are in `StateTrusted` can be used by the user with the guarantee of +"finality". If a block in `StateVerified` is used, it might be that +detection later finds a fork, and a roll-back might be needed. + +**Remark:** The assumption of one correct node, does not render +verification useless. It is true that if the primary and the +secondaries return the same block we may trust it. However, if there +is a node that provides a different block, the light node still needs +verification to understand whether there is a fork, or whether the +different block is just bogus (without any support of some previous +validator set). + +**Remark:** A light node may choose the full nodes it communicates +with (the light node and the full node might even belong to the same +stakeholder) so the assumption might be justified in some cases. + +In the future, we will do the following changes + +- we assume that only from time to time, the light node is + connected to a correct full node +- this means for some limited time, the light node might have no + means to defend against light client attacks +- as a result we do not have finality +- once the light node reconnects with a correct full node, it + should detect the light client attack and submit evidence. + +Under these assumptions, `StateTrusted` loses its meaning. As a +result, it should be removed from the API. We suggest that we replace +it with a flag "trusted" that can be used + +- internally for efficiency reasons (to maintain + [LCD-INV-TRUSTED-AGREED.1] until a fork is detected) +- by light client based on the "one correct full node" assumption + +---- diff --git a/spec/light-client/detection/draft-functions.md b/spec/light-client/detection/draft-functions.md new file mode 100644 index 00000000000..c56594a533a --- /dev/null +++ b/spec/light-client/detection/draft-functions.md @@ -0,0 +1,289 @@ +# Draft of Functions for Fork detection and Proof of Fork Submisstion + +This document collects drafts of function for generating and +submitting proof of fork in the IBC context + +- [IBC](#on---chain-ibc-component) + +- [Relayer](#relayer) + +## On-chain IBC Component + +> The following is a suggestions to change the function defined in ICS 007 + +#### [TAG-IBC-MISBEHAVIOR.1] + +```go +func checkMisbehaviourAndUpdateState(cs: ClientState, PoF: LightNodeProofOfFork) +``` + +**TODO:** finish conditions + +- Implementation remark +- Expected precondition + - PoF.TrustedBlock.Header is equal to lightBlock on store with + same height + - both traces end with header of same height + - headers are different + - both traces are supported by PoF.TrustedBlock (`supports` + defined in [TMBC-FUNC]), that is, for `t = currentTimestamp()` (see + ICS 024) + - supports(PoF.TrustedBlock, PoF.PrimaryTrace[1], t) + - supports(PoF.PrimaryTrace[i], PoF.PrimaryTrace[i+1], t) for + *0 < i < length(PoF.PrimaryTrace)* + - supports(PoF.TrustedBlock, PoF.SecondaryTrace[1], t) + - supports(PoF.SecondaryTrace[i], PoF.SecondaryTrace[i+1], t) for + *0 < i < length(PoF.SecondaryTrace)* +- Expected postcondition + - set cs.FrozenHeight to min(cs.FrozenHeight, PoF.TrustedBlock.Header.Height) +- Error condition + - none + +---- + +> The following is a suggestions to add functionality to ICS 002 and 007. +> I suppose the above is the most efficient way to get the required +> information. Another option is to subscribe to "header install" +> events via CosmosSDK + +#### [TAG-IBC-HEIGHTS.1] + +```go +func QueryHeightsRange(id, from, to) ([]Height) +``` + +- Expected postcondition + - returns all heights *h*, with *from <= h <= to* for which the + IBC component has a consensus state. + +---- + +> This function can be used if the relayer has no information about +> the IBC component. This allows late-joining relayers to also +> participate in fork dection and the generation in proof of +> fork. Alternatively, we may also postulate that relayers are not +> responsible to detect forks for heights before they started (and +> subscribed to the transactions reporting fresh headers being +> installed at the IBC component). + +## Relayer + +### Auxiliary Functions to be implemented in the Light Client + +#### [LCV-LS-FUNC-GET-PREV.1] + +```go +func (ls LightStore) GetPreviousVerified(height Height) (LightBlock, bool) +``` + +- Expected postcondition + - returns a verified LightBlock, whose height is maximal among all + verified lightblocks with height smaller than `height` + +---- + +### Relayer Submitting Proof of Fork to the IBC Component + +There are two ways the relayer can detect a fork + +- by the fork detector of one of its lightclients +- be checking the consensus state of the IBC component + +The following function ignores how the proof of fork was generated. +It takes a proof of fork as input and computes a proof of fork that + will be accepted by the IBC component. +The problem addressed here is that both, the relayer's light client + and the IBC component have incomplete light stores, that might + not have all light blocks in common. +Hence the relayer has to figure out what the IBC component knows + (intuitively, a meeting point between the two lightstores + computed in `commonRoot`) and compute a proof of fork + (`extendPoF`) that the IBC component will accept based on its + knowledge. + +The auxiliary functions `commonRoot` and `extendPoF` are +defined below. + +#### [TAG-SUBMIT-POF-IBC.1] + +```go +func SubmitIBCProofOfFork( + lightStore LightStore, + PoF: LightNodeProofOfFork, + ibc IBCComponent) (Error) { + if ibc.queryChainConsensusState(PoF.TrustedBlock.Height) = PoF.TrustedBlock { + // IBC component has root of PoF on store, we can just submit + ibc.submitMisbehaviourToClient(ibc.id,PoF) + return Success + // note sure about the id parameter + } + else { + // the ibc component does not have the TrustedBlock and might + // even be on yet a different branch. We have to compute a PoF + // that the ibc component can verifiy based on its current + // knowledge + + ibcLightBlock, lblock, _, result := commonRoot(lightStore, ibc, PoF.TrustedBlock) + + if result = Success { + newPoF = extendPoF(ibcLightBlock, lblock, lightStore, PoF) + ibc.submitMisbehaviourToClient(ibc.id, newPoF) + return Success + } + else{ + return CouldNotGeneratePoF + } + } +} +``` + +**TODO:** finish conditions + +- Implementation remark +- Expected precondition +- Expected postcondition +- Error condition + - none + +---- + +### Auxiliary Functions at the Relayer + +> If the relayer detects a fork, it has to compute a proof of fork that +> will convince the IBC component. That is it has to compare the +> relayer's local lightstore against the lightstore of the IBC +> component, and find common ancestor lightblocks. + +#### [TAG-COMMON-ROOT.1] + +```go +func commonRoot(lightStore LightStore, ibc IBCComponent, lblock +LightBlock) (LightBlock, LightBlock, LightStore, Result) { + + auxLS.Init + + // first we ask for the heights the ibc component is aware of + ibcHeights = ibc.QueryHeightsRange( + ibc.id, + lightStore.LowestVerified().Height, + lblock.Height - 1); + // this function does not exist yet. Alternatively, we may + // request all transactions that installed headers via CosmosSDK + + + for { + h, result = max(ibcHeights) + if result = Empty { + return (_, _, _, NoRoot) + } + ibcLightBlock = ibc.queryChainConsensusState(h) + auxLS.Update(ibcLightBlock, StateVerified); + connector, result := Connector(lightStore, ibcLightBlock, lblock.Header.Height) + if result = success { + return (ibcLightBlock, connector, auxLS, Success) + } + else{ + ibcHeights.remove(h) + } + } +} +``` + +- Expected postcondition + - returns + - a lightBlock b1 from the IBC component, and + - a lightBlock b2 + from the local lightStore with height less than + lblock.Header.Hight, s.t. b1 supports b2, and + - a lightstore with the blocks downloaded from + the ibc component + +---- + +#### [TAG-LS-FUNC-CONNECT.1] + +```go +func Connector (lightStore LightStore, lb LightBlock, h Height) (LightBlock, bool) +``` + +- Expected postcondition + - returns a verified LightBlock from lightStore with height less + than *h* that can be + verified by lb in one step. + +**TODO:** for the above to work we need an invariant that all verified +lightblocks form a chain of trust. Otherwise, we need a lightblock +that has a chain of trust to height. + +> Once the common root is found, a proof of fork that will be accepted +> by the IBC component needs to be generated. This is done in the +> following function. + +#### [TAG-EXTEND-POF.1] + +```go +func extendPoF (root LightBlock, + connector LightBlock, + lightStore LightStore, + Pof LightNodeProofofFork) (LightNodeProofofFork} +``` + +- Implementation remark + - PoF is not sufficient to convince an IBC component, so we extend + the proof of fork farther in the past +- Expected postcondition + - returns a newPOF: + - newPoF.TrustedBlock = root + - let prefix = + connector + + lightStore.Subtrace(connector.Header.Height, PoF.TrustedBlock.Header.Height-1) + + PoF.TrustedBlock + - newPoF.PrimaryTrace = prefix + PoF.PrimaryTrace + - newPoF.SecondaryTrace = prefix + PoF.SecondaryTrace + +### Detection a fork at the IBC component + +The following functions is assumed to be called regularly to check +that latest consensus state of the IBC component. Alternatively, this +logic can be executed whenever the relayer is informed (via an event) +that a new header has been installed. + +#### [TAG-HANDLER-DETECT-FORK.1] + +```go +func DetectIBCFork(ibc IBCComponent, lightStore LightStore) (LightNodeProofOfFork, Error) { + cs = ibc.queryClientState(ibc); + lb, found := lightStore.Get(cs.Header.Height) + if !found { + **TODO:** need verify to target + lb, result = LightClient.Main(primary, lightStore, cs.Header.Height) + // [LCV-FUNC-IBCMAIN.1] + **TODO** decide what to do following the outcome of Issue #499 + + // I guess here we have to get into the light client + + } + if cs != lb { + // IBC component disagrees with my primary. + // I fetch the + ibcLightBlock, lblock, ibcStore, result := commonRoot(lightStore, ibc, lb) + pof = new LightNodeProofOfFork; + pof.TrustedBlock := ibcLightBlock + pof.PrimaryTrace := ibcStore + cs + pof.SecondaryTrace := lightStore.Subtrace(lblock.Header.Height, + lb.Header.Height); + return(pof, Fork) + } + return(nil , NoFork) +} +``` + +**TODO:** finish conditions + +- Implementation remark + - we ask the handler for the lastest check. Cross-check with the + chain. In case they deviate we generate PoF. + - we assume IBC component is correct. It has verified the + consensus state +- Expected precondition +- Expected postcondition diff --git a/spec/light-client/detection/req-ibc-detection.md b/spec/light-client/detection/req-ibc-detection.md new file mode 100644 index 00000000000..439ca26b641 --- /dev/null +++ b/spec/light-client/detection/req-ibc-detection.md @@ -0,0 +1,345 @@ +# Requirements for Fork Detection in the IBC Context + +## What you need to know about IBC + +In the following, I distilled what I considered relevant from + + + +### Components and their interface + +#### Tendermint Blockchains + +> I assume you know what that is. + +#### An IBC/Tendermint correspondence + +| IBC Term | Tendermint-RS Spec Term | Comment | +|----------|-------------------------| --------| +| `CommitmentRoot` | AppState | app hash | +| `ConsensusState` | Lightblock | not all fields are there. NextValidator is definitly needed | +| `ClientState` | latest light block + configuration parameters (e.g., trusting period + `frozenHeight` | NextValidators missing; what is `proofSpecs`?| +| `frozenHeight` | height of fork | set when a fork is detected | +| "would-have-been-fooled" | light node fork detection | light node may submit proof of fork to IBC component to halt it | +| `Height` | (no epochs) | (epoch,height) pair in lexicographical order (`compare`) | +| `Header` | ~signed header | validatorSet explicit (no hash); nextValidators missing | +| `Evidence` | t.b.d. | definition unclear "which the light client would have considered valid". Data structure will need to change | +| `verify` | `ValidAndVerified` | signature does not match perfectly (ClientState vs. LightBlock) + in `checkMisbehaviourAndUpdateState` it is unclear whether it uses traces or goes to h1 and h2 in one step | + +#### Some IBC links + +- [QueryConsensusState](https://github.com/cosmos/cosmos-sdk/blob/2651427ab4c6ea9f81d26afa0211757fc76cf747/x/ibc/02-client/client/utils/utils.go#L68) + +#### Required Changes in ICS 007 + +- `assert(height > 0)` in definition of `initialise` doesn't match + definition of `Height` as *(epoch,height)* pair. + +- `initialise` needs to be updated to new data structures + +- `clientState.frozenHeight` semantics seem not totally consistent in + document. E.g., `min` needs to be defined over optional value in + `checkMisbehaviourAndUpdateState`. Also, if you are frozen, why do + you accept more evidence. + +- `checkValidityAndUpdateState` + - `verify`: it needs to be clarified that checkValidityAndUpdateState + does not perform "bisection" (as currently hinted in the text) but + performs a single step of "skipping verification", called, + `ValidAndVerified` + - `assert (header.height > clientState.latestHeight)`: no old + headers can be installed. This might be OK, but we need to check + interplay with misbehavior + - clienstState needs to be updated according to complete data + structure + +- `checkMisbehaviourAndUpdateState`: as evidence will contain a trace + (or two), the assertion that uses verify will need to change. + +- ICS 002 states w.r.t. `queryChainConsensusState` that "Note that + retrieval of past consensus states by height (as opposed to just the + current consensus state) is convenient but not required." For + Tendermint fork detection, this seems to be a necessity. + +- `Header` should become a lightblock + +- `Evidence` should become `LightNodeProofOfFork` [LCV-DATA-POF.1] + +- `upgradeClientState` what is the semantics (in particular what is + `height` doing?). + +- `checkMisbehaviourAndUpdateState(cs: ClientState, PoF: + LightNodeProofOfFork)` needs to be adapted + +#### Handler + +A blockchain runs a **handler** that passively collects information about + other blockchains. It can be thought of a state machine that takes + input events. + +- the state includes a lightstore (I guess called `ConsensusState` + in IBC) + +- The following function is used to pass a header to a handler + +```go +type checkValidityAndUpdateState = (Header) => Void +``` + + For Tendermint, it will perform + `ValidandVerified`, that is, it does the trusting period check and the + +1/3 check (+2/3 for sequential headers). + If it verifies a header, it adds it to its lightstore, + if it does not pass verification it drops it. + Right now it only accepts a header more recent then the latest + header, + and drops older + ones or ones that could not be verified. + +> The above paragraph captures what I believe what is the current + logic of `checkValidityAndUpdateState`. It may be subject to + change. E.g., maintain a lightstore with state (unverified, verified) + +- The following function is used to pass "evidence" (this we + will need to make precise eventually) to a handler + +```go +type checkMisbehaviourAndUpdateState = (bytes) => Void +``` + + We have to design this, and the data that the handler can use to + check that there was some misbehavior (fork) in order react on + it, e.g., flagging a situation and + stop the protocol. + +- The following function is used to query the light store (`ConsensusState`) + +```go +type queryChainConsensusState = (height: uint64) => ConsensusState +``` + +#### Relayer + +- The active components are called **relayer**. + +- a relayer contains light clients to two (or more?) blockchains + +- the relayer send headers and data to the handler to invoke + `checkValidityAndUpdateState` and + `checkMisbehaviourAndUpdateState`. It may also query + `queryChainConsensusState`. + +- multiple relayers may talk to one handler. Some relayers might be + faulty. We assume existence of at least single correct relayer. + +## Informal Problem Statement: Fork detection in IBC + +### Relayer requirement: Evidence for Handler + +- The relayer should provide the handler with + "evidence" that there was a fork. + +- The relayer can read the handler's consensus state. Thus the relayer can + feed the handler precisely the information the handler needs to detect a + fork. + What is this + information needs to be specified. + +- The information depends on the verification the handler does. It + might be necessary to provide a bisection proof (list of + lightblocks) so that the handler can verify based on its local + lightstore a header *h* that is conflicting with a header *h'* in the + local lightstore, that is, *h != h'* and *h.Height = h'.Height* + +### Relayer requirement: Fork detection + +Let's assume there is a fork at chain A. There are two ways the +relayer can figure that out: + +1. as the relayer contains a light client for A, it also includes a fork + detector that can detect a fork. + +2. the relayer may also detect a fork by observing that the + handler for chain A (on chain B) + is on a different branch than the relayer + +- in both detection scenarios, the relayer should submit evidence to + full nodes of chain A where there is a fork. As we assume a fullnode + has a complete list of blocks, it is sufficient to send "Bucky's + evidence" (), + that is, + - two lightblocks from different branches + + - a lightblock (perhaps just a height) from which both blocks + can be verified. + +- in the scenario 2., the relayer must feed the A-handler (on chain B) + a proof of a fork on A so that chain B can react accordingly + +### Handler requirement + +- there are potentially many relayers, some correct some faulty + +- a handler cannot trust the information provided by the relayer, + but must verify + (Доверя́й, но проверя́й) + +- in case of a fork, we accept that the handler temporarily stores + headers (tagged as verified). + +- eventually, a handler should be informed + (`checkMisbehaviourAndUpdateState`) + by some relayer that it has + verified a header from a fork. Then the handler should do what is + required by IBC in this case (stop?) + +### Challenges in the handler requirement + +- handlers and relayers work on different lightstores. In principle + the lightstore need not intersect in any heights a priori + +- if a relayer sees a header *h* it doesn't know at a handler (`queryChainConsensusState`), the + relayer needs to + verify that header. If it cannot do it locally based on downloaded + and verified (trusted?) light blocks, it might need to use + `VerifyToTarget` (bisection). To call `VerifyToTarget` we might keep + *h* in the lightstore. If verification fails, we need to download the + "alternative" header of height *h.Height* to generate evidence for + the handler. + +- we have to specify what precisely `queryChainConsensusState` + returns. It cannot be the complete lightstore. Is the last header enough? + +- we would like to assume that every now and then (smaller than the + trusting period) a correct relayer checks whether the handler is on a + different branch than the relayer. + And we would like that this is enough to achieve + the Handler requirement. + + - here the correctness argument would be easy if a correct relayer is + based on a light client with a *trusted* state, that is, a light + client who never changes its opinion about trusted. Then if such a + correct relayer checks-in with a handler, it will detect a fork, and + act in time. + + - if the light client does not provide this interface, in the case of + a fork, we need some assumption about a correct relayer being on a + different branch than the handler, and we need such a relayer to + check-in not too late. Also + what happens if the relayer's light client is forced to roll-back + its lightstore? + Does it have to re-check all handlers? + +## On the interconnectedness of things + +In the broader discussion of so-called "fork accountability" there are +several subproblems + +- Fork detection + +- Evidence creation and submission + +- Isolating misbehaving nodes (and report them for punishment over abci) + +### Fork detection + +The preliminary specification ./detection.md formalizes the notion of +a fork. Roughly, a fork exists if there are two conflicting headers +for the same height, where both are supported by bonded full nodes +(that have been validators in the near past, that is, within the +trusting period). We distinguish between *fork on the chain* where two +conflicting blocks are signed by +2/3 of the validators of that +height, and a *light client fork* where one of the conflicting headers +is not signed by +2/3 of the current height, but by +1/3 of the +validators of some smaller height. + +In principle everyone can detect a fork + +- ./detection talks about the Tendermint light client with a focus on + light nodes. A relayer runs such light clients and may detect + forks in this way + +- in IBC, a relayer can see that a handler is on a conflicting branch + - the relayer should feed the handler the necessary information so + that it can halt + - the relayer should report the fork to a full node + +### Evidence creation and submission + +- the information sent from the relayer to the handler could be called + evidence, but this is perhaps a bad idea because the information sent to a + full node can also be called evidence. But this evidence might still + not be enough as the full node might need to run the "fork + accountability" protocol to generate evidence in the form of + consensus messages. So perhaps we should + introduce different terms for: + + - proof of fork for the handler (basically consisting of lightblocks) + - proof of fork for a full node (basically consisting of (fewer) lightblocks) + - proof of misbehavior (consensus messages) + +### Isolating misbehaving nodes + +- this is the job of a full node. + +- might be subjective in the future: the protocol depends on what the + full node believes is the "correct" chain. Right now we postulate + that every full node is on the correct chain, that is, there is no + fork on the chain. + +- The full node figures out which nodes are + - lunatic + - double signing + - amnesic; **using the challenge response protocol** + +- We do not punish "phantom" validators + - currently we understand a phantom validator as a node that + - signs a block for a height in which it is not in the + validator set + - the node is not part of the +1/3 of previous validators that + are used to support the header. Whether we call a validator + phantom might be subjective and depend on the header we + check against. Their formalization actually seems not so + clear. + - they can only do something if there are +1/3 faulty validators + that are either lunatic, double signing, or amnesic. + - abci requires that we only report bonded validators. So if a + node is a "phantom", we would need the check whether the node is + bonded, which currently is expensive, as it requires checking + blocks from the last three weeks. + - in the future, with state sync, a correct node might be + convinced by faulty nodes that it is in the validator set. Then + it might appear to be "phantom" although it behaves correctly + +## Next steps + +> The following points are subject to my limited knowledge of the +> state of the work on IBC. Some/most of it might already exist and we +> will just need to bring everything together. + +- "proof of fork for a full node" defines a clean interface between + fork detection and misbehavior isolation. So it should be produced + by protocols (light client, the relayer). So we should fix that + first. + +- Given the problems of not having a light client architecture spec, + for the relayer we should start with this. E.g. + + - the relayer runs light clients for two chains + - the relayer regularly queries consensus state of a handler + - the relayer needs to check the consensus state + - this involves local checks + - this involves calling the light client + - the relayer uses the light client to do IBC business (channels, + packets, connections, etc.) + - the relayer submits proof of fork to handlers and full nodes + +> the list is definitely not complete. I think part of this +> (perhaps all) is +> covered by what Anca presented recently. + +We will need to define what we expect from these components + +- for the parts where the relayer talks to the handler, we need to fix + the interface, and what the handler does + +- we write specs for these components. diff --git a/spec/light-client/experiments.png b/spec/light-client/experiments.png new file mode 100644 index 0000000000000000000000000000000000000000..94166ffa31ee93970b8b97cafe375ba580c1d319 GIT binary patch literal 83681 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 z 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 Another related line is IBC attack detection and submission at the +> relayer, as well as attack verification at the IBC handler. This +> will call for yet another spec. + +# Status + +This document is work in progress. In order to develop the +specification step-by-step, +it assumes certain details of [verification](https://informal.systems) and +[detection](https://informal.systems) that are not specified in the respective current +versions yet. This inconsistencies will be addresses over several +upcoming PRs. + +# Part I - Tendermint Blockchain + +See [verification spec](addLinksWhenDone) + +# Part II - Sequential Problem Definition + +#### **[LC-SEQ-INIT-LIVE.1]** + +Upon initialization, the light client gets as input a header of the +blockchain, or the genesis file of the blockchain, and eventually +stores a header of the blockchain. + +#### **[LC-SEQ-LIVE.1]** + +The light client gets a sequence of heights as inputs. For each input +height *targetHeight*, it eventually stores the header of height +*targetHeight*. + +#### **[LC-SEQ-SAFE.1]** + +The light client never stores a header which is not in the blockchain. + +# Part III - Light Client as Distributed System + +## Computational Model + +The light client communicates with remote processes only via the +[verification](TODO) and the [detection](TODO) protocols. The +respective assumptions are given there. + +## Distributed Problem Statement + +### Two Kinds of Liveness + +In case of light client attacks, the sequential problem statement +cannot always be satisfied. The lightclient cannot decide which block +is from the chain and which is not. As a result, the light client just +creates evidence, submits it, and terminates. +For the liveness property, we thus add the +possibility that instead of adding a lightblock, we also might terminate +in case there is an attack. + +#### **[LC-DIST-TERM.1]** + +The light client either runs forever or it *terminates on attack*. + +### Design choices + +#### [LC-DIST-STORE.1] + +The light client has a local data structure called LightStore +that contains light blocks (that contain a header). + +> The light store exposes functions to query and update it. They are +> specified [here](TODO:onceVerificationIsMerged). + +**TODO:** reference light store invariant [LCV-INV-LS-ROOT.2] once +verification is merged + +#### **[LC-DIST-SAFE.1]** + +It is always the case that every header in *LightStore* was +generated by an instance of Tendermint consensus. + +#### **[LC-DIST-LIVE.1]** + +Whenever the light client gets a new height *h* as input, + +- and there is +no light client attack up to height *h*, then the lightclient +eventually puts the lightblock of height *h* in the lightstore and +wait for another input. +- otherwise, that is, if there +is a light client attack on height *h*, then the light client +must perform one of the following: + - it terminates on attack. + - it eventually puts the lightblock of height *h* in the lightstore and +wait for another input. + +> Observe that the "existence of a lightclient attack" just means that some node has generated a conflicting block. It does not necessarily mean that a (faulty) peer sends such a block to "our" lightclient. Thus, even if there is an attack somewhere in the system, our lightclient might still continue to operate normally. + +### Solving the sequential specification + +[LC-DIST-SAFE.1] is guaranteed by the detector; in particular it +follows from +[[LCD-DIST-INV-STORE.1]](TODO) +[[LCD-DIST-LIVE.1]](TODO) + +# Part IV - Light Client Supervisor Protocol + +We provide a specification for a sequential Light Client Supervisor. +The local code for verification is presented by a sequential function +`Sequential-Supervisor` to highlight the control flow of this +functionality. Each lightblock is first verified with a primary, and then +cross-checked with secondaries, and if all goes well, the lightblock +is +added (with the attribute "trusted") to the +lightstore. Intermiate lightblocks that were used to verify the target +block but were not cross-checked are stored as "verified" + +> We note that if a different concurrency model is considered +> for an implementation, the semantics of the lightstore might change: +> In a concurrent implementation, we might do verification for some +> height *h*, add the +> lightblock to the lightstore, and start concurrent threads that +> +> - do verification for the next height *h' != h* +> - do cross-checking for height *h*. If we find an attack, we remove +> *h* from the lightstore. +> - the user might already start to use *h* +> +> Thus, this concurrency model changes the semantics of the +> lightstore (not all lightblocks that are read by the user are +> trusted; they may be removed if +> we find a problem). Whether this is desirable, and whether the gain in +> performance is worth it, we keep for future versions/discussion of +> lightclient protocols. + +## Definitions + +### Peers + +#### **[LC-DATA-PEERS.1]:** + +A fixed set of full nodes is provided in the configuration upon +initialization. Initially this set is partitioned into + +- one full node that is the *primary* (singleton set), +- a set *Secondaries* (of fixed size, e.g., 3), +- a set *FullNodes*; it excludes *primary* and *Secondaries* nodes. +- A set *FaultyNodes* of nodes that the light client suspects of + being faulty; it is initially empty + +#### **[LC-INV-NODES.1]:** + +The detector shall maintain the following invariants: + +- *FullNodes \intersect Secondaries = {}* +- *FullNodes \intersect FaultyNodes = {}* +- *Secondaries \intersect FaultyNodes = {}* + +and the following transition invariant + +- *FullNodes' \union Secondaries' \union FaultyNodes' = FullNodes + \union Secondaries \union FaultyNodes* + +#### **[LC-FUNC-REPLACE-PRIMARY.1]:** + +```go +Replace_Primary(root-of-trust LightBlock) +``` + +- Implementation remark + - the primary is replaced by a secondary + - to maintain a constant size of secondaries, need to + - pick a new secondary *nsec* while ensuring [LC-INV-ROOT-AGREED.1] + - that is, we need to ensure that root-of-trust = FetchLightBlock(nsec, root-of-trust.Header.Height) +- Expected precondition + - *FullNodes* is nonempty +- Expected postcondition + - *primary* is moved to *FaultyNodes* + - a secondary *s* is moved from *Secondaries* to primary +- Error condition + - if precondition is violated + +#### **[LC-FUNC-REPLACE-SECONDARY.1]:** + +```go +Replace_Secondary(addr Address, root-of-trust LightBlock) +``` + +- Implementation remark + - maintain [LC-INV-ROOT-AGREED.1], that is, + ensure root-of-trust = FetchLightBlock(nsec, root-of-trust.Header.Height) +- Expected precondition + - *FullNodes* is nonempty +- Expected postcondition + - addr is moved from *Secondaries* to *FaultyNodes* + - an address *nsec* is moved from *FullNodes* to *Secondaries* +- Error condition + - if precondition is violated + +### Data Types + +The core data structure of the protocol is the LightBlock. + +#### **[LC-DATA-LIGHTBLOCK.1]** + +```go +type LightBlock struct { + Header Header + Commit Commit + Validators ValidatorSet + NextValidators ValidatorSet + Provider PeerID +} +``` + +#### **[LC-DATA-LIGHTSTORE.1]** + +LightBlocks are stored in a structure which stores all LightBlock from +initialization or received from peers. + +```go +type LightStore struct { + ... +} + +``` + +We use the functions that the LightStore exposes, which +are defined in the [verification specification](TODO). + +### Inputs + +The lightclient is initialized with LCInitData + +#### **[LC-DATA-INIT.1]** + +```go +type LCInitData struct { + lightBlock LightBlock + genesisDoc GenesisDoc +} +``` + +where only one of the components must be provided. `GenesisDoc` is +defined in the [Tendermint +Types](https://github.com/tendermint/tendermint/blob/master/types/genesis.go). + +#### **[LC-DATA-GENESIS.1]** + +```go +type GenesisDoc struct { + GenesisTime time.Time `json:"genesis_time"` + ChainID string `json:"chain_id"` + InitialHeight int64 `json:"initial_height"` + ConsensusParams *tmproto.ConsensusParams `json:"consensus_params,omitempty"` + Validators []GenesisValidator `json:"validators,omitempty"` + AppHash tmbytes.HexBytes `json:"app_hash"` + AppState json.RawMessage `json:"app_state,omitempty"` +} +``` + +We use the following function +`makeblock` so that we create a lightblock from the genesis +file in order to do verification based on the data from the genesis +file using the same verification function we use in normal operation. + +#### **[LC-FUNC-MAKEBLOCK.1]** + +```go +func makeblock (genesisDoc GenesisDoc) (lightBlock LightBlock)) +``` + +- Implementation remark + - none +- Expected precondition + - none +- Expected postcondition + - lightBlock.Header.Height = genesisDoc.InitialHeight + - lightBlock.Header.Time = genesisDoc.GenesisTime + - lightBlock.Header.LastBlockID = nil + - lightBlock.Header.LastCommit = nil + - lightBlock.Header.Validators = genesisDoc.Validators + - lightBlock.Header.NextValidators = genesisDoc.Validators + - lightBlock.Header.Data = nil + - lightBlock.Header.AppState = genesisDoc.AppState + - lightBlock.Header.LastResult = nil + - lightBlock.Commit = nil + - lightBlock.Validators = genesisDoc.Validators + - lightBlock.NextValidators = genesisDoc.Validators + - lightBlock.Provider = nil +- Error condition + - none + +---- + +### Configuration Parameters + +#### **[LC-INV-ROOT-AGREED.1]** + +In the Sequential-Supervisor, it is always the case that the primary +and all secondaries agree on lightStore.Latest(). + +### Assumptions + +We have to assume that the initialization data (the lightblock or the +genesis file) are consistent with the blockchain. This is subjective +initialization and it cannot be checked locally. + +### Invariants + +#### **[LC-INV-PEERLIST.1]:** + +The peer list contains a primary and a secondary. + +> If the invariant is violated, the light client does not have enough +> peers to download headers from. As a result, the light client +> needs to terminate in case this invariant is violated. + +## Supervisor + +### Outline + +The supervisor implements the functionality of the lightclient. It is +initialized with a genesis file or with a lightblock the user +trusts. This initialization is subjective, that is, the security of +the lightclient is based on the validity of the input. If the genesis +file or the lightblock deviate from the actual ones on the blockchain, +the lightclient provides no guarantees. + +After initialization, the supervisor awaits an input, that is, the +height of the next lightblock that should be obtained. Then it +downloads, verifies, and cross-checks a lightblock, and if all tests +go through, the light block (and possibly other lightblocks) are added +to the lightstore, which is returned in an output event to the user. + +The following main loop does the interaction with the user (input, +output) and calls the following two functions: + +- `InitLightClient`: it initializes the lightstore either with the + provided lightblock or with the lightblock that corresponds to the + first block generated by the blockchain (by the validators defined + by the genesis file) +- `VerifyAndDetect`: takes as input a lightstore and a height and + returns the updated lightstore. + +#### **[LC-FUNC-SUPERVISOR.1]:** + +```go +func Sequential-Supervisor (initdata LCInitData) (Error) { + + lightStore,result := InitLightClient(initData); + if result != OK { + return result; + } + + loop { + // get the next height + nextHeight := input(); + + lightStore,result := VerifyAndDetect(lightStore, nextHeight); + + if result == OK { + output(LightStore.Get(targetHeight)); + // we only output a trusted lightblock + } + else { + return result + } + // QUESTION: is it OK to generate output event in normal case, + // and terminate with failure in the (light client) attack case? + } +} +``` + +- Implementation remark + - infinite loop unless a light client attack is detected + - In typical implementations (e.g., the one in Rust), + there are mutliple input actions: + `VerifytoLatest`, `LatestTrusted`, and `GetStatus`. The + information can be easily obtained from the lightstore, so that + we do not treat these requests explicitly here but just consider + the request for a block of a given height which requires more + involved computation and communication. +- Expected precondition + - *LCInitData* contains a genesis file or a lightblock. +- Expected postcondition + - if a light client attack is detected: it stops and submits + evidence (in `InitLightClient` or `VerifyAndDetect`) + - otherwise: non. It runs forever. +- Invariant: *lightStore* contains trusted lightblocks only. +- Error condition + - if `InitLightClient` or `VerifyAndDetect` fails (if a attack is + detected, or if [LCV-INV-TP.1] is violated) + +---- + +### Details of the Functions + +#### Initialization + +The light client is based on subjective initialization. It has to +trust the initial data given to it by the user. It cannot do any +detection of attack. So either upon initialization we obtain a +lightblock and just initialize the lightstore with it. Or in case of a +genesis file, we download, verify, and cross-check the first block, to +initialize the lightstore with this first block. The reason is that +we want to maintain [LCV-INV-TP.1] from the beginning. + +> If the lightclient is initialized with a lightblock, one might think +> it may increase trust, when one cross-checks the initial light +> block. However, if a peer provides a conflicting +> lightblock, the question is to distinguish the case of a +> [bogus](https://informal.systems) block (upon which operation should proceed) from a +> [light client attack](https://informal.systems) (upon which operation should stop). In +> case of a bogus block, the lightclient might be forced to do +> backwards verification until the blocks are out of the trusting +> period, to make sure no previous validator set could have generated +> the bogus block, which effectively opens up a DoS attack on the lightclient +> without adding effective robustness. + +#### **[LC-FUNC-INIT.1]:** + +```go +func InitLightClient (initData LCInitData) (LightStore, Error) { + + if LCInitData.LightBlock != nil { + // we trust the provided initial block. + newblock := LCInitData.LightBlock + } + else { + genesisBlock := makeblock(initData.genesisDoc); + + result := NoResult; + while result != ResultSuccess { + current = FetchLightBlock(PeerList.primary(), genesisBlock.Header.Height + 1) + // QUESTION: is the height with "+1" OK? + + if CANNOT_VERIFY = ValidAndVerify(genesisBlock, current) { + Replace_Primary(); + } + else { + result = ResultSuccess + } + } + + // cross-check + auxLS := new LightStore + auxLS.Add(current) + Evidences := AttackDetector(genesisBlock, auxLS) + if Evidences.Empty { + newBlock := current + } + else { + // [LC-SUMBIT-EVIDENCE.1] + submitEvidence(Evidences); + return(nil, ErrorAttack); + } + } + + lightStore := new LightStore; + lightStore.Add(newBlock); + return (lightStore, OK); +} + +``` + +- Implementation remark + - none +- Expected precondition + - *LCInitData* contains either a genesis file of a lightblock + - if genesis it passes `ValidateAndComplete()` see [Tendermint](https://informal.systems) +- Expected postcondition + - *lightStore* initialized with trusted lightblock. It has either been + cross-checked (from genesis) or it has initial trust from the + user. +- Error condition + - if precondition is violated + - empty peerList + +---- + +#### Main verification and detection logic + +#### **[LC-FUNC-MAIN-VERIF-DETECT.1]:** + +```go +func VerifyAndDetect (lightStore LightStore, targetHeight Height) + (LightStore, Result) { + + b1, r1 = lightStore.Get(targetHeight) + if r1 == true { + if b1.State == StateTrusted { + // block already there and trusted + return (lightStore, ResultSuccess) + } + else { + // We have a lightblock in the store, but it has not been + // cross-checked by now. We do that now. + root_of_trust, auxLS := lightstore.TraceTo(b1); + + // Cross-check + Evidences := AttackDetector(root_of_trust, auxLS); + if Evidences.Empty { + // no attack detected, we trust the new lightblock + lightStore.Update(auxLS.Latest(), + StateTrusted, + verfiedLS.Latest().verification-root); + return (lightStore, OK); + } + else { + // there is an attack, we exit + submitEvidence(Evidences); + return(lightStore, ErrorAttack); + } + } + } + + // get the lightblock with maximum height smaller than targetHeight + // would typically be the heighest, if we always move forward + root_of_trust, r2 = lightStore.LatestPrevious(targetHeight); + + if r2 = false { + // there is no lightblock from which we can do forward + // (skipping) verification. Thus we have to go backwards. + // No cross-check needed. We trust hashes. Therefore, we + // directly return the result + return Backwards(primary, lightStore.Lowest(), targetHeight) + } + else { + // Forward verification + detection + result := NoResult; + while result != ResultSuccess { + verifiedLS,result := VerifyToTarget(primary, + root_of_trust, + nextHeight); + if result == ResultFailure { + // pick new primary (promote a secondary to primary) + Replace_Primary(root_of_trust); + } + else if result == ResultExpired { + return (lightStore, result) + } + } + + // Cross-check + Evidences := AttackDetector(root_of_trust, verifiedLS); + if Evidences.Empty { + // no attack detected, we trust the new lightblock + verifiedLS.Update(verfiedLS.Latest(), + StateTrusted, + verfiedLS.Latest().verification-root); + lightStore.store_chain(verifidLS); + return (lightStore, OK); + } + else { + // there is an attack, we exit + return(lightStore, ErrorAttack); + } + } +} +``` + +- Implementation remark + - none +- Expected precondition + - none +- Expected postcondition + - lightblock of height *targetHeight* (and possibly additional blocks) added to *lightStore* +- Error condition + - an attack is detected + - [LC-DATA-PEERLIST-INV.1] is violated + +---- \ No newline at end of file diff --git a/spec/light-client/supervisor/supervisor_001_draft.tla b/spec/light-client/supervisor/supervisor_001_draft.tla new file mode 100644 index 00000000000..949a7c0200c --- /dev/null +++ b/spec/light-client/supervisor/supervisor_001_draft.tla @@ -0,0 +1,71 @@ +------------------------- MODULE supervisor_001_draft ------------------------ +(* +This is the beginning of a spec that will eventually use verification and detector API +*) + +EXTENDS Integers, FiniteSets + +VARIABLES + state, + output + +vars == <> + +CONSTANT + INITDATA + +Init == + /\ state = "Init" + /\ output = "none" + +NextInit == + /\ state = "Init" + /\ \/ state' = "EnterLoop" + \/ state' = "FailedToInitialize" + /\ UNCHANGED output + +NextVerifyToTarget == + /\ state = "EnterLoop" + /\ \/ state' = "EnterLoop" \* replace primary + \/ state' = "EnterDetect" + \/ state' = "ExhaustedPeersPrimary" + /\ UNCHANGED output + +NextAttackDetector == + /\ state = "EnterDetect" + /\ \/ state' = "NoEvidence" + \/ state' = "EvidenceFound" + \/ state' = "ExhaustedPeersSecondaries" + /\ UNCHANGED output + +NextVerifyAndDetect == + \/ NextVerifyToTarget + \/ NextAttackDetector + +NextOutput == + /\ state = "NoEvidence" + /\ state' = "EnterLoop" + /\ output' = "data" \* to generate a trace + +NextTerminated == + /\ \/ state = "FailedToInitialize" + \/ state = "ExhaustedPeersPrimary" + \/ state = "EvidenceFound" + \/ state = "ExhaustedPeersSecondaries" + /\ UNCHANGED vars + +Next == + \/ NextInit + \/ NextVerifyAndDetect + \/ NextOutput + \/ NextTerminated + +InvEnoughPeers == + /\ state /= "ExhaustedPeersPrimary" + /\ state /= "ExhaustedPeersSecondaries" + + +============================================================================= +\* Modification History +\* Last modified Sun Oct 18 11:48:45 CEST 2020 by widder +\* Created Sun Oct 18 11:18:53 CEST 2020 by widder diff --git a/spec/light-client/supervisor/supervisor_002_draft.md b/spec/light-client/supervisor/supervisor_002_draft.md new file mode 100644 index 00000000000..4300b8044dd --- /dev/null +++ b/spec/light-client/supervisor/supervisor_002_draft.md @@ -0,0 +1,131 @@ +# Draft of Light Client Supervisor for discussion + +## Modification to the initialization + +The lightclient is initialized with LCInitData + +### **[LC-DATA-INIT.2]** + +```go +type LCInitData struct { + TrustedBlock LightBlock + Genesis GenesisDoc + TrustedHash []byte + TrustedHeight int64 +} +``` + +where only one of the components must be provided. `GenesisDoc` is +defined in the [Tendermint +Types](https://github.com/tendermint/tendermint/blob/master/types/genesis.go). + + +### Initialization + +The light client is based on subjective initialization. It has to +trust the initial data given to it by the user. It cannot perform any +detection of an attack yet instead requires an initial point of trust. +There are three forms of initial data which are used to obtain the +first trusted block: + +- A trusted block from a prior initialization +- A trusted height and hash +- A genesis file + +The golang light client implementation checks this initial data in that +order; first attempting to find a trusted block from the trusted store, +then acquiring a light block from the primary at the trusted height and matching +the hash, or finally checking for a genesis file to verify the initial header. + +The light client doesn't need to check if the trusted block is within the +trusted period because it already trusts it, however, if the light block is +outside the trust period, there is a higher chance the light client won't be +able to verify anything. + +Cross-checking this trusted block with providers upon initialization is helpful +for ensuring that the node is responsive and correctly configured but does not +increase trust since proving a conflicting block is a +[light client attack](https://github.com/tendermint/spec/blob/master/spec/light-client/detection/detection_003_reviewed.md#tmbc-lc-attack1) +and not just a [bogus](https://github.com/tendermint/spec/blob/master/spec/light-client/detection/detection_003_reviewed.md#tmbc-bogus1) block could result in +performing backwards verification beyond the trusted period, thus a fruitless +endeavour. + +However, with the notion of it's better to fail earlier than later, the golang +light client implementation will perform a consistency check on all providers +and will error if one returns a different header, allowing the user +the opportunity to reinitialize. + +#### **[LC-FUNC-INIT.2]:** + +```go +func InitLightClient(initData LCInitData) (LightStore, Error) { + var initialBlock LightBlock + + switch { + case LCInitData.TrustedBlock != nil: + // we trust the block from a prior initialization + initialBlock = LCInitData.TrustedBlock + + case LCInitData.TrustedHash != nil: + untrustedBlock := FetchLightBlock(PeerList.Primary(), LCInitData.TrustedHeight) + + + // verify that the hashes match + if untrustedBlock.Hash() != LCInitData.TrustedHash { + return nil, Error("Primary returned block with different hash") + } + // after checking the hash we now trust the block + initialBlock = untrustedBlock + } + case LCInitData.Genesis != nil: + untrustedBlock := FetchLightBlock(PeerList.Primary(), LCInitData.Genesis.InitialHeight) + + // verify that 2/3+ of the validator set signed the untrustedBlock + if err := VerifyCommitFull(untrustedBlock.Commit, LCInitData.Genesis.Validators); err != nil { + return nil, err + } + + // we can now trust the block + initialBlock = untrustedBlock + default: + return nil, Error("No initial data was provided") + + // This is done in the golang version but is optional and not strictly part of the protocol + if err := CrossCheck(initialBlock, PeerList.Witnesses()); err != nil { + return nil, err + } + + // initialize light store + lightStore := new LightStore; + lightStore.Add(newBlock); + return (lightStore, OK); +} + +func CrossCheck(lb LightBlock, witnesses []Provider) error { + for _, witness := range witnesses { + witnessBlock := FetchLightBlock(witness, lb.Height) + + if witnessBlock.Hash() != lb.Hash() { + return Error("Witness has different block") + } + } + return OK +} + +``` + +- Implementation remark + - none +- Expected precondition + - *LCInitData* contains either a genesis file of a lightblock + - if genesis it passes `ValidateAndComplete()` see [Tendermint](https://informal.systems) +- Expected postcondition + - *lightStore* initialized with trusted lightblock. It has either been + cross-checked (from genesis) or it has initial trust from the + user. +- Error condition + - if precondition is violated + - empty peerList + +---- + diff --git a/spec/light-client/verification/001bmc-apalache.csv b/spec/light-client/verification/001bmc-apalache.csv new file mode 100644 index 00000000000..8d5ad8ea3af --- /dev/null +++ b/spec/light-client/verification/001bmc-apalache.csv @@ -0,0 +1,49 @@ +no,filename,tool,timeout,init,inv,next,args +1,MC4_3_correct.tla,apalache,1h,,PositiveBeforeTrustedHeaderExpires,,--length=30 +2,MC4_3_correct.tla,apalache,1h,,CorrectnessInv,,--length=30 +3,MC4_3_correct.tla,apalache,1h,,PrecisionInv,,--length=30 +4,MC4_3_correct.tla,apalache,1h,,SuccessOnCorrectPrimaryAndChainOfTrust,,--length=30 +5,MC4_3_correct.tla,apalache,1h,,NoFailedBlocksOnSuccessInv,,--length=30 +6,MC4_3_correct.tla,apalache,1h,,StoredHeadersAreVerifiedOrNotTrustedInv,,--length=30 +7,MC4_3_correct.tla,apalache,1h,,CorrectPrimaryAndTimeliness,,--length=30 +8,MC4_3_correct.tla,apalache,1h,,Complexity,,--length=30 +9,MC4_3_faulty.tla,apalache,1h,,PositiveBeforeTrustedHeaderExpires,,--length=30 +10,MC4_3_faulty.tla,apalache,1h,,CorrectnessInv,,--length=30 +11,MC4_3_faulty.tla,apalache,1h,,PrecisionInv,,--length=30 +12,MC4_3_faulty.tla,apalache,1h,,SuccessOnCorrectPrimaryAndChainOfTrust,,--length=30 +13,MC4_3_faulty.tla,apalache,1h,,NoFailedBlocksOnSuccessInv,,--length=30 +14,MC4_3_faulty.tla,apalache,1h,,StoredHeadersAreVerifiedOrNotTrustedInv,,--length=30 +15,MC4_3_faulty.tla,apalache,1h,,CorrectPrimaryAndTimeliness,,--length=30 +16,MC4_3_faulty.tla,apalache,1h,,Complexity,,--length=30 +17,MC5_5_correct.tla,apalache,1h,,PositiveBeforeTrustedHeaderExpires,,--length=30 +18,MC5_5_correct.tla,apalache,1h,,CorrectnessInv,,--length=30 +19,MC5_5_correct.tla,apalache,1h,,PrecisionInv,,--length=30 +20,MC5_5_correct.tla,apalache,1h,,SuccessOnCorrectPrimaryAndChainOfTrust,,--length=30 +21,MC5_5_correct.tla,apalache,1h,,NoFailedBlocksOnSuccessInv,,--length=30 +22,MC5_5_correct.tla,apalache,1h,,StoredHeadersAreVerifiedOrNotTrustedInv,,--length=30 +23,MC5_5_correct.tla,apalache,1h,,CorrectPrimaryAndTimeliness,,--length=30 +24,MC5_5_correct.tla,apalache,1h,,Complexity,,--length=30 +25,MC5_5_faulty.tla,apalache,1h,,PositiveBeforeTrustedHeaderExpires,,--length=30 +26,MC5_5_faulty.tla,apalache,1h,,CorrectnessInv,,--length=30 +27,MC5_5_faulty.tla,apalache,1h,,PrecisionInv,,--length=30 +28,MC5_5_faulty.tla,apalache,1h,,SuccessOnCorrectPrimaryAndChainOfTrust,,--length=30 +29,MC5_5_faulty.tla,apalache,1h,,NoFailedBlocksOnSuccessInv,,--length=30 +30,MC5_5_faulty.tla,apalache,1h,,StoredHeadersAreVerifiedOrNotTrustedInv,,--length=30 +31,MC5_5_faulty.tla,apalache,1h,,CorrectPrimaryAndTimeliness,,--length=30 +32,MC5_5_faulty.tla,apalache,1h,,Complexity,,--length=30 +33,MC7_5_faulty.tla,apalache,10h,,PositiveBeforeTrustedHeaderExpires,,--length=30 +34,MC7_5_faulty.tla,apalache,10h,,CorrectnessInv,,--length=30 +35,MC7_5_faulty.tla,apalache,10h,,PrecisionInv,,--length=30 +36,MC7_5_faulty.tla,apalache,10h,,SuccessOnCorrectPrimaryAndChainOfTrust,,--length=30 +37,MC7_5_faulty.tla,apalache,10h,,NoFailedBlocksOnSuccessInv,,--length=30 +38,MC7_5_faulty.tla,apalache,10h,,StoredHeadersAreVerifiedOrNotTrustedInv,,--length=30 +39,MC7_5_faulty.tla,apalache,10h,,CorrectPrimaryAndTimeliness,,--length=30 +40,MC7_5_faulty.tla,apalache,10h,,Complexity,,--length=30 +41,MC4_7_faulty.tla,apalache,10h,,PositiveBeforeTrustedHeaderExpires,,--length=30 +42,MC4_7_faulty.tla,apalache,10h,,CorrectnessInv,,--length=30 +43,MC4_7_faulty.tla,apalache,10h,,PrecisionInv,,--length=30 +44,MC4_7_faulty.tla,apalache,10h,,SuccessOnCorrectPrimaryAndChainOfTrust,,--length=30 +45,MC4_7_faulty.tla,apalache,10h,,NoFailedBlocksOnSuccessInv,,--length=30 +46,MC4_7_faulty.tla,apalache,10h,,StoredHeadersAreVerifiedOrNotTrustedInv,,--length=30 +47,MC4_7_faulty.tla,apalache,10h,,CorrectPrimaryAndTimeliness,,--length=30 +48,MC4_7_faulty.tla,apalache,10h,,Complexity,,--length=30 diff --git a/spec/light-client/verification/002bmc-apalache-ok.csv b/spec/light-client/verification/002bmc-apalache-ok.csv new file mode 100644 index 00000000000..eb26aa89e57 --- /dev/null +++ b/spec/light-client/verification/002bmc-apalache-ok.csv @@ -0,0 +1,55 @@ +no;filename;tool;timeout;init;inv;next;args +1;MC4_3_correct.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=5 +2;MC4_3_correct.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=5 +3;MC4_3_correct.tla;apalache;1h;;CorrectnessInv;;--length=5 +4;MC4_3_correct.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=5 +5;MC4_3_correct.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=5 +6;MC4_3_correct.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=5 +7;MC4_3_correct.tla;apalache;1h;;Complexity;;--length=5 +8;MC4_3_correct.tla;apalache;1h;;ApiPostInv;;--length=5 +9;MC4_4_correct.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=7 +10;MC4_4_correct.tla;apalache;1h;;CorrectnessInv;;--length=7 +11;MC4_4_correct.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=7 +12;MC4_4_correct.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=7 +13;MC4_4_correct.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=7 +14;MC4_4_correct.tla;apalache;1h;;Complexity;;--length=7 +15;MC4_4_correct.tla;apalache;1h;;ApiPostInv;;--length=7 +16;MC4_5_correct.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=11 +17;MC4_5_correct.tla;apalache;1h;;CorrectnessInv;;--length=11 +18;MC4_5_correct.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=11 +19;MC4_5_correct.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=11 +20;MC4_5_correct.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=11 +21;MC4_5_correct.tla;apalache;1h;;Complexity;;--length=11 +22;MC4_5_correct.tla;apalache;1h;;ApiPostInv;;--length=11 +23;MC5_5_correct.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=11 +24;MC5_5_correct.tla;apalache;1h;;CorrectnessInv;;--length=11 +25;MC5_5_correct.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=11 +26;MC5_5_correct.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=11 +27;MC5_5_correct.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=11 +28;MC5_5_correct.tla;apalache;1h;;Complexity;;--length=11 +29;MC5_5_correct.tla;apalache;1h;;ApiPostInv;;--length=11 +30;MC4_3_faulty.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=5 +31;MC4_3_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=5 +32;MC4_3_faulty.tla;apalache;1h;;CorrectnessInv;;--length=5 +33;MC4_3_faulty.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=5 +34;MC4_3_faulty.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=5 +35;MC4_3_faulty.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=5 +36;MC4_3_faulty.tla;apalache;1h;;Complexity;;--length=5 +37;MC4_3_faulty.tla;apalache;1h;;ApiPostInv;;--length=5 +38;MC4_4_faulty.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=7 +39;MC4_4_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=7 +40;MC4_4_faulty.tla;apalache;1h;;CorrectnessInv;;--length=7 +41;MC4_4_faulty.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=7 +42;MC4_4_faulty.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=7 +43;MC4_4_faulty.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=7 +44;MC4_4_faulty.tla;apalache;1h;;Complexity;;--length=7 +45;MC4_4_faulty.tla;apalache;1h;;ApiPostInv;;--length=7 +46;MC4_5_faulty.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=11 +47;MC4_5_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=11 +48;MC4_5_faulty.tla;apalache;1h;;CorrectnessInv;;--length=11 +49;MC4_5_faulty.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=11 +50;MC4_5_faulty.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=11 +51;MC4_5_faulty.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=11 +52;MC4_5_faulty.tla;apalache;1h;;Complexity;;--length=11 +53;MC4_5_faulty.tla;apalache;1h;;ApiPostInv;;--length=11 + diff --git a/spec/light-client/verification/003bmc-apalache-error.csv b/spec/light-client/verification/003bmc-apalache-error.csv new file mode 100644 index 00000000000..ad5ef96548a --- /dev/null +++ b/spec/light-client/verification/003bmc-apalache-error.csv @@ -0,0 +1,45 @@ +no;filename;tool;timeout;init;inv;next;args +1;MC4_3_correct.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=5 +2;MC4_3_correct.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=5 +3;MC4_3_correct.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=5 +4;MC4_3_correct.tla;apalache;1h;;PrecisionInv;;--length=5 +5;MC4_3_correct.tla;apalache;1h;;PrecisionBuggyInv;;--length=5 +6;MC4_3_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=5 +7;MC4_3_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=5 +8;MC4_4_correct.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=7 +9;MC4_4_correct.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=7 +10;MC4_4_correct.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=7 +11;MC4_4_correct.tla;apalache;1h;;PrecisionInv;;--length=7 +12;MC4_4_correct.tla;apalache;1h;;PrecisionBuggyInv;;--length=7 +13;MC4_4_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=7 +14;MC4_4_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=7 +15;MC4_5_correct.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=11 +16;MC4_5_correct.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=11 +17;MC4_5_correct.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=11 +18;MC4_5_correct.tla;apalache;1h;;PrecisionInv;;--length=11 +19;MC4_5_correct.tla;apalache;1h;;PrecisionBuggyInv;;--length=11 +20;MC4_5_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=11 +21;MC4_5_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=11 +22;MC4_5_correct.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=11 +23;MC4_3_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=5 +24;MC4_3_faulty.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=5 +25;MC4_3_faulty.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=5 +26;MC4_3_faulty.tla;apalache;1h;;PrecisionInv;;--length=5 +27;MC4_3_faulty.tla;apalache;1h;;PrecisionBuggyInv;;--length=5 +28;MC4_3_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=5 +29;MC4_3_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=5 +30;MC4_4_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=7 +31;MC4_4_faulty.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=7 +32;MC4_4_faulty.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=7 +33;MC4_4_faulty.tla;apalache;1h;;PrecisionInv;;--length=7 +34;MC4_4_faulty.tla;apalache;1h;;PrecisionBuggyInv;;--length=7 +35;MC4_4_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=7 +36;MC4_4_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=7 +37;MC4_5_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=11 +38;MC4_5_faulty.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=11 +39;MC4_5_faulty.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=11 +40;MC4_5_faulty.tla;apalache;1h;;PrecisionInv;;--length=11 +41;MC4_5_faulty.tla;apalache;1h;;PrecisionBuggyInv;;--length=11 +42;MC4_5_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=11 +43;MC4_5_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=11 +44;MC4_5_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=11 diff --git a/spec/light-client/verification/004bmc-apalache-ok.csv b/spec/light-client/verification/004bmc-apalache-ok.csv new file mode 100644 index 00000000000..bf4f53ea2a3 --- /dev/null +++ b/spec/light-client/verification/004bmc-apalache-ok.csv @@ -0,0 +1,10 @@ +no;filename;tool;timeout;init;inv;next;args +1;LCD_MC3_3_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 +2;LCD_MC3_3_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 +3;LCD_MC3_3_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 +4;LCD_MC3_4_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 +5;LCD_MC3_4_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 +6;LCD_MC3_4_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 +7;LCD_MC4_4_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 +8;LCD_MC4_4_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 +9;LCD_MC4_4_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 diff --git a/spec/light-client/verification/005bmc-apalache-error.csv b/spec/light-client/verification/005bmc-apalache-error.csv new file mode 100644 index 00000000000..1b9dd05ca9b --- /dev/null +++ b/spec/light-client/verification/005bmc-apalache-error.csv @@ -0,0 +1,4 @@ +no;filename;tool;timeout;init;inv;next;args +1;LCD_MC3_3_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 +2;LCD_MC3_4_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 +3;LCD_MC4_4_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 diff --git a/spec/light-client/verification/Blockchain_002_draft.tla b/spec/light-client/verification/Blockchain_002_draft.tla new file mode 100644 index 00000000000..f2ca5aba5a7 --- /dev/null +++ b/spec/light-client/verification/Blockchain_002_draft.tla @@ -0,0 +1,171 @@ +------------------------ MODULE Blockchain_002_draft ----------------------------- +(* + This is a high-level specification of Tendermint blockchain + that is designed specifically for the light client. + Validators have the voting power of one. If you like to model various + voting powers, introduce multiple copies of the same validator + (do not forget to give them unique names though). + *) +EXTENDS Integers, FiniteSets + +Min(a, b) == IF a < b THEN a ELSE b + +CONSTANT + AllNodes, + (* a set of all nodes that can act as validators (correct and faulty) *) + ULTIMATE_HEIGHT, + (* a maximal height that can be ever reached (modelling artifact) *) + TRUSTING_PERIOD + (* the period within which the validators are trusted *) + +Heights == 1..ULTIMATE_HEIGHT (* possible heights *) + +(* A commit is just a set of nodes who have committed the block *) +Commits == SUBSET AllNodes + +(* The set of all block headers that can be on the blockchain. + This is a simplified version of the Block data structure in the actual implementation. *) +BlockHeaders == [ + height: Heights, + \* the block height + time: Int, + \* the block timestamp in some integer units + lastCommit: Commits, + \* the nodes who have voted on the previous block, the set itself instead of a hash + (* in the implementation, only the hashes of V and NextV are stored in a block, + as V and NextV are stored in the application state *) + VS: SUBSET AllNodes, + \* the validators of this bloc. We store the validators instead of the hash. + NextVS: SUBSET AllNodes + \* the validators of the next block. We store the next validators instead of the hash. +] + +(* A signed header is just a header together with a set of commits *) +LightBlocks == [header: BlockHeaders, Commits: Commits] + +VARIABLES + now, + (* the current global time in integer units *) + blockchain, + (* A sequence of BlockHeaders, which gives us a bird view of the blockchain. *) + Faulty + (* A set of faulty nodes, which can act as validators. We assume that the set + of faulty processes is non-decreasing. If a process has recovered, it should + connect using a different id. *) + +(* all variables, to be used with UNCHANGED *) +vars == <> + +(* The set of all correct nodes in a state *) +Corr == AllNodes \ Faulty + +(* APALACHE annotations *) +a <: b == a \* type annotation + +NT == STRING +NodeSet(S) == S <: {NT} +EmptyNodeSet == NodeSet({}) + +BT == [height |-> Int, time |-> Int, lastCommit |-> {NT}, VS |-> {NT}, NextVS |-> {NT}] + +LBT == [header |-> BT, Commits |-> {NT}] +(* end of APALACHE annotations *) + +(****************************** BLOCKCHAIN ************************************) + +(* the header is still within the trusting period *) +InTrustingPeriod(header) == + now < header.time + TRUSTING_PERIOD + +(* + Given a function pVotingPower \in D -> Powers for some D \subseteq AllNodes + and pNodes \subseteq D, test whether the set pNodes \subseteq AllNodes has + more than 2/3 of voting power among the nodes in D. + *) +TwoThirds(pVS, pNodes) == + LET TP == Cardinality(pVS) + SP == Cardinality(pVS \intersect pNodes) + IN + 3 * SP > 2 * TP \* when thinking in real numbers, not integers: SP > 2.0 / 3.0 * TP + +(* + Given a set of FaultyNodes, test whether the voting power of the correct nodes in D + is more than 2/3 of the voting power of the faulty nodes in D. + *) +IsCorrectPower(pFaultyNodes, pVS) == + LET FN == pFaultyNodes \intersect pVS \* faulty nodes in pNodes + CN == pVS \ pFaultyNodes \* correct nodes in pNodes + CP == Cardinality(CN) \* power of the correct nodes + FP == Cardinality(FN) \* power of the faulty nodes + IN + \* CP + FP = TP is the total voting power, so we write CP > 2.0 / 3 * TP as follows: + CP > 2 * FP \* Note: when FP = 0, this implies CP > 0. + +(* This is what we believe is the assumption about failures in Tendermint *) +FaultAssumption(pFaultyNodes, pNow, pBlockchain) == + \A h \in Heights: + pBlockchain[h].time + TRUSTING_PERIOD > pNow => + IsCorrectPower(pFaultyNodes, pBlockchain[h].NextVS) + +(* Can a block be produced by a correct peer, or an authenticated Byzantine peer *) +IsLightBlockAllowedByDigitalSignatures(ht, block) == + \/ block.header = blockchain[ht] \* signed by correct and faulty (maybe) + \/ block.Commits \subseteq Faulty /\ block.header.height = ht /\ block.header.time >= 0 \* signed only by faulty + +(* + Initialize the blockchain to the ultimate height right in the initial states. + We pick the faulty validators statically, but that should not affect the light client. + *) +InitToHeight == + /\ Faulty \in SUBSET AllNodes \* some nodes may fail + \* pick the validator sets and last commits + /\ \E vs, lastCommit \in [Heights -> SUBSET AllNodes]: + \E timestamp \in [Heights -> Int]: + \* now is at least as early as the timestamp in the last block + /\ \E tm \in Int: now = tm /\ tm >= timestamp[ULTIMATE_HEIGHT] + \* the genesis starts on day 1 + /\ timestamp[1] = 1 + /\ vs[1] = AllNodes + /\ lastCommit[1] = EmptyNodeSet + /\ \A h \in Heights \ {1}: + /\ lastCommit[h] \subseteq vs[h - 1] \* the non-validators cannot commit + /\ TwoThirds(vs[h - 1], lastCommit[h]) \* the commit has >2/3 of validator votes + /\ IsCorrectPower(Faulty, vs[h]) \* the correct validators have >2/3 of power + /\ timestamp[h] > timestamp[h - 1] \* the time grows monotonically + /\ timestamp[h] < timestamp[h - 1] + TRUSTING_PERIOD \* but not too fast + \* form the block chain out of validator sets and commits (this makes apalache faster) + /\ blockchain = [h \in Heights |-> + [height |-> h, + time |-> timestamp[h], + VS |-> vs[h], + NextVS |-> IF h < ULTIMATE_HEIGHT THEN vs[h + 1] ELSE AllNodes, + lastCommit |-> lastCommit[h]] + ] \****** + + +(* is the blockchain in the faulty zone where the Tendermint security model does not apply *) +InFaultyZone == + ~FaultAssumption(Faulty, now, blockchain) + +(********************* BLOCKCHAIN ACTIONS ********************************) +(* + Advance the clock by zero or more time units. + *) +AdvanceTime == + \E tm \in Int: tm >= now /\ now' = tm + /\ UNCHANGED <> + +(* + One more process fails. As a result, the blockchain may move into the faulty zone. + The light client is not using this action, as the faults are picked in the initial state. + However, this action may be useful when reasoning about fork detection. + *) +OneMoreFault == + /\ \E n \in AllNodes \ Faulty: + /\ Faulty' = Faulty \cup {n} + /\ Faulty' /= AllNodes \* at least process remains non-faulty + /\ UNCHANGED <> +============================================================================= +\* Modification History +\* Last modified Wed Jun 10 14:10:54 CEST 2020 by igor +\* Created Fri Oct 11 15:45:11 CEST 2019 by igor diff --git a/spec/light-client/verification/Blockchain_003_draft.tla b/spec/light-client/verification/Blockchain_003_draft.tla new file mode 100644 index 00000000000..2b37c1b1818 --- /dev/null +++ b/spec/light-client/verification/Blockchain_003_draft.tla @@ -0,0 +1,164 @@ +------------------------ MODULE Blockchain_003_draft ----------------------------- +(* + This is a high-level specification of Tendermint blockchain + that is designed specifically for the light client. + Validators have the voting power of one. If you like to model various + voting powers, introduce multiple copies of the same validator + (do not forget to give them unique names though). + *) +EXTENDS Integers, FiniteSets + +Min(a, b) == IF a < b THEN a ELSE b + +CONSTANT + AllNodes, + (* a set of all nodes that can act as validators (correct and faulty) *) + ULTIMATE_HEIGHT, + (* a maximal height that can be ever reached (modelling artifact) *) + TRUSTING_PERIOD + (* the period within which the validators are trusted *) + +Heights == 1..ULTIMATE_HEIGHT (* possible heights *) + +(* A commit is just a set of nodes who have committed the block *) +Commits == SUBSET AllNodes + +(* The set of all block headers that can be on the blockchain. + This is a simplified version of the Block data structure in the actual implementation. *) +BlockHeaders == [ + height: Heights, + \* the block height + time: Int, + \* the block timestamp in some integer units + lastCommit: Commits, + \* the nodes who have voted on the previous block, the set itself instead of a hash + (* in the implementation, only the hashes of V and NextV are stored in a block, + as V and NextV are stored in the application state *) + VS: SUBSET AllNodes, + \* the validators of this bloc. We store the validators instead of the hash. + NextVS: SUBSET AllNodes + \* the validators of the next block. We store the next validators instead of the hash. +] + +(* A signed header is just a header together with a set of commits *) +LightBlocks == [header: BlockHeaders, Commits: Commits] + +VARIABLES + refClock, + (* the current global time in integer units as perceived by the reference chain *) + blockchain, + (* A sequence of BlockHeaders, which gives us a bird view of the blockchain. *) + Faulty + (* A set of faulty nodes, which can act as validators. We assume that the set + of faulty processes is non-decreasing. If a process has recovered, it should + connect using a different id. *) + +(* all variables, to be used with UNCHANGED *) +vars == <> + +(* The set of all correct nodes in a state *) +Corr == AllNodes \ Faulty + +(* APALACHE annotations *) +a <: b == a \* type annotation + +NT == STRING +NodeSet(S) == S <: {NT} +EmptyNodeSet == NodeSet({}) + +BT == [height |-> Int, time |-> Int, lastCommit |-> {NT}, VS |-> {NT}, NextVS |-> {NT}] + +LBT == [header |-> BT, Commits |-> {NT}] +(* end of APALACHE annotations *) + +(****************************** BLOCKCHAIN ************************************) + +(* the header is still within the trusting period *) +InTrustingPeriod(header) == + refClock < header.time + TRUSTING_PERIOD + +(* + Given a function pVotingPower \in D -> Powers for some D \subseteq AllNodes + and pNodes \subseteq D, test whether the set pNodes \subseteq AllNodes has + more than 2/3 of voting power among the nodes in D. + *) +TwoThirds(pVS, pNodes) == + LET TP == Cardinality(pVS) + SP == Cardinality(pVS \intersect pNodes) + IN + 3 * SP > 2 * TP \* when thinking in real numbers, not integers: SP > 2.0 / 3.0 * TP + +(* + Given a set of FaultyNodes, test whether the voting power of the correct nodes in D + is more than 2/3 of the voting power of the faulty nodes in D. + + Parameters: + - pFaultyNodes is a set of nodes that are considered faulty + - pVS is a set of all validators, maybe including Faulty, intersecting with it, etc. + - pMaxFaultRatio is a pair <> that limits the ratio a / b of the faulty + validators from above (exclusive) + *) +FaultyValidatorsFewerThan(pFaultyNodes, pVS, maxRatio) == + LET FN == pFaultyNodes \intersect pVS \* faulty nodes in pNodes + CN == pVS \ pFaultyNodes \* correct nodes in pNodes + CP == Cardinality(CN) \* power of the correct nodes + FP == Cardinality(FN) \* power of the faulty nodes + IN + \* CP + FP = TP is the total voting power + LET TP == CP + FP IN + FP * maxRatio[2] < TP * maxRatio[1] + +(* Can a block be produced by a correct peer, or an authenticated Byzantine peer *) +IsLightBlockAllowedByDigitalSignatures(ht, block) == + \/ block.header = blockchain[ht] \* signed by correct and faulty (maybe) + \/ /\ block.Commits \subseteq Faulty + /\ block.header.height = ht + /\ block.header.time >= 0 \* signed only by faulty + +(* + Initialize the blockchain to the ultimate height right in the initial states. + We pick the faulty validators statically, but that should not affect the light client. + + Parameters: + - pMaxFaultyRatioExclusive is a pair <> that bound the number of + faulty validators in each block by the ratio a / b (exclusive) + *) +InitToHeight(pMaxFaultyRatioExclusive) == + /\ Faulty \in SUBSET AllNodes \* some nodes may fail + \* pick the validator sets and last commits + /\ \E vs, lastCommit \in [Heights -> SUBSET AllNodes]: + \E timestamp \in [Heights -> Int]: + \* refClock is at least as early as the timestamp in the last block + /\ \E tm \in Int: refClock = tm /\ tm >= timestamp[ULTIMATE_HEIGHT] + \* the genesis starts on day 1 + /\ timestamp[1] = 1 + /\ vs[1] = AllNodes + /\ lastCommit[1] = EmptyNodeSet + /\ \A h \in Heights \ {1}: + /\ lastCommit[h] \subseteq vs[h - 1] \* the non-validators cannot commit + /\ TwoThirds(vs[h - 1], lastCommit[h]) \* the commit has >2/3 of validator votes + \* the faulty validators have the power below the threshold + /\ FaultyValidatorsFewerThan(Faulty, vs[h], pMaxFaultyRatioExclusive) + /\ timestamp[h] > timestamp[h - 1] \* the time grows monotonically + /\ timestamp[h] < timestamp[h - 1] + TRUSTING_PERIOD \* but not too fast + \* form the block chain out of validator sets and commits (this makes apalache faster) + /\ blockchain = [h \in Heights |-> + [height |-> h, + time |-> timestamp[h], + VS |-> vs[h], + NextVS |-> IF h < ULTIMATE_HEIGHT THEN vs[h + 1] ELSE AllNodes, + lastCommit |-> lastCommit[h]] + ] \****** + +(********************* BLOCKCHAIN ACTIONS ********************************) +(* + Advance the clock by zero or more time units. + *) +AdvanceTime == + /\ \E tm \in Int: tm >= refClock /\ refClock' = tm + /\ UNCHANGED <> + +============================================================================= +\* Modification History +\* Last modified Wed Jun 10 14:10:54 CEST 2020 by igor +\* Created Fri Oct 11 15:45:11 CEST 2019 by igor diff --git a/spec/light-client/verification/Blockchain_A_1.tla b/spec/light-client/verification/Blockchain_A_1.tla new file mode 100644 index 00000000000..70f59bf9750 --- /dev/null +++ b/spec/light-client/verification/Blockchain_A_1.tla @@ -0,0 +1,171 @@ +------------------------ MODULE Blockchain_A_1 ----------------------------- +(* + This is a high-level specification of Tendermint blockchain + that is designed specifically for the light client. + Validators have the voting power of one. If you like to model various + voting powers, introduce multiple copies of the same validator + (do not forget to give them unique names though). + *) +EXTENDS Integers, FiniteSets + +Min(a, b) == IF a < b THEN a ELSE b + +CONSTANT + AllNodes, + (* a set of all nodes that can act as validators (correct and faulty) *) + ULTIMATE_HEIGHT, + (* a maximal height that can be ever reached (modelling artifact) *) + TRUSTING_PERIOD + (* the period within which the validators are trusted *) + +Heights == 1..ULTIMATE_HEIGHT (* possible heights *) + +(* A commit is just a set of nodes who have committed the block *) +Commits == SUBSET AllNodes + +(* The set of all block headers that can be on the blockchain. + This is a simplified version of the Block data structure in the actual implementation. *) +BlockHeaders == [ + height: Heights, + \* the block height + time: Int, + \* the block timestamp in some integer units + lastCommit: Commits, + \* the nodes who have voted on the previous block, the set itself instead of a hash + (* in the implementation, only the hashes of V and NextV are stored in a block, + as V and NextV are stored in the application state *) + VS: SUBSET AllNodes, + \* the validators of this bloc. We store the validators instead of the hash. + NextVS: SUBSET AllNodes + \* the validators of the next block. We store the next validators instead of the hash. +] + +(* A signed header is just a header together with a set of commits *) +LightBlocks == [header: BlockHeaders, Commits: Commits] + +VARIABLES + now, + (* the current global time in integer units *) + blockchain, + (* A sequence of BlockHeaders, which gives us a bird view of the blockchain. *) + Faulty + (* A set of faulty nodes, which can act as validators. We assume that the set + of faulty processes is non-decreasing. If a process has recovered, it should + connect using a different id. *) + +(* all variables, to be used with UNCHANGED *) +vars == <> + +(* The set of all correct nodes in a state *) +Corr == AllNodes \ Faulty + +(* APALACHE annotations *) +a <: b == a \* type annotation + +NT == STRING +NodeSet(S) == S <: {NT} +EmptyNodeSet == NodeSet({}) + +BT == [height |-> Int, time |-> Int, lastCommit |-> {NT}, VS |-> {NT}, NextVS |-> {NT}] + +LBT == [header |-> BT, Commits |-> {NT}] +(* end of APALACHE annotations *) + +(****************************** BLOCKCHAIN ************************************) + +(* the header is still within the trusting period *) +InTrustingPeriod(header) == + now <= header.time + TRUSTING_PERIOD + +(* + Given a function pVotingPower \in D -> Powers for some D \subseteq AllNodes + and pNodes \subseteq D, test whether the set pNodes \subseteq AllNodes has + more than 2/3 of voting power among the nodes in D. + *) +TwoThirds(pVS, pNodes) == + LET TP == Cardinality(pVS) + SP == Cardinality(pVS \intersect pNodes) + IN + 3 * SP > 2 * TP \* when thinking in real numbers, not integers: SP > 2.0 / 3.0 * TP + +(* + Given a set of FaultyNodes, test whether the voting power of the correct nodes in D + is more than 2/3 of the voting power of the faulty nodes in D. + *) +IsCorrectPower(pFaultyNodes, pVS) == + LET FN == pFaultyNodes \intersect pVS \* faulty nodes in pNodes + CN == pVS \ pFaultyNodes \* correct nodes in pNodes + CP == Cardinality(CN) \* power of the correct nodes + FP == Cardinality(FN) \* power of the faulty nodes + IN + \* CP + FP = TP is the total voting power, so we write CP > 2.0 / 3 * TP as follows: + CP > 2 * FP \* Note: when FP = 0, this implies CP > 0. + +(* This is what we believe is the assumption about failures in Tendermint *) +FaultAssumption(pFaultyNodes, pNow, pBlockchain) == + \A h \in Heights: + pBlockchain[h].time + TRUSTING_PERIOD > pNow => + IsCorrectPower(pFaultyNodes, pBlockchain[h].NextVS) + +(* Can a block be produced by a correct peer, or an authenticated Byzantine peer *) +IsLightBlockAllowedByDigitalSignatures(ht, block) == + \/ block.header = blockchain[ht] \* signed by correct and faulty (maybe) + \/ block.Commits \subseteq Faulty /\ block.header.height = ht \* signed only by faulty + +(* + Initialize the blockchain to the ultimate height right in the initial states. + We pick the faulty validators statically, but that should not affect the light client. + *) +InitToHeight == + /\ Faulty \in SUBSET AllNodes \* some nodes may fail + \* pick the validator sets and last commits + /\ \E vs, lastCommit \in [Heights -> SUBSET AllNodes]: + \E timestamp \in [Heights -> Int]: + \* now is at least as early as the timestamp in the last block + /\ \E tm \in Int: now = tm /\ tm >= timestamp[ULTIMATE_HEIGHT] + \* the genesis starts on day 1 + /\ timestamp[1] = 1 + /\ vs[1] = AllNodes + /\ lastCommit[1] = EmptyNodeSet + /\ \A h \in Heights \ {1}: + /\ lastCommit[h] \subseteq vs[h - 1] \* the non-validators cannot commit + /\ TwoThirds(vs[h - 1], lastCommit[h]) \* the commit has >2/3 of validator votes + /\ IsCorrectPower(Faulty, vs[h]) \* the correct validators have >2/3 of power + /\ timestamp[h] > timestamp[h - 1] \* the time grows monotonically + /\ timestamp[h] < timestamp[h - 1] + TRUSTING_PERIOD \* but not too fast + \* form the block chain out of validator sets and commits (this makes apalache faster) + /\ blockchain = [h \in Heights |-> + [height |-> h, + time |-> timestamp[h], + VS |-> vs[h], + NextVS |-> IF h < ULTIMATE_HEIGHT THEN vs[h + 1] ELSE AllNodes, + lastCommit |-> lastCommit[h]] + ] \****** + + +(* is the blockchain in the faulty zone where the Tendermint security model does not apply *) +InFaultyZone == + ~FaultAssumption(Faulty, now, blockchain) + +(********************* BLOCKCHAIN ACTIONS ********************************) +(* + Advance the clock by zero or more time units. + *) +AdvanceTime == + \E tm \in Int: tm >= now /\ now' = tm + /\ UNCHANGED <> + +(* + One more process fails. As a result, the blockchain may move into the faulty zone. + The light client is not using this action, as the faults are picked in the initial state. + However, this action may be useful when reasoning about fork detection. + *) +OneMoreFault == + /\ \E n \in AllNodes \ Faulty: + /\ Faulty' = Faulty \cup {n} + /\ Faulty' /= AllNodes \* at least process remains non-faulty + /\ UNCHANGED <> +============================================================================= +\* Modification History +\* Last modified Wed Jun 10 14:10:54 CEST 2020 by igor +\* Created Fri Oct 11 15:45:11 CEST 2019 by igor diff --git a/spec/light-client/verification/LCVerificationApi_003_draft.tla b/spec/light-client/verification/LCVerificationApi_003_draft.tla new file mode 100644 index 00000000000..909eab92b8a --- /dev/null +++ b/spec/light-client/verification/LCVerificationApi_003_draft.tla @@ -0,0 +1,192 @@ +-------------------- MODULE LCVerificationApi_003_draft -------------------------- +(** + * The common interface of the light client verification and detection. + *) +EXTENDS Integers, FiniteSets + +\* the parameters of Light Client +CONSTANTS + TRUSTING_PERIOD, + (* the period within which the validators are trusted *) + CLOCK_DRIFT, + (* the assumed precision of the clock *) + REAL_CLOCK_DRIFT, + (* the actual clock drift, which under normal circumstances should not + be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) + FAULTY_RATIO + (* a pair <> that limits that ratio of faulty validator in the blockchain + from above (exclusive). Tendermint security model prescribes 1 / 3. *) + +VARIABLES + localClock (* current time as measured by the light client *) + +(* the header is still within the trusting period *) +InTrustingPeriodLocal(header) == + \* note that the assumption about the drift reduces the period of trust + localClock < header.time + TRUSTING_PERIOD - CLOCK_DRIFT + +(* the header is still within the trusting period, even if the clock can go backwards *) +InTrustingPeriodLocalSurely(header) == + \* note that the assumption about the drift reduces the period of trust + localClock < header.time + TRUSTING_PERIOD - 2 * CLOCK_DRIFT + +(* ensure that the local clock does not drift far away from the global clock *) +IsLocalClockWithinDrift(local, global) == + /\ global - REAL_CLOCK_DRIFT <= local + /\ local <= global + REAL_CLOCK_DRIFT + +(** + * Check that the commits in an untrusted block form 1/3 of the next validators + * in a trusted header. + *) +SignedByOneThirdOfTrusted(trusted, untrusted) == + LET TP == Cardinality(trusted.header.NextVS) + SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) + IN + 3 * SP > TP + +(** + The first part of the precondition of ValidAndVerified, which does not take + the current time into account. + + [LCV-FUNC-VALID.1::TLA-PRE-UNTIMED.1] + *) +ValidAndVerifiedPreUntimed(trusted, untrusted) == + LET thdr == trusted.header + uhdr == untrusted.header + IN + /\ thdr.height < uhdr.height + \* the trusted block has been created earlier + /\ thdr.time < uhdr.time + /\ untrusted.Commits \subseteq uhdr.VS + /\ LET TP == Cardinality(uhdr.VS) + SP == Cardinality(untrusted.Commits) + IN + 3 * SP > 2 * TP + /\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS + (* As we do not have explicit hashes we ignore these three checks of the English spec: + + 1. "trusted.Commit is a commit is for the header trusted.Header, + i.e. it contains the correct hash of the header". + 2. untrusted.Validators = hash(untrusted.Header.Validators) + 3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) + *) + +(** + Check the precondition of ValidAndVerified, including the time checks. + + [LCV-FUNC-VALID.1::TLA-PRE.1] + *) +ValidAndVerifiedPre(trusted, untrusted, checkFuture) == + LET thdr == trusted.header + uhdr == untrusted.header + IN + /\ InTrustingPeriodLocal(thdr) + \* The untrusted block is not from the future (modulo clock drift). + \* Do the check, if it is required. + /\ checkFuture => uhdr.time < localClock + CLOCK_DRIFT + /\ ValidAndVerifiedPreUntimed(trusted, untrusted) + + +(** + Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. + This test does take current time into account, but only looks at the block structure. + + [LCV-FUNC-VALID.1::TLA-UNTIMED.1] + *) +ValidAndVerifiedUntimed(trusted, untrusted) == + IF ~ValidAndVerifiedPreUntimed(trusted, untrusted) + THEN "INVALID" + ELSE IF untrusted.header.height = trusted.header.height + 1 + \/ SignedByOneThirdOfTrusted(trusted, untrusted) + THEN "SUCCESS" + ELSE "NOT_ENOUGH_TRUST" + +(** + Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. + + [LCV-FUNC-VALID.1::TLA.1] + *) +ValidAndVerified(trusted, untrusted, checkFuture) == + IF ~ValidAndVerifiedPre(trusted, untrusted, checkFuture) + THEN "INVALID" + ELSE IF ~InTrustingPeriodLocal(untrusted.header) + (* We leave the following test for the documentation purposes. + The implementation should do this test, as signature verification may be slow. + In the TLA+ specification, ValidAndVerified happens in no time. + *) + THEN "FAILED_TRUSTING_PERIOD" + ELSE IF untrusted.header.height = trusted.header.height + 1 + \/ SignedByOneThirdOfTrusted(trusted, untrusted) + THEN "SUCCESS" + ELSE "NOT_ENOUGH_TRUST" + + +(** + The invariant of the light store that is not related to the blockchain + *) +LightStoreInv(fetchedLightBlocks, lightBlockStatus) == + \A lh, rh \in DOMAIN fetchedLightBlocks: + \* for every pair of stored headers that have been verified + \/ lh >= rh + \/ lightBlockStatus[lh] /= "StateVerified" + \/ lightBlockStatus[rh] /= "StateVerified" + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" + \* or the left header is outside the trusting period, so no guarantees + \/ LET lhdr == fetchedLightBlocks[lh] + rhdr == fetchedLightBlocks[rh] + IN + \* we can verify the right one using the left one + "SUCCESS" = ValidAndVerifiedUntimed(lhdr, rhdr) + +(** + Correctness states that all the obtained headers are exactly like in the blockchain. + + It is always the case that every verified header in LightStore was generated by + an instance of Tendermint consensus. + + [LCV-DIST-SAFE.1::CORRECTNESS-INV.1] + *) +CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" => + fetchedLightBlocks[h].header = blockchain[h] + +(** + * When the light client terminates, there are no failed blocks. + * (Otherwise, someone lied to us.) + *) +NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] /= "StateFailed" + +(** + The expected post-condition of VerifyToTarget. + *) +VerifyToTargetPost(blockchain, isPeerCorrect, + fetchedLightBlocks, lightBlockStatus, + trustedHeight, targetHeight, finalState) == + LET trustedHeader == fetchedLightBlocks[trustedHeight].header IN + \* The light client is not lying us on the trusted block. + \* It is straightforward to detect. + /\ lightBlockStatus[trustedHeight] = "StateVerified" + /\ trustedHeight \in DOMAIN fetchedLightBlocks + /\ trustedHeader = blockchain[trustedHeight] + \* the invariants we have found in the light client verification + \* there is a problem with trusting period + /\ isPeerCorrect + => CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) + \* a correct peer should fail the light client, + \* if the trusted block is in the trusting period + /\ isPeerCorrect /\ InTrustingPeriodLocalSurely(trustedHeader) + => finalState = "finishedSuccess" + /\ finalState = "finishedSuccess" => + /\ lightBlockStatus[targetHeight] = "StateVerified" + /\ targetHeight \in DOMAIN fetchedLightBlocks + /\ NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) + /\ LightStoreInv(fetchedLightBlocks, lightBlockStatus) + + +================================================================================== diff --git a/spec/light-client/verification/Lightclient_002_draft.tla b/spec/light-client/verification/Lightclient_002_draft.tla new file mode 100644 index 00000000000..32c807f6e62 --- /dev/null +++ b/spec/light-client/verification/Lightclient_002_draft.tla @@ -0,0 +1,465 @@ +-------------------------- MODULE Lightclient_002_draft ---------------------------- +(** + * A state-machine specification of the lite client, following the English spec: + * + * https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification.md + *) + +EXTENDS Integers, FiniteSets + +\* the parameters of Light Client +CONSTANTS + TRUSTED_HEIGHT, + (* an index of the block header that the light client trusts by social consensus *) + TARGET_HEIGHT, + (* an index of the block header that the light client tries to verify *) + TRUSTING_PERIOD, + (* the period within which the validators are trusted *) + IS_PRIMARY_CORRECT + (* is primary correct? *) + +VARIABLES (* see TypeOK below for the variable types *) + state, (* the current state of the light client *) + nextHeight, (* the next height to explore by the light client *) + nprobes (* the lite client iteration, or the number of block tests *) + +(* the light store *) +VARIABLES + fetchedLightBlocks, (* a function from heights to LightBlocks *) + lightBlockStatus, (* a function from heights to block statuses *) + latestVerified (* the latest verified block *) + +(* the variables of the lite client *) +lcvars == <> + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +InitMonitor(verified, current, now, verdict) == + /\ prevVerified = verified + /\ prevCurrent = current + /\ prevNow = now + /\ prevVerdict = verdict + +NextMonitor(verified, current, now, verdict) == + /\ prevVerified' = verified + /\ prevCurrent' = current + /\ prevNow' = now + /\ prevVerdict' = verdict + + +(******************* Blockchain instance ***********************************) + +\* the parameters that are propagated into Blockchain +CONSTANTS + AllNodes + (* a set of all nodes that can act as validators (correct and faulty) *) + +\* the state variables of Blockchain, see Blockchain.tla for the details +VARIABLES now, blockchain, Faulty + +\* All the variables of Blockchain. For some reason, BC!vars does not work +bcvars == <> + +(* Create an instance of Blockchain. + We could write EXTENDS Blockchain, but then all the constants and state variables + would be hidden inside the Blockchain module. + *) +ULTIMATE_HEIGHT == TARGET_HEIGHT + 1 + +BC == INSTANCE Blockchain_002_draft WITH + now <- now, blockchain <- blockchain, Faulty <- Faulty + +(************************** Lite client ************************************) + +(* the heights on which the light client is working *) +HEIGHTS == TRUSTED_HEIGHT..TARGET_HEIGHT + +(* the control states of the lite client *) +States == { "working", "finishedSuccess", "finishedFailure" } + +(** + Check the precondition of ValidAndVerified. + + [LCV-FUNC-VALID.1::TLA-PRE.1] + *) +ValidAndVerifiedPre(trusted, untrusted) == + LET thdr == trusted.header + uhdr == untrusted.header + IN + /\ BC!InTrustingPeriod(thdr) + /\ thdr.height < uhdr.height + \* the trusted block has been created earlier (no drift here) + /\ thdr.time < uhdr.time + \* the untrusted block is not from the future + /\ uhdr.time < now + /\ untrusted.Commits \subseteq uhdr.VS + /\ LET TP == Cardinality(uhdr.VS) + SP == Cardinality(untrusted.Commits) + IN + 3 * SP > 2 * TP + /\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS + (* As we do not have explicit hashes we ignore these three checks of the English spec: + + 1. "trusted.Commit is a commit is for the header trusted.Header, + i.e. it contains the correct hash of the header". + 2. untrusted.Validators = hash(untrusted.Header.Validators) + 3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) + *) + +(** + * Check that the commits in an untrusted block form 1/3 of the next validators + * in a trusted header. + *) +SignedByOneThirdOfTrusted(trusted, untrusted) == + LET TP == Cardinality(trusted.header.NextVS) + SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) + IN + 3 * SP > TP + +(** + Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. + + [LCV-FUNC-VALID.1::TLA.1] + *) +ValidAndVerified(trusted, untrusted) == + IF ~ValidAndVerifiedPre(trusted, untrusted) + THEN "INVALID" + ELSE IF ~BC!InTrustingPeriod(untrusted.header) + (* We leave the following test for the documentation purposes. + The implementation should do this test, as signature verification may be slow. + In the TLA+ specification, ValidAndVerified happens in no time. + *) + THEN "FAILED_TRUSTING_PERIOD" + ELSE IF untrusted.header.height = trusted.header.height + 1 + \/ SignedByOneThirdOfTrusted(trusted, untrusted) + THEN "SUCCESS" + ELSE "NOT_ENOUGH_TRUST" + +(* + Initial states of the light client. + Initially, only the trusted light block is present. + *) +LCInit == + /\ state = "working" + /\ nextHeight = TARGET_HEIGHT + /\ nprobes = 0 \* no tests have been done so far + /\ LET trustedBlock == blockchain[TRUSTED_HEIGHT] + trustedLightBlock == [header |-> trustedBlock, Commits |-> AllNodes] + IN + \* initially, fetchedLightBlocks is a function of one element, i.e., TRUSTED_HEIGHT + /\ fetchedLightBlocks = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock] + \* initially, lightBlockStatus is a function of one element, i.e., TRUSTED_HEIGHT + /\ lightBlockStatus = [h \in {TRUSTED_HEIGHT} |-> "StateVerified"] + \* the latest verified block the the trusted block + /\ latestVerified = trustedLightBlock + /\ InitMonitor(trustedLightBlock, trustedLightBlock, now, "SUCCESS") + +\* block should contain a copy of the block from the reference chain, with a matching commit +CopyLightBlockFromChain(block, height) == + LET ref == blockchain[height] + lastCommit == + IF height < ULTIMATE_HEIGHT + THEN blockchain[height + 1].lastCommit + \* for the ultimate block, which we never use, as ULTIMATE_HEIGHT = TARGET_HEIGHT + 1 + ELSE blockchain[height].VS + IN + block = [header |-> ref, Commits |-> lastCommit] + +\* Either the primary is correct and the block comes from the reference chain, +\* or the block is produced by a faulty primary. +\* +\* [LCV-FUNC-FETCH.1::TLA.1] +FetchLightBlockInto(block, height) == + IF IS_PRIMARY_CORRECT + THEN CopyLightBlockFromChain(block, height) + ELSE BC!IsLightBlockAllowedByDigitalSignatures(height, block) + +\* add a block into the light store +\* +\* [LCV-FUNC-UPDATE.1::TLA.1] +LightStoreUpdateBlocks(lightBlocks, block) == + LET ht == block.header.height IN + [h \in DOMAIN lightBlocks \union {ht} |-> + IF h = ht THEN block ELSE lightBlocks[h]] + +\* update the state of a light block +\* +\* [LCV-FUNC-UPDATE.1::TLA.1] +LightStoreUpdateStates(statuses, ht, blockState) == + [h \in DOMAIN statuses \union {ht} |-> + IF h = ht THEN blockState ELSE statuses[h]] + +\* Check, whether newHeight is a possible next height for the light client. +\* +\* [LCV-FUNC-SCHEDULE.1::TLA.1] +CanScheduleTo(newHeight, pLatestVerified, pNextHeight, pTargetHeight) == + LET ht == pLatestVerified.header.height IN + \/ /\ ht = pNextHeight + /\ ht < pTargetHeight + /\ pNextHeight < newHeight + /\ newHeight <= pTargetHeight + \/ /\ ht < pNextHeight + /\ ht < pTargetHeight + /\ ht < newHeight + /\ newHeight < pNextHeight + \/ /\ ht = pTargetHeight + /\ newHeight = pTargetHeight + +\* The loop of VerifyToTarget. +\* +\* [LCV-FUNC-MAIN.1::TLA-LOOP.1] +VerifyToTargetLoop == + \* the loop condition is true + /\ latestVerified.header.height < TARGET_HEIGHT + \* pick a light block, which will be constrained later + /\ \E current \in BC!LightBlocks: + \* Get next LightBlock for verification + /\ IF nextHeight \in DOMAIN fetchedLightBlocks + THEN \* copy the block from the light store + /\ current = fetchedLightBlocks[nextHeight] + /\ UNCHANGED fetchedLightBlocks + ELSE \* retrieve a light block and save it in the light store + /\ FetchLightBlockInto(current, nextHeight) + /\ fetchedLightBlocks' = LightStoreUpdateBlocks(fetchedLightBlocks, current) + \* Record that one more probe has been done (for complexity and model checking) + /\ nprobes' = nprobes + 1 + \* Verify the current block + /\ LET verdict == ValidAndVerified(latestVerified, current) IN + NextMonitor(latestVerified, current, now, verdict) /\ + \* Decide whether/how to continue + CASE verdict = "SUCCESS" -> + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateVerified") + /\ latestVerified' = current + /\ state' = + IF latestVerified'.header.height < TARGET_HEIGHT + THEN "working" + ELSE "finishedSuccess" + /\ \E newHeight \in HEIGHTS: + /\ CanScheduleTo(newHeight, current, nextHeight, TARGET_HEIGHT) + /\ nextHeight' = newHeight + + [] verdict = "NOT_ENOUGH_TRUST" -> + (* + do nothing: the light block current passed validation, but the validator + set is too different to verify it. We keep the state of + current at StateUnverified. For a later iteration, Schedule + might decide to try verification of that light block again. + *) + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateUnverified") + /\ \E newHeight \in HEIGHTS: + /\ CanScheduleTo(newHeight, latestVerified, nextHeight, TARGET_HEIGHT) + /\ nextHeight' = newHeight + /\ UNCHANGED <> + + [] OTHER -> + \* verdict is some error code + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateFailed") + /\ state' = "finishedFailure" + /\ UNCHANGED <> + +\* The terminating condition of VerifyToTarget. +\* +\* [LCV-FUNC-MAIN.1::TLA-LOOPCOND.1] +VerifyToTargetDone == + /\ latestVerified.header.height >= TARGET_HEIGHT + /\ state' = "finishedSuccess" + /\ UNCHANGED <> + /\ UNCHANGED <> + +(********************* Lite client + Blockchain *******************) +Init == + \* the blockchain is initialized immediately to the ULTIMATE_HEIGHT + /\ BC!InitToHeight + \* the light client starts + /\ LCInit + +(* + The system step is very simple. + The light client is either executing VerifyToTarget, or it has terminated. + (In the latter case, a model checker reports a deadlock.) + Simultaneously, the global clock may advance. + *) +Next == + /\ state = "working" + /\ VerifyToTargetLoop \/ VerifyToTargetDone + /\ BC!AdvanceTime \* the global clock is advanced by zero or more time units + +(************************* Types ******************************************) +TypeOK == + /\ state \in States + /\ nextHeight \in HEIGHTS + /\ latestVerified \in BC!LightBlocks + /\ \E HS \in SUBSET HEIGHTS: + /\ fetchedLightBlocks \in [HS -> BC!LightBlocks] + /\ lightBlockStatus + \in [HS -> {"StateVerified", "StateUnverified", "StateFailed"}] + +(************************* Properties ******************************************) + +(* The properties to check *) +\* this invariant candidate is false +NeverFinish == + state = "working" + +\* this invariant candidate is false +NeverFinishNegative == + state /= "finishedFailure" + +\* This invariant holds true, when the primary is correct. +\* This invariant candidate is false when the primary is faulty. +NeverFinishNegativeWhenTrusted == + (*(minTrustedHeight <= TRUSTED_HEIGHT)*) + BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + => state /= "finishedFailure" + +\* this invariant candidate is false +NeverFinishPositive == + state /= "finishedSuccess" + +(** + Correctness states that all the obtained headers are exactly like in the blockchain. + + It is always the case that every verified header in LightStore was generated by + an instance of Tendermint consensus. + + [LCV-DIST-SAFE.1::CORRECTNESS-INV.1] + *) +CorrectnessInv == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" => + fetchedLightBlocks[h].header = blockchain[h] + +(** + Check that the sequence of the headers in storedLightBlocks satisfies ValidAndVerified = "SUCCESS" pairwise + This property is easily violated, whenever a header cannot be trusted anymore. + *) +StoredHeadersAreVerifiedInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers + \/ lh >= rh + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh + \* or we can verify the right one using the left one + \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) + +\* An improved version of StoredHeadersAreSound, assuming that a header may be not trusted. +\* This invariant candidate is also violated, +\* as there may be some unverified blocks left in the middle. +StoredHeadersAreVerifiedOrNotTrustedInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers + \/ lh >= rh + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh + \* or we can verify the right one using the left one + \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) + \* or the left header is outside the trusting period, so no guarantees + \/ ~BC!InTrustingPeriod(fetchedLightBlocks[lh].header) + +(** + * An improved version of StoredHeadersAreSoundOrNotTrusted, + * checking the property only for the verified headers. + * This invariant holds true. + *) +ProofOfChainOfTrustInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: + \* for every pair of stored headers that have been verified + \/ lh >= rh + \/ lightBlockStatus[lh] = "StateUnverified" + \/ lightBlockStatus[rh] = "StateUnverified" + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" + \* or the left header is outside the trusting period, so no guarantees + \/ ~(BC!InTrustingPeriod(fetchedLightBlocks[lh].header)) + \* or we can verify the right one using the left one + \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) + +(** + * When the light client terminates, there are no failed blocks. (Otherwise, someone lied to us.) + *) +NoFailedBlocksOnSuccessInv == + state = "finishedSuccess" => + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] /= "StateFailed" + +\* This property states that whenever the light client finishes with a positive outcome, +\* the trusted header is still within the trusting period. +\* We expect this property to be violated. And Apalache shows us a counterexample. +PositiveBeforeTrustedHeaderExpires == + (state = "finishedSuccess") => BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + +\* If the primary is correct and the initial trusted block has not expired, +\* then whenever the algorithm terminates, it reports "success" +CorrectPrimaryAndTimeliness == + (BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + /\ state /= "working" /\ IS_PRIMARY_CORRECT) => + state = "finishedSuccess" + +(** + If the primary is correct and there is a trusted block that has not expired, + then whenever the algorithm terminates, it reports "success". + + [LCV-DIST-LIVE.1::SUCCESS-CORR-PRIMARY-CHAIN-OF-TRUST.1] + *) +SuccessOnCorrectPrimaryAndChainOfTrust == + (\E h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" /\ BC!InTrustingPeriod(blockchain[h]) + /\ state /= "working" /\ IS_PRIMARY_CORRECT) => + state = "finishedSuccess" + +\* Lite Client Completeness: If header h was correctly generated by an instance +\* of Tendermint consensus (and its age is less than the trusting period), +\* then the lite client should eventually set trust(h) to true. +\* +\* Note that Completeness assumes that the lite client communicates with a correct full node. +\* +\* We decompose completeness into Termination (liveness) and Precision (safety). +\* Once again, Precision is an inverse version of the safety property in Completeness, +\* as A => B is logically equivalent to ~B => ~A. +PrecisionInv == + (state = "finishedFailure") + => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period + \/ \E h \in DOMAIN fetchedLightBlocks: + LET lightBlock == fetchedLightBlocks[h] IN + \* the full node lied to the lite client about the block header + \/ lightBlock.header /= blockchain[h] + \* the full node lied to the lite client about the commits + \/ lightBlock.Commits /= lightBlock.header.VS + +\* the old invariant that was found to be buggy by TLC +PrecisionBuggyInv == + (state = "finishedFailure") + => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period + \/ \E h \in DOMAIN fetchedLightBlocks: + LET lightBlock == fetchedLightBlocks[h] IN + \* the full node lied to the lite client about the block header + lightBlock.header /= blockchain[h] + +\* the worst complexity +Complexity == + LET N == TARGET_HEIGHT - TRUSTED_HEIGHT + 1 IN + state /= "working" => + (2 * nprobes <= N * (N - 1)) + +(* + We omit termination, as the algorithm deadlocks in the end. + So termination can be demonstrated by finding a deadlock. + Of course, one has to analyze the deadlocked state and see that + the algorithm has indeed terminated there. +*) +============================================================================= +\* Modification History +\* Last modified Fri Jun 26 12:08:28 CEST 2020 by igor +\* Created Wed Oct 02 16:39:42 CEST 2019 by igor diff --git a/spec/light-client/verification/Lightclient_003_draft.tla b/spec/light-client/verification/Lightclient_003_draft.tla new file mode 100644 index 00000000000..e17a88491b8 --- /dev/null +++ b/spec/light-client/verification/Lightclient_003_draft.tla @@ -0,0 +1,493 @@ +-------------------------- MODULE Lightclient_003_draft ---------------------------- +(** + * A state-machine specification of the lite client verification, + * following the English spec: + * + * https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification.md + *) + +EXTENDS Integers, FiniteSets + +\* the parameters of Light Client +CONSTANTS + TRUSTED_HEIGHT, + (* an index of the block header that the light client trusts by social consensus *) + TARGET_HEIGHT, + (* an index of the block header that the light client tries to verify *) + TRUSTING_PERIOD, + (* the period within which the validators are trusted *) + CLOCK_DRIFT, + (* the assumed precision of the clock *) + REAL_CLOCK_DRIFT, + (* the actual clock drift, which under normal circumstances should not + be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) + IS_PRIMARY_CORRECT, + (* is primary correct? *) + FAULTY_RATIO + (* a pair <> that limits that ratio of faulty validator in the blockchain + from above (exclusive). Tendermint security model prescribes 1 / 3. *) + +VARIABLES (* see TypeOK below for the variable types *) + localClock, (* the local clock of the light client *) + state, (* the current state of the light client *) + nextHeight, (* the next height to explore by the light client *) + nprobes (* the lite client iteration, or the number of block tests *) + +(* the light store *) +VARIABLES + fetchedLightBlocks, (* a function from heights to LightBlocks *) + lightBlockStatus, (* a function from heights to block statuses *) + latestVerified (* the latest verified block *) + +(* the variables of the lite client *) +lcvars == <> + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +InitMonitor(verified, current, pLocalClock, verdict) == + /\ prevVerified = verified + /\ prevCurrent = current + /\ prevLocalClock = pLocalClock + /\ prevVerdict = verdict + +NextMonitor(verified, current, pLocalClock, verdict) == + /\ prevVerified' = verified + /\ prevCurrent' = current + /\ prevLocalClock' = pLocalClock + /\ prevVerdict' = verdict + + +(******************* Blockchain instance ***********************************) + +\* the parameters that are propagated into Blockchain +CONSTANTS + AllNodes + (* a set of all nodes that can act as validators (correct and faulty) *) + +\* the state variables of Blockchain, see Blockchain.tla for the details +VARIABLES refClock, blockchain, Faulty + +\* All the variables of Blockchain. For some reason, BC!vars does not work +bcvars == <> + +(* Create an instance of Blockchain. + We could write EXTENDS Blockchain, but then all the constants and state variables + would be hidden inside the Blockchain module. + *) +ULTIMATE_HEIGHT == TARGET_HEIGHT + 1 + +BC == INSTANCE Blockchain_003_draft WITH + refClock <- refClock, blockchain <- blockchain, Faulty <- Faulty + +(************************** Lite client ************************************) + +(* the heights on which the light client is working *) +HEIGHTS == TRUSTED_HEIGHT..TARGET_HEIGHT + +(* the control states of the lite client *) +States == { "working", "finishedSuccess", "finishedFailure" } + +\* The verification functions are implemented in the API +API == INSTANCE LCVerificationApi_003_draft + + +(* + Initial states of the light client. + Initially, only the trusted light block is present. + *) +LCInit == + /\ \E tm \in Int: + tm >= 0 /\ API!IsLocalClockWithinDrift(tm, refClock) /\ localClock = tm + /\ state = "working" + /\ nextHeight = TARGET_HEIGHT + /\ nprobes = 0 \* no tests have been done so far + /\ LET trustedBlock == blockchain[TRUSTED_HEIGHT] + trustedLightBlock == [header |-> trustedBlock, Commits |-> AllNodes] + IN + \* initially, fetchedLightBlocks is a function of one element, i.e., TRUSTED_HEIGHT + /\ fetchedLightBlocks = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock] + \* initially, lightBlockStatus is a function of one element, i.e., TRUSTED_HEIGHT + /\ lightBlockStatus = [h \in {TRUSTED_HEIGHT} |-> "StateVerified"] + \* the latest verified block the the trusted block + /\ latestVerified = trustedLightBlock + /\ InitMonitor(trustedLightBlock, trustedLightBlock, localClock, "SUCCESS") + +\* block should contain a copy of the block from the reference chain, with a matching commit +CopyLightBlockFromChain(block, height) == + LET ref == blockchain[height] + lastCommit == + IF height < ULTIMATE_HEIGHT + THEN blockchain[height + 1].lastCommit + \* for the ultimate block, which we never use, as ULTIMATE_HEIGHT = TARGET_HEIGHT + 1 + ELSE blockchain[height].VS + IN + block = [header |-> ref, Commits |-> lastCommit] + +\* Either the primary is correct and the block comes from the reference chain, +\* or the block is produced by a faulty primary. +\* +\* [LCV-FUNC-FETCH.1::TLA.1] +FetchLightBlockInto(block, height) == + IF IS_PRIMARY_CORRECT + THEN CopyLightBlockFromChain(block, height) + ELSE BC!IsLightBlockAllowedByDigitalSignatures(height, block) + +\* add a block into the light store +\* +\* [LCV-FUNC-UPDATE.1::TLA.1] +LightStoreUpdateBlocks(lightBlocks, block) == + LET ht == block.header.height IN + [h \in DOMAIN lightBlocks \union {ht} |-> + IF h = ht THEN block ELSE lightBlocks[h]] + +\* update the state of a light block +\* +\* [LCV-FUNC-UPDATE.1::TLA.1] +LightStoreUpdateStates(statuses, ht, blockState) == + [h \in DOMAIN statuses \union {ht} |-> + IF h = ht THEN blockState ELSE statuses[h]] + +\* Check, whether newHeight is a possible next height for the light client. +\* +\* [LCV-FUNC-SCHEDULE.1::TLA.1] +CanScheduleTo(newHeight, pLatestVerified, pNextHeight, pTargetHeight) == + LET ht == pLatestVerified.header.height IN + \/ /\ ht = pNextHeight + /\ ht < pTargetHeight + /\ pNextHeight < newHeight + /\ newHeight <= pTargetHeight + \/ /\ ht < pNextHeight + /\ ht < pTargetHeight + /\ ht < newHeight + /\ newHeight < pNextHeight + \/ /\ ht = pTargetHeight + /\ newHeight = pTargetHeight + +\* The loop of VerifyToTarget. +\* +\* [LCV-FUNC-MAIN.1::TLA-LOOP.1] +VerifyToTargetLoop == + \* the loop condition is true + /\ latestVerified.header.height < TARGET_HEIGHT + \* pick a light block, which will be constrained later + /\ \E current \in BC!LightBlocks: + \* Get next LightBlock for verification + /\ IF nextHeight \in DOMAIN fetchedLightBlocks + THEN \* copy the block from the light store + /\ current = fetchedLightBlocks[nextHeight] + /\ UNCHANGED fetchedLightBlocks + ELSE \* retrieve a light block and save it in the light store + /\ FetchLightBlockInto(current, nextHeight) + /\ fetchedLightBlocks' = LightStoreUpdateBlocks(fetchedLightBlocks, current) + \* Record that one more probe has been done (for complexity and model checking) + /\ nprobes' = nprobes + 1 + \* Verify the current block + /\ LET verdict == API!ValidAndVerified(latestVerified, current, TRUE) IN + NextMonitor(latestVerified, current, localClock, verdict) /\ + \* Decide whether/how to continue + CASE verdict = "SUCCESS" -> + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateVerified") + /\ latestVerified' = current + /\ state' = + IF latestVerified'.header.height < TARGET_HEIGHT + THEN "working" + ELSE "finishedSuccess" + /\ \E newHeight \in HEIGHTS: + /\ CanScheduleTo(newHeight, current, nextHeight, TARGET_HEIGHT) + /\ nextHeight' = newHeight + + [] verdict = "NOT_ENOUGH_TRUST" -> + (* + do nothing: the light block current passed validation, but the validator + set is too different to verify it. We keep the state of + current at StateUnverified. For a later iteration, Schedule + might decide to try verification of that light block again. + *) + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateUnverified") + /\ \E newHeight \in HEIGHTS: + /\ CanScheduleTo(newHeight, latestVerified, nextHeight, TARGET_HEIGHT) + /\ nextHeight' = newHeight + /\ UNCHANGED <> + + [] OTHER -> + \* verdict is some error code + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateFailed") + /\ state' = "finishedFailure" + /\ UNCHANGED <> + +\* The terminating condition of VerifyToTarget. +\* +\* [LCV-FUNC-MAIN.1::TLA-LOOPCOND.1] +VerifyToTargetDone == + /\ latestVerified.header.height >= TARGET_HEIGHT + /\ state' = "finishedSuccess" + /\ UNCHANGED <> + /\ UNCHANGED <> + +(* + The local and global clocks can be updated. They can also drift from each other. + Note that the local clock can actually go backwards in time. + However, it still stays in the drift envelope + of [refClock - REAL_CLOCK_DRIFT, refClock + REAL_CLOCK_DRIFT]. + *) +AdvanceClocks == + /\ BC!AdvanceTime + /\ \E tm \in Int: + /\ tm >= 0 + /\ API!IsLocalClockWithinDrift(tm, refClock') + /\ localClock' = tm + \* if you like the clock to always grow monotonically, uncomment the next line: + \*/\ localClock' > localClock + +(********************* Lite client + Blockchain *******************) +Init == + \* the blockchain is initialized immediately to the ULTIMATE_HEIGHT + /\ BC!InitToHeight(FAULTY_RATIO) + \* the light client starts + /\ LCInit + +(* + The system step is very simple. + The light client is either executing VerifyToTarget, or it has terminated. + (In the latter case, a model checker reports a deadlock.) + Simultaneously, the global clock may advance. + *) +Next == + /\ state = "working" + /\ VerifyToTargetLoop \/ VerifyToTargetDone + /\ AdvanceClocks + +(************************* Types ******************************************) +TypeOK == + /\ state \in States + /\ localClock \in Nat + /\ refClock \in Nat + /\ nextHeight \in HEIGHTS + /\ latestVerified \in BC!LightBlocks + /\ \E HS \in SUBSET HEIGHTS: + /\ fetchedLightBlocks \in [HS -> BC!LightBlocks] + /\ lightBlockStatus + \in [HS -> {"StateVerified", "StateUnverified", "StateFailed"}] + +(************************* Properties ******************************************) + +(* The properties to check *) +\* this invariant candidate is false +NeverFinish == + state = "working" + +\* this invariant candidate is false +NeverFinishNegative == + state /= "finishedFailure" + +\* This invariant holds true, when the primary is correct. +\* This invariant candidate is false when the primary is faulty. +NeverFinishNegativeWhenTrusted == + BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + => state /= "finishedFailure" + +\* this invariant candidate is false +NeverFinishPositive == + state /= "finishedSuccess" + + +(** + Check that the target height has been reached upon successful termination. + *) +TargetHeightOnSuccessInv == + state = "finishedSuccess" => + /\ TARGET_HEIGHT \in DOMAIN fetchedLightBlocks + /\ lightBlockStatus[TARGET_HEIGHT] = "StateVerified" + +(** + Correctness states that all the obtained headers are exactly like in the blockchain. + + It is always the case that every verified header in LightStore was generated by + an instance of Tendermint consensus. + + [LCV-DIST-SAFE.1::CORRECTNESS-INV.1] + *) +CorrectnessInv == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" => + fetchedLightBlocks[h].header = blockchain[h] + +(** + No faulty block was used to construct a proof. This invariant holds, + only if FAULTY_RATIO < 1/3. + *) +NoTrustOnFaultyBlockInv == + (state = "finishedSuccess" + /\ fetchedLightBlocks[TARGET_HEIGHT].header = blockchain[TARGET_HEIGHT]) + => CorrectnessInv + +(** + Check that the sequence of the headers in storedLightBlocks satisfies ValidAndVerified = "SUCCESS" pairwise + This property is easily violated, whenever a header cannot be trusted anymore. + *) +StoredHeadersAreVerifiedInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers + \/ lh >= rh + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh + \* or we can verify the right one using the left one + \/ "SUCCESS" = API!ValidAndVerified(fetchedLightBlocks[lh], + fetchedLightBlocks[rh], FALSE) + +\* An improved version of StoredHeadersAreVerifiedInv, +\* assuming that a header may be not trusted. +\* This invariant candidate is also violated, +\* as there may be some unverified blocks left in the middle. +\* This property is violated under two conditions: +\* (1) the primary is faulty and there are at least 4 blocks, +\* (2) the primary is correct and there are at least 5 blocks. +StoredHeadersAreVerifiedOrNotTrustedInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers + \/ lh >= rh + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh + \* or we can verify the right one using the left one + \/ "SUCCESS" = API!ValidAndVerified(fetchedLightBlocks[lh], + fetchedLightBlocks[rh], FALSE) + \* or the left header is outside the trusting period, so no guarantees + \/ ~API!InTrustingPeriodLocal(fetchedLightBlocks[lh].header) + +(** + * An improved version of StoredHeadersAreSoundOrNotTrusted, + * checking the property only for the verified headers. + * This invariant holds true if CLOCK_DRIFT <= REAL_CLOCK_DRIFT. + *) +ProofOfChainOfTrustInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: + \* for every pair of stored headers that have been verified + \/ lh >= rh + \/ lightBlockStatus[lh] = "StateUnverified" + \/ lightBlockStatus[rh] = "StateUnverified" + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" + \* or the left header is outside the trusting period, so no guarantees + \/ ~(API!InTrustingPeriodLocal(fetchedLightBlocks[lh].header)) + \* or we can verify the right one using the left one + \/ "SUCCESS" = API!ValidAndVerified(fetchedLightBlocks[lh], + fetchedLightBlocks[rh], FALSE) + +(** + * When the light client terminates, there are no failed blocks. (Otherwise, someone lied to us.) + *) +NoFailedBlocksOnSuccessInv == + state = "finishedSuccess" => + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] /= "StateFailed" + +\* This property states that whenever the light client finishes with a positive outcome, +\* the trusted header is still within the trusting period. +\* We expect this property to be violated. And Apalache shows us a counterexample. +PositiveBeforeTrustedHeaderExpires == + (state = "finishedSuccess") => + BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + +\* If the primary is correct and the initial trusted block has not expired, +\* then whenever the algorithm terminates, it reports "success". +\* This property fails. +CorrectPrimaryAndTimeliness == + (BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + /\ state /= "working" /\ IS_PRIMARY_CORRECT) => + state = "finishedSuccess" + +(** + If the primary is correct and there is a trusted block that has not expired, + then whenever the algorithm terminates, it reports "success". + This property only holds true, if the local clock is always growing monotonically. + If the local clock can go backwards in the envelope + [refClock - CLOCK_DRIFT, refClock + CLOCK_DRIFT], then the property fails. + + [LCV-DIST-LIVE.1::SUCCESS-CORR-PRIMARY-CHAIN-OF-TRUST.1] + *) +SuccessOnCorrectPrimaryAndChainOfTrustLocal == + (\E h \in DOMAIN fetchedLightBlocks: + /\ lightBlockStatus[h] = "StateVerified" + /\ API!InTrustingPeriodLocal(blockchain[h]) + /\ state /= "working" /\ IS_PRIMARY_CORRECT) => + state = "finishedSuccess" + +(** + Similar to SuccessOnCorrectPrimaryAndChainOfTrust, but using the blockchain clock. + It fails because the local clock of the client drifted away, so it rejects a block + that has not expired yet (according to the local clock). + *) +SuccessOnCorrectPrimaryAndChainOfTrustGlobal == + (\E h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" /\ BC!InTrustingPeriod(blockchain[h]) + /\ state /= "working" /\ IS_PRIMARY_CORRECT) => + state = "finishedSuccess" + +\* Lite Client Completeness: If header h was correctly generated by an instance +\* of Tendermint consensus (and its age is less than the trusting period), +\* then the lite client should eventually set trust(h) to true. +\* +\* Note that Completeness assumes that the lite client communicates with a correct full node. +\* +\* We decompose completeness into Termination (liveness) and Precision (safety). +\* Once again, Precision is an inverse version of the safety property in Completeness, +\* as A => B is logically equivalent to ~B => ~A. +\* +\* This property holds only when CLOCK_DRIFT = 0 and REAL_CLOCK_DRIFT = 0. +PrecisionInv == + (state = "finishedFailure") + => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period + \/ \E h \in DOMAIN fetchedLightBlocks: + LET lightBlock == fetchedLightBlocks[h] IN + \* the full node lied to the lite client about the block header + \/ lightBlock.header /= blockchain[h] + \* the full node lied to the lite client about the commits + \/ lightBlock.Commits /= lightBlock.header.VS + +\* the old invariant that was found to be buggy by TLC +PrecisionBuggyInv == + (state = "finishedFailure") + => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period + \/ \E h \in DOMAIN fetchedLightBlocks: + LET lightBlock == fetchedLightBlocks[h] IN + \* the full node lied to the lite client about the block header + lightBlock.header /= blockchain[h] + +\* the worst complexity +Complexity == + LET N == TARGET_HEIGHT - TRUSTED_HEIGHT + 1 IN + state /= "working" => + (2 * nprobes <= N * (N - 1)) + +(** + If the light client has terminated, then the expected postcondition holds true. + *) +ApiPostInv == + state /= "working" => + API!VerifyToTargetPost(blockchain, IS_PRIMARY_CORRECT, + fetchedLightBlocks, lightBlockStatus, + TRUSTED_HEIGHT, TARGET_HEIGHT, state) + +(* + We omit termination, as the algorithm deadlocks in the end. + So termination can be demonstrated by finding a deadlock. + Of course, one has to analyze the deadlocked state and see that + the algorithm has indeed terminated there. +*) +============================================================================= +\* Modification History +\* Last modified Fri Jun 26 12:08:28 CEST 2020 by igor +\* Created Wed Oct 02 16:39:42 CEST 2019 by igor diff --git a/spec/light-client/verification/Lightclient_A_1.tla b/spec/light-client/verification/Lightclient_A_1.tla new file mode 100644 index 00000000000..70e6caf002c --- /dev/null +++ b/spec/light-client/verification/Lightclient_A_1.tla @@ -0,0 +1,440 @@ +-------------------------- MODULE Lightclient_A_1 ---------------------------- +(** + * A state-machine specification of the lite client, following the English spec: + * + * ./verification_001_published.md + *) + +EXTENDS Integers, FiniteSets + +\* the parameters of Light Client +CONSTANTS + TRUSTED_HEIGHT, + (* an index of the block header that the light client trusts by social consensus *) + TARGET_HEIGHT, + (* an index of the block header that the light client tries to verify *) + TRUSTING_PERIOD, + (* the period within which the validators are trusted *) + IS_PRIMARY_CORRECT + (* is primary correct? *) + +VARIABLES (* see TypeOK below for the variable types *) + state, (* the current state of the light client *) + nextHeight, (* the next height to explore by the light client *) + nprobes (* the lite client iteration, or the number of block tests *) + +(* the light store *) +VARIABLES + fetchedLightBlocks, (* a function from heights to LightBlocks *) + lightBlockStatus, (* a function from heights to block statuses *) + latestVerified (* the latest verified block *) + +(* the variables of the lite client *) +lcvars == <> + +(******************* Blockchain instance ***********************************) + +\* the parameters that are propagated into Blockchain +CONSTANTS + AllNodes + (* a set of all nodes that can act as validators (correct and faulty) *) + +\* the state variables of Blockchain, see Blockchain.tla for the details +VARIABLES now, blockchain, Faulty + +\* All the variables of Blockchain. For some reason, BC!vars does not work +bcvars == <> + +(* Create an instance of Blockchain. + We could write EXTENDS Blockchain, but then all the constants and state variables + would be hidden inside the Blockchain module. + *) +ULTIMATE_HEIGHT == TARGET_HEIGHT + 1 + +BC == INSTANCE Blockchain_A_1 WITH + now <- now, blockchain <- blockchain, Faulty <- Faulty + +(************************** Lite client ************************************) + +(* the heights on which the light client is working *) +HEIGHTS == TRUSTED_HEIGHT..TARGET_HEIGHT + +(* the control states of the lite client *) +States == { "working", "finishedSuccess", "finishedFailure" } + +(** + Check the precondition of ValidAndVerified. + + [LCV-FUNC-VALID.1::TLA-PRE.1] + *) +ValidAndVerifiedPre(trusted, untrusted) == + LET thdr == trusted.header + uhdr == untrusted.header + IN + /\ BC!InTrustingPeriod(thdr) + /\ thdr.height < uhdr.height + \* the trusted block has been created earlier (no drift here) + /\ thdr.time <= uhdr.time + /\ untrusted.Commits \subseteq uhdr.VS + /\ LET TP == Cardinality(uhdr.VS) + SP == Cardinality(untrusted.Commits) + IN + 3 * SP > 2 * TP + /\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS + (* As we do not have explicit hashes we ignore these three checks of the English spec: + + 1. "trusted.Commit is a commit is for the header trusted.Header, + i.e. it contains the correct hash of the header". + 2. untrusted.Validators = hash(untrusted.Header.Validators) + 3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) + *) + +(** + * Check that the commits in an untrusted block form 1/3 of the next validators + * in a trusted header. + *) +SignedByOneThirdOfTrusted(trusted, untrusted) == + LET TP == Cardinality(trusted.header.NextVS) + SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) + IN + 3 * SP > TP + +(** + Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. + + [LCV-FUNC-VALID.1::TLA.1] + *) +ValidAndVerified(trusted, untrusted) == + IF ~ValidAndVerifiedPre(trusted, untrusted) + THEN "FAILED_VERIFICATION" + ELSE IF ~BC!InTrustingPeriod(untrusted.header) + (* We leave the following test for the documentation purposes. + The implementation should do this test, as signature verification may be slow. + In the TLA+ specification, ValidAndVerified happens in no time. + *) + THEN "FAILED_TRUSTING_PERIOD" + ELSE IF untrusted.header.height = trusted.header.height + 1 + \/ SignedByOneThirdOfTrusted(trusted, untrusted) + THEN "OK" + ELSE "CANNOT_VERIFY" + +(* + Initial states of the light client. + Initially, only the trusted light block is present. + *) +LCInit == + /\ state = "working" + /\ nextHeight = TARGET_HEIGHT + /\ nprobes = 0 \* no tests have been done so far + /\ LET trustedBlock == blockchain[TRUSTED_HEIGHT] + trustedLightBlock == [header |-> trustedBlock, Commits |-> AllNodes] + IN + \* initially, fetchedLightBlocks is a function of one element, i.e., TRUSTED_HEIGHT + /\ fetchedLightBlocks = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock] + \* initially, lightBlockStatus is a function of one element, i.e., TRUSTED_HEIGHT + /\ lightBlockStatus = [h \in {TRUSTED_HEIGHT} |-> "StateVerified"] + \* the latest verified block the the trusted block + /\ latestVerified = trustedLightBlock + +\* block should contain a copy of the block from the reference chain, with a matching commit +CopyLightBlockFromChain(block, height) == + LET ref == blockchain[height] + lastCommit == + IF height < ULTIMATE_HEIGHT + THEN blockchain[height + 1].lastCommit + \* for the ultimate block, which we never use, as ULTIMATE_HEIGHT = TARGET_HEIGHT + 1 + ELSE blockchain[height].VS + IN + block = [header |-> ref, Commits |-> lastCommit] + +\* Either the primary is correct and the block comes from the reference chain, +\* or the block is produced by a faulty primary. +\* +\* [LCV-FUNC-FETCH.1::TLA.1] +FetchLightBlockInto(block, height) == + IF IS_PRIMARY_CORRECT + THEN CopyLightBlockFromChain(block, height) + ELSE BC!IsLightBlockAllowedByDigitalSignatures(height, block) + +\* add a block into the light store +\* +\* [LCV-FUNC-UPDATE.1::TLA.1] +LightStoreUpdateBlocks(lightBlocks, block) == + LET ht == block.header.height IN + [h \in DOMAIN lightBlocks \union {ht} |-> + IF h = ht THEN block ELSE lightBlocks[h]] + +\* update the state of a light block +\* +\* [LCV-FUNC-UPDATE.1::TLA.1] +LightStoreUpdateStates(statuses, ht, blockState) == + [h \in DOMAIN statuses \union {ht} |-> + IF h = ht THEN blockState ELSE statuses[h]] + +\* Check, whether newHeight is a possible next height for the light client. +\* +\* [LCV-FUNC-SCHEDULE.1::TLA.1] +CanScheduleTo(newHeight, pLatestVerified, pNextHeight, pTargetHeight) == + LET ht == pLatestVerified.header.height IN + \/ /\ ht = pNextHeight + /\ ht < pTargetHeight + /\ pNextHeight < newHeight + /\ newHeight <= pTargetHeight + \/ /\ ht < pNextHeight + /\ ht < pTargetHeight + /\ ht < newHeight + /\ newHeight < pNextHeight + \/ /\ ht = pTargetHeight + /\ newHeight = pTargetHeight + +\* The loop of VerifyToTarget. +\* +\* [LCV-FUNC-MAIN.1::TLA-LOOP.1] +VerifyToTargetLoop == + \* the loop condition is true + /\ latestVerified.header.height < TARGET_HEIGHT + \* pick a light block, which will be constrained later + /\ \E current \in BC!LightBlocks: + \* Get next LightBlock for verification + /\ IF nextHeight \in DOMAIN fetchedLightBlocks + THEN \* copy the block from the light store + /\ current = fetchedLightBlocks[nextHeight] + /\ UNCHANGED fetchedLightBlocks + ELSE \* retrieve a light block and save it in the light store + /\ FetchLightBlockInto(current, nextHeight) + /\ fetchedLightBlocks' = LightStoreUpdateBlocks(fetchedLightBlocks, current) + \* Record that one more probe has been done (for complexity and model checking) + /\ nprobes' = nprobes + 1 + \* Verify the current block + /\ LET verdict == ValidAndVerified(latestVerified, current) IN + \* Decide whether/how to continue + CASE verdict = "OK" -> + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateVerified") + /\ latestVerified' = current + /\ state' = + IF latestVerified'.header.height < TARGET_HEIGHT + THEN "working" + ELSE "finishedSuccess" + /\ \E newHeight \in HEIGHTS: + /\ CanScheduleTo(newHeight, current, nextHeight, TARGET_HEIGHT) + /\ nextHeight' = newHeight + + [] verdict = "CANNOT_VERIFY" -> + (* + do nothing: the light block current passed validation, but the validator + set is too different to verify it. We keep the state of + current at StateUnverified. For a later iteration, Schedule + might decide to try verification of that light block again. + *) + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateUnverified") + /\ \E newHeight \in HEIGHTS: + /\ CanScheduleTo(newHeight, latestVerified, nextHeight, TARGET_HEIGHT) + /\ nextHeight' = newHeight + /\ UNCHANGED <> + + [] OTHER -> + \* verdict is some error code + /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateFailed") + /\ state' = "finishedFailure" + /\ UNCHANGED <> + +\* The terminating condition of VerifyToTarget. +\* +\* [LCV-FUNC-MAIN.1::TLA-LOOPCOND.1] +VerifyToTargetDone == + /\ latestVerified.header.height >= TARGET_HEIGHT + /\ state' = "finishedSuccess" + /\ UNCHANGED <> + +(********************* Lite client + Blockchain *******************) +Init == + \* the blockchain is initialized immediately to the ULTIMATE_HEIGHT + /\ BC!InitToHeight + \* the light client starts + /\ LCInit + +(* + The system step is very simple. + The light client is either executing VerifyToTarget, or it has terminated. + (In the latter case, a model checker reports a deadlock.) + Simultaneously, the global clock may advance. + *) +Next == + /\ state = "working" + /\ VerifyToTargetLoop \/ VerifyToTargetDone + /\ BC!AdvanceTime \* the global clock is advanced by zero or more time units + +(************************* Types ******************************************) +TypeOK == + /\ state \in States + /\ nextHeight \in HEIGHTS + /\ latestVerified \in BC!LightBlocks + /\ \E HS \in SUBSET HEIGHTS: + /\ fetchedLightBlocks \in [HS -> BC!LightBlocks] + /\ lightBlockStatus + \in [HS -> {"StateVerified", "StateUnverified", "StateFailed"}] + +(************************* Properties ******************************************) + +(* The properties to check *) +\* this invariant candidate is false +NeverFinish == + state = "working" + +\* this invariant candidate is false +NeverFinishNegative == + state /= "finishedFailure" + +\* This invariant holds true, when the primary is correct. +\* This invariant candidate is false when the primary is faulty. +NeverFinishNegativeWhenTrusted == + (*(minTrustedHeight <= TRUSTED_HEIGHT)*) + BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + => state /= "finishedFailure" + +\* this invariant candidate is false +NeverFinishPositive == + state /= "finishedSuccess" + +(** + Correctness states that all the obtained headers are exactly like in the blockchain. + + It is always the case that every verified header in LightStore was generated by + an instance of Tendermint consensus. + + [LCV-DIST-SAFE.1::CORRECTNESS-INV.1] + *) +CorrectnessInv == + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" => + fetchedLightBlocks[h].header = blockchain[h] + +(** + Check that the sequence of the headers in storedLightBlocks satisfies ValidAndVerified = "OK" pairwise + This property is easily violated, whenever a header cannot be trusted anymore. + *) +StoredHeadersAreVerifiedInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers + \/ lh >= rh + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh + \* or we can verify the right one using the left one + \/ "OK" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) + +\* An improved version of StoredHeadersAreSound, assuming that a header may be not trusted. +\* This invariant candidate is also violated, +\* as there may be some unverified blocks left in the middle. +StoredHeadersAreVerifiedOrNotTrustedInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers + \/ lh >= rh + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh + \* or we can verify the right one using the left one + \/ "OK" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) + \* or the left header is outside the trusting period, so no guarantees + \/ ~BC!InTrustingPeriod(fetchedLightBlocks[lh].header) + +(** + * An improved version of StoredHeadersAreSoundOrNotTrusted, + * checking the property only for the verified headers. + * This invariant holds true. + *) +ProofOfChainOfTrustInv == + state = "finishedSuccess" + => + \A lh, rh \in DOMAIN fetchedLightBlocks: + \* for every pair of stored headers that have been verified + \/ lh >= rh + \/ lightBlockStatus[lh] = "StateUnverified" + \/ lightBlockStatus[rh] = "StateUnverified" + \* either there is a header between them + \/ \E mh \in DOMAIN fetchedLightBlocks: + lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" + \* or the left header is outside the trusting period, so no guarantees + \/ ~(BC!InTrustingPeriod(fetchedLightBlocks[lh].header)) + \* or we can verify the right one using the left one + \/ "OK" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) + +(** + * When the light client terminates, there are no failed blocks. (Otherwise, someone lied to us.) + *) +NoFailedBlocksOnSuccessInv == + state = "finishedSuccess" => + \A h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] /= "StateFailed" + +\* This property states that whenever the light client finishes with a positive outcome, +\* the trusted header is still within the trusting period. +\* We expect this property to be violated. And Apalache shows us a counterexample. +PositiveBeforeTrustedHeaderExpires == + (state = "finishedSuccess") => BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + +\* If the primary is correct and the initial trusted block has not expired, +\* then whenever the algorithm terminates, it reports "success" +CorrectPrimaryAndTimeliness == + (BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) + /\ state /= "working" /\ IS_PRIMARY_CORRECT) => + state = "finishedSuccess" + +(** + If the primary is correct and there is a trusted block that has not expired, + then whenever the algorithm terminates, it reports "success". + + [LCV-DIST-LIVE.1::SUCCESS-CORR-PRIMARY-CHAIN-OF-TRUST.1] + *) +SuccessOnCorrectPrimaryAndChainOfTrust == + (\E h \in DOMAIN fetchedLightBlocks: + lightBlockStatus[h] = "StateVerified" /\ BC!InTrustingPeriod(blockchain[h]) + /\ state /= "working" /\ IS_PRIMARY_CORRECT) => + state = "finishedSuccess" + +\* Lite Client Completeness: If header h was correctly generated by an instance +\* of Tendermint consensus (and its age is less than the trusting period), +\* then the lite client should eventually set trust(h) to true. +\* +\* Note that Completeness assumes that the lite client communicates with a correct full node. +\* +\* We decompose completeness into Termination (liveness) and Precision (safety). +\* Once again, Precision is an inverse version of the safety property in Completeness, +\* as A => B is logically equivalent to ~B => ~A. +PrecisionInv == + (state = "finishedFailure") + => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period + \/ \E h \in DOMAIN fetchedLightBlocks: + LET lightBlock == fetchedLightBlocks[h] IN + \* the full node lied to the lite client about the block header + \/ lightBlock.header /= blockchain[h] + \* the full node lied to the lite client about the commits + \/ lightBlock.Commits /= lightBlock.header.VS + +\* the old invariant that was found to be buggy by TLC +PrecisionBuggyInv == + (state = "finishedFailure") + => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period + \/ \E h \in DOMAIN fetchedLightBlocks: + LET lightBlock == fetchedLightBlocks[h] IN + \* the full node lied to the lite client about the block header + lightBlock.header /= blockchain[h] + +\* the worst complexity +Complexity == + LET N == TARGET_HEIGHT - TRUSTED_HEIGHT + 1 IN + state /= "working" => + (2 * nprobes <= N * (N - 1)) + +(* + We omit termination, as the algorithm deadlocks in the end. + So termination can be demonstrated by finding a deadlock. + Of course, one has to analyze the deadlocked state and see that + the algorithm has indeed terminated there. +*) +============================================================================= +\* Modification History +\* Last modified Fri Jun 26 12:08:28 CEST 2020 by igor +\* Created Wed Oct 02 16:39:42 CEST 2019 by igor diff --git a/spec/light-client/verification/MC4_3_correct.tla b/spec/light-client/verification/MC4_3_correct.tla new file mode 100644 index 00000000000..a27d8de05de --- /dev/null +++ b/spec/light-client/verification/MC4_3_correct.tla @@ -0,0 +1,26 @@ +---------------------------- MODULE MC4_3_correct --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 3 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================== diff --git a/spec/light-client/verification/MC4_3_faulty.tla b/spec/light-client/verification/MC4_3_faulty.tla new file mode 100644 index 00000000000..74b278900b6 --- /dev/null +++ b/spec/light-client/verification/MC4_3_faulty.tla @@ -0,0 +1,26 @@ +---------------------------- MODULE MC4_3_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 3 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================== diff --git a/spec/light-client/verification/MC4_4_correct.tla b/spec/light-client/verification/MC4_4_correct.tla new file mode 100644 index 00000000000..0a8d96b59cd --- /dev/null +++ b/spec/light-client/verification/MC4_4_correct.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC4_4_correct --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 4 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC4_4_correct_drifted.tla b/spec/light-client/verification/MC4_4_correct_drifted.tla new file mode 100644 index 00000000000..7fefe349ea8 --- /dev/null +++ b/spec/light-client/verification/MC4_4_correct_drifted.tla @@ -0,0 +1,26 @@ +---------------------- MODULE MC4_4_correct_drifted --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 4 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 30 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================== diff --git a/spec/light-client/verification/MC4_4_faulty.tla b/spec/light-client/verification/MC4_4_faulty.tla new file mode 100644 index 00000000000..167fa61fb11 --- /dev/null +++ b/spec/light-client/verification/MC4_4_faulty.tla @@ -0,0 +1,26 @@ +---------------------------- MODULE MC4_4_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 4 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================== diff --git a/spec/light-client/verification/MC4_4_faulty_drifted.tla b/spec/light-client/verification/MC4_4_faulty_drifted.tla new file mode 100644 index 00000000000..e37c3cb404c --- /dev/null +++ b/spec/light-client/verification/MC4_4_faulty_drifted.tla @@ -0,0 +1,26 @@ +---------------------- MODULE MC4_4_faulty_drifted --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 4 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 30 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================== diff --git a/spec/light-client/verification/MC4_5_correct.tla b/spec/light-client/verification/MC4_5_correct.tla new file mode 100644 index 00000000000..cffb22cc8f4 --- /dev/null +++ b/spec/light-client/verification/MC4_5_correct.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC4_5_correct --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 5 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC4_5_faulty.tla b/spec/light-client/verification/MC4_5_faulty.tla new file mode 100644 index 00000000000..3d3a002907b --- /dev/null +++ b/spec/light-client/verification/MC4_5_faulty.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC4_5_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 5 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +IS_PRICLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +MARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC4_6_faulty.tla b/spec/light-client/verification/MC4_6_faulty.tla new file mode 100644 index 00000000000..64f164854b7 --- /dev/null +++ b/spec/light-client/verification/MC4_6_faulty.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC4_6_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 6 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +IS_PRCLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC4_7_faulty.tla b/spec/light-client/verification/MC4_7_faulty.tla new file mode 100644 index 00000000000..dc6a94eb1d4 --- /dev/null +++ b/spec/light-client/verification/MC4_7_faulty.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC4_7_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 7 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC5_5_correct.tla b/spec/light-client/verification/MC5_5_correct.tla new file mode 100644 index 00000000000..00b4151f7c4 --- /dev/null +++ b/spec/light-client/verification/MC5_5_correct.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC5_5_correct --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 5 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC5_5_correct_peer_two_thirds_faulty.tla b/spec/light-client/verification/MC5_5_correct_peer_two_thirds_faulty.tla new file mode 100644 index 00000000000..d4212032fc4 --- /dev/null +++ b/spec/light-client/verification/MC5_5_correct_peer_two_thirds_faulty.tla @@ -0,0 +1,26 @@ +------------------- MODULE MC5_5_correct_peer_two_thirds_faulty ---------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 5 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC5_5_faulty.tla b/spec/light-client/verification/MC5_5_faulty.tla new file mode 100644 index 00000000000..f63d175a172 --- /dev/null +++ b/spec/light-client/verification/MC5_5_faulty.tla @@ -0,0 +1,26 @@ +----------------- MODULE MC5_5_faulty --------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 5 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC5_5_faulty_peer_two_thirds_faulty.tla b/spec/light-client/verification/MC5_5_faulty_peer_two_thirds_faulty.tla new file mode 100644 index 00000000000..ef9974d0621 --- /dev/null +++ b/spec/light-client/verification/MC5_5_faulty_peer_two_thirds_faulty.tla @@ -0,0 +1,26 @@ +----------------- MODULE MC5_5_faulty_peer_two_thirds_faulty --------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 5 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<2, 3>> \* < 2 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC5_7_faulty.tla b/spec/light-client/verification/MC5_7_faulty.tla new file mode 100644 index 00000000000..63461b0c891 --- /dev/null +++ b/spec/light-client/verification/MC5_7_faulty.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC5_7_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 7 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC7_5_faulty.tla b/spec/light-client/verification/MC7_5_faulty.tla new file mode 100644 index 00000000000..860f9c0aa8e --- /dev/null +++ b/spec/light-client/verification/MC7_5_faulty.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC7_5_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5", "n6", "n7"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 5 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/MC7_7_faulty.tla b/spec/light-client/verification/MC7_7_faulty.tla new file mode 100644 index 00000000000..79e328f141e --- /dev/null +++ b/spec/light-client/verification/MC7_7_faulty.tla @@ -0,0 +1,26 @@ +------------------------- MODULE MC7_7_faulty --------------------------- + +AllNodes == {"n1", "n2", "n3", "n4", "n5", "n6", "n7"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 7 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting +REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevLocalClock, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/spec/light-client/verification/README.md b/spec/light-client/verification/README.md new file mode 100644 index 00000000000..8777374ac99 --- /dev/null +++ b/spec/light-client/verification/README.md @@ -0,0 +1,577 @@ +--- +order: 1 +parent: + title: Verification + order: 2 +--- +# Core Verification + +## Problem statement + +We assume that the light client knows a (base) header `inithead` it trusts (by social consensus or because +the light client has decided to trust the header before). The goal is to check whether another header +`newhead` can be trusted based on the data in `inithead`. + +The correctness of the protocol is based on the assumption that `inithead` was generated by an instance of +Tendermint consensus. + +### Failure Model + +For the purpose of the following definitions we assume that there exists a function +`validators` that returns the corresponding validator set for the given hash. + +The light client protocol is defined with respect to the following failure model: + +Given a known bound `TRUSTED_PERIOD`, and a block `b` with header `h` generated at time `Time` +(i.e. `h.Time = Time`), a set of validators that hold more than 2/3 of the voting power +in `validators(b.Header.NextValidatorsHash)` is correct until time `b.Header.Time + TRUSTED_PERIOD`. + +*Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), +while `Header.Time` corresponds to the [BFT time](../consensus/bft-time.md). In this note, we assume that clocks of correct processes +are synchronized (for example using NTP), and therefore there is bounded clock drift (`CLOCK_DRIFT`) between local clocks and +BFT time. More precisely, for every correct light client process and every `header.Time` (i.e. BFT Time, for a header correctly +generated by the Tendermint consensus), the following inequality holds: `Header.Time < now + CLOCK_DRIFT`, +where `now` corresponds to the system clock at the light client process. + +Furthermore, we assume that `TRUSTED_PERIOD` is (several) order of magnitude bigger than `CLOCK_DRIFT` (`TRUSTED_PERIOD >> CLOCK_DRIFT`), +as `CLOCK_DRIFT` (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks. + +We expect a light client process defined in this document to be used in the context in which there is some +larger period during which misbehaving validators can be detected and punished (we normally refer to it as `UNBONDING_PERIOD` +due to the "bonding" mechanism in modern proof of stake systems). Furthermore, we assume that +`TRUSTED_PERIOD < UNBONDING_PERIOD` and that they are normally of the same order of magnitude, for example +`TRUSTED_PERIOD = UNBONDING_PERIOD / 2`. + +The specification in this document considers an implementation of the light client under the Failure Model defined above. +Mechanisms like `fork accountability` and `evidence submission` are defined in the context of `UNBONDING_PERIOD` and +they incentivize validators to follow the protocol specification defined in this document. If they don't, +and we have 1/3 (or more) faulty validators, safety may be violated. Our approach then is +to *detect* these cases (after the fact), and take suitable repair actions (automatic and social). +This is discussed in document on [Fork accountability](./accountability.md). + +The term "trusted" above indicates that the correctness of the protocol depends on +this assumption. It is in the responsibility of the user that runs the light client to make sure that the risk +of trusting a corrupted/forged `inithead` is negligible. + +*Remark*: This failure model might change to a hybrid version that takes heights into account in the future. + +### High Level Solution + +Upon initialization, the light client is given a header `inithead` it trusts (by +social consensus). When a light clients sees a new signed header `snh`, it has to decide whether to trust the new +header. Trust can be obtained by (possibly) the combination of three methods. + +1. **Uninterrupted sequence of headers.** Given a trusted header `h` and an untrusted header `h1`, +the light client trusts a header `h1` if it trusts all headers in between `h` and `h1`. + +2. **Trusted period.** Given a trusted header `h`, an untrusted header `h1 > h` and `TRUSTED_PERIOD` during which +the failure model holds, we can check whether at least one validator, that has been continuously correct +from `h.Time` until now, has signed `h1`. If this is the case, we can trust `h1`. + +3. **Bisection.** If a check according to 2. (trusted period) fails, the light client can try to +obtain a header `hp` whose height lies between `h` and `h1` in order to check whether `h` can be used to +get trust for `hp`, and `hp` can be used to get trust for `snh`. If this is the case we can trust `h1`; +if not, we continue recursively until either we found set of headers that can build (transitively) trust relation +between `h` and `h1`, or we failed as two consecutive headers don't verify against each other. + +## Definitions + +### Data structures + +In the following, only the details of the data structures needed for this specification are given. + + ```go + type Header struct { + Height int64 + Time Time // the chain time when the header (block) was generated + + LastBlockID BlockID // prev block info + ValidatorsHash []byte // hash of the validators for the current block + NextValidatorsHash []byte // hash of the validators for the next block + } + + type SignedHeader struct { + Header Header + Commit Commit // commit for the given header + } + + type ValidatorSet struct { + Validators []Validator + TotalVotingPower int64 + } + + type Validator struct { + Address Address // validator address (we assume validator's addresses are unique) + VotingPower int64 // validator's voting power + } + + type TrustedState { + SignedHeader SignedHeader + ValidatorSet ValidatorSet + } + ``` + +### Functions + +For the purpose of this light client specification, we assume that the Tendermint Full Node +exposes the following functions over Tendermint RPC: + +```go + // returns signed header: Header with Commit, for the given height + func Commit(height int64) (SignedHeader, error) + + // returns validator set for the given height + func Validators(height int64) (ValidatorSet, error) +``` + +Furthermore, we assume the following auxiliary functions: + +```go + // returns true if the commit is for the header, ie. if it contains + // the correct hash of the header; otherwise false + func matchingCommit(header Header, commit Commit) bool + + // returns the set of validators from the given validator set that + // committed the block (that correctly signed the block) + // it assumes signature verification so it can be computationally expensive + func signers(commit Commit, validatorSet ValidatorSet) []Validator + + // returns the voting power the validators in v1 have according to their voting power in set v2 + // it does not assume signature verification + func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 + + // returns hash of the given validator set + func hash(v2 ValidatorSet) []byte +``` + +In the functions below we will be using `trustThreshold` as a parameter. For simplicity +we assume that `trustThreshold` is a float between `1/3` and `2/3` and we will not be checking it +in the pseudo-code. + +**VerifySingle.** The function `VerifySingle` attempts to validate given untrusted header and the corresponding validator sets +based on a given trusted state. It ensures that the trusted state is still within its trusted period, +and that the untrusted header is within assumed `clockDrift` bound of the passed time `now`. +Note that this function is not making external (RPC) calls to the full node; the whole logic is +based on the local (given) state. This function is supposed to be used by the IBC handlers. + +```go +func VerifySingle(untrustedSh SignedHeader, + untrustedVs ValidatorSet, + untrustedNextVs ValidatorSet, + trustedState TrustedState, + trustThreshold float, + trustingPeriod Duration, + clockDrift Duration, + now Time) (TrustedState, error) { + + if untrustedSh.Header.Time > now + clockDrift { + return (trustedState, ErrInvalidHeaderTime) + } + + trustedHeader = trustedState.SignedHeader.Header + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (state, ErrHeaderNotWithinTrustedPeriod) + } + + // we assume that time it takes to execute verifySingle function + // is several order of magnitudes smaller than trustingPeriod + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if error != nil return (state, error) + + // the untrusted header is now trusted + newTrustedState = TrustedState(untrustedSh, untrustedNextVs) + return (newTrustedState, nil) +} + +// return true if header is within its light client trusted period; otherwise returns false +func isWithinTrustedPeriod(header Header, + trustingPeriod Duration, + now Time) bool { + + return header.Time + trustedPeriod > now +} +``` + +Note that in case `VerifySingle` returns without an error (untrusted header +is successfully verified) then we have a guarantee that the transition of the trust +from `trustedState` to `newTrustedState` happened during the trusted period of +`trustedState.SignedHeader.Header`. + +TODO: Explain what happens in case `VerifySingle` returns with an error. + +**verifySingle.** The function `verifySingle` verifies a single untrusted header +against a given trusted state. It includes all validations and signature verification. +It is not publicly exposed since it does not check for header expiry (time constraints) +and hence it's possible to use it incorrectly. + +```go +func verifySingle(trustedState TrustedState, + untrustedSh SignedHeader, + untrustedVs ValidatorSet, + untrustedNextVs ValidatorSet, + trustThreshold float) error { + + untrustedHeader = untrustedSh.Header + untrustedCommit = untrustedSh.Commit + + trustedHeader = trustedState.SignedHeader.Header + trustedVs = trustedState.ValidatorSet + + if trustedHeader.Height >= untrustedHeader.Height return ErrNonIncreasingHeight + if trustedHeader.Time >= untrustedHeader.Time return ErrNonIncreasingTime + + // validate the untrusted header against its commit, vals, and next_vals + error = validateSignedHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs) + if error != nil return error + + // check for adjacent headers + if untrustedHeader.Height == trustedHeader.Height + 1 { + if trustedHeader.NextValidatorsHash != untrustedHeader.ValidatorsHash { + return ErrInvalidAdjacentHeaders + } + } else { + error = verifyCommitTrusting(trustedVs, untrustedCommit, untrustedVs, trustThreshold) + if error != nil return error + } + + // verify the untrusted commit + return verifyCommitFull(untrustedVs, untrustedCommit) +} + +// returns nil if header and validator sets are consistent; otherwise returns error +func validateSignedHeaderAndVals(signedHeader SignedHeader, vs ValidatorSet, nextVs ValidatorSet) error { + header = signedHeader.Header + if hash(vs) != header.ValidatorsHash return ErrInvalidValidatorSet + if hash(nextVs) != header.NextValidatorsHash return ErrInvalidNextValidatorSet + if !matchingCommit(header, signedHeader.Commit) return ErrInvalidCommitValue + return nil +} + +// returns nil if at least single correst signer signed the commit; otherwise returns error +func verifyCommitTrusting(trustedVs ValidatorSet, + commit Commit, + untrustedVs ValidatorSet, + trustLevel float) error { + + totalPower := trustedVs.TotalVotingPower + signedPower := votingPowerIn(signers(commit, untrustedVs), trustedVs) + + // check that the signers account for more than max(1/3, trustLevel) of the voting power + // this ensures that there is at least single correct validator in the set of signers + if signedPower < max(1/3, trustLevel) * totalPower return ErrInsufficientVotingPower + return nil +} + +// returns nil if commit is signed by more than 2/3 of voting power of the given validator set +// return error otherwise +func verifyCommitFull(vs ValidatorSet, commit Commit) error { + totalPower := vs.TotalVotingPower; + signedPower := votingPowerIn(signers(commit, vs), vs) + + // check the signers account for +2/3 of the voting power + if signedPower * 3 <= totalPower * 2 return ErrInvalidCommit + return nil +} +``` + +**VerifyHeaderAtHeight.** The function `VerifyHeaderAtHeight` captures high level +logic, i.e., application call to the light client module to download and verify header +for some height. + +```go +func VerifyHeaderAtHeight(untrustedHeight int64, + trustedState TrustedState, + trustThreshold float, + trustingPeriod Duration, + clockDrift Duration) (TrustedState, error)) { + + trustedHeader := trustedState.SignedHeader.Header + + now := System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (trustedState, ErrHeaderNotWithinTrustedPeriod) + } + + newTrustedState, err := VerifyBisection(untrustedHeight, + trustedState, + trustThreshold, + trustingPeriod, + clockDrift, + now) + + if err != nil return (trustedState, err) + + now = System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (trustedState, ErrHeaderNotWithinTrustedPeriod) + } + + return (newTrustedState, err) +} +``` + +Note that in case `VerifyHeaderAtHeight` returns without an error (untrusted header +is successfully verified) then we have a guarantee that the transition of the trust +from `trustedState` to `newTrustedState` happened during the trusted period of +`trustedState.SignedHeader.Header`. + +In case `VerifyHeaderAtHeight` returns with an error, then either (i) the full node we are talking to is faulty +or (ii) the trusted header has expired (it is outside its trusted period). In case (i) the full node is faulty so +light client should disconnect and reinitialise with new peer. In the case (ii) as the trusted header has expired, +we need to reinitialise light client with a new trusted header (that is within its trusted period), +but we don't necessarily need to disconnect from the full node we are talking to (as we haven't observed full node misbehavior in this case). + +**VerifyBisection.** The function `VerifyBisection` implements +recursive logic for checking if it is possible building trust +relationship between `trustedState` and untrusted header at the given height over +finite set of (downloaded and verified) headers. + +```go +func VerifyBisection(untrustedHeight int64, + trustedState TrustedState, + trustThreshold float, + trustingPeriod Duration, + clockDrift Duration, + now Time) (TrustedState, error) { + + untrustedSh, error := Commit(untrustedHeight) + if error != nil return (trustedState, ErrRequestFailed) + + untrustedHeader = untrustedSh.Header + + // note that we pass now during the recursive calls. This is fine as + // all other untrusted headers we download during recursion will be + // for a smaller heights, and therefore should happen before. + if untrustedHeader.Time > now + clockDrift { + return (trustedState, ErrInvalidHeaderTime) + } + + untrustedVs, error := Validators(untrustedHeight) + if error != nil return (trustedState, ErrRequestFailed) + + untrustedNextVs, error := Validators(untrustedHeight + 1) + if error != nil return (trustedState, ErrRequestFailed) + + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if fatalError(error) return (trustedState, error) + + if error == nil { + // the untrusted header is now trusted. + newTrustedState = TrustedState(untrustedSh, untrustedNextVs) + return (newTrustedState, nil) + } + + // at this point in time we need to do bisection + pivotHeight := ceil((trustedHeader.Height + untrustedHeight) / 2) + + error, newTrustedState = VerifyBisection(pivotHeight, + trustedState, + trustThreshold, + trustingPeriod, + clockDrift, + now) + if error != nil return (newTrustedState, error) + + return VerifyBisection(untrustedHeight, + newTrustedState, + trustThreshold, + trustingPeriod, + clockDrift, + now) +} + +func fatalError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod OR + err == ErrInvalidAdjacentHeaders OR + err == ErrNonIncreasingHeight OR + err == ErrNonIncreasingTime OR + err == ErrInvalidValidatorSet OR + err == ErrInvalidNextValidatorSet OR + err == ErrInvalidCommitValue OR + err == ErrInvalidCommit +} +``` + +### The case `untrustedHeader.Height < trustedHeader.Height` + +In the use case where someone tells the light client that application data that is relevant for it +can be read in the block of height `k` and the light client trusts a more recent header, we can use the +hashes to verify headers "down the chain." That is, we iterate down the heights and check the hashes in each step. + +*Remark.* For the case were the light client trusts two headers `i` and `j` with `i < k < j`, we should +discuss/experiment whether the forward or the backward method is more effective. + +```go +func VerifyHeaderBackwards(trustedHeader Header, + untrustedHeader Header, + trustingPeriod Duration, + clockDrift Duration) error { + + if untrustedHeader.Height >= trustedHeader.Height return ErrErrNonDecreasingHeight + if untrustedHeader.Time >= trustedHeader.Time return ErrNonDecreasingTime + + now := System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + + old := trustedHeader + for i := trustedHeader.Height - 1; i > untrustedHeader.Height; i-- { + untrustedSh, error := Commit(i) + if error != nil return ErrRequestFailed + + if (hash(untrustedSh.Header) != old.LastBlockID.Hash) { + return ErrInvalidAdjacentHeaders + } + + old := untrustedSh.Header + } + + if hash(untrustedHeader) != old.LastBlockID.Hash { + return ErrInvalidAdjacentHeaders + } + + now := System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + + return nil + } +``` + +*Assumption*: In the following, we assume that *untrusted_h.Header.height > trusted_h.Header.height*. We will quickly discuss the other case in the next section. + +We consider the following set-up: + +- the light client communicates with one full node +- the light client locally stores all the headers that has passed basic verification and that are within light client trust period. In the pseudo code below we +write *Store.Add(header)* for this. If a header failed to verify, then +the full node we are talking to is faulty and we should disconnect from it and reinitialise with new peer. +- If `CanTrust` returns *error*, then the light client has seen a forged header or the trusted header has expired (it is outside its trusted period). + - In case of forged header, the full node is faulty so light client should disconnect and reinitialise with new peer. If the trusted header has expired, + we need to reinitialise light client with new trusted header (that is within its trusted period), but we don't necessarily need to disconnect from the full node + we are talking to (as we haven't observed full node misbehavior in this case). + +## Correctness of the Light Client Protocols + +### Definitions + +- `TRUSTED_PERIOD`: trusted period +- for realtime `t`, the predicate `correct(v,t)` is true if the validator `v` + follows the protocol until time `t` (we will see about recovery later). +- Validator fields. We will write a validator as a tuple `(v,p)` such that + - `v` is the identifier (i.e., validator address; we assume identifiers are unique in each validator set) + - `p` is its voting power +- For each header `h`, we write `trust(h) = true` if the light client trusts `h`. + +### Failure Model + +If a block `b` with a header `h` is generated at time `Time` (i.e. `h.Time = Time`), then a set of validators that +hold more than `2/3` of the voting power in `validators(h.NextValidatorsHash)` is correct until time +`h.Time + TRUSTED_PERIOD`. + +Formally, +\[ +\sum_{(v,p) \in validators(h.NextValidatorsHash) \wedge correct(v,h.Time + TRUSTED_PERIOD)} p > +2/3 \sum_{(v,p) \in validators(h.NextValidatorsHash)} p +\] + +The light client communicates with a full node and learns new headers. The goal is to locally decide whether to trust a header. Our implementation needs to ensure the following two properties: + +- *Light Client Completeness*: If a header `h` was correctly generated by an instance of Tendermint consensus (and its age is less than the trusted period), +then the light client should eventually set `trust(h)` to `true`. + +- *Light Client Accuracy*: If a header `h` was *not generated* by an instance of Tendermint consensus, then the light client should never set `trust(h)` to true. + +*Remark*: If in the course of the computation, the light client obtains certainty that some headers were forged by adversaries +(that is were not generated by an instance of Tendermint consensus), it may submit (a subset of) the headers it has seen as evidence of misbehavior. + +*Remark*: In Completeness we use "eventually", while in practice `trust(h)` should be set to true before `h.Time + TRUSTED_PERIOD`. If not, the header +cannot be trusted because it is too old. + +*Remark*: If a header `h` is marked with `trust(h)`, but it is too old at some point in time we denote with `now` (`h.Time + TRUSTED_PERIOD < now`), +then the light client should set `trust(h)` to `false` again at time `now`. + +*Assumption*: Initially, the light client has a header `inithead` that it trusts, that is, `inithead` was correctly generated by the Tendermint consensus. + +To reason about the correctness, we may prove the following invariant. + +*Verification Condition: light Client Invariant.* + For each light client `l` and each header `h`: +if `l` has set `trust(h) = true`, + then validators that are correct until time `h.Time + TRUSTED_PERIOD` have more than two thirds of the voting power in `validators(h.NextValidatorsHash)`. + + Formally, + \[ + \sum_{(v,p) \in validators(h.NextValidatorsHash) \wedge correct(v,h.Time + TRUSTED_PERIOD)} p > + 2/3 \sum_{(v,p) \in validators(h.NextValidatorsHash)} p + \] + +*Remark.* To prove the invariant, we will have to prove that the light client only trusts headers that were correctly generated by Tendermint consensus. +Then the formula above follows from the failure model. + +## Details + +**Observation 1.** If `h.Time + TRUSTED_PERIOD > now`, we trust the validator set `validators(h.NextValidatorsHash)`. + +When we say we trust `validators(h.NextValidatorsHash)` we do `not` trust that each individual validator in `validators(h.NextValidatorsHash)` +is correct, but we only trust the fact that less than `1/3` of them are faulty (more precisely, the faulty ones have less than `1/3` of the total voting power). + +*`VerifySingle` correctness arguments* + +Light Client Accuracy: + +- Assume by contradiction that `untrustedHeader` was not generated correctly and the light client sets trust to true because `verifySingle` returns without error. +- `trustedState` is trusted and sufficiently new +- by the Failure Model, less than `1/3` of the voting power held by faulty validators => at least one correct validator `v` has signed `untrustedHeader`. +- as `v` is correct up to now, it followed the Tendermint consensus protocol at least up to signing `untrustedHeader` => `untrustedHeader` was correctly generated. +We arrive at the required contradiction. + +Light Client Completeness: + +- The check is successful if sufficiently many validators of `trustedState` are still validators in the height `untrustedHeader.Height` and signed `untrustedHeader`. +- If `untrustedHeader.Height = trustedHeader.Height + 1`, and both headers were generated correctly, the test passes. + +*Verification Condition:* We may need a Tendermint invariant stating that if `untrustedSignedHeader.Header.Height = trustedHeader.Height + 1` then +`signers(untrustedSignedHeader.Commit) \subseteq validators(trustedHeader.NextValidatorsHash)`. + +*Remark*: The variable `trustThreshold` can be used if the user believes that relying on one correct validator is not sufficient. +However, in case of (frequent) changes in the validator set, the higher the `trustThreshold` is chosen, the more unlikely it becomes that +`verifySingle` returns with an error for non-adjacent headers. + +- `VerifyBisection` correctness arguments (sketch)* + +Light Client Accuracy: + +- Assume by contradiction that the header at `untrustedHeight` obtained from the full node was not generated correctly and +the light client sets trust to true because `VerifyBisection` returns without an error. +- `VerifyBisection` returns without error only if all calls to `verifySingle` in the recursion return without error (return `nil`). +- Thus we have a sequence of headers that all satisfied the `verifySingle` +- again a contradiction + +light Client Completeness: + +This is only ensured if upon `Commit(pivot)` the light client is always provided with a correctly generated header. + +*Stalling* + +With `VerifyBisection`, a faulty full node could stall a light client by creating a long sequence of headers that are queried one-by-one by the light client and look OK, +before the light client eventually detects a problem. There are several ways to address this: + +- Each call to `Commit` could be issued to a different full node +- Instead of querying header by header, the light client tells a full node which header it trusts, and the height of the header it needs. The full node responds with +the header along with a proof consisting of intermediate headers that the light client can use to verify. Roughly, `VerifyBisection` would then be executed at the full node. +- We may set a timeout how long `VerifyBisection` may take. diff --git a/spec/light-client/verification/verification_001_published.md b/spec/light-client/verification/verification_001_published.md new file mode 100644 index 00000000000..90dacc7490a --- /dev/null +++ b/spec/light-client/verification/verification_001_published.md @@ -0,0 +1,1178 @@ +# Light Client Verification + +The light client implements a read operation of a +[header][TMBC-HEADER-link] from the [blockchain][TMBC-SEQ-link], by +communicating with full nodes. As some full nodes may be faulty, this +functionality must be implemented in a fault-tolerant way. + +In the Tendermint blockchain, the validator set may change with every +new block. The staking and unbonding mechanism induces a [security +model][TMBC-FM-2THIRDS-link]: starting at time *Time* of the +[header][TMBC-HEADER-link], +more than two-thirds of the next validators of a new block are correct +for the duration of *TrustedPeriod*. The fault-tolerant read +operation is designed for this security model. + +The challenge addressed here is that the light client might have a +block of height *h1* and needs to read the block of height *h2* +greater than *h1*. Checking all headers of heights from *h1* to *h2* +might be too costly (e.g., in terms of energy for mobile devices). +This specification tries to reduce the number of intermediate blocks +that need to be checked, by exploiting the guarantees provided by the +[security model][TMBC-FM-2THIRDS-link]. + +# Status + +This document is thoroughly reviewed, and the protocol has been +formalized in TLA+ and model checked. + +## Issues that need to be addressed + +As it is part of the larger light node, its data structures and +functions interact with the fork dectection functionality of the light +client. As a result of the work on +[Pull Request 479](https://github.com/informalsystems/tendermint-rs/pull/479) we +established the need for an update in the data structures in [Issue 499](https://github.com/informalsystems/tendermint-rs/issues/499). This +will not change the verification logic, but it will record information +about verification that can be used in fork detection (in particular +in computing more efficiently the proof of fork). + +# Outline + +- [Part I](#part-i---tendermint-blockchain): Introduction of + relevant terms of the Tendermint +blockchain. + +- [Part II](#part-ii---sequential-definition-of-the-verification-problem): Introduction +of the problem addressed by the Lightclient Verification protocol. + - [Verification Informal Problem + statement](#Verification-Informal-Problem-statement): For the general + audience, that is, engineers who want to get an overview over what + the component is doing from a bird's eye view. + - [Sequential Problem statement](#Sequential-Problem-statement): + Provides a mathematical definition of the problem statement in + its sequential form, that is, ignoring the distributed aspect of + the implementation of the blockchain. + +- [Part III](#part-iii---light-client-as-distributed-system): Distributed + aspects of the light client, system assumptions and temporal + logic specifications. + + - [Incentives](#incentives): how faulty full nodes may benefit from + misbehaving and how correct full nodes benefit from cooperating. + + - [Computational Model](#Computational-Model): + timing and correctness assumptions. + + - [Distributed Problem Statement](#Distributed-Problem-Statement): + temporal properties that formalize safety and liveness + properties in the distributed setting. + +- [Part IV](#part-iv---light-client-verification-protocol): + Specification of the protocols. + + - [Definitions](#Definitions): Describes inputs, outputs, + variables used by the protocol, auxiliary functions + + - [Core Verification](#core-verification): gives an outline of the solution, + and details of the functions used (with preconditions, + postconditions, error conditions). + + - [Liveness Scenarios](#liveness-scenarios): when the light + client makes progress depends heavily on the changes in the + validator sets of the blockchain. We discuss some typical scenarios. + +- [Part V](#part-v---supporting-the-ibc-relayer): The above parts + focus on a common case where the last verified block has height *h1* + and the + requested height *h2* satisfies *h2 > h1*. For IBC, there are + scenarios where this might not be the case. In this part, we provide + some preliminaries for supporting this. As not all details of the + IBC requirements are clear by now, we do not provide a complete + specification at this point. We mark with "Open Question" points + that need to be addressed in order to finalize this specification. + It should be noted that the technically + most challenging case is the one specified in Part IV. + +In this document we quite extensively use tags in order to be able to +reference assumptions, invariants, etc. in future communication. In +these tags we frequently use the following short forms: + +- TMBC: Tendermint blockchain +- SEQ: for sequential specifications +- LCV: Lightclient Verification +- LIVE: liveness +- SAFE: safety +- FUNC: function +- INV: invariant +- A: assumption + +# Part I - Tendermint Blockchain + +## Header Fields necessary for the Light Client + +#### **[TMBC-HEADER.1]** + +A set of blockchain transactions is stored in a data structure called +*block*, which contains a field called *header*. (The data structure +*block* is defined [here][block]). As the header contains hashes to +the relevant fields of the block, for the purpose of this +specification, we will assume that the blockchain is a list of +headers, rather than a list of blocks. + +#### **[TMBC-HASH-UNIQUENESS.1]** + +We assume that every hash in the header identifies the data it hashes. +Therefore, in this specification, we do not distinguish between hashes and the +data they represent. + +#### **[TMBC-HEADER-FIELDS.1]** + +A header contains the following fields: + +- `Height`: non-negative integer +- `Time`: time (integer) +- `LastBlockID`: Hashvalue +- `LastCommit` DomainCommit +- `Validators`: DomainVal +- `NextValidators`: DomainVal +- `Data`: DomainTX +- `AppState`: DomainApp +- `LastResults`: DomainRes + +#### **[TMBC-SEQ.1]** + +The Tendermint blockchain is a list *chain* of headers. + +#### **[TMBC-VALIDATOR-PAIR.1]** + +Given a full node, a +*validator pair* is a pair *(peerID, voting_power)*, where + +- *peerID* is the PeerID (public key) of a full node, +- *voting_power* is an integer (representing the full node's + voting power in a certain consensus instance). + +> In the Golang implementation the data type for *validator +pair* is called `Validator` + +#### **[TMBC-VALIDATOR-SET.1]** + +A *validator set* is a set of validator pairs. For a validator set +*vs*, we write *TotalVotingPower(vs)* for the sum of the voting powers +of its validator pairs. + +#### **[TMBC-VOTE.1]** + +A *vote* contains a `prevote` or `precommit` message sent and signed by +a validator node during the execution of [consensus][arXiv]. Each +message contains the following fields + +- `Type`: prevote or precommit +- `Height`: positive integer +- `Round` a positive integer +- `BlockID` a Hashvalue of a block (not necessarily a block of the chain) + +#### **[TMBC-COMMIT.1]** + +A commit is a set of `precommit` message. + +## Tendermint Failure Model + +#### **[TMBC-AUTH-BYZ.1]** + +We assume the authenticated Byzantine fault model in which no node (faulty or +correct) may break digital signatures, but otherwise, no additional +assumption is made about the internal behavior of faulty +nodes. That is, faulty nodes are only limited in that they cannot forge +messages. + +#### **[TMBC-TIME-PARAMS.1]** + +A Tendermint blockchain has the following configuration parameters: + +- *unbondingPeriod*: a time duration. +- *trustingPeriod*: a time duration smaller than *unbondingPeriod*. + +#### **[TMBC-CORRECT.1]** + +We define a predicate *correctUntil(n, t)*, where *n* is a node and *t* is a +time point. +The predicate *correctUntil(n, t)* is true if and only if the node *n* +follows all the protocols (at least) until time *t*. + +#### **[TMBC-FM-2THIRDS.1]** + +If a block *h* is in the chain, +then there exists a subset *CorrV* +of *h.NextValidators*, such that: + +- *TotalVotingPower(CorrV) > 2/3 + TotalVotingPower(h.NextValidators)*; cf. [TMBC-VALIDATOR-SET.1] +- For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n, + h.Time + trustingPeriod)*; cf. [TMBC-CORRECT.1] + +> The definition of correct +> [**[TMBC-CORRECT.1]**][TMBC-CORRECT-link] refers to realtime, while it +> is used here with *Time* and *trustingPeriod*, which are "hardware +> times". We do not make a distinction here. + +#### **[TMBC-CORR-FULL.1]** + +Every correct full node locally stores a prefix of the +current list of headers from [**[TMBC-SEQ.1]**][TMBC-SEQ-link]. + +## What the Light Client Checks + +> From [TMBC-FM-2THIRDS.1] we directly derive the following observation: + +#### **[TMBC-VAL-CONTAINS-CORR.1]** + +Given a (trusted) block *tb* of the blockchain, a given set of full nodes +*N* contains a correct node at a real-time *t*, if + +- *t - trustingPeriod < tb.Time < t* +- the voting power in tb.NextValidators of nodes in *N* is more + than 1/3 of *TotalVotingPower(tb.NextValidators)* + +> The following describes how a commit for a given block *b* must look +> like. + +#### **[TMBC-SOUND-DISTR-POSS-COMMIT.1]** + +For a block *b*, each element *pc* of *PossibleCommit(b)* satisfies: + +- *pc* contains only votes (cf. [TMBC-VOTE.1]) + by validators from *b.Validators* +- the sum of the voting powers in *pc* is greater than 2/3 + *TotalVotingPower(b.Validators)* +- and there is an *r* such that each vote *v* in *pc* satisfies + - v.Type = precommit + - v.Height = b.Height + - v.Round = r + - v.blockID = hash(b) + +> The following property comes from the validity of the [consensus][arXiv]: A +> correct validator node only sends `prevote` or `precommit`, if +> `BlockID` of the new (to-be-decided) block is equal to the hash of +> the last block. + +#### **[TMBC-VAL-COMMIT.1]** + +If for a block *b*, a commit *c* + +- contains at least one validator pair *(v,p)* such that *v* is a + **correct** validator node, and +- is contained in *PossibleCommit(b)* + +then the block *b* is on the blockchain. + +## Context of this document + +In this document we specify the light client verification component, +called *Core Verification*. The *Core Verification* communicates with +a full node. As full nodes may be faulty, it cannot trust the +received information, but the light client has to check whether the +header it receives coincides with the one generated by Tendermint +consensus. + +The two + properties [[TMBC-VAL-CONTAINS-CORR.1]][TMBC-VAL-CONTAINS-CORR-link] and +[[TMBC-VAL-COMMIT]][TMBC-VAL-COMMIT-link] formalize the checks done + by this specification: +Given a trusted block *tb* and an untrusted block *ub* with a commit *cub*, +one has to check that *cub* is in *PossibleCommit(ub)*, and that *cub* +contains a correct node using *tb*. + +# Part II - Sequential Definition of the Verification Problem + +## Verification Informal Problem statement + +Given a height *targetHeight* as an input, the *Verifier* eventually +stores a header *h* of height *targetHeight* locally. This header *h* +is generated by the Tendermint [blockchain][block]. In +particular, a header that was not generated by the blockchain should +never be stored. + +## Sequential Problem statement + +#### **[LCV-SEQ-LIVE.1]** + +The *Verifier* gets as input a height *targetHeight*, and eventually stores the +header of height *targetHeight* of the blockchain. + +#### **[LCV-SEQ-SAFE.1]** + +The *Verifier* never stores a header which is not in the blockchain. + +# Part III - Light Client as Distributed System + +## Incentives + +Faulty full nodes may benefit from lying to the light client, by making the +light client accept a block that deviates (e.g., contains additional +transactions) from the one generated by Tendermint consensus. +Users using the light client might be harmed by accepting a forged header. + +The [fork detector][fork-detector] of the light client may help the +correct full nodes to understand whether their header is a good one. +Hence, in combination with the light client detector, the correct full +nodes have the incentive to respond. We can thus base liveness +arguments on the assumption that correct full nodes reliably talk to +the light client. + +## Computational Model + +#### **[LCV-A-PEER.1]** + +The verifier communicates with a full node called *primary*. No assumption is made about the full node (it may be correct or faulty). + +#### **[LCV-A-COMM.1]** + +Communication between the light client and a correct full node is +reliable and bounded in time. Reliable communication means that +messages are not lost, not duplicated, and eventually delivered. There +is a (known) end-to-end delay *Delta*, such that if a message is sent +at time *t* then it is received and processes by time *t + Delta*. +This implies that we need a timeout of at least *2 Delta* for remote +procedure calls to ensure that the response of a correct peer arrives +before the timeout expires. + +#### **[LCV-A-TFM.1]** + +The Tendermint blockchain satisfies the Tendermint failure model [**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link]. + +#### **[LCV-A-VAL.1]** + +The system satisfies [**[TMBC-AUTH-BYZ.1]**][TMBC-Auth-Byz-link] and +[**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link]. Thus, there is a +blockchain that satisfies the soundness requirements (that is, the +validation rules in [[block]]). + +## Distributed Problem Statement + +### Two Kinds of Termination + +We do not assume that *primary* is correct. Under this assumption no +protocol can guarantee the combination of the sequential +properties. Thus, in the (unreliable) distributed setting, we consider +two kinds of termination (successful and failure) and we will specify +below under what (favorable) conditions *Core Verification* ensures to +terminate successfully, and satisfy the requirements of the sequential +problem statement: + +#### **[LCV-DIST-TERM.1]** + +*Core Verification* either *terminates +successfully* or it *terminates with failure*. + +### Design choices + +#### **[LCV-DIST-STORE.1]** + +*Core Verification* has a local data structure called *LightStore* that +contains light blocks (that contain a header). For each light block we +record whether it is verified. + +#### **[LCV-DIST-PRIMARY.1]** + +*Core Verification* has a local variable *primary* that contains the PeerID of a full node. + +#### **[LCV-DIST-INIT.1]** + +*LightStore* is initialized with a header *trustedHeader* that was correctly +generated by the Tendermint consensus. We say *trustedHeader* is verified. + +### Temporal Properties + +#### **[LCV-DIST-SAFE.1]** + +It is always the case that every verified header in *LightStore* was +generated by an instance of Tendermint consensus. + +#### **[LCV-DIST-LIVE.1]** + +From time to time, a new instance of *Core Verification* is called with a +height *targetHeight* greater than the height of any header in *LightStore*. +Each instance must eventually terminate. + +- If + - the *primary* is correct (and locally has the block of + *targetHeight*), and + - *LightStore* always contains a verified header whose age is less than the + trusting period, + then *Core Verification* adds a verified header *hd* with height + *targetHeight* to *LightStore* and it **terminates successfully** + +> These definitions imply that if the primary is faulty, a header may or +> may not be added to *LightStore*. In any case, +> [**[LCV-DIST-SAFE.1]**](#lcv-vc-inv) must hold. +> The invariant [**[LCV-DIST-SAFE.1]**](#lcv-dist-safe) and the liveness +> requirement [**[LCV-DIST-LIVE.1]**](#lcv-dist-life) +> allow that verified headers are added to *LightStore* whose +> height was not passed +> to the verifier (e.g., intermediate headers used in bisection; see below). +> Note that for liveness, initially having a *trustedHeader* within +> the *trustinPeriod* is not sufficient. However, as this +> specification will leave some freedom with respect to the strategy +> in which order to download intermediate headers, we do not give a +> more precise liveness specification here. After giving the +> specification of the protocol, we will discuss some liveness +> scenarios [below](#liveness-scenarios). + +### Solving the sequential specification + +This specification provides a partial solution to the sequential specification. +The *Verifier* solves the invariant of the sequential part + +[**[LCV-DIST-SAFE.1]**](#lcv-vc-inv) => [**[LCV-SEQ-SAFE.1]**](#lcv-seq-inv) + +In the case the primary is correct, and there is a recent header in *LightStore*, the verifier satisfies the liveness requirements. + +⋀ *primary is correct* +⋀ always ∃ verified header in LightStore. *header.Time* > *now* - *trustingPeriod* +⋀ [**[LCV-A-Comm.1]**](#lcv-a-comm) ⋀ ( + ( [**[TMBC-CorrFull.1]**][TMBC-CorrFull-link] ⋀ + [**[LCV-DIST-LIVE.1]**](#lcv-vc-live) ) + ⟹ [**[LCV-SEQ-LIVE.1]**](#lcv-seq-live) +) + +# Part IV - Light Client Verification Protocol + +We provide a specification for Light Client Verification. The local +code for verification is presented by a sequential function +`VerifyToTarget` to highlight the control flow of this functionality. +We note that if a different concurrency model is considered for +an implementation, the sequential flow of the function may be +implemented with mutexes, etc. However, the light client verification +is partitioned into three blocks that can be implemented and tested +independently: + +- `FetchLightBlock` is called to download a light block (header) of a + given height from a peer. +- `ValidAndVerified` is a local code that checks the header. +- `Schedule` decides which height to try to verify next. We keep this + underspecified as different implementations (currently in Goland and + Rust) may implement different optimizations here. We just provide + necessary conditions on how the height may evolve. + + + + +## Definitions + +### Data Types + +The core data structure of the protocol is the LightBlock. + +#### **[LCV-DATA-LIGHTBLOCK.1]** + +```go +type LightBlock struct { + Header Header + Commit Commit + Validators ValidatorSet +} +``` + +#### **[LCV-DATA-LIGHTSTORE.1]** + +LightBlocks are stored in a structure which stores all LightBlock from +initialization or received from peers. + +```go +type LightStore struct { + ... +} + +``` + +Each LightBlock is in one of the following states: + +```go +type VerifiedState int + +const ( + StateUnverified = iota + 1 + StateVerified + StateFailed + StateTrusted +) +``` + +> Only the detector module sets a lightBlock state to `StateTrusted` +> and only if it was `StateVerified` before. + +The LightStore exposes the following functions to query stored LightBlocks. + +#### **[LCV-FUNC-GET.1]** + +```go +func (ls LightStore) Get(height Height) (LightBlock, bool) +``` + +- Expected postcondition + - returns a LightBlock at a given height or false in the second argument if + the LightStore does not contain the specified LightBlock. + +#### **[LCV-FUNC-LATEST-VERIF.1]** + +```go +func (ls LightStore) LatestVerified() LightBlock +``` + +- Expected postcondition + - returns the highest light block whose state is `StateVerified` + or `StateTrusted` + +#### **[LCV-FUNC-UPDATE.2]** + +```go +func (ls LightStore) Update(lightBlock LightBlock, + verfiedState VerifiedState + verifiedBy Height) +``` + +- Expected postcondition + - The state of the LightBlock is set to *verifiedState*. + - verifiedBy of the Lightblock is set to *Height* + +> The following function is used only in the detector specification +> listed here for completeness. + +#### **[LCV-FUNC-LATEST-TRUSTED.1]** + +```go +func (ls LightStore) LatestTrusted() LightBlock +``` + +- Expected postcondition + - returns the highest light block that has been verified and + checked by the detector. + +#### **[LCV-FUNC-FILTER.1]** + +```go +func (ls LightStore) FilterVerified() LightSTore +``` + +- Expected postcondition + - returns only the LightBlocks with state verified. + +### Inputs + +- *lightStore*: stores light blocks that have been downloaded and that + passed verification. Initially it contains a light block with + *trustedHeader*. +- *primary*: peerID +- *targetHeight*: the height of the needed header + +### Configuration Parameters + +- *trustThreshold*: a float. Can be used if correctness should not be based on more voting power and 1/3. +- *trustingPeriod*: a time duration [**[TMBC-TIME_PARAMS.1]**][TMBC-TIME_PARAMS-link]. +- *clockDrift*: a time duration. Correction parameter dealing with only approximately synchronized clocks. + +### Variables + +- *nextHeight*: initially *targetHeight* + > *nextHeight* should be thought of the "height of the next header we need + > to download and verify" + +### Assumptions + +#### **[LCV-A-INIT.1]** + +- *trustedHeader* is from the blockchain + +- *targetHeight > LightStore.LatestVerified.Header.Height* + +### Invariants + +#### **[LCV-INV-TP.1]** + +It is always the case that *LightStore.LatestTrusted.Header.Time > now - trustingPeriod*. + +> If the invariant is violated, the light client does not have a +> header it can trust. A trusted header must be obtained externally, +> its trust can only be based on social consensus. + +### Used Remote Functions + +We use the functions `commit` and `validators` that are provided +by the [RPC client for Tendermint][RPC]. + +```go +func Commit(height int64) (SignedHeader, error) +``` + +- Implementation remark + - RPC to full node *n* + - JSON sent: + +```javascript +// POST /commit +{ + "jsonrpc": "2.0", + "id": "ccc84631-dfdb-4adc-b88c-5291ea3c2cfb", // UUID v4, unique per request + "method": "commit", + "params": { + "height": 1234 + } +} +``` + +- Expected precondition + - header of `height` exists on blockchain +- Expected postcondition + - if *n* is correct: Returns the signed header of height `height` + from the blockchain if communication is timely (no timeout) + - if *n* is faulty: Returns a signed header with arbitrary content +- Error condition + - if *n* is correct: precondition violated or timeout + - if *n* is faulty: arbitrary error + +---- + +```go +func Validators(height int64) (ValidatorSet, error) +``` + +- Implementation remark + - RPC to full node *n* + - JSON sent: + +```javascript +// POST /validators +{ + "jsonrpc": "2.0", + "id": "ccc84631-dfdb-4adc-b88c-5291ea3c2cfb", // UUID v4, unique per request + "method": "validators", + "params": { + "height": 1234 + } +} +``` + +- Expected precondition + - header of `height` exists on blockchain +- Expected postcondition + - if *n* is correct: Returns the validator set of height `height` + from the blockchain if communication is timely (no timeout) + - if *n* is faulty: Returns arbitrary validator set +- Error condition + - if *n* is correct: precondition violated or timeout + - if *n* is faulty: arbitrary error + +---- + +### Communicating Function + +#### **[LCV-FUNC-FETCH.1]** + + ```go +func FetchLightBlock(peer PeerID, height Height) LightBlock +``` + +- Implementation remark + - RPC to peer at *PeerID* + - calls `Commit` for *height* and `Validators` for *height* and *height+1* +- Expected precondition + - `height` is less than or equal to height of the peer **[LCV-IO-PRE-HEIGHT.1]** +- Expected postcondition: + - if *node* is correct: + - Returns the LightBlock *lb* of height `height` + that is consistent with the blockchain + - *lb.provider = peer* **[LCV-IO-POST-PROVIDER.1]** + - *lb.Header* is a header consistent with the blockchain + - *lb.Validators* is the validator set of the blockchain at height *nextHeight* + - *lb.NextValidators* is the validator set of the blockchain at height *nextHeight + 1* + - if *node* is faulty: Returns a LightBlock with arbitrary content + [**[TMBC-AUTH-BYZ.1]**][TMBC-Auth-Byz-link] +- Error condition + - if *n* is correct: precondition violated + - if *n* is faulty: arbitrary error + - if *lb.provider != peer* + - times out after 2 Delta (by assumption *n* is faulty) + +---- + +## Core Verification + +### Outline + +The `VerifyToTarget` is the main function and uses the following functions. + +- `FetchLightBlock` is called to download the next light block. It is + the only function that communicates with other nodes +- `ValidAndVerified` checks whether header is valid and checks if a + new lightBlock should be trusted + based on a previously verified lightBlock. +- `Schedule` decides which height to try to verify next + +In the following description of `VerifyToTarget` we do not deal with error +handling. If any of the above function returns an error, VerifyToTarget just +passes the error on. + +#### **[LCV-FUNC-MAIN.1]** + +```go +func VerifyToTarget(primary PeerID, lightStore LightStore, + targetHeight Height) (LightStore, Result) { + + nextHeight := targetHeight + + for lightStore.LatestVerified.height < targetHeight { + + // Get next LightBlock for verification + current, found := lightStore.Get(nextHeight) + if !found { + current = FetchLightBlock(primary, nextHeight) + lightStore.Update(current, StateUnverified) + } + + // Verify + verdict = ValidAndVerified(lightStore.LatestVerified, current) + + // Decide whether/how to continue + if verdict == SUCCESS { + lightStore.Update(current, StateVerified) + } + else if verdict == NOT_ENOUGH_TRUST { + // do nothing + // the light block current passed validation, but the validator + // set is too different to verify it. We keep the state of + // current at StateUnverified. For a later iteration, Schedule + // might decide to try verification of that light block again. + } + else { + // verdict is some error code + lightStore.Update(current, StateFailed) + // possibly remove all LightBlocks from primary + return (lightStore, ResultFailure) + } + nextHeight = Schedule(lightStore, nextHeight, targetHeight) + } + return (lightStore, ResultSuccess) +} +``` + +- Expected precondition + - *lightStore* contains a LightBlock within the *trustingPeriod* **[LCV-PRE-TP.1]** + - *targetHeight* is greater than the height of all the LightBlocks in *lightStore* +- Expected postcondition: + - returns *lightStore* that contains a LightBlock that corresponds to a block + of the blockchain of height *targetHeight* + (that is, the LightBlock has been added to *lightStore*) **[LCV-POST-LS.1]** +- Error conditions + - if the precondition is violated + - if `ValidAndVerified` or `FetchLightBlock` report an error + - if [**[LCV-INV-TP.1]**](#LCV-INV-TP.1) is violated + +### Details of the Functions + +#### **[LCV-FUNC-VALID.1]** + +```go +func ValidAndVerified(trusted LightBlock, untrusted LightBlock) Result +``` + +- Expected precondition: + - *untrusted* is valid, that is, satisfies the soundness [checks][block] + - *untrusted* is **well-formed**, that is, + - *untrusted.Header.Time < now + clockDrift* + - *untrusted.Validators = hash(untrusted.Header.Validators)* + - *untrusted.NextValidators = hash(untrusted.Header.NextValidators)* + - *trusted.Header.Time > now - trustingPeriod* + - *trusted.Commit* is a commit for the header + *trusted.Header*, i.e., it contains + the correct hash of the header, and +2/3 of signatures + - the `Height` and `Time` of `trusted` are smaller than the Height and + `Time` of `untrusted`, respectively + - the *untrusted.Header* is well-formed (passes the tests from + [[block]]), and in particular + - if the untrusted header `unstrusted.Header` is the immediate + successor of `trusted.Header`, then it holds that + - *trusted.Header.NextValidators = + untrusted.Header.Validators*, and + moreover, + - *untrusted.Header.Commit* + - contains signatures by more than two-thirds of the validators + - contains no signature from nodes that are not in *trusted.Header.NextValidators* +- Expected postcondition: + - Returns `SUCCESS`: + - if *untrusted* is the immediate successor of *trusted*, or otherwise, + - if the signatures of a set of validators that have more than + *max(1/3,trustThreshold)* of voting power in + *trusted.Nextvalidators* is contained in + *untrusted.Commit* (that is, header passes the tests + [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] + and [**[TMBC-VAL-COMMIT.1]**][TMBC-VAL-COMMIT-link]) + - Returns `NOT_ENOUGH_TRUST` if: + - *untrusted* is *not* the immediate successor of + *trusted* + and the *max(1/3,trustThreshold)* threshold is not reached + (that is, if + [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] + fails and header is does not violate the soundness + checks [[block]]). +- Error condition: + - if precondition violated + +---- + +#### **[LCV-FUNC-SCHEDULE.1]** + +```go +func Schedule(lightStore, nextHeight, targetHeight) Height +``` + +- Implementation remark: If picks the next height to be verified. + We keep the precise choice of the next header under-specified. It is + subject to performance optimizations that do not influence the correctness +- Expected postcondition: **[LCV-SCHEDULE-POST.1]** + Return *H* s.t. + 1. if *lightStore.LatestVerified.Height = nextHeight* and + *lightStore.LatestVerified < targetHeight* then + *nextHeight < H <= targetHeight* + 2. if *lightStore.LatestVerified.Height < nextHeight* and + *lightStore.LatestVerified.Height < targetHeight* then + *lightStore.LatestVerified.Height < H < nextHeight* + 3. if *lightStore.LatestVerified.Height = targetHeight* then + *H = targetHeight* + +> Case i. captures the case where the light block at height *nextHeight* +> has been verified, and we can choose a height closer to the *targetHeight*. +> As we get the *lightStore* as parameter, the choice of the next height can +> depend on the *lightStore*, e.g., we can pick a height for which we have +> already downloaded a light block. +> In Case ii. the header of *nextHeight* could not be verified, and we need to pick a smaller height. +> In Case iii. is a special case when we have verified the *targetHeight*. + +### Solving the distributed specification + +*trustedStore* is implemented by the light blocks in lightStore that +have the state *StateVerified*. + +#### Argument for [**[LCV-DIST-SAFE.1]**](#lcv-dist-safe) + +- `ValidAndVerified` implements the soundness checks and the checks + [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] and + [**[TMBC-VAL-COMMIT.1]**][TMBC-VAL-COMMIT-link] under + the assumption [**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link] +- Only if `ValidAndVerified` returns with `SUCCESS`, the state of a light block is + set to *StateVerified*. + +#### Argument for [**[LCV-DIST-LIVE.1]**](#lcv-dist-life) + +- If *primary* is correct, + - `FetchLightBlock` will always return a light block consistent + with the blockchain + - `ValidAndVerified` either verifies the header using the trusting + period or falls back to sequential + verification + - If [**[LCV-INV-TP.1]**](#LCV-INV-TP.1) holds, eventually every + header will be verified and core verification **terminates successfully**. + - successful termination depends on the age of *lightStore.LatestVerified* + (for instance, initially on the age of *trustedHeader*) and the + changes of the validator sets on the blockchain. + We will give some examples [below](#liveness-scenarios). +- If *primary* is faulty, + - it either provides headers that pass all the tests, and we + return with the header + - it provides one header that fails a test, core verification + **terminates with failure**. + - it times out and core verification + **terminates with failure**. + +## Liveness Scenarios + +The liveness argument above assumes [**[LCV-INV-TP.1]**](#LCV-INV-TP.1) + +which requires that there is a header that does not expire before the +target height is reached. Here we discuss scenarios to ensure this. + +Let *startHeader* be *LightStore.LatestVerified* when core +verification is called (*trustedHeader*) and *startTime* be the time +core verification is invoked. + +In order to ensure liveness, *LightStore* always needs to contain a +verified (or initially trusted) header whose time is within the +trusting period. To ensure this, core verification needs to add new +headers to *LightStore* and verify them, before all headers in +*LightStore* expire. + +#### Many changes in validator set + + Let's consider `Schedule` implements + bisection, that is, it halves the distance. + Assume the case where the validator set changes completely in each +block. Then the + method in this specification needs to +sequentially verify all headers. That is, for + +- *W = log_2 (targetHeight - startHeader.Height)*, + +*W* headers need to be downloaded and checked before the +header of height *startHeader.Height + 1* is added to *LightStore*. + +- Let *Comp* + be the local computation time needed to check headers and signatures + for one header. +- Then we need in the worst case *Comp + 2 Delta* to download and + check one header. +- Then the first time a verified header could be added to *LightStore* is + startTime + W * (Comp + 2 Delta) +- [TP.1] However, it can only be added if we still have a header in + *LightStore*, + which is not + expired, that is only the case if + - startHeader.Time > startTime + WCG * (Comp + 2 Delta) - + trustingPeriod, + - that is, if core verification is started at + startTime < startHeader.Time + trustingPeriod - WCG * (Comp + 2 Delta) + +- one may then do an inductive argument from this point on, depending + on the implementation of `Schedule`. We may have to account for the + headers that are already + downloaded, but they are checked against the new *LightStore.LatestVerified*. + +> We observe that +> the worst case time it needs to verify the header of height +> *targetHeight* depends mainly on how frequent the validator set on the +> blockchain changes. That core verification terminates successfully +> crucially depends on the check [TP.1], that is, that the headers in +> *LightStore* do not expire in the time needed to download more +> headers, which depends on the creation time of the headers in +> *LightStore*. That is, termination of core verification is highly +> depending on the data stored in the blockchain. +> The current light client core verification protocol exploits that, in +> practice, changes in the validator set are rare. For instance, +> consider the following scenario. + +#### No change in validator set + +If on the blockchain the validator set of the block at height +*targetHeight* is equal to *startHeader.NextValidators*: + +- there is one round trip in `FetchLightBlock` to download the light + block + of height + *targetHeight*, and *Comp* to check it. +- as the validator sets are equal, `Verify` returns `SUCCESS`, if + *startHeader.Time > now - trustingPeriod*. +- that is, if *startTime < startHeader.Header.Time + trustingPeriod - + 2 Delta - Comp*, then core verification terminates successfully + +# Part V - Supporting the IBC Relayer + +The above specification focuses on the most common case, which also +constitutes the most challenging task: using the Tendermint [security +model][TMBC-FM-2THIRDS-link] to verify light blocks without +downloading all intermediate blocks. To focus on this challenge, above +we have restricted ourselves to the case where *targetHeight* is +greater than the height of any trusted header. This simplified +presentation of the algorithm as initially +`lightStore.LatestVerified()` is less than *targetHeight*, and in the +process of verification `lightStore.LatestVerified()` increases until +*targetHeight* is reached. + +For [IBC][ibc-rs] it might be that some "older" header is +needed, that is, *targetHeight < lightStore.LatestVerified()*. In this section we present a preliminary design, and we mark some +remaining open questions. +If *targetHeight < lightStore.LatestVerified()* our design separates +the following cases: + +- A previous instance of `VerifyToTarget` has already downloaded the + light block of *targetHeight*. There are two cases + - the light block has been verified + - the light block has not been verified yet +- No light block of *targetHeight* had been downloaded before. There + are two cases: + - there exists a verified light block of height less than *targetHeight* + - otherwise. In this case we need to do "backwards verification" + using the hash of the previous block in the `LastBlockID` field + of a header. + +**Open Question:** what are the security assumptions for backward +verification. Should we check that the light block we verify from +(and/or the checked light block) is within the trusting period? + +The design just presents the above case +distinction as a function, and defines some auxiliary functions in the +same way the protocol was presented in +[Part IV](#part-iv---light-client-verification-protocol). + +```go +func (ls LightStore) LatestPrevious(height Height) (LightBlock, bool) +``` + +- Expected postcondition + - returns a light block *lb* that satisfies: + - *lb* is in lightStore + - *lb* is verified and not expired + - *lb.Header.Height < height* + - for all *b* in lightStore s.t. *b* is verified and not expired it + holds *lb.Header.Height >= b.Header.Height* + - *false* in the second argument if + the LightStore does not contain such an *lb*. + +```go +func (ls LightStore) MinVerified() (LightBlock, bool) +``` + +- Expected postcondition + - returns a light block *lb* that satisfies: + - *lb* is in lightStore + - *lb* is verified **Open Question:** replace by trusted? + - *lb.Header.Height* is minimal in the lightStore + - **Open Question:** according to this, it might be expired (outside the + trusting period). This approach appears safe. Are there reasons we + should not do that? + - *false* in the second argument if + the LightStore does not contain such an *lb*. + +If a height that is smaller than the smallest height in the lightstore +is required, we check the hashes backwards. This is done with the +following function: + +#### **[LCV-FUNC-BACKWARDS.1]** + +```go +func Backwards (primary PeerID, lightStore LightStore, targetHeight Height) + (LightStore, Result) { + + lb,res = lightStore.MinVerified() + if res = false { + return (lightStore, ResultFailure) + } + + latest := lb.Header + for i := lb.Header.height - 1; i >= targetHeight; i-- { + // here we download height-by-height. We might first download all + // headers down to targetHeight and then check them. + current := FetchLightBlock(primary,i) + if (hash(current) != latest.Header.LastBlockId) { + return (lightStore, ResultFailure) + } + else { + lightStore.Update(current, StateVerified) + // **Open Question:** Do we need a new state type for + // backwards verified light blocks? + } + latest = current + } + return (lightStore, ResultSuccess) +} +``` + +The following function just decided based on the required height which +method should be used. + +#### **[LCV-FUNC-IBCMAIN.1]** + +```go +func Main (primary PeerID, lightStore LightStore, targetHeight Height) + (LightStore, Result) { + + b1, r1 = lightStore.Get(targetHeight) + if r1 = true and b1.State = StateVerified { + // block already there + return (lightStore, ResultSuccess) + } + + if targetHeight > lightStore.LatestVerified.height { + // case of Part IV + return VerifyToTarget(primary, lightStore, targetHeight) + } + else { + b2, r2 = lightStore.LatestPrevious(targetHeight); + if r2 = true { + // make auxiliary lightStore auxLS to call VerifyToTarget. + // VerifyToTarget uses LatestVerified of the given lightStore + // For that we need: + // auxLS.LatestVerified = lightStore.LatestPrevious(targetHeight) + auxLS.Init; + auxLS.Update(b2,StateVerified); + if r1 = true { + // we need to verify a previously downloaded light block. + // we add it to the auxiliary store so that VerifyToTarget + // does not download it again + auxLS.Update(b1,b1.State); + } + auxLS, res2 = VerifyToTarget(primary, auxLS, targetHeight) + // move all lightblocks from auxLS to lightStore, + // maintain state + // we do that whether VerifyToTarget was successful or not + for i, s range auxLS { + lighStore.Update(s,s.State) + } + return (lightStore, res2) + } + else { + return Backwards(primary, lightStore, targetHeight) + } + } +} +``` + + + + + + + + + + + + + + + + + + + +# References + +[[block]] Specification of the block data structure. + +[[RPC]] RPC client for Tendermint + +[[fork-detector]] The specification of the light client fork detector. + +[[fullnode]] Specification of the full node API + +[[ibc-rs]] Rust implementation of IBC modules and relayer. + +[[lightclient]] The light client ADR [77d2651 on Dec 27, 2019]. + +[RPC]: https://docs.tendermint.com/master/rpc/ + +[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md + +[TMBC-HEADER-link]: #tmbc-header1 +[TMBC-SEQ-link]: #tmbc-seq1 +[TMBC-CorrFull-link]: #tmbc-corr-full1 +[TMBC-Auth-Byz-link]: #tmbc-auth-byz1 +[TMBC-TIME_PARAMS-link]: #tmbc-time-params1 +[TMBC-FM-2THIRDS-link]: #tmbc-fm-2thirds1 +[TMBC-VAL-CONTAINS-CORR-link]: #tmbc-val-contains-corr1 +[TMBC-VAL-COMMIT-link]: #tmbc-val-commit1 +[TMBC-SOUND-DISTR-POSS-COMMIT-link]: #tmbc-sound-distr-poss-commit1 + +[lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md +[fork-detector]: https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/detection.md +[fullnode]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md + +[ibc-rs]:https://github.com/informalsystems/ibc-rs + +[FN-LuckyCase-link]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#fn-luckycase + +[blockchain-validator-set]: https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.md#data-structures +[fullnode-data-structures]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#data-structures + +[FN-ManifestFaulty-link]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#fn-manifestfaulty + +[arXiv]: https://arxiv.org/abs/1807.04938 \ No newline at end of file diff --git a/spec/light-client/verification/verification_002_draft.md b/spec/light-client/verification/verification_002_draft.md new file mode 100644 index 00000000000..9047eb4171f --- /dev/null +++ b/spec/light-client/verification/verification_002_draft.md @@ -0,0 +1,1061 @@ +# Light Client Verification + +The light client implements a read operation of a +[header][TMBC-HEADER-link] from the [blockchain][TMBC-SEQ-link], by +communicating with full nodes. As some full nodes may be faulty, this +functionality must be implemented in a fault-tolerant way. + +In the Tendermint blockchain, the validator set may change with every +new block. The staking and unbonding mechanism induces a [security +model][TMBC-FM-2THIRDS-link]: starting at time *Time* of the +[header][TMBC-HEADER-link], +more than two-thirds of the next validators of a new block are correct +for the duration of *TrustedPeriod*. The fault-tolerant read +operation is designed for this security model. + +The challenge addressed here is that the light client might have a +block of height *h1* and needs to read the block of height *h2* +greater than *h1*. Checking all headers of heights from *h1* to *h2* +might be too costly (e.g., in terms of energy for mobile devices). +This specification tries to reduce the number of intermediate blocks +that need to be checked, by exploiting the guarantees provided by the +[security model][TMBC-FM-2THIRDS-link]. + +# Status + +## Previous Versions + +- [[001_published]](./verification_001_published.md) + is thoroughly reviewed, and the protocol has been +formalized in TLA+ and model checked. + +## Issues that are addressed in this revision + +As it is part of the larger light node, its data structures and +functions interact with the attack dectection functionality of the light +client. As a result of the work on + +- [attack detection](https://github.com/tendermint/spec/pull/164) for light nodes + +- attack detection for IBC and [relayer requirements](https://github.com/informalsystems/tendermint-rs/issues/497) + +- light client + [supervisor](https://github.com/tendermint/spec/pull/159) (also in + [Rust proposal](https://github.com/informalsystems/tendermint-rs/pull/509)) + +adaptations to the semantics and functions exposed by the LightStore +needed to be made. In contrast to [version +001](./verification_001_published.md) we specify the following: + +- `VerifyToTarget` and `Backwards` are called with a single lightblock + as root of trust in contrast to passing the complete lightstore. + +- During verification, we record for each lightblock which other + lightblock can be used to verify it in one step. This is needed to + generate verification traces that are needed for IBC. + +# Outline + +- [Part I](#part-i---tendermint-blockchain): Introduction of + relevant terms of the Tendermint +blockchain. + +- [Part II](#part-ii---sequential-definition-of-the-verification-problem): Introduction +of the problem addressed by the Lightclient Verification protocol. + - [Verification Informal Problem + statement](#Verification-Informal-Problem-statement): For the general + audience, that is, engineers who want to get an overview over what + the component is doing from a bird's eye view. + - [Sequential Problem statement](#Sequential-Problem-statement): + Provides a mathematical definition of the problem statement in + its sequential form, that is, ignoring the distributed aspect of + the implementation of the blockchain. + +- [Part III](#part-iii---light-client-as-distributed-system): Distributed + aspects of the light client, system assumptions and temporal + logic specifications. + + - [Incentives](#incentives): how faulty full nodes may benefit from + misbehaving and how correct full nodes benefit from cooperating. + + - [Computational Model](#Computational-Model): + timing and correctness assumptions. + + - [Distributed Problem Statement](#Distributed-Problem-Statement): + temporal properties that formalize safety and liveness + properties in the distributed setting. + +- [Part IV](#part-iv---light-client-verification-protocol): + Specification of the protocols. + + - [Definitions](#Definitions): Describes inputs, outputs, + variables used by the protocol, auxiliary functions + + - [Core Verification](#core-verification): gives an outline of the solution, + and details of the functions used (with preconditions, + postconditions, error conditions). + + - [Liveness Scenarios](#liveness-scenarios): when the light + client makes progress depends heavily on the changes in the + validator sets of the blockchain. We discuss some typical scenarios. + +- [Part V](#part-v---supporting-the-ibc-relayer): The above parts + focus on a common case where the last verified block has height *h1* + and the + requested height *h2* satisfies *h2 > h1*. For IBC, there are + scenarios where this might not be the case. In this part, we provide + some preliminaries for supporting this. As not all details of the + IBC requirements are clear by now, we do not provide a complete + specification at this point. We mark with "Open Question" points + that need to be addressed in order to finalize this specification. + It should be noted that the technically + most challenging case is the one specified in Part IV. + +In this document we quite extensively use tags in order to be able to +reference assumptions, invariants, etc. in future communication. In +these tags we frequently use the following short forms: + +- TMBC: Tendermint blockchain +- SEQ: for sequential specifications +- LCV: Lightclient Verification +- LIVE: liveness +- SAFE: safety +- FUNC: function +- INV: invariant +- A: assumption + +# Part I - Tendermint Blockchain + +## Header Fields necessary for the Light Client + +#### **[TMBC-HEADER.1]** + +A set of blockchain transactions is stored in a data structure called +*block*, which contains a field called *header*. (The data structure +*block* is defined [here][block]). As the header contains hashes to +the relevant fields of the block, for the purpose of this +specification, we will assume that the blockchain is a list of +headers, rather than a list of blocks. + +#### **[TMBC-HASH-UNIQUENESS.1]** + +We assume that every hash in the header identifies the data it hashes. +Therefore, in this specification, we do not distinguish between hashes and the +data they represent. + +#### **[TMBC-HEADER-FIELDS.2]** + +A header contains the following fields: + +- `Height`: non-negative integer +- `Time`: time (non-negative integer) +- `LastBlockID`: Hashvalue +- `LastCommit` DomainCommit +- `Validators`: DomainVal +- `NextValidators`: DomainVal +- `Data`: DomainTX +- `AppState`: DomainApp +- `LastResults`: DomainRes + +#### **[TMBC-SEQ.1]** + +The Tendermint blockchain is a list *chain* of headers. + +#### **[TMBC-VALIDATOR-PAIR.1]** + +Given a full node, a +*validator pair* is a pair *(peerID, voting_power)*, where + +- *peerID* is the PeerID (public key) of a full node, +- *voting_power* is an integer (representing the full node's + voting power in a certain consensus instance). + +> In the Golang implementation the data type for *validator +pair* is called `Validator` + +#### **[TMBC-VALIDATOR-SET.1]** + +A *validator set* is a set of validator pairs. For a validator set +*vs*, we write *TotalVotingPower(vs)* for the sum of the voting powers +of its validator pairs. + +#### **[TMBC-VOTE.1]** + +A *vote* contains a `prevote` or `precommit` message sent and signed by +a validator node during the execution of [consensus][arXiv]. Each +message contains the following fields + +- `Type`: prevote or precommit +- `Height`: positive integer +- `Round` a positive integer +- `BlockID` a Hashvalue of a block (not necessarily a block of the chain) + +#### **[TMBC-COMMIT.1]** + +A commit is a set of `precommit` message. + +## Tendermint Failure Model + +#### **[TMBC-AUTH-BYZ.1]** + +We assume the authenticated Byzantine fault model in which no node (faulty or +correct) may break digital signatures, but otherwise, no additional +assumption is made about the internal behavior of faulty +nodes. That is, faulty nodes are only limited in that they cannot forge +messages. + +#### **[TMBC-TIME-PARAMS.1]** + +A Tendermint blockchain has the following configuration parameters: + +- *unbondingPeriod*: a time duration. +- *trustingPeriod*: a time duration smaller than *unbondingPeriod*. + +#### **[TMBC-CORRECT.1]** + +We define a predicate *correctUntil(n, t)*, where *n* is a node and *t* is a +time point. +The predicate *correctUntil(n, t)* is true if and only if the node *n* +follows all the protocols (at least) until time *t*. + +#### **[TMBC-FM-2THIRDS.1]** + +If a block *h* is in the chain, +then there exists a subset *CorrV* +of *h.NextValidators*, such that: + +- *TotalVotingPower(CorrV) > 2/3 + TotalVotingPower(h.NextValidators)*; cf. [TMBC-VALIDATOR-SET.1] +- For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n, + h.Time + trustingPeriod)*; cf. [TMBC-CORRECT.1] + +> The definition of correct +> [**[TMBC-CORRECT.1]**][TMBC-CORRECT-link] refers to realtime, while it +> is used here with *Time* and *trustingPeriod*, which are "hardware +> times". We do not make a distinction here. + +#### **[TMBC-CORR-FULL.1]** + +Every correct full node locally stores a prefix of the +current list of headers from [**[TMBC-SEQ.1]**][TMBC-SEQ-link]. + +## What the Light Client Checks + +> From [TMBC-FM-2THIRDS.1] we directly derive the following observation: + +#### **[TMBC-VAL-CONTAINS-CORR.1]** + +Given a (trusted) block *tb* of the blockchain, a given set of full nodes +*N* contains a correct node at a real-time *t*, if + +- *t - trustingPeriod < tb.Time < t* +- the voting power in tb.NextValidators of nodes in *N* is more + than 1/3 of *TotalVotingPower(tb.NextValidators)* + +> The following describes how a commit for a given block *b* must look +> like. + +#### **[TMBC-SOUND-DISTR-POSS-COMMIT.1]** + +For a block *b*, each element *pc* of *PossibleCommit(b)* satisfies: + +- *pc* contains only votes (cf. [TMBC-VOTE.1]) + by validators from *b.Validators* +- the sum of the voting powers in *pc* is greater than 2/3 + *TotalVotingPower(b.Validators)* +- and there is an *r* such that each vote *v* in *pc* satisfies + - v.Type = precommit + - v.Height = b.Height + - v.Round = r + - v.blockID = hash(b) + +> The following property comes from the validity of the [consensus][arXiv]: A +> correct validator node only sends `prevote` or `precommit`, if +> `BlockID` of the new (to-be-decided) block is equal to the hash of +> the last block. + +#### **[TMBC-VAL-COMMIT.1]** + +If for a block *b*, a commit *c* + +- contains at least one validator pair *(v,p)* such that *v* is a + **correct** validator node, and +- is contained in *PossibleCommit(b)* + +then the block *b* is on the blockchain. + +## Context of this document + +In this document we specify the light client verification component, +called *Core Verification*. The *Core Verification* communicates with +a full node. As full nodes may be faulty, it cannot trust the +received information, but the light client has to check whether the +header it receives coincides with the one generated by Tendermint +consensus. + +The two + properties [[TMBC-VAL-CONTAINS-CORR.1]][TMBC-VAL-CONTAINS-CORR-link] and +[[TMBC-VAL-COMMIT]][TMBC-VAL-COMMIT-link] formalize the checks done + by this specification: +Given a trusted block *tb* and an untrusted block *ub* with a commit *cub*, +one has to check that *cub* is in *PossibleCommit(ub)*, and that *cub* +contains a correct node using *tb*. + +# Part II - Sequential Definition of the Verification Problem + +## Verification Informal Problem statement + +Given a height *targetHeight* as an input, the *Verifier* eventually +stores a header *h* of height *targetHeight* locally. This header *h* +is generated by the Tendermint [blockchain][block]. In +particular, a header that was not generated by the blockchain should +never be stored. + +## Sequential Problem statement + +#### **[LCV-SEQ-LIVE.1]** + +The *Verifier* gets as input a height *targetHeight*, and eventually stores the +header of height *targetHeight* of the blockchain. + +#### **[LCV-SEQ-SAFE.1]** + +The *Verifier* never stores a header which is not in the blockchain. + +# Part III - Light Client as Distributed System + +## Incentives + +Faulty full nodes may benefit from lying to the light client, by making the +light client accept a block that deviates (e.g., contains additional +transactions) from the one generated by Tendermint consensus. +Users using the light client might be harmed by accepting a forged header. + +The [attack detector][attack-detector] of the light client may help the +correct full nodes to understand whether their header is a good one. +Hence, in combination with the light client detector, the correct full +nodes have the incentive to respond. We can thus base liveness +arguments on the assumption that correct full nodes reliably talk to +the light client. + +## Computational Model + +#### **[LCV-A-PEER.1]** + +The verifier communicates with a full node called *primary*. No assumption is made about the full node (it may be correct or faulty). + +#### **[LCV-A-COMM.1]** + +Communication between the light client and a correct full node is +reliable and bounded in time. Reliable communication means that +messages are not lost, not duplicated, and eventually delivered. There +is a (known) end-to-end delay *Delta*, such that if a message is sent +at time *t* then it is received and processes by time *t + Delta*. +This implies that we need a timeout of at least *2 Delta* for remote +procedure calls to ensure that the response of a correct peer arrives +before the timeout expires. + +#### **[LCV-A-TFM.1]** + +The Tendermint blockchain satisfies the Tendermint failure model [**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link]. + +#### **[LCV-A-VAL.1]** + +The system satisfies [**[TMBC-AUTH-BYZ.1]**][TMBC-Auth-Byz-link] and +[**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link]. Thus, there is a +blockchain that satisfies the soundness requirements (that is, the +validation rules in [[block]]). + +## Distributed Problem Statement + +### Two Kinds of Termination + +We do not assume that *primary* is correct. Under this assumption no +protocol can guarantee the combination of the sequential +properties. Thus, in the (unreliable) distributed setting, we consider +two kinds of termination (successful and failure) and we will specify +below under what (favorable) conditions *Core Verification* ensures to +terminate successfully, and satisfy the requirements of the sequential +problem statement: + +#### **[LCV-DIST-TERM.1]** + +*Core Verification* either *terminates +successfully* or it *terminates with failure*. + +### Design choices + +#### **[LCV-DIST-STORE.2]** + +*Core Verification* returns a data structure called *LightStore* that +contains light blocks (that contain a header). + +#### **[LCV-DIST-INIT.2]** + +*Core Verification* is called with + +- *primary*: the PeerID of a full node (with verification communicates) +- *root*: a light block (the root of trust) +- *targetHeight*: a height (the height of a header that should be obtained) + +### Temporal Properties + +#### **[LCV-DIST-SAFE.2]** + +It is always the case that every header in *LightStore* was +generated by an instance of Tendermint consensus. + +#### **[LCV-DIST-LIVE.2]** + +If a new instance of *Core Verification* is called with a +height *targetHeight* greater than root.Header.Height it must +must eventually terminate. + +- If + - the *primary* is correct (and locally has the block of + *targetHeight*), and + - the age of root is always less than the trusting period, + then *Core Verification* adds a verified header *hd* with height + *targetHeight* to *LightStore* and it **terminates successfully** + +> These definitions imply that if the primary is faulty, a header may or +> may not be added to *LightStore*. In any case, +> [**[LCV-DIST-SAFE.2]**](#lcv-dist-safe2) must hold. +> The invariant [**[LCV-DIST-SAFE.2]**](#lcv-dist-safe2) and the liveness +> requirement [**[LCV-DIST-LIVE.2]**](#lcv-dist-life) +> allow that verified headers are added to *LightStore* whose +> height was not passed +> to the verifier (e.g., intermediate headers used in bisection; see below). +> Note that for liveness, initially having a *root* within +> the *trustinPeriod* is not sufficient. However, as this +> specification will leave some freedom with respect to the strategy +> in which order to download intermediate headers, we do not give a +> more precise liveness specification here. After giving the +> specification of the protocol, we will discuss some liveness +> scenarios [below](#liveness-scenarios). + +### Solving the sequential specification + +This specification provides a partial solution to the sequential specification. +The *Verifier* solves the invariant of the sequential part + +[**[LCV-DIST-SAFE.2]**](#lcv-dist-safe2) => [**[LCV-SEQ-SAFE.1]**](#lcv-seq-safe1) + +In the case the primary is correct, and *root* is a recent header in *LightStore*, the verifier satisfies the liveness requirements. + +⋀ *primary is correct* +⋀ *root.header.Time* > *now* - *trustingPeriod* +⋀ [**[LCV-A-Comm.1]**](#lcv-a-comm) ⋀ ( + ( [**[TMBC-CorrFull.1]**][TMBC-CorrFull-link] ⋀ + [**[LCV-DIST-LIVE.2]**](#lcv-dist-live2) ) + ⟹ [**[LCV-SEQ-LIVE.1]**](#lcv-seq-live1) +) + +# Part IV - Light Client Verification Protocol + +We provide a specification for Light Client Verification. The local +code for verification is presented by a sequential function +`VerifyToTarget` to highlight the control flow of this functionality. +We note that if a different concurrency model is considered for +an implementation, the sequential flow of the function may be +implemented with mutexes, etc. However, the light client verification +is partitioned into three blocks that can be implemented and tested +independently: + +- `FetchLightBlock` is called to download a light block (header) of a + given height from a peer. +- `ValidAndVerified` is a local code that checks the header. +- `Schedule` decides which height to try to verify next. We keep this + underspecified as different implementations (currently in Goland and + Rust) may implement different optimizations here. We just provide + necessary conditions on how the height may evolve. + + + + +## Definitions + +### Data Types + +The core data structure of the protocol is the LightBlock. + +#### **[LCV-DATA-LIGHTBLOCK.1]** + +```go +type LightBlock struct { + Header Header + Commit Commit + Validators ValidatorSet +} +``` + +#### **[LCV-DATA-LIGHTSTORE.2]** + +LightBlocks are stored in a structure which stores all LightBlock from +initialization or received from peers. + +```go +type LightStore struct { + ... +} + +``` + +#### **[LCV-DATA-LS-ROOT.2]** + +For each lightblock in a lightstore we record in a field `verification-root` of +type Height. + +> `verification-root` records the height of a lightblock that can be used to verify +> the lightblock in one step + +#### **[LCV-INV-LS-ROOT.2]** + +At all times, if a lightblock *b* in a lightstore has *b.verification-root = h*, +then + +- the lightstore contains a lightblock with height *h*, or +- *b* has the minimal height of all lightblocks in lightstore, then + b.verification-root should be nil. + +The LightStore exposes the following functions to query stored LightBlocks. + +#### **[LCV-DATA-LS-STATE.1]** + +Each LightBlock is in one of the following states: + +```go +type VerifiedState int + +const ( + StateUnverified = iota + 1 + StateVerified + StateFailed + StateTrusted +) +``` + +#### **[LCV-FUNC-GET.1]** + +```go +func (ls LightStore) Get(height Height) (LightBlock, bool) +``` + +- Expected postcondition + - returns a LightBlock at a given height or false in the second argument if + the LightStore does not contain the specified LightBlock. + +#### **[LCV-FUNC-LATEST.1]** + +```go +func (ls LightStore) Latest() LightBlock +``` + +- Expected postcondition + - returns the highest light block + +#### **[LCV-FUNC-ADD.1]** + +```go +func (ls LightStore) Add(newBlock) +``` + +- Expected precondition + - the lightstore is empty +- Expected postcondition + - adds newBlock into light store + +#### **[LCV-FUNC-STORE.1]** + +```go +func (ls LightStore) store_chain(newLS LightStore) +``` + +- Expected postcondition + - adds `newLS` to the lightStore. + +#### **[LCV-FUNC-LATEST-VERIF.2]** + +```go +func (ls LightStore) LatestVerified() LightBlock +``` + +- Expected postcondition + - returns the highest light block whose state is `StateVerified` + +#### **[LCV-FUNC-FILTER.1]** + +```go +func (ls LightStore) FilterVerified() LightStore +``` + +- Expected postcondition + - returns all the lightblocks of the lightstore with state `StateVerified` + +#### **[LCV-FUNC-UPDATE.2]** + +```go +func (ls LightStore) Update(lightBlock LightBlock, verfiedState +VerifiedState, root-height Height) +``` + +- Expected postcondition + - the lightblock is part of the lightstore + - The state of the LightBlock is set to *verifiedState*. + - The verification-root of the LightBlock is set to *root-height* + +```go +func (ls LightStore) TraceTo(lightBlock LightBlock) (LightBlock, LightStore) +``` + +- Expected postcondition + - returns a **trusted** lightblock `root` from the lightstore with a height + less than `lightBlock` + - returns a lightstore that contains lightblocks that constitute a + [verification trace](TODOlinkToDetectorSpecOnceThere) from + `root` to `lightBlock` (including `lightBlock`) + +### Inputs + +- *root*: A light block that is trusted +- *primary*: peerID +- *targetHeight*: the height of the needed header + +### Configuration Parameters + +- *trustThreshold*: a float. Can be used if correctness should not be based on more voting power and 1/3. +- *trustingPeriod*: a time duration [**[TMBC-TIME_PARAMS.1]**][TMBC-TIME_PARAMS-link]. +- *clockDrift*: a time duration. Correction parameter dealing with only approximately synchronized clocks. + +### Variables + +- *nextHeight*: initially *targetHeight* + > *nextHeight* should be thought of the "height of the next header we need + > to download and verify" + +### Assumptions + +#### **[LCV-A-INIT.2]** + +- *root* is from the blockchain + +- *targetHeight > root.Header.Height* + +### Invariants + +#### **[LCV-INV-TP.1]** + +It is always the case that *LightStore.LatestTrusted.Header.Time > now - trustingPeriod*. + +> If the invariant is violated, the light client does not have a +> header it can trust. A trusted header must be obtained externally, +> its trust can only be based on social consensus. +> We use the convention that root is assumed to be verified. + +### Used Remote Functions + +We use the functions `commit` and `validators` that are provided +by the [RPC client for Tendermint][RPC]. + +```go +func Commit(height int64) (SignedHeader, error) +``` + +- Implementation remark + - RPC to full node *n* + - JSON sent: + +```javascript +// POST /commit +{ + "jsonrpc": "2.0", + "id": "ccc84631-dfdb-4adc-b88c-5291ea3c2cfb", // UUID v4, unique per request + "method": "commit", + "params": { + "height": 1234 + } +} +``` + +- Expected precondition + - header of `height` exists on blockchain +- Expected postcondition + - if *n* is correct: Returns the signed header of height `height` + from the blockchain if communication is timely (no timeout) + - if *n* is faulty: Returns a signed header with arbitrary content +- Error condition + - if *n* is correct: precondition violated or timeout + - if *n* is faulty: arbitrary error + +----; + +```go +func Validators(height int64) (ValidatorSet, error) +``` + +- Implementation remark + - RPC to full node *n* + - JSON sent: + +```javascript +// POST /validators +{ + "jsonrpc": "2.0", + "id": "ccc84631-dfdb-4adc-b88c-5291ea3c2cfb", // UUID v4, unique per request + "method": "validators", + "params": { + "height": 1234 + } +} +``` + +- Expected precondition + - header of `height` exists on blockchain +- Expected postcondition + - if *n* is correct: Returns the validator set of height `height` + from the blockchain if communication is timely (no timeout) + - if *n* is faulty: Returns arbitrary validator set +- Error condition + - if *n* is correct: precondition violated or timeout + - if *n* is faulty: arbitrary error + +----; + +### Communicating Function + +#### **[LCV-FUNC-FETCH.1]** + + ```go +func FetchLightBlock(peer PeerID, height Height) LightBlock +``` + +- Implementation remark + - RPC to peer at *PeerID* + - calls `Commit` for *height* and `Validators` for *height* and *height+1* +- Expected precondition + - `height` is less than or equal to height of the peer **[LCV-IO-PRE-HEIGHT.1]** +- Expected postcondition: + - if *node* is correct: + - Returns the LightBlock *lb* of height `height` + that is consistent with the blockchain + - *lb.provider = peer* **[LCV-IO-POST-PROVIDER.1]** + - *lb.Header* is a header consistent with the blockchain + - *lb.Validators* is the validator set of the blockchain at height *nextHeight* + - *lb.NextValidators* is the validator set of the blockchain at height *nextHeight + 1* + - if *node* is faulty: Returns a LightBlock with arbitrary content + [**[TMBC-AUTH-BYZ.1]**][TMBC-Auth-Byz-link] +- Error condition + - if *n* is correct: precondition violated + - if *n* is faulty: arbitrary error + - if *lb.provider != peer* + - times out after 2 Delta (by assumption *n* is faulty) + +----; + +## Core Verification + +### Outline + +The `VerifyToTarget` is the main function and uses the following functions. + +- `FetchLightBlock` is called to download the next light block. It is + the only function that communicates with other nodes +- `ValidAndVerified` checks whether header is valid and checks if a + new lightBlock should be trusted + based on a previously verified lightBlock. +- `Schedule` decides which height to try to verify next + +In the following description of `VerifyToTarget` we do not deal with error +handling. If any of the above function returns an error, VerifyToTarget just +passes the error on. + +#### **[LCV-FUNC-MAIN.2]** + +```go +func VerifyToTarget(primary PeerID, root LightBlock, + targetHeight Height) (LightStore, Result) { + + lightStore = new LightStore; + lightStore.Update(root, StateVerified, root.verifiedBy); + nextHeight := targetHeight; + + for lightStore.LatestVerified.height < targetHeight { + + // Get next LightBlock for verification + current, found := lightStore.Get(nextHeight) + if !found { + current = FetchLightBlock(primary, nextHeight) + lightStore.Update(current, StateUnverified, nil) + } + + // Verify + verdict = ValidAndVerified(lightStore.LatestVerified, current) + + // Decide whether/how to continue + if verdict == SUCCESS { + lightStore.Update(current, StateVerified, lightStore.LatestVerified.Height) + } + else if verdict == NOT_ENOUGH_TRUST { + // do nothing + // the light block current passed validation, but the validator + // set is too different to verify it. We keep the state of + // current at StateUnverified. For a later iteration, Schedule + // might decide to try verification of that light block again. + } + else { + // verdict is some error code + lightStore.Update(current, StateFailed, nil) + return (nil, ResultFailure) + } + nextHeight = Schedule(lightStore, nextHeight, targetHeight) + } + return (lightStore.FilterVerified, ResultSuccess) +} +``` + +- Expected precondition + - *root* is within the *trustingPeriod* **[LCV-PRE-TP.1]** + - *targetHeight* is greater than the height of *root* +- Expected postcondition: + - returns *lightStore* that contains a LightBlock that corresponds to a block + of the blockchain of height *targetHeight* + (that is, the LightBlock has been added to *lightStore*) **[LCV-POST-LS.1]** +- Error conditions + - if the precondition is violated + - if `ValidAndVerified` or `FetchLightBlock` report an error + - if [**[LCV-INV-TP.1]**](#LCV-INV-TP.1) is violated + +### Details of the Functions + +#### **[LCV-FUNC-VALID.2]** + +```go +func ValidAndVerified(trusted LightBlock, untrusted LightBlock) Result +``` + +- Expected precondition: + - *untrusted* is valid, that is, satisfies the soundness [checks][block] + - *untrusted* is **well-formed**, that is, + - *untrusted.Header.Time < now + clockDrift* + - *untrusted.Validators = hash(untrusted.Header.Validators)* + - *untrusted.NextValidators = hash(untrusted.Header.NextValidators)* + - *trusted.Header.Time > now - trustingPeriod* + - the `Height` and `Time` of `trusted` are smaller than the Height and + `Time` of `untrusted`, respectively + - the *untrusted.Header* is well-formed (passes the tests from + [[block]]), and in particular + - if the untrusted header `unstrusted.Header` is the immediate + successor of `trusted.Header`, then it holds that + - *trusted.Header.NextValidators = + untrusted.Header.Validators*, and + moreover, + - *untrusted.Header.Commit* + - contains signatures by more than two-thirds of the validators + - contains no signature from nodes that are not in *trusted.Header.NextValidators* +- Expected postcondition: + - Returns `SUCCESS`: + - if *untrusted* is the immediate successor of *trusted*, or otherwise, + - if the signatures of a set of validators that have more than + *max(1/3,trustThreshold)* of voting power in + *trusted.Nextvalidators* is contained in + *untrusted.Commit* (that is, header passes the tests + [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] + and [**[TMBC-VAL-COMMIT.1]**][TMBC-VAL-COMMIT-link]) + - Returns `NOT_ENOUGH_TRUST` if: + - *untrusted* is *not* the immediate successor of + *trusted* + and the *max(1/3,trustThreshold)* threshold is not reached + (that is, if + [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] + fails and header is does not violate the soundness + checks [[block]]). +- Error condition: + - if precondition violated + +----; + +#### **[LCV-FUNC-SCHEDULE.1]** + +```go +func Schedule(lightStore, nextHeight, targetHeight) Height +``` + +- Implementation remark: If picks the next height to be verified. + We keep the precise choice of the next header under-specified. It is + subject to performance optimizations that do not influence the correctness +- Expected postcondition: **[LCV-SCHEDULE-POST.1]** + Return *H* s.t. + 1. if *lightStore.LatestVerified.Height = nextHeight* and + *lightStore.LatestVerified < targetHeight* then + *nextHeight < H <= targetHeight* + 2. if *lightStore.LatestVerified.Height < nextHeight* and + *lightStore.LatestVerified.Height < targetHeight* then + *lightStore.LatestVerified.Height < H < nextHeight* + 3. if *lightStore.LatestVerified.Height = targetHeight* then + *H = targetHeight* + +> Case i. captures the case where the light block at height *nextHeight* +> has been verified, and we can choose a height closer to the *targetHeight*. +> As we get the *lightStore* as parameter, the choice of the next height can +> depend on the *lightStore*, e.g., we can pick a height for which we have +> already downloaded a light block. +> In Case ii. the header of *nextHeight* could not be verified, and we need to pick a smaller height. +> In Case iii. is a special case when we have verified the *targetHeight*. + +### Solving the distributed specification + +Analogous to [[001_published]](./verification_001_published.md#solving-the-distributed-specification) + +## Liveness Scenarios + +Analogous to [[001_published]](./verification_001_published.md#liveness-scenarios) + +# Part V - Supporting the IBC Relayer + +The above specification focuses on the most common case, which also +constitutes the most challenging task: using the Tendermint [security +model][TMBC-FM-2THIRDS-link] to verify light blocks without +downloading all intermediate blocks. To focus on this challenge, above +we have restricted ourselves to the case where *targetHeight* is +greater than the height of any trusted header. This simplified +presentation of the algorithm as initially +`lightStore.LatestVerified()` is less than *targetHeight*, and in the +process of verification `lightStore.LatestVerified()` increases until +*targetHeight* is reached. + +For [IBC][ibc-rs] there are two additional challenges: + +1. it might be that some "older" header is needed, that is, +*targetHeight < lightStore.LatestVerified()*. The +[supervisor](../supervisor/supervisor.md) checks whether it is in this +case by calling `LatestPrevious` and `MinVerified` and if so it calls +`Backwards`. All these functions are specified below. + +2. In order to submit proof of a light client attack, a relayer may + need to submit a verification trace. This it is important to + compute such a trace efficiently. That it can be done is based on + the invariant [[LCV-INV-LS-ROOT.2]](#LCV-INV-LS-ROOT2) that needs + to be maintained by the light client. In particular + `VerifyToTarget` and `Backwards` need to take care of setting + `verification-root`. + +#### **[LCV-FUNC-LATEST-PREV.2]** + +```go +func (ls LightStore) LatestPrevious(height Height) (LightBlock, bool) +``` + +- Expected postcondition + - returns a light block *lb* that satisfies: + - *lb* is in lightStore + - *lb* is in StateTrusted + - *lb* is not expired + - *lb.Header.Height < height* + - for all *b* in lightStore s.t. *b* is trusted and not expired it + holds *lb.Header.Height >= b.Header.Height* + - *false* in the second argument if + the LightStore does not contain such an *lb*. + +----; + +#### **[LCV-FUNC-LOWEST.2]** + +```go +func (ls LightStore) Lowest() (LightBlock) +``` + +- Expected postcondition + - returns the lowest trusted light block within trusting period + +----; + +#### **[LCV-FUNC-MIN.2]** + +```go +func (ls LightStore) MinVerified() (LightBlock, bool) +``` + +- Expected postcondition + - returns a light block *lb* that satisfies: + - *lb* is in lightStore + - *lb.Header.Height* is minimal in the lightStore + - *false* in the second argument if + the LightStore does not contain such an *lb*. + +If a height that is smaller than the smallest height in the lightstore +is required, we check the hashes backwards. This is done with the +following function: + +#### **[LCV-FUNC-BACKWARDS.2]** + +```go +func Backwards (primary PeerID, root LightBlock, targetHeight Height) + (LightStore, Result) { + + lb := root; + lightStore := new LightStore; + lightStore.Update(lb, StateTrusted, lb.verifiedBy) + + latest := lb.Header + for i := lb.Header.height - 1; i >= targetHeight; i-- { + // here we download height-by-height. We might first download all + // headers down to targetHeight and then check them. + current := FetchLightBlock(primary,i) + if (hash(current) != latest.Header.LastBlockId) { + return (nil, ResultFailure) + } + else { + // latest and current are linked together by LastBlockId + // therefore it is not relevant which we verified first + // for consistency, we store latest was veried using + // current so that the verifiedBy is always pointing down + // the chain + lightStore.Update(current, StateTrusted, nil) + lightStore.Update(latest, StateTrusted, current.Header.Height) + } + latest = current + } + return (lightStore, ResultSuccess) +} +``` + +# References + +[[block]] Specification of the block data structure. + +[[RPC]] RPC client for Tendermint + +[[attack-detector]] The specification of the light client attack detector. + +[[fullnode]] Specification of the full node API + +[[ibc-rs]] Rust implementation of IBC modules and relayer. + +[[lightclient]] The light client ADR [77d2651 on Dec 27, 2019]. + +[RPC]: https://docs.tendermint.com/master/rpc/ + +[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md + +[TMBC-HEADER-link]: #tmbc-header1 +[TMBC-SEQ-link]: #tmbc-seq1 +[TMBC-CorrFull-link]: #tmbc-corr-full1 +[TMBC-Auth-Byz-link]: #tmbc-auth-byz1 +[TMBC-TIME_PARAMS-link]: #tmbc-time-params1 +[TMBC-FM-2THIRDS-link]: #tmbc-fm-2thirds1 +[TMBC-VAL-CONTAINS-CORR-link]: #tmbc-val-contains-corr1 +[TMBC-VAL-COMMIT-link]: #tmbc-val-commit1 +[TMBC-SOUND-DISTR-POSS-COMMIT-link]: #tmbc-sound-distr-poss-commit1 + +[lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md +[attack-detector]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_001_reviewed.md +[fullnode]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md + +[ibc-rs]:https://github.com/informalsystems/ibc-rs + +[blockchain-validator-set]: https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.md#data-structures +[fullnode-data-structures]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#data-structures + +[FN-ManifestFaulty-link]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#fn-manifestfaulty + +[arXiv]: https://arxiv.org/abs/1807.04938 diff --git a/spec/light-client/verification/verification_003_draft.md b/spec/light-client/verification/verification_003_draft.md new file mode 100644 index 00000000000..cd38e7e9676 --- /dev/null +++ b/spec/light-client/verification/verification_003_draft.md @@ -0,0 +1,76 @@ +# Light Client Verificaiton + +#### **[LCV-FUNC-VERIFYCOMMITLIGHT.1]** + +VerifyCommitLight verifies that 2/3+ of the signatures for a validator set were for +a given blockID. The function will finish early and thus may not check all signatures. + +```go +func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID, +height int64, commit *Commit) error { + // run a basic validation of the arguments + if err := verifyBasicValsAndCommit(vals, commit, height, blockID); err != nil { + return err + } + + // calculate voting power needed + votingPowerNeeded := vals.TotalVotingPower() * 2 / 3 + + var ( + val *Validator + valIdx int32 + seenVals = make(map[int32]int, len(commit.Signatures)) + talliedVotingPower int64 = 0 + voteSignBytes []byte + ) + for idx, commitSig := range commit.Signatures { + // ignore all commit signatures that are not for the block + if !commitSig.ForBlock() { + continue + } + + // If the vals and commit have a 1-to-1 correspondance we can retrieve + // them by index else we need to retrieve them by address + if lookUpByIndex { + val = vals.Validators[idx] + } else { + valIdx, val = vals.GetByAddress(commitSig.ValidatorAddress) + + // if the signature doesn't belong to anyone in the validator set + // then we just skip over it + if val == nil { + continue + } + + // because we are getting validators by address we need to make sure + // that the same validator doesn't commit twice + if firstIndex, ok := seenVals[valIdx]; ok { + secondIndex := idx + return fmt.Errorf("double vote from %v (%d and %d)", val, firstIndex, secondIndex) + } + seenVals[valIdx] = idx + } + + voteSignBytes = commit.VoteSignBytes(chainID, int32(idx)) + + if !val.PubKey.VerifySignature(voteSignBytes, commitSig.Signature) { + return fmt.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature) + } + + // Add the voting power of the validator + // to the tally + talliedVotingPower += val.VotingPower + + // check if we have enough signatures and can thus exit early + if talliedVotingPower > votingPowerNeeded { + return nil + } + } + + if got, needed := talliedVotingPower, votingPowerNeeded; got <= needed { + return ErrNotEnoughVotingPowerSigned{Got: got, Needed: needed} + } + + return nil +} +``` \ No newline at end of file diff --git a/spec/p2p/config.md b/spec/p2p/config.md new file mode 100644 index 00000000000..b63c04f28d5 --- /dev/null +++ b/spec/p2p/config.md @@ -0,0 +1,49 @@ +# P2P Config + +Here we describe configuration options around the Peer Exchange. +These can be set using flags or via the `$TMHOME/config/config.toml` file. + +## Seed Mode + +`--p2p.seed_mode` + +The node operates in seed mode. In seed mode, a node continuously crawls the network for peers, +and upon incoming connection shares some peers and disconnects. + +## Seeds + +`--p2p.seeds “id100000000000000000000000000000000@1.2.3.4:26656,id200000000000000000000000000000000@2.3.4.5:4444”` + +Dials these seeds when we need more peers. They should return a list of peers and then disconnect. +If we already have enough peers in the address book, we may never need to dial them. + +## Persistent Peers + +`--p2p.persistent_peers “id100000000000000000000000000000000@1.2.3.4:26656,id200000000000000000000000000000000@2.3.4.5:26656”` + +Dial these peers and auto-redial them if the connection fails. +These are intended to be trusted persistent peers that can help +anchor us in the p2p network. The auto-redial uses exponential +backoff and will give up after a day of trying to connect. + +But If `persistent_peers_max_dial_period` is set greater than zero, +pause between each dial to each persistent peer will not exceed `persistent_peers_max_dial_period` +during exponential backoff and we keep trying again without giving up + +**Note:** If `seeds` and `persistent_peers` intersect, +the user will be warned that seeds may auto-close connections +and that the node may not be able to keep the connection persistent. + +## Private Peers + +`--p2p.private_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` + +These are IDs of the peers that we do not add to the address book or gossip to +other peers. They stay private to us. + +## Unconditional Peers + +`--p2p.unconditional_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` + +These are IDs of the peers which are allowed to be connected by both inbound or outbound regardless of +`max_num_inbound_peers` or `max_num_outbound_peers` of user's node reached or not. diff --git a/spec/p2p/connection.md b/spec/p2p/connection.md new file mode 100644 index 00000000000..33178f47943 --- /dev/null +++ b/spec/p2p/connection.md @@ -0,0 +1,111 @@ +# P2P Multiplex Connection + +## MConnection + +`MConnection` is a multiplex connection that supports multiple independent streams +with distinct quality of service guarantees atop a single TCP connection. +Each stream is known as a `Channel` and each `Channel` has a globally unique _byte id_. +Each `Channel` also has a relative priority that determines the quality of service +of the `Channel` compared to other `Channel`s. +The _byte id_ and the relative priorities of each `Channel` are configured upon +initialization of the connection. + +The `MConnection` supports three packet types: + +- Ping +- Pong +- Msg + +### Ping and Pong + +The ping and pong messages consist of writing a single byte to the connection; 0x1 and 0x2, respectively. + +When we haven't received any messages on an `MConnection` in time `pingTimeout`, we send a ping message. +When a ping is received on the `MConnection`, a pong is sent in response only if there are no other messages +to send and the peer has not sent us too many pings (TODO). + +If a pong or message is not received in sufficient time after a ping, the peer is disconnected from. + +### Msg + +Messages in channels are chopped into smaller `msgPacket`s for multiplexing. + +```go +type msgPacket struct { + ChannelID byte + EOF byte // 1 means message ends here. + Bytes []byte +} +``` + +The `msgPacket` is serialized using [Proto3](https://developers.google.com/protocol-buffers/docs/proto3). +The received `Bytes` of a sequential set of packets are appended together +until a packet with `EOF=1` is received, then the complete serialized message +is returned for processing by the `onReceive` function of the corresponding channel. + +### Multiplexing + +Messages are sent from a single `sendRoutine`, which loops over a select statement and results in the sending +of a ping, a pong, or a batch of data messages. The batch of data messages may include messages from multiple channels. +Message bytes are queued for sending in their respective channel, with each channel holding one unsent message at a time. +Messages are chosen for a batch one at a time from the channel with the lowest ratio of recently sent bytes to channel priority. + +## Sending Messages + +There are two methods for sending messages: + +```go +func (m MConnection) Send(chID byte, msg interface{}) bool {} +func (m MConnection) TrySend(chID byte, msg interface{}) bool {} +``` + +`Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued +for the channel with the given id byte `chID`. The message `msg` is serialized +using the `tendermint/go-amino` submodule's `WriteBinary()` reflection routine. + +`TrySend(chID, msg)` is a nonblocking call that queues the message msg in the channel +with the given id byte chID if the queue is not full; otherwise it returns false immediately. + +`Send()` and `TrySend()` are also exposed for each `Peer`. + +## Peer + +Each peer has one `MConnection` instance, and includes other information such as whether the connection +was outbound, whether the connection should be recreated if it closes, various identity information about the node, +and other higher level thread-safe data used by the reactors. + +## Switch/Reactor + +The `Switch` handles peer connections and exposes an API to receive incoming messages +on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one +or more `Channels`. So while sending outgoing messages is typically performed on the peer, +incoming messages are received on the reactor. + +```go +// Declare a MyReactor reactor that handles messages on MyChannelID. +type MyReactor struct{} + +func (reactor MyReactor) GetChannels() []*ChannelDescriptor { + return []*ChannelDescriptor{ChannelDescriptor{ID:MyChannelID, Priority: 1}} +} + +func (reactor MyReactor) Receive(chID byte, peer *Peer, msgBytes []byte) { + r, n, err := bytes.NewBuffer(msgBytes), new(int64), new(error) + msgString := ReadString(r, n, err) + fmt.Println(msgString) +} + +// Other Reactor methods omitted for brevity +... + +switch := NewSwitch([]Reactor{MyReactor{}}) + +... + +// Send a random message to all outbound connections +for _, peer := range switch.Peers().List() { + if peer.IsOutbound() { + peer.Send(MyChannelID, "Here's a random message") + } +} +``` diff --git a/spec/p2p/messages/README.md b/spec/p2p/messages/README.md new file mode 100644 index 00000000000..1b5f5c60dda --- /dev/null +++ b/spec/p2p/messages/README.md @@ -0,0 +1,19 @@ +--- +order: 1 +parent: + title: Messages + order: 1 +--- + +# Messages + +An implementation of the spec consists of many components. While many parts of these components are implementation specific, the p2p messages are not. In this section we will be covering all the p2p messages of components. + +There are two parts to the P2P messages, the message and the channel. The channel is message specific and messages are specific to components of Tendermint. When a node connect to a peer it will tell the other node which channels are available. This notifies the peer what services the connecting node offers. You can read more on channels in [connection.md](../connection.md#mconnection) + +- [Block Sync](./block-sync.md) +- [Mempool](./mempool.md) +- [Evidence](./evidence.md) +- [State Sync](./state-sync.md) +- [Pex](./pex.md) +- [Consensus](./consensus.md) diff --git a/spec/p2p/messages/block-sync.md b/spec/p2p/messages/block-sync.md new file mode 100644 index 00000000000..48aa6155fd8 --- /dev/null +++ b/spec/p2p/messages/block-sync.md @@ -0,0 +1,68 @@ +--- +order: 2 +--- + +# Block Sync + +## Channel + +Block sync has one channel. + +| Name | Number | +|-------------------|--------| +| BlockchainChannel | 64 | + +## Message Types + +There are multiple message types for Block Sync + +### BlockRequest + +BlockRequest asks a peer for a block at the height specified. + +| Name | Type | Description | Field Number | +|--------|-------|---------------------------|--------------| +| Height | int64 | Height of requested block | 1 | + +### NoBlockResponse + +NoBlockResponse notifies the peer requesting a block that the node does not contain it. + +| Name | Type | Description | Field Number | +|--------|-------|---------------------------|--------------| +| Height | int64 | Height of requested block | 1 | + +### BlockResponse + +BlockResponse contains the block requested. + +| Name | Type | Description | Field Number | +|-------|----------------------------------------------|-----------------|--------------| +| Block | [Block](../../core/data_structures.md#block) | Requested Block | 1 | + +### StatusRequest + +StatusRequest is an empty message that notifies the peer to respond with the highest and lowest blocks it has stored. + +> Empty message. + +### StatusResponse + +StatusResponse responds to a peer with the highest and lowest block stored. + +| Name | Type | Description | Field Number | +|--------|-------|-------------------------------------------------------------------|--------------| +| Height | int64 | Current Height of a node | 1 | +| base | int64 | First known block, if pruning is enabled it will be higher than 1 | 1 | + +### Message + +Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buffers/docs/proto#oneof). The `oneof` consists of five messages. + +| Name | Type | Description | Field Number | +|-------------------|----------------------------------|--------------------------------------------------------------|--------------| +| block_request | [BlockRequest](#blockrequest) | Request a block from a peer | 1 | +| no_block_response | [NoBlockResponse](#noblockresponse) | Response saying it doe snot have the requested block | 2 | +| block_response | [BlockResponse](#blockresponse) | Response with requested block | 3 | +| status_request | [StatusRequest](#statusrequest) | Request the highest and lowest block numbers from a peer | 4 | +| status_response | [StatusResponse](#statusresponse) | Response with the highest and lowest block numbers the store | 5 | diff --git a/spec/p2p/messages/consensus.md b/spec/p2p/messages/consensus.md new file mode 100644 index 00000000000..19c103750a4 --- /dev/null +++ b/spec/p2p/messages/consensus.md @@ -0,0 +1,149 @@ +--- +order: 7 +--- + +# Consensus + +## Channel + +Consensus has four separate channels. The channel identifiers are listed below. + +| Name | Number | +|--------------------|--------| +| StateChannel | 32 | +| DataChannel | 33 | +| VoteChannel | 34 | +| VoteSetBitsChannel | 35 | + +## Message Types + +### Proposal + +Proposal is sent when a new block is proposed. It is a suggestion of what the +next block in the blockchain should be. + +| Name | Type | Description | Field Number | +|----------|----------------------------------------------------|----------------------------------------|--------------| +| proposal | [Proposal](../../core/data_structures.md#proposal) | Proposed Block to come to consensus on | 1 | + +### Vote + +Vote is sent to vote for some block (or to inform others that a process does not vote in the +current round). Vote is defined in the +[Blockchain](https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md#blockidd) +section and contains validator's +information (validator address and index), height and round for which the vote is sent, vote type, +blockID if process vote for some block (`nil` otherwise) and a timestamp when the vote is sent. The +message is signed by the validator private key. + +| Name | Type | Description | Field Number | +|------|--------------------------------------------|---------------------------|--------------| +| vote | [Vote](../../core/data_structures.md#vote) | Vote for a proposed Block | 1 | + +### BlockPart + +BlockPart is sent when gossiping a piece of the proposed block. It contains height, round +and the block part. + +| Name | Type | Description | Field Number | +|--------|--------------------------------------------|----------------------------------------|--------------| +| height | int64 | Height of corresponding block. | 1 | +| round | int32 | Round of voting to finalize the block. | 2 | +| part | [Part](../../core/data_structures.md#part) | A part of the block. | 3 | + +### NewRoundStep + +NewRoundStep is sent for every step transition during the core consensus algorithm execution. +It is used in the gossip part of the Tendermint protocol to inform peers about a current +height/round/step a process is in. + +| Name | Type | Description | Field Number | +|--------------------------|--------|----------------------------------------|--------------| +| height | int64 | Height of corresponding block | 1 | +| round | int32 | Round of voting to finalize the block. | 2 | +| step | uint32 | | 3 | +| seconds_since_start_time | int64 | | 4 | +| last_commit_round | int32 | | 5 | + +### NewValidBlock + +NewValidBlock is sent when a validator observes a valid block B in some round r, +i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r. +It contains height and round in which valid block is observed, block parts header that describes +the valid block and is used to obtain all +block parts, and a bit array of the block parts a process currently has, so its peers can know what +parts it is missing so they can send them. +In case the block is also committed, then IsCommit flag is set to true. + +| Name | Type | Description | Field Number | +|-----------------------|--------------------------------------------------------------|----------------------------------------|--------------| +| height | int64 | Height of corresponding block | 1 | +| round | int32 | Round of voting to finalize the block. | 2 | +| block_part_set_header | [PartSetHeader](../../core/data_structures.md#partsetheader) | | 3 | +| block_parts | int32 | | 4 | +| is_commit | bool | | 5 | + +### ProposalPOL + +ProposalPOL is sent when a previous block is re-proposed. +It is used to inform peers in what round the process learned for this block (ProposalPOLRound), +and what prevotes for the re-proposed block the process has. + +| Name | Type | Description | Field Number | +|--------------------|----------|-------------------------------|--------------| +| height | int64 | Height of corresponding block | 1 | +| proposal_pol_round | int32 | | 2 | +| proposal_pol | bitarray | | 3 | + +### ReceivedVote + +ReceivedVote is sent to indicate that a particular vote has been received. It contains height, +round, vote type and the index of the validator that is the originator of the corresponding vote. + +| Name | Type | Description | Field Number | +|--------|------------------------------------------------------------------|----------------------------------------|--------------| +| height | int64 | Height of corresponding block | 1 | +| round | int32 | Round of voting to finalize the block. | 2 | +| type | [SignedMessageType](../../core/data_structures.md#signedmsgtype) | | 3 | +| index | int32 | | 4 | + +### VoteSetMaj23 + +VoteSetMaj23 is sent to indicate that a process has seen +2/3 votes for some BlockID. +It contains height, round, vote type and the BlockID. + +| Name | Type | Description | Field Number | +|--------|------------------------------------------------------------------|----------------------------------------|--------------| +| height | int64 | Height of corresponding block | 1 | +| round | int32 | Round of voting to finalize the block. | 2 | +| type | [SignedMessageType](../../core/data_structures.md#signedmsgtype) | | 3 | + +### VoteSetBits + +VoteSetBits is sent to communicate the bit-array of votes a process has seen for a given +BlockID. It contains height, round, vote type, BlockID and a bit array of +the votes a process has. + +| Name | Type | Description | Field Number | +|----------|------------------------------------------------------------------|----------------------------------------|--------------| +| height | int64 | Height of corresponding block | 1 | +| round | int32 | Round of voting to finalize the block. | 2 | +| type | [SignedMessageType](../../core/data_structures.md#signedmsgtype) | | 3 | +| block_id | [BlockID](../../core/data_structures.md#blockid) | | 4 | +| votes | BitArray | Round of voting to finalize the block. | 5 | + +### Message + +Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buffers/docs/proto#oneof). + +| Name | Type | Description | Field Number | +|-----------------|---------------------------------|----------------------------------------|--------------| +| new_round_step | [NewRoundStep](#newroundstep) | Height of corresponding block | 1 | +| new_valid_block | [NewValidBlock](#newvalidblock) | Round of voting to finalize the block. | 2 | +| proposal | [Proposal](#proposal) | | 3 | +| proposal_pol | [ProposalPOL](#proposalpol) | | 4 | +| block_part | [BlockPart](#blockpart) | | 5 | +| vote | [Vote](#vote) | | 6 | +| received_vote | [ReceivedVote](#ReceivedVote) | | 7 | +| vote_set_maj23 | [VoteSetMaj23](#votesetmaj23) | | 8 | +| vote_set_bits | [VoteSetBits](#votesetbits) | | 9 | diff --git a/spec/p2p/messages/evidence.md b/spec/p2p/messages/evidence.md new file mode 100644 index 00000000000..34fc40a9155 --- /dev/null +++ b/spec/p2p/messages/evidence.md @@ -0,0 +1,23 @@ +--- +order: 3 +--- + +# Evidence + +## Channel + +Evidence has one channel. The channel identifier is listed below. + +| Name | Number | +|-----------------|--------| +| EvidenceChannel | 56 | + +## Message Types + +### EvidenceList + +EvidenceList consists of a list of verified evidence. This evidence will already have been propagated throughout the network. EvidenceList is used in two places, as a p2p message and within the block [block](../../core/data_structures.md#block) as well. + +| Name | Type | Description | Field Number | +|----------|-------------------------------------------------------------|------------------------|--------------| +| evidence | repeated [Evidence](../../core/data_structures.md#evidence) | List of valid evidence | 1 | diff --git a/spec/p2p/messages/mempool.md b/spec/p2p/messages/mempool.md new file mode 100644 index 00000000000..8f3925cad51 --- /dev/null +++ b/spec/p2p/messages/mempool.md @@ -0,0 +1,33 @@ +--- +order: 4 +--- +# Mempool + +## Channel + +Mempool has one channel. The channel identifier is listed below. + +| Name | Number | +|----------------|--------| +| MempoolChannel | 48 | + +## Message Types + +There is currently only one message that Mempool broadcasts and receives over +the p2p gossip network (via the reactor): `TxsMessage` + +### Txs + +A list of transactions. These transactions have been checked against the application for validity. This does not mean that the transactions are valid, it is up to the application to check this. + +| Name | Type | Description | Field Number | +|------|----------------|----------------------|--------------| +| txs | repeated bytes | List of transactions | 1 | + +### Message + +Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buffers/docs/proto#oneof). The one of consists of one message [`Txs`](#txs). + +| Name | Type | Description | Field Number | +|------|-------------|-----------------------|--------------| +| txs | [Txs](#txs) | List of transactions | 1 | diff --git a/spec/p2p/messages/pex.md b/spec/p2p/messages/pex.md new file mode 100644 index 00000000000..e12a076e562 --- /dev/null +++ b/spec/p2p/messages/pex.md @@ -0,0 +1,76 @@ +--- +order: 6 +--- + +# Peer Exchange + +## Channels + +Pex has one channel. The channel identifier is listed below. + +| Name | Number | +|------------|--------| +| PexChannel | 0 | + +## Message Types + +The current PEX service has two versions. The first uses IP/port pair but since the p2p stack is moving towards a transport agnostic approach, +node endpoints require a `Protocol` and `Path` hence the V2 version uses a [url](https://golang.org/pkg/net/url/#URL) instead. + +### PexRequest + +PexRequest is an empty message requesting a list of peers. + +> EmptyRequest + +### PexResponse + +PexResponse is an list of net addresses provided to a peer to dial. + +| Name | Type | Description | Field Number | +|-------|------------------------------------|------------------------------------------|--------------| +| addresses | repeated [PexAddress](#PexAddress) | List of peer addresses available to dial | 1 | + +### PexAddress + +PexAddress provides needed information for a node to dial a peer. + +| Name | Type | Description | Field Number | +|------|--------|------------------|--------------| +| id | string | NodeID of a peer | 1 | +| ip | string | The IP of a node | 2 | +| port | port | Port of a peer | 3 | + + +### PexRequestV2 + +PexRequest is an empty message requesting a list of peers. + +> EmptyRequest + +### PexResponseV2 + +PexResponse is an list of net addresses provided to a peer to dial. + +| Name | Type | Description | Field Number | +|-------|------------------------------------|------------------------------------------|--------------| +| addresses | repeated [PexAddressV2](#PexAddressV2) | List of peer addresses available to dial | 1 | + +### PexAddressV2 + +PexAddress provides needed information for a node to dial a peer. + +| Name | Type | Description | Field Number | +|------|--------|------------------|--------------| +| url | string | See [golang url](https://golang.org/pkg/net/url/#URL) | 1 | + +### Message + +Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buffers/docs/proto#oneof). The one of consists of two messages. + +| Name | Type | Description | Field Number | +|--------------|---------------------------|------------------------------------------------------|--------------| +| pex_request | [PexRequest](#PexRequest) | Empty request asking for a list of addresses to dial | 1 | +| pex_response | [PexResponse](#PexResponse) | List of addresses to dial | 2 | +| pex_request_v2 | [PexRequestV2](#PexRequestV2) | Empty request asking for a list of addresses to dial | 3 | +| pex_response_v2 | [PexRespinseV2](#PexResponseV2) | List of addresses to dial | 4 | diff --git a/spec/p2p/messages/state-sync.md b/spec/p2p/messages/state-sync.md new file mode 100644 index 00000000000..71e3ae71b19 --- /dev/null +++ b/spec/p2p/messages/state-sync.md @@ -0,0 +1,133 @@ +--- +order: 5 +--- + +# State Sync + +## Channels + +State sync has four distinct channels. The channel identifiers are listed below. + +| Name | Number | +|-------------------|--------| +| SnapshotChannel | 96 | +| ChunkChannel | 97 | +| LightBlockChannel | 98 | +| ParamsChannel | 99 | + +## Message Types + +### SnapshotRequest + +When a new node begin state syncing, it will ask all peers it encounters if it has any +available snapshots: + +| Name | Type | Description | Field Number | +|----------|--------|-------------|--------------| + +### SnapShotResponse + +The receiver will query the local ABCI application via `ListSnapshots`, and send a message +containing snapshot metadata (limited to 4 MB) for each of the 10 most recent snapshots: and stored at the application layer. When a peer is starting it will request snapshots. + +| Name | Type | Description | Field Number | +|----------|--------|-----------------------------------------------------------|--------------| +| height | uint64 | Height at which the snapshot was taken | 1 | +| format | uint32 | Format of the snapshot. | 2 | +| chunks | uint32 | How many chunks make up the snapshot | 3 | +| hash | bytes | Arbitrary snapshot hash | 4 | +| metadata | bytes | Arbitrary application data. **May be non-deterministic.** | 5 | + +### ChunkRequest + +The node running state sync will offer these snapshots to the local ABCI application via +`OfferSnapshot` ABCI calls, and keep track of which peers contain which snapshots. Once a snapshot +is accepted, the state syncer will request snapshot chunks from appropriate peers: + +| Name | Type | Description | Field Number | +|--------|--------|-------------------------------------------------------------|--------------| +| height | uint64 | Height at which the chunk was created | 1 | +| format | uint32 | Format chosen for the chunk. **May be non-deterministic.** | 2 | +| index | uint32 | Index of the chunk within the snapshot. | 3 | + +### ChunkResponse + +The receiver will load the requested chunk from its local application via `LoadSnapshotChunk`, +and respond with it (limited to 16 MB): + +| Name | Type | Description | Field Number | +|---------|--------|-------------------------------------------------------------|--------------| +| height | uint64 | Height at which the chunk was created | 1 | +| format | uint32 | Format chosen for the chunk. **May be non-deterministic.** | 2 | +| index | uint32 | Index of the chunk within the snapshot. | 3 | +| hash | bytes | Arbitrary snapshot hash | 4 | +| missing | bool | Arbitrary application data. **May be non-deterministic.** | 5 | + +Here, `Missing` is used to signify that the chunk was not found on the peer, since an empty +chunk is a valid (although unlikely) response. + +The returned chunk is given to the ABCI application via `ApplySnapshotChunk` until the snapshot +is restored. If a chunk response is not returned within some time, it will be re-requested, +possibly from a different peer. + +The ABCI application is able to request peer bans and chunk refetching as part of the ABCI protocol. + +### LightBlockRequest + +To verify state and to provide state relevant information for consensus, the node will ask peers for +light blocks at specified heights. + +| Name | Type | Description | Field Number | +|----------|--------|----------------------------|--------------| +| height | uint64 | Height of the light block | 1 | + +### LightBlockResponse + +The receiver will retrieve and construct the light block from both the block and state stores. The +receiver will verify the data by comparing the hashes and store the header, commit and validator set +if necessary. The light block at the height of the snapshot will be used to verify the `AppHash`. + +| Name | Type | Description | Field Number | +|---------------|---------------------------------------------------------|--------------------------------------|--------------| +| light_block | [LightBlock](../../core/data_structures.md#lightblock) | Light block at the height requested | 1 | + +State sync will use [light client verification](../../light-client/verification.README.md) to verify +the light blocks. + + +If no state sync is in progress (i.e. during normal operation), any unsolicited response messages +are discarded. + +### ParamsRequest + +In order to build tendermint state, the state provider will request the params at the height of the snapshot and use the header to verify it. + +| Name | Type | Description | Field Number | +|----------|--------|----------------------------|--------------| +| height | uint64 | Height of the consensus params | 1 | + + +### ParamsResponse + +A reciever to the request will use the state store to fetch the consensus params at that height and return it to the sender. + +| Name | Type | Description | Field Number | +|----------|--------|---------------------------------|--------------| +| height | uint64 | Height of the consensus params | 1 | +| consensus_params | [ConsensusParams](../../core/data_structures.md#ConsensusParams) | Consensus params at the height requested | 2 | + + +### Message + +Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buffers/docs/proto#oneof). The `oneof` consists of eight messages. + +| Name | Type | Description | Field Number | +|----------------------|--------------------------------------------|----------------------------------------------|--------------| +| snapshots_request | [SnapshotRequest](#snapshotrequest) | Request a recent snapshot from a peer | 1 | +| snapshots_response | [SnapshotResponse](#snapshotresponse) | Respond with the most recent snapshot stored | 2 | +| chunk_request | [ChunkRequest](#chunkrequest) | Request chunks of the snapshot. | 3 | +| chunk_response | [ChunkRequest](#chunkresponse) | Response of chunks used to recreate state. | 4 | +| light_block_request | [LightBlockRequest](#lightblockrequest) | Request a light block. | 5 | +| light_block_response | [LightBlockResponse](#lightblockresponse) | Respond with a light block | 6 | +| params_request | [ParamsRequest](#paramsrequest) | Request the consensus params at a height. | 7 | +| params_response | [ParamsResponse](#paramsresponse) | Respond with the consensus params | 8 | diff --git a/spec/p2p/node.md b/spec/p2p/node.md new file mode 100644 index 00000000000..0bbcf0b5bdb --- /dev/null +++ b/spec/p2p/node.md @@ -0,0 +1,67 @@ +# Peer Discovery + +A Tendermint P2P network has different kinds of nodes with different requirements for connectivity to one another. +This document describes what kind of nodes Tendermint should enable and how they should work. + +## Seeds + +Seeds are the first point of contact for a new node. +They return a list of known active peers and then disconnect. + +Seeds should operate full nodes with the PEX reactor in a "crawler" mode +that continuously explores to validate the availability of peers. + +Seeds should only respond with some top percentile of the best peers it knows about. +See [the peer-exchange docs](https://github.com/tendermint/spec/blob/master/spec/reactors/pex/pex.md) for + details on peer quality. + +## New Full Node + +A new node needs a few things to connect to the network: + +- a list of seeds, which can be provided to Tendermint via config file or flags, + or hardcoded into the software by in-process apps +- a `ChainID`, also called `Network` at the p2p layer +- a recent block height, H, and hash, HASH for the blockchain. + +The values `H` and `HASH` must be received and corroborated by means external to Tendermint, and specific to the user - ie. via the user's trusted social consensus. +This requirement to validate `H` and `HASH` out-of-band and via social consensus +is the essential difference in security models between Proof-of-Work and Proof-of-Stake blockchains. + +With the above, the node then queries some seeds for peers for its chain, +dials those peers, and runs the Tendermint protocols with those it successfully connects to. + +When the peer catches up to height H, it ensures the block hash matches HASH. +If not, Tendermint will exit, and the user must try again - either they are connected +to bad peers or their social consensus is invalid. + +## Restarted Full Node + +A node checks its address book on startup and attempts to connect to peers from there. +If it can't connect to any peers after some time, it falls back to the seeds to find more. + +Restarted full nodes can run the `blockchain` or `consensus` reactor protocols to sync up +to the latest state of the blockchain from wherever they were last. +In a Proof-of-Stake context, if they are sufficiently far behind (greater than the length +of the unbonding period), they will need to validate a recent `H` and `HASH` out-of-band again +so they know they have synced the correct chain. + +## Validator Node + +A validator node is a node that interfaces with a validator signing key. +These nodes require the highest security, and should not accept incoming connections. +They should maintain outgoing connections to a controlled set of "Sentry Nodes" that serve +as their proxy shield to the rest of the network. + +Validators that know and trust each other can accept incoming connections from one another and maintain direct private connectivity via VPN. + +## Sentry Node + +Sentry nodes are guardians of a validator node and provide it access to the rest of the network. +They should be well connected to other full nodes on the network. +Sentry nodes may be dynamic, but should maintain persistent connections to some evolving random subset of each other. +They should always expect to have direct incoming connections from the validator node and its backup(s). +They do not report the validator node's address in the PEX and +they may be more strict about the quality of peers they keep. + +Sentry nodes belonging to validators that trust each other may wish to maintain persistent connections via VPN with one another, but only report each other sparingly in the PEX. diff --git a/spec/p2p/peer.md b/spec/p2p/peer.md new file mode 100644 index 00000000000..52d00b5846e --- /dev/null +++ b/spec/p2p/peer.md @@ -0,0 +1,130 @@ +# Peers + +This document explains how Tendermint Peers are identified and how they connect to one another. + +For details on peer discovery, see the [peer exchange (PEX) reactor doc](https://github.com/tendermint/spec/blob/master/spec/reactors/pex/pex.md). + +## Peer Identity + +Tendermint peers are expected to maintain long-term persistent identities in the form of a public key. +Each peer has an ID defined as `peer.ID == peer.PubKey.Address()`, where `Address` uses the scheme defined in `crypto` package. + +A single peer ID can have multiple IP addresses associated with it, but a node +will only ever connect to one at a time. + +When attempting to connect to a peer, we use the PeerURL: `@:`. +We will attempt to connect to the peer at IP:PORT, and verify, +via authenticated encryption, that it is in possession of the private key +corresponding to ``. This prevents man-in-the-middle attacks on the peer layer. + +## Connections + +All p2p connections use TCP. +Upon establishing a successful TCP connection with a peer, +two handshakes are performed: one for authenticated encryption, and one for Tendermint versioning. +Both handshakes have configurable timeouts (they should complete quickly). + +### Authenticated Encryption Handshake + +Tendermint implements the Station-to-Station protocol +using X25519 keys for Diffie-Helman key-exchange and chacha20poly1305 for encryption. + +Previous versions of this protocol (0.32 and below) suffered from malleability attacks whereas an active man +in the middle attacker could compromise confidentiality as described in [Prime, Order Please! +Revisiting Small Subgroup and Invalid Curve Attacks on +Protocols using Diffie-Hellman](https://eprint.iacr.org/2019/526.pdf). + +We have added dependency on the Merlin a keccak based transcript hashing protocol to ensure non-malleability. + +It goes as follows: + +- generate an ephemeral X25519 keypair +- send the ephemeral public key to the peer +- wait to receive the peer's ephemeral public key +- create a new Merlin Transcript with the string "TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH" +- Sort the ephemeral keys and add the high labeled "EPHEMERAL_UPPER_PUBLIC_KEY" and the low keys labeled "EPHEMERAL_LOWER_PUBLIC_KEY" to the Merlin transcript. +- compute the Diffie-Hellman shared secret using the peers ephemeral public key and our ephemeral private key +- add the DH secret to the transcript labeled DH_SECRET. +- generate two keys to use for encryption (sending and receiving) and a challenge for authentication as follows: + - create a hkdf-sha256 instance with the key being the diffie hellman shared secret, and info parameter as + `TENDERMINT_SECRET_CONNECTION_KEY_AND_CHALLENGE_GEN` + - get 64 bytes of output from hkdf-sha256 + - if we had the smaller ephemeral pubkey, use the first 32 bytes for the key for receiving, the second 32 bytes for sending; else the opposite. +- use a separate nonce for receiving and sending. Both nonces start at 0, and should support the full 96 bit nonce range +- all communications from now on are encrypted in 1400 byte frames (plus encoding overhead), + using the respective secret and nonce. Each nonce is incremented by one after each use. +- we now have an encrypted channel, but still need to authenticate +- extract a 32 bytes challenge from merlin transcript with the label "SECRET_CONNECTION_MAC" +- sign the common challenge obtained from the hkdf with our persistent private key +- send the amino encoded persistent pubkey and signature to the peer +- wait to receive the persistent public key and signature from the peer +- verify the signature on the challenge using the peer's persistent public key + +If this is an outgoing connection (we dialed the peer) and we used a peer ID, +then finally verify that the peer's persistent public key corresponds to the peer ID we dialed, +ie. `peer.PubKey.Address() == `. + +The connection has now been authenticated. All traffic is encrypted. + +Note: only the dialer can authenticate the identity of the peer, +but this is what we care about since when we join the network we wish to +ensure we have reached the intended peer (and are not being MITMd). + +### Peer Filter + +Before continuing, we check if the new peer has the same ID as ourselves or +an existing peer. If so, we disconnect. + +We also check the peer's address and public key against +an optional whitelist which can be managed through the ABCI app - +if the whitelist is enabled and the peer does not qualify, the connection is +terminated. + +### Tendermint Version Handshake + +The Tendermint Version Handshake allows the peers to exchange their NodeInfo: + +```golang +type NodeInfo struct { + Version p2p.Version + ID p2p.ID + ListenAddr string + + Network string + SoftwareVersion string + Channels []int8 + + Moniker string + Other NodeInfoOther +} + +type Version struct { + P2P uint64 + Block uint64 + App uint64 +} + +type NodeInfoOther struct { + TxIndex string + RPCAddress string +} +``` + +The connection is disconnected if: + +- `peer.NodeInfo.ID` is not equal `peerConn.ID` +- `peer.NodeInfo.Version.Block` does not match ours +- `peer.NodeInfo.Network` is not the same as ours +- `peer.Channels` does not intersect with our known Channels. +- `peer.NodeInfo.ListenAddr` is malformed or is a DNS host that cannot be + resolved + +At this point, if we have not disconnected, the peer is valid. +It is added to the switch and hence all reactors via the `AddPeer` method. +Note that each reactor may handle multiple channels. + +## Connection Activity + +Once a peer is added, incoming messages for a given reactor are handled through +that reactor's `Receive` method, and output messages are sent directly by the Reactors +on each peer. A typical reactor maintains per-peer go-routine(s) that handle this. diff --git a/spec/p2p/readme.md b/spec/p2p/readme.md new file mode 100644 index 00000000000..96867aad056 --- /dev/null +++ b/spec/p2p/readme.md @@ -0,0 +1,6 @@ +--- +order: 1 +parent: + title: P2P + order: 6 +--- diff --git a/spec/rpc/README.md b/spec/rpc/README.md new file mode 100644 index 00000000000..7cdf417dca3 --- /dev/null +++ b/spec/rpc/README.md @@ -0,0 +1,1264 @@ +--- +order: 1 +parent: + title: RPC + order: 6 +--- + +# RPC spec + +This file defines the JSON-RPC spec of Tendermint. This is meant to be implemented by all clients. + +## Support + + | | [Tendermint-Go](https://github.com/tendermint/tendermint/) | [endermint-Rs](https://github.com/informalsystems/tendermint-rs) | + |--------------|:----------------------------------------------------------:|:----------------------------------------------------------------:| + | JSON-RPC 2.0 | ✅ | ✅ | + | HTTP | ✅ | ✅ | + | HTTPS | ✅ | ❌ | + | WS | ✅ | ✅ | + + | Routes | [Tendermint-Go](https://github.com/tendermint/tendermint/) | [Tendermint-Rs](https://github.com/informalsystems/tendermint-rs) | + |-----------------------------------------|:----------------------------------------------------------:|:-----------------------------------------------------------------:| + | [Health](#health) | ✅ | ✅ | + | [Status](#status) | ✅ | ✅ | + | [NetInfo](#netinfo) | ✅ | ✅ | + | [Blockchain](#blockchain) | ✅ | ✅ | + | [Block](#block) | ✅ | ✅ | + | [BlockByHash](#blockbyhash) | ✅ | ❌ | + | [BlockResults](#blockresults) | ✅ | ✅ | + | [Commit](#commit) | ✅ | ✅ | + | [Validators](#validators) | ✅ | ✅ | + | [Genesis](#genesis) | ✅ | ✅ | + | [GenesisChunked](#genesischunked) | ✅ | ❌ | + | [ConsensusParams](#consensusparams) | ✅ | ❌ | + | [UnconfirmedTxs](#unconfirmedtxs) | ✅ | ❌ | + | [NumUnconfirmedTxs](#numunconfirmedtxs) | ✅ | ❌ | + | [Tx](#tx) | ✅ | ❌ | + | [BroadCastTxSync](#broadcasttxsync) | ✅ | ✅ | + | [BroadCastTxAsync](#broadcasttxasync) | ✅ | ✅ | + | [ABCIInfo](#abciinfo) | ✅ | ✅ | + | [ABCIQuery](#abciquery) | ✅ | ✅ | + | [BroadcastTxAsync](#broadcasttxasync) | ✅ | ✅ | + | [BroadcastEvidence](#broadcastevidence) | ✅ | ✅ | + +## Timestamps + +Timestamps in the RPC layer of Tendermint follows RFC3339Nano. The RFC3339Nano format removes trailing zeros from the seconds field. + +This means if a block has a timestamp like: `1985-04-12T23:20:50.5200000Z`, the value returned in the RPC will be `1985-04-12T23:20:50.52Z`. + + + +## Info Routes + +### Health + +Node heartbeat + +#### Parameters + +None + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/health +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"health\"}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": -1, + "result": {} +} +``` + +### Status + +Get Tendermint status including node info, pubkey, latest block hash, app hash, block height and time. + +#### Parameters + +None + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/status +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"status\"}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": -1, + "result": { + "node_info": { + "protocol_version": { + "p2p": "8", + "block": "11", + "app": "0" + }, + "id": "b93270b358a72a2db30089f3856475bb1f918d6d", + "listen_addr": "tcp://0.0.0.0:26656", + "network": "cosmoshub-4", + "version": "v0.34.8", + "channels": "40202122233038606100", + "moniker": "aib-hub-node", + "other": { + "tx_index": "on", + "rpc_address": "tcp://0.0.0.0:26657" + } + }, + "sync_info": { + "latest_block_hash": "50F03C0EAACA8BCA7F9C14189ACE9C05A9A1BBB5268DB63DC6A3C848D1ECFD27", + "latest_app_hash": "2316CFF7644219F4F15BEE456435F280E2B38955EEA6D4617CCB6D7ABF781C22", + "latest_block_height": "5622165", + "latest_block_time": "2021-03-25T14:00:43.356134226Z", + "earliest_block_hash": "1455A0C15AC49BB506992EC85A3CD4D32367E53A087689815E01A524231C3ADF", + "earliest_app_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "earliest_block_height": "5200791", + "earliest_block_time": "2019-12-11T16:11:34Z", + "catching_up": false + }, + "validator_info": { + "address": "38FB765D0092470989360ECA1C89CD06C2C1583C", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "Z+8kntVegi1sQiWLYwFSVLNWqdAUGEy7lskL78gxLZI=" + }, + "voting_power": "0" + } + } +} +``` + +### NetInfo + +Network information + +#### Parameters + +None + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/net_info +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"net_info\"}" +``` + +#### Response + +```json +{ + "id": 0, + "jsonrpc": "2.0", + "result": { + "listening": true, + "listeners": [ + "Listener(@)" + ], + "n_peers": "1", + "peers": [ + { + "node_id": "5576458aef205977e18fd50b274e9b5d9014525a", + "url": "tcp://5576458aef205977e18fd50b274e9b5d9014525a@95.179.155.35:26656" + } + ] + } +} +``` + +### Blockchain + +Get block headers. Returned in descending order. May be limited in quantity. + +#### Parameters + +- `minHeight (integer)`: The lowest block to be returned in the response +- `maxHeight (integer)`: The highest block to be returned in the response + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/blockchain + +curl http://127.0.0.1:26657/blockchain?minHeight=1&maxHeight=2 +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"blockchain\",\"params\":{\"minHeight\":\"1\", \"maxHeight\":\"2\"}}" +``` + +#### Response + +```json +{ + "id": 0, + "jsonrpc": "2.0", + "result": { + "last_height": "1276718", + "block_metas": [ + { + "block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "block_size": 1000000, + "header": { + "version": { + "block": "10", + "app": "0" + }, + "chain_id": "cosmoshub-2", + "height": "12", + "time": "2019-04-22T17:01:51.701356223Z", + "last_block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "last_commit_hash": "21B9BC845AD2CB2C4193CDD17BFC506F1EBE5A7402E84AD96E64171287A34812", + "data_hash": "970886F99E77ED0D60DA8FCE0447C2676E59F2F77302B0C4AA10E1D02F18EF73", + "validators_hash": "D658BFD100CA8025CFD3BECFE86194322731D387286FBD26E059115FD5F2BCA0", + "next_validators_hash": "D658BFD100CA8025CFD3BECFE86194322731D387286FBD26E059115FD5F2BCA0", + "consensus_hash": "0F2908883A105C793B74495EB7D6DF2EEA479ED7FC9349206A65CB0F9987A0B8", + "app_hash": "223BF64D4A01074DC523A80E76B9BBC786C791FB0A1893AC5B14866356FCFD6C", + "last_results_hash": "", + "evidence_hash": "", + "proposer_address": "D540AB022088612AC74B287D076DBFBC4A377A2E" + }, + "num_txs": "54" + } + ] + } +} +``` + +### Block + +Get block at a specified height. + +#### Parameters + +- `height (integer)`: height of the requested block. If no height is specified the latest block will be used. + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/block + +curl http://127.0.0.1:26657/block?height=1 +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"block\",\"params\":{\"height\":\"1\"}}" +``` + +#### Response + +```json +{ + "id": 0, + "jsonrpc": "2.0", + "result": { + "block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "block": { + "header": { + "version": { + "block": "10", + "app": "0" + }, + "chain_id": "cosmoshub-2", + "height": "12", + "time": "2019-04-22T17:01:51.701356223Z", + "last_block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "last_commit_hash": "21B9BC845AD2CB2C4193CDD17BFC506F1EBE5A7402E84AD96E64171287A34812", + "data_hash": "970886F99E77ED0D60DA8FCE0447C2676E59F2F77302B0C4AA10E1D02F18EF73", + "validators_hash": "D658BFD100CA8025CFD3BECFE86194322731D387286FBD26E059115FD5F2BCA0", + "next_validators_hash": "D658BFD100CA8025CFD3BECFE86194322731D387286FBD26E059115FD5F2BCA0", + "consensus_hash": "0F2908883A105C793B74495EB7D6DF2EEA479ED7FC9349206A65CB0F9987A0B8", + "app_hash": "223BF64D4A01074DC523A80E76B9BBC786C791FB0A1893AC5B14866356FCFD6C", + "last_results_hash": "", + "evidence_hash": "", + "proposer_address": "D540AB022088612AC74B287D076DBFBC4A377A2E" + }, + "data": [ + "yQHwYl3uCkKoo2GaChRnd+THLQ2RM87nEZrE19910Z28ABIUWW/t8AtIMwcyU0sT32RcMDI9GF0aEAoFdWF0b20SBzEwMDAwMDASEwoNCgV1YXRvbRIEMzEwMRCd8gEaagom61rphyEDoJPxlcjRoNDtZ9xMdvs+lRzFaHe2dl2P5R2yVCWrsHISQKkqX5H1zXAIJuC57yw0Yb03Fwy75VRip0ZBtLiYsUqkOsPUoQZAhDNP+6LY+RUwz/nVzedkF0S29NZ32QXdGv0=" + ], + "evidence": [ + { + "type": "string", + "height": 0, + "time": 0, + "total_voting_power": 0, + "validator": { + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "A6DoBUypNtUAyEHWtQ9bFjfNg8Bo9CrnkUGl6k6OHN4=" + }, + "voting_power": 0, + "address": "string" + } + } + ], + "last_commit": { + "height": 0, + "round": 0, + "block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "signatures": [ + { + "type": 2, + "height": "1262085", + "round": 0, + "block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "timestamp": "2019-08-01T11:39:38.867269833Z", + "validator_address": "000001E443FD237E4B616E2FA69DF4EE3D49A94F", + "validator_index": 0, + "signature": "DBchvucTzAUEJnGYpNvMdqLhBAHG4Px8BsOBB3J3mAFCLGeuG7uJqy+nVngKzZdPhPi8RhmE/xcw/M9DOJjEDg==" + } + ] + } + } + } +} +``` + +### BlockByHash + +#### Parameters + +- `hash (string)`: Hash of the block to query for. + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/block_by_hash?hash=0xD70952032620CC4E2737EB8AC379806359D8E0B17B0488F627997A0B043ABDED +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"block_by_hash\",\"params\":{\"hash\":\"0xD70952032620CC4E2737EB8AC379806359D8E0B17B0488F627997A0B043ABDED\"}}" +``` + +#### Response + +```json +{ + "id": 0, + "jsonrpc": "2.0", + "result": { + "block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "block": { + "header": { + "version": { + "block": "10", + "app": "0" + }, + "chain_id": "cosmoshub-2", + "height": "12", + "time": "2019-04-22T17:01:51.701356223Z", + "last_block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "last_commit_hash": "21B9BC845AD2CB2C4193CDD17BFC506F1EBE5A7402E84AD96E64171287A34812", + "data_hash": "970886F99E77ED0D60DA8FCE0447C2676E59F2F77302B0C4AA10E1D02F18EF73", + "validators_hash": "D658BFD100CA8025CFD3BECFE86194322731D387286FBD26E059115FD5F2BCA0", + "next_validators_hash": "D658BFD100CA8025CFD3BECFE86194322731D387286FBD26E059115FD5F2BCA0", + "consensus_hash": "0F2908883A105C793B74495EB7D6DF2EEA479ED7FC9349206A65CB0F9987A0B8", + "app_hash": "223BF64D4A01074DC523A80E76B9BBC786C791FB0A1893AC5B14866356FCFD6C", + "last_results_hash": "", + "evidence_hash": "", + "proposer_address": "D540AB022088612AC74B287D076DBFBC4A377A2E" + }, + "data": [ + "yQHwYl3uCkKoo2GaChRnd+THLQ2RM87nEZrE19910Z28ABIUWW/t8AtIMwcyU0sT32RcMDI9GF0aEAoFdWF0b20SBzEwMDAwMDASEwoNCgV1YXRvbRIEMzEwMRCd8gEaagom61rphyEDoJPxlcjRoNDtZ9xMdvs+lRzFaHe2dl2P5R2yVCWrsHISQKkqX5H1zXAIJuC57yw0Yb03Fwy75VRip0ZBtLiYsUqkOsPUoQZAhDNP+6LY+RUwz/nVzedkF0S29NZ32QXdGv0=" + ], + "evidence": [ + { + "type": "string", + "height": 0, + "time": 0, + "total_voting_power": 0, + "validator": { + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "A6DoBUypNtUAyEHWtQ9bFjfNg8Bo9CrnkUGl6k6OHN4=" + }, + "voting_power": 0, + "address": "string" + } + } + ], + "last_commit": { + "height": 0, + "round": 0, + "block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "signatures": [ + { + "type": 2, + "height": "1262085", + "round": 0, + "block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "timestamp": "2019-08-01T11:39:38.867269833Z", + "validator_address": "000001E443FD237E4B616E2FA69DF4EE3D49A94F", + "validator_index": 0, + "signature": "DBchvucTzAUEJnGYpNvMdqLhBAHG4Px8BsOBB3J3mAFCLGeuG7uJqy+nVngKzZdPhPi8RhmE/xcw/M9DOJjEDg==" + } + ] + } + } + } +} +``` + +### BlockResults + +### Parameters + +- `height (integer)`: Height of the block which contains the results. If no height is specified, the latest block height will be used + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/block_results + + +curl http://127.0.0.1:26657/block_results?height=1 +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"block_results\",\"params\":{\"height\":\"1\"}}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "height": "12", + "total_gas_used": "100", + "txs_results": [ + { + "code": "0", + "data": "", + "log": "not enough gas", + "info": "", + "gas_wanted": "100", + "gas_used": "100", + "events": [ + { + "type": "app", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "c2VuZA==", + "index": false + } + ] + } + ], + "codespace": "ibc" + } + ], + "begin_block_events": [ + { + "type": "app", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "c2VuZA==", + "index": false + } + ] + } + ], + "end_block": [ + { + "type": "app", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "c2VuZA==", + "index": false + } + ] + } + ], + "validator_updates": [ + { + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "9tK9IT+FPdf2qm+5c2qaxi10sWP+3erWTKgftn2PaQM=" + }, + "power": "300" + } + ], + "consensus_params_updates": { + "block": { + "max_bytes": "22020096", + "max_gas": "1000", + "time_iota_ms": "1000" + }, + "evidence": { + "max_age": "100000" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + } + } + } +} +``` + +### Commit + +#### Parameters + +- `height (integer)`: Height of the block the requested commit pertains to. If no height is set the latest commit will be returned. + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/commit + + +curl http://127.0.0.1:26657/commit?height=1 +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"commit\",\"params\":{\"height\":\"1\"}}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "signed_header": { + "header": { + "version": { + "block": "10", + "app": "0" + }, + "chain_id": "cosmoshub-2", + "height": "12", + "time": "2019-04-22T17:01:51.701356223Z", + "last_block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "last_commit_hash": "21B9BC845AD2CB2C4193CDD17BFC506F1EBE5A7402E84AD96E64171287A34812", + "data_hash": "970886F99E77ED0D60DA8FCE0447C2676E59F2F77302B0C4AA10E1D02F18EF73", + "validators_hash": "D658BFD100CA8025CFD3BECFE86194322731D387286FBD26E059115FD5F2BCA0", + "next_validators_hash": "D658BFD100CA8025CFD3BECFE86194322731D387286FBD26E059115FD5F2BCA0", + "consensus_hash": "0F2908883A105C793B74495EB7D6DF2EEA479ED7FC9349206A65CB0F9987A0B8", + "app_hash": "223BF64D4A01074DC523A80E76B9BBC786C791FB0A1893AC5B14866356FCFD6C", + "last_results_hash": "", + "evidence_hash": "", + "proposer_address": "D540AB022088612AC74B287D076DBFBC4A377A2E" + }, + "commit": { + "height": "1311801", + "round": 0, + "block_id": { + "hash": "112BC173FD838FB68EB43476816CD7B4C6661B6884A9E357B417EE957E1CF8F7", + "parts": { + "total": 1, + "hash": "38D4B26B5B725C4F13571EFE022C030390E4C33C8CF6F88EDD142EA769642DBD" + } + }, + "signatures": [ + { + "block_id_flag": 2, + "validator_address": "000001E443FD237E4B616E2FA69DF4EE3D49A94F", + "timestamp": "2019-04-22T17:01:58.376629719Z", + "signature": "14jaTQXYRt8kbLKEhdHq7AXycrFImiLuZx50uOjs2+Zv+2i7RTG/jnObD07Jo2ubZ8xd7bNBJMqkgtkd0oQHAw==" + } + ] + } + }, + "canonical": true + } +} +``` + +### Validators + +#### Parameters + +- `height (integer)`: Block height at which the validators were present on. If no height is set the latest commit will be returned. +- `page (integer)`: +- `per_page (integer)`: + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/validators +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"validators\",\"params\":{\"height\":\"1\", \"page\":\"1\", \"per_page\":\"20\"}}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "block_height": "55", + "validators": [ + { + "address": "000001E443FD237E4B616E2FA69DF4EE3D49A94F", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "9tK9IT+FPdf2qm+5c2qaxi10sWP+3erWTKgftn2PaQM=" + }, + "voting_power": "239727", + "proposer_priority": "-11896414" + } + ], + "count": "1", + "total": "25" + } +} +``` + +### Genesis + +Get Genesis of the chain. If the response is large, this operation +will return an error: use `genesis_chunked` instead. + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/genesis +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"genesis\"}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "genesis": { + "genesis_time": "2019-04-22T17:00:00Z", + "chain_id": "cosmoshub-2", + "initial_height": "2", + "consensus_params": { + "block": { + "max_bytes": "22020096", + "max_gas": "1000", + "time_iota_ms": "1000" + }, + "evidence": { + "max_age": "100000" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + } + }, + "validators": [ + { + "address": "B00A6323737F321EB0B8D59C6FD497A14B60938A", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "cOQZvh/h9ZioSeUMZB/1Vy1Xo5x2sjrVjlE/qHnYifM=" + }, + "power": "9328525", + "name": "Certus One" + } + ], + "app_hash": "", + "app_state": {} + } + } +} +``` + +### GenesisChunked + +Get the genesis document in a chunks to support easily transfering larger documents. + +#### Parameters + +- `chunk` (integer): the index number of the chunk that you wish to + fetch. These IDs are 0 indexed. + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/genesis_chunked?chunk=0 +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"genesis_chunked\",\"params\":{\"chunk\":0}}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "chunk": 0, + "total": 10, + "data": "dGVuZGVybWludAo=" + } +} +``` + +### ConsensusParams + +Get the consensus parameters. + +#### Parameters + +- `height (integer)`: Block height at which the consensus params would like to be fetched for. + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/consensus_params +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"consensus_params\"}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "block_height": "1", + "consensus_params": { + "block": { + "max_bytes": "22020096", + "max_gas": "1000", + "time_iota_ms": "1000" + }, + "evidence": { + "max_age": "100000" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + } + } + } +} +``` + +### UnconfirmedTxs + +Get a list of unconfirmed transactions. + +#### Parameters + +- `limit (integer)` The amount of txs to respond with. + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/unconfirmed_txs +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"unconfirmed_txs\, \"params\":{\"limit\":\"20\"}}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "n_txs": "82", + "total": "82", + "total_bytes": "19974", + "txs": [ + "gAPwYl3uCjCMTXENChSMnIkb5ZpYHBKIZqecFEV2tuZr7xIUA75/FmYq9WymsOBJ0XSJ8yV8zmQKMIxNcQ0KFIyciRvlmlgcEohmp5wURXa25mvvEhQbrvwbvlNiT+Yjr86G+YQNx7kRVgowjE1xDQoUjJyJG+WaWBwSiGannBRFdrbma+8SFK2m+1oxgILuQLO55n8mWfnbIzyPCjCMTXENChSMnIkb5ZpYHBKIZqecFEV2tuZr7xIUQNGfkmhTNMis4j+dyMDIWXdIPiYKMIxNcQ0KFIyciRvlmlgcEohmp5wURXa25mvvEhS8sL0D0wwgGCItQwVowak5YB38KRIUCg4KBXVhdG9tEgUxMDA1NBDoxRgaagom61rphyECn8x7emhhKdRCB2io7aS/6Cpuq5NbVqbODmqOT3jWw6kSQKUresk+d+Gw0BhjiggTsu8+1voW+VlDCQ1GRYnMaFOHXhyFv7BCLhFWxLxHSAYT8a5XqoMayosZf9mANKdXArA=" + ] + } +} +``` + +### NumUnconfirmedTxs + +Get data about unconfirmed transactions. + +#### Parameters + +None + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/num_unconfirmed_txs +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"num_unconfirmed_txs\"}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "n_txs": "31", + "total": "82", + "total_bytes": "19974" + } +} +``` + +### Tx + +#### Parameters + +- `hash (string)`: The hash of the transaction +- `prove (bool)`: If the response should include proof the transaction was included in a block. + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/num_unconfirmed_txs +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"num_unconfirmed_txs\"}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "hash": "D70952032620CC4E2737EB8AC379806359D8E0B17B0488F627997A0B043ABDED", + "height": "1000", + "index": 0, + "tx_result": { + "log": "[{\"msg_index\":\"0\",\"success\":true,\"log\":\"\"}]", + "gas_wanted": "200000", + "gas_used": "28596", + "tags": [ + { + "key": "YWN0aW9u", + "value": "c2VuZA==", + "index": false + } + ] + }, + "tx": "5wHwYl3uCkaoo2GaChQmSIu8hxpJxLcCuIi8fiHN4TMwrRIU/Af1cEG7Rcs/6LjTl7YjRSymJfYaFAoFdWF0b20SCzE0OTk5OTk1MDAwEhMKDQoFdWF0b20SBDUwMDAQwJoMGmoKJuta6YchAwswBShaB1wkZBctLIhYqBC3JrAI28XGzxP+rVEticGEEkAc+khTkKL9CDE47aDvjEHvUNt+izJfT4KVF2v2JkC+bmlH9K08q3PqHeMI9Z5up+XMusnTqlP985KF+SI5J3ZOIhhNYWRlIGJ5IENpcmNsZSB3aXRoIGxvdmU=" + } +} +``` + +## Transaction Routes + +### BroadCastTxSync + +Returns with the response from CheckTx. Does not wait for DeliverTx result. + +#### Parameters + +- `tx (string)`: The transaction encoded + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/broadcast_tx_sync?tx=encoded_tx +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"broadcast_tx_sync\",\"params\":{\"tx\":\"a/encoded_tx/c\"}}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "code": "0", + "data": "", + "log": "", + "codespace": "ibc", + "hash": "0D33F2F03A5234F38706E43004489E061AC40A2E" + }, + "error": "" +} +``` + +### BroadCastTxAsync + +Returns right away, with no response. Does not wait for CheckTx nor DeliverTx results. + +#### Parameters + +- `tx (string)`: The transaction encoded + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/broadcast_tx_async?tx=encoded_tx +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"broadcast_tx_async\",\"params\":{\"tx\":\"a/encoded_tx/c\"}}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "code": "0", + "data": "", + "log": "", + "codespace": "ibc", + "hash": "0D33F2F03A5234F38706E43004489E061AC40A2E" + }, + "error": "" +} +``` + +### CheckTx + +Checks the transaction without executing it. + +#### Parameters + +- `tx (string)`: String of the encoded transaction + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/check_tx?tx=encoded_tx +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"check_tx\",\"params\":{\"tx\":\"a/encoded_tx/c\"}}" +``` + +#### Response + +```json +{ + "id": 0, + "jsonrpc": "2.0", + "error": "", + "result": { + "code": "0", + "data": "", + "log": "", + "info": "", + "gas_wanted": "1", + "gas_used": "0", + "events": [ + { + "type": "app", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "c2VuZA==", + "index": false + } + ] + } + ], + "codespace": "bank" + } +} +``` + +## ABCI Routes + +### ABCIInfo + +Get some info about the application. + +#### Parameters + +None + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/abci_info +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"abci_info\"}" +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 0, + "result": { + "response": { + "data": "{\"size\":0}", + "version": "0.16.1", + "app_version": "1314126" + } + } +} +``` + +### ABCIQuery + +Query the application for some information. + +#### Parameters + +- `path (string)`: Path to the data. This is defined by the application. +- `data (string)`: The data requested +- `height (integer)`: Height at which the data is being requested for. +- `prove (bool)`: Include proofs of the transactions inclusion in the block + +#### Request + +##### HTTP + +```sh +curl http://127.0.0.1:26657/abci_query?path="a/b/c"=IHAVENOIDEA&height=1&prove=true +``` + +##### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"abci_query\",\"params\":{\"path\":\"a/b/c\", \"height\":\"1\", \"bool\":\"true\"}}" +``` + +#### Response + +```json +{ + "error": "", + "result": { + "response": { + "log": "exists", + "height": "0", + "proof": "010114FED0DAD959F36091AD761C922ABA3CBF1D8349990101020103011406AA2262E2F448242DF2C2607C3CDC705313EE3B0001149D16177BC71E445476174622EA559715C293740C", + "value": "61626364", + "key": "61626364", + "index": "-1", + "code": "0" + } + }, + "id": 0, + "jsonrpc": "2.0" +} +``` + +## Evidence Routes + +### BroadcastEvidence + +Broadcast evidence of the misbehavior. + +#### Parameters + +- `evidence (string)`: + +#### Request + +##### HTTP + +```sh +curl http://localhost:26657/broadcast_evidence?evidence=JSON_EVIDENCE_encoded +``` + +#### JSONRPC + +```sh +curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"broadcast_evidence\",\"params\":{\"evidence\":\"JSON_EVIDENCE_encoded\"}}" +``` + +#### Response + +```json +{ + "error": "", + "result": "", + "id": 0, + "jsonrpc": "2.0" +} +``` diff --git a/state/mocks/block_store.go b/state/mocks/block_store.go index 4493a6e3f2c..20e6a9b1607 100644 --- a/state/mocks/block_store.go +++ b/state/mocks/block_store.go @@ -193,13 +193,13 @@ func (_m *BlockStore) Size() int64 { return r0 } -type mockConstructorTestingTNewBlockStore interface { +type NewBlockStoreT interface { mock.TestingT Cleanup(func()) } // NewBlockStore creates a new instance of BlockStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewBlockStore(t mockConstructorTestingTNewBlockStore) *BlockStore { +func NewBlockStore(t NewBlockStoreT) *BlockStore { mock := &BlockStore{} mock.Mock.Test(t) diff --git a/state/mocks/evidence_pool.go b/state/mocks/evidence_pool.go index 7279d36f710..b4d57d04d7d 100644 --- a/state/mocks/evidence_pool.go +++ b/state/mocks/evidence_pool.go @@ -70,13 +70,13 @@ func (_m *EvidencePool) Update(_a0 state.State, _a1 types.EvidenceList) { _m.Called(_a0, _a1) } -type mockConstructorTestingTNewEvidencePool interface { +type NewEvidencePoolT interface { mock.TestingT Cleanup(func()) } // NewEvidencePool creates a new instance of EvidencePool. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewEvidencePool(t mockConstructorTestingTNewEvidencePool) *EvidencePool { +func NewEvidencePool(t NewEvidencePoolT) *EvidencePool { mock := &EvidencePool{} mock.Mock.Test(t) diff --git a/state/mocks/store.go b/state/mocks/store.go index 8cbe490800b..0ad39aaa370 100644 --- a/state/mocks/store.go +++ b/state/mocks/store.go @@ -241,13 +241,13 @@ func (_m *Store) SaveABCIResponses(_a0 int64, _a1 *tendermintstate.ABCIResponses return r0 } -type mockConstructorTestingTNewStore interface { +type NewStoreT interface { mock.TestingT Cleanup(func()) } // NewStore creates a new instance of Store. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewStore(t mockConstructorTestingTNewStore) *Store { +func NewStore(t NewStoreT) *Store { mock := &Store{} mock.Mock.Test(t) diff --git a/statesync/mocks/state_provider.go b/statesync/mocks/state_provider.go index f52b9e33d2d..5029f615e44 100644 --- a/statesync/mocks/state_provider.go +++ b/statesync/mocks/state_provider.go @@ -83,13 +83,13 @@ func (_m *StateProvider) State(ctx context.Context, height uint64) (state.State, return r0, r1 } -type mockConstructorTestingTNewStateProvider interface { +type NewStateProviderT interface { mock.TestingT Cleanup(func()) } // NewStateProvider creates a new instance of StateProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewStateProvider(t mockConstructorTestingTNewStateProvider) *StateProvider { +func NewStateProvider(t NewStateProviderT) *StateProvider { mock := &StateProvider{} mock.Mock.Test(t)

#_$y479`m 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 yG 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