From 9d864da353af0ea064913cadc47a010a813b91ba Mon Sep 17 00:00:00 2001 From: "Milosevic, Zarko" Date: Mon, 10 Jul 2017 08:28:56 +0200 Subject: [PATCH 001/223] Initial commit --- .gitignore | 6 + IEEEtran.bst | 2417 ++++++++++++++++++++++ IEEEtran.cls | 4733 +++++++++++++++++++++++++++++++++++++++++++ algorithmicplus.sty | 195 ++ consensus.tex | 142 ++ definitions.tex | 46 + homodel.sty | 19 + latex8.bst | 1124 ++++++++++ latex8.sty | 168 ++ lit.bib | 1562 ++++++++++++++ paper.tex | 150 ++ readme.md | 4 + rounddiag.sty | 61 + technote.sty | 118 ++ 14 files changed, 10745 insertions(+) create mode 100644 .gitignore create mode 100644 IEEEtran.bst create mode 100644 IEEEtran.cls create mode 100644 algorithmicplus.sty create mode 100644 consensus.tex create mode 100644 definitions.tex create mode 100644 homodel.sty create mode 100644 latex8.bst create mode 100644 latex8.sty create mode 100644 lit.bib create mode 100644 paper.tex create mode 100644 readme.md create mode 100644 rounddiag.sty create mode 100644 technote.sty diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..7b044d43f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.aux +*.bbl +*.blg +*.log +*.pdf +*.gz diff --git a/IEEEtran.bst b/IEEEtran.bst new file mode 100644 index 000000000..53fbc030a --- /dev/null +++ b/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/IEEEtran.cls b/IEEEtran.cls new file mode 100644 index 000000000..9c967d555 --- /dev/null +++ b/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/algorithmicplus.sty b/algorithmicplus.sty new file mode 100644 index 000000000..de7ca01ea --- /dev/null +++ b/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/consensus.tex b/consensus.tex new file mode 100644 index 000000000..d960b602b --- /dev/null +++ b/consensus.tex @@ -0,0 +1,142 @@ + +\section{Tendermint} +\label{sec:consensus} + +\newcommand\Propose{\mathsf{PROPOSE}} +\newcommand\PrePrepare{\mathsf{INIT}} +\newcommand\Prepare{\mathsf{PREVOTE}} +\newcommand\Commit{\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}} + + +\begin{algorithm}[htb!] +\def\baselinestretch{1} +\scriptsize\raggedright +\begin{algorithmic}[1] +\SHORTSPACE +\INIT{} + \STATE $v_p := v \in V$ \COMMENT{value considered for consensus} + \STATE $round_p := 0$ \COMMENT{current round number} + \STATE $vote_p := null$ \COMMENT{locked value} + \STATE $ts_p := -1$ \COMMENT{ if $ts_p \ge 0$, it represents round in which $vote_p$ has been locked} + \STATE $proof_p := \emptyset$ \COMMENT{set of signed messages as a proof that $vote_p$ has been locked in round $ts_p$} + \STATE $state_p \in \set{init=0,prepared=1,comitted=2}$, initially $init$ + \STATE $decision_p = null$ +\ENDINIT + +\SPACE +\UPON{start} + \STATE $StartRound(0)$ +\ENDUPON + +\SPACE +\UPON{receiving $\li{\Propose,round_p, vote, ts, proof}$ \From\ $\coord(round_p)$ \With\ $state_p = init$} \label{line:tab:recvInit} + \IF{$isValidProposal(vote,ts,proof)$} + \STATE \PBroadcast \ $\li{\Prepare,round_p,vote}$ to all \label{line:tab:send-echo} + \STATE $state_p \assign prepared$ \label{line:tab:setStateToEchoed} + \IF{$vote \neq vote_p \wedge ts > ts_p$} + \STATE $vote_p \assign null$ + \STATE $ts_p \assign -1$ + \ENDIF + \ENDIF +\ENDUPON + +\SPACE +\UPON{receiving $\li{\Prepare,round_p,vote}$ \From\ $2f+1$ processes \With\ +$state_p \le prepared$} \label{line:tab:recvEcho} + \STATE $vote_p \assign vote$ \label{line:tab:setV} + \STATE $ts_p \assign round_p$ \label{line:tab:setTs} + \STATE \PBroadcast \ $\li{\Commit,round_p,vote_p}$ to all \label{line:tab:send-commit} + \STATE $state_p \assign committed$ \label{line:tab:setStateToCommitted} + \STATE $proof_p \assign$ \{set of received signed messages\} +\ENDUPON + +\SPACE +\UPON{receiving ($\li{\Commit,round,vote}$ \From\ $2f+1$ processes \With\ $decision_p \neq null$} \label{line:tab:recvCommit} + \STATE $decision_p \assign vote$ \label{line:tab:setStateToDecided} +\ENDUPON + +\SPACE +\UPON{receiving message \textbf{with} $round > round_p$ \From\ $f+1$ processes} \label{line:tab:skipViews} +\STATE $StartRound(round)$ +\ENDUPON + + +\SPACE +\FUNCTION{$StartRound(round)$} \label{line:tab:startRound} + \STATE $round_p \assign round$ + \STATE $state_p \assign init$ + \IF{$\coord(round_p) = p$} + \IF{$ts_p \ge 0$} + \STATE $proposal \assign vote_p$ + \ELSE + \STATE $proposal \assign v_p$ + \ENDIF + \STATE \PBroadcast\ $\li{\Propose,round_p,proposal,ts_p, proof_p}$ to all \label{line:tab:send-propose} + \ENDIF + \STATE \textbf{after} $timeout$ execute $OnTimeout(round_p)$ +\ENDFUNCTION + +\SPACE +\FUNCTION{$OnTimeout(round)$} \label{line:tab:onTimeoutStart} + \IF{$round = round_p$} + \STATE $StartRound(round+1)$ \label{line:tab:onTimeout} + \ENDIF +\ENDFUNCTION + +\SPACE +\FUNCTION{$isValidProposal(vote, ts, proof)$} \label{line:tab:isValidProposal} +\IF{$vote = vote_p \vee ts_p = -1$} + \STATE \textbf{return} true +\ENDIF +\IF{$ts > ts_p$ \textbf{and} $proof$ confirms that $vote$ was locked at round $ts$} + \STATE \textbf{return} true +\ENDIF +\STATE \textbf{return} false +\ENDFUNCTION + +\end{algorithmic} +\caption{Tendermint consensus algorithm} +\label{alg:tendermint} +\end{algorithm} + + +In this section we present a new Byzantine consensus algorithm called Tendermint\footnote{https://github.com/tendermint/tendermint} +(see Algorithm~\ref{alg:tendermint}). +The algorithm requires $n > 3f$ processes to tolerate at most f Byzantine faults, but for simplicity we present the algorithm for the case $n = 3f + 1$. The upon rules of Algorithm~\ref{alg:tendermint} are executed atomically. + +It shares the basic mechanisms called locking and unlocking of a value with DSL algorithm for authenticated faults (Algorithm 2 from \cite{DLS88:jacm}), but algorithm structure and implementation of these mechanisms is different in Tendermint. + +The algorithm proceeds in a sequence of rounds, where each round has a dedicated coordinator (called also proposer), that is defined with a deterministic function $\coord$. A correct process moves to a higher round either if a round timeout has expired (line 37) or if process receives a message from a correct process that is in a higher round (lines 26-27). We assume that messages from higher rounds are internally stored by the messaging layer (in case the rule from line 26 is satisfied, round transition is triggered), and then delivered to a process when it enters the corresponding round. Messages from lower rounds are dropped. + +The locking mechanism has a role in ensuring agreement property of consensus, while the unlocking mechanism is related to termination property. Locking guarantees that two correct processes can lock only single value in some round $r$ (they can however potentially lock different values in distinct rounds). A correct process can have only a single value locked at a particular point of algorithm execution. In Algorithm~\ref{alg:tendermint}, a correct process $p$ locks a value $v$ in round $r$ by setting a variable $vote_p$ to $v$ and a variable $ts_p$ to $r$. The unlocking mechanism ensures that eventually (after GST) all correct processes can have only single value $v$ locked. This helps to bring system into univalent configuration~\cite{FLP85:jacm}. In Algorithm~\ref{alg:tendermint}, a process $p$ unlocks a value by setting $vote_p$ to $null$ and $ts_p$ to -1. Initially, all correct process do not lock any value. + +Each round is composed of three phases (steps) called propose, prepare and commit. A round starts with a propose phase (lines 28-37) in which a dedicated proposer sends its proposal to all processes. If a proposer $p$ has locked some value $v$, then it proposes $v$; otherwise it proposes it's initial value $v_p$ (lines 32-35). In addition to proposed value, $\Propose$ message also contains $ts$ and a proof of locking a value (in case proposal is equal to locked value), that consist of a set of signed messages received by the process $p$ in the round in which the value was locked. + +When a correct process receives a proposal message from a designated proposer (lines 11-17), it checks if the message is a valid proposal (lines 41-46). A correct process $p$ considers proposal as valid if (i) proposal value is equal to process locked value ($vote_p$) or if $p$ hasn't locked any value ($ts_p = -1$), or (ii) if proposal is different value but from a higher round and with a valid proof of locking. If a process accepts proposal as valid, it enters prepare phase by sending proposed value in $PREVOTE$ message to all processes. In case (ii), as there is a valid locked value in a higher round, the process unlocks it's value. + +When a correct process $p$ receives $2f+1$ $PREVOTE$ messages for the same value $v$, then the process locks a value $v$ (and saves a proof of locking into variable $proof_p$) and enters commit step by sending $v$ in $PRECOMMIT$ message (lines 18-23). When process received $2f+1$ $PRECOMMIT$ messages for some value $v$, it decides value $v$. + +Note that algorithm relies on $state$ variable to prevent correct processes from sending more than one $PREVOTE$ and $PRECOMMIT$ messages in a single round $r$. For simplicity, the current version of the algorithm does not provide termination condition, i.e., processes continue round execution even after deciding. We also assume that round timeout is being incremented in each round so that eventually all correct processes are able to receive all messages from correct processes in a round, before the round timeout expires. +Furthermore, the algorithm solves only single instance consensus problem. The multi-instance version of Tendermint will be addressed in the next version of the document. However, it should be relatively straightforward to modify Algorithm~\ref{alg:tendermint} to get multi-instance version with the following modifications: (i) adding consensus instance number in each message, (ii) having per instance algorithm state variables and (iii) adding logic for starting next consensus instance. + +\subsection{Proof of correctness (sketch)} + +\textbf{Agreement} Let assume that a first correct process that decides is $p$ and that $p$ decides value $v$ in round $r$. As $p$ decided $v$ this means that $p$ receives at least $2f+1$ $PRECOMMIT$ messages with value $v$ in the round $r$ (line 24). This means that at least $f+1$ correct processes have locked value $v$ in round $r$ and have it's variable $vote$ set to $v$ and $ts$ variable set to $r$. As correct processes can send only single $PRECOMMIT$ message per round, and there are at most $f$ faulty processes, then no correct process can receive $2f+1$ $PRECOMMIT$ messages in the round $r$ for some other value $v' \neq v$. Therefore, no correct process can decide value different than $v$ in the round $r$. + +Now let consider the smallest round $r' > r$ in which correct process $q$ locks some value $v'$. As $q$ locks value $v'$ this means that $q$ has received at least $2f+1$ $PREVOTE$ messages equal to $v'$ in the round $r'$. Therefore, at least $f+1$ correct processes has sent $v'$ in $PREVOTE$ message in the round $r'$. As $f+1$ correct process $q'$ locked value $v$ in the round $r$, this means that one of those processes sent $PROPOSE$ message with value $v'$ in the round $r'$. According to function $isValidProposal$, $q'$ received valid proof of locking for value $v'$. This is in contradiction with the assumption that $r'$ is the smallest round in which some correct process locks some value (TODO: improve this part, it is not very precise as it could be reasoned that a Byzantine process could create correct proof of locking for some value $v'$ and round greater than $r'$ which can be proved impossible with the similar reasoning). As no correct process can lock any value different from $v$ in rounds $r' > r$, then no correct process can decide value different from $v$. + +\textbf{Termination} + +The Algorithm~\ref{alg:tendermint} does not terminate as the following scenario is possible: we consider rounds after GST, starting from round $r$ and at round $r$ a correct process $p$ is the only process that locks some value $v$ among the correct processes ($0 \le ts_p \le r$). Assume that no correct process has decided. Let assume that $p$ would coordinate the round $r'>r$. In rounds smaller than $r'$ $p$ will reject any proposal from correct processes if it's not equal to $v$ (which is possible if all correct processes have different initial values), so it will not send $PREPARE$ message. As faulty processes could decide not to send any message, no correct processes will be able to lock a value and to decide in those rounds. Now let assume that in round $r'-1$ some correct process $q$ locks value $v' \neq v$ because a faulty process (together with all correct processes except $p$) sends $PREVOTE$ message with $v'$ to $q$. As $q$ locks value $v'$ in round higher than $r$, $q$ will reject proposal from $p$ in round $r'$ so if faulty processes stay silent, again no correct processes can decide in that round. This scenario can be repeated also with the rounds $k-1$ and $k$, in which the process $q$ is proposer. + +The property that needs to be ensured is the following: after GST, there exist a round such that a proposal from a correct process is accepted by all correct processes. The solution could be adding one more step (lock-release step) at the end of each round where correct processes would send it's locked value (together with ts and proof) to all processes, and then a correct process will release lock upon reception of proof of locking for a different value from higher round. We will also need to modify lines 35 and 36 to propose the value with the valid proof of locking from the highest round (if process is aware of such value) or initial value. + + + + diff --git a/definitions.tex b/definitions.tex new file mode 100644 index 000000000..c2a2c3c85 --- /dev/null +++ b/definitions.tex @@ -0,0 +1,46 @@ +\section{Definitions} +\label{sec:definitions} + + +\subsection{Model} + +We consider a system composed of $n$ server processes $\Pi = \{ 1, \dots, n\}$ that communicate by exchanging messages. +All messages sent between processes are digitally signed. +Processes can be \emph{correct} or \emph{faulty}, where correct processes +follow the algorithm and the faulty one may behave in an arbitrary way, i.e., we consider Byzantine faults. +We assume than the number of faulty servers is limited to $f$. + +We consider a partially synchronous system model: +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 correct process $q$, then $q$ will receive $m$ +before $t+\Delta$. +We do not make any assumption +before $GST$. For example, messages among correct processes can be +delayed, dropped or duplicated before $GST$. Spoofing/impersonation attacks are +assumed to be impossible also before $GST$. + +We assume that process steps (which might include +sending and receiving messages) +take zero time. +Processes are equipped with clocks able to measure local timeouts. + + \subsection{Consensus} + \label{sec:consensus} + + \newcommand{\propose}{\mathsf{Propose}} + \newcommand{\decide}{\mathsf{Decide}} + + In the consensus problem, every process has an initial value from a + set $V$ and has eventually to irrevocably decide on a value from + $V$. + The problem is defined by an agreement, a termination, and a validity + property. + + \begin{itemize} + \item \emph{Agreement:} No two correct processes decide differently. + \item \emph{Termination:} All correct processes eventually decide. + \item \emph{Validity:} If all correct processes have the same initial value, and no process + is faulty, then this is the only possible decision value. + \end{itemize} diff --git a/homodel.sty b/homodel.sty new file mode 100644 index 000000000..f5e2ef701 --- /dev/null +++ b/homodel.sty @@ -0,0 +1,19 @@ +\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{\Alg}{\mathcal{A}} +\newcommand{\Pred}{\mathcal{P}} +\newcommand{\Spr}{S_p^r} +\newcommand{\Tpr}{T_p^r} +\newcommand{\mupr}{\vec{\mu}_p^{\,r}} + +\newconstruct{\SEND}{$\Spr$:}{}{\ENDSEND}{} +\newconstruct{\TRAN}{$\Tpr$:}{}{\ENDTRAN}{} +\newconstruct{\ROUND}{\textbf{Round}}{\!\textbf{:}}{\ENDROUND}{} +\newconstruct{\SROUND}{\textbf{Selection Round}}{\!\textbf{:}}{\ENDSROUND}{} +\newconstruct{\VROUND}{\textbf{Validation Round}}{\!\textbf{:}}{\ENDVROUND}{} +\newconstruct{\DROUND}{\textbf{Decision Round}}{\!\textbf{:}}{\ENDDROUND}{} +\newconstruct{\VARIABLES}{\textbf{Variables:}}{}{\ENDVARIABLES}{} +\newconstruct{\INIT}{\textbf{Initialization:}}{}{\ENDINIT}{} diff --git a/latex8.bst b/latex8.bst new file mode 100644 index 000000000..2c7af5647 --- /dev/null +++ b/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/latex8.sty b/latex8.sty new file mode 100644 index 000000000..1e6b0dc7e --- /dev/null +++ b/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/lit.bib b/lit.bib new file mode 100644 index 000000000..573d913b9 --- /dev/null +++ b/lit.bib @@ -0,0 +1,1562 @@ +%--- 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}, +} diff --git a/paper.tex b/paper.tex new file mode 100644 index 000000000..b0c9e11b7 --- /dev/null +++ b/paper.tex @@ -0,0 +1,150 @@ +%\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} +\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{Tendermint Byzantine Consensus Algorithm} + + +\author{\IEEEauthorblockN{Zarko Milosevic} +} + + +% make the title area +\maketitle + + +%\begin{abstract} +%The paper formally introduces blockchain problem and present Tendermint, %algorithm that solves blockchain in the partially synchronous system model. +%\end{abstract} + +%\noindent \textbf{Keywords:} Blockchain, Byzantine Fault Tolerance, State Machine %Replication + + +\input{definitions} +\input{consensus} + +%\section*{Acknowledgment} + + +\bibliographystyle{IEEEtran} +\bibliography{lit} + +%\appendix + +\end{document} diff --git a/readme.md b/readme.md new file mode 100644 index 000000000..8c9f44640 --- /dev/null +++ b/readme.md @@ -0,0 +1,4 @@ +# Tendermint-spec + +The repository contains the specification (and the proofs) of the Tendermint +blockchain protocol. diff --git a/rounddiag.sty b/rounddiag.sty new file mode 100644 index 000000000..cc8c7d0fc --- /dev/null +++ b/rounddiag.sty @@ -0,0 +1,61 @@ +% 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/technote.sty b/technote.sty new file mode 100644 index 000000000..5353f13cd --- /dev/null +++ b/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 +} From 4a81d0a02f9996985c5b621c70b02e369e37df92 Mon Sep 17 00:00:00 2001 From: "Milosevic, Zarko" Date: Fri, 14 Jul 2017 15:27:05 +0200 Subject: [PATCH 002/223] Add three timeouts and align pseudocode better with existing algorithm --- .gitignore | 2 + .idea/vcs.xml | 6 +++ consensus.tex | 132 ++++++++++++++++++++++++++++++++++---------------- paper.dvi | Bin 0 -> 27416 bytes 4 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 .idea/vcs.xml create mode 100644 paper.dvi diff --git a/.gitignore b/.gitignore index 7b044d43f..1270055c1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ *.log *.pdf *.gz +*.dvi +.idea diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/consensus.tex b/consensus.tex index d960b602b..3b7fb9d11 100644 --- a/consensus.tex +++ b/consensus.tex @@ -1,8 +1,8 @@ \section{Tendermint} -\label{sec:consensus} +\label{sec:tendermint} -\newcommand\Propose{\mathsf{PROPOSE}} +\newcommand\Propose{\mathsf{PROPOSAL}} \newcommand\PrePrepare{\mathsf{INIT}} \newcommand\Prepare{\mathsf{PREVOTE}} \newcommand\Commit{\mathsf{PRECOMMIT}} @@ -13,6 +13,13 @@ \newcommand\NewPrePrepare{\mathsf{VC\mbox{-}INIT}} \newcommand\coord{\mathsf{proposer}} +\newcommand\initState{init} +\newcommand\preVoted{pre\mbox{-}voted} +\newcommand\preCommitted{pre\mbox{-}committed} + +\newcommand\timeoutPropose{timeoutPropose} +\newcommand\timeoutPrevote{timeoutPrevote} +\newcommand\timeoutCommit{timeoutPreCommit} \begin{algorithm}[htb!] \def\baselinestretch{1} @@ -22,11 +29,12 @@ \INIT{} \STATE $v_p := v \in V$ \COMMENT{value considered for consensus} \STATE $round_p := 0$ \COMMENT{current round number} - \STATE $vote_p := null$ \COMMENT{locked value} + \STATE $vote_p := nill$ \COMMENT{locked value} \STATE $ts_p := -1$ \COMMENT{ if $ts_p \ge 0$, it represents round in which $vote_p$ has been locked} \STATE $proof_p := \emptyset$ \COMMENT{set of signed messages as a proof that $vote_p$ has been locked in round $ts_p$} - \STATE $state_p \in \set{init=0,prepared=1,comitted=2}$, initially $init$ - \STATE $decision_p = null$ + \STATE $state_p \in \set{\initState=0,\preVoted=1,\preCommitted=2}$, initially $\initState$ + \STATE $decision_p = nill$ + \ENDINIT \SPACE @@ -35,60 +43,96 @@ \ENDUPON \SPACE -\UPON{receiving $\li{\Propose,round_p, vote, ts, proof}$ \From\ $\coord(round_p)$ \With\ $state_p = init$} \label{line:tab:recvInit} +\FUNCTION{$StartRound(round)$} \label{line:tab:startRound} +\STATE $round_p \assign round$ +\STATE $state_p \assign init$ +\IF{$\coord(round_p) = p$} +\IF{$ts_p \ge 0$} +\STATE $proposal \assign vote_p$ +\ELSE +\STATE $proposal \assign v_p$ +\ENDIF +\STATE \PBroadcast\ $\li{\Propose,round_p,proposal,ts_p, proof_p}$ to all \label{line:tab:send-propose} +\ENDIF +\STATE \textbf{after} $\timeoutPropose$ execute $OnTimeoutPropose(round_p)$ +\ENDFUNCTION + + +\SPACE +\UPON{receiving $\li{\Propose,round_p, vote, ts, proof}$ \From\ $\coord(round_p)$ \With\ $state_p = \initState$} \label{line:tab:recvInit} \IF{$isValidProposal(vote,ts,proof)$} \STATE \PBroadcast \ $\li{\Prepare,round_p,vote}$ to all \label{line:tab:send-echo} - \STATE $state_p \assign prepared$ \label{line:tab:setStateToEchoed} + \STATE $state_p \assign \preVoted$ \label{line:tab:setStateToEchoed} \IF{$vote \neq vote_p \wedge ts > ts_p$} - \STATE $vote_p \assign null$ + \STATE $vote_p \assign nill$ \STATE $ts_p \assign -1$ \ENDIF \ENDIF \ENDUPON \SPACE -\UPON{receiving $\li{\Prepare,round_p,vote}$ \From\ $2f+1$ processes \With\ -$state_p \le prepared$} \label{line:tab:recvEcho} +\FUNCTION{$OnTimeoutPropose(round)$} \label{line:tab:onTimeoutPropose} +\IF{$round = round_p \wedge state_p = \initState$} +\STATE \PBroadcast \ $\li{\Prepare,round_p,nill}$ to all +\STATE $state_p \assign \preVoted$ +\ENDIF +\ENDFUNCTION + +\SPACE +\UPON{receiving $\li{\Prepare,round,vote}$ \From\ $2f+1$ processes \With\ +($round = round_p \wedge state_p \le \preVoted$) or $round > round_p$} \label{line:tab:recvEcho} \STATE $vote_p \assign vote$ \label{line:tab:setV} - \STATE $ts_p \assign round_p$ \label{line:tab:setTs} + \IF{$vote = nill$} + \STATE $ts_p \assign -1$ + \ELSE + \STATE $ts_p \assign round$ \label{line:tab:setTs} + \ENDIF + \STATE $round_p \assign round$ \STATE \PBroadcast \ $\li{\Commit,round_p,vote_p}$ to all \label{line:tab:send-commit} - \STATE $state_p \assign committed$ \label{line:tab:setStateToCommitted} + \STATE $state_p \assign \preCommitted$ \label{line:tab:setStateToCommitted} \STATE $proof_p \assign$ \{set of received signed messages\} \ENDUPON \SPACE -\UPON{receiving ($\li{\Commit,round,vote}$ \From\ $2f+1$ processes \With\ $decision_p \neq null$} \label{line:tab:recvCommit} - \STATE $decision_p \assign vote$ \label{line:tab:setStateToDecided} +\UPON{receiving $\li{\Prepare,round_p,*}$ \From\ $2f+1$ processes \With\ + $state_p = \preVoted$ \COMMENT{a sign * denotes any value}} +\STATE \textbf{after} $\timeoutPrevote$ execute $OnTimeoutPrevote(round_p)$ \ENDUPON +\SPACE +\FUNCTION{$OnTimeoutPrevote(round)$} \label{line:tab:onTimeoutPrevote} +\IF{$round = round_p \wedge state_p = \preVoted$} +\STATE \PBroadcast \ $\li{\Commit,round_p,nill}$ to all +\STATE $state_p \assign \preCommitted$ +\ENDIF +\ENDFUNCTION + +\SPACE +\UPON{receiving ($\li{\Commit,round,vote}$ \From\ $2f+1$ processes \With\ $round \ge round_p$} \label{line:tab:recvCommit} + \IF{$vote \neq nill \wedge decision_p = nill$} + \STATE $decision_p \assign vote$ \label{line:tab:setStateToDecided} + \ENDIF + \STATE $StartRound(round+1)$ +\ENDUPON + +\SPACE +\UPON{receiving $\li{\Commit,round_p,*}$ \From\ $2f+1$ processes \With\ + $state_p = \preCommitted$ \COMMENT{a sign * denotes any value}} +\STATE \textbf{after} $\timeoutPreCommit$ execute $OnTimeoutPreCommit(round_p)$ +\ENDUPON + +\SPACE +\FUNCTION{$OnTimeoutPreCommit(round)$} \label{line:tab:onTimeoutCommit} +\IF{$round = round_p \wedge state_p = \preCommitted$} +\STATE $StartRound(round+1)$ +\ENDIF +\ENDFUNCTION + \SPACE \UPON{receiving message \textbf{with} $round > round_p$ \From\ $f+1$ processes} \label{line:tab:skipViews} \STATE $StartRound(round)$ \ENDUPON - -\SPACE -\FUNCTION{$StartRound(round)$} \label{line:tab:startRound} - \STATE $round_p \assign round$ - \STATE $state_p \assign init$ - \IF{$\coord(round_p) = p$} - \IF{$ts_p \ge 0$} - \STATE $proposal \assign vote_p$ - \ELSE - \STATE $proposal \assign v_p$ - \ENDIF - \STATE \PBroadcast\ $\li{\Propose,round_p,proposal,ts_p, proof_p}$ to all \label{line:tab:send-propose} - \ENDIF - \STATE \textbf{after} $timeout$ execute $OnTimeout(round_p)$ -\ENDFUNCTION - -\SPACE -\FUNCTION{$OnTimeout(round)$} \label{line:tab:onTimeoutStart} - \IF{$round = round_p$} - \STATE $StartRound(round+1)$ \label{line:tab:onTimeout} - \ENDIF -\ENDFUNCTION - \SPACE \FUNCTION{$isValidProposal(vote, ts, proof)$} \label{line:tab:isValidProposal} \IF{$vote = vote_p \vee ts_p = -1$} @@ -106,6 +150,8 @@ $state_p \le prepared$} \label{line:tab:recvEcho} \end{algorithm} +\textbf{NOTE: The algorithm description and the corresponding proofs refers to the previous version of the pseudocode and needs to be updated!} + In this section we present a new Byzantine consensus algorithm called Tendermint\footnote{https://github.com/tendermint/tendermint} (see Algorithm~\ref{alg:tendermint}). The algorithm requires $n > 3f$ processes to tolerate at most f Byzantine faults, but for simplicity we present the algorithm for the case $n = 3f + 1$. The upon rules of Algorithm~\ref{alg:tendermint} are executed atomically. @@ -118,22 +164,22 @@ The locking mechanism has a role in ensuring agreement property of consensus, wh Each round is composed of three phases (steps) called propose, prepare and commit. A round starts with a propose phase (lines 28-37) in which a dedicated proposer sends its proposal to all processes. If a proposer $p$ has locked some value $v$, then it proposes $v$; otherwise it proposes it's initial value $v_p$ (lines 32-35). In addition to proposed value, $\Propose$ message also contains $ts$ and a proof of locking a value (in case proposal is equal to locked value), that consist of a set of signed messages received by the process $p$ in the round in which the value was locked. -When a correct process receives a proposal message from a designated proposer (lines 11-17), it checks if the message is a valid proposal (lines 41-46). A correct process $p$ considers proposal as valid if (i) proposal value is equal to process locked value ($vote_p$) or if $p$ hasn't locked any value ($ts_p = -1$), or (ii) if proposal is different value but from a higher round and with a valid proof of locking. If a process accepts proposal as valid, it enters prepare phase by sending proposed value in $PREVOTE$ message to all processes. In case (ii), as there is a valid locked value in a higher round, the process unlocks it's value. +When a correct process receives a proposal message from a designated proposer (lines 11-17), it checks if the message is a valid proposal (lines 41-46). A correct process $p$ considers proposal as valid if (i) proposal value is equal to process locked value ($vote_p$) or if $p$ hasn't locked any value ($ts_p = -1$), or (ii) if proposal is different value but from a higher round and with a valid proof of locking. If a process accepts proposal as valid, it enters prepare phase by sending proposed value in $\Prepare$ message to all processes. In case (ii), as there is a valid locked value in a higher round, the process unlocks it's value. -When a correct process $p$ receives $2f+1$ $PREVOTE$ messages for the same value $v$, then the process locks a value $v$ (and saves a proof of locking into variable $proof_p$) and enters commit step by sending $v$ in $PRECOMMIT$ message (lines 18-23). When process received $2f+1$ $PRECOMMIT$ messages for some value $v$, it decides value $v$. +When a correct process $p$ receives $2f+1$ $\Prepare$ messages for the same value $v$, then the process locks a value $v$ (and saves a proof of locking into variable $proof_p$) and enters commit step by sending $v$ in $\Commit$ message (lines 18-23). When process received $2f+1$ $\Commit$ messages for some value $v$, it decides value $v$. -Note that algorithm relies on $state$ variable to prevent correct processes from sending more than one $PREVOTE$ and $PRECOMMIT$ messages in a single round $r$. For simplicity, the current version of the algorithm does not provide termination condition, i.e., processes continue round execution even after deciding. We also assume that round timeout is being incremented in each round so that eventually all correct processes are able to receive all messages from correct processes in a round, before the round timeout expires. +Note that algorithm relies on $state$ variable to prevent correct processes from sending more than one $\Prepare$ and $\Commit$ messages in a single round $r$. For simplicity, the current version of the algorithm does not provide termination condition, i.e., processes continue round execution even after deciding. We also assume that round timeout is being incremented in each round so that eventually all correct processes are able to receive all messages from correct processes in a round, before the round timeout expires. Furthermore, the algorithm solves only single instance consensus problem. The multi-instance version of Tendermint will be addressed in the next version of the document. However, it should be relatively straightforward to modify Algorithm~\ref{alg:tendermint} to get multi-instance version with the following modifications: (i) adding consensus instance number in each message, (ii) having per instance algorithm state variables and (iii) adding logic for starting next consensus instance. \subsection{Proof of correctness (sketch)} -\textbf{Agreement} Let assume that a first correct process that decides is $p$ and that $p$ decides value $v$ in round $r$. As $p$ decided $v$ this means that $p$ receives at least $2f+1$ $PRECOMMIT$ messages with value $v$ in the round $r$ (line 24). This means that at least $f+1$ correct processes have locked value $v$ in round $r$ and have it's variable $vote$ set to $v$ and $ts$ variable set to $r$. As correct processes can send only single $PRECOMMIT$ message per round, and there are at most $f$ faulty processes, then no correct process can receive $2f+1$ $PRECOMMIT$ messages in the round $r$ for some other value $v' \neq v$. Therefore, no correct process can decide value different than $v$ in the round $r$. +\textbf{Agreement} Let assume that a first correct process that decides is $p$ and that $p$ decides value $v$ in round $r$. As $p$ decided $v$ this means that $p$ receives at least $2f+1$ $\Commit$ messages with value $v$ in the round $r$ (line 24). This means that at least $f+1$ correct processes have locked value $v$ in round $r$ and have it's variable $vote$ set to $v$ and $ts$ variable set to $r$. As correct processes can send only single $\Commit$ message per round, and there are at most $f$ faulty processes, then no correct process can receive $2f+1$ $\Commit$ messages in the round $r$ for some other value $v' \neq v$. Therefore, no correct process can decide value different than $v$ in the round $r$. -Now let consider the smallest round $r' > r$ in which correct process $q$ locks some value $v'$. As $q$ locks value $v'$ this means that $q$ has received at least $2f+1$ $PREVOTE$ messages equal to $v'$ in the round $r'$. Therefore, at least $f+1$ correct processes has sent $v'$ in $PREVOTE$ message in the round $r'$. As $f+1$ correct process $q'$ locked value $v$ in the round $r$, this means that one of those processes sent $PROPOSE$ message with value $v'$ in the round $r'$. According to function $isValidProposal$, $q'$ received valid proof of locking for value $v'$. This is in contradiction with the assumption that $r'$ is the smallest round in which some correct process locks some value (TODO: improve this part, it is not very precise as it could be reasoned that a Byzantine process could create correct proof of locking for some value $v'$ and round greater than $r'$ which can be proved impossible with the similar reasoning). As no correct process can lock any value different from $v$ in rounds $r' > r$, then no correct process can decide value different from $v$. +Now let consider the smallest round $r' > r$ in which correct process $q$ locks some value $v'$. As $q$ locks value $v'$ this means that $q$ has received at least $2f+1$ $\Prepare$ messages equal to $v'$ in the round $r'$. Therefore, at least $f+1$ correct processes has sent $v'$ in $\Prepare$ message in the round $r'$. As $f+1$ correct process $q'$ locked value $v$ in the round $r$, this means that one of those processes sent $PROPOSE$ message with value $v'$ in the round $r'$. According to function $isValidProposal$, $q'$ received valid proof of locking for value $v'$. This is in contradiction with the assumption that $r'$ is the smallest round in which some correct process locks some value (TODO: improve this part, it is not very precise as it could be reasoned that a Byzantine process could create correct proof of locking for some value $v'$ and round greater than $r'$ which can be proved impossible with the similar reasoning). As no correct process can lock any value different from $v$ in rounds $r' > r$, then no correct process can decide value different from $v$. \textbf{Termination} -The Algorithm~\ref{alg:tendermint} does not terminate as the following scenario is possible: we consider rounds after GST, starting from round $r$ and at round $r$ a correct process $p$ is the only process that locks some value $v$ among the correct processes ($0 \le ts_p \le r$). Assume that no correct process has decided. Let assume that $p$ would coordinate the round $r'>r$. In rounds smaller than $r'$ $p$ will reject any proposal from correct processes if it's not equal to $v$ (which is possible if all correct processes have different initial values), so it will not send $PREPARE$ message. As faulty processes could decide not to send any message, no correct processes will be able to lock a value and to decide in those rounds. Now let assume that in round $r'-1$ some correct process $q$ locks value $v' \neq v$ because a faulty process (together with all correct processes except $p$) sends $PREVOTE$ message with $v'$ to $q$. As $q$ locks value $v'$ in round higher than $r$, $q$ will reject proposal from $p$ in round $r'$ so if faulty processes stay silent, again no correct processes can decide in that round. This scenario can be repeated also with the rounds $k-1$ and $k$, in which the process $q$ is proposer. +The Algorithm~\ref{alg:tendermint} does not terminate as the following scenario is possible: we consider rounds after GST, starting from round $r$ and at round $r$ a correct process $p$ is the only process that locks some value $v$ among the correct processes ($0 \le ts_p \le r$). Assume that no correct process has decided. Let assume that $p$ would coordinate the round $r'>r$. In rounds smaller than $r'$ $p$ will reject any proposal from correct processes if it's not equal to $v$ (which is possible if all correct processes have different initial values), so it will not send $PREPARE$ message. As faulty processes could decide not to send any message, no correct processes will be able to lock a value and to decide in those rounds. Now let assume that in round $r'-1$ some correct process $q$ locks value $v' \neq v$ because a faulty process (together with all correct processes except $p$) sends $\Prepare$ message with $v'$ to $q$. As $q$ locks value $v'$ in round higher than $r$, $q$ will reject proposal from $p$ in round $r'$ so if faulty processes stay silent, again no correct processes can decide in that round. This scenario can be repeated also with the rounds $k-1$ and $k$, in which the process $q$ is proposer. The property that needs to be ensured is the following: after GST, there exist a round such that a proposal from a correct process is accepted by all correct processes. The solution could be adding one more step (lock-release step) at the end of each round where correct processes would send it's locked value (together with ts and proof) to all processes, and then a correct process will release lock upon reception of proof of locking for a different value from higher round. We will also need to modify lines 35 and 36 to propose the value with the valid proof of locking from the highest round (if process is aware of such value) or initial value. diff --git a/paper.dvi b/paper.dvi new file mode 100644 index 0000000000000000000000000000000000000000..51821d55d1a4d7bdda0296e61efd1fee3a0ff286 GIT binary patch literal 27416 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 literal 0 HcmV?d00001 From 2866ba1a2c0684338702a892a09521244dd403b4 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Fri, 17 Nov 2017 17:00:28 +0100 Subject: [PATCH 003/223] Align protocol with Tendermint code and add find valid value mechanism --- README.md | 25 +++++ consensus.tex | 249 ++++++++++++++++++++++++++++++------------------ definitions.tex | 68 +++++++------ lit.bib | 18 ++++ paper.tex | 4 +- proof.tex | 2 + readme.md | 4 - 7 files changed, 241 insertions(+), 129 deletions(-) create mode 100644 README.md create mode 100644 proof.tex delete mode 100644 readme.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..e67db6cf0 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# 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/consensus.tex b/consensus.tex index 3b7fb9d11..8399e1438 100644 --- a/consensus.tex +++ b/consensus.tex @@ -2,10 +2,13 @@ \section{Tendermint} \label{sec:tendermint} -\newcommand\Propose{\mathsf{PROPOSAL}} +\newcommand\Disseminate{\textbf{Disseminate}} + +\newcommand\Proposal{\mathsf{PROPOSAL}} +\newcommand\ProposalPart{\mathsf{PROPOSAL\mbox{-}PART}} \newcommand\PrePrepare{\mathsf{INIT}} -\newcommand\Prepare{\mathsf{PREVOTE}} -\newcommand\Commit{\mathsf{PRECOMMIT}} +\newcommand\Prevote{\mathsf{PREVOTE}} +\newcommand\Precommit{\mathsf{PRECOMMIT}} \newcommand\Decision{\mathsf{DECISION}} \newcommand\ViewChange{\mathsf{VC}} @@ -13,13 +16,17 @@ \newcommand\NewPrePrepare{\mathsf{VC\mbox{-}INIT}} \newcommand\coord{\mathsf{proposer}} -\newcommand\initState{init} -\newcommand\preVoted{pre\mbox{-}voted} -\newcommand\preCommitted{pre\mbox{-}committed} +\newcommand\newHeight{newHeight} +\newcommand\newRound{newRound} +\newcommand\nil{nil} +\newcommand\prevote{prevote} +\newcommand\precommit{precommit} +\newcommand\commit{commit} \newcommand\timeoutPropose{timeoutPropose} \newcommand\timeoutPrevote{timeoutPrevote} -\newcommand\timeoutCommit{timeoutPreCommit} +\newcommand\timeoutPrecommit{timeoutPrecommit} +\newcommand\proofOfLocking{proof\mbox{-}of\mbox{-}locking} \begin{algorithm}[htb!] \def\baselinestretch{1} @@ -27,14 +34,13 @@ \begin{algorithmic}[1] \SHORTSPACE \INIT{} - \STATE $v_p := v \in V$ \COMMENT{value considered for consensus} + \STATE $height_p := 0$ \COMMENT{current height, or consensus instance we are currently executing} \STATE $round_p := 0$ \COMMENT{current round number} - \STATE $vote_p := nill$ \COMMENT{locked value} - \STATE $ts_p := -1$ \COMMENT{ if $ts_p \ge 0$, it represents round in which $vote_p$ has been locked} - \STATE $proof_p := \emptyset$ \COMMENT{set of signed messages as a proof that $vote_p$ has been locked in round $ts_p$} - \STATE $state_p \in \set{\initState=0,\preVoted=1,\preCommitted=2}$, initially $\initState$ - \STATE $decision_p = nill$ - + \STATE $step_p \in \set{\propose=0, \prevote=1, \precommit=2, \commit=3}$, initially $\propose$ \COMMENT{current round step} + \STATE $lockedValue := nil$ + \STATE $lockedRound := -1$ + \STATE $validValue := nil$ + \STATE $validRound := -1$ \ENDINIT \SPACE @@ -45,144 +51,199 @@ \SPACE \FUNCTION{$StartRound(round)$} \label{line:tab:startRound} \STATE $round_p \assign round$ -\STATE $state_p \assign init$ -\IF{$\coord(round_p) = p$} -\IF{$ts_p \ge 0$} -\STATE $proposal \assign vote_p$ -\ELSE -\STATE $proposal \assign v_p$ -\ENDIF -\STATE \PBroadcast\ $\li{\Propose,round_p,proposal,ts_p, proof_p}$ to all \label{line:tab:send-propose} +\STATE $state_p \assign \propose$ +\IF{$\coord(height_p, round_p) = p$} + \IF{$lockedValue_p \neq \nil$} + \STATE $proposal \assign lockedValue$ + \ELSIF{$validValue_p \neq \nil$} + \STATE $proposal \assign validValue$ + \ELSE + \STATE $proposal \assign getValue()$ + \ENDIF + \STATE \PBroadcast\ $\li{\Proposal,height_p, round_p, proposal}$ to all \label{line:tab:send-proposal} \ENDIF -\STATE \textbf{after} $\timeoutPropose$ execute $OnTimeoutPropose(round_p)$ +\STATE \textbf{after} $\timeoutPropose$ execute $OnTimeoutPropose(height_p, round_p)$ \ENDFUNCTION - \SPACE -\UPON{receiving $\li{\Propose,round_p, vote, ts, proof}$ \From\ $\coord(round_p)$ \With\ $state_p = \initState$} \label{line:tab:recvInit} - \IF{$isValidProposal(vote,ts,proof)$} - \STATE \PBroadcast \ $\li{\Prepare,round_p,vote}$ to all \label{line:tab:send-echo} - \STATE $state_p \assign \preVoted$ \label{line:tab:setStateToEchoed} - \IF{$vote \neq vote_p \wedge ts > ts_p$} - \STATE $vote_p \assign nill$ - \STATE $ts_p \assign -1$ - \ENDIF +\UPON{receiving $\li{\Proposal,height_p,round_p, proposal}$ \From\ $\coord(height_p,round_p)$ \With\ $state_p = \propose$} \label{line:tab:recvProposal} + \IF{$lockedValue_p \neq \nil$} \label{line:tab:hasLockedValue} + \STATE \PBroadcast \ $\li{\Prevote,height_p,round_p,lockedValue_p}$ to all \label{line:tab:send-locked-value} + \ELSIF{$valid(proposal))$} \label{line:tab:locked-value-nil} + \STATE \PBroadcast \ $\li{\Prevote,height_p,round_p,proposal}$ to all \label{line:tab:send-prevote} + \ELSE + \STATE \PBroadcast \ $\li{\Prevote,height_p,round_p,\nil}$ to all \label{line:tab:send-prevote-nil} \ENDIF + \STATE $state_p \assign \prevote$ \label{line:tab:setStateToPrevote} \ENDUPON \SPACE -\FUNCTION{$OnTimeoutPropose(round)$} \label{line:tab:onTimeoutPropose} -\IF{$round = round_p \wedge state_p = \initState$} -\STATE \PBroadcast \ $\li{\Prepare,round_p,nill}$ to all -\STATE $state_p \assign \preVoted$ +\FUNCTION{$OnTimeoutPropose(height,round)$} \label{line:tab:onTimeoutPropose} +\IF{$height = height_p \wedge round = round_p \wedge state_p = \propose$} + \STATE \PBroadcast \ $\li{\Prevote,height_p,round_p,\nil}$ to all + \STATE $state_p \assign \prevote$ \ENDIF \ENDFUNCTION \SPACE -\UPON{receiving $\li{\Prepare,round,vote}$ \From\ $2f+1$ processes \With\ -($round = round_p \wedge state_p \le \preVoted$) or $round > round_p$} \label{line:tab:recvEcho} - \STATE $vote_p \assign vote$ \label{line:tab:setV} - \IF{$vote = nill$} - \STATE $ts_p \assign -1$ +\UPON{receiving $\li{\Prevote,height_p, round_p,*}$ \From\ at least $2f+1$ processes \With\ + $state_p \le \prevote$} \label{line:tab:recvAny2/3Prevote} + \STATE \textbf{after} $\timeoutPrevote$ execute $OnTimeoutPrevote(height_p, round_p)$ \label{line:tab:timeoutPrevote} +\ENDUPON + +\SPACE +\UPON{receiving $\li{\Prevote,height_p, round_p,v}$ \From\ at least $2f+1$ processes \With\ +$state_p \le \prevote$} \label{line:tab:recvPrevote} + \IF{$v \neq \nil$} + \STATE $lockedValue_p \assign v$ \label{line:tab:setLockedValue} + \STATE $lockedRound_p \assign round_p$ \label{line:tab:setLockedRound} + \STATE \PBroadcast \ $\li{\Precommit,height_p,round_p,lockedValue_p}$ to all \label{line:tab:send-precommit} \ELSE - \STATE $ts_p \assign round$ \label{line:tab:setTs} + \STATE \PBroadcast \ $\li{\Precommit,height_p,round_p,\nil}$ to all \label{line:tab:send-precommit-nil} \ENDIF - \STATE $round_p \assign round$ - \STATE \PBroadcast \ $\li{\Commit,round_p,vote_p}$ to all \label{line:tab:send-commit} - \STATE $state_p \assign \preCommitted$ \label{line:tab:setStateToCommitted} - \STATE $proof_p \assign$ \{set of received signed messages\} + \STATE $state_p \assign \precommit$ \label{line:tab:setStateToCommit} \ENDUPON \SPACE -\UPON{receiving $\li{\Prepare,round_p,*}$ \From\ $2f+1$ processes \With\ - $state_p = \preVoted$ \COMMENT{a sign * denotes any value}} -\STATE \textbf{after} $\timeoutPrevote$ execute $OnTimeoutPrevote(round_p)$ +\UPON{receiving $\li{\Prevote,height_p,round,v}$ \From\ at least $2f+1$ processes \With\ + $round > lockedRound_p$} \label{line:tab:unlockRule} +\IF{$v \neq lockedValue_p$} + \STATE $lockedValue_p \assign \nil$ + \STATE $lockedRound_p \assign -1$ +\ENDIF \ENDUPON \SPACE -\FUNCTION{$OnTimeoutPrevote(round)$} \label{line:tab:onTimeoutPrevote} -\IF{$round = round_p \wedge state_p = \preVoted$} -\STATE \PBroadcast \ $\li{\Commit,round_p,nill}$ to all -\STATE $state_p \assign \preCommitted$ +\UPON{receiving $\li{\Prevote,height_p,round,v}$ \From\ at least $2f+1$ processes \With\ + $v \neq \nil \wedge round > validRound_p$} \label{line:tab:validValueRule} + \STATE $validValue_p \assign v$ \label{line:tab:setValidValue} + \STATE $validRound_p \assign round$ \label{line:tab:setValidRound} +\ENDUPON + +\SPACE +\FUNCTION{$OnTimeoutPrevote(height,round)$} \label{line:tab:onTimeoutPrevote} +\IF{$height = height_p \wedge round = round_p \wedge state_p = \prevote$} + \STATE \PBroadcast \ $\li{\Precommit,height_p,round_p,\nil}$ to all \label{line:tab:precommit-nil-onTimeout} + \STATE $state_p \assign \precommit$ \ENDIF \ENDFUNCTION \SPACE -\UPON{receiving ($\li{\Commit,round,vote}$ \From\ $2f+1$ processes \With\ $round \ge round_p$} \label{line:tab:recvCommit} - \IF{$vote \neq nill \wedge decision_p = nill$} - \STATE $decision_p \assign vote$ \label{line:tab:setStateToDecided} +\UPON{receiving $\li{\Precommit,height_p,round,v}$ \From\ at least $2f+1$ processes \With\ $state_p < \commit$} \label{line:tab:onPrecommitRule} + \IF{$vote \neq \nil$} + \STATE $decide(height_p, v)$ \label{line:tab:decide} + \STATE$height_p \assign height_p + 1$ + \STATE reset $round_p$, $step_p$, $lockedRound_p$, $lockedValue_p$, $validValue_p$ and $validRound$ to init values + \STATE $StartRound(0)$ \ENDIF - \STATE $StartRound(round+1)$ + \IF{$round_p = round$} + \STATE $StartRound(round_p+1)$ + \ENDIF \ENDUPON \SPACE -\UPON{receiving $\li{\Commit,round_p,*}$ \From\ $2f+1$ processes \With\ - $state_p = \preCommitted$ \COMMENT{a sign * denotes any value}} -\STATE \textbf{after} $\timeoutPreCommit$ execute $OnTimeoutPreCommit(round_p)$ +\UPON{receiving $\li{\Precommit,height_p,round_p,*}$ \From\ at least $2f+1$ processes \With\ $state_p = \precommit$} \label{line:tab:startTimeoutPrecommit} + \STATE \textbf{after} $\timeoutPrecommit$ execute $OnTimeoutPrecommit(height_p, round_p)$ \ENDUPON \SPACE -\FUNCTION{$OnTimeoutPreCommit(round)$} \label{line:tab:onTimeoutCommit} -\IF{$round = round_p \wedge state_p = \preCommitted$} -\STATE $StartRound(round+1)$ +\FUNCTION{$OnTimeoutPrecommit(height,round)$} \label{line:tab:onTimeoutPrecommit} +\IF{$height = height_p \wedge round = round_p \wedge state_p = \precommit$} +\STATE $StartRound(round_p+1)$ \ENDIF \ENDFUNCTION \SPACE -\UPON{receiving message \textbf{with} $round > round_p$ \From\ $f+1$ processes} \label{line:tab:skipViews} -\STATE $StartRound(round)$ +\UPON{receiving message \textbf{with} $height = height_p$ and $round > round_p$ \From\ at least $f+1$ processes} \label{line:tab:skipRounds} + \STATE $StartRound(round)$ \ENDUPON -\SPACE -\FUNCTION{$isValidProposal(vote, ts, proof)$} \label{line:tab:isValidProposal} -\IF{$vote = vote_p \vee ts_p = -1$} - \STATE \textbf{return} true -\ENDIF -\IF{$ts > ts_p$ \textbf{and} $proof$ confirms that $vote$ was locked at round $ts$} - \STATE \textbf{return} true -\ENDIF -\STATE \textbf{return} false -\ENDFUNCTION - \end{algorithmic} \caption{Tendermint consensus algorithm} \label{alg:tendermint} \end{algorithm} -\textbf{NOTE: The algorithm description and the corresponding proofs refers to the previous version of the pseudocode and needs to be updated!} - In this section we present a new Byzantine consensus algorithm called Tendermint\footnote{https://github.com/tendermint/tendermint} (see Algorithm~\ref{alg:tendermint}). -The algorithm requires $n > 3f$ processes to tolerate at most f Byzantine faults, but for simplicity we present the algorithm for the case $n = 3f + 1$. The upon rules of Algorithm~\ref{alg:tendermint} are executed atomically. +The algorithm requires $N > 3f$, i.e., faulty processes have together the voting power that is smaller than one third of the total voting power. For simplicity we present the algorithm for the case $N = 3f + 1$. We use the notation "upon receiving message $\li{TAG, h, r, v}$ from at least $X$ processes", to denote that a message with the given field values (* denotes any value) has been received from number of processes with the aggregate voting power equals at least $X$. +The upon rules of Algorithm~\ref{alg:tendermint} are executed atomically. +Algorithm shares the basic mechanisms called the locking and the unlocking of a value with the DLS algorithm for authenticated faults (the Algorithm 2 from \cite{DLS88:jacm}), but the algorithm structure and the implementation of these mechanisms is different in Tendermint. -It shares the basic mechanisms called locking and unlocking of a value with DSL algorithm for authenticated faults (Algorithm 2 from \cite{DLS88:jacm}), but algorithm structure and implementation of these mechanisms is different in Tendermint. +Tendermint consensus algorithm is optimized for the gossip based communication so the consensus layer creates a minimum number of information that needs to be gossiped. Processes exchange only three message types: $\Proposal$, $\Prevote$ and $\Precommit$, and there is only a single mode of execution, i.e., there is no separation between the normal and the recovery mode, which is the case in PBFT-like protocols (e.g., \cite{CL02:tcs}, \cite{Ver09:spinning} or \cite{Cle09:aardvark}). We believe that this makes the protocol simpler to understand and implement correctly, as one of the Tendermint goals is proposing \emph{understandable} Byzantine consensus protocol following Raft path~\cite{Ongaro14:raft}. -The algorithm proceeds in a sequence of rounds, where each round has a dedicated coordinator (called also proposer), that is defined with a deterministic function $\coord$. A correct process moves to a higher round either if a round timeout has expired (line 37) or if process receives a message from a correct process that is in a higher round (lines 26-27). We assume that messages from higher rounds are internally stored by the messaging layer (in case the rule from line 26 is satisfied, round transition is triggered), and then delivered to a process when it enters the corresponding round. Messages from lower rounds are dropped. +The algorithm proceeds in a sequence of rounds, where each round has a dedicated coordinator (called proposer). The assignment scheme of rounds to coordinators is known to all processes and is given as a function $\coord(height, round)$ returning the coordinator for round $r$ in the consensus instance $height$. In the algorithm~\ref{alg:tendermint} processes agree (decides) on a sequence of values (solves multiple instances problem) by sequentially executing one consensus instances after the other. + + Tendermint decomposes the consensus problem into four relatively independent mechanisms (or subproblems) which are discussed in the following subsections: +\begin{itemize} + \item locking mechanism + \item unlocking mechanism + \item finding the right value to propose +\end{itemize} -The locking mechanism has a role in ensuring agreement property of consensus, while the unlocking mechanism is related to termination property. Locking guarantees that two correct processes can lock only single value in some round $r$ (they can however potentially lock different values in distinct rounds). A correct process can have only a single value locked at a particular point of algorithm execution. In Algorithm~\ref{alg:tendermint}, a correct process $p$ locks a value $v$ in round $r$ by setting a variable $vote_p$ to $v$ and a variable $ts_p$ to $r$. The unlocking mechanism ensures that eventually (after GST) all correct processes can have only single value $v$ locked. This helps to bring system into univalent configuration~\cite{FLP85:jacm}. In Algorithm~\ref{alg:tendermint}, a process $p$ unlocks a value by setting $vote_p$ to $null$ and $ts_p$ to -1. Initially, all correct process do not lock any value. +\subsection{Locking mechanism} +\label{sec:locking} -Each round is composed of three phases (steps) called propose, prepare and commit. A round starts with a propose phase (lines 28-37) in which a dedicated proposer sends its proposal to all processes. If a proposer $p$ has locked some value $v$, then it proposes $v$; otherwise it proposes it's initial value $v_p$ (lines 32-35). In addition to proposed value, $\Propose$ message also contains $ts$ and a proof of locking a value (in case proposal is equal to locked value), that consist of a set of signed messages received by the process $p$ in the round in which the value was locked. +A process in Tendermint initially does not have any preference about what the decided value should be ($lockedValue := nil$). The process learns about possible decision value from the coordinator of the current round. As a proposer might be a faulty process, different processes +might receive different suggestions. Using the locking mechanism, correct processes try to converge to a single value within the single round boundary\footnote{We will explain in the part related to safety how we will build on this mechanism to ensure safety across round boundaries.}. +If during the locking phase, a sufficient number of correct processes succeed in agreeing on the common value, then they will later decide on that value. -When a correct process receives a proposal message from a designated proposer (lines 11-17), it checks if the message is a valid proposal (lines 41-46). A correct process $p$ considers proposal as valid if (i) proposal value is equal to process locked value ($vote_p$) or if $p$ hasn't locked any value ($ts_p = -1$), or (ii) if proposal is different value but from a higher round and with a valid proof of locking. If a process accepts proposal as valid, it enters prepare phase by sending proposed value in $\Prepare$ message to all processes. In case (ii), as there is a valid locked value in a higher round, the process unlocks it's value. +More formally, the locking mechanism ensures that two correct processes can lock only a single value in some round $r$ (they can however potentially lock different values in distinct rounds). In Algorithm~\ref{alg:tendermint}, a correct process $p$ locks a value $v$ in round $r$ by setting a variable $lockedValue_p$ to $v$ and a variable $lockedRound_p$ to $r$ (see line~\ref{line:tab:setLockedValue} and \ref{line:tab:setLockedRound}). A correct process can have only a single value locked at a particular point of the algorithm execution. +However, before a value is decided, a process might change his mind and unlock its current value using the unlocking mechanism. -When a correct process $p$ receives $2f+1$ $\Prepare$ messages for the same value $v$, then the process locks a value $v$ (and saves a proof of locking into variable $proof_p$) and enters commit step by sending $v$ in $\Commit$ message (lines 18-23). When process received $2f+1$ $\Commit$ messages for some value $v$, it decides value $v$. +Locking phase starts by a process suggesting what value should be locked in the current round. This is achieved by sending a $\Prevote$ message to all processes. If a correct process has locked some value $v$ ($lockedValue = v \neq \nil$), he votes for $v$ and ignores the suggested value by the proposer (see lines~\ref{line:tab:hasLockedValue}-\ref{line:tab:send-locked-value}). Otherwise, a process might accept the value suggested by the proposer if the proposal is valid\footnote{Validity of a value is an application specific and defined by the function \emph{valid()}.}, and vote for that value (see lines~\ref{line:tab:locked-value-nil}-\ref{line:tab:setStateToPrevote}). In case the suggested value is invalid or a process has not received any proposal from the current coordinator within $\timeoutPropose$ from the start of the current round, it votes with $\nil$ value (see line~\ref{line:tab:send-prevote-nil} and line \ref{line:tab:onTimeoutPropose}). Note that $\nil$ is special value that can not be locked, and it is used to inform others that the algorithm step has not completed successfully; in case of the locking phase, a process has not received a valid value from the proposer on time. -Note that algorithm relies on $state$ variable to prevent correct processes from sending more than one $\Prepare$ and $\Commit$ messages in a single round $r$. For simplicity, the current version of the algorithm does not provide termination condition, i.e., processes continue round execution even after deciding. We also assume that round timeout is being incremented in each round so that eventually all correct processes are able to receive all messages from correct processes in a round, before the round timeout expires. -Furthermore, the algorithm solves only single instance consensus problem. The multi-instance version of Tendermint will be addressed in the next version of the document. However, it should be relatively straightforward to modify Algorithm~\ref{alg:tendermint} to get multi-instance version with the following modifications: (i) adding consensus instance number in each message, (ii) having per instance algorithm state variables and (iii) adding logic for starting next consensus instance. +A process locks value $v$ if it receives at least $2f+1$ $\Prevote$ messages for the value $v$ in the current round (see lines~\ref{line:tab:recvPrevote}-\ref{line:tab:setLockedRound}). A correct process sends only a single $\Prevote$ message in every round. Algorithm relies on the $state$ variable to prevent correct processes from sending more than one $\Prevote$ message in a single round. As a correct process sends a single $\Prevote$ message in a round, and $f$ is the total voting power of faulty processes, two correct processes cannot receive $2f+1$ $\Prevote$ messages for different values in a single round. So if two correct processes lock some value in a given round $r$, then it must be the same value. We will prove this formally in the section~\ref{sec:proof}. -\subsection{Proof of correctness (sketch)} +The locking phase ends by processes informing others if they locked some value in the current round (or not) by sending $\Precommit$ message. If a process has locked some value $v$ in the current round, then this value is sent in the $\Precommit$ message (see line~\ref{line:tab:send-precommit}); otherwise $\nil$ is sent (line~\ref{line:tab:send-precommit-nil} and \ref{line:tab:precommit-nil-onTimeout}). A correct process sends only single $\Precommit$ message in every round. Similarly as with the $\Prevote$ message, the algorithm relies on the $state$ variable to prevent correct processes from sending more than one $\Precommit$ message in a single round. -\textbf{Agreement} Let assume that a first correct process that decides is $p$ and that $p$ decides value $v$ in round $r$. As $p$ decided $v$ this means that $p$ receives at least $2f+1$ $\Commit$ messages with value $v$ in the round $r$ (line 24). This means that at least $f+1$ correct processes have locked value $v$ in round $r$ and have it's variable $vote$ set to $v$ and $ts$ variable set to $r$. As correct processes can send only single $\Commit$ message per round, and there are at most $f$ faulty processes, then no correct process can receive $2f+1$ $\Commit$ messages in the round $r$ for some other value $v' \neq v$. Therefore, no correct process can decide value different than $v$ in the round $r$. +\subsection{Unlocking mechanism} +\label{sec:unlocking} -Now let consider the smallest round $r' > r$ in which correct process $q$ locks some value $v'$. As $q$ locks value $v'$ this means that $q$ has received at least $2f+1$ $\Prepare$ messages equal to $v'$ in the round $r'$. Therefore, at least $f+1$ correct processes has sent $v'$ in $\Prepare$ message in the round $r'$. As $f+1$ correct process $q'$ locked value $v$ in the round $r$, this means that one of those processes sent $PROPOSE$ message with value $v'$ in the round $r'$. According to function $isValidProposal$, $q'$ received valid proof of locking for value $v'$. This is in contradiction with the assumption that $r'$ is the smallest round in which some correct process locks some value (TODO: improve this part, it is not very precise as it could be reasoned that a Byzantine process could create correct proof of locking for some value $v'$ and round greater than $r'$ which can be proved impossible with the similar reasoning). As no correct process can lock any value different from $v$ in rounds $r' > r$, then no correct process can decide value different from $v$. +Initially, a correct process does not lock any value. During the algorithm execution, processes might end up locking different values in different rounds. As we discussed in the locking section (section~\ref{sec:locking}), a correct process that has locked some value will vote +only for that value at the beginning of the locking phase. With correct processes voting for different values during the locking phase, it is not possible to reach an agreement what value should be locked, and later decided. Without unlocking mechanism in place, the algorithm could loop forever in that state, and no decision will be reached. The unlocking mechanism is therefore essential for the algorithm termination as it allows processes to release locks and try to converge to a common value in the following rounds. In Algorithm~\ref{alg:tendermint}, a correct process $p$ unlocks a value by setting $lockedValue_p$ to $\nil$ and $lockedRound_p$ to $-1$. -\textbf{Termination} +A correct process $p$ releases its locked value if he observes that some correct process might\footnote{We say that other process \emph{might} have locked different value as we do not get a direct message from that process claiming that he locked a value with the corresponding proof of locking. We infer this information by observing $\Prevote$ messages.} have locked a different value in the round $r$ higher than the current $lockedRound_p$ ($r > lockedRound_p$). This is the case if a process $p$ receives at least $2f+1$ $\Prevote$ messages with value $v' \neq lockedValue_p$ and $round > lockedRound_p$ (see the rule starting at line~\ref{line:tab:unlockRule}). The process also implicitly releases a lock on its current value by locking a different value at lines~\ref{line:tab:setLockedValue} and \ref{line:tab:setLockedRound}. -The Algorithm~\ref{alg:tendermint} does not terminate as the following scenario is possible: we consider rounds after GST, starting from round $r$ and at round $r$ a correct process $p$ is the only process that locks some value $v$ among the correct processes ($0 \le ts_p \le r$). Assume that no correct process has decided. Let assume that $p$ would coordinate the round $r'>r$. In rounds smaller than $r'$ $p$ will reject any proposal from correct processes if it's not equal to $v$ (which is possible if all correct processes have different initial values), so it will not send $PREPARE$ message. As faulty processes could decide not to send any message, no correct processes will be able to lock a value and to decide in those rounds. Now let assume that in round $r'-1$ some correct process $q$ locks value $v' \neq v$ because a faulty process (together with all correct processes except $p$) sends $\Prepare$ message with $v'$ to $q$. As $q$ locks value $v'$ in round higher than $r$, $q$ will reject proposal from $p$ in round $r'$ so if faulty processes stay silent, again no correct processes can decide in that round. This scenario can be repeated also with the rounds $k-1$ and $k$, in which the process $q$ is proposer. +The unlocking mechanism, together with the underlying gossip layer, ensures the following properties: -The property that needs to be ensured is the following: after GST, there exist a round such that a proposal from a correct process is accepted by all correct processes. The solution could be adding one more step (lock-release step) at the end of each round where correct processes would send it's locked value (together with ts and proof) to all processes, and then a correct process will release lock upon reception of proof of locking for a different value from higher round. We will also need to modify lines 35 and 36 to propose the value with the valid proof of locking from the highest round (if process is aware of such value) or initial value. +\begin{itemize} + \item (i) if a correct process $p$ locks a value $v$ in a round $r$ during a good period (period of synchrony), then at the end of the round $r$ all correct processes will lock either $v$ or they will not lock any value + \item (ii) if a correct process $p$ locks a value $v$ in a round $r$, then every correct process $q$ that has locked a different value in some of the previous rounds, i.e., $lockedValue_q \neq v$ and $lockedRound_q < r$, will eventually unlock it's value. +\end{itemize} - +From the property (ii) follows that after a sufficiently big good period in which no correct process manages to lock a value, all correct processes will lock either some common value $v$ or they will not lock any value (where $v$ is the value locked in the highest round $r$ by some correct process). This follows from the fact that processes keep resending messages at the gossip layer, so during good periods, $\Prevote$ messages that led to the correct process locking the value $v$ in the round $r$ are received by all correct processes. Therefore, they either stay with $v$ as its locked value, or unlock its current value. + +Both properties ensure that during a sufficiently long good period, there will eventually be a round, such that at the beginning of this round, all correct processes will lock the same value or they will not lock any value. As we will explain in the following section (\ref{sec:findRighValue}), this is one of the essential mechanisms for ensuring termination of the Tendermint consensus algorithm. + +\subsection{Finding the right value to propose} +\label{sec:findRighValue} + +As we discussed in section~\ref{sec:locking}, a correct process in Tendermint votes always for its locked value (if it exists) or for the \emph{valid} value suggested by the coordinator of the current round. In order to reach a decision in some round $r$ it is necessary that all correct processes vote for the same value in that round (we do not depend on faulty processes for termination). Then correct processes can lock that value, and later decide. Tendermint relies on the coordinator to ensure that processes that have not locked any value, vote for the same value. As a correct process always votes for its locked value, then several conditions need to be fulfilled just before the round $r$ starts so we can achieve such preferable scenario: + +\begin{enumerate} + \item if a correct process $p$ has locked some value $v$ at the beginning of the round $r$, then all other correct processes have either locked $v$ or they have not locked any value. \label{enum:termination:v-is-locked} + \item the proposer is a correct process, and a value suggested by the proposer is $v$. \label{enum:termination:v-is-proposed} +\end{enumerate} + +In case those conditions hold, all correct processes will lock $v$ in the round $r$ and later decide. + +The existence of a round where the condition \ref{enum:termination:v-is-locked}) holds, follows from the unlocking mechanism. The first part of the condition \ref{enum:termination:v-is-proposed}) follows from the fact that $\coord$ function eventually provides a correct proposer. The second part of the condition \ref{enum:termination:v-is-proposed}) is more tricky as the proposer needs to suggest exactly the value that is locked by correct processes. If the proposer has locked $v$ at the beginning of the round $r$, then it trivially follows from the condition \ref{enum:termination:v-is-locked}). Otherwise, we need an additional mechanism that ensures that the correct proposer suggests $v$. + +In order to ensure this condition, Tendermint has an additional mechanism for determining what is the good value to suggest such that this condition holds. It is implemented using variables $validValue$ and $validRound$. Using these two variables every process tracks the highest round ($validRound$) in which correct process might have locked some value ($validValue$). So whenever process observes at least $2f+1$ $\Prevote$ messages with $round > validRound_p$ (see the rule at line~\ref{line:tab:validValueRule}) for some value $v \neq \nil$, +it updates $validValue$ and $validRound$ variables. + +This mechanism, together with the underlying gossip layer, ensures the following properties: +\begin{itemize} + \item (i) if a correct process $p$ locks a value $v$ in a round $r$ during a good period (period of synchrony), then at the end of the round $r$ all correct processes will have $validValue$ equal to $v$ and $validRound$ equal to $r$. + \item (ii) if no correct process $p$ locks some value during a sufficiently long good period, then all correct processes will eventually have $validValue$ equal to the same value $v$. +\end{itemize} + +If during a good period, no correct process locks a value during a sufficiently long period (so the property i) does not hold), then +all correct processes will have $validValue$ equal to $v$ due to the following reasoning: During this time period, all correct processes will receive delayed $\li{Prevote}$ messages from previous rounds (because of resending messages at the gossip layer). Now let denote with $r$ and $v$, a round and a value, such that there are at least $2f+1$ $\li{Prevote}$ messages with $r$ and $v$, and $r$ is the highest among such $(*, r)$ pairs. Then all correct processes will receive those messages and set $validValue$ to $v$. + +In both cases, during sufficiently long good period, there will eventually be a round, such that at the beginning of this round, all correct processes will have $validValue$ equal to the same value; and in case some correct process has locked some value $v$, then the $validValue$ will be equal to $v$. + +The unlocking mechanism and the mechanism for determining the right proposal value ensures that during a period of synchrony, there will be a round $r$ such that (i) all correct processes either lock the same value $v$ or they do not lock any value, and (ii) the $validValue$ is equal to $v$ at all correct processes. If a proposer of such round $r$ is correct, then he proposes $v$, all correct processes vote for $v$ during the locking phase; therefore they lock $v$, send a $\Precommit$ message with $v$ and later decide. The formal proof of termination is provided in section~\ref{sec:proof}. diff --git a/definitions.tex b/definitions.tex index c2a2c3c85..6ef188a98 100644 --- a/definitions.tex +++ b/definitions.tex @@ -4,43 +4,51 @@ \subsection{Model} -We consider a system composed of $n$ server processes $\Pi = \{ 1, \dots, n\}$ that communicate by exchanging messages. -All messages sent between processes are digitally signed. -Processes can be \emph{correct} or \emph{faulty}, where correct processes -follow the algorithm and the faulty one may behave in an arbitrary way, i.e., we consider Byzantine faults. -We assume than the number of faulty servers is limited to $f$. +We consider a system composed of $n$ server processes $\Pi = \{ 1, \dots, n\}$. +Server 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, and we denote with $N$ the total voting power of all processes in the system. +Furthermore, we assume that the total voting power of faulty processes is bounded with a system parameter $f$. -We consider a partially synchronous system model: -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 correct process $q$, then $q$ will receive $m$ -before $t+\Delta$. -We do not make any assumption -before $GST$. For example, messages among correct processes can be -delayed, dropped or duplicated before $GST$. Spoofing/impersonation attacks are -assumed to be impossible also before $GST$. +Processes communicate by exchanging messages. We assume a model where processes are not part of the single administration domain; therefore we cannot enforce a direct network connectivity between all server processes. +Instead, we assume that each server process is connected to a small number of processes called peers, +such that there is an indirect communication channel between all correct server processes. Communication between processes is established using gossip protocol. + +We assume that processes communicate over wide-area network. Formally, we model the network communication using the partially synchronous system model~\cite{DLS88:jacm}, or rather a slightly weaker variant of this model: we assume that the system alternates between good periods (during which the system is synchronous) and bad periods (during which the system is asynchronous and messages can be lost). During good periods, there is a bound $\Delta$ such that all +communication among correct processes is reliable and $\Delta$-timely, i.e., if a correct process +$p$ sends message $m$ at time $t$ to a correct process $q$, then $q$ will receive $m$ +before $t+\Delta$. Note that as we do not assume direct communication channels among all correct processes, this implies that before the message $m$ reaches $q$, it will pass through a number of +correct servers that will forward the message $m$ using gossip protocol towards $q$. +Messages among correct processes can be +delayed, dropped or duplicated during bad periods. Spoofing/impersonation attacks are assumed to be impossible also during bad periods. +The bound $\Delta$ is a system parameter whose value is not required to be known for the safety of the algorithm presented. However, the timing bounds derived for our algorithm, and thus the guaranteed latency requires knowledge of $\Delta$. + +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. + +We assume integrity of direct communication channels between peers, that is if a process $p$ received a message $m$ from process $q$, then $q$ sent message $m$ to $p$ before. All protocol messages are signed, i.e., when a correct process $q$ receives a signed message $m$ from its peer, the process $q$ can verify who was the original message sender. + +The details of the Tendermint gossip protocol will be discussed in a separate technical report. For the sake of this report it is sufficient to assume the following properties provided by the gossip protocol (in addition to the properties about partial synchrony of the network stated above): + +\begin{itemize} + \item Messages that are being gossiped come from 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. +\end{itemize} -We assume that process steps (which might include -sending and receiving messages) -take zero time. -Processes are equipped with clocks able to measure local timeouts. \subsection{Consensus} \label{sec:consensus} - \newcommand{\propose}{\mathsf{Propose}} - \newcommand{\decide}{\mathsf{Decide}} + \newcommand{\propose}{\mathsf{propose}} + \newcommand{\decide}{\mathsf{decide}} - In the consensus problem, every process has an initial value from a - set $V$ and has eventually to irrevocably decide on a value from - $V$. - The problem is defined by an agreement, a termination, and a validity - property. +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 differently. - \item \emph{Termination:} All correct processes eventually decide. - \item \emph{Validity:} If all correct processes have the same initial value, and no process - is faulty, then this is the only possible decision value. + \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 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/lit.bib b/lit.bib index 573d913b9..7a6eec996 100644 --- a/lit.bib +++ b/lit.bib @@ -1560,3 +1560,21 @@ 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} +} diff --git a/paper.tex b/paper.tex index b0c9e11b7..4415a9200 100644 --- a/paper.tex +++ b/paper.tex @@ -121,7 +121,8 @@ \title{Tendermint Byzantine Consensus Algorithm} -\author{\IEEEauthorblockN{Zarko Milosevic} +\author{\IEEEauthorblockN{Ethan Buchman, Jae Kwon and Zarko Milosevic} +% \IEEEauthorblockN{Tendermint}\ } @@ -138,6 +139,7 @@ \input{definitions} \input{consensus} +\input{proof} %\section*{Acknowledgment} diff --git a/proof.tex b/proof.tex new file mode 100644 index 000000000..5554a9edb --- /dev/null +++ b/proof.tex @@ -0,0 +1,2 @@ +\section{Formal proof of Tendermint consensus algorithm} +\label{sec:proof} \ No newline at end of file diff --git a/readme.md b/readme.md deleted file mode 100644 index 8c9f44640..000000000 --- a/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -# Tendermint-spec - -The repository contains the specification (and the proofs) of the Tendermint -blockchain protocol. From 87abbf78e6855862c42359513b2ce59b3c839e4f Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 3 Sep 2019 14:07:40 +0200 Subject: [PATCH 004/223] 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 --- README.md | 28 +- consensus.tex | 249 ----------- IEEEtran.bst => consensus/IEEEtran.bst | 0 IEEEtran.cls => consensus/IEEEtran.cls | 0 consensus/README.md | 25 ++ .../algorithmicplus.sty | 0 consensus/conclusion.tex | 16 + consensus/consensus.tex | 397 ++++++++++++++++++ consensus/definitions.tex | 117 ++++++ homodel.sty => consensus/homodel.sty | 17 +- consensus/intro.tex | 138 ++++++ latex8.bst => consensus/latex8.bst | 0 latex8.sty => consensus/latex8.sty | 0 lit.bib => consensus/lit.bib | 81 +++- paper.tex => consensus/paper.tex | 29 +- consensus/proof.tex | 280 ++++++++++++ rounddiag.sty => consensus/rounddiag.sty | 17 +- technote.sty => consensus/technote.sty | 0 definitions.tex | 54 --- paper.dvi | Bin 27416 -> 0 bytes proof.tex | 2 - 21 files changed, 1097 insertions(+), 353 deletions(-) delete mode 100644 consensus.tex rename IEEEtran.bst => consensus/IEEEtran.bst (100%) rename IEEEtran.cls => consensus/IEEEtran.cls (100%) create mode 100644 consensus/README.md rename algorithmicplus.sty => consensus/algorithmicplus.sty (100%) create mode 100644 consensus/conclusion.tex create mode 100644 consensus/consensus.tex create mode 100644 consensus/definitions.tex rename homodel.sty => consensus/homodel.sty (75%) create mode 100644 consensus/intro.tex rename latex8.bst => consensus/latex8.bst (100%) rename latex8.sty => consensus/latex8.sty (100%) rename lit.bib => consensus/lit.bib (94%) rename paper.tex => consensus/paper.tex (78%) create mode 100644 consensus/proof.tex rename rounddiag.sty => consensus/rounddiag.sty (87%) rename technote.sty => consensus/technote.sty (100%) delete mode 100644 definitions.tex delete mode 100644 paper.dvi delete mode 100644 proof.tex diff --git a/README.md b/README.md index e67db6cf0..068a3c171 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,7 @@ -# Tendermint-spec +# 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` +This repository contains the paper describing the Tendermint consensus +algorithm, including formal proofs of its safety and liveness. +For the pdf, see the [latest +release](https://github.com/tendermint/spec/releases). diff --git a/consensus.tex b/consensus.tex deleted file mode 100644 index 8399e1438..000000000 --- a/consensus.tex +++ /dev/null @@ -1,249 +0,0 @@ - -\section{Tendermint} -\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\prevote{prevote} -\newcommand\precommit{precommit} -\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 $height_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=0, \prevote=1, \precommit=2, \commit=3}$, initially $\propose$ \COMMENT{current round step} - \STATE $lockedValue := nil$ - \STATE $lockedRound := -1$ - \STATE $validValue := nil$ - \STATE $validRound := -1$ -\ENDINIT - -\SPACE -\UPON{start} - \STATE $StartRound(0)$ -\ENDUPON - -\SPACE -\FUNCTION{$StartRound(round)$} \label{line:tab:startRound} -\STATE $round_p \assign round$ -\STATE $state_p \assign \propose$ -\IF{$\coord(height_p, round_p) = p$} - \IF{$lockedValue_p \neq \nil$} - \STATE $proposal \assign lockedValue$ - \ELSIF{$validValue_p \neq \nil$} - \STATE $proposal \assign validValue$ - \ELSE - \STATE $proposal \assign getValue()$ - \ENDIF - \STATE \PBroadcast\ $\li{\Proposal,height_p, round_p, proposal}$ to all \label{line:tab:send-proposal} -\ENDIF -\STATE \textbf{after} $\timeoutPropose$ execute $OnTimeoutPropose(height_p, round_p)$ -\ENDFUNCTION - -\SPACE -\UPON{receiving $\li{\Proposal,height_p,round_p, proposal}$ \From\ $\coord(height_p,round_p)$ \With\ $state_p = \propose$} \label{line:tab:recvProposal} - \IF{$lockedValue_p \neq \nil$} \label{line:tab:hasLockedValue} - \STATE \PBroadcast \ $\li{\Prevote,height_p,round_p,lockedValue_p}$ to all \label{line:tab:send-locked-value} - \ELSIF{$valid(proposal))$} \label{line:tab:locked-value-nil} - \STATE \PBroadcast \ $\li{\Prevote,height_p,round_p,proposal}$ to all \label{line:tab:send-prevote} - \ELSE - \STATE \PBroadcast \ $\li{\Prevote,height_p,round_p,\nil}$ to all \label{line:tab:send-prevote-nil} - \ENDIF - \STATE $state_p \assign \prevote$ \label{line:tab:setStateToPrevote} -\ENDUPON - -\SPACE -\FUNCTION{$OnTimeoutPropose(height,round)$} \label{line:tab:onTimeoutPropose} -\IF{$height = height_p \wedge round = round_p \wedge state_p = \propose$} - \STATE \PBroadcast \ $\li{\Prevote,height_p,round_p,\nil}$ to all - \STATE $state_p \assign \prevote$ -\ENDIF -\ENDFUNCTION - -\SPACE -\UPON{receiving $\li{\Prevote,height_p, round_p,*}$ \From\ at least $2f+1$ processes \With\ - $state_p \le \prevote$} \label{line:tab:recvAny2/3Prevote} - \STATE \textbf{after} $\timeoutPrevote$ execute $OnTimeoutPrevote(height_p, round_p)$ \label{line:tab:timeoutPrevote} -\ENDUPON - -\SPACE -\UPON{receiving $\li{\Prevote,height_p, round_p,v}$ \From\ at least $2f+1$ processes \With\ -$state_p \le \prevote$} \label{line:tab:recvPrevote} - \IF{$v \neq \nil$} - \STATE $lockedValue_p \assign v$ \label{line:tab:setLockedValue} - \STATE $lockedRound_p \assign round_p$ \label{line:tab:setLockedRound} - \STATE \PBroadcast \ $\li{\Precommit,height_p,round_p,lockedValue_p}$ to all \label{line:tab:send-precommit} - \ELSE - \STATE \PBroadcast \ $\li{\Precommit,height_p,round_p,\nil}$ to all \label{line:tab:send-precommit-nil} - \ENDIF - \STATE $state_p \assign \precommit$ \label{line:tab:setStateToCommit} -\ENDUPON - -\SPACE -\UPON{receiving $\li{\Prevote,height_p,round,v}$ \From\ at least $2f+1$ processes \With\ - $round > lockedRound_p$} \label{line:tab:unlockRule} -\IF{$v \neq lockedValue_p$} - \STATE $lockedValue_p \assign \nil$ - \STATE $lockedRound_p \assign -1$ -\ENDIF -\ENDUPON - -\SPACE -\UPON{receiving $\li{\Prevote,height_p,round,v}$ \From\ at least $2f+1$ processes \With\ - $v \neq \nil \wedge round > validRound_p$} \label{line:tab:validValueRule} - \STATE $validValue_p \assign v$ \label{line:tab:setValidValue} - \STATE $validRound_p \assign round$ \label{line:tab:setValidRound} -\ENDUPON - -\SPACE -\FUNCTION{$OnTimeoutPrevote(height,round)$} \label{line:tab:onTimeoutPrevote} -\IF{$height = height_p \wedge round = round_p \wedge state_p = \prevote$} - \STATE \PBroadcast \ $\li{\Precommit,height_p,round_p,\nil}$ to all \label{line:tab:precommit-nil-onTimeout} - \STATE $state_p \assign \precommit$ -\ENDIF -\ENDFUNCTION - -\SPACE -\UPON{receiving $\li{\Precommit,height_p,round,v}$ \From\ at least $2f+1$ processes \With\ $state_p < \commit$} \label{line:tab:onPrecommitRule} - \IF{$vote \neq \nil$} - \STATE $decide(height_p, v)$ \label{line:tab:decide} - \STATE$height_p \assign height_p + 1$ - \STATE reset $round_p$, $step_p$, $lockedRound_p$, $lockedValue_p$, $validValue_p$ and $validRound$ to init values - \STATE $StartRound(0)$ - \ENDIF - \IF{$round_p = round$} - \STATE $StartRound(round_p+1)$ - \ENDIF -\ENDUPON - -\SPACE -\UPON{receiving $\li{\Precommit,height_p,round_p,*}$ \From\ at least $2f+1$ processes \With\ $state_p = \precommit$} \label{line:tab:startTimeoutPrecommit} - \STATE \textbf{after} $\timeoutPrecommit$ execute $OnTimeoutPrecommit(height_p, round_p)$ -\ENDUPON - -\SPACE -\FUNCTION{$OnTimeoutPrecommit(height,round)$} \label{line:tab:onTimeoutPrecommit} -\IF{$height = height_p \wedge round = round_p \wedge state_p = \precommit$} -\STATE $StartRound(round_p+1)$ -\ENDIF -\ENDFUNCTION - -\SPACE -\UPON{receiving message \textbf{with} $height = height_p$ and $round > round_p$ \From\ at least $f+1$ processes} \label{line:tab:skipRounds} - \STATE $StartRound(round)$ -\ENDUPON - -\end{algorithmic} -\caption{Tendermint consensus algorithm} -\label{alg:tendermint} -\end{algorithm} - - -In this section we present a new Byzantine consensus algorithm called Tendermint\footnote{https://github.com/tendermint/tendermint} -(see Algorithm~\ref{alg:tendermint}). -The algorithm requires $N > 3f$, i.e., faulty processes have together the voting power that is smaller than one third of the total voting power. For simplicity we present the algorithm for the case $N = 3f + 1$. We use the notation "upon receiving message $\li{TAG, h, r, v}$ from at least $X$ processes", to denote that a message with the given field values (* denotes any value) has been received from number of processes with the aggregate voting power equals at least $X$. -The upon rules of Algorithm~\ref{alg:tendermint} are executed atomically. -Algorithm shares the basic mechanisms called the locking and the unlocking of a value with the DLS algorithm for authenticated faults (the Algorithm 2 from \cite{DLS88:jacm}), but the algorithm structure and the implementation of these mechanisms is different in Tendermint. - -Tendermint consensus algorithm is optimized for the gossip based communication so the consensus layer creates a minimum number of information that needs to be gossiped. Processes exchange only three message types: $\Proposal$, $\Prevote$ and $\Precommit$, and there is only a single mode of execution, i.e., there is no separation between the normal and the recovery mode, which is the case in PBFT-like protocols (e.g., \cite{CL02:tcs}, \cite{Ver09:spinning} or \cite{Cle09:aardvark}). We believe that this makes the protocol simpler to understand and implement correctly, as one of the Tendermint goals is proposing \emph{understandable} Byzantine consensus protocol following Raft path~\cite{Ongaro14:raft}. - -The algorithm proceeds in a sequence of rounds, where each round has a dedicated coordinator (called proposer). The assignment scheme of rounds to coordinators is known to all processes and is given as a function $\coord(height, round)$ returning the coordinator for round $r$ in the consensus instance $height$. In the algorithm~\ref{alg:tendermint} processes agree (decides) on a sequence of values (solves multiple instances problem) by sequentially executing one consensus instances after the other. - - Tendermint decomposes the consensus problem into four relatively independent mechanisms (or subproblems) which are discussed in the following subsections: -\begin{itemize} - \item locking mechanism - \item unlocking mechanism - \item finding the right value to propose -\end{itemize} - -\subsection{Locking mechanism} -\label{sec:locking} - -A process in Tendermint initially does not have any preference about what the decided value should be ($lockedValue := nil$). The process learns about possible decision value from the coordinator of the current round. As a proposer might be a faulty process, different processes -might receive different suggestions. Using the locking mechanism, correct processes try to converge to a single value within the single round boundary\footnote{We will explain in the part related to safety how we will build on this mechanism to ensure safety across round boundaries.}. -If during the locking phase, a sufficient number of correct processes succeed in agreeing on the common value, then they will later decide on that value. - -More formally, the locking mechanism ensures that two correct processes can lock only a single value in some round $r$ (they can however potentially lock different values in distinct rounds). In Algorithm~\ref{alg:tendermint}, a correct process $p$ locks a value $v$ in round $r$ by setting a variable $lockedValue_p$ to $v$ and a variable $lockedRound_p$ to $r$ (see line~\ref{line:tab:setLockedValue} and \ref{line:tab:setLockedRound}). A correct process can have only a single value locked at a particular point of the algorithm execution. -However, before a value is decided, a process might change his mind and unlock its current value using the unlocking mechanism. - -Locking phase starts by a process suggesting what value should be locked in the current round. This is achieved by sending a $\Prevote$ message to all processes. If a correct process has locked some value $v$ ($lockedValue = v \neq \nil$), he votes for $v$ and ignores the suggested value by the proposer (see lines~\ref{line:tab:hasLockedValue}-\ref{line:tab:send-locked-value}). Otherwise, a process might accept the value suggested by the proposer if the proposal is valid\footnote{Validity of a value is an application specific and defined by the function \emph{valid()}.}, and vote for that value (see lines~\ref{line:tab:locked-value-nil}-\ref{line:tab:setStateToPrevote}). In case the suggested value is invalid or a process has not received any proposal from the current coordinator within $\timeoutPropose$ from the start of the current round, it votes with $\nil$ value (see line~\ref{line:tab:send-prevote-nil} and line \ref{line:tab:onTimeoutPropose}). Note that $\nil$ is special value that can not be locked, and it is used to inform others that the algorithm step has not completed successfully; in case of the locking phase, a process has not received a valid value from the proposer on time. - -A process locks value $v$ if it receives at least $2f+1$ $\Prevote$ messages for the value $v$ in the current round (see lines~\ref{line:tab:recvPrevote}-\ref{line:tab:setLockedRound}). A correct process sends only a single $\Prevote$ message in every round. Algorithm relies on the $state$ variable to prevent correct processes from sending more than one $\Prevote$ message in a single round. As a correct process sends a single $\Prevote$ message in a round, and $f$ is the total voting power of faulty processes, two correct processes cannot receive $2f+1$ $\Prevote$ messages for different values in a single round. So if two correct processes lock some value in a given round $r$, then it must be the same value. We will prove this formally in the section~\ref{sec:proof}. - -The locking phase ends by processes informing others if they locked some value in the current round (or not) by sending $\Precommit$ message. If a process has locked some value $v$ in the current round, then this value is sent in the $\Precommit$ message (see line~\ref{line:tab:send-precommit}); otherwise $\nil$ is sent (line~\ref{line:tab:send-precommit-nil} and \ref{line:tab:precommit-nil-onTimeout}). A correct process sends only single $\Precommit$ message in every round. Similarly as with the $\Prevote$ message, the algorithm relies on the $state$ variable to prevent correct processes from sending more than one $\Precommit$ message in a single round. - -\subsection{Unlocking mechanism} -\label{sec:unlocking} - -Initially, a correct process does not lock any value. During the algorithm execution, processes might end up locking different values in different rounds. As we discussed in the locking section (section~\ref{sec:locking}), a correct process that has locked some value will vote -only for that value at the beginning of the locking phase. With correct processes voting for different values during the locking phase, it is not possible to reach an agreement what value should be locked, and later decided. Without unlocking mechanism in place, the algorithm could loop forever in that state, and no decision will be reached. The unlocking mechanism is therefore essential for the algorithm termination as it allows processes to release locks and try to converge to a common value in the following rounds. In Algorithm~\ref{alg:tendermint}, a correct process $p$ unlocks a value by setting $lockedValue_p$ to $\nil$ and $lockedRound_p$ to $-1$. - -A correct process $p$ releases its locked value if he observes that some correct process might\footnote{We say that other process \emph{might} have locked different value as we do not get a direct message from that process claiming that he locked a value with the corresponding proof of locking. We infer this information by observing $\Prevote$ messages.} have locked a different value in the round $r$ higher than the current $lockedRound_p$ ($r > lockedRound_p$). This is the case if a process $p$ receives at least $2f+1$ $\Prevote$ messages with value $v' \neq lockedValue_p$ and $round > lockedRound_p$ (see the rule starting at line~\ref{line:tab:unlockRule}). The process also implicitly releases a lock on its current value by locking a different value at lines~\ref{line:tab:setLockedValue} and \ref{line:tab:setLockedRound}. - -The unlocking mechanism, together with the underlying gossip layer, ensures the following properties: - -\begin{itemize} - \item (i) if a correct process $p$ locks a value $v$ in a round $r$ during a good period (period of synchrony), then at the end of the round $r$ all correct processes will lock either $v$ or they will not lock any value - \item (ii) if a correct process $p$ locks a value $v$ in a round $r$, then every correct process $q$ that has locked a different value in some of the previous rounds, i.e., $lockedValue_q \neq v$ and $lockedRound_q < r$, will eventually unlock it's value. -\end{itemize} - -From the property (ii) follows that after a sufficiently big good period in which no correct process manages to lock a value, all correct processes will lock either some common value $v$ or they will not lock any value (where $v$ is the value locked in the highest round $r$ by some correct process). This follows from the fact that processes keep resending messages at the gossip layer, so during good periods, $\Prevote$ messages that led to the correct process locking the value $v$ in the round $r$ are received by all correct processes. Therefore, they either stay with $v$ as its locked value, or unlock its current value. - -Both properties ensure that during a sufficiently long good period, there will eventually be a round, such that at the beginning of this round, all correct processes will lock the same value or they will not lock any value. As we will explain in the following section (\ref{sec:findRighValue}), this is one of the essential mechanisms for ensuring termination of the Tendermint consensus algorithm. - -\subsection{Finding the right value to propose} -\label{sec:findRighValue} - -As we discussed in section~\ref{sec:locking}, a correct process in Tendermint votes always for its locked value (if it exists) or for the \emph{valid} value suggested by the coordinator of the current round. In order to reach a decision in some round $r$ it is necessary that all correct processes vote for the same value in that round (we do not depend on faulty processes for termination). Then correct processes can lock that value, and later decide. Tendermint relies on the coordinator to ensure that processes that have not locked any value, vote for the same value. As a correct process always votes for its locked value, then several conditions need to be fulfilled just before the round $r$ starts so we can achieve such preferable scenario: - -\begin{enumerate} - \item if a correct process $p$ has locked some value $v$ at the beginning of the round $r$, then all other correct processes have either locked $v$ or they have not locked any value. \label{enum:termination:v-is-locked} - \item the proposer is a correct process, and a value suggested by the proposer is $v$. \label{enum:termination:v-is-proposed} -\end{enumerate} - -In case those conditions hold, all correct processes will lock $v$ in the round $r$ and later decide. - -The existence of a round where the condition \ref{enum:termination:v-is-locked}) holds, follows from the unlocking mechanism. The first part of the condition \ref{enum:termination:v-is-proposed}) follows from the fact that $\coord$ function eventually provides a correct proposer. The second part of the condition \ref{enum:termination:v-is-proposed}) is more tricky as the proposer needs to suggest exactly the value that is locked by correct processes. If the proposer has locked $v$ at the beginning of the round $r$, then it trivially follows from the condition \ref{enum:termination:v-is-locked}). Otherwise, we need an additional mechanism that ensures that the correct proposer suggests $v$. - -In order to ensure this condition, Tendermint has an additional mechanism for determining what is the good value to suggest such that this condition holds. It is implemented using variables $validValue$ and $validRound$. Using these two variables every process tracks the highest round ($validRound$) in which correct process might have locked some value ($validValue$). So whenever process observes at least $2f+1$ $\Prevote$ messages with $round > validRound_p$ (see the rule at line~\ref{line:tab:validValueRule}) for some value $v \neq \nil$, -it updates $validValue$ and $validRound$ variables. - -This mechanism, together with the underlying gossip layer, ensures the following properties: -\begin{itemize} - \item (i) if a correct process $p$ locks a value $v$ in a round $r$ during a good period (period of synchrony), then at the end of the round $r$ all correct processes will have $validValue$ equal to $v$ and $validRound$ equal to $r$. - \item (ii) if no correct process $p$ locks some value during a sufficiently long good period, then all correct processes will eventually have $validValue$ equal to the same value $v$. -\end{itemize} - -If during a good period, no correct process locks a value during a sufficiently long period (so the property i) does not hold), then -all correct processes will have $validValue$ equal to $v$ due to the following reasoning: During this time period, all correct processes will receive delayed $\li{Prevote}$ messages from previous rounds (because of resending messages at the gossip layer). Now let denote with $r$ and $v$, a round and a value, such that there are at least $2f+1$ $\li{Prevote}$ messages with $r$ and $v$, and $r$ is the highest among such $(*, r)$ pairs. Then all correct processes will receive those messages and set $validValue$ to $v$. - -In both cases, during sufficiently long good period, there will eventually be a round, such that at the beginning of this round, all correct processes will have $validValue$ equal to the same value; and in case some correct process has locked some value $v$, then the $validValue$ will be equal to $v$. - -The unlocking mechanism and the mechanism for determining the right proposal value ensures that during a period of synchrony, there will be a round $r$ such that (i) all correct processes either lock the same value $v$ or they do not lock any value, and (ii) the $validValue$ is equal to $v$ at all correct processes. If a proposer of such round $r$ is correct, then he proposes $v$, all correct processes vote for $v$ during the locking phase; therefore they lock $v$, send a $\Precommit$ message with $v$ and later decide. The formal proof of termination is provided in section~\ref{sec:proof}. - - diff --git a/IEEEtran.bst b/consensus/IEEEtran.bst similarity index 100% rename from IEEEtran.bst rename to consensus/IEEEtran.bst diff --git a/IEEEtran.cls b/consensus/IEEEtran.cls similarity index 100% rename from IEEEtran.cls rename to consensus/IEEEtran.cls diff --git a/consensus/README.md b/consensus/README.md new file mode 100644 index 000000000..e67db6cf0 --- /dev/null +++ b/consensus/README.md @@ -0,0 +1,25 @@ +# 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/algorithmicplus.sty b/consensus/algorithmicplus.sty similarity index 100% rename from algorithmicplus.sty rename to consensus/algorithmicplus.sty diff --git a/consensus/conclusion.tex b/consensus/conclusion.tex new file mode 100644 index 000000000..dd17ccf44 --- /dev/null +++ b/consensus/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/consensus/consensus.tex b/consensus/consensus.tex new file mode 100644 index 000000000..3265b61c7 --- /dev/null +++ b/consensus/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/consensus/definitions.tex b/consensus/definitions.tex new file mode 100644 index 000000000..f73d316e8 --- /dev/null +++ b/consensus/definitions.tex @@ -0,0 +1,117 @@ +\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., 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 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 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$.}. Messages among +correct processes can be delayed, dropped or duplicated before GST. +Spoofing/impersonation attacks are assumed to be impossible at all times due to +the use of public-key cryptography. 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. All protocol messages are signed, i.e., 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$. + +The details of the Tendermint gossip protocol will be discussed in a separate +technical report. For the sake of this paper it is sufficient to assume that +messages are being gossiped between processes and the following property holds +(in addition to the partial synchrony network assumptions): + +\begin{itemize} \item \emph{Gossip communication:} 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} + + +%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/homodel.sty b/consensus/homodel.sty similarity index 75% rename from homodel.sty rename to consensus/homodel.sty index f5e2ef701..19f83e926 100644 --- a/homodel.sty +++ b/consensus/homodel.sty @@ -3,17 +3,30 @@ \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}{} -\newconstruct{\VARIABLES}{\textbf{Variables:}}{}{\ENDVARIABLES}{} -\newconstruct{\INIT}{\textbf{Initialization:}}{}{\ENDINIT}{} diff --git a/consensus/intro.tex b/consensus/intro.tex new file mode 100644 index 000000000..493b509e9 --- /dev/null +++ b/consensus/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/latex8.bst b/consensus/latex8.bst similarity index 100% rename from latex8.bst rename to consensus/latex8.bst diff --git a/latex8.sty b/consensus/latex8.sty similarity index 100% rename from latex8.sty rename to consensus/latex8.sty diff --git a/lit.bib b/consensus/lit.bib similarity index 94% rename from lit.bib rename to consensus/lit.bib index 7a6eec996..4abc83e70 100644 --- a/lit.bib +++ b/consensus/lit.bib @@ -1566,7 +1566,7 @@ Vincent Gramoli and Mikel Larrea and Michel Raynal}, - title = {(Leader/Randomization/Signature)-free Byzantine Consensus for Consortium + title = {Leader/Randomization/Signature-free Byzantine Consensus for Consortium Blockchains}, journal = {CoRR}, volume = {abs/1702.03068}, @@ -1578,3 +1578,82 @@ 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/paper.tex b/consensus/paper.tex similarity index 78% rename from paper.tex rename to consensus/paper.tex index 4415a9200..22f8b405f 100644 --- a/paper.tex +++ b/consensus/paper.tex @@ -17,7 +17,7 @@ % correct bad hyphenation here \hyphenation{op-tical net-works semi-conduc-tor} -\usepackage[caption=false,font=footnotesize]{subfig} +%\usepackage[caption=false,font=footnotesize]{subfig} \usepackage{tikz} \usetikzlibrary{decorations,shapes,backgrounds,calc} \tikzstyle{msg}=[->,black,>=latex] @@ -34,6 +34,7 @@ \usepackage{graphicx} \usepackage{epstopdf} \usepackage{amssymb} +\usepackage{rounddiag} \graphicspath{{../}} \usepackage{technote} @@ -53,7 +54,7 @@ %\newconstruct{\ON}{\textbf{on}}{\textbf{do}}{\ENDON}{\textbf{end on}} \newcommand\With{\textbf{while}} \newcommand\From{\textbf{from}} -\newcommand\Broadcast{\textbf{Broadcast}} +\newcommand\Broadcast{\textbf{broadcast}} \newcommand\PBroadcast{send} \newcommand\UpCall{\textbf{UpCall}} \newcommand\DownCall{\textbf{DownCall}} @@ -118,31 +119,31 @@ % % paper title % can use linebreaks \\ within to get better formatting as desired -\title{Tendermint Byzantine Consensus Algorithm} +\title{The latest gossip on BFT consensus\vspace{-0.7\baselineskip}} -\author{\IEEEauthorblockN{Ethan Buchman, Jae Kwon and Zarko Milosevic} -% \IEEEauthorblockN{Tendermint}\ + +\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} -%The paper formally introduces blockchain problem and present Tendermint, %algorithm that solves blockchain in the partially synchronous system model. -%\end{abstract} +\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} - -%\section*{Acknowledgment} - +\input{conclusion} \bibliographystyle{IEEEtran} \bibliography{lit} diff --git a/consensus/proof.tex b/consensus/proof.tex new file mode 100644 index 000000000..1c84d9b11 --- /dev/null +++ b/consensus/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/rounddiag.sty b/consensus/rounddiag.sty similarity index 87% rename from rounddiag.sty rename to consensus/rounddiag.sty index cc8c7d0fc..a6ca5d883 100644 --- a/rounddiag.sty +++ b/consensus/rounddiag.sty @@ -1,4 +1,4 @@ -% ROUNDDIAG STYLE +% ROUNDDIAG STYLE % for LaTeX version 2e % by -- 2008 Martin Hutle % @@ -34,28 +34,29 @@ \newenvironment{rounddiag}[2]{ \begin{center} \begin{tikzpicture} -\foreach \i in {1,...,#1} +\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} +} +\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}; + \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}{}}}% + \foreach \i in {1,...,#1}{ + \foreach \j in {1,...,#1}{ + { \rdmessage{\i}{\j}{}}}}}% }{% \end{tikzpicture} \end{center} } - diff --git a/technote.sty b/consensus/technote.sty similarity index 100% rename from technote.sty rename to consensus/technote.sty diff --git a/definitions.tex b/definitions.tex deleted file mode 100644 index 6ef188a98..000000000 --- a/definitions.tex +++ /dev/null @@ -1,54 +0,0 @@ -\section{Definitions} -\label{sec:definitions} - - -\subsection{Model} - -We consider a system composed of $n$ server processes $\Pi = \{ 1, \dots, n\}$. -Server 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, and we denote with $N$ the total voting power of all processes in the system. -Furthermore, we assume that the total voting power of faulty processes is bounded with a system parameter $f$. - -Processes communicate by exchanging messages. We assume a model where processes are not part of the single administration domain; therefore we cannot enforce a direct network connectivity between all server processes. -Instead, we assume that each server process is connected to a small number of processes called peers, -such that there is an indirect communication channel between all correct server processes. Communication between processes is established using gossip protocol. - -We assume that processes communicate over wide-area network. Formally, we model the network communication using the partially synchronous system model~\cite{DLS88:jacm}, or rather a slightly weaker variant of this model: we assume that the system alternates between good periods (during which the system is synchronous) and bad periods (during which the system is asynchronous and messages can be lost). During good periods, there is a bound $\Delta$ such that all -communication among correct processes is reliable and $\Delta$-timely, i.e., if a correct process -$p$ sends message $m$ at time $t$ to a correct process $q$, then $q$ will receive $m$ -before $t+\Delta$. Note that as we do not assume direct communication channels among all correct processes, this implies that before the message $m$ reaches $q$, it will pass through a number of -correct servers that will forward the message $m$ using gossip protocol towards $q$. -Messages among correct processes can be -delayed, dropped or duplicated during bad periods. Spoofing/impersonation attacks are assumed to be impossible also during bad periods. -The bound $\Delta$ is a system parameter whose value is not required to be known for the safety of the algorithm presented. However, the timing bounds derived for our algorithm, and thus the guaranteed latency requires knowledge of $\Delta$. - -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. - -We assume integrity of direct communication channels between peers, that is if a process $p$ received a message $m$ from process $q$, then $q$ sent message $m$ to $p$ before. All protocol messages are signed, i.e., when a correct process $q$ receives a signed message $m$ from its peer, the process $q$ can verify who was the original message sender. - -The details of the Tendermint gossip protocol will be discussed in a separate technical report. For the sake of this report it is sufficient to assume the following properties provided by the gossip protocol (in addition to the properties about partial synchrony of the network stated above): - -\begin{itemize} - \item Messages that are being gossiped come from 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. -\end{itemize} - - - \subsection{Consensus} - \label{sec:consensus} - - \newcommand{\propose}{\mathsf{propose}} - \newcommand{\decide}{\mathsf{decide}} - -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 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/paper.dvi b/paper.dvi deleted file mode 100644 index 51821d55d1a4d7bdda0296e61efd1fee3a0ff286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27416 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 diff --git a/proof.tex b/proof.tex deleted file mode 100644 index 5554a9edb..000000000 --- a/proof.tex +++ /dev/null @@ -1,2 +0,0 @@ -\section{Formal proof of Tendermint consensus algorithm} -\label{sec:proof} \ No newline at end of file From 81a0198af23e8b64230e8986f5f960a25c3e43d8 Mon Sep 17 00:00:00 2001 From: Marko Date: Sat, 14 Sep 2019 19:56:17 +0200 Subject: [PATCH 005/223] This PR is to create signed commits to be able to merge (#50) Signed-off-by: Marko Baricevic --- README.md | 6 +-- papers/README.md | 5 +++ research/README.md | 5 +++ rfc/README.md | 23 +++++++++++ rfc/rfc_template.md | 39 +++++++++++++++++++ spec/README.md | 5 +++ {consensus => spec/consensus}/IEEEtran.bst | 0 {consensus => spec/consensus}/IEEEtran.cls | 0 {consensus => spec/consensus}/README.md | 0 .../consensus}/algorithmicplus.sty | 0 {consensus => spec/consensus}/conclusion.tex | 0 {consensus => spec/consensus}/consensus.tex | 0 {consensus => spec/consensus}/definitions.tex | 0 {consensus => spec/consensus}/homodel.sty | 0 {consensus => spec/consensus}/intro.tex | 0 {consensus => spec/consensus}/latex8.bst | 0 {consensus => spec/consensus}/latex8.sty | 0 {consensus => spec/consensus}/lit.bib | 0 {consensus => spec/consensus}/paper.tex | 0 {consensus => spec/consensus}/proof.tex | 0 {consensus => spec/consensus}/rounddiag.sty | 0 {consensus => spec/consensus}/technote.sty | 0 22 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 papers/README.md create mode 100644 research/README.md create mode 100644 rfc/README.md create mode 100644 rfc/rfc_template.md create mode 100644 spec/README.md rename {consensus => spec/consensus}/IEEEtran.bst (100%) rename {consensus => spec/consensus}/IEEEtran.cls (100%) rename {consensus => spec/consensus}/README.md (100%) rename {consensus => spec/consensus}/algorithmicplus.sty (100%) rename {consensus => spec/consensus}/conclusion.tex (100%) rename {consensus => spec/consensus}/consensus.tex (100%) rename {consensus => spec/consensus}/definitions.tex (100%) rename {consensus => spec/consensus}/homodel.sty (100%) rename {consensus => spec/consensus}/intro.tex (100%) rename {consensus => spec/consensus}/latex8.bst (100%) rename {consensus => spec/consensus}/latex8.sty (100%) rename {consensus => spec/consensus}/lit.bib (100%) rename {consensus => spec/consensus}/paper.tex (100%) rename {consensus => spec/consensus}/proof.tex (100%) rename {consensus => spec/consensus}/rounddiag.sty (100%) rename {consensus => spec/consensus}/technote.sty (100%) diff --git a/README.md b/README.md index 068a3c171..9056750ea 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Tendermint Spec -This repository contains the paper describing the Tendermint consensus -algorithm, including formal proofs of its safety and liveness. +This repository contains specs of the current and future implementation of Tendermint the protocol, papers that have been published by the ICF or AIB and ongoing research in a variety of areas. -For the pdf, see the [latest -release](https://github.com/tendermint/spec/releases). +- For the pdf, see the [latest release](https://github.com/tendermint/spec/releases). diff --git a/papers/README.md b/papers/README.md new file mode 100644 index 000000000..dd8835681 --- /dev/null +++ b/papers/README.md @@ -0,0 +1,5 @@ +# Papers + +This folder houses Papers that have either been published or are a work in progress. + +## Table of Contents diff --git a/research/README.md b/research/README.md new file mode 100644 index 000000000..dcb51a4bc --- /dev/null +++ b/research/README.md @@ -0,0 +1,5 @@ +# Research + +This folder holds ongoing research and topics of interest. The [Interchain Foundation](https://interchain.io) has research topics that they are willing fund the research for, to find out more you can go to there [funding repo](https://github.com/interchainio/funding/blob/master/research.md). + +You can apply for a research grant [here](https://docs.google.com/forms/d/e/1FAIpQLSclH4R5G7WgNpKPvXxPPFRA7rAoyX8nNvsJAQJpZNZwWWjFmA/viewform) diff --git a/rfc/README.md b/rfc/README.md new file mode 100644 index 000000000..9682b9f1a --- /dev/null +++ b/rfc/README.md @@ -0,0 +1,23 @@ +# Request for Comments (RFC) + +RFC stands for `Request for Comments`. It is a social device use to float and polish an idea prior to the inclusion into an existing or new spec/paper/research topic. + +An RFC should not be used for bug reports or trivial discussions - the overhead of compiling an RFC does not justify it. + +An RFC should not consist only of a problem statement (use a standard issue for that). + +A RFC should consist of: + +- Changelog +- Context on the relevant goals and the current state +- Proposed Solution +- Summary of pros and cons +- References + +If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match. + +**Note the context/background should be written in the present tense.** + +Some RFC's will be presented at a Tendermint Dev Session. If you are an outside contributor and have submitted a RFC, you may be invited to present your RFC at one of these calls. + +## Table of Contents diff --git a/rfc/rfc_template.md b/rfc/rfc_template.md new file mode 100644 index 000000000..817066ee4 --- /dev/null +++ b/rfc/rfc_template.md @@ -0,0 +1,39 @@ +# RFC {RFC-NUMBER}: {TITLE} + +## Changelog + +- {date}: {changelog} + +## Author(s) + +- {First Name} {github handle} + +## Context + +> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution. + +## Proposal + +> It should contain a detailed breakdown of how the problem should be resolved including diagrams and other supporting materials needed to present the case and implementation roadmap for the proposed changes. The reader should be able to fully understand the proposal. This section should be broken up using ## subsections as needed. + +## Status + +> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. If a later RFC changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement. + +{Deprecated|Proposed|Accepted} + +## Consequences + +> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. + +### Positive + +### Negative + +### Neutral + +## References + +> Are there any relevant PR comments, issues that led up to this, or articles referenced for why we made the given design choice? If so link them here! + +- {reference link} diff --git a/spec/README.md b/spec/README.md new file mode 100644 index 000000000..ba881643e --- /dev/null +++ b/spec/README.md @@ -0,0 +1,5 @@ +# Spec + +This folder houses the spec of Tendermint the Protocol. + +**Note: We are currently working on expanding the spec and will slowly be migrating it from Tendermint the repo** diff --git a/consensus/IEEEtran.bst b/spec/consensus/IEEEtran.bst similarity index 100% rename from consensus/IEEEtran.bst rename to spec/consensus/IEEEtran.bst diff --git a/consensus/IEEEtran.cls b/spec/consensus/IEEEtran.cls similarity index 100% rename from consensus/IEEEtran.cls rename to spec/consensus/IEEEtran.cls diff --git a/consensus/README.md b/spec/consensus/README.md similarity index 100% rename from consensus/README.md rename to spec/consensus/README.md diff --git a/consensus/algorithmicplus.sty b/spec/consensus/algorithmicplus.sty similarity index 100% rename from consensus/algorithmicplus.sty rename to spec/consensus/algorithmicplus.sty diff --git a/consensus/conclusion.tex b/spec/consensus/conclusion.tex similarity index 100% rename from consensus/conclusion.tex rename to spec/consensus/conclusion.tex diff --git a/consensus/consensus.tex b/spec/consensus/consensus.tex similarity index 100% rename from consensus/consensus.tex rename to spec/consensus/consensus.tex diff --git a/consensus/definitions.tex b/spec/consensus/definitions.tex similarity index 100% rename from consensus/definitions.tex rename to spec/consensus/definitions.tex diff --git a/consensus/homodel.sty b/spec/consensus/homodel.sty similarity index 100% rename from consensus/homodel.sty rename to spec/consensus/homodel.sty diff --git a/consensus/intro.tex b/spec/consensus/intro.tex similarity index 100% rename from consensus/intro.tex rename to spec/consensus/intro.tex diff --git a/consensus/latex8.bst b/spec/consensus/latex8.bst similarity index 100% rename from consensus/latex8.bst rename to spec/consensus/latex8.bst diff --git a/consensus/latex8.sty b/spec/consensus/latex8.sty similarity index 100% rename from consensus/latex8.sty rename to spec/consensus/latex8.sty diff --git a/consensus/lit.bib b/spec/consensus/lit.bib similarity index 100% rename from consensus/lit.bib rename to spec/consensus/lit.bib diff --git a/consensus/paper.tex b/spec/consensus/paper.tex similarity index 100% rename from consensus/paper.tex rename to spec/consensus/paper.tex diff --git a/consensus/proof.tex b/spec/consensus/proof.tex similarity index 100% rename from consensus/proof.tex rename to spec/consensus/proof.tex diff --git a/consensus/rounddiag.sty b/spec/consensus/rounddiag.sty similarity index 100% rename from consensus/rounddiag.sty rename to spec/consensus/rounddiag.sty diff --git a/consensus/technote.sty b/spec/consensus/technote.sty similarity index 100% rename from consensus/technote.sty rename to spec/consensus/technote.sty From 9b3531d7d688a6bff27d904e4db91b07ed1b7120 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 17 Sep 2019 16:06:20 +0200 Subject: [PATCH 006/223] 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 --- spec/blockchain/blockchain.md | 504 ++++++++++++++++++ spec/blockchain/encoding.md | 344 ++++++++++++ spec/blockchain/state.md | 141 +++++ spec/consensus/bft-time.md | 54 ++ .../{ => consensus-paper}/IEEEtran.bst | 0 .../{ => consensus-paper}/IEEEtran.cls | 0 .../consensus/{ => consensus-paper}/README.md | 0 .../{ => consensus-paper}/algorithmicplus.sty | 0 .../{ => consensus-paper}/conclusion.tex | 0 .../{ => consensus-paper}/consensus.tex | 0 .../{ => consensus-paper}/definitions.tex | 0 .../{ => consensus-paper}/homodel.sty | 0 .../consensus/{ => consensus-paper}/intro.tex | 0 .../{ => consensus-paper}/latex8.bst | 0 .../{ => consensus-paper}/latex8.sty | 0 spec/consensus/{ => consensus-paper}/lit.bib | 0 .../consensus/{ => consensus-paper}/paper.tex | 0 .../consensus/{ => consensus-paper}/proof.tex | 0 .../{ => consensus-paper}/rounddiag.sty | 0 .../{ => consensus-paper}/technote.sty | 0 spec/consensus/consensus.md | 339 ++++++++++++ spec/consensus/creating-proposal.md | 42 ++ spec/consensus/fork-accountability.md | 319 +++++++++++ spec/consensus/light-client.md | 329 ++++++++++++ spec/consensus/signing.md | 205 +++++++ spec/consensus/wal.md | 32 ++ 26 files changed, 2309 insertions(+) create mode 100644 spec/blockchain/blockchain.md create mode 100644 spec/blockchain/encoding.md create mode 100644 spec/blockchain/state.md create mode 100644 spec/consensus/bft-time.md rename spec/consensus/{ => consensus-paper}/IEEEtran.bst (100%) rename spec/consensus/{ => consensus-paper}/IEEEtran.cls (100%) rename spec/consensus/{ => consensus-paper}/README.md (100%) rename spec/consensus/{ => consensus-paper}/algorithmicplus.sty (100%) rename spec/consensus/{ => consensus-paper}/conclusion.tex (100%) rename spec/consensus/{ => consensus-paper}/consensus.tex (100%) rename spec/consensus/{ => consensus-paper}/definitions.tex (100%) rename spec/consensus/{ => consensus-paper}/homodel.sty (100%) rename spec/consensus/{ => consensus-paper}/intro.tex (100%) rename spec/consensus/{ => consensus-paper}/latex8.bst (100%) rename spec/consensus/{ => consensus-paper}/latex8.sty (100%) rename spec/consensus/{ => consensus-paper}/lit.bib (100%) rename spec/consensus/{ => consensus-paper}/paper.tex (100%) rename spec/consensus/{ => consensus-paper}/proof.tex (100%) rename spec/consensus/{ => consensus-paper}/rounddiag.sty (100%) rename spec/consensus/{ => consensus-paper}/technote.sty (100%) create mode 100644 spec/consensus/consensus.md create mode 100644 spec/consensus/creating-proposal.md create mode 100644 spec/consensus/fork-accountability.md create mode 100644 spec/consensus/light-client.md create mode 100644 spec/consensus/signing.md create mode 100644 spec/consensus/wal.md diff --git a/spec/blockchain/blockchain.md b/spec/blockchain/blockchain.md new file mode 100644 index 000000000..f4ddcd75f --- /dev/null +++ b/spec/blockchain/blockchain.md @@ -0,0 +1,504 @@ +# Blockchain + +Here we describe the data structures in the Tendermint blockchain and the rules for validating them. + +## Data Structures + +The Tendermint blockchains consists of a short list of basic data types: + +- `Block` +- `Header` +- `Version` +- `BlockID` +- `Time` +- `Data` (for transactions) +- `Commit` and `Vote` +- `EvidenceData` and `Evidence` + +## Block + +A block consists of a header, transactions, votes (the commit), +and a list of evidence of malfeasance (ie. signing conflicting votes). + +```go +type Block struct { + Header Header + Txs Data + Evidence EvidenceData + LastCommit Commit +} +``` + +Note the `LastCommit` is the set of votes that committed the last block. + +## 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: + +```go +type Header struct { + // basic block info + Version Version + ChainID string + Height int64 + Time Time + NumTxs int64 + TotalTxs int64 + + // prev block info + LastBlockID BlockID + + // hashes of block data + LastCommitHash []byte // commit from validators from the last block + DataHash []byte // MerkleRoot of transaction hashes + + // hashes from the app output from the prev block + ValidatorsHash []byte // validators for the current block + NextValidatorsHash []byte // validators for the next block + ConsensusHash []byte // consensus params for current block + AppHash []byte // state after txs from the previous block + LastResultsHash []byte // root hash of all results from the txs from the previous block + + // consensus info + EvidenceHash []byte // evidence included in the block + ProposerAddress []byte // original proposer of the block +``` + +Further details on each of these fields is described below. + +## Version + +The `Version` contains the protocol version for the blockchain and the +application as two `uint64` values: + +```go +type Version struct { + Block uint64 + App uint64 +} +``` + +## BlockID + +The `BlockID` contains two distinct Merkle roots of the block. +The first, used as the block's main hash, is the MerkleRoot +of all the fields in the header (ie. `MerkleRoot(header)`. +The second, used for secure gossipping of the block during consensus, +is the MerkleRoot of the complete serialized block +cut into parts (ie. `MerkleRoot(MakeParts(block))`). +The `BlockID` includes these two hashes, as well as the number of +parts (ie. `len(MakeParts(block))`) + +```go +type BlockID struct { + Hash []byte + PartsHeader PartSetHeader +} + +type PartSetHeader struct { + Total int32 + Hash []byte +} +``` + +See [MerkleRoot](./encoding.md#MerkleRoot) for details. + +## Time + +Tendermint uses the +[Google.Protobuf.WellKnownTypes.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp) +format, which uses two integers, one for Seconds and for Nanoseconds. + +## Data + +Data is just a wrapper for a list of transactions, where transactions are +arbitrary byte arrays: + +``` +type Data struct { + Txs [][]byte +} +``` + +## Commit + +Commit is a simple wrapper for a list of votes, with one vote for each +validator. It also contains the relevant BlockID: + +``` +type Commit struct { + BlockID BlockID + Precommits []Vote +} +``` + +NOTE: this will likely change to reduce the commit size by eliminating redundant +information - see [issue #1648](https://github.com/tendermint/tendermint/issues/1648). + +## Vote + +A vote is a signed message from a validator for a particular block. +The vote includes information about the validator signing it. + +```go +type Vote struct { + Type byte + Height int64 + Round int + BlockID BlockID + Timestamp Time + ValidatorAddress []byte + ValidatorIndex int + Signature []byte +} +``` + +There are two types of votes: +a _prevote_ has `vote.Type == 1` and +a _precommit_ has `vote.Type == 2`. + +## Signature + +Signatures in Tendermint are raw bytes representing the underlying signature. + +See the [signature spec](./encoding.md#key-types) for more. + +## EvidenceData + +EvidenceData is a simple wrapper for a list of evidence: + +```go +type EvidenceData struct { + Evidence []Evidence +} +``` + +## Evidence + +Evidence in Tendermint is implemented as an interface. +This means any evidence is encoded using its Amino prefix. +There is currently only a single type, the `DuplicateVoteEvidence`. + +``` +// amino name: "tendermint/DuplicateVoteEvidence" +type DuplicateVoteEvidence struct { + PubKey PubKey + VoteA Vote + VoteB Vote +} +``` + +See the [pubkey spec](./encoding.md#key-types) for more. + +## Validation + +Here we describe the validation rules for every element in a block. +Blocks which do not satisfy these rules are considered invalid. + +We abuse notation by using something that looks like Go, supplemented with English. +A statement such as `x == y` is an assertion - if it fails, the item is invalid. + +We refer to certain globally available objects: +`block` is the block under consideration, +`prevBlock` is the `block` at the previous height, +and `state` keeps track of the validator set, the consensus parameters +and other results from the application. At the point when `block` is the block under consideration, +the current version of the `state` corresponds to the state +after executing transactions from the `prevBlock`. +Elements of an object are accessed as expected, +ie. `block.Header`. +See the [definition of `State`](./state.md). + +### Header + +A Header is valid if its corresponding fields are valid. + +### Version + +``` +block.Version.Block == state.Version.Block +block.Version.App == state.Version.App +``` + +The block version must match the state version. + +### ChainID + +``` +len(block.ChainID) < 50 +``` + +ChainID must be less than 50 bytes. + +### Height + +```go +block.Header.Height > 0 +block.Header.Height == prevBlock.Header.Height + 1 +``` + +The height is an incrementing integer. The first block has `block.Header.Height == 1`. + +### Time + +``` +block.Header.Timestamp >= prevBlock.Header.Timestamp + state.consensusParams.Block.TimeIotaMs +block.Header.Timestamp == MedianTime(block.LastCommit, state.LastValidators) +``` + +The block timestamp must be monotonic. +It must equal the weighted median of the timestamps of the valid votes in the block.LastCommit. + +Note: the timestamp of a vote must be greater by at least one millisecond than that of the +block being voted on. + +The timestamp of the first block must be equal to the genesis time (since +there's no votes to compute the median). + +``` +if block.Header.Height == 1 { + block.Header.Timestamp == genesisTime +} +``` + +See the section on [BFT time](../consensus/bft-time.md) for more details. + +### NumTxs + +```go +block.Header.NumTxs == len(block.Txs.Txs) +``` + +Number of transactions included in the block. + +### TotalTxs + +```go +block.Header.TotalTxs == prevBlock.Header.TotalTxs + block.Header.NumTxs +``` + +The cumulative sum of all transactions included in this blockchain. + +The first block has `block.Header.TotalTxs = block.Header.NumberTxs`. + +### LastBlockID + +LastBlockID is the previous block's BlockID: + +```go +prevBlockParts := MakeParts(prevBlock) +block.Header.LastBlockID == BlockID { + Hash: MerkleRoot(prevBlock.Header), + PartsHeader{ + Hash: MerkleRoot(prevBlockParts), + Total: len(prevBlockParts), + }, +} +``` + +The first block has `block.Header.LastBlockID == BlockID{}`. + +### LastCommitHash + +```go +block.Header.LastCommitHash == MerkleRoot(block.LastCommit.Precommits) +``` + +MerkleRoot of the votes included in the block. +These are the votes that committed the previous block. + +The first block has `block.Header.LastCommitHash == []byte{}` + +### DataHash + +```go +block.Header.DataHash == MerkleRoot(Hashes(block.Txs.Txs)) +``` + +MerkleRoot of the hashes of transactions included in the block. + +Note the transactions are hashed before being included in the Merkle tree, +so the leaves of the Merkle tree are the hashes, not the transactions +themselves. This is because transaction hashes are regularly used as identifiers for +transactions. + +### ValidatorsHash + +```go +block.ValidatorsHash == MerkleRoot(state.Validators) +``` + +MerkleRoot of the current validator set that is committing the block. +This can be used to validate the `LastCommit` included in the next block. +Note the validators are sorted by their address before computing the MerkleRoot. + +### NextValidatorsHash + +```go +block.NextValidatorsHash == MerkleRoot(state.NextValidators) +``` + +MerkleRoot of the next validator set that will be the validator set that commits the next block. +This is included so that the current validator set gets a chance to sign the +next validator sets Merkle root. +Note the validators are sorted by their address before computing the MerkleRoot. + +### ConsensusHash + +```go +block.ConsensusHash == state.ConsensusParams.Hash() +``` + +Hash of the amino-encoding of a subset of the consensus parameters. + +### AppHash + +```go +block.AppHash == state.AppHash +``` + +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 has `block.Header.AppHash == []byte{}`. + +### LastResultsHash + +```go +block.ResultsHash == MerkleRoot(state.LastResults) +``` + +MerkleRoot of the results of the transactions in the previous block. + +The first block has `block.Header.ResultsHash == []byte{}`. + +## EvidenceHash + +```go +block.EvidenceHash == MerkleRoot(block.Evidence) +``` + +MerkleRoot of the evidence of Byzantine behaviour included in this block. + +### ProposerAddress + +```go +block.Header.ProposerAddress in state.Validators +``` + +Address of the original proposer of the block. Must be a current validator. + +## Txs + +Arbitrary length array of arbitrary length byte-arrays. + +## LastCommit + +The first height is an exception - it requires the LastCommit to be empty: + +```go +if block.Header.Height == 1 { + len(b.LastCommit) == 0 +} +``` + +Otherwise, we require: + +```go +len(block.LastCommit) == len(state.LastValidators) +talliedVotingPower := 0 +for i, vote := range block.LastCommit{ + if vote == nil{ + continue + } + vote.Type == 2 + vote.Height == block.LastCommit.Height() + vote.Round == block.LastCommit.Round() + vote.BlockID == block.LastBlockID + + val := state.LastValidators[i] + vote.Verify(block.ChainID, val.PubKey) == true + + talliedVotingPower += val.VotingPower +} + +talliedVotingPower > (2/3) * TotalVotingPower(state.LastValidators) +``` + +Includes one (possibly nil) vote for every current validator. +Non-nil votes must be Precommits. +All votes must be for the same height and round. +All votes must be for the previous block. +All votes must have a valid signature from the corresponding validator. +The sum total 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. + +### Vote + +A vote is a signed message broadcast in the consensus for a particular block at a particular height and round. +When stored in the blockchain or propagated over the network, votes are encoded in Amino. +For signing, votes are represented via `CanonicalVote` and also encoded using amino (protobuf compatible) via +`Vote.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(vote.SignBytes(chainID), vote.Signature) { + return ErrVoteInvalidSignature + } + return nil +} +``` + +where `pubKey.Verify` performs the appropriate digital signature verification of the `pubKey` +against the given signature and message bytes. + +## Evidence + +There is currently only one kind of evidence, `DuplicateVoteEvidence`. + +DuplicateVoteEvidence `ev` is valid if + +- `ev.VoteA` and `ev.VoteB` can be verified with `ev.PubKey` +- `ev.VoteA` and `ev.VoteB` have the same `Height, Round, Address, Index, Type` +- `ev.VoteA.BlockID != ev.VoteB.BlockID` +- `(block.Height - ev.VoteA.Height) < MAX_EVIDENCE_AGE` + +# Execution + +Once a block is validated, it can be executed against the state. + +The state follows this recursive equation: + +```go +state(1) = 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 +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) + + return State{ + LastResults: abciResponses.DeliverTxResults, + AppHash: AppHash, + LastValidators: state.Validators, + Validators: state.NextValidators, + NextValidators: UpdateValidators(state.NextValidators, ValidatorChanges), + ConsensusParams: UpdateConsensusParams(state.ConsensusParams, ConsensusParamChanges), + } +} +``` diff --git a/spec/blockchain/encoding.md b/spec/blockchain/encoding.md new file mode 100644 index 000000000..170e91605 --- /dev/null +++ b/spec/blockchain/encoding.md @@ -0,0 +1,344 @@ +# Encoding + +## Amino + +Tendermint uses the proto3 derivative [Amino](https://github.com/tendermint/go-amino) for all data structures. +Think of Amino as an object-oriented proto3 with native JSON support. +The goal of the Amino encoding protocol is to bring parity between application +logic objects and persistence objects. + +Please see the [Amino +specification](https://github.com/tendermint/go-amino#amino-encoding-for-go) for +more details. + +Notably, every object that satisfies an interface (eg. a particular kind of p2p message, +or a particular kind of pubkey) is registered with a global name, the hash of +which is included in the object's encoding as the so-called "prefix bytes". + +We define the `func AminoEncode(obj interface{}) []byte` function to take an +arbitrary object and return the Amino encoded bytes. + +## 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 Amino encoded before being hashed. +So `SHA256(obj)` is short for `SHA256(AminoEncode(obj))`. + +## Public Key Cryptography + +Tendermint uses Amino to distinguish between different types of private keys, +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, as they can be +derived the same way as the others using Amino. + +All registered objects are encoded by Amino using a 4-byte PrefixBytes that +uniquely identifies the object and includes information about its underlying +type. For details on how PrefixBytes are computed, see the [Amino +spec](https://github.com/tendermint/go-amino#computing-the-prefix-and-disambiguation-bytes). + +In what follows, we provide the type names and prefix bytes directly. +Notice that when encoding byte-arrays, the length of the byte-array is appended +to the PrefixBytes. Thus the encoding of a byte array becomes ` `. In other words, to encode any type listed below you do not need to be +familiar with amino encoding. +You can simply use below table and concatenate Prefix || Length (of raw bytes) || raw bytes +( while || stands for byte concatenation here). + +| Type | Name | Prefix | Length | Notes | +| ----------------------- | ---------------------------------- | ---------- | -------- | ----- | +| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | | +| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | | +| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | | +| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | | +| PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | | + +### Example + +For example, the 33-byte (or 0x21-byte in hex) Secp256k1 pubkey +`020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` +would be encoded as +`EB5AE98721020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` + +### Key Types + +Each type specifies it's own pubkey, address, and signature format. + +#### Ed25519 + +TODO: pubkey + +The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key: + +``` +address = SHA256(pubkey)[:20] +``` + +The signature is the raw 64-byte ED25519 signature. + +#### Secp256k1 + +TODO: pubkey + +The address is the RIPEMD160 hash of the SHA256 hash of the OpenSSL compressed public key: + +``` +address = RIPEMD160(SHA256(pubkey)) +``` + +This is the same as Bitcoin. + +The signature is the 64-byte concatenation of ECDSA `r` and `s` (ie. `r || s`), +where `s` is lexicographically less than its inverse, to prevent malleability. +This is like Ethereum, but without the extra byte for pubkey recovery, since +Tendermint assumes the pubkey is always provided anyway. + +#### Multisig + +TODO + +## 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`). + +```go +type BitArray struct { + Bits int + Elems []uint64 +} +``` + +This type is easily encoded directly by Amino. + +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`). + +```go +type Part struct { + Index int + Bytes []byte + Proof SimpleProof +} +``` + +See details of SimpleProof, below. + +### MakeParts + +Encode an object using Amino and slice it into parts. +Tendermint uses a part size of 65536 bytes. + +```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: + +``` + 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(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 nil + 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: + +``` +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 amino 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 amino encoding the individual `struct` elements. + +### Simple Merkle Proof + +Proof that a leaf is in a Merkle tree is composed as follows: + +```golang +type SimpleProof struct { + Total int + Index int + LeafHash []byte + Aunts [][]byte +} +``` + +Which is verified as follows: + +```golang +func (proof SimpleProof) 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) +} +``` + +### 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/master/docs/clients/lite/specification.md) + +## JSON + +### Amino + +Amino also supports JSON encoding - registered types are simply encoded as: + +``` +{ + "type": "", + "value": +} +``` + +For instance, an ED25519 PubKey would look like: + +``` +{ + "type": "tendermint/PubKeyEd25519", + "value": "uZ4h63OFWuQ36ZZ4Bd6NF+/w9fWUwrOncrQsackrsTk=" +} +``` + +Where the `"value"` is the base64 encoding of the raw pubkey bytes, and the +`"type"` is the amino name for Ed25519 pubkeys. + +### Signed Messages + +Signed messages (eg. votes, proposals) in the consensus are encoded using Amino. + +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 Amino encoding of the following struct: + +```go +type CanonicalVote struct { + Type byte + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + BlockID CanonicalBlockID + Timestamp time.Time + ChainID string +} +``` + +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. +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/blockchain/state.md b/spec/blockchain/state.md new file mode 100644 index 000000000..15fc37761 --- /dev/null +++ b/spec/blockchain/state.md @@ -0,0 +1,141 @@ +# State + +## 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. + +Note that the `State` object itself is an implementation detail, since it is never +included in a block or gossipped over the network, and we never compute +its hash. Thus we do not include here details of how the `State` object is +persisted or queried. That said, the types it contains are part of the specification, since +their Merkle roots are included in blocks and their values are used in +validation. + +```go +type State struct { + Version Version + LastResults []Result + AppHash []byte + + LastValidators []Validator + Validators []Validator + NextValidators []Validator + + ConsensusParams ConsensusParams +} +``` + +### Result + +```go +type Result struct { + Code uint32 + Data []byte +} +``` + +`Result` is the result of executing a transaction against the application. +It returns a result code and an arbitrary byte array (ie. a return value). + +NOTE: the Result needs to be updated to include more fields returned from +processing transactions, like gas variables and tags - see +[issue 1007](https://github.com/tendermint/tendermint/issues/1007). + +### Validator + +A validator is an active participant in the consensus with a public key and a voting power. +Validator's also contain an address field, which is a hash digest of the PubKey. + +```go +type Validator struct { + Address []byte + PubKey PubKey + VotingPower int64 +} +``` + +When hashing the Validator struct, the address is not included, +because it is redundant with the pubkey. + +The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, must always be sorted by validator address, +so that there is a canonical order for computing the MerkleRoot. + +We also define a `TotalVotingPower` function, to return the total voting power: + +```go +func TotalVotingPower(vals []Validators) int64{ + sum := 0 + for v := range vals{ + sum += v.VotingPower + } + return sum +} +``` + +### ConsensusParams + +ConsensusParams define various limits for blockchain data structures. +Like validator sets, they are set during genesis and can be updated by the application through ABCI. +When hashed, only a subset of the params are included, to allow the params to +evolve without breaking the header. + +```go +type ConsensusParams struct { + Block + Evidence + Validator +} + +type hashedParams struct { + BlockMaxBytes int64 + BlockMaxGas int64 +} + +func (params ConsensusParams) Hash() []byte { + SHA256(hashedParams{ + BlockMaxBytes: params.Block.MaxBytes, + BlockMaxGas: params.Block.MaxGas, + }) +} + +type BlockParams struct { + MaxBytes int64 + MaxGas int64 + TimeIotaMs int64 +} + +type EvidenceParams struct { + MaxAge int64 +} + +type ValidatorParams struct { + PubKeyTypes []string +} +``` + +#### 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. + +The minimal time between consecutive blocks is controlled by the +`ConsensusParams.Block.TimeIotaMs`. + +#### Evidence + +For evidence in a block to be valid, it must satisfy: + +``` +block.Header.Height - evidence.Height < ConsensusParams.Evidence.MaxAge +``` + +#### Validator + +Validators from genesis file and `ResponseEndBlock` must have pubkeys of type ∈ +`ConsensusParams.Validator.PubKeyTypes`. diff --git a/spec/consensus/bft-time.md b/spec/consensus/bft-time.md new file mode 100644 index 000000000..8e02f6abe --- /dev/null +++ b/spec/consensus/bft-time.md @@ -0,0 +1,54 @@ +# 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 + config.BlockTimeIota, time.Now())`, where `time.Now()` + denotes local Unix time in milliseconds, and `config.BlockTimeIota` is a configuration parameter that corresponds + to the minimum timestamp increment of the next block. + + - else if `rs.Proposal` is defined then + `vote.Time = max(rs.Proposal.Timestamp + config.BlockTimeIota, 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/IEEEtran.bst b/spec/consensus/consensus-paper/IEEEtran.bst similarity index 100% rename from spec/consensus/IEEEtran.bst rename to spec/consensus/consensus-paper/IEEEtran.bst diff --git a/spec/consensus/IEEEtran.cls b/spec/consensus/consensus-paper/IEEEtran.cls similarity index 100% rename from spec/consensus/IEEEtran.cls rename to spec/consensus/consensus-paper/IEEEtran.cls diff --git a/spec/consensus/README.md b/spec/consensus/consensus-paper/README.md similarity index 100% rename from spec/consensus/README.md rename to spec/consensus/consensus-paper/README.md diff --git a/spec/consensus/algorithmicplus.sty b/spec/consensus/consensus-paper/algorithmicplus.sty similarity index 100% rename from spec/consensus/algorithmicplus.sty rename to spec/consensus/consensus-paper/algorithmicplus.sty diff --git a/spec/consensus/conclusion.tex b/spec/consensus/consensus-paper/conclusion.tex similarity index 100% rename from spec/consensus/conclusion.tex rename to spec/consensus/consensus-paper/conclusion.tex diff --git a/spec/consensus/consensus.tex b/spec/consensus/consensus-paper/consensus.tex similarity index 100% rename from spec/consensus/consensus.tex rename to spec/consensus/consensus-paper/consensus.tex diff --git a/spec/consensus/definitions.tex b/spec/consensus/consensus-paper/definitions.tex similarity index 100% rename from spec/consensus/definitions.tex rename to spec/consensus/consensus-paper/definitions.tex diff --git a/spec/consensus/homodel.sty b/spec/consensus/consensus-paper/homodel.sty similarity index 100% rename from spec/consensus/homodel.sty rename to spec/consensus/consensus-paper/homodel.sty diff --git a/spec/consensus/intro.tex b/spec/consensus/consensus-paper/intro.tex similarity index 100% rename from spec/consensus/intro.tex rename to spec/consensus/consensus-paper/intro.tex diff --git a/spec/consensus/latex8.bst b/spec/consensus/consensus-paper/latex8.bst similarity index 100% rename from spec/consensus/latex8.bst rename to spec/consensus/consensus-paper/latex8.bst diff --git a/spec/consensus/latex8.sty b/spec/consensus/consensus-paper/latex8.sty similarity index 100% rename from spec/consensus/latex8.sty rename to spec/consensus/consensus-paper/latex8.sty diff --git a/spec/consensus/lit.bib b/spec/consensus/consensus-paper/lit.bib similarity index 100% rename from spec/consensus/lit.bib rename to spec/consensus/consensus-paper/lit.bib diff --git a/spec/consensus/paper.tex b/spec/consensus/consensus-paper/paper.tex similarity index 100% rename from spec/consensus/paper.tex rename to spec/consensus/consensus-paper/paper.tex diff --git a/spec/consensus/proof.tex b/spec/consensus/consensus-paper/proof.tex similarity index 100% rename from spec/consensus/proof.tex rename to spec/consensus/consensus-paper/proof.tex diff --git a/spec/consensus/rounddiag.sty b/spec/consensus/consensus-paper/rounddiag.sty similarity index 100% rename from spec/consensus/rounddiag.sty rename to spec/consensus/consensus-paper/rounddiag.sty diff --git a/spec/consensus/technote.sty b/spec/consensus/consensus-paper/technote.sty similarity index 100% rename from spec/consensus/technote.sty rename to spec/consensus/consensus-paper/technote.sty diff --git a/spec/consensus/consensus.md b/spec/consensus/consensus.md new file mode 100644 index 000000000..9c43ea615 --- /dev/null +++ b/spec/consensus/consensus.md @@ -0,0 +1,339 @@ +# 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](../blockchain/blockchain.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: + +``` +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 + +``` + +-------------------------------------+ + 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 `HasVote` 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](../blockchain/blockchain.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). diff --git a/spec/consensus/creating-proposal.md b/spec/consensus/creating-proposal.md new file mode 100644 index 000000000..831d16571 --- /dev/null +++ b/spec/consensus/creating-proposal.md @@ -0,0 +1,42 @@ +# 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 - + MaxAminoOverheadForBlock - + 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 because the evidence size is unknown at the moment, we subtract +maximum evidence size (1/10th of the maximum block size). + +```go +func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 { + return maxBytes - + MaxAminoOverheadForBlock - + MaxHeaderBytes - + int64(valsCount)*MaxVoteBytes - + MaxEvidenceBytesPerBlock(maxBytes) +} +``` diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/fork-accountability.md new file mode 100644 index 000000000..a6af8a1ba --- /dev/null +++ b/spec/consensus/fork-accountability.md @@ -0,0 +1,319 @@ +# Fork accountability -- Problem statement and attacks + +## 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 at most 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 (lite clients) that the blocks had been correctly generated. In addition, Tendermint agreement might be violated in the case where more than 1/3 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 Lite 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 Lite 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 more than 1/3 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 at most 1/3 of the voting power is faulty, this misbehavior is detectable but will not lead to a safety violation. Thus, unless they have more than 1/3 (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 lite clients. However, even without breaking this system invariant, lite clients can be subject to a fork: + +* Fork-Lite. 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 lite 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-Lite). + + + + + +## 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 lite 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: lite client with sequential header verification +- LCB: lite 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 lite 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, lite 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-Lite), 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 lite client (LCS or LCB) contacts a full node to obtain headers (or blocks), the corrupted state may propagate to the lite 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:** Lite 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 lite clients. It requires more than 1/3 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 more than 1/3 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 lite clients, +* For both we need a detection and recovery mechanism. + +#### Scenario 2: Equivocation to a lite 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 lite clients obtains B and trusts at as it is signed by more and 2/3 of the voting power. + +Consequences: + +Once equivocation is used to attack lite 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 lite 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 lite 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 lite 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 lite 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 lite client about arbitrary state. In case of LCB where security threshold is at minimum, an attacker can arbitrarily modify application state with more than 1/3 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 lite clients. + +#### Scenario 3: At most 2/3 of faults + +Validators: + +* a set F of faulty validators with more than 1/3 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: https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit?usp=sharing. + +If a lite client is attacked using this attack with more than 1/3 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 lite 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 more than 1/3 of the voting power +* a set F2 of faulty validators with at most 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 https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit?usp=sharing 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 different mechanism that would require as an evidence a sequence of blocks that lead to that state. This might be very tricky to implement. + +### Back to the past + +In this kind of attacks 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 lite clients. + +#### Scenario 5: + +Validators: +* C1 - a set of correct validators with 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 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 lite 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 exists 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 lite client has a trust in a header for height *h* (and the corresponding validator set VS1). +* As part of bisection header verification, it verifies 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 he is eclipsed. + +### Lunatic validator + +Lunatic validator agrees to sign commit messages for arbitrary application state. It is used to attack lite 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 ignore to check if proposed value is valid before voting for it? diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md new file mode 100644 index 000000000..18dc280a3 --- /dev/null +++ b/spec/consensus/light-client.md @@ -0,0 +1,329 @@ +# Lite client + +A lite client is a process that connects to Tendermint full nodes and then tries to verify application data using the Merkle proofs. + +## Context of this document + +In order to make sure that full nodes have the incentive to follow the protocol, we have to address the following three Issues + +1) The lite client needs a method to verify headers it obtains from full nodes according to trust assumptions -- this document. + +2) The lite client must be able to connect to one correct full node to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document. + +3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- see #3840 + +## Problem statement + + +We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because the lite 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. The term "trusting" above indicates that the correctness on the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged *inithead* is negligible. + + +## Definitions + +### Data structures + +In the following, only the details of the data structures needed for this specification are given. + + * header fields + - *height* + - *bfttime*: the chain time when the header (block) was generated + - *V*: validator set containing validators for this block. + - *NextV*: validator set for next block. + - *commit*: evidence that block with height *height* - 1 was committed by a set of validators (canonical commit). We will use ```signers(commit)``` to refer to the set of validators that committed the block. + + * signed header fields: contains a header and a *commit* for the current header; a "seen commit". In the Tendermint consensus the "canonical commit" is stored in header *height* + 1. + + * For each header *h* it has locally stored, the lite client stores whether + it trusts *h*. We write *trust(h) = true*, if this is the case. + + * Validator fields. We will write a validator as a tuple *(v,p)* such that + + *v* is the identifier (we assume identifiers are unique in each validator set) + + *p* is its voting power + + +### Functions + +For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following function over Tendermint RPC: +```go + func Commit(height int64) (SignedHeader, error) + // returns signed header: header (with the fields from + // above) with Commit that include signatures of + // validators that signed the header + + + type SignedHeader struct { + Header Header + Commit Commit + } +``` + +### Definitions + +* *tp*: trusting 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). + + + + +### Tendermint Failure Model + +If a block *h* is generated at time *bfttime* (and this time is stored in the block), then a set of validators that hold more than 2/3 of the voting power in h.Header.NextV is correct until time h.Header.bfttime + tp. + +Formally, +\[ +\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > +2/3 \sum_{(v,p) \in h.Header.NextV} p +\] + +*Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), while *bfttime* corresponds to the reading of the local clock of a validator (how this time is computed may change when the Tendermint consensus is modified). In this note, we assume that all clocks are synchronized to realtime. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and *tp* is in the order of weeks. + +*Remark*: This failure model might change to a hybrid version that takes heights into account in the future. + +The specification in this document considers an implementation of the lite client under this assumption. Issues like *counter-factual signing* and *fork accountability* and *evidence submission* are mechanisms that justify this assumption by incentivizing validators to follow the protocol. +If they don't, and we have more that 1/3 faults, 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 an upcoming document on "Fork accountability". (These safety violations include the lite client wrongly trusting a header, a fork in the blockchain, etc.) + + +## Lite Client Trusting Spec + +The lite 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: + +- 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. + +- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. + +*Remark*: If in the course of the computation, the lite 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.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. + +*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. + +*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. + +To reason about the correctness, we may prove the following invariant. + +*Verification Condition: Lite Client Invariant.* + For each lite client *l* and each header *h*: +if *l* has set *trust(h) = true*, + then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. + + Formally, + \[ + \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > + 2/3 \sum_{(v,p) \in h.Header.NextV} p + \] + +*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. + + +## High Level Solution + +Upon initialization, the lite client is given a header *inithead* it trusts (by +social consensus). It is assumed that *inithead* satisfies the lite client invariant. (If *inithead* has been correctly generated by Tendermint consensus, the invariant follows from the Tendermint Failure Model.) + +When a lite clients sees a signed new 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 proof.** If a block is appended to the chain, where the last block +is trusted (and properly committed by the old validator set in the next block), +and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. +Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. + +2. **Trusting period.** Based on a trusted block *h*, and the lite client +invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. +If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. + +3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* 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 *snh*; if not, we may continue recursively. + +## How to use it + +We consider the following use case: + the lite client wants to verify a header for some given height *k*. Thus: + - it requests the signed header for height *k* from a full node + - it tries to verify this header with the methods described here. + +This can be used in several settings: + - someone tells the lite client that application data that is relevant for it can be read in the block of height *k*. + - the lite clients wants the latest state. It asks a full nude for the current height, and uses the response for *k*. + + +## Details + +*Assumptions* + +1. *tp < unbonding period*. +2. *snh.Header.bfttime < now* +3. *snh.Header.bfttime < h.Header.bfttime+tp* +4. *trust(h)=true* + + +**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old +validator set *h.Header.NextV*. + +When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* is correct, but we only trust the fact that at most 1/3 of them are faulty (more precisely, the faulty ones have at most 1/3 of the total voting power). + + + +### Functions + +The function *Bisection* checks whether to trust header *h2* based on the trusted header *h1*. It does so by calling +the function *CheckSupport* in the process of +bisection/recursion. *CheckSupport* implements the trusted period method and, for two adjacent headers (in term of heights), it checks uninterrupted sequence of proof. + +*Assumption*: In the following, we assume that *h2.Header.height > h1.Header.height*. We will quickly discuss the other case in the next section. + +We consider the following set-up: +- the lite client communicates with one full node +- the lite client locally stores all the signed headers it obtained (trusted or not). In the pseudo code below we write *Store(header)* for this. +- If *Bisection* returns *false*, then the lite client has seen a forged header. + * However, it does not know which header(s) is/are the problematic one(s). + * In this case, the lite client can submit (some of) the headers it has seen as evidence. As the lite client communicates with one full node only when executing Bisection, there are two cases + - the full node is faulty + - the full node is correct and there was a fork in Tendermint consensus. Header *h1* is from a different branch than the one taken by the full node. This case is not focus of this document, but will be treated in the document on fork accountability. + +- the lite client must retry to retrieve correct headers from another full node + * it picks a new full node + * it restarts *Bisection* + * there might be optimizations; a lite client may not need to call *Commit(k)*, for a height *k* for which it already has a signed header it trusts. + * how to make sure that a lite client can communicate with a correct full node will be the focus of a separate document (recall Issue 3 from "Context of this document"). + +**Auxiliary Functions.** We will use the function ```votingpower_in(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; +we will write ```totalVotingPower(V)``` for ```votingpower_in(V,V)```, which returns the total voting power in V. +We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. + +**CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. + +```go + func CheckSupport(h1,h2,trustlevel) bool { + if h1.Header.bfttime + tp < now { // Observation 1 + return false // old header was once trusted but it is expired + } + vp_all := totalVotingPower(h1.Header.NextV) + // total sum of voting power of validators in h2 + + if h2.Header.height == h1.Header.height + 1 { + // specific check for adjacent headers; everything must be + // properly signed. + // also check that h2.Header.V == h1.Header.NextV + // Plus the following check that 2/3 of the voting power + // in h1 signed h2 + return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > + 2/3 * vp_all) + // signing validators are more than two third in h1. + } + + return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > + max(1/3,trustlevel) * vp_all) + // get validators in h1 that signed h2 + // sum of voting powers in h1 of + // validators that signed h2 + // is more than a third in h1 + } +``` + + *Remark*: Basic header verification must be done for *h2*. Similar checks are done in: + https://github.com/tendermint/tendermint/blob/master/types/validator_set.go#L591-L633 + + *Remark*: There are some sanity checks which are not in the code: + *h2.Header.height > h1.Header.height* and *h2.Header.bfttime > h1.Header.bfttime* and *h2.Header.bfttime < now*. + + *Remark*: ```return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all)``` may return false even if *h2* was properly generated by Tendermint consensus in the case of big changes in the validator sets. However, the check ```return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > + 2/3 * vp_all)``` must return true if *h1* and *h2* were generated by Tendermint consensus. + +*Remark*: The 1/3 check differs from a previously proposed method that was based on intersecting validator sets and checking that the new validator set contains "enough" correct validators. We found that the old check is not suited for realistic changes in the validator sets. The new method is not only based on cardinalities, but also exploits that we can trust what is signed by a correct validator (i.e., signed by more than 1/3 of the voting power). + +*Correctness arguments* + +Towards Lite Client Accuracy: +- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because *CheckSupport* returns true. +- h1 is trusted and sufficiently new +- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed *h2*. +- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing *h2* => *h2* was correctly generated, we arrive at the required contradiction. + + +Towards Lite Client Completeness: +- The check is successful if sufficiently many validators of *h1* are still validators in *h2* and signed *h2*. +- If *h2.Header.height = h1.Header.height + 1*, and both headers were generated correctly, the test passes + +*Verification Condition:* We may need a Tendermint invariant stating that if *h2.Header.height = h1.Header.height + 1* then *signers(h2.Commit) \subseteq h1.Header.NextV*. + +*Remark*: The variable *trustlevel* 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 *trustlevel* is chosen, the more unlikely it becomes that CheckSupport returns true for non-adjacent headers. + +**Bisection.** The following function uses CheckSupport in a recursion to find intermediate headers that allow to establish a sequence of trust. + + + + +```go +func Bisection(h1,h2,trustlevel) bool{ + if CheckSupport(h1,h2,trustlevel) { + return true + } + if h2.Header.height == h1.Header.height + 1 { + // we have adjacent headers that are not matching (failed + // the CheckSupport) + // we could submit evidence here + return false + } + pivot := (h1.Header.height + h2.Header.height) / 2 + hp := Commit(pivot) + // ask a full node for header of height pivot + Store(hp) + // store header hp locally + if Bisection(h1,hp,trustlevel) { + // only check right branch if hp is trusted + // (otherwise a lot of unnecessary computation may be done) + return Bisection(hp,h2,trustlevel) + } + else { + return false + } +} +``` + + + + +*Correctness arguments (sketch)* + +Lite Client Accuracy: +- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because Bisection returns true. +- Bisection returns true only if all calls to CheckSupport in the recursion return true. +- Thus we have a sequence of headers that all satisfied the CheckSupport +- again a contradiction + +Lite Client Completeness: + +This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. + +*Stalling* + +With Bisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite 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 lite 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, Bisection would then be executed at the full node. +* We may set a timeout how long bisection may take. + + +### The case *h2.Header.height < h1.Header.height* + +In the use case where someone tells the lite client that application data that is relevant for it can be read in the block of height *k* and the lite 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 lite 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 Backwards(h1,h2) bool { + assert (h2.Header.height < h1.Header.height) + old := h1 + for i := h1.Header.height - 1; i > h2.Header.height; i-- { + new := Commit(i) + Store(new) + if (hash(new) != old.Header.hash) { + return false + } + old := new + } + return (hash(h2) == old.Header.hash) + } +``` diff --git a/spec/consensus/signing.md b/spec/consensus/signing.md new file mode 100644 index 000000000..78944fa52 --- /dev/null +++ b/spec/consensus/signing.md @@ -0,0 +1,205 @@ +# 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: + +``` +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: + +``` +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`: + +``` +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: + +``` +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: + +``` +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. diff --git a/spec/consensus/wal.md b/spec/consensus/wal.md new file mode 100644 index 000000000..92bbfc4b2 --- /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-corruptionn) +and recovery strategies. From 95cf253b6df623066ff7cd4074a94e7a3f147c7a Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 17 Sep 2019 16:07:08 +0200 Subject: [PATCH 007/223] Add ABCI SPEC (#51) - move the abci spec from tendermint to spec repo Signed-off-by: Marko Baricevic --- spec/README.md | 4 + spec/abci/README.md | 19 ++ spec/abci/abci.md | 532 +++++++++++++++++++++++++++++++++++++ spec/abci/apps.md | 453 +++++++++++++++++++++++++++++++ spec/abci/client-server.md | 109 ++++++++ 5 files changed, 1117 insertions(+) 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 diff --git a/spec/README.md b/spec/README.md index ba881643e..00c112f79 100644 --- a/spec/README.md +++ b/spec/README.md @@ -3,3 +3,7 @@ This folder houses the spec of Tendermint the Protocol. **Note: We are currently working on expanding the spec and will slowly be migrating it from Tendermint the repo** + +### Table of Contents + +- [ABCI Spec](./abci/README.md) diff --git a/spec/abci/README.md b/spec/abci/README.md new file mode 100644 index 000000000..56d5e8aaf --- /dev/null +++ b/spec/abci/README.md @@ -0,0 +1,19 @@ +# Overview + +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_, where each method has a corresponding `Request` and `Response` +message type. Tendermint calls the ABCI methods on the ABCI application by sending the `Request*` +messages and receiving the `Response*` messages in return. + +All message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto). +This allows Tendermint to run applications written in any programming language. + +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 000000000..275c4dcd8 --- /dev/null +++ b/spec/abci/abci.md @@ -0,0 +1,532 @@ +# Methods and Types + +## Overview + +The ABCI message types are defined in a [protobuf +file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto). + +ABCI methods are split across 3 separate ABCI _connections_: + +- `Consensus Connection`: `InitChain, BeginBlock, DeliverTx, EndBlock, Commit` +- `Mempool Connection`: `CheckTx` +- `Info Connection`: `Info, SetOption, Query` + +The `Consensus Connection` is driven by a consensus protocol and is responsible +for block execution. +The `Mempool Connection` is for validating new transactions, before they're +shared or included in a block. +The `Info Connection` is for initialization and for queries from the user. + +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 + +Some methods (`Echo, Info, InitChain, BeginBlock, EndBlock, Commit`), +don't return errors because an error would indicate a critical failure +in the application and there's nothing Tendermint can do. The problem +should be addressed and both Tendermint and the application restarted. + +All other methods (`SetOption, Query, CheckTx, DeliverTx`) return an +application-specific response `Code uint32`, where only `0` is reserved +for `OK`. + +Finally, `Query`, `CheckTx`, and `DeliverTx` include a `Codespace string`, whose +intended use is to disambiguate `Code` values returned by different domains of the +application. The `Codespace` is a namespace for the `Code`. + +## Events + +Some methods (`CheckTx, BeginBlock, DeliverTx, EndBlock`) +include an `Events` field in their `Response*`. Each event contains a type and a +list of attributes, which are key-value pairs denoting something about what happened +during the method's execution. + +Events 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 +tag, 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 even type itself. + +Example: + +```go + abci.ResponseDeliverTx{ + // ... + Events: []abci.Event{ + { + Type: "validator.provisions", + Attributes: cmn.KVPairs{ + cmn.KVPair{Key: []byte("address"), Value: []byte("...")}, + cmn.KVPair{Key: []byte("amount"), Value: []byte("...")}, + cmn.KVPair{Key: []byte("balance"), Value: []byte("...")}, + }, + }, + { + Type: "validator.provisions", + Attributes: cmn.KVPairs{ + cmn.KVPair{Key: []byte("address"), Value: []byte("...")}, + cmn.KVPair{Key: []byte("amount"), Value: []byte("...")}, + cmn.KVPair{Key: []byte("balance"), Value: []byte("...")}, + }, + }, + { + Type: "validator.slashed", + Attributes: cmn.KVPairs{ + cmn.KVPair{Key: []byte("address"), Value: []byte("...")}, + cmn.KVPair{Key: []byte("amount"), Value: []byte("...")}, + cmn.KVPair{Key: []byte("reason"), Value: []byte("...")}, + }, + }, + // ... + }, +} +``` + +## Determinism + +ABCI applications must implement deterministic finite-state machines to be +securely replicated by the Tendermint consensus. 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 (`SetOption, 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. + +## 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**: + - `Version (string)`: The Tendermint software semantic version + - `BlockVersion (uint64)`: The Tendermint Block Protocol version + - `P2PVersion (uint64)`: The Tendermint P2P Protocol version +- **Response**: + - `Data (string)`: Some arbitrary information + - `Version (string)`: The application software semantic version + - `AppVersion (uint64)`: The application protocol version + - `LastBlockHeight (int64)`: Latest block for which the app has + called Commit + - `LastBlockAppHash ([]byte)`: Latest result of Commit +- **Usage**: + - Return information about the application state. + - Used to sync Tendermint with the application during a handshake + that happens on startup. + - The returned `AppVersion` will be included in the Header of every block. + - Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to + be updated during `Commit`, ensuring that `Commit` is never + called twice for the same block height. + +### SetOption + +- **Request**: + - `Key (string)`: Key to set + - `Value (string)`: Value to set for key +- **Response**: + - `Code (uint32)`: Response code + - `Log (string)`: The output of the application's logger. May + be non-deterministic. + - `Info (string)`: Additional information. May + be non-deterministic. +- **Usage**: + - Set non-consensus critical application specific options. + - e.g. Key="min-fee", Value="100fermion" could set the minimum fee + required for CheckTx (but not DeliverTx - that would be + consensus critical). + +### InitChain + +- **Request**: + - `Time (google.protobuf.Timestamp)`: Genesis time. + - `ChainID (string)`: ID of the blockchain. + - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters. + - `Validators ([]ValidatorUpdate)`: Initial genesis validators. + - `AppStateBytes ([]byte)`: Serialized initial application state. Amino-encoded JSON bytes. +- **Response**: + - `ConsensusParams (ConsensusParams)`: Initial + consensus-critical parameters. + - `Validators ([]ValidatorUpdate)`: Initial validator set (if non empty). +- **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, the initial validator set will be the + ResponseInitChain.Validators (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**: + - `Data ([]byte)`: Raw query bytes. Can be used with or in lieu + of Path. + - `Path (string)`: Path of request, like an HTTP GET path. Can be + used with or in liue 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/...' + - `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 + - `Prove (bool)`: Return Merkle proof with response if possible +- **Response**: + - `Code (uint32)`: Response code. + - `Log (string)`: The output of the application's logger. May + be non-deterministic. + - `Info (string)`: Additional information. May + be non-deterministic. + - `Index (int64)`: The index of the key in the tree. + - `Key ([]byte)`: The key of the matching data. + - `Value ([]byte)`: The value of the matching data. + - `Proof (Proof)`: Serialized proof for the value data, if requested, to be + verified against the `AppHash` for the given Height. + - `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 + - `Codespace (string)`: Namespace for the `Code`. +- **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**: + - `Hash ([]byte)`: The block's hash. This can be derived from the + block header. + - `Header (struct{})`: The block header. + - `LastCommitInfo (LastCommitInfo)`: Info about the last commit, including the + round, and the list of validators and which ones signed the last block. + - `ByzantineValidators ([]Evidence)`: List of evidence of + validators that acted maliciously. +- **Response**: + - `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing +- **Usage**: + - Signals the beginning of a new block. Called prior to + any DeliverTxs. + - 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. NOTE validators here do not + include pubkeys. + +### CheckTx + +- **Request**: + - `Tx ([]byte)`: The request transaction bytes + - `Type (CheckTxType)`: What type of `CheckTx` request is this? At present, + there are two possible values: `CheckTx_New` (the default, which says + that a full check is required), and `CheckTx_Recheck` (when the mempool is + initiating a normal recheck of a transaction). +- **Response**: + - `Code (uint32)`: Response code + - `Data ([]byte)`: Result bytes, if any. + - `Log (string)`: The output of the application's logger. May + be non-deterministic. + - `Info (string)`: Additional information. May + be non-deterministic. + - `GasWanted (int64)`: Amount of gas requested for transaction. + - `GasUsed (int64)`: Amount of gas consumed by transaction. + - `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing + transactions (eg. by account). + - `Codespace (string)`: Namespace for the `Code`. +- **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 need not execute the transaction in full, but rather a light-weight + yet stateful validation, like checking signatures and account balances, but + 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**: + - `Tx ([]byte)`: The request transaction bytes. +- **Response**: + - `Code (uint32)`: Response code. + - `Data ([]byte)`: Result bytes, if any. + - `Log (string)`: The output of the application's logger. May + be non-deterministic. + - `Info (string)`: Additional information. May + be non-deterministic. + - `GasWanted (int64)`: Amount of gas requested for transaction. + - `GasUsed (int64)`: Amount of gas consumed by transaction. + - `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing + transactions (eg. by account). + - `Codespace (string)`: Namespace for the `Code`. +- **Usage**: + - The workhorse of the application - non-optional. + - Execute the transaction in full. + - `ResponseDeliverTx.Code == 0` only if the transaction is fully valid. + +### EndBlock + +- **Request**: + - `Height (int64)`: Height of the block just executed. +- **Response**: + - `ValidatorUpdates ([]ValidatorUpdate)`: Changes to validator set (set + voting power to 0 to remove). + - `ConsensusParamUpdates (ConsensusParams)`: Changes to + consensus-critical time, size, and other parameters. + - `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing +- **Usage**: + - Signals the end of a block. + - Called after all transactions, prior to each Commit. + - Validator updates returned by block `H` impact blocks `H+1`, `H+2`, and + `H+3`, but only effects changes on the validator set of `H+2`: + - `H+1`: NextValidatorsHash + - `H+2`: ValidatorsHash (and thus the validator set) + - `H+3`: LastCommitInfo (ie. the last validator set) + - Consensus params returned for block `H` apply for block `H+1` + +### Commit + +- **Response**: + - `Data ([]byte)`: The Merkle root hash of the application state +- **Usage**: + - 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. + +## Data Types + +### Header + +- **Fields**: + - `Version (Version)`: Version of the blockchain and the application + - `ChainID (string)`: ID of the blockchain + - `Height (int64)`: Height of the block in the chain + - `Time (google.protobuf.Timestamp)`: Time of the previous block. + For heights > 1, it's the weighted median of the timestamps of the valid + votes in the block.LastCommit. + For height == 1, it's genesis time. + - `NumTxs (int32)`: Number of transactions in the block + - `TotalTxs (int64)`: Total number of transactions in the blockchain until + now + - `LastBlockID (BlockID)`: Hash of the previous (parent) block + - `LastCommitHash ([]byte)`: Hash of the previous block's commit + - `ValidatorsHash ([]byte)`: Hash of the validator set for this block + - `NextValidatorsHash ([]byte)`: Hash of the validator set for the next block + - `ConsensusHash ([]byte)`: Hash of the consensus parameters for this block + - `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the + Merkle root of the application state after executing the previous block's + transactions + - `LastResultsHash ([]byte)`: Hash of the ABCI results returned by the last block + - `EvidenceHash ([]byte)`: Hash of the evidence included in this block + - `ProposerAddress ([]byte)`: Original proposer for the block +- **Usage**: + - Provided in RequestBeginBlock + - Provides important context about the current state of the blockchain - + especially height and time. + - Provides the proposer of the current block, for use in proposer-based + reward mechanisms. + +### Version + +- **Fields**: + - `Block (uint64)`: Protocol version of the blockchain data structures. + - `App (uint64)`: Protocol version of the application. +- **Usage**: + - Block version should be static in the life of a blockchain. + - App version may be updated over time by the application. + +### Validator + +- **Fields**: + - `Address ([]byte)`: Address of the validator (hash of the public key) + - `Power (int64)`: Voting power of the validator +- **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**: + - `PubKey (PubKey)`: Public key of the validator + - `Power (int64)`: Voting power of the validator +- **Usage**: + - Validator identified by PubKey + - Used to tell Tendermint to update the validator set + +### VoteInfo + +- **Fields**: + - `Validator (Validator)`: A validator + - `SignedLastBlock (bool)`: Indicates whether or not the validator signed + the last block +- **Usage**: + - Indicates whether a validator signed the last block, allowing for rewards + based on validator availability + +### PubKey + +- **Fields**: + - `Type (string)`: Type of the public key. A simple string like `"ed25519"`. + In the future, may indicate a serialization algorithm to parse the `Data`, + for instance `"amino"`. + - `Data ([]byte)`: Public key data. For a simple public key, it's just the + raw bytes. If the `Type` indicates an encoding algorithm, this is the + encoded public key. +- **Usage**: + - A generic and extensible typed public key + +### Evidence + +- **Fields**: + - `Type (string)`: Type of the evidence. A hierarchical path like + "duplicate/vote". + - `Validator (Validator`: The offending validator + - `Height (int64)`: Height when the offense was committed + - `Time (google.protobuf.Timestamp)`: Time of the block at height `Height`. + It is the proposer's local time when block was created. + - `TotalVotingPower (int64)`: Total voting power of the validator set at + height `Height` + +### LastCommitInfo + +- **Fields**: + - `Round (int32)`: Commit round. + - `Votes ([]VoteInfo)`: List of validators addresses in the last validator set + with their voting power and whether or not they signed a vote. + +### ConsensusParams + +- **Fields**: + - `Block (BlockParams)`: Parameters limiting the size of a block and time between consecutive blocks. + - `Evidence (EvidenceParams)`: Parameters limiting the validity of + evidence of byzantine behaviour. + - `Validator (ValidatorParams)`: Parameters limitng the types of pubkeys validators can use. + +### BlockParams + +- **Fields**: + - `MaxBytes (int64)`: Max size of a block, in bytes. + - `MaxGas (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! + +### EvidenceParams + +- **Fields**: + - `MaxAge (int64)`: Max age of evidence, in blocks. Evidence older than this + is considered stale and ignored. + - This should correspond with an app's "unbonding period" or other + similar mechanism for handling Nothing-At-Stake attacks. + - NOTE: this should change to time (instead of blocks)! + +### ValidatorParams + +- **Fields**: + - `PubKeyTypes ([]string)`: List of accepted pubkey types. Uses same + naming as `PubKey.Type`. + +### Proof + +- **Fields**: + - `Ops ([]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. + +### ProofOp + +- **Fields**: + - `Type (string)`: Type of Merkle proof and how it's encoded. + - `Key ([]byte)`: Key in the Merkle tree that this proof is for. + - `Data ([]byte)`: Encoded Merkle proof for the key. diff --git a/spec/abci/apps.md b/spec/abci/apps.md new file mode 100644 index 000000000..528f92634 --- /dev/null +++ b/spec/abci/apps.md @@ -0,0 +1,453 @@ +# 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](#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 + +Since Tendermint maintains three 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`. + +### 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 three +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`. + +The CheckTxState may be updated concurrently with the DeliverTxState, as +messages may be sent concurrently on the Consensus and Mempool connections. However, +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. + +After `Commit`, CheckTx is run again on all transactions that remain in the +node's local mempool after filtering those included in the block. To prevent the +mempool from rechecking all transactions every time a block is committed, set +the configuration option `mempool.recheck=false`. As of Tendermint v0.32.1, +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, the mempool will unlock and new transactions can be processed through CheckTx again. + +Note that CheckTx doesn't have to check everything that affects transaction validity; the +expensive things can be skipped. In fact, CheckTx doesn't have to check +anything; it might say that any transaction is a valid transaction. +Unlike DeliverTx, CheckTx is just there as +a sort of weak filter to keep invalid transactions out of the blockchain. It's +weak, because a Byzantine node doesn't care about CheckTx; it can propose a +block full of invalid transactions if it wants. + +### Info 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`, +ie. after the full block has been processed and the state committed to disk. +Otherwise it should never be modified. + +## Transaction Results + +`ResponseCheckTx` and `ResponseDeliverTx` contain the same fields. + +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` 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. + +`Tags` include any tags for the execution, though since the transaction has not +been committed yet, they are effectively ignored by Tendermint. + +### DeliverTx + +If DeliverTx returns `Code != 0`, the transaction will be considered invalid, +though it is still included in the block. + +`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. + +`Tags` include any tags 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. + +See issue [#1007](https://github.com/tendermint/tendermint/issues/1007) for how +the tags will be hashed into the next block header. + +## Validator Updates + +The application may set the validator set during InitChain, and 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 in an update once. If an update includes +duplicates, the block execution will fail irrecoverably. + +### InitChain + +ResponseInitChain can return a list of validators. +If the list is empty, Tendermint will use the validators loaded in the genesis +file. +If the list is not empty, Tendermint will use it for the validator set. +This way the application can determine the initial validator set for the +blockchain. + +### EndBlock + +Updates to the Tendermint validator set can be made by returning +`ValidatorUpdate` objects in the `ResponseEndBlock`: + +``` +message ValidatorUpdate { + PubKey pub_key + int64 power +} + +message PubKey { + string type + bytes data +} +``` + +The `pub_key` currently supports only one type: + +- `type = "ed25519"` and `data = ` + +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. + +### Block.MaxBytes + +The maximum size of a complete Amino encoded block. +This is enforced by Tendermint consensus. + +This implies a maximum tx 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`. + +### Block.MaxGas + +The maximum of the sum of `GasWanted` 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. + +### Block.TimeIotaMs + +The minimum time between consecutive blocks (in milliseconds). +This is enforced by Tendermint consensus. + +Must have `TimeIotaMs > 0` to ensure time monotonicity. + +### EvidenceParams.MaxAge + +This is the maximum age of evidence. +This is enforced by Tendermint consensus. +If a block includes evidence older than this, the block will be rejected +(validators won't vote for it). + +Must have `MaxAge > 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 its nil, Tendermint will use the params loaded in the genesis +file. If it's 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 its nil, Tendermint will do nothing. +If it's 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 lite-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 lite-client proofs. + +ABCI applications can take advantage of more efficient lite-client proofs for +their state as follows: + +- return the Merkle root of the deterministic application state in +`ResponseCommit.Data`. +- it 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 lite-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: + +``` +message Proof { + 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 but not H+1, 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. + +``` +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 we never call 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. + diff --git a/spec/abci/client-server.md b/spec/abci/client-server.md new file mode 100644 index 000000000..94485f0d9 --- /dev/null +++ b/spec/abci/client-server.md @@ -0,0 +1,109 @@ +# 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/abci/types/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 only) +- 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), +[Python](https://github.com/tendermint/tendermint/tree/master/abci/example/python3/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 TendermintCore 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/abci/types/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 three 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). From fa3430ad163a2a0ed77aa3f624a70cd9b8b84b78 Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 2 Oct 2019 15:28:56 -0700 Subject: [PATCH 008/223] 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 --- spec/consensus/signing.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/consensus/signing.md b/spec/consensus/signing.md index 78944fa52..574a38305 100644 --- a/spec/consensus/signing.md +++ b/spec/consensus/signing.md @@ -203,3 +203,27 @@ In other words, a vote should only be signed if it's: 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). From 513c67230ffb3a042378eca987a0d13a5116657d Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 7 Oct 2019 15:02:20 +0200 Subject: [PATCH 009/223] 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 --- spec/README.md | 82 +++- spec/p2p/config.md | 38 ++ spec/p2p/connection.md | 111 ++++++ spec/p2p/node.md | 66 ++++ spec/p2p/peer.md | 119 ++++++ .../bcv1/img/bc-reactor-new-datastructs.png | Bin 0 -> 44461 bytes .../bcv1/img/bc-reactor-new-fsm.png | Bin 0 -> 42091 bytes .../bcv1/img/bc-reactor-new-goroutines.png | Bin 0 -> 140946 bytes spec/reactors/block_sync/bcv1/impl-v1.md | 237 ++++++++++++ .../block_sync/img/bc-reactor-routines.png | Bin 0 -> 271695 bytes spec/reactors/block_sync/img/bc-reactor.png | Bin 0 -> 44211 bytes spec/reactors/block_sync/impl.md | 44 +++ spec/reactors/block_sync/reactor.md | 308 +++++++++++++++ spec/reactors/consensus/consensus-reactor.md | 353 ++++++++++++++++++ spec/reactors/consensus/consensus.md | 184 +++++++++ spec/reactors/consensus/proposer-selection.md | 291 +++++++++++++++ spec/reactors/evidence/reactor.md | 10 + spec/reactors/mempool/concurrency.md | 8 + spec/reactors/mempool/config.md | 54 +++ spec/reactors/mempool/functionality.md | 43 +++ spec/reactors/mempool/messages.md | 61 +++ spec/reactors/mempool/reactor.md | 22 ++ spec/reactors/pex/pex.md | 132 +++++++ spec/reactors/pex/reactor.md | 12 + 24 files changed, 2170 insertions(+), 5 deletions(-) create mode 100644 spec/p2p/config.md create mode 100644 spec/p2p/connection.md create mode 100644 spec/p2p/node.md create mode 100644 spec/p2p/peer.md create mode 100644 spec/reactors/block_sync/bcv1/img/bc-reactor-new-datastructs.png create mode 100644 spec/reactors/block_sync/bcv1/img/bc-reactor-new-fsm.png create mode 100644 spec/reactors/block_sync/bcv1/img/bc-reactor-new-goroutines.png create mode 100644 spec/reactors/block_sync/bcv1/impl-v1.md create mode 100644 spec/reactors/block_sync/img/bc-reactor-routines.png create mode 100644 spec/reactors/block_sync/img/bc-reactor.png create mode 100644 spec/reactors/block_sync/impl.md create mode 100644 spec/reactors/block_sync/reactor.md create mode 100644 spec/reactors/consensus/consensus-reactor.md create mode 100644 spec/reactors/consensus/consensus.md create mode 100644 spec/reactors/consensus/proposer-selection.md create mode 100644 spec/reactors/evidence/reactor.md create mode 100644 spec/reactors/mempool/concurrency.md create mode 100644 spec/reactors/mempool/config.md create mode 100644 spec/reactors/mempool/functionality.md create mode 100644 spec/reactors/mempool/messages.md create mode 100644 spec/reactors/mempool/reactor.md create mode 100644 spec/reactors/pex/pex.md create mode 100644 spec/reactors/pex/reactor.md diff --git a/spec/README.md b/spec/README.md index 00c112f79..7ec9387c2 100644 --- a/spec/README.md +++ b/spec/README.md @@ -1,9 +1,81 @@ -# Spec +# Overview -This folder houses the spec of Tendermint the Protocol. +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. -**Note: We are currently working on expanding the spec and will slowly be migrating it from Tendermint the repo** +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)! -### Table of Contents +## Contents -- [ABCI Spec](./abci/README.md) +- [Overview](#overview) + +### Data Structures + +- [Encoding and Digests](./blockchain/encoding.md) +- [Blockchain](./blockchain/blockchain.md) +- [State](./blockchain/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.md) + +### P2P and Network Protocols + +- [The Base P2P Layer](./p2p/): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections +- [Peer Exchange (PEX)](./reactors/pex/): gossip known peer addresses so peers can find each other +- [Block Sync](./reactors/block_sync/): gossip blocks so peers can catch up quickly +- [Consensus](./reactors/consensus/): gossip votes and block parts so new blocks can be committed +- [Mempool](./reactors/mempool/): gossip transactions so they get included in blocks +- [Evidence](./reactors/evidence/): sending invalid evidence will stop the peer + +### Software + +- [ABCI](./software/abci.md): Details about interactions between the + application and consensus engine over ABCI +- [Write-Ahead Log](./software/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/p2p/config.md b/spec/p2p/config.md new file mode 100644 index 000000000..7ff2b5e8d --- /dev/null +++ b/spec/p2p/config.md @@ -0,0 +1,38 @@ +# 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. + +**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. diff --git a/spec/p2p/connection.md b/spec/p2p/connection.md new file mode 100644 index 000000000..fd2e7bc4d --- /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. + +``` +type msgPacket struct { + ChannelID byte + EOF byte // 1 means message ends here. + Bytes []byte +} +``` + +The `msgPacket` is serialized using [go-amino](https://github.com/tendermint/go-amino) and prefixed with 0x3. +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/node.md b/spec/p2p/node.md new file mode 100644 index 000000000..6d37eeb78 --- /dev/null +++ b/spec/p2p/node.md @@ -0,0 +1,66 @@ +# 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/tendermint/blob/master/docs/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 000000000..f5c2e7bf2 --- /dev/null +++ b/spec/p2p/peer.md @@ -0,0 +1,119 @@ +# 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/tendermint/blob/master/docs/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 handhsakes 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. +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 +- compute the Diffie-Hellman shared secret using the peers ephemeral public key and our ephemeral private key +- 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 96 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 the last 32 bytes of output for the challenge +- 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 1024 byte frames, + 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 +- 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/reactors/block_sync/bcv1/img/bc-reactor-new-datastructs.png b/spec/reactors/block_sync/bcv1/img/bc-reactor-new-datastructs.png new file mode 100644 index 0000000000000000000000000000000000000000..1a92871a5bcebf2f88658623b82c6e49b9cd25bc GIT binary patch literal 44461 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 literal 0 HcmV?d00001 diff --git a/spec/reactors/block_sync/bcv1/img/bc-reactor-new-fsm.png b/spec/reactors/block_sync/bcv1/img/bc-reactor-new-fsm.png new file mode 100644 index 0000000000000000000000000000000000000000..87d6fad931c46840ac2f47243e10f9b7978046af GIT binary patch literal 42091 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 S9i>1)ezKCEC91^?gZ>}K2PIPg literal 0 HcmV?d00001 diff --git a/spec/reactors/block_sync/bcv1/img/bc-reactor-new-goroutines.png b/spec/reactors/block_sync/bcv1/img/bc-reactor-new-goroutines.png new file mode 100644 index 0000000000000000000000000000000000000000..ee853ea935cb2d7adf35d63fcf576b9df305e0f4 GIT binary patch literal 140946 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
vg`M)Wq8{7?F**9ln|l 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 E57oWiU;qFB literal 0 HcmV?d00001 diff --git a/spec/reactors/block_sync/bcv1/impl-v1.md b/spec/reactors/block_sync/bcv1/impl-v1.md new file mode 100644 index 000000000..0ffaaea69 --- /dev/null +++ b/spec/reactors/block_sync/bcv1/impl-v1.md @@ -0,0 +1,237 @@ +# Blockchain Reactor v1 + +### Data Structures +The data structures used are illustrated below. + +![Data Structures](img/bc-reactor-new-datastructs.png) + +#### BlockchainReactor +- is a `p2p.BaseReactor`. +- has a `store.BlockStore` for persistence. +- executes blocks using an `sm.BlockExecutor`. +- starts the FSM and the `poolRoutine()`. +- relays the fast-sync responses and switch messages to the FSM. +- handles errors from the FSM and when necessarily reports them to the switch. +- implements the blockchain reactor interface used by the FSM to send requests, errors to the switch and state timer resets. +- registers all the concrete types and interfaces for serialisation. + +```go +type BlockchainReactor struct { + p2p.BaseReactor + + initialState sm.State // immutable + state sm.State + + blockExec *sm.BlockExecutor + store *store.BlockStore + + fastSync bool + + fsm *BcReactorFSM + blocksSynced int + + // Receive goroutine forwards messages to this channel to be processed in the context of the poolRoutine. + messagesForFSMCh chan bcReactorMessage + + // Switch goroutine may send RemovePeer to the blockchain reactor. This is an error message that is relayed + // to this channel to be processed in the context of the poolRoutine. + errorsForFSMCh chan bcReactorMessage + + // This channel is used by the FSM and indirectly the block pool to report errors to the blockchain reactor and + // the switch. + eventsFromFSMCh chan bcFsmMessage +} +``` + +#### BcReactorFSM +- implements a simple finite state machine. +- has a state and a state timer. +- has a `BlockPool` to keep track of block requests sent to peers and blocks received from peers. +- uses an interface to send status requests, block requests and reporting errors. The interface is implemented by the `BlockchainReactor` and tests. + +```go +type BcReactorFSM struct { + logger log.Logger + mtx sync.Mutex + + startTime time.Time + + state *bcReactorFSMState + stateTimer *time.Timer + pool *BlockPool + + // interface used to call the Blockchain reactor to send StatusRequest, BlockRequest, reporting errors, etc. + toBcR bcReactor +} +``` + +#### BlockPool +- maintains a peer set, implemented as a map of peer ID to `BpPeer`. +- maintains a set of requests made to peers, implemented as a map of block request heights to peer IDs. +- maintains a list of future block requests needed to advance the fast-sync. This is a list of block heights. +- keeps track of the maximum height of the peers in the set. +- uses an interface to send requests and report errors to the reactor (via FSM). + +```go +type BlockPool struct { + logger log.Logger + // Set of peers that have sent status responses, with height bigger than pool.Height + peers map[p2p.ID]*BpPeer + // Set of block heights and the corresponding peers from where a block response is expected or has been received. + blocks map[int64]p2p.ID + + plannedRequests map[int64]struct{} // list of blocks to be assigned peers for blockRequest + nextRequestHeight int64 // next height to be added to plannedRequests + + Height int64 // height of next block to execute + MaxPeerHeight int64 // maximum height of all peers + toBcR bcReactor +} +``` +Some reasons for the `BlockPool` data structure content: +1. If a peer is removed by the switch fast access is required to the peer and the block requests made to that peer in order to redo them. +2. When block verification fails fast access is required from the block height to the peer and the block requests made to that peer in order to redo them. +3. The `BlockchainReactor` main routine decides when the block pool is running low and asks the `BlockPool` (via FSM) to make more requests. The `BlockPool` creates a list of requests and triggers the sending of the block requests (via the interface). The reason it maintains a list of requests is the redo operations that may occur during error handling. These are redone when the `BlockchainReactor` requires more blocks. + +#### BpPeer +- keeps track of a single peer, with height bigger than the initial height. +- maintains the block requests made to the peer and the blocks received from the peer until they are executed. +- monitors the peer speed when there are pending requests. +- it has an active timer when pending requests are present and reports error on timeout. + +```go +type BpPeer struct { + logger log.Logger + ID p2p.ID + + Height int64 // the peer reported height + NumPendingBlockRequests int // number of requests still waiting for block responses + blocks map[int64]*types.Block // blocks received or expected to be received from this peer + blockResponseTimer *time.Timer + recvMonitor *flow.Monitor + params *BpPeerParams // parameters for timer and monitor + + onErr func(err error, peerID p2p.ID) // function to call on error +} +``` + +### Concurrency Model + +The diagram below shows the goroutines (depicted by the gray blocks), timers (shown on the left with their values) and channels (colored rectangles). The FSM box shows some of the functionality and it is not a separate goroutine. + +The interface used by the FSM is shown in light red with the `IF` block. This is used to: +- send block requests +- report peer errors to the switch - this results in the reactor calling `switch.StopPeerForError()` and, if triggered by the peer timeout routine, a `removePeerEv` is sent to the FSM and action is taken from the context of the `poolRoutine()` +- ask the reactor to reset the state timers. The timers are owned by the FSM while the timeout routine is defined by the reactor. This was done in order to avoid running timers in tests and will change in the next revision. + +There are two main goroutines implemented by the blockchain reactor. All I/O operations are performed from the `poolRoutine()` context while the CPU intensive operations related to the block execution are performed from the context of the `executeBlocksRoutine()`. All goroutines are detailed in the next sections. + +![Go Routines Diagram](img/bc-reactor-new-goroutines.png) + +#### Receive() +Fast-sync messages from peers are received by this goroutine. It performs basic validation and: +- in helper mode (i.e. for request message) it replies immediately. This is different than the proposal in adr-040 that specifies having the FSM handling these. +- forwards response messages to the `poolRoutine()`. + +#### poolRoutine() +(named kept as in the previous reactor). +It starts the `executeBlocksRoutine()` and the FSM. It then waits in a loop for events. These are received from the following channels: +- `sendBlockRequestTicker.C` - every 10msec the reactor asks FSM to make more block requests up to a maximum. Note: currently this value is constant but could be changed based on low/ high watermark thresholds for the number of blocks received and waiting to be processed, the number of blockResponse messages waiting in messagesForFSMCh, etc. +- `statusUpdateTicker.C` - every 10 seconds the reactor broadcasts status requests to peers. While adr-040 specifies this to run within the FSM, at this point this functionality is kept in the reactor. +- `messagesForFSMCh` - the `Receive()` goroutine sends status and block response messages to this channel and the reactor calls FSM to handle them. +- `errorsForFSMCh` - this channel receives the following events: + - peer remove - when the switch removes a peer + - sate timeout event - when FSM state timers trigger + The reactor forwards this messages to the FSM. +- `eventsFromFSMCh` - there are two type of events sent over this channel: + - `syncFinishedEv` - triggered when FSM enters `finished` state and calls the switchToConsensus() interface function. + - `peerErrorEv`- peer timer expiry goroutine sends this event over the channel for processing from poolRoutine() context. + +#### executeBlocksRoutine() +Started by the `poolRoutine()`, it retrieves blocks from the pool and executes them: +- `processReceivedBlockTicker.C` - a ticker event is received over the channel every 10msec and its handling results in a signal being sent to the doProcessBlockCh channel. +- doProcessBlockCh - events are received on this channel as described as above and upon processing blocks are retrieved from the pool and executed. + + +### FSM + +![fsm](img/bc-reactor-new-fsm.png) + +#### States +##### init (aka unknown) +The FSM is created in `unknown` state. When started, by the reactor (`startFSMEv`), it broadcasts Status requests and transitions to `waitForPeer` state. + +##### waitForPeer +In this state, the FSM waits for a Status responses from a "tall" peer. A timer is running in this state to allow the FSM to finish if there are no useful peers. + +If the timer expires, it moves to `finished` state and calls the reactor to switch to consensus. +If a Status response is received from a peer within the timeout, the FSM transitions to `waitForBlock` state. + +##### waitForBlock +In this state the FSM makes Block requests (triggered by a ticker in reactor) and waits for Block responses. There is a timer running in this state to detect if a peer is not sending the block at current processing height. If the timer expires, the FSM removes the peer where the request was sent and all requests made to that peer are redone. + +As blocks are received they are stored by the pool. Block execution is independently performed by the reactor and the result reported to the FSM: +- if there are no errors, the FSM increases the pool height and resets the state timer. +- if there are errors, the peers that delivered the two blocks (at height and height+1) are removed and the requests redone. + +In this state the FSM may receive peer remove events in any of the following scenarios: +- the switch is removing a peer +- a peer is penalized because it has not responded to some block requests for a long time +- a peer is penalized for being slow + +When processing of the last block (the one with height equal to the highest peer height minus one) is successful, the FSM transitions to `finished` state. +If after a peer update or removal the pool height is same as maxPeerHeight, the FSM transitions to `finished` state. + +##### finished +When entering this state, the FSM calls the reactor to switch to consensus and performs cleanup. + +#### Events + +The following events are handled by the FSM: + +```go +const ( + startFSMEv = iota + 1 + statusResponseEv + blockResponseEv + processedBlockEv + makeRequestsEv + stopFSMEv + peerRemoveEv = iota + 256 + stateTimeoutEv +) +``` + +### Examples of Scenarios and Termination Handling +A few scenarios are covered in this section together with the current/ proposed handling. +In general, the scenarios involving faulty peers are made worse by the fact that they may quickly be re-added. + +#### 1. No Tall Peers + +S: In this scenario a node is started and while there are status responses received, none of the peers are at a height higher than this node. + +H: The FSM times out in `waitForPeer` state, moves to `finished` state where it calls the reactor to switch to consensus. + +#### 2. Typical Fast Sync + +S: A node fast syncs blocks from honest peers and eventually downloads and executes the penultimate block. + +H: The FSM in `waitForBlock` state will receive the processedBlockEv from the reactor and detect that the termination height is achieved. + +#### 3. Peer Claims Big Height but no Blocks + +S: In this scenario a faulty peer claims a big height (for which there are no blocks). + +H: The requests for the non-existing block will timeout, the peer removed and the pool's `MaxPeerHeight` updated. FSM checks if the termination height is achieved when peers are removed. + +#### 4. Highest Peer Removed or Updated to Short + +S: The fast sync node is caught up with all peers except one tall peer. The tall peer is removed or it sends status response with low height. + +H: FSM checks termination condition on peer removal and updates. + +#### 5. Block At Current Height Delayed + +S: A peer can block the progress of fast sync by delaying indefinitely the block response for the current processing height (h1). + +H: Currently, given h1 < h2, there is no enforcement at peer level that the response for h1 should be received before h2. So a peer will timeout only after delivering all blocks except h1. However the `waitForBlock` state timer fires if the block for current processing height is not received within a timeout. The peer is removed and the requests to that peer (including the one for current height) redone. diff --git a/spec/reactors/block_sync/img/bc-reactor-routines.png b/spec/reactors/block_sync/img/bc-reactor-routines.png new file mode 100644 index 0000000000000000000000000000000000000000..3f574a79b1ad304e7c03cfdb717c4f9aa600359a GIT binary patch literal 271695 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 z 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 I?OV_O1Bz9-`2YX_ literal 0 HcmV?d00001 diff --git a/spec/reactors/block_sync/impl.md b/spec/reactors/block_sync/impl.md new file mode 100644 index 000000000..35a37debb --- /dev/null +++ b/spec/reactors/block_sync/impl.md @@ -0,0 +1,44 @@ +## Blockchain Reactor v0 Modules + +### Blockchain Reactor + +- coordinates the pool for syncing +- coordinates the store for persistence +- coordinates the playing of blocks towards the app using a sm.BlockExecutor +- handles switching between fastsync and consensus +- it is a p2p.BaseReactor +- starts the pool.Start() and its poolRoutine() +- registers all the concrete types and interfaces for serialisation + +#### poolRoutine + +- listens to these channels: + - pool requests blocks from a specific peer by posting to requestsCh, block reactor then sends + a &bcBlockRequestMessage for a specific height + - pool signals timeout of a specific peer by posting to timeoutsCh + - switchToConsensusTicker to periodically try and switch to consensus + - trySyncTicker to periodically check if we have fallen behind and then catch-up sync + - if there aren't any new blocks available on the pool it skips syncing +- tries to sync the app by taking downloaded blocks from the pool, gives them to the app and stores + them on disk +- implements Receive which is called by the switch/peer + - calls AddBlock on the pool when it receives a new block from a peer + +### Block Pool + +- responsible for downloading blocks from peers +- makeRequestersRoutine() + - removes timeout peers + - starts new requesters by calling makeNextRequester() +- requestRoutine(): + - picks a peer and sends the request, then blocks until: + - pool is stopped by listening to pool.Quit + - requester is stopped by listening to Quit + - request is redone + - we receive a block + - gotBlockCh is strange + + +### Go Routines in Blockchain Reactor + +![Go Routines Diagram](img/bc-reactor-routines.png) diff --git a/spec/reactors/block_sync/reactor.md b/spec/reactors/block_sync/reactor.md new file mode 100644 index 000000000..91fd79b0b --- /dev/null +++ b/spec/reactors/block_sync/reactor.md @@ -0,0 +1,308 @@ +# Blockchain Reactor + +The Blockchain Reactor's high level responsibility is to enable peers who are +far behind the current state of the consensus to quickly catch up by downloading +many blocks in parallel, verifying their commits, and executing them against the +ABCI application. + +Tendermint full nodes run the Blockchain Reactor as a service to provide blocks +to new nodes. New nodes run the Blockchain Reactor in "fast_sync" mode, +where they actively make requests for more blocks until they sync up. +Once caught up, "fast_sync" mode is disabled and the node switches to +using (and turns on) the Consensus Reactor. + +## Message Types + +```go +const ( + msgTypeBlockRequest = byte(0x10) + msgTypeBlockResponse = byte(0x11) + msgTypeNoBlockResponse = byte(0x12) + msgTypeStatusResponse = byte(0x20) + msgTypeStatusRequest = byte(0x21) +) +``` + +```go +type bcBlockRequestMessage struct { + Height int64 +} + +type bcNoBlockResponseMessage struct { + Height int64 +} + +type bcBlockResponseMessage struct { + Block Block +} + +type bcStatusRequestMessage struct { + Height int64 + +type bcStatusResponseMessage struct { + Height int64 +} +``` + +## Architecture and algorithm + +The Blockchain reactor is organised as a set of concurrent tasks: + +- Receive routine of Blockchain Reactor +- Task for creating Requesters +- Set of Requesters tasks and - Controller task. + +![Blockchain Reactor Architecture Diagram](img/bc-reactor.png) + +### Data structures + +These are the core data structures necessarily to provide the Blockchain Reactor logic. + +Requester data structure is used to track assignment of request for `block` at position `height` to a peer with id equals to `peerID`. + +```go +type Requester { + mtx Mutex + block Block + height int64 + 
 peerID p2p.ID + redoChannel chan p2p.ID //redo may send multi-time; peerId is used to identify repeat +} +``` + +Pool is a core data structure that stores last executed block (`height`), assignment of requests to peers (`requesters`), current height for each peer and number of pending requests for each peer (`peers`), maximum peer height, etc. + +```go +type Pool { + mtx Mutex + requesters map[int64]*Requester + height int64 + peers map[p2p.ID]*Peer + maxPeerHeight int64 + numPending int32 + store BlockStore + requestsChannel chan<- BlockRequest + errorsChannel chan<- peerError +} +``` + +Peer data structure stores for each peer current `height` and number of pending requests sent to the peer (`numPending`), etc. + +```go +type Peer struct { + id p2p.ID + height int64 + numPending int32 + timeout *time.Timer + didTimeout bool +} +``` + +BlockRequest is internal data structure used to denote current mapping of request for a block at some `height` to a peer (`PeerID`). + +```go +type BlockRequest { + Height int64 + PeerID p2p.ID +} +``` + +### Receive routine of Blockchain Reactor + +It is executed upon message reception on the BlockchainChannel inside p2p receive routine. There is a separate p2p receive routine (and therefore receive routine of the Blockchain Reactor) executed for each peer. Note that try to send will not block (returns immediately) if outgoing buffer is full. + +```go +handleMsg(pool, m): + upon receiving bcBlockRequestMessage m from peer p: + block = load block for height m.Height from pool.store + if block != nil then + try to send BlockResponseMessage(block) to p + else + try to send bcNoBlockResponseMessage(m.Height) to p + + upon receiving bcBlockResponseMessage m from peer p: + pool.mtx.Lock() + requester = pool.requesters[m.Height] + if requester == nil then + error("peer sent us a block we didn't expect") + continue + + if requester.block == nil and requester.peerID == p then + requester.block = m + pool.numPending -= 1 // atomic decrement + peer = pool.peers[p] + if peer != nil then + peer.numPending-- + if peer.numPending == 0 then + peer.timeout.Stop() + // NOTE: we don't send Quit signal to the corresponding requester task! + else + trigger peer timeout to expire after peerTimeout + pool.mtx.Unlock() + + + upon receiving bcStatusRequestMessage m from peer p: + try to send bcStatusResponseMessage(pool.store.Height) + + upon receiving bcStatusResponseMessage m from peer p: + pool.mtx.Lock() + peer = pool.peers[p] + if peer != nil then + peer.height = m.height + else + peer = create new Peer data structure with id = p and height = m.Height + pool.peers[p] = peer + + if m.Height > pool.maxPeerHeight then + pool.maxPeerHeight = m.Height + pool.mtx.Unlock() + +onTimeout(p): + send error message to pool error channel + peer = pool.peers[p] + peer.didTimeout = true +``` + +### Requester tasks + +Requester task is responsible for fetching a single block at position `height`. + +```go +fetchBlock(height, pool): + while true do { + peerID = nil + block = nil + peer = pickAvailablePeer(height) + peerID = peer.id + + enqueue BlockRequest(height, peerID) to pool.requestsChannel + redo = false + while !redo do + select { + upon receiving Quit message do + return + upon receiving redo message with id on redoChannel do + if peerID == id { + mtx.Lock() + pool.numPending++ + redo = true + mtx.UnLock() + } + } + } + +pickAvailablePeer(height): + selectedPeer = nil + while selectedPeer = nil do + pool.mtx.Lock() + for each peer in pool.peers do + if !peer.didTimeout and peer.numPending < maxPendingRequestsPerPeer and peer.height >= height then + peer.numPending++ + selectedPeer = peer + break + pool.mtx.Unlock() + + if selectedPeer = nil then + sleep requestIntervalMS + + return selectedPeer +``` + +sleep for requestIntervalMS + +### Task for creating Requesters + +This task is responsible for continuously creating and starting Requester tasks. + +```go +createRequesters(pool): + while true do + if !pool.isRunning then break + if pool.numPending < maxPendingRequests or size(pool.requesters) < maxTotalRequesters then + pool.mtx.Lock() + nextHeight = pool.height + size(pool.requesters) + requester = create new requester for height nextHeight + pool.requesters[nextHeight] = requester + pool.numPending += 1 // atomic increment + start requester task + pool.mtx.Unlock() + else + sleep requestIntervalMS + pool.mtx.Lock() + for each peer in pool.peers do + if !peer.didTimeout && peer.numPending > 0 && peer.curRate < minRecvRate then + send error on pool error channel + peer.didTimeout = true + if peer.didTimeout then + for each requester in pool.requesters do + if requester.getPeerID() == peer then + enqueue msg on requestor's redoChannel + delete(pool.peers, peerID) + pool.mtx.Unlock() +``` + +### Main blockchain reactor controller task + +```go +main(pool): + create trySyncTicker with interval trySyncIntervalMS + create statusUpdateTicker with interval statusUpdateIntervalSeconds + create switchToConsensusTicker with interval switchToConsensusIntervalSeconds + + while true do + select { + upon receiving BlockRequest(Height, Peer) on pool.requestsChannel: + try to send bcBlockRequestMessage(Height) to Peer + + upon receiving error(peer) on errorsChannel: + stop peer for error + + upon receiving message on statusUpdateTickerChannel: + broadcast bcStatusRequestMessage(bcR.store.Height) // message sent in a separate routine + + upon receiving message on switchToConsensusTickerChannel: + pool.mtx.Lock() + receivedBlockOrTimedOut = pool.height > 0 || (time.Now() - pool.startTime) > 5 Seconds + ourChainIsLongestAmongPeers = pool.maxPeerHeight == 0 || pool.height >= pool.maxPeerHeight + haveSomePeers = size of pool.peers > 0 + pool.mtx.Unlock() + if haveSomePeers && receivedBlockOrTimedOut && ourChainIsLongestAmongPeers then + switch to consensus mode + + upon receiving message on trySyncTickerChannel: + for i = 0; i < 10; i++ do + pool.mtx.Lock() + firstBlock = pool.requesters[pool.height].block + secondBlock = pool.requesters[pool.height].block + if firstBlock == nil or secondBlock == nil then continue + pool.mtx.Unlock() + verify firstBlock using LastCommit from secondBlock + if verification failed + pool.mtx.Lock() + peerID = pool.requesters[pool.height].peerID + redoRequestsForPeer(peerId) + delete(pool.peers, peerID) + stop peer peerID for error + pool.mtx.Unlock() + else + delete(pool.requesters, pool.height) + save firstBlock to store + pool.height++ + execute firstBlock + } + +redoRequestsForPeer(pool, peerId): + for each requester in pool.requesters do + if requester.getPeerID() == peerID + enqueue msg on redoChannel for requester +``` + +## Channels + +Defines `maxMsgSize` for the maximum size of incoming messages, +`SendQueueCapacity` and `RecvBufferCapacity` for maximum sending and +receiving buffers respectively. These are supposed to prevent amplification +attacks by setting up the upper limit on how much data we can receive & send to +a peer. + +Sending incorrectly encoded data will result in stopping the peer. diff --git a/spec/reactors/consensus/consensus-reactor.md b/spec/reactors/consensus/consensus-reactor.md new file mode 100644 index 000000000..47c6949a7 --- /dev/null +++ b/spec/reactors/consensus/consensus-reactor.md @@ -0,0 +1,353 @@ +# Consensus Reactor + +Consensus Reactor defines a reactor for the consensus service. It contains the ConsensusState service that +manages the state of the Tendermint consensus internal state machine. +When Consensus Reactor is started, it starts Broadcast Routine which starts ConsensusState service. +Furthermore, for each peer that is added to the Consensus Reactor, it creates (and manages) the known peer state +(that is used extensively in gossip routines) and starts the following three routines for the peer p: +Gossip Data Routine, Gossip Votes Routine and QueryMaj23Routine. Finally, Consensus Reactor is responsible +for decoding messages received from a peer and for adequate processing of the message depending on its type and content. +The processing normally consists of updating the known peer state and for some messages +(`ProposalMessage`, `BlockPartMessage` and `VoteMessage`) also forwarding message to ConsensusState module +for further processing. In the following text we specify the core functionality of those separate unit of executions +that are part of the Consensus Reactor. + +## ConsensusState service + +Consensus State handles execution of the Tendermint BFT consensus algorithm. It processes votes and proposals, +and upon reaching agreement, commits blocks to the chain and executes them against the application. +The internal state machine receives input from peers, the internal validator and from a timer. + +Inside Consensus State we have the following units of execution: Timeout Ticker and Receive Routine. +Timeout Ticker is a timer that schedules timeouts conditional on the height/round/step that are processed +by the Receive Routine. + +### Receive Routine of the ConsensusState service + +Receive Routine of the ConsensusState handles messages which may cause internal consensus state transitions. +It is the only routine that updates RoundState that contains internal consensus state. +Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majorities. +It receives messages from peers, internal validators and from Timeout Ticker +and invokes the corresponding handlers, potentially updating the RoundState. +The details of the protocol (together with formal proofs of correctness) implemented by the Receive Routine are +discussed in separate document. For understanding of this document +it is sufficient to understand that the Receive Routine manages and updates RoundState data structure that is +then extensively used by the gossip routines to determine what information should be sent to peer processes. + +## Round State + +RoundState defines the internal consensus state. It contains height, round, round step, a current validator set, +a proposal and proposal block for the current round, locked round and block (if some block is being locked), set of +received votes and last commit and last validators set. + +```golang +type RoundState struct { + Height int64 + Round int + Step RoundStepType + Validators ValidatorSet + Proposal Proposal + ProposalBlock Block + ProposalBlockParts PartSet + LockedRound int + LockedBlock Block + LockedBlockParts PartSet + Votes HeightVoteSet + LastCommit VoteSet + LastValidators ValidatorSet +} +``` + +Internally, consensus will run as a state machine with the following states: + +- RoundStepNewHeight +- RoundStepNewRound +- RoundStepPropose +- RoundStepProposeWait +- RoundStepPrevote +- RoundStepPrevoteWait +- RoundStepPrecommit +- RoundStepPrecommitWait +- RoundStepCommit + +## Peer Round State + +Peer round state contains the known state of a peer. It is being updated by the Receive routine of +Consensus Reactor and by the gossip routines upon sending a message to the peer. + +```golang +type PeerRoundState struct { + Height int64 // Height peer is at + Round int // Round peer is at, -1 if unknown. + Step RoundStepType // Step peer is at + Proposal bool // True if peer has proposal for this round + ProposalBlockPartsHeader PartSetHeader + ProposalBlockParts BitArray + ProposalPOLRound int // Proposal's POL round. -1 if none. + ProposalPOL BitArray // nil until ProposalPOLMessage received. + Prevotes BitArray // All votes peer has for this round + Precommits BitArray // All precommits peer has for this round + LastCommitRound int // Round of commit for last height. -1 if none. + LastCommit BitArray // All commit precommits of commit for last height. + CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. + CatchupCommit BitArray // All commit precommits peer has for this height & CatchupCommitRound +} +``` + +## Receive method of Consensus reactor + +The entry point of the Consensus reactor is a receive method. When a message is received from a peer p, +normally the peer round state is updated correspondingly, and some messages +are passed for further processing, for example to ConsensusState service. We now specify the processing of messages +in the receive method of Consensus reactor for each message type. In the following message handler, `rs` and `prs` denote +`RoundState` and `PeerRoundState`, respectively. + +### NewRoundStepMessage handler + +``` +handleMessage(msg): + if msg is from smaller height/round/step then return + // Just remember these values. + prsHeight = prs.Height + prsRound = prs.Round + prsCatchupCommitRound = prs.CatchupCommitRound + prsCatchupCommit = prs.CatchupCommit + + Update prs with values from msg + if prs.Height or prs.Round has been updated then + reset Proposal related fields of the peer state + if prs.Round has been updated and msg.Round == prsCatchupCommitRound then + prs.Precommits = psCatchupCommit + if prs.Height has been updated then + if prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound then + prs.LastCommitRound = msg.LastCommitRound + prs.LastCommit = prs.Precommits + } else { + prs.LastCommitRound = msg.LastCommitRound + prs.LastCommit = nil + } + Reset prs.CatchupCommitRound and prs.CatchupCommit +``` + +### NewValidBlockMessage handler + +``` +handleMessage(msg): + if prs.Height != msg.Height then return + + if prs.Round != msg.Round && !msg.IsCommit then return + + prs.ProposalBlockPartsHeader = msg.BlockPartsHeader + prs.ProposalBlockParts = msg.BlockParts +``` + +### HasVoteMessage handler + +``` +handleMessage(msg): + if prs.Height == msg.Height then + prs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index) +``` + +### VoteSetMaj23Message handler + +``` +handleMessage(msg): + if prs.Height == msg.Height then + Record in rs that a peer claim to have ⅔ majority for msg.BlockID + Send VoteSetBitsMessage showing votes node has for that BlockId +``` + +### ProposalMessage handler + +``` +handleMessage(msg): + if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return + prs.Proposal = true + if prs.ProposalBlockParts == empty set then // otherwise it is set in NewValidBlockMessage handler + prs.ProposalBlockPartsHeader = msg.BlockPartsHeader + prs.ProposalPOLRound = msg.POLRound + prs.ProposalPOL = nil + Send msg through internal peerMsgQueue to ConsensusState service +``` + +### ProposalPOLMessage handler + +``` +handleMessage(msg): + if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then return + prs.ProposalPOL = msg.ProposalPOL +``` + +### BlockPartMessage handler + +``` +handleMessage(msg): + if prs.Height != msg.Height || prs.Round != msg.Round then return + Record in prs that peer has block part msg.Part.Index + Send msg trough internal peerMsgQueue to ConsensusState service +``` + +### VoteMessage handler + +``` +handleMessage(msg): + Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and round + Send msg trough internal peerMsgQueue to ConsensusState service +``` + +### VoteSetBitsMessage handler + +``` +handleMessage(msg): + Update prs for the bit-array of votes peer claims to have for the msg.BlockID +``` + +## Gossip Data Routine + +It is used to send the following messages to the peer: `BlockPartMessage`, `ProposalMessage` and +`ProposalPOLMessage` on the DataChannel. The gossip data routine is based on the local RoundState (`rs`) +and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below: + +``` +1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts then + Part = pick a random proposal block part the peer does not have + Send BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannel + if send returns true, record that the peer knows the corresponding block Part + Continue + +1b) if (0 < prs.Height) and (prs.Height < rs.Height) then + help peer catch up using gossipDataForCatchup function + Continue + +1c) if (rs.Height != prs.Height) or (rs.Round != prs.Round) then + Sleep PeerGossipSleepDuration + Continue + +// at this point rs.Height == prs.Height and rs.Round == prs.Round +1d) if (rs.Proposal != nil and !prs.Proposal) then + Send ProposalMessage(rs.Proposal) to the peer + if send returns true, record that the peer knows Proposal + if 0 <= rs.Proposal.POLRound then + polRound = rs.Proposal.POLRound + prevotesBitArray = rs.Votes.Prevotes(polRound).BitArray() + Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray) + Continue + +2) Sleep PeerGossipSleepDuration +``` + +### Gossip Data For Catchup + +This function is responsible for helping peer catch up if it is at the smaller height (prs.Height < rs.Height). +The function executes the following logic: + + if peer does not have all block parts for prs.ProposalBlockPart then + blockMeta = Load Block Metadata for height prs.Height from blockStore + if (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) then + Sleep PeerGossipSleepDuration + return + Part = pick a random proposal block part the peer does not have + Send BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannel + if send returns true, record that the peer knows the corresponding block Part + return + else Sleep PeerGossipSleepDuration + +## Gossip Votes Routine + +It is used to send the following message: `VoteMessage` on the VoteChannel. +The gossip votes routine is based on the local RoundState (`rs`) +and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below: + +``` +1a) if rs.Height == prs.Height then + if prs.Step == RoundStepNewHeight then + vote = random vote from rs.LastCommit the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + + if prs.Step <= RoundStepPrevote and prs.Round != -1 and prs.Round <= rs.Round then + Prevotes = rs.Votes.Prevotes(prs.Round) + vote = random vote from Prevotes the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + + if prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round then + Precommits = rs.Votes.Precommits(prs.Round) + vote = random vote from Precommits the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + + if prs.ProposalPOLRound != -1 then + PolPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound) + vote = random vote from PolPrevotes the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + +1b) if prs.Height != 0 and rs.Height == prs.Height+1 then + vote = random vote from rs.LastCommit peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + +1c) if prs.Height != 0 and rs.Height >= prs.Height+2 then + Commit = get commit from BlockStore for prs.Height + vote = random vote from Commit the peer does not have + Send VoteMessage(vote) to the peer + if send returns true, continue + +2) Sleep PeerGossipSleepDuration +``` + +## QueryMaj23Routine + +It is used to send the following message: `VoteSetMaj23Message`. `VoteSetMaj23Message` is sent to indicate that a given +BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs`) and the known PeerRoundState +(`prs`). The routine repeats forever the logic shown below. + +``` +1a) if rs.Height == prs.Height then + Prevotes = rs.Votes.Prevotes(prs.Round) + if there is a ⅔ majority for some blockId in Prevotes then + m = VoteSetMaj23Message(prs.Height, prs.Round, Prevote, blockId) + Send m to peer + Sleep PeerQueryMaj23SleepDuration + +1b) if rs.Height == prs.Height then + Precommits = rs.Votes.Precommits(prs.Round) + if there is a ⅔ majority for some blockId in Precommits then + m = VoteSetMaj23Message(prs.Height,prs.Round,Precommit,blockId) + Send m to peer + Sleep PeerQueryMaj23SleepDuration + +1c) if rs.Height == prs.Height and prs.ProposalPOLRound >= 0 then + Prevotes = rs.Votes.Prevotes(prs.ProposalPOLRound) + if there is a ⅔ majority for some blockId in Prevotes then + m = VoteSetMaj23Message(prs.Height,prs.ProposalPOLRound,Prevotes,blockId) + Send m to peer + Sleep PeerQueryMaj23SleepDuration + +1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height and + prs.Height <= blockStore.Height() then + Commit = LoadCommit(prs.Height) + m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.blockId) + Send m to peer + Sleep PeerQueryMaj23SleepDuration + +2) Sleep PeerQueryMaj23SleepDuration +``` + +## Broadcast routine + +The Broadcast routine subscribes to an internal event bus to receive new round steps and votes messages, and broadcasts messages to peers upon receiving those +events. +It broadcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state event. Note that +broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel. +Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers on the StateChannel. + +## Channels + +Defines 4 channels: state, data, vote and vote_set_bits. Each channel +has `SendQueueCapacity` and `RecvBufferCapacity` and +`RecvMessageCapacity` set to `maxMsgSize`. + +Sending incorrectly encoded data will result in stopping the peer. diff --git a/spec/reactors/consensus/consensus.md b/spec/reactors/consensus/consensus.md new file mode 100644 index 000000000..55960874a --- /dev/null +++ b/spec/reactors/consensus/consensus.md @@ -0,0 +1,184 @@ +# Tendermint Consensus Reactor + +Tendermint Consensus is a distributed protocol executed by validator processes to agree on +the next block to be added to the Tendermint blockchain. The protocol proceeds in rounds, where +each round is a try to reach agreement on the next block. A round starts by having a dedicated +process (called proposer) suggesting to other processes what should be the next block with +the `ProposalMessage`. +The processes respond by voting for a block with `VoteMessage` (there are two kinds of vote +messages, prevote and precommit votes). Note that a proposal message is just a suggestion what the +next block should be; a validator might vote with a `VoteMessage` for a different block. If in some +round, enough number of processes vote for the same block, then this block is committed and later +added to the blockchain. `ProposalMessage` and `VoteMessage` are signed by the private key of the +validator. The internals of the protocol and how it ensures safety and liveness properties are +explained in a forthcoming document. + +For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the +block as the block size is big, i.e., they don't embed the block inside `Proposal` and +`VoteMessage`. Instead, they reach agreement on the `BlockID` (see `BlockID` definition in +[Blockchain](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#blockid) section) that uniquely identifies each block. The block itself is +disseminated to validator processes using peer-to-peer gossiping protocol. It starts by having a +proposer first splitting a block into a number of block parts, that are then gossiped between +processes using `BlockPartMessage`. + +Validators in Tendermint communicate by peer-to-peer gossiping protocol. Each validator is connected +only to a subset of processes called peers. By the gossiping protocol, a validator send to its peers +all needed information (`ProposalMessage`, `VoteMessage` and `BlockPartMessage`) so they can +reach agreement on some block, and also obtain the content of the chosen block (block parts). As +part of the gossiping protocol, processes also send auxiliary messages that inform peers about the +executed steps of the core consensus algorithm (`NewRoundStepMessage` and `NewValidBlockMessage`), and +also messages that inform peers what votes the process has seen (`HasVoteMessage`, +`VoteSetMaj23Message` and `VoteSetBitsMessage`). These messages are then used in the gossiping +protocol to determine what messages a process should send to its peers. + +We now describe the content of each message exchanged during Tendermint consensus protocol. + +## ProposalMessage + +ProposalMessage is sent when a new block is proposed. It is a suggestion of what the +next block in the blockchain should be. + +```go +type ProposalMessage struct { + Proposal Proposal +} +``` + +### 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. + +```go +type Proposal struct { + Height int64 + Round int + POLRound int + BlockID BlockID + Timestamp Time + Signature Signature +} +``` + +## VoteMessage + +VoteMessage 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/tendermint/blob/master/docs/spec/blockchain/blockchain.md#blockid) 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. + +```go +type VoteMessage struct { + Vote Vote +} +``` + +## BlockPartMessage + +BlockPartMessage is sent when gossipping a piece of the proposed block. It contains height, round +and the block part. + +```go +type BlockPartMessage struct { + Height int64 + Round int + Part Part +} +``` + +## NewRoundStepMessage + +NewRoundStepMessage 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. + +```go +type NewRoundStepMessage struct { + Height int64 + Round int + Step RoundStepType + SecondsSinceStartTime int + LastCommitRound int +} +``` + +## NewValidBlockMessage + +NewValidBlockMessage 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. + +```go +type NewValidBlockMessage struct { + Height int64 + Round int + BlockPartsHeader PartSetHeader + BlockParts BitArray + IsCommit bool +} +``` + +## ProposalPOLMessage + +ProposalPOLMessage 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. + +```go +type ProposalPOLMessage struct { + Height int64 + ProposalPOLRound int + ProposalPOL BitArray +} +``` + +## HasVoteMessage + +HasVoteMessage 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. + +```go +type HasVoteMessage struct { + Height int64 + Round int + Type byte + Index int +} +``` + +## VoteSetMaj23Message + +VoteSetMaj23Message is sent to indicate that a process has seen +2/3 votes for some BlockID. +It contains height, round, vote type and the BlockID. + +```go +type VoteSetMaj23Message struct { + Height int64 + Round int + Type byte + BlockID BlockID +} +``` + +## VoteSetBitsMessage + +VoteSetBitsMessage 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. + +```go +type VoteSetBitsMessage struct { + Height int64 + Round int + Type byte + BlockID BlockID + Votes BitArray +} +``` diff --git a/spec/reactors/consensus/proposer-selection.md b/spec/reactors/consensus/proposer-selection.md new file mode 100644 index 000000000..6cb596ec0 --- /dev/null +++ b/spec/reactors/consensus/proposer-selection.md @@ -0,0 +1,291 @@ +# Proposer selection procedure in Tendermint + +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: + +``` + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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/reactors/evidence/reactor.md b/spec/reactors/evidence/reactor.md new file mode 100644 index 000000000..efa63aa4c --- /dev/null +++ b/spec/reactors/evidence/reactor.md @@ -0,0 +1,10 @@ +# Evidence Reactor + +## Channels + +[#1503](https://github.com/tendermint/tendermint/issues/1503) + +Sending invalid evidence will result in stopping the peer. + +Sending incorrectly encoded data or data exceeding `maxMsgSize` will result +in stopping the peer. diff --git a/spec/reactors/mempool/concurrency.md b/spec/reactors/mempool/concurrency.md new file mode 100644 index 000000000..a6870db9b --- /dev/null +++ b/spec/reactors/mempool/concurrency.md @@ -0,0 +1,8 @@ +# Mempool Concurrency + +Look at the concurrency model this uses... + +- Receiving CheckTx +- Broadcasting new tx +- Interfaces with consensus engine, reap/update while checking +- Calling the ABCI app (ordering. callbacks. how proxy works alongside the blockchain proxy which actually writes blocks) diff --git a/spec/reactors/mempool/config.md b/spec/reactors/mempool/config.md new file mode 100644 index 000000000..4fb756fa4 --- /dev/null +++ b/spec/reactors/mempool/config.md @@ -0,0 +1,54 @@ +# Mempool Configuration + +Here we describe configuration options around mempool. +For the purposes of this document, they are described +as command-line flags, but they can also be passed in as +environmental variables or in the config.toml file. The +following are all equivalent: + +Flag: `--mempool.recheck=false` + +Environment: `TM_MEMPOOL_RECHECK=false` + +Config: + +``` +[mempool] +recheck = false +``` + +## Recheck + +`--mempool.recheck=false` (default: true) + +Recheck determines if the mempool rechecks all pending +transactions after a block was committed. Once a block +is committed, the mempool removes all valid transactions +that were successfully included in the block. + +If `recheck` is true, then it will rerun CheckTx on +all remaining transactions with the new block state. + +## Broadcast + +`--mempool.broadcast=false` (default: true) + +Determines whether this node gossips any valid transactions +that arrive in mempool. Default is to gossip anything that +passes checktx. If this is disabled, transactions are not +gossiped, but instead stored locally and added to the next +block this node is the proposer. + +## WalDir + +`--mempool.wal_dir=/tmp/gaia/mempool.wal` (default: $TM_HOME/data/mempool.wal) + +This defines the directory where mempool writes the write-ahead +logs. These files can be used to reload unbroadcasted +transactions if the node crashes. + +If the directory passed in is an absolute path, the wal file is +created there. If the directory is a relative path, the path is +appended to home directory of the tendermint process to +generate an absolute path to the wal directory +(default `$HOME/.tendermint` or set via `TM_HOME` or `--home``) diff --git a/spec/reactors/mempool/functionality.md b/spec/reactors/mempool/functionality.md new file mode 100644 index 000000000..ea902225d --- /dev/null +++ b/spec/reactors/mempool/functionality.md @@ -0,0 +1,43 @@ +# Mempool Functionality + +The mempool maintains a list of potentially valid transactions, +both to broadcast to other nodes, as well as to provide to the +consensus reactor when it is selected as the block proposer. + +There are two sides to the mempool state: + +- External: get, check, and broadcast new transactions +- Internal: return valid transaction, update list after block commit + +## External functionality + +External functionality is exposed via network interfaces +to potentially untrusted actors. + +- CheckTx - triggered via RPC or P2P +- Broadcast - gossip messages after a successful check + +## Internal functionality + +Internal functionality is exposed via method calls to other +code compiled into the tendermint binary. + +- ReapMaxBytesMaxGas - get txs to propose in the next block. Guarantees that the + size of the txs is less than MaxBytes, and gas is less than MaxGas +- Update - remove tx that were included in last block +- ABCI.CheckTx - call ABCI app to validate the tx + +What does it provide the consensus reactor? +What guarantees does it need from the ABCI app? +(talk about interleaving processes in concurrency) + +## Optimizations + +The implementation within this library also implements a tx cache. +This is so that signatures don't have to be reverified if the tx has +already been seen before. +However, we only store valid txs in the cache, not invalid ones. +This is because invalid txs could become good later. +Txs that are included in a block aren't removed from the cache, +as they still may be getting received over the p2p network. +These txs are stored in the cache by their hash, to mitigate memory concerns. diff --git a/spec/reactors/mempool/messages.md b/spec/reactors/mempool/messages.md new file mode 100644 index 000000000..9c583ac0f --- /dev/null +++ b/spec/reactors/mempool/messages.md @@ -0,0 +1,61 @@ +# Mempool Messages + +## P2P Messages + +There is currently only one message that Mempool broadcasts +and receives over the p2p gossip network (via the reactor): +`TxMessage` + +```go +// TxMessage is a MempoolMessage containing a transaction. +type TxMessage struct { + Tx types.Tx +} +``` + +TxMessage is go-amino encoded and prepended with `0x1` as a +"type byte". This is followed by a go-amino encoded byte-slice. +Prefix of 40=0x28 byte tx is: `0x010128...` followed by +the actual 40-byte tx. Prefix of 350=0x015e byte tx is: +`0x0102015e...` followed by the actual 350 byte tx. + +(Please see the [go-amino repo](https://github.com/tendermint/go-amino#an-interface-example) for more information) + +## RPC Messages + +Mempool exposes `CheckTx([]byte)` over the RPC interface. + +It can be posted via `broadcast_commit`, `broadcast_sync` or +`broadcast_async`. They all parse a message with one argument, +`"tx": "HEX_ENCODED_BINARY"` and differ in only how long they +wait before returning (sync makes sure CheckTx passes, commit +makes sure it was included in a signed block). + +Request (`POST http://gaia.zone:26657/`): + +```json +{ + "id": "", + "jsonrpc": "2.0", + "method": "broadcast_sync", + "params": { + "tx": "F012A4BC68..." + } +} +``` + +Response: + +```json +{ + "error": "", + "result": { + "hash": "E39AAB7A537ABAA237831742DCE1117F187C3C52", + "log": "", + "data": "", + "code": 0 + }, + "id": "", + "jsonrpc": "2.0" +} +``` diff --git a/spec/reactors/mempool/reactor.md b/spec/reactors/mempool/reactor.md new file mode 100644 index 000000000..7e9a2d8fe --- /dev/null +++ b/spec/reactors/mempool/reactor.md @@ -0,0 +1,22 @@ +# Mempool Reactor + +## Channels + +See [this issue](https://github.com/tendermint/tendermint/issues/1503) + +Mempool maintains a cache of the last 10000 transactions to prevent +replaying old transactions (plus transactions coming from other +validators, who are continually exchanging transactions). Read [Replay +Protection](../../../app-dev/app-development.md#replay-protection) +for details. + +Sending incorrectly encoded data or data exceeding `maxMsgSize` will result +in stopping the peer. + +The mempool will not send a tx back to any peer which it received it from. + +The reactor assigns an `uint16` number for each peer and maintains a map from +p2p.ID to `uint16`. Each mempool transaction carries a list of all the senders +(`[]uint16`). The list is updated every time mempool receives a transaction it +is already seen. `uint16` assumes that a node will never have over 65535 active +peers (0 is reserved for unknown source - e.g. RPC). diff --git a/spec/reactors/pex/pex.md b/spec/reactors/pex/pex.md new file mode 100644 index 000000000..268b4a318 --- /dev/null +++ b/spec/reactors/pex/pex.md @@ -0,0 +1,132 @@ +# Peer Strategy and Exchange + +Here we outline the design of the AddressBook +and how it used by the Peer Exchange Reactor (PEX) to ensure we are connected +to good peers and to gossip peers to others. + +## Peer Types + +Certain peers are special in that they are specified by the user as `persistent`, +which means we auto-redial them if the connection fails, or if we fail to dial +them. +Some peers can be marked as `private`, which means +we will not put them in the address book or gossip them to others. + +All peers except private peers and peers coming from them are tracked using the +address book. + +The rest of our peers are only distinguished by being either +inbound (they dialed our public address) or outbound (we dialed them). + +## Discovery + +Peer discovery begins with a list of seeds. + +When we don't have enough peers, we + +1. ask existing peers +2. dial seeds if we're not dialing anyone currently + +On startup, we will also immediately dial the given list of `persistent_peers`, +and will attempt to maintain persistent connections with them. If the +connections die, or we fail to dial, we will redial every 5s for a few minutes, +then switch to an exponential backoff schedule, and after about a day of +trying, stop dialing the peer. + +As long as we have less than `MaxNumOutboundPeers`, we periodically request +additional peers from each of our own and try seeds. + +## Listening + +Peers listen on a configurable ListenAddr that they self-report in their +NodeInfo during handshakes with other peers. Peers accept up to +`MaxNumInboundPeers` incoming peers. + +## Address Book + +Peers are tracked via their ID (their PubKey.Address()). +Peers are added to the address book from the PEX when they first connect to us or +when we hear about them from other peers. + +The address book is arranged in sets of buckets, and distinguishes between +vetted (old) and unvetted (new) peers. It keeps different sets of buckets for vetted and +unvetted peers. Buckets provide randomization over peer selection. Peers are put +in buckets according to their IP groups. + +A vetted peer can only be in one bucket. An unvetted peer can be in multiple buckets, and +each instance of the peer can have a different IP:PORT. + +If we're trying to add a new peer but there's no space in its bucket, we'll +remove the worst peer from that bucket to make room. + +## Vetting + +When a peer is first added, it is unvetted. +Marking a peer as vetted is outside the scope of the `p2p` package. +For Tendermint, a Peer becomes vetted once it has contributed sufficiently +at the consensus layer; ie. once it has sent us valid and not-yet-known +votes and/or block parts for `NumBlocksForVetted` blocks. +Other users of the p2p package can determine their own conditions for when a peer is marked vetted. + +If a peer becomes vetted but there are already too many vetted peers, +a randomly selected one of the vetted peers becomes unvetted. + +If a peer becomes unvetted (either a new peer, or one that was previously vetted), +a randomly selected one of the unvetted peers is removed from the address book. + +More fine-grained tracking of peer behaviour can be done using +a trust metric (see below), but it's best to start with something simple. + +## Select Peers to Dial + +When we need more peers, we pick addresses randomly from the addrbook with some +configurable bias for unvetted peers. The bias should be lower when we have +fewer peers and can increase as we obtain more, ensuring that our first peers +are more trustworthy, but always giving us the chance to discover new good +peers. + +We track the last time we dialed a peer and the number of unsuccessful attempts +we've made. If too many attempts are made, we mark the peer as bad. + +Connection attempts are made with exponential backoff (plus jitter). Because +the selection process happens every `ensurePeersPeriod`, we might not end up +dialing a peer for much longer than the backoff duration. + +If we fail to connect to the peer after 16 tries (with exponential backoff), we +remove from address book completely. + +## Select Peers to Exchange + +When we’re asked for peers, we select them as follows: + +- select at most `maxGetSelection` peers +- try to select at least `minGetSelection` peers - if we have less than that, select them all. +- select a random, unbiased `getSelectionPercent` of the peers + +Send the selected peers. Note we select peers for sending without bias for vetted/unvetted. + +## Preventing Spam + +There are various cases where we decide a peer has misbehaved and we disconnect from them. +When this happens, the peer is removed from the address book and black listed for +some amount of time. We call this "Disconnect and Mark". +Note that the bad behaviour may be detected outside the PEX reactor itself +(for instance, in the mconnection, or another reactor), but it must be communicated to the PEX reactor +so it can remove and mark the peer. + +In the PEX, if a peer sends us an unsolicited list of peers, +or if the peer sends a request too soon after another one, +we Disconnect and MarkBad. + +## Trust Metric + +The quality of peers can be tracked in more fine-grained detail using a +Proportional-Integral-Derivative (PID) controller that incorporates +current, past, and rate-of-change data to inform peer quality. + +While a PID trust metric has been implemented, it remains for future work +to use it in the PEX. + +See the [trustmetric](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-006-trust-metric.md) +and [trustmetric useage](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-007-trust-metric-usage.md) +architecture docs for more details. diff --git a/spec/reactors/pex/reactor.md b/spec/reactors/pex/reactor.md new file mode 100644 index 000000000..468f182cc --- /dev/null +++ b/spec/reactors/pex/reactor.md @@ -0,0 +1,12 @@ +# PEX Reactor + +## Channels + +Defines only `SendQueueCapacity`. [#1503](https://github.com/tendermint/tendermint/issues/1503) + +Implements rate-limiting by enforcing minimal time between two consecutive +`pexRequestMessage` requests. If the peer sends us addresses we did not ask, +it is stopped. + +Sending incorrectly encoded data or data exceeding `maxMsgSize` will result +in stopping the peer. From f618acf2ab8b899d4e0f8108cecb0d141dfc956c Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Tue, 29 Oct 2019 12:01:13 +0100 Subject: [PATCH 010/223] Fix model section --- .../consensus/consensus-paper/definitions.tex | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/spec/consensus/consensus-paper/definitions.tex b/spec/consensus/consensus-paper/definitions.tex index f73d316e8..454dd445d 100644 --- a/spec/consensus/consensus-paper/definitions.tex +++ b/spec/consensus/consensus-paper/definitions.tex @@ -4,7 +4,7 @@ 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., Byzantine faults. We assume that each process +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 @@ -13,19 +13,30 @@ 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 the \emph{partially +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 correct process $q$, then $q$ will receive $m$ before $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$.}. Messages among -correct processes can be delayed, dropped or duplicated before GST. -Spoofing/impersonation attacks are assumed to be impossible at all times due to -the use of public-key cryptography. The bound $\Delta$ and GST are system +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 @@ -37,18 +48,16 @@ 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. All protocol messages are signed, i.e., when a correct +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$. +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). -The details of the Tendermint gossip protocol will be discussed in a separate -technical report. For the sake of this paper it is sufficient to assume that -messages are being gossiped between processes and the following property holds -(in addition to the partial synchrony network assumptions): - -\begin{itemize} \item \emph{Gossip communication:} 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} %Messages that are being gossiped are created by the consensus layer. We can From a4b68ec2fbca4eb06d383a7d06cf37b3e59a4680 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Fri, 15 Nov 2019 12:23:35 +0100 Subject: [PATCH 011/223] Add non-recursive specification of Bisection algorithm - Fix timing issues by introducing Delta parameter --- spec/consensus/light-client.md | 115 +++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 41 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 18dc280a3..8f2d79746 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -193,33 +193,34 @@ We consider the following set-up: we will write ```totalVotingPower(V)``` for ```votingpower_in(V,V)```, which returns the total voting power in V. We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. -**CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. +**CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. Time constraint is +captured by the `hasExpired` function that depends on trusted period (`tp`) and a parameter `Delta` that denotes minimum duration of header so it is +not considered expired. ```go + func hasExpired(h) { + if h.Header.bfttime + tp - Delta < now { // Observation 1 + return true + } + + // basic validation (function `verify`) has already been called on h2 func CheckSupport(h1,h2,trustlevel) bool { - if h1.Header.bfttime + tp < now { // Observation 1 - return false // old header was once trusted but it is expired - } + if hasExpired(h1) then return false //old header was once trusted but it is expired + vp_all := totalVotingPower(h1.Header.NextV) - // total sum of voting power of validators in h2 + // total sum of voting power of validators in h1 if h2.Header.height == h1.Header.height + 1 { - // specific check for adjacent headers; everything must be - // properly signed. - // also check that h2.Header.V == h1.Header.NextV - // Plus the following check that 2/3 of the voting power - // in h1 signed h2 - return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > - 2/3 * vp_all) - // signing validators are more than two third in h1. - } + // specific check for adjacent headers + if h1.Header.NextV == h2.Header.V then + return hasExpired(h2) + } + } - return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > - max(1/3,trustlevel) * vp_all) - // get validators in h1 that signed h2 - // sum of voting powers in h1 of - // validators that signed h2 - // is more than a third in h1 + // validators that signed h2 are more than a third of voting power in h1 + if (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all) { + return hasExpired(h2) + } } ``` @@ -257,28 +258,60 @@ Towards Lite Client Completeness: ```go +func verify(h) { + if hasExpired(h) return false + + vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h + + if votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all { + return hasExpired(h) + } else { + return false + } +} + func Bisection(h1,h2,trustlevel) bool{ - if CheckSupport(h1,h2,trustlevel) { - return true - } - if h2.Header.height == h1.Header.height + 1 { - // we have adjacent headers that are not matching (failed - // the CheckSupport) - // we could submit evidence here - return false - } - pivot := (h1.Header.height + h2.Header.height) / 2 - hp := Commit(pivot) - // ask a full node for header of height pivot - Store(hp) - // store header hp locally - if Bisection(h1,hp,trustlevel) { - // only check right branch if hp is trusted - // (otherwise a lot of unnecessary computation may be done) - return Bisection(hp,h2,trustlevel) - } - else { - return false + th := h1 // th is trusted header + while th.Header.Height <= h2.Header.height do { + // try to move trusted header forward with stored headers + // we assume here that iteration will be done in order of header heights + ih := th + for all stored headers h s.t ih.Header.Height < h.Header.height < h2.Header.height do { + if CheckSupport(th,h,trustlevel) { + th = h + } else if h.Header.height == th.Header.height + 1 { + return false // fail to verify succesive headers! + } else break // for + } + + if CheckSupport(th,h2,trustlevel) { + return hasExpired(h2) + } + + if h2.Header.height == th.Header.height + 1 { + // we have adjacent headers that are not matching (failed the CheckSupport) + // we could submit evidence here + return false + } + + // try to move th + endHeight = h2.Header.height + foundPivot = false + while(!foundPivot) { + pivot := (th.Header.height + endHeight) / 2 + hp := Commit(pivot) + if !verify(hp) return false + Store(hp) + + if CheckSupport(th,hd,trustlevel) { + th = hd + foundPivot = true + } else if pivot.Header.height == th.Header.height + 1 { + return false // fail to verify succesive headers! + } + + endHeight = pivot + } } } ``` From 7b3138e69490f410768d9b1ffc7a17abc23ea397 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 21 Nov 2019 11:15:24 +0100 Subject: [PATCH 012/223] 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 --- spec/README.md | 26 +- spec/abci/README.md | 6 +- spec/abci/abci.md | 5 +- spec/blockchain/blockchain.md | 26 +- spec/blockchain/encoding.md | 10 +- spec/blockchain/readme.md | 5 + spec/blockchain/state.md | 3 + spec/consensus/fork-accountability.md | 18 +- spec/consensus/light-client.md | 243 +++++++++---------- spec/consensus/readme.md | 5 + spec/consensus/wal.md | 2 +- spec/p2p/readme.md | 5 + spec/reactors/consensus/consensus-reactor.md | 29 ++- spec/reactors/mempool/config.md | 2 +- spec/reactors/readme.md | 5 + 15 files changed, 206 insertions(+), 184 deletions(-) create mode 100644 spec/blockchain/readme.md create mode 100644 spec/consensus/readme.md create mode 100644 spec/p2p/readme.md create mode 100644 spec/reactors/readme.md diff --git a/spec/README.md b/spec/README.md index 7ec9387c2..a0a67f7b5 100644 --- a/spec/README.md +++ b/spec/README.md @@ -1,4 +1,12 @@ -# Overview +--- +order: 1 +title: Overview +parent: + title: Tendermint Spec + order: 7 +--- + +# Tendermint Spec This is a markdown specification of the Tendermint blockchain. It defines the base data structures, how they are validated, @@ -27,18 +35,18 @@ please submit them to our [bug bounty](https://tendermint.com/security)! ### P2P and Network Protocols -- [The Base P2P Layer](./p2p/): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections -- [Peer Exchange (PEX)](./reactors/pex/): gossip known peer addresses so peers can find each other -- [Block Sync](./reactors/block_sync/): gossip blocks so peers can catch up quickly -- [Consensus](./reactors/consensus/): gossip votes and block parts so new blocks can be committed -- [Mempool](./reactors/mempool/): gossip transactions so they get included in blocks -- [Evidence](./reactors/evidence/): sending invalid evidence will stop the peer +- [The Base P2P Layer](./p2p/node.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections +- [Peer Exchange (PEX)](./reactors/pex/reactor.md): gossip known peer addresses so peers can find each other +- [Block Sync](./reactors/block_sync/reactor.md): gossip blocks so peers can catch up quickly +- [Consensus](./reactors/consensus/consensus.md): gossip votes and block parts so new blocks can be committed +- [Mempool](./reactors/mempool/reactor.md): gossip transactions so they get included in blocks +- [Evidence](./reactors/evidence/reactor.md): sending invalid evidence will stop the peer ### Software -- [ABCI](./software/abci.md): Details about interactions between the +- [ABCI](./abci/README.md): Details about interactions between the application and consensus engine over ABCI -- [Write-Ahead Log](./software/wal.md): Details about how the consensus +- [Write-Ahead Log](./consensus/wal.md): Details about how the consensus engine preserves data and recovers from crash failures ## Overview diff --git a/spec/abci/README.md b/spec/abci/README.md index 56d5e8aaf..a8293e4bb 100644 --- a/spec/abci/README.md +++ b/spec/abci/README.md @@ -1,4 +1,8 @@ -# Overview +--- +cards: true +--- + +# ABCI ABCI is the interface between Tendermint (a state-machine replication engine) and your application (the actual state machine). It consists of a set of diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 275c4dcd8..e48b711b3 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -54,7 +54,7 @@ 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 even type itself. +encoded strings along with the event type itself. Example: @@ -393,9 +393,6 @@ Commit are included in the header of the next block. For heights > 1, it's the weighted median of the timestamps of the valid votes in the block.LastCommit. For height == 1, it's genesis time. - - `NumTxs (int32)`: Number of transactions in the block - - `TotalTxs (int64)`: Total number of transactions in the blockchain until - now - `LastBlockID (BlockID)`: Hash of the previous (parent) block - `LastCommitHash ([]byte)`: Hash of the previous block's commit - `ValidatorsHash ([]byte)`: Hash of the validator set for this block diff --git a/spec/blockchain/blockchain.md b/spec/blockchain/blockchain.md index f4ddcd75f..d5fd6988d 100644 --- a/spec/blockchain/blockchain.md +++ b/spec/blockchain/blockchain.md @@ -43,8 +43,6 @@ type Header struct { ChainID string Height int64 Time Time - NumTxs int64 - TotalTxs int64 // prev block info LastBlockID BlockID @@ -168,7 +166,7 @@ See the [signature spec](./encoding.md#key-types) for more. EvidenceData is a simple wrapper for a list of evidence: -```go +``` type EvidenceData struct { Evidence []Evidence } @@ -189,6 +187,8 @@ type DuplicateVoteEvidence struct { } ``` +Votes are lexicographically sorted on `BlockID`. + See the [pubkey spec](./encoding.md#key-types) for more. ## Validation @@ -264,24 +264,6 @@ if block.Header.Height == 1 { See the section on [BFT time](../consensus/bft-time.md) for more details. -### NumTxs - -```go -block.Header.NumTxs == len(block.Txs.Txs) -``` - -Number of transactions included in the block. - -### TotalTxs - -```go -block.Header.TotalTxs == prevBlock.Header.TotalTxs + block.Header.NumTxs -``` - -The cumulative sum of all transactions included in this blockchain. - -The first block has `block.Header.TotalTxs = block.Header.NumberTxs`. - ### LastBlockID LastBlockID is the previous block's BlockID: @@ -433,6 +415,8 @@ All votes must have a valid signature from the corresponding validator. The sum total 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`). + ### Vote A vote is a signed message broadcast in the consensus for a particular block at a particular height and round. diff --git a/spec/blockchain/encoding.md b/spec/blockchain/encoding.md index 170e91605..7c4db8e92 100644 --- a/spec/blockchain/encoding.md +++ b/spec/blockchain/encoding.md @@ -155,7 +155,9 @@ See details of SimpleProof, below. ### MakeParts Encode an object using Amino and slice it into parts. -Tendermint uses a part size of 65536 bytes. +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 @@ -288,9 +290,13 @@ func computeHashFromAunts(index, total int, leafHash []byte, innerHashes [][]byt } ``` +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/master/docs/clients/lite/specification.md) +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 diff --git a/spec/blockchain/readme.md b/spec/blockchain/readme.md new file mode 100644 index 000000000..bd9b7e4f4 --- /dev/null +++ b/spec/blockchain/readme.md @@ -0,0 +1,5 @@ +--- +cards: true +--- + +# Blockchain diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md index 15fc37761..0430819fa 100644 --- a/spec/blockchain/state.md +++ b/spec/blockchain/state.md @@ -27,6 +27,9 @@ type State struct { } ``` +Note there is a hard-coded limit of 10000 validators. This is inherited from the +limit on the number of votes in a commit. + ### Result ```go diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/fork-accountability.md index a6af8a1ba..2eb662aef 100644 --- a/spec/consensus/fork-accountability.md +++ b/spec/consensus/fork-accountability.md @@ -187,7 +187,7 @@ Validators: 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 lite clients obtains B and trusts at as it is signed by more and 2/3 of the voting power. +* the lite clients obtains B and trusts at as it is signed by more than 2/3 of the voting power. Consequences: @@ -251,11 +251,11 @@ Consequences: 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 https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit?usp=sharing 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 different mechanism that would require as an evidence a sequence of blocks that lead to that state. This might be very tricky to implement. +**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 attacks 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 lite clients. +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 lite clients. #### Scenario 5: @@ -271,7 +271,7 @@ Validators: Execution: * in a round *r* of height *h* we have C1 precommitting a value A, -* C2 precommits nil, +* 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. @@ -283,7 +283,7 @@ 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. +**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 @@ -296,19 +296,19 @@ Validators: Execution: -* There is a fork, and there exists two different headers for height *h + k*, with different validator sets: +* 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 lite client has a trust in a header for height *h* (and the corresponding validator set VS1). -* As part of bisection header verification, it verifies header at height *h + k* with new validator set VS2'. +* 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 he is eclipsed. +**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. ### Lunatic validator @@ -316,4 +316,4 @@ Lunatic validator agrees to sign commit messages for arbitrary application state 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 ignore to check if proposed value is valid before voting for it? +**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/consensus/light-client.md b/spec/consensus/light-client.md index 18dc280a3..9486b80d2 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -6,19 +6,17 @@ A lite client is a process that connects to Tendermint full nodes and then tries In order to make sure that full nodes have the incentive to follow the protocol, we have to address the following three Issues -1) The lite client needs a method to verify headers it obtains from full nodes according to trust assumptions -- this document. +1. The lite client needs a method to verify headers it obtains from full nodes according to trust assumptions -- this document. -2) The lite client must be able to connect to one correct full node to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document. +2. The lite client must be able to connect to one correct full node to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document. -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- see #3840 +3. In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- see #3840 ## Problem statement +We assume that the lite client knows a (base) header _inithead_ it trusts (by social consensus or because the lite 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_. -We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because the lite 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. The term "trusting" above indicates that the correctness on the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged *inithead* is negligible. - +The correctness of the protocol is based on the assumption that _inithead_ was generated by an instance of Tendermint consensus. The term "trusting" above indicates that the correctness on the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged _inithead_ is negligible. ## Definitions @@ -26,26 +24,27 @@ The correctness of the protocol is based on the assumption that *inithead* was g In the following, only the details of the data structures needed for this specification are given. - * header fields - - *height* - - *bfttime*: the chain time when the header (block) was generated - - *V*: validator set containing validators for this block. - - *NextV*: validator set for next block. - - *commit*: evidence that block with height *height* - 1 was committed by a set of validators (canonical commit). We will use ```signers(commit)``` to refer to the set of validators that committed the block. +- header fields - * signed header fields: contains a header and a *commit* for the current header; a "seen commit". In the Tendermint consensus the "canonical commit" is stored in header *height* + 1. + - _height_ + - _bfttime_: the chain time when the header (block) was generated + - _V_: validator set containing validators for this block. + - _NextV_: validator set for next block. + - _commit_: evidence that block with height _height_ - 1 was committed by a set of validators (canonical commit). We will use `signers(commit)` to refer to the set of validators that committed the block. - * For each header *h* it has locally stored, the lite client stores whether - it trusts *h*. We write *trust(h) = true*, if this is the case. +- signed header fields: contains a header and a _commit_ for the current header; a "seen commit". In the Tendermint consensus the "canonical commit" is stored in header _height_ + 1. - * Validator fields. We will write a validator as a tuple *(v,p)* such that - + *v* is the identifier (we assume identifiers are unique in each validator set) - + *p* is its voting power +- For each header _h_ it has locally stored, the lite client stores whether + it trusts _h_. We write _trust(h) = true_, if this is the case. +- Validator fields. We will write a validator as a tuple _(v,p)_ such that + - _v_ is the identifier (we assume identifiers are unique in each validator set) + - _p_ is its voting powers ### Functions For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following function over Tendermint RPC: + ```go func Commit(height int64) (SignedHeader, error) // returns signed header: header (with the fields from @@ -61,137 +60,132 @@ For the purpose of this lite client specification, we assume that the Tendermint ### Definitions -* *tp*: trusting 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). - - - +- _tp_: trusting 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). ### Tendermint Failure Model -If a block *h* is generated at time *bfttime* (and this time is stored in the block), then a set of validators that hold more than 2/3 of the voting power in h.Header.NextV is correct until time h.Header.bfttime + tp. +If a block _h_ is generated at time _bfttime_ (and this time is stored in the block), then a set of validators that hold more than 2/3 of the voting power in h.Header.NextV is correct until time h.Header.bfttime + tp. Formally, \[ -\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > -2/3 \sum_{(v,p) \in h.Header.NextV} p +\sum*{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > +2/3 \sum*{(v,p) \in h.Header.NextV} p \] -*Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), while *bfttime* corresponds to the reading of the local clock of a validator (how this time is computed may change when the Tendermint consensus is modified). In this note, we assume that all clocks are synchronized to realtime. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and *tp* is in the order of weeks. +_Assumption_: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), while _bfttime_ corresponds to the reading of the local clock of a validator (how this time is computed may change when the Tendermint consensus is modified). In this note, we assume that all clocks are synchronized to realtime. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and _tp_ is in the order of weeks. -*Remark*: This failure model might change to a hybrid version that takes heights into account in the future. - -The specification in this document considers an implementation of the lite client under this assumption. Issues like *counter-factual signing* and *fork accountability* and *evidence submission* are mechanisms that justify this assumption by incentivizing validators to follow the protocol. -If they don't, and we have more that 1/3 faults, 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 an upcoming document on "Fork accountability". (These safety violations include the lite client wrongly trusting a header, a fork in the blockchain, etc.) +_Remark_: This failure model might change to a hybrid version that takes heights into account in the future. +The specification in this document considers an implementation of the lite client under this assumption. Issues like _counter-factual signing_ and _fork accountability_ and _evidence submission_ are mechanisms that justify this assumption by incentivizing validators to follow the protocol. +If they don't, and we have more that 1/3 faults, 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 an upcoming document on "Fork accountability". (These safety violations include the lite client wrongly trusting a header, a fork in the blockchain, etc.) ## Lite Client Trusting Spec The lite 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: -- 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. +- 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. -- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. +- Lite Client Accuracy: If header _h_ was _not generated_ by an instance of Tendermint consensus, then the lite client should never set _trust(h)_ to true. -*Remark*: If in the course of the computation, the lite 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_: If in the course of the computation, the lite 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.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. +_Remark_: In Completeness we use "eventually", while in practice _trust(h)_ should be set to true before _h.Header.bfttime + tp_. If not, the block cannot be trusted because it is too old. -*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. +_Remark_: If a header _h_ is marked with _trust(h)_, but it is too old (its bfttime is more than _tp_ ago), then the lite client should set _trust(h)_ to false again. -*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. +_Assumption_: Initially, the lite client has a header _inithead_ that it trusts correctly, that is, _inithead_ was correctly generated by the Tendermint consensus. To reason about the correctness, we may prove the following invariant. -*Verification Condition: Lite Client Invariant.* - For each lite client *l* and each header *h*: -if *l* has set *trust(h) = true*, - then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. +_Verification Condition: Lite Client Invariant._ +For each lite client _l_ and each header _h_: +if _l_ has set _trust(h) = true_, +then validators that are correct until time _h.Header.bfttime + tp_ have more than two thirds of the voting power in _h.Header.NextV_. - Formally, - \[ - \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > - 2/3 \sum_{(v,p) \in h.Header.NextV} p - \] - -*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. +Formally, +\[ +\sum*{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > +2/3 \sum*{(v,p) \in h.Header.NextV} p +\] +_Remark._ To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. ## High Level Solution -Upon initialization, the lite client is given a header *inithead* it trusts (by -social consensus). It is assumed that *inithead* satisfies the lite client invariant. (If *inithead* has been correctly generated by Tendermint consensus, the invariant follows from the Tendermint Failure Model.) +Upon initialization, the lite client is given a header _inithead_ it trusts (by +social consensus). It is assumed that _inithead_ satisfies the lite client invariant. (If _inithead_ has been correctly generated by Tendermint consensus, the invariant follows from the Tendermint Failure Model.) -When a lite clients sees a signed new header *snh*, it has to decide whether to trust the new +When a lite clients sees a signed new 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 proof.** If a block is appended to the chain, where the last block -is trusted (and properly committed by the old validator set in the next block), -and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. -Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. + is trusted (and properly committed by the old validator set in the next block), + and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. + Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. -2. **Trusting period.** Based on a trusted block *h*, and the lite client -invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. -If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. +2. **Trusting period.** Based on a trusted block _h_, and the lite client + invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from _h.Header.bfttime_ until now, has signed _snh_. + If this is the case, similarly to above, the chosen validator set in _snh_ does not violate the Tendermint Failure Model. -3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* 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 *snh*; if not, we may continue recursively. +3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header _hp_ whose height lies between _h_ and _snh_ 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 _snh_; if not, we may continue recursively. ## How to use it We consider the following use case: - the lite client wants to verify a header for some given height *k*. Thus: - - it requests the signed header for height *k* from a full node - - it tries to verify this header with the methods described here. +the lite client wants to verify a header for some given height _k_. Thus: + +- it requests the signed header for height _k_ from a full node +- it tries to verify this header with the methods described here. This can be used in several settings: - - someone tells the lite client that application data that is relevant for it can be read in the block of height *k*. - - the lite clients wants the latest state. It asks a full nude for the current height, and uses the response for *k*. +- someone tells the lite client that application data that is relevant for it can be read in the block of height _k_. +- the lite clients wants the latest state. It asks a full node for the current height, and uses the response for _k_. ## Details -*Assumptions* +_Assumptions_ -1. *tp < unbonding period*. -2. *snh.Header.bfttime < now* -3. *snh.Header.bfttime < h.Header.bfttime+tp* -4. *trust(h)=true* - - -**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old -validator set *h.Header.NextV*. - -When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* is correct, but we only trust the fact that at most 1/3 of them are faulty (more precisely, the faulty ones have at most 1/3 of the total voting power). +1. _tp < unbonding period_. +2. _snh.Header.bfttime < now_ +3. _snh.Header.bfttime < h.Header.bfttime+tp_ +4. _trust(h)=true_ +**Observation 1.** If _h.Header.bfttime + tp > now_, we trust the old +validator set _h.Header.NextV_. +When we say we trust _h.Header.NextV_ we do _not_ trust that each individual validator in _h.Header.NextV_ is correct, but we only trust the fact that at most 1/3 of them are faulty (more precisely, the faulty ones have at most 1/3 of the total voting power). ### Functions -The function *Bisection* checks whether to trust header *h2* based on the trusted header *h1*. It does so by calling -the function *CheckSupport* in the process of -bisection/recursion. *CheckSupport* implements the trusted period method and, for two adjacent headers (in term of heights), it checks uninterrupted sequence of proof. +The function _Bisection_ checks whether to trust header _h2_ based on the trusted header _h1_. It does so by calling +the function _CheckSupport_ in the process of +bisection/recursion. _CheckSupport_ implements the trusted period method and, for two adjacent headers (in term of heights), it checks uninterrupted sequence of proof. -*Assumption*: In the following, we assume that *h2.Header.height > h1.Header.height*. We will quickly discuss the other case in the next section. +_Assumption_: In the following, we assume that _h2.Header.height > h1.Header.height_. We will quickly discuss the other case in the next section. We consider the following set-up: + - the lite client communicates with one full node -- the lite client locally stores all the signed headers it obtained (trusted or not). In the pseudo code below we write *Store(header)* for this. -- If *Bisection* returns *false*, then the lite client has seen a forged header. - * However, it does not know which header(s) is/are the problematic one(s). - * In this case, the lite client can submit (some of) the headers it has seen as evidence. As the lite client communicates with one full node only when executing Bisection, there are two cases +- the lite client locally stores all the signed headers it obtained (trusted or not). In the pseudo code below we write _Store(header)_ for this. +- If _Bisection_ returns _false_, then the lite client has seen a forged header. + + - However, it does not know which header(s) is/are the problematic one(s). + - In this case, the lite client can submit (some of) the headers it has seen as evidence. As the lite client communicates with one full node only when executing Bisection, there are two cases - the full node is faulty - - the full node is correct and there was a fork in Tendermint consensus. Header *h1* is from a different branch than the one taken by the full node. This case is not focus of this document, but will be treated in the document on fork accountability. + - the full node is correct and there was a fork in Tendermint consensus. Header _h1_ is from a different branch than the one taken by the full node. This case is not focus of this document, but will be treated in the document on fork accountability. - the lite client must retry to retrieve correct headers from another full node - * it picks a new full node - * it restarts *Bisection* - * there might be optimizations; a lite client may not need to call *Commit(k)*, for a height *k* for which it already has a signed header it trusts. - * how to make sure that a lite client can communicate with a correct full node will be the focus of a separate document (recall Issue 3 from "Context of this document"). + - it picks a new full node + - it restarts _Bisection_ + - there might be optimizations; a lite client may not need to call _Commit(k)_, for a height _k_ for which it already has a signed header it trusts. + - how to make sure that a lite client can communicate with a correct full node will be the focus of a separate document (recall Issue 3 from "Context of this document"). -**Auxiliary Functions.** We will use the function ```votingpower_in(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; -we will write ```totalVotingPower(V)``` for ```votingpower_in(V,V)```, which returns the total voting power in V. -We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. +**Auxiliary Functions.** We will use the function `votingpower_in(V1,V2)` to compute the voting power the validators in set V1 have according to their voting power in set V2; +we will write `totalVotingPower(V)` for `votingpower_in(V,V)`, which returns the total voting power in V. +We further use the function `signers(Commit)` that returns the set of validators that signed the Commit. **CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. @@ -199,7 +193,7 @@ We further use the function ```signers(Commit)``` that returns the set of valida func CheckSupport(h1,h2,trustlevel) bool { if h1.Header.bfttime + tp < now { // Observation 1 return false // old header was once trusted but it is expired - } + } vp_all := totalVotingPower(h1.Header.NextV) // total sum of voting power of validators in h2 @@ -223,39 +217,36 @@ We further use the function ```signers(Commit)``` that returns the set of valida } ``` - *Remark*: Basic header verification must be done for *h2*. Similar checks are done in: - https://github.com/tendermint/tendermint/blob/master/types/validator_set.go#L591-L633 +_Remark_: Basic header verification must be done for _h2_. Similar checks are done in: + https://github.com/tendermint/tendermint/blob/master/types/validator_set.go#L591-L633 - *Remark*: There are some sanity checks which are not in the code: - *h2.Header.height > h1.Header.height* and *h2.Header.bfttime > h1.Header.bfttime* and *h2.Header.bfttime < now*. +_Remark_: There are some sanity checks which are not in the code: +_h2.Header.height > h1.Header.height_ and _h2.Header.bfttime > h1.Header.bfttime_ and _h2.Header.bfttime < now_. - *Remark*: ```return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all)``` may return false even if *h2* was properly generated by Tendermint consensus in the case of big changes in the validator sets. However, the check ```return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > - 2/3 * vp_all)``` must return true if *h1* and *h2* were generated by Tendermint consensus. +_Remark_: `return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all)` may return false even if _h2_ was properly generated by Tendermint consensus in the case of big changes in the validator sets. However, the check `return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > 2/3 * vp_all)` must return true if _h1_ and _h2_ were generated by Tendermint consensus. -*Remark*: The 1/3 check differs from a previously proposed method that was based on intersecting validator sets and checking that the new validator set contains "enough" correct validators. We found that the old check is not suited for realistic changes in the validator sets. The new method is not only based on cardinalities, but also exploits that we can trust what is signed by a correct validator (i.e., signed by more than 1/3 of the voting power). +_Remark_: The 1/3 check differs from a previously proposed method that was based on intersecting validator sets and checking that the new validator set contains "enough" correct validators. We found that the old check is not suited for realistic changes in the validator sets. The new method is not only based on cardinalities, but also exploits that we can trust what is signed by a correct validator (i.e., signed by more than 1/3 of the voting power). -*Correctness arguments* +_Correctness arguments_ Towards Lite Client Accuracy: -- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because *CheckSupport* returns true. -- h1 is trusted and sufficiently new -- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed *h2*. -- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing *h2* => *h2* was correctly generated, we arrive at the required contradiction. +- Assume by contradiction that _h2_ was not generated correctly and the lite client sets trust to true because _CheckSupport_ returns true. +- h1 is trusted and sufficiently new +- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator _v_ has signed _h2_. +- as _v_ is correct up to now, it followed the Tendermint consensus protocol at least up to signing _h2_ => _h2_ was correctly generated, we arrive at the required contradiction. Towards Lite Client Completeness: -- The check is successful if sufficiently many validators of *h1* are still validators in *h2* and signed *h2*. -- If *h2.Header.height = h1.Header.height + 1*, and both headers were generated correctly, the test passes -*Verification Condition:* We may need a Tendermint invariant stating that if *h2.Header.height = h1.Header.height + 1* then *signers(h2.Commit) \subseteq h1.Header.NextV*. +- The check is successful if sufficiently many validators of _h1_ are still validators in _h2_ and signed _h2_. +- If _h2.Header.height = h1.Header.height + 1_, and both headers were generated correctly, the test passes -*Remark*: The variable *trustlevel* 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 *trustlevel* is chosen, the more unlikely it becomes that CheckSupport returns true for non-adjacent headers. +_Verification Condition:_ We may need a Tendermint invariant stating that if _h2.Header.height = h1.Header.height + 1_ then _signers(h2.Commit) \subseteq h1.Header.NextV_. + +_Remark_: The variable _trustlevel_ 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 _trustlevel_ is chosen, the more unlikely it becomes that CheckSupport returns true for non-adjacent headers. **Bisection.** The following function uses CheckSupport in a recursion to find intermediate headers that allow to establish a sequence of trust. - - - ```go func Bisection(h1,h2,trustlevel) bool{ if CheckSupport(h1,h2,trustlevel) { @@ -270,7 +261,7 @@ func Bisection(h1,h2,trustlevel) bool{ pivot := (h1.Header.height + h2.Header.height) / 2 hp := Commit(pivot) // ask a full node for header of height pivot - Store(hp) + Store(hp) // store header hp locally if Bisection(h1,hp,trustlevel) { // only check right branch if hp is trusted @@ -281,36 +272,34 @@ func Bisection(h1,h2,trustlevel) bool{ return false } } -``` +``` - - - -*Correctness arguments (sketch)* +_Correctness arguments (sketch)_ Lite Client Accuracy: -- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because Bisection returns true. + +- Assume by contradiction that _h2_ was not generated correctly and the lite client sets trust to true because Bisection returns true. - Bisection returns true only if all calls to CheckSupport in the recursion return true. - Thus we have a sequence of headers that all satisfied the CheckSupport - again a contradiction Lite Client Completeness: -This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. +This is only ensured if upon _Commit(pivot)_ the lite client is always provided with a correctly generated header. -*Stalling* +_Stalling_ With Bisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite 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 lite 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, Bisection would then be executed at the full node. -* We may set a timeout how long bisection may take. +- Each call to `Commit` could be issued to a different full node +- Instead of querying header by header, the lite 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, Bisection would then be executed at the full node. +- We may set a timeout how long bisection may take. -### The case *h2.Header.height < h1.Header.height* +### The case _h2.Header.height < h1.Header.height_ -In the use case where someone tells the lite client that application data that is relevant for it can be read in the block of height *k* and the lite 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. +In the use case where someone tells the lite client that application data that is relevant for it can be read in the block of height _k_ and the lite 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 lite 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. +_Remark._ For the case were the lite 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 Backwards(h1,h2) bool { diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md new file mode 100644 index 000000000..0ffc4b0bf --- /dev/null +++ b/spec/consensus/readme.md @@ -0,0 +1,5 @@ +--- +cards: true +--- + +# Consensus diff --git a/spec/consensus/wal.md b/spec/consensus/wal.md index 92bbfc4b2..95d1bad12 100644 --- a/spec/consensus/wal.md +++ b/spec/consensus/wal.md @@ -28,5 +28,5 @@ 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-corruptionn) +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/p2p/readme.md b/spec/p2p/readme.md new file mode 100644 index 000000000..4d85d382c --- /dev/null +++ b/spec/p2p/readme.md @@ -0,0 +1,5 @@ +--- +cards: true +--- + +# P2P diff --git a/spec/reactors/consensus/consensus-reactor.md b/spec/reactors/consensus/consensus-reactor.md index 47c6949a7..71368f06e 100644 --- a/spec/reactors/consensus/consensus-reactor.md +++ b/spec/reactors/consensus/consensus-reactor.md @@ -40,7 +40,7 @@ RoundState defines the internal consensus state. It contains height, round, roun a proposal and proposal block for the current round, locked round and block (if some block is being locked), set of received votes and last commit and last validators set. -```golang +```go type RoundState struct { Height int64 Round int @@ -96,11 +96,13 @@ type PeerRoundState struct { ## Receive method of Consensus reactor -The entry point of the Consensus reactor is a receive method. When a message is received from a peer p, -normally the peer round state is updated correspondingly, and some messages -are passed for further processing, for example to ConsensusState service. We now specify the processing of messages -in the receive method of Consensus reactor for each message type. In the following message handler, `rs` and `prs` denote -`RoundState` and `PeerRoundState`, respectively. +The entry point of the Consensus reactor is a receive method. When a message is +received from a peer p, normally the peer round state is updated +correspondingly, and some messages are passed for further processing, for +example to ConsensusState service. We now specify the processing of messages in +the receive method of Consensus reactor for each message type. In the following +message handler, `rs` and `prs` denote `RoundState` and `PeerRoundState`, +respectively. ### NewRoundStepMessage handler @@ -134,13 +136,16 @@ handleMessage(msg): ``` handleMessage(msg): if prs.Height != msg.Height then return - + if prs.Round != msg.Round && !msg.IsCommit then return - + prs.ProposalBlockPartsHeader = msg.BlockPartsHeader prs.ProposalBlockParts = msg.BlockParts ``` +The number of block parts is limited to 1601 (`types.MaxBlockPartsCount`) to +protect the node against DOS attacks. + ### HasVoteMessage handler ``` @@ -179,6 +184,9 @@ handleMessage(msg): prs.ProposalPOL = msg.ProposalPOL ``` +The number of votes is limited to 10000 (`types.MaxVotesCount`) to protect the +node against DOS attacks. + ### BlockPartMessage handler ``` @@ -203,6 +211,9 @@ handleMessage(msg): Update prs for the bit-array of votes peer claims to have for the msg.BlockID ``` +The number of votes is limited to 10000 (`types.MaxVotesCount`) to protect the +node against DOS attacks. + ## Gossip Data Routine It is used to send the following messages to the peer: `BlockPartMessage`, `ProposalMessage` and @@ -338,7 +349,7 @@ BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs` ## Broadcast routine -The Broadcast routine subscribes to an internal event bus to receive new round steps and votes messages, and broadcasts messages to peers upon receiving those +The Broadcast routine subscribes to an internal event bus to receive new round steps and votes messages, and broadcasts messages to peers upon receiving those events. It broadcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state event. Note that broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel. diff --git a/spec/reactors/mempool/config.md b/spec/reactors/mempool/config.md index 4fb756fa4..77deded42 100644 --- a/spec/reactors/mempool/config.md +++ b/spec/reactors/mempool/config.md @@ -51,4 +51,4 @@ If the directory passed in is an absolute path, the wal file is created there. If the directory is a relative path, the path is appended to home directory of the tendermint process to generate an absolute path to the wal directory -(default `$HOME/.tendermint` or set via `TM_HOME` or `--home``) +(default `$HOME/.tendermint` or set via `TM_HOME` or `--home`) diff --git a/spec/reactors/readme.md b/spec/reactors/readme.md new file mode 100644 index 000000000..82a19485b --- /dev/null +++ b/spec/reactors/readme.md @@ -0,0 +1,5 @@ +--- +cards: true +--- + +# Reactors From 953523c3cb99fdb8c8f7a2d21e3a99094279e9de Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 23 Nov 2019 14:02:08 +0400 Subject: [PATCH 013/223] 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 --- spec/blockchain/blockchain.md | 87 +++++++++++++------- spec/reactors/consensus/consensus-reactor.md | 2 +- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/spec/blockchain/blockchain.md b/spec/blockchain/blockchain.md index d5fd6988d..39802bcd5 100644 --- a/spec/blockchain/blockchain.md +++ b/spec/blockchain/blockchain.md @@ -29,7 +29,7 @@ type Block struct { } ``` -Note the `LastCommit` is the set of votes that committed the last block. +Note the `LastCommit` is the set of signatures of validators that committed the last block. ## Header @@ -121,18 +121,47 @@ type Data struct { ## Commit -Commit is a simple wrapper for a list of votes, with one vote for each -validator. It also contains the relevant BlockID: +Commit is a simple wrapper for a list of signatures, with one for each +validator. It also contains the relevant BlockID, height and round: -``` +```go type Commit struct { - BlockID BlockID - Precommits []Vote + Height int64 + Round int + BlockID BlockID + Signatures []CommitSig } ``` -NOTE: this will likely change to reduce the commit size by eliminating redundant -information - see [issue #1648](https://github.com/tendermint/tendermint/issues/1648). +## 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. + +```go +type BlockIDFlag byte + +const ( + // BlockIDFlagAbsent - no vote was received from a validator. + BlockIDFlagAbsent BlockIDFlag = 0x01 + // BlockIDFlagCommit - voted for the Commit.BlockID. + BlockIDFlagCommit = 0x02 + // BlockIDFlagNil - voted for nil. + BlockIDFlagNil = 0x03 +) + +type CommitSig struct { + BlockIDFlag BlockIDFlag + ValidatorAddress Address + Timestamp time.Time + Signature []byte +} +``` + +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)). ## Vote @@ -248,7 +277,7 @@ block.Header.Timestamp == MedianTime(block.LastCommit, state.LastValidators) ``` The block timestamp must be monotonic. -It must equal the weighted median of the timestamps of the valid votes in the block.LastCommit. +It must equal the weighted median of the timestamps of the valid signatures in the block.LastCommit. Note: the timestamp of a vote must be greater by at least one millisecond than that of the block being voted on. @@ -284,11 +313,12 @@ The first block has `block.Header.LastBlockID == BlockID{}`. ### LastCommitHash ```go -block.Header.LastCommitHash == MerkleRoot(block.LastCommit.Precommits) +block.Header.LastCommitHash == MerkleRoot(block.LastCommit.Signatures) ``` -MerkleRoot of the votes included in the block. -These are the votes that committed the previous block. +MerkleRoot of the signatures included in the block. +These are the commit signatures of the validators that committed the previous +block. The first block has `block.Header.LastCommitHash == []byte{}` @@ -376,11 +406,11 @@ Arbitrary length array of arbitrary length byte-arrays. ## LastCommit -The first height is an exception - it requires the LastCommit to be empty: +The first height is an exception - it requires the `LastCommit` to be empty: ```go if block.Header.Height == 1 { - len(b.LastCommit) == 0 + len(b.LastCommit) == 0 } ``` @@ -388,29 +418,26 @@ Otherwise, we require: ```go len(block.LastCommit) == len(state.LastValidators) + talliedVotingPower := 0 -for i, vote := range block.LastCommit{ - if vote == nil{ - continue - } - vote.Type == 2 - vote.Height == block.LastCommit.Height() - vote.Round == block.LastCommit.Round() - vote.BlockID == block.LastBlockID +for i, commitSig := range block.LastCommit.Signatures { + if commitSig.Absent() { + continue + } - val := state.LastValidators[i] - vote.Verify(block.ChainID, val.PubKey) == true + vote.BlockID == block.LastBlockID - talliedVotingPower += val.VotingPower + val := state.LastValidators[i] + vote.Verify(block.ChainID, val.PubKey) == true + + talliedVotingPower += val.VotingPower } -talliedVotingPower > (2/3) * TotalVotingPower(state.LastValidators) +talliedVotingPower > (2/3)*TotalVotingPower(state.LastValidators) ``` -Includes one (possibly nil) vote for every current validator. -Non-nil votes must be Precommits. -All votes must be for the same height and round. -All votes must be for the previous block. +Includes one vote for every current validator. +All votes must either be for the previous block, nil or absent. All votes must have a valid signature from the corresponding validator. The sum total 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. diff --git a/spec/reactors/consensus/consensus-reactor.md b/spec/reactors/consensus/consensus-reactor.md index 71368f06e..7ac9d20b0 100644 --- a/spec/reactors/consensus/consensus-reactor.md +++ b/spec/reactors/consensus/consensus-reactor.md @@ -340,7 +340,7 @@ BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs` 1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height and prs.Height <= blockStore.Height() then Commit = LoadCommit(prs.Height) - m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.blockId) + m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.BlockID) Send m to peer Sleep PeerQueryMaj23SleepDuration From 4ee393c3daa6d0278fb49af55e48641515b0f270 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 28 Nov 2019 15:42:35 +0100 Subject: [PATCH 014/223] Clean up error conditions and simplify pseudocode --- spec/consensus/light-client.md | 98 +++++++++++++++++----------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 8f2d79746..9832eefcb 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -198,29 +198,38 @@ captured by the `hasExpired` function that depends on trusted period (`tp`) and not considered expired. ```go - func hasExpired(h) { + // return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false + func hasExpired(h) bool { if h.Header.bfttime + tp - Delta < now { // Observation 1 return true } - // basic validation (function `verify`) has already been called on h2 - func CheckSupport(h1,h2,trustlevel) bool { - if hasExpired(h1) then return false //old header was once trusted but it is expired + // return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; otherwise false. Additional checks should be done in the implementation + // to ensure header is well formed. + func verify(h) bool { + vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h + return votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all + } - vp_all := totalVotingPower(h1.Header.NextV) - // total sum of voting power of validators in h1 + // Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`). + // returns (true, nil) in case h2 can be trusted based on h1, (false, nil) in case it cannot be trusted but no errors are observed during check and (false, error) in case + // an error is detected (for example adjacent headers are not consistent). + func CheckSupport(h1,h2,trustlevel) (bool, error) { + assume h1.Header.Height < h2.header.Height - if h2.Header.height == h1.Header.height + 1 { - // specific check for adjacent headers - if h1.Header.NextV == h2.Header.V then - return hasExpired(h2) - } - } + if hasExpired(h1) return (false, ErrHeaderExpired(h1)) - // validators that signed h2 are more than a third of voting power in h1 - if (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all) { - return hasExpired(h2) - } + // total sum of voting power of validators in h1.NextV + vp_all := totalVotingPower(h1.Header.NextV) + + // check for adjacent headers + if (h2.Header.height == h1.Header.height + 1) { + if h1.Header.NextV == h2.Header.V return (true, nil) + else return (false, ErrInvalidAdjacentHeaders) + } else { + // check for non-adjacent headers + return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil) + } } ``` @@ -258,58 +267,47 @@ Towards Lite Client Completeness: ```go -func verify(h) { - if hasExpired(h) return false +// return (true, nil) in case we can trust header h2 based on header h1; otherwise return (false, error) where error captures the nature of the error. +func Bisection(h1,h2,trustlevel) (bool, error) { + assume h1.Header.Height < h2.header.Height - vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h + ok, err = CheckSupport(h1,h2,trustlevel) + if (ok or err != nil) return (ok, err) - if votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all { - return hasExpired(h) - } else { - return false - } -} - -func Bisection(h1,h2,trustlevel) bool{ + // we cannot verify h2 based on h1, so we try to move trusted header closer to h2 so we can verify h2 th := h1 // th is trusted header - while th.Header.Height <= h2.Header.height do { + while th.Header.Height <= h2.Header.height - 1 do { // try to move trusted header forward with stored headers - // we assume here that iteration will be done in order of header heights ih := th - for all stored headers h s.t ih.Header.Height < h.Header.height < h2.Header.height do { - if CheckSupport(th,h,trustlevel) { + for all stored headers h s.t ih.Header.Height < h.Header.height < h2.Header.height do { // try to move trusted header forward + // we assume here that iteration is done in the order of header heights + ok, err = CheckSupport(th,h,trustlevel) + if err != nil { return (ok, err) } + if ok { th = h - } else if h.Header.height == th.Header.height + 1 { - return false // fail to verify succesive headers! - } else break // for + } } - if CheckSupport(th,h2,trustlevel) { - return hasExpired(h2) - } + // at this point we have potentially updated th based on stored headers so we try to verify h2 based on new trusted header + ok, err = CheckSupport(th,h2,trustlevel) + if (ok or err != nil) return (ok, err) - if h2.Header.height == th.Header.height + 1 { - // we have adjacent headers that are not matching (failed the CheckSupport) - // we could submit evidence here - return false - } - - // try to move th + // we cannot verify h2 based on th, so we try to move trusted header closer to h2 by downloading header(s) between th and h2 endHeight = h2.Header.height foundPivot = false while(!foundPivot) { pivot := (th.Header.height + endHeight) / 2 hp := Commit(pivot) - if !verify(hp) return false + if !verify(hp) return (false, ErrInvalidHeader(hp)) Store(hp) - if CheckSupport(th,hd,trustlevel) { - th = hd + // try to move trusted header forward to hp + ok, err = CheckSupport(th,hp,trustlevel) + if err != nil { return (ok, err) } + if ok { + th = hp foundPivot = true - } else if pivot.Header.height == th.Header.height + 1 { - return false // fail to verify succesive headers! } - endHeight = pivot } } From 2306108d8ab5565337efcb7a3e1ef2b9902dc5f7 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Mon, 2 Dec 2019 12:12:45 +0100 Subject: [PATCH 015/223] Apply suggestions from code review Co-Authored-By: Anca Zamfir --- spec/consensus/light-client.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 9832eefcb..42b0102ec 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -202,9 +202,11 @@ not considered expired. func hasExpired(h) bool { if h.Header.bfttime + tp - Delta < now { // Observation 1 return true + } } - // return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; otherwise false. Additional checks should be done in the implementation + // return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; + // otherwise false. Additional checks should be done in the implementation // to ensure header is well formed. func verify(h) bool { vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h @@ -224,11 +226,12 @@ not considered expired. // check for adjacent headers if (h2.Header.height == h1.Header.height + 1) { - if h1.Header.NextV == h2.Header.V return (true, nil) - else return (false, ErrInvalidAdjacentHeaders) + if h1.Header.NextV == h2.Header.V + return (true, nil) + return (false, ErrInvalidAdjacentHeaders) } else { // check for non-adjacent headers - return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil) + return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil) } } ``` @@ -279,7 +282,8 @@ func Bisection(h1,h2,trustlevel) (bool, error) { while th.Header.Height <= h2.Header.height - 1 do { // try to move trusted header forward with stored headers ih := th - for all stored headers h s.t ih.Header.Height < h.Header.height < h2.Header.height do { // try to move trusted header forward + // try to move trusted header forward + for h in stored headers s.t ih.Header.Height < h.Header.height < h2.Header.height do { // we assume here that iteration is done in the order of header heights ok, err = CheckSupport(th,h,trustlevel) if err != nil { return (ok, err) } @@ -288,7 +292,8 @@ func Bisection(h1,h2,trustlevel) (bool, error) { } } - // at this point we have potentially updated th based on stored headers so we try to verify h2 based on new trusted header + // at this point we have potentially updated th based on stored headers so we try to verify h2 + // based on new trusted header ok, err = CheckSupport(th,h2,trustlevel) if (ok or err != nil) return (ok, err) From dbc876510493f3f7e2d5550da2b1d70d05637896 Mon Sep 17 00:00:00 2001 From: dongsamb Date: Wed, 4 Dec 2019 17:09:24 +0900 Subject: [PATCH 016/223] 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 --- spec/p2p/config.md | 12 ++++++++++++ spec/reactors/pex/pex.md | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/spec/p2p/config.md b/spec/p2p/config.md index 7ff2b5e8d..bddda4a4b 100644 --- a/spec/p2p/config.md +++ b/spec/p2p/config.md @@ -26,6 +26,10 @@ 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. @@ -36,3 +40,11 @@ and that the node may not be able to keep the connection persistent. 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/reactors/pex/pex.md b/spec/reactors/pex/pex.md index 268b4a318..68a50c51b 100644 --- a/spec/reactors/pex/pex.md +++ b/spec/reactors/pex/pex.md @@ -31,7 +31,12 @@ On startup, we will also immediately dial the given list of `persistent_peers`, and will attempt to maintain persistent connections with them. If the connections die, or we fail to dial, we will redial every 5s for a few minutes, then switch to an exponential backoff schedule, and after about a day of -trying, stop dialing the peer. +trying, stop dialing the peer. This behavior is when `persistent_peers_max_dial_period` is configured to zero. + +But If `persistent_peers_max_dial_period` is set greater than zero, terms between each dial to each persistent peer +will not exceed `persistent_peers_max_dial_period` during exponential backoff. +Therefore, `dial_period` = min(`persistent_peers_max_dial_period`, `exponential_backoff_dial_period`) +and we keep trying again regardless of `maxAttemptsToDial` As long as we have less than `MaxNumOutboundPeers`, we periodically request additional peers from each of our own and try seeds. @@ -93,7 +98,8 @@ the selection process happens every `ensurePeersPeriod`, we might not end up dialing a peer for much longer than the backoff duration. If we fail to connect to the peer after 16 tries (with exponential backoff), we -remove from address book completely. +remove from address book completely. But for persistent peers, we indefinitely try to +dial all persistent peers unless `persistent_peers_max_dial_period` is configured to zero ## Select Peers to Exchange From 743a65861396e36022b2704e4383198b42c9cfbe Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Wed, 4 Dec 2019 06:44:28 -0500 Subject: [PATCH 017/223] Add sr25519 amino documentation (#67) * sr25519 amino * Update spec/blockchain/encoding.md Co-Authored-By: Marko --- spec/blockchain/encoding.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/blockchain/encoding.md b/spec/blockchain/encoding.md index 7c4db8e92..31b9c0625 100644 --- a/spec/blockchain/encoding.md +++ b/spec/blockchain/encoding.md @@ -62,8 +62,10 @@ You can simply use below table and concatenate Prefix || Length (of raw bytes) | | Type | Name | Prefix | Length | Notes | | ----------------------- | ---------------------------------- | ---------- | -------- | ----- | | PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | | +| PubKeySr25519 | tendermint/PubKeySr25519 | 0x0DFB1005 | 0x20 | | | PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | | | PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | | +| PrivKeySr25519 | tendermint/PrivKeySr25519 | 0x2F82D78B | 0x20 | | | PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | | | PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | | @@ -90,6 +92,18 @@ address = SHA256(pubkey)[:20] The signature is the raw 64-byte ED25519 signature. +#### Sr25519 + +TODO: pubkey + +The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key: + +``` +address = SHA256(pubkey)[:20] +``` + +The signature is the raw 64-byte ED25519 signature. + #### Secp256k1 TODO: pubkey From afda2d39b6a29104f483da8896b5c3de8667e53d Mon Sep 17 00:00:00 2001 From: Anca Zamfir Date: Fri, 6 Dec 2019 12:43:16 +0100 Subject: [PATCH 018/223] some suggestions for pseuodocode changes --- spec/consensus/light-client.md | 151 +++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 56 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 42b0102ec..97f0a3564 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -203,6 +203,7 @@ not considered expired. if h.Header.bfttime + tp - Delta < now { // Observation 1 return true } + return false } // return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; @@ -266,62 +267,6 @@ Towards Lite Client Completeness: **Bisection.** The following function uses CheckSupport in a recursion to find intermediate headers that allow to establish a sequence of trust. - - - -```go -// return (true, nil) in case we can trust header h2 based on header h1; otherwise return (false, error) where error captures the nature of the error. -func Bisection(h1,h2,trustlevel) (bool, error) { - assume h1.Header.Height < h2.header.Height - - ok, err = CheckSupport(h1,h2,trustlevel) - if (ok or err != nil) return (ok, err) - - // we cannot verify h2 based on h1, so we try to move trusted header closer to h2 so we can verify h2 - th := h1 // th is trusted header - while th.Header.Height <= h2.Header.height - 1 do { - // try to move trusted header forward with stored headers - ih := th - // try to move trusted header forward - for h in stored headers s.t ih.Header.Height < h.Header.height < h2.Header.height do { - // we assume here that iteration is done in the order of header heights - ok, err = CheckSupport(th,h,trustlevel) - if err != nil { return (ok, err) } - if ok { - th = h - } - } - - // at this point we have potentially updated th based on stored headers so we try to verify h2 - // based on new trusted header - ok, err = CheckSupport(th,h2,trustlevel) - if (ok or err != nil) return (ok, err) - - // we cannot verify h2 based on th, so we try to move trusted header closer to h2 by downloading header(s) between th and h2 - endHeight = h2.Header.height - foundPivot = false - while(!foundPivot) { - pivot := (th.Header.height + endHeight) / 2 - hp := Commit(pivot) - if !verify(hp) return (false, ErrInvalidHeader(hp)) - Store(hp) - - // try to move trusted header forward to hp - ok, err = CheckSupport(th,hp,trustlevel) - if err != nil { return (ok, err) } - if ok { - th = hp - foundPivot = true - } - endHeight = pivot - } - } -} -``` - - - - *Correctness arguments (sketch)* Lite Client Accuracy: @@ -363,3 +308,97 @@ func Backwards(h1,h2) bool { return (hash(h2) == old.Header.hash) } ``` + +```go +// return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false +func hasExpired(h) bool { + if now - h.Header.bfttime > tp - Delta + return true // Observation 1 + return false +} + +// return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; +// otherwise false. Additional checks should be done in the implementation +// to ensure header is well formed. +func verify(h) bool { + vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h + return votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all +} + +// Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`). +// returns (true, nil) in case h2 can be trusted based on h1, (false, nil) in case it cannot be trusted but no errors +// are observed during check and (false, error) in case an error is detected (for example adjacent +// headers are not consistent). +func CheckSupport(h1,h2,trustlevel) (bool, error) { + assume h1.Header.Height < h2.header.Height + + // check for adjacent headers + if h2.Header.height == h1.Header.height + 1 { + if h1.Header.NextV == h2.Header.V return nil + return false, ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in h1.NextV + vp_all := totalVotingPower(h1.Header.NextV) + // check for non-adjacent headers + return votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil +} + +// return nil in case we can trust header h2 based on header h1, error otherwise +func Bisection(h1,h2,trustlevel) error { + assume h1.Header.Height < h2.header.Height + + th := h1 + pivot = h2.Header.height + untrustedHeaders = [] + + while h2 not in Store.Headers() { // could be replaced with while true and check if h2 is stored in the loop + // check support for pivot, bisect if no support until a higher trusted header is found + while pivot < h1.Header.height { + hp := Commit(pivot) + if !verify(hp) return ErrInvalidHeader(hp) + done, err = CheckSupport(th,hp,trustlevel) + if err != nil return err + if done { + th = hp + Store.Add(hp) + break + } + untrustedHeaders.add(hp) + pivot := (th.Header.height + endHeight) / 2 + } + + // try to move trusted header forward + for h in untrustedHeaders { + // we assume here that iteration is done in the order of header heights + done, err = CheckSupport(th,h,trustlevel) + if err != nil return err + if done { + th = h + Store.Add(h) + } + } + } + return nil +} + +func VerifyHeader(h2, trustlevel) error { + if h2 in Store.Headers() + return nil + + Store.DisableExpiration() + // get the highest trusted headers lower than h2 + h1 = Store.HighestTrusted(h2.Header.height) + if h1 == nil + return ErrNoTrustedHeader + + err = Bisection(h1, h2, trustlevel) + if err != nil return err + + // remove all expired headers and start the expiration timer + Store.EnableExpiration() + if h2 in Store.Headers() return nil + return ErrHeaderExpired +} + +``` \ No newline at end of file From 5c580846bb19cd7af7f718658f9d8365f7aeddd3 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Fri, 6 Dec 2019 15:56:05 +0100 Subject: [PATCH 019/223] Improved error handling --- spec/consensus/light-client.md | 86 +++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 42b0102ec..ee9f792c4 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -168,38 +168,38 @@ When we say we trust *h.Header.NextV* we do *not* trust that each individual val ### Functions -The function *Bisection* checks whether to trust header *h2* based on the trusted header *h1*. It does so by calling -the function *CheckSupport* in the process of -bisection/recursion. *CheckSupport* implements the trusted period method and, for two adjacent headers (in term of heights), it checks uninterrupted sequence of proof. +The function *CanTrust* checks whether to trust header *h2* based on the trusted header *h1*. It does so by (potentially) +building transitive trust relation between *h1* and *h2*, over some intermediate headers. For example, in case we cannot trust +header *h2* based on the trusted header *h1*, the function *CanTrust* will try to find headers such that we can transition trust +from *h1* over intermediate headers to *h2*. We will give two implementations of *CanTrust* based on *CheckSupport*, the one based +on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier +to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. + +Both implementations of *CanTrust* function are based on *CheckSupport* function that implements the conditions under which we can trust a +header *h2* given the trust in the header *h1* as a single step, +i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. + *Assumption*: In the following, we assume that *h2.Header.height > h1.Header.height*. We will quickly discuss the other case in the next section. We consider the following set-up: - the lite client communicates with one full node -- the lite client locally stores all the signed headers it obtained (trusted or not). In the pseudo code below we write *Store(header)* for this. -- If *Bisection* returns *false*, then the lite client has seen a forged header. - * However, it does not know which header(s) is/are the problematic one(s). - * In this case, the lite client can submit (some of) the headers it has seen as evidence. As the lite client communicates with one full node only when executing Bisection, there are two cases - - the full node is faulty - - the full node is correct and there was a fork in Tendermint consensus. Header *h1* is from a different branch than the one taken by the full node. This case is not focus of this document, but will be treated in the document on fork accountability. - -- the lite client must retry to retrieve correct headers from another full node - * it picks a new full node - * it restarts *Bisection* - * there might be optimizations; a lite client may not need to call *Commit(k)*, for a height *k* for which it already has a signed header it trusts. - * how to make sure that a lite client can communicate with a correct full node will be the focus of a separate document (recall Issue 3 from "Context of this document"). +- the lite client locally stores all the headers that has passed basic verification. In the pseudo code below we write *Store(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 lite client. +- If *CanTrust* returns *error*, then the lite 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 lite client should disconnect and reinitialise with new trusted header. **Auxiliary Functions.** We will use the function ```votingpower_in(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; we will write ```totalVotingPower(V)``` for ```votingpower_in(V,V)```, which returns the total voting power in V. We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. **CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. Time constraint is -captured by the `hasExpired` function that depends on trusted period (`tp`) and a parameter `Delta` that denotes minimum duration of header so it is -not considered expired. +captured by the ```hasExpired``` function that depends on trusted period (`tp`) and it returns true in case the header is outside its trusted period. +```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. ```go // return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false - func hasExpired(h) bool { + func hasExpired(h, Delta) bool { if h.Header.bfttime + tp - Delta < now { // Observation 1 return true } @@ -214,12 +214,14 @@ not considered expired. } // Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`). - // returns (true, nil) in case h2 can be trusted based on h1, (false, nil) in case it cannot be trusted but no errors are observed during check and (false, error) in case - // an error is detected (for example adjacent headers are not consistent). - func CheckSupport(h1,h2,trustlevel) (bool, error) { + // returns nil in case h2 can be trusted based on h1, otherwise returns error. + // ErrHeaderExpired is used to signal that h1 has expired, + // ErrInvalidAdjacentHeaders that adjacent headers are not consistent and + // ErrTooMuchChange that there is not enough intersection between validator sets to have skipping condition true. + func CheckSupport(h1,h2,trustlevel) error { assume h1.Header.Height < h2.header.Height - if hasExpired(h1) return (false, ErrHeaderExpired(h1)) + if hasExpired(h1) return ErrHeaderExpired(h1) // total sum of voting power of validators in h1.NextV vp_all := totalVotingPower(h1.Header.NextV) @@ -227,11 +229,14 @@ not considered expired. // check for adjacent headers if (h2.Header.height == h1.Header.height + 1) { if h1.Header.NextV == h2.Header.V - return (true, nil) - return (false, ErrInvalidAdjacentHeaders) + return nil + return ErrInvalidAdjacentHeaders } else { // check for non-adjacent headers - return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil) + if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all + return nil + else + return ErrTooMuchChange(h1, h2) } } ``` @@ -267,11 +272,9 @@ Towards Lite Client Completeness: **Bisection.** The following function uses CheckSupport in a recursion to find intermediate headers that allow to establish a sequence of trust. - - ```go // return (true, nil) in case we can trust header h2 based on header h1; otherwise return (false, error) where error captures the nature of the error. -func Bisection(h1,h2,trustlevel) (bool, error) { +func CanTrust(h1,h2,trustlevel) (bool, error) { assume h1.Header.Height < h2.header.Height ok, err = CheckSupport(h1,h2,trustlevel) @@ -319,6 +322,33 @@ func Bisection(h1,h2,trustlevel) (bool, error) { } ``` +```go +func CanTrustBisection(h1,h2,trustlevel) bool{ + if CheckSupport(h1,h2,trustlevel) { + return true + } + if h2.Header.height == h1.Header.height + 1 { + // we have adjacent headers that are not matching (failed + // the CheckSupport) + // we could submit evidence here + return false + } + pivot := (h1.Header.height + h2.Header.height) / 2 + hp := Commit(pivot) + // ask a full node for header of height pivot + Store(hp) + // store header hp locally + if Bisection(h1,hp,trustlevel) { + // only check right branch if hp is trusted + // (otherwise a lot of unnecessary computation may be done) + return Bisection(hp,h2,trustlevel) + } + else { + return false + } +} +``` + From 9ddfc798139ad5c77c92d4cc0cbebd12fc573327 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 11 Dec 2019 16:13:47 +0100 Subject: [PATCH 020/223] Add explanation on difference between trusted models --- spec/consensus/light-client.md | 334 ++++++++++++++------------------- 1 file changed, 138 insertions(+), 196 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 76ad8c823..1e4ed66f2 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -6,18 +6,18 @@ A lite client is a process that connects to Tendermint full nodes and then tries In order to make sure that full nodes have the incentive to follow the protocol, we have to address the following three Issues -1) The lite client needs a method to verify headers it obtains from full nodes according to trust assumptions -- this document. +1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. -2) The lite client must be able to connect to one correct full node to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document. +2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- see #3840 +3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). ## Problem statement We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because the lite 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. The term "trusting" above indicates that the correctness on the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged *inithead* is negligible. +The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of Tendermint consensus. The term "trusting" above indicates that the correctness of the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged *inithead* is negligible. ## Definitions @@ -61,7 +61,7 @@ For the purpose of this lite client specification, we assume that the Tendermint ### Definitions -* *tp*: trusting period +* *TRUSTED_PERIOD*: trusting 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). @@ -70,11 +70,11 @@ For the purpose of this lite client specification, we assume that the Tendermint ### Tendermint Failure Model -If a block *h* is generated at time *bfttime* (and this time is stored in the block), then a set of validators that hold more than 2/3 of the voting power in h.Header.NextV is correct until time h.Header.bfttime + tp. +If a block *h* is generated at time *bfttime* (and this time is stored in the block), then a set of validators that hold more than 2/3 of the voting power in h.Header.NextV is correct until time h.Header.bfttime + TRUSTED_PERIOD. Formally, \[ -\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > +\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > 2/3 \sum_{(v,p) \in h.Header.NextV} p \] @@ -151,40 +151,39 @@ This can be used in several settings: ## Details -*Assumptions* - -1. *tp < unbonding period*. -2. *snh.Header.bfttime < now* -3. *snh.Header.bfttime < h.Header.bfttime+tp* -4. *trust(h)=true* - - **Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old validator set *h.Header.NextV*. When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* is correct, but we only trust the fact that at most 1/3 of them are faulty (more precisely, the faulty ones have at most 1/3 of the total voting power). - ### Functions The function *CanTrust* checks whether to trust header *h2* based on the trusted header *h1*. It does so by (potentially) building transitive trust relation between *h1* and *h2*, over some intermediate headers. For example, in case we cannot trust header *h2* based on the trusted header *h1*, the function *CanTrust* will try to find headers such that we can transition trust -from *h1* over intermediate headers to *h2*. We will give two implementations of *CanTrust* based on *CheckSupport*, the one based +from *h1* over intermediate headers to *h2*. We will give two implementations of *CanTrust*, the one based on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. -Both implementations of *CanTrust* function are based on *CheckSupport* function that implements the conditions under which we can trust a +Both implementations of *CanTrust* function are based on *CheckSupport* function that implements the skipping conditions under which we can trust a header *h2* given the trust in the header *h1* as a single step, i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. +In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting +headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability +protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always +operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound +for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula +holds: +```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. + *Assumption*: In the following, we assume that *h2.Header.height > h1.Header.height*. We will quickly discuss the other case in the next section. We consider the following set-up: - the lite client communicates with one full node -- the lite client locally stores all the headers that has passed basic verification. In the pseudo code below we write *Store(header)* for this. If a header failed to verify, then +- the lite client locally stores all the headers that has passed basic verification and that are within lite client trust period. In the pseudo code below we write *Store(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 lite client. - If *CanTrust* returns *error*, then the lite 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 lite client should disconnect and reinitialise with new trusted header. @@ -193,17 +192,15 @@ the full node we are talking to is faulty and we should disconnect from it and r we will write ```totalVotingPower(V)``` for ```votingpower_in(V,V)```, which returns the total voting power in V. We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. -**CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. Time constraint is -captured by the ```hasExpired``` function that depends on trusted period (`tp`) and it returns true in case the header is outside its trusted period. +**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header h2 based on header h1. +Time validity of a header is captured by the ```isWithinTrustedPeriodWithin``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns +true in case the header is within its lite client trusted period. ```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. ```go - // return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false - func hasExpired(h, Delta) bool { - if h.Header.bfttime + tp - Delta < now { // Observation 1 - return true - } - return false + // return true if header is within its lite client trusted period; otherwise it returns false + func isWithinTrustedPeriod(h) bool { + return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now } // return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; @@ -216,13 +213,18 @@ captured by the ```hasExpired``` function that depends on trusted period (`tp`) // Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`). // returns nil in case h2 can be trusted based on h1, otherwise returns error. - // ErrHeaderExpired is used to signal that h1 has expired, + // ErrHeaderExpired is used to signal that h1 has expired with respect lite client trusted period, // ErrInvalidAdjacentHeaders that adjacent headers are not consistent and // ErrTooMuchChange that there is not enough intersection between validator sets to have skipping condition true. func CheckSupport(h1,h2,trustlevel) error { - assume h1.Header.Height < h2.header.Height + assume h1.Header.Height < h2.header.Height and h1.Header.bfttime < h2.Header.bfttime and h2.Header.bfttime < now - if hasExpired(h1) return ErrHeaderExpired(h1) + if !isWithinTrustedPeriod(h1) return ErrHeaderNotWithinTrustedPeriod(h1) + + // Although while executing the rest of CheckSupport function, h1 can expiry based on the lite client trusted period, this is not problem as + // lite client trusted period is smaller than trusted period of the header based on Tendermint Failure model, i.e., there is a significant + // time period (measure in days) during which validator set that has signed h1 can be trusted + // Furthermore, CheckSupport function is not doing expensive operation (neither rpc nor signature verification), so it should execute fast. // total sum of voting power of validators in h1.NextV vp_all := totalVotingPower(h1.Header.NextV) @@ -232,27 +234,14 @@ captured by the ```hasExpired``` function that depends on trusted period (`tp`) if h1.Header.NextV == h2.Header.V return nil return ErrInvalidAdjacentHeaders - } else { - // check for non-adjacent headers - if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all - return nil - else - return ErrTooMuchChange(h1, h2) } + // check for non-adjacent headers + if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all return nil + return ErrTooMuchChange + } ``` - *Remark*: Basic header verification must be done for *h2*. Similar checks are done in: - https://github.com/tendermint/tendermint/blob/master/types/validator_set.go#L591-L633 - - *Remark*: There are some sanity checks which are not in the code: - *h2.Header.height > h1.Header.height* and *h2.Header.bfttime > h1.Header.bfttime* and *h2.Header.bfttime < now*. - - *Remark*: ```return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all)``` may return false even if *h2* was properly generated by Tendermint consensus in the case of big changes in the validator sets. However, the check ```return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > - 2/3 * vp_all)``` must return true if *h1* and *h2* were generated by Tendermint consensus. - -*Remark*: The 1/3 check differs from a previously proposed method that was based on intersecting validator sets and checking that the new validator set contains "enough" correct validators. We found that the old check is not suited for realistic changes in the validator sets. The new method is not only based on cardinalities, but also exploits that we can trust what is signed by a correct validator (i.e., signed by more than 1/3 of the voting power). - *Correctness arguments* Towards Lite Client Accuracy: @@ -270,83 +259,124 @@ Towards Lite Client Completeness: *Remark*: The variable *trustlevel* 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 *trustlevel* is chosen, the more unlikely it becomes that CheckSupport returns true for non-adjacent headers. -**Bisection.** The following function uses CheckSupport in a recursion to find intermediate headers that allow to establish a sequence of trust. +**VerifyHeader.** The function *VerifyHeader* captures high level logic, i.e., application call to the lite client module to (optionally download) and +verify header for some height. The core verification logic is captured by *CanTrust* function that iteratively try to establish trust in given header +by relying on *CheckSupport* function. ```go -// return (true, nil) in case we can trust header h2 based on header h1; otherwise return (false, error) where error captures the nature of the error. -func CanTrust(h1,h2,trustlevel) (bool, error) { +func VerifyHeader(height, trustlevel) error { + if h2, exists := Store.Get(height); exists { + if isWithinTrustedPeriod(h2) return nil + return ErrHeaderNotWithinTrustedPeriod(h2) + } + else { + h2 := Commit(height) + if !verify(h2) return ErrInvalidHeader(h2) + if !isWithinTrustedPeriod(h2) return ErrHeaderNotWithinTrustedPeriod(h2) + } + + // get the highest trusted headers lower than h2 + h1 = Store.HighestTrustedSmallerThan(height) + if h1 == nil + return ErrNoTrustedHeader + + err = CanTrust(h1, h2, trustlevel) // or CanTrustBisection((h1, h2, trustlevel) + if err != nil return err + + if isWithinTrustedPeriod(h2) { + Store.add(h2) // we store only trusted headers, as we assume that only trusted headers are influencing end user business decisions. + return nil + } + return ErrHeaderNotTrusted(h2) +} + + +// return nil in case we can trust header h2 based on header h1; otherwise return error where error captures the nature of the error. +func CanTrust(h1,h2,trustlevel) error { assume h1.Header.Height < h2.header.Height - ok, err = CheckSupport(h1,h2,trustlevel) - if (ok or err != nil) return (ok, err) + err = CheckSupport(h1,h2,trustlevel) + if err == nil { + Store.Add(h2) + return nil + } + if err != ErrTooMuchChange return err // we cannot verify h2 based on h1, so we try to move trusted header closer to h2 so we can verify h2 th := h1 // th is trusted header - while th.Header.Height <= h2.Header.height - 1 do { - // try to move trusted header forward with stored headers - ih := th + untrustedHeaders := [] + + while true { + endHeight = h2.Header.height + foundPivot = false + while(!foundPivot) { + pivot := (th.Header.height + endHeight) / 2 + hp := Commit(pivot) + if !verify(hp) return ErrInvalidHeader(hp) + // try to move trusted header forward to hp + err = CheckSupport(th,hp,trustlevel) + if (err != nil and err != ErrTooMuchChange) return err + if err == nil { + th = hp + Store.Add(hp) + foundPivot = true + } + untrustedHeaders.add(hp) + endHeight = pivot + } + // try to move trusted header forward - for h in stored headers s.t ih.Header.Height < h.Header.height < h2.Header.height do { + for h in untrustedHeaders { // we assume here that iteration is done in the order of header heights - ok, err = CheckSupport(th,h,trustlevel) - if err != nil { return (ok, err) } - if ok { + err = CheckSupport(th,h,trustlevel) + if (err != nil and err != ErrTooMuchChange) return err + if err == nil { th = h + Store.Add(h) + untrustedHeaders.Remove(h) } } // at this point we have potentially updated th based on stored headers so we try to verify h2 // based on new trusted header - ok, err = CheckSupport(th,h2,trustlevel) - if (ok or err != nil) return (ok, err) - - // we cannot verify h2 based on th, so we try to move trusted header closer to h2 by downloading header(s) between th and h2 - endHeight = h2.Header.height - foundPivot = false - while(!foundPivot) { - pivot := (th.Header.height + endHeight) / 2 - hp := Commit(pivot) - if !verify(hp) return (false, ErrInvalidHeader(hp)) - Store(hp) - - // try to move trusted header forward to hp - ok, err = CheckSupport(th,hp,trustlevel) - if err != nil { return (ok, err) } - if ok { - th = hp - foundPivot = true - } - endHeight = pivot + err = CheckSupport(h1,h2,trustlevel) + if err == nil { + Store.Add(h2) + return nil } + if err != ErrTooMuchChange return err } + return nil // this line should never be reached } ``` ```go -func CanTrustBisection(h1,h2,trustlevel) bool{ - if CheckSupport(h1,h2,trustlevel) { - return true - } - if h2.Header.height == h1.Header.height + 1 { - // we have adjacent headers that are not matching (failed - // the CheckSupport) - // we could submit evidence here - return false +func CanTrustBisection(h1,h2,trustlevel) error { + assume h1.Header.Height < h2.header.Height + + err = CheckSupport(h1,h2,trustlevel) + if err == nil { + Store.Add(h2) + return nil } + if err != ErrTooMuchChange return err + pivot := (h1.Header.height + h2.Header.height) / 2 hp := Commit(pivot) - // ask a full node for header of height pivot - Store(hp) - // store header hp locally - if Bisection(h1,hp,trustlevel) { - // only check right branch if hp is trusted - // (otherwise a lot of unnecessary computation may be done) - return Bisection(hp,h2,trustlevel) - } - else { - return false + if !verify(hp) return ErrInvalidHeader(hp) + + err = CanTrustBisection(h1,hp,trustlevel) + if err == nil { + Store.Add(hp) + err2 = CanTrustBisection(hp,h2,trustlevel) + if err2 == nil { + Store.Add(h2) + return nil + } + return err2 } + return err } ``` @@ -356,8 +386,8 @@ func CanTrustBisection(h1,h2,trustlevel) bool{ *Correctness arguments (sketch)* Lite Client Accuracy: -- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because Bisection returns true. -- Bisection returns true only if all calls to CheckSupport in the recursion return true. +- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil. +- CanTrustBisection returns true only if all calls to CheckSupport in the recursion return nil. - Thus we have a sequence of headers that all satisfied the CheckSupport - again a contradiction @@ -367,7 +397,7 @@ This is only ensured if upon *Commit(pivot)* the lite client is always provided *Stalling* -With Bisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite client eventually detects a problem. There are several ways to address this: +With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite 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 lite 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, Bisection would then be executed at the full node. * We may set a timeout how long bisection may take. @@ -380,111 +410,23 @@ In the use case where someone tells the lite client that application data that i *Remark.* For the case were the lite 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 Backwards(h1,h2) bool { +func Backwards(h1,h2) error { assert (h2.Header.height < h1.Header.height) + if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) + old := h1 for i := h1.Header.height - 1; i > h2.Header.height; i-- { new := Commit(i) - Store(new) if (hash(new) != old.Header.hash) { - return false + return ErrInvalidAdjacentHeaders } old := new + if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) } - return (hash(h2) == old.Header.hash) + if hash(h2) == old.Header.hash return ErrInvalidAdjacentHeaders + return nil } ``` -```go -// return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false -func hasExpired(h) bool { - if now - h.Header.bfttime > tp - Delta - return true // Observation 1 - return false -} -// return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; -// otherwise false. Additional checks should be done in the implementation -// to ensure header is well formed. -func verify(h) bool { - vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h - return votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all -} -// Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`). -// returns (true, nil) in case h2 can be trusted based on h1, (false, nil) in case it cannot be trusted but no errors -// are observed during check and (false, error) in case an error is detected (for example adjacent -// headers are not consistent). -func CheckSupport(h1,h2,trustlevel) (bool, error) { - assume h1.Header.Height < h2.header.Height - - // check for adjacent headers - if h2.Header.height == h1.Header.height + 1 { - if h1.Header.NextV == h2.Header.V return nil - return false, ErrInvalidAdjacentHeaders - } - - // total sum of voting power of validators in h1.NextV - vp_all := totalVotingPower(h1.Header.NextV) - // check for non-adjacent headers - return votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil -} - -// return nil in case we can trust header h2 based on header h1, error otherwise -func Bisection(h1,h2,trustlevel) error { - assume h1.Header.Height < h2.header.Height - - th := h1 - pivot = h2.Header.height - untrustedHeaders = [] - - while h2 not in Store.Headers() { // could be replaced with while true and check if h2 is stored in the loop - // check support for pivot, bisect if no support until a higher trusted header is found - while pivot < h1.Header.height { - hp := Commit(pivot) - if !verify(hp) return ErrInvalidHeader(hp) - done, err = CheckSupport(th,hp,trustlevel) - if err != nil return err - if done { - th = hp - Store.Add(hp) - break - } - untrustedHeaders.add(hp) - pivot := (th.Header.height + endHeight) / 2 - } - - // try to move trusted header forward - for h in untrustedHeaders { - // we assume here that iteration is done in the order of header heights - done, err = CheckSupport(th,h,trustlevel) - if err != nil return err - if done { - th = h - Store.Add(h) - } - } - } - return nil -} - -func VerifyHeader(h2, trustlevel) error { - if h2 in Store.Headers() - return nil - - Store.DisableExpiration() - // get the highest trusted headers lower than h2 - h1 = Store.HighestTrusted(h2.Header.height) - if h1 == nil - return ErrNoTrustedHeader - - err = Bisection(h1, h2, trustlevel) - if err != nil return err - - // remove all expired headers and start the expiration timer - Store.EnableExpiration() - if h2 in Store.Headers() return nil - return ErrHeaderExpired -} - -``` \ No newline at end of file From 4f7c55507cb99c35c7774c583f182859283f3017 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 12 Dec 2019 12:29:12 +0100 Subject: [PATCH 021/223] Address reviewer's comments --- spec/consensus/light-client.md | 176 ++++++++++++++++----------------- 1 file changed, 86 insertions(+), 90 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 1e4ed66f2..ec91c5aa3 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -122,6 +122,7 @@ if *l* has set *trust(h) = true*, Upon initialization, the lite client is given a header *inithead* it trusts (by social consensus). It is assumed that *inithead* satisfies the lite client invariant. (If *inithead* has been correctly generated by Tendermint consensus, the invariant follows from the Tendermint Failure Model.) +Note that the *inithead* should be within its trusted period during initialization. When a lite clients sees a signed new header *snh*, it has to decide whether to trust the new header. Trust can be obtained by (possibly) the combination of three methods. @@ -186,7 +187,9 @@ We consider the following set-up: - the lite client locally stores all the headers that has passed basic verification and that are within lite client trust period. In the pseudo code below we write *Store(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 lite client. - If *CanTrust* returns *error*, then the lite 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 lite client should disconnect and reinitialise with new trusted header. + * In case of forged header, the full node is faulty so lite client should disconnect and reinitialise with new trusted header. If the trusted header has expired, + we need to reinitialise lite 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). **Auxiliary Functions.** We will use the function ```votingpower_in(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; we will write ```totalVotingPower(V)``` for ```votingpower_in(V,V)```, which returns the total voting power in V. @@ -203,28 +206,35 @@ true in case the header is within its lite client trusted period. return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now } - // return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; - // otherwise false. Additional checks should be done in the implementation + // return true if header is correctly signed by 2/3+ voting power in the corresponding + // validator set; otherwise false. Additional checks should be done in the implementation // to ensure header is well formed. func verify(h) bool { vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h return votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all } - // Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`). - // returns nil in case h2 can be trusted based on h1, otherwise returns error. - // ErrHeaderExpired is used to signal that h1 has expired with respect lite client trusted period, - // ErrInvalidAdjacentHeaders that adjacent headers are not consistent and - // ErrTooMuchChange that there is not enough intersection between validator sets to have skipping condition true. - func CheckSupport(h1,h2,trustlevel) error { - assume h1.Header.Height < h2.header.Height and h1.Header.bfttime < h2.Header.bfttime and h2.Header.bfttime < now + // Captures skipping condition. h1 and h2 have already passed basic validation + // (function `verify`). + // Returns nil in case h2 can be trusted based on h1, otherwise returns error. + // ErrHeaderExpired is used when h1 has expired with respect to lite client trusted period, + // ErrInvalidAdjacentHeaders when that adjacent headers are not consistent and + // ErrTooMuchChange when there is not enough intersection between validator sets to have + // skipping condition true. + func CheckSupport(h1,h2,trustThreshold) error { + assume h1.Header.Height < h2.header.Height and + h1.Header.bfttime < h2.Header.bfttime and + h2.Header.bfttime < now - if !isWithinTrustedPeriod(h1) return ErrHeaderNotWithinTrustedPeriod(h1) + if !isWithinTrustedPeriod(h1) return ErrHeaderNotWithinTrustedPeriod(h1) - // Although while executing the rest of CheckSupport function, h1 can expiry based on the lite client trusted period, this is not problem as - // lite client trusted period is smaller than trusted period of the header based on Tendermint Failure model, i.e., there is a significant - // time period (measure in days) during which validator set that has signed h1 can be trusted - // Furthermore, CheckSupport function is not doing expensive operation (neither rpc nor signature verification), so it should execute fast. + // Although while executing the rest of CheckSupport function, h1 can expiry based + // on the lite client trusted period, this is not problem as lite client trusted + // period is smaller than trusted period of the header based on Tendermint Failure + // model, i.e., there is a significant time period (measure in days) during which + // validator set that has signed h1 can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. // total sum of voting power of validators in h1.NextV vp_all := totalVotingPower(h1.Header.NextV) @@ -236,7 +246,9 @@ true in case the header is within its lite client trusted period. return ErrInvalidAdjacentHeaders } // check for non-adjacent headers - if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all return nil + if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } return ErrTooMuchChange } @@ -257,7 +269,7 @@ Towards Lite Client Completeness: *Verification Condition:* We may need a Tendermint invariant stating that if *h2.Header.height = h1.Header.height + 1* then *signers(h2.Commit) \subseteq h1.Header.NextV*. -*Remark*: The variable *trustlevel* 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 *trustlevel* is chosen, the more unlikely it becomes that CheckSupport returns true for non-adjacent headers. +*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 CheckSupport returns true for non-adjacent headers. **VerifyHeader.** The function *VerifyHeader* captures high level logic, i.e., application call to the lite client module to (optionally download) and verify header for some height. The core verification logic is captured by *CanTrust* function that iteratively try to establish trust in given header @@ -265,97 +277,81 @@ by relying on *CheckSupport* function. ```go -func VerifyHeader(height, trustlevel) error { - if h2, exists := Store.Get(height); exists { - if isWithinTrustedPeriod(h2) return nil - return ErrHeaderNotWithinTrustedPeriod(h2) - } - else { - h2 := Commit(height) - if !verify(h2) return ErrInvalidHeader(h2) - if !isWithinTrustedPeriod(h2) return ErrHeaderNotWithinTrustedPeriod(h2) - } +func VerifyHeader(height, trustThreshold) error { + if h2, exists := Store.Get(height); exists { + if isWithinTrustedPeriod(h2) { return nil } + return ErrHeaderNotWithinTrustedPeriod(h2) + } - // get the highest trusted headers lower than h2 - h1 = Store.HighestTrustedSmallerThan(height) - if h1 == nil - return ErrNoTrustedHeader + h2 := Commit(height) + if !verify(h2) { return ErrInvalidHeader(h2) } + if !isWithinTrustedPeriod(h2) { return ErrHeaderNotWithinTrustedPeriod(h2) } - err = CanTrust(h1, h2, trustlevel) // or CanTrustBisection((h1, h2, trustlevel) - if err != nil return err + // get the highest trusted headers lower than h2 + h1 = Store.HighestTrustedSmallerThan(height) + if h1 == nil { return ErrNoTrustedHeader } - if isWithinTrustedPeriod(h2) { - Store.add(h2) // we store only trusted headers, as we assume that only trusted headers are influencing end user business decisions. - return nil - } - return ErrHeaderNotTrusted(h2) + err = CanTrust(h1, h2, trustThreshold) // or CanTrustBisection((h1, h2, trustThreshold) + if err != nil { return err } + + if isWithinTrustedPeriod(h2) { + Store.add(h2) + // we store only trusted headers, as we assume that only trusted headers + // are influencing end user business decisions. + return nil + } + return ErrHeaderNotTrusted(h2) } -// return nil in case we can trust header h2 based on header h1; otherwise return error where error captures the nature of the error. -func CanTrust(h1,h2,trustlevel) error { +// return nil in case we can trust header h2 based on header h1; otherwise return error +// where error captures the nature of the error. +func CanTrust(h1,h2,trustThreshold) error { assume h1.Header.Height < h2.header.Height - err = CheckSupport(h1,h2,trustlevel) - if err == nil { - Store.Add(h2) - return nil - } - if err != ErrTooMuchChange return err - - // we cannot verify h2 based on h1, so we try to move trusted header closer to h2 so we can verify h2 th := h1 // th is trusted header - untrustedHeaders := [] + untrustedHeaders := [h2] while true { - endHeight = h2.Header.height - foundPivot = false - while(!foundPivot) { - pivot := (th.Header.height + endHeight) / 2 - hp := Commit(pivot) - if !verify(hp) return ErrInvalidHeader(hp) - // try to move trusted header forward to hp - err = CheckSupport(th,hp,trustlevel) - if (err != nil and err != ErrTooMuchChange) return err - if err == nil { - th = hp - Store.Add(hp) - foundPivot = true - } - untrustedHeaders.add(hp) - endHeight = pivot - } - - // try to move trusted header forward - for h in untrustedHeaders { - // we assume here that iteration is done in the order of header heights - err = CheckSupport(th,h,trustlevel) - if (err != nil and err != ErrTooMuchChange) return err - if err == nil { - th = h - Store.Add(h) - untrustedHeaders.Remove(h) - } - } - - // at this point we have potentially updated th based on stored headers so we try to verify h2 - // based on new trusted header - err = CheckSupport(h1,h2,trustlevel) + for h in untrustedHeaders { + // we assume here that iteration is done in the order of header heights + err = CheckSupport(th,h,trustThreshold) if err == nil { - Store.Add(h2) - return nil + th = h + Store.Add(h) + untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) + if th == h2 { return nil } } - if err != ErrTooMuchChange return err + if (err != ErrTooMuchChange) { return err } + } + + endHeight = min(untrustedHeaders) + foundPivot = false + while(!foundPivot) { + pivot := (th.Header.height + endHeight) / 2 + hp := Commit(pivot) + if !verify(hp) { return ErrInvalidHeader(hp) } + // try to move trusted header forward to hp + err = CheckSupport(th,hp,trustThreshold) + if (err != nil and err != ErrTooMuchChange) return err + if err == nil { + th = hp + Store.Add(hp) + foundPivot = true + } + untrustedHeaders.add(hp) + endHeight = pivot + } } return nil // this line should never be reached } ``` ```go -func CanTrustBisection(h1,h2,trustlevel) error { +func CanTrustBisection(h1,h2,trustThreshold) error { assume h1.Header.Height < h2.header.Height - err = CheckSupport(h1,h2,trustlevel) + err = CheckSupport(h1,h2,trustThreshold) if err == nil { Store.Add(h2) return nil @@ -366,10 +362,10 @@ func CanTrustBisection(h1,h2,trustlevel) error { hp := Commit(pivot) if !verify(hp) return ErrInvalidHeader(hp) - err = CanTrustBisection(h1,hp,trustlevel) + err = CanTrustBisection(h1,hp,trustThreshold) if err == nil { Store.Add(hp) - err2 = CanTrustBisection(hp,h2,trustlevel) + err2 = CanTrustBisection(hp,h2,trustThreshold) if err2 == nil { Store.Add(h2) return nil @@ -423,7 +419,7 @@ func Backwards(h1,h2) error { old := new if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) } - if hash(h2) == old.Header.hash return ErrInvalidAdjacentHeaders + if hash(h2) != old.Header.hash return ErrInvalidAdjacentHeaders return nil } ``` From ee0cc537b8cb6b7052b39e0b0c84a7c1d03d821a Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 25 Dec 2019 13:58:27 +0100 Subject: [PATCH 022/223] Addressing reviewer's comments --- spec/consensus/light-client.md | 227 +++++++++++++++++++-------------- 1 file changed, 132 insertions(+), 95 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index ec91c5aa3..e1bc86197 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -27,13 +27,16 @@ The correctness of the protocol is based on the assumption that *inithead* was g In the following, only the details of the data structures needed for this specification are given. * header fields - - *height* - - *bfttime*: the chain time when the header (block) was generated - - *V*: validator set containing validators for this block. - - *NextV*: validator set for next block. - - *commit*: evidence that block with height *height* - 1 was committed by a set of validators (canonical commit). We will use ```signers(commit)``` to refer to the set of validators that committed the block. + - *Height* + - *Time*: the chain time when the header (block) was generated + - *ValidatorsHash*: hash of the validators for the current block + - *NextValidatorsHash*: hash of the validators for the next block + - *LastCommitHash*: hash commit from validators from the last block + - *commit*: evidence that block with height *height* - 1 was committed by a set of validators (canonical commit). + We will use ```signers(commit)``` to refer to the set of validators that committed the block. - * signed header fields: contains a header and a *commit* for the current header; a "seen commit". In the Tendermint consensus the "canonical commit" is stored in header *height* + 1. + * signed header fields: contains a header and a *commit* for the current header; a "seen commit". + In Tendermint consensus the "canonical commit" is stored in header *height* + 1. * For each header *h* it has locally stored, the lite client stores whether it trusts *h*. We write *trust(h) = true*, if this is the case. @@ -45,20 +48,42 @@ In the following, only the details of the data structures needed for this specif ### Functions -For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following function over Tendermint RPC: +For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: ```go + // returns signed header: header (with the fields from above) with Commit + // that include signatures of validators that signed the header func Commit(height int64) (SignedHeader, error) - // returns signed header: header (with the fields from - // above) with Commit that include signatures of - // validators that signed the header + // returns validator set for the given height + func Validators(height int64) (ValidatorSet, error) type SignedHeader struct { Header Header Commit Commit } + + type ValidatorSet struct { + Validators []Validator + } + + type Validator struct { + Address Address + VotingPower int64 + } ``` +Furthermore, we assume the following auxiliary functions: +```go + + // returns the validator set for the given validator hash + func validators(validatorsHash []byte) ValidatorSet + + // TODO: define precisely what this functions is supposed to be doing + func signers(commit) []Validator + +``` + + ### Definitions * *TRUSTED_PERIOD*: trusting period @@ -70,7 +95,9 @@ For the purpose of this lite client specification, we assume that the Tendermint ### Tendermint Failure Model -If a block *h* is generated at time *bfttime* (and this time is stored in the block), then a set of validators that hold more than 2/3 of the voting power in h.Header.NextV is correct until time h.Header.bfttime + TRUSTED_PERIOD. +If a block *b* is generated at time *Time* (and this time is stored in the block), then 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```. Formally, \[ @@ -82,8 +109,12 @@ Formally, *Remark*: This failure model might change to a hybrid version that takes heights into account in the future. -The specification in this document considers an implementation of the lite client under this assumption. Issues like *counter-factual signing* and *fork accountability* and *evidence submission* are mechanisms that justify this assumption by incentivizing validators to follow the protocol. -If they don't, and we have more that 1/3 faults, 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 an upcoming document on "Fork accountability". (These safety violations include the lite client wrongly trusting a header, a fork in the blockchain, etc.) +The specification in this document considers an implementation of the lite client under the Tendermint Failure Model. Issues +like *counter-factual signing* and *fork accountability* and *evidence submission* are mechanisms that justify this assumption by +incentivizing validators to follow the protocol. If they don't, and we have more that 1/3 faults, 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 an upcoming document on "Fork accountability". (These safety violations include the lite client wrongly +trusting a header, a fork in the blockchain, etc.) ## Lite Client Trusting Spec @@ -148,6 +179,8 @@ We consider the following use case: This can be used in several settings: - someone tells the lite client that application data that is relevant for it can be read in the block of height *k*. - the lite clients wants the latest state. It asks a full nude for the current height, and uses the response for *k*. + - in case of inter-blockchain communication protocol (IBC) the light client runs on a chain and someone feeds it + signed headers as input and it computes whether it can trust it. ## Details @@ -155,20 +188,21 @@ This can be used in several settings: **Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old validator set *h.Header.NextV*. -When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* is correct, but we only trust the fact that at most 1/3 of them are faulty (more precisely, the faulty ones have at most 1/3 of the total voting power). +When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* 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). ### Functions -The function *CanTrust* checks whether to trust header *h2* based on the trusted header *h1*. It does so by (potentially) -building transitive trust relation between *h1* and *h2*, over some intermediate headers. For example, in case we cannot trust -header *h2* based on the trusted header *h1*, the function *CanTrust* will try to find headers such that we can transition trust -from *h1* over intermediate headers to *h2*. We will give two implementations of *CanTrust*, the one based +The function *CanTrust* checks whether to trust header *untrusted_h* based on the trusted header *trusted_h*. It does so by (potentially) +building transitive trust relation between *trusted_h* and *untrusted_h*, over some intermediate headers. For example, in case we cannot trust +header *untrusted_h* based on the trusted header *trusted_h*, the function *CanTrust* will try to find headers such that we can transition trust +from *trusted_h* over intermediate headers to *untrusted_h*. We will give two implementations of *CanTrust*, the one based on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. Both implementations of *CanTrust* function are based on *CheckSupport* function that implements the skipping conditions under which we can trust a -header *h2* given the trust in the header *h1* as a single step, +header *untrusted_h* given the trust in the header *trusted_h* as a single step, i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting @@ -180,29 +214,30 @@ holds: ```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. -*Assumption*: In the following, we assume that *h2.Header.height > h1.Header.height*. We will quickly discuss the other case in the next section. +*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 lite client communicates with one full node -- the lite client locally stores all the headers that has passed basic verification and that are within lite client trust period. In the pseudo code below we write *Store(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 lite client. +- the lite client locally stores all the headers that has passed basic verification and that are within lite 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 lite 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 lite client should disconnect and reinitialise with new trusted header. If the trusted header has expired, + * In case of forged header, the full node is faulty so lite client should disconnect and reinitialise with new peer. If the trusted header has expired, we need to reinitialise lite 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). -**Auxiliary Functions.** We will use the function ```votingpower_in(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; -we will write ```totalVotingPower(V)``` for ```votingpower_in(V,V)```, which returns the total voting power in V. +**Auxiliary Functions.** We will use the function ```votingPowerIn(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; +we will write ```totalVotingPower(V)``` for ```votingPowerIn(V,V)```, which returns the total voting power in V. We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. -**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header h2 based on header h1. -Time validity of a header is captured by the ```isWithinTrustedPeriodWithin``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns +**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header untrusted_h based on header trusted_h. +Time validity of a header is captured by the ```isWithinTrustedPeriod``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns true in case the header is within its lite client trusted period. ```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. ```go // return true if header is within its lite client trusted period; otherwise it returns false - func isWithinTrustedPeriod(h) bool { + func isWithinTrustedPeriod(h, now) bool { return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now } @@ -211,63 +246,63 @@ true in case the header is within its lite client trusted period. // to ensure header is well formed. func verify(h) bool { vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h - return votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all + return votingPowerIn(signers(h.Commit),h.Header.V) > 2/3 * vp_all } - // Captures skipping condition. h1 and h2 have already passed basic validation + // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation // (function `verify`). - // Returns nil in case h2 can be trusted based on h1, otherwise returns error. - // ErrHeaderExpired is used when h1 has expired with respect to lite client trusted period, + // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. + // ErrHeaderNotWithinTrustedPeriod is used when trusted_h has expired with respect to lite client trusted period, // ErrInvalidAdjacentHeaders when that adjacent headers are not consistent and // ErrTooMuchChange when there is not enough intersection between validator sets to have // skipping condition true. - func CheckSupport(h1,h2,trustThreshold) error { - assume h1.Header.Height < h2.header.Height and - h1.Header.bfttime < h2.Header.bfttime and - h2.Header.bfttime < now + func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now - if !isWithinTrustedPeriod(h1) return ErrHeaderNotWithinTrustedPeriod(h1) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - // Although while executing the rest of CheckSupport function, h1 can expiry based + // Although while executing the rest of CheckSupport function, trusted_h can expire based // on the lite client trusted period, this is not problem as lite client trusted // period is smaller than trusted period of the header based on Tendermint Failure // model, i.e., there is a significant time period (measure in days) during which - // validator set that has signed h1 can be trusted. Furthermore, CheckSupport function + // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function // is not doing expensive operation (neither rpc nor signature verification), so it // should execute fast. - // total sum of voting power of validators in h1.NextV - vp_all := totalVotingPower(h1.Header.NextV) + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } - // check for adjacent headers - if (h2.Header.height == h1.Header.height + 1) { - if h1.Header.NextV == h2.Header.V - return nil - return ErrInvalidAdjacentHeaders - } - // check for non-adjacent headers - if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustThreshold) * vp_all { + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { return nil - } - return ErrTooMuchChange - + } + return ErrTooMuchChange } ``` *Correctness arguments* Towards Lite Client Accuracy: -- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because *CheckSupport* returns true. -- h1 is trusted and sufficiently new -- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed *h2*. -- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing *h2* => *h2* was correctly generated, we arrive at the required contradiction. +- Assume by contradiction that *untrusted_h* was not generated correctly and the lite client sets trust to true because *CheckSupport* returns true. +- trusted_h is trusted and sufficiently new +- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed *untrusted_h*. +- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing *untrusted_h* => *untrusted_h* was correctly generated, we arrive at the required contradiction. Towards Lite Client Completeness: -- The check is successful if sufficiently many validators of *h1* are still validators in *h2* and signed *h2*. -- If *h2.Header.height = h1.Header.height + 1*, and both headers were generated correctly, the test passes +- The check is successful if sufficiently many validators of *trusted_h* are still validators in *untrusted_h* and signed *untrusted_h*. +- If *untrusted_h.Header.height = trusted_h.Header.height + 1*, and both headers were generated correctly, the test passes -*Verification Condition:* We may need a Tendermint invariant stating that if *h2.Header.height = h1.Header.height + 1* then *signers(h2.Commit) \subseteq h1.Header.NextV*. +*Verification Condition:* We may need a Tendermint invariant stating that if *untrusted_h.Header.height = trusted_h.Header.height + 1* then *signers(untrusted_h.Commit) \subseteq trusted_h.Header.NextV*. *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 CheckSupport returns true for non-adjacent headers. @@ -278,39 +313,41 @@ by relying on *CheckSupport* function. ```go func VerifyHeader(height, trustThreshold) error { - if h2, exists := Store.Get(height); exists { - if isWithinTrustedPeriod(h2) { return nil } - return ErrHeaderNotWithinTrustedPeriod(h2) + if untrusted_h, exists := Store.Get(height); exists { + if isWithinTrustedPeriod(untrusted_h) return nil + return ErrHeaderNotWithinTrustedPeriod(untrusted_h) } - h2 := Commit(height) - if !verify(h2) { return ErrInvalidHeader(h2) } - if !isWithinTrustedPeriod(h2) { return ErrHeaderNotWithinTrustedPeriod(h2) } + untrusted_h := Commit(height) + if !verify(untrusted_h) { return ErrInvalidHeader(untrusted_h) } + if !isWithinTrustedPeriod(untrusted_h) { return ErrHeaderNotWithinTrustedPeriod(untrusted_h) } - // get the highest trusted headers lower than h2 - h1 = Store.HighestTrustedSmallerThan(height) - if h1 == nil { return ErrNoTrustedHeader } + // get the highest trusted headers lower than untrusted_h + trusted_h = Store.HighestTrustedSmallerThan(height) + if trusted_h == nil { return ErrNoTrustedHeader } - err = CanTrust(h1, h2, trustThreshold) // or CanTrustBisection((h1, h2, trustThreshold) + err = CanTrust(trusted_h, untrusted_h, trustThreshold) // or CanTrustBisection((trusted_h, untrusted_h, trustThreshold) if err != nil { return err } - if isWithinTrustedPeriod(h2) { - Store.add(h2) + if isWithinTrustedPeriod(untrusted_h) { + Store.add(untrusted_h) // we store only trusted headers, as we assume that only trusted headers // are influencing end user business decisions. return nil } - return ErrHeaderNotTrusted(h2) + return ErrHeaderNotTrusted(untrusted_h) } -// return nil in case we can trust header h2 based on header h1; otherwise return error +// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error // where error captures the nature of the error. -func CanTrust(h1,h2,trustThreshold) error { - assume h1.Header.Height < h2.header.Height +// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. +func CanTrust(trusted_h,untrusted_h,trustThreshold) error { + assume trusted_h.Header.Height < untrusted_h.header.Height - th := h1 // th is trusted header - untrustedHeaders := [h2] + th := trusted_h // th is trusted header + // untrustedHeader is a list of verified headers that have not passed CheckSupport() + untrustedHeaders := [untrusted_h] while true { for h in untrustedHeaders { @@ -320,7 +357,7 @@ func CanTrust(h1,h2,trustThreshold) error { th = h Store.Add(h) untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if th == h2 { return nil } + if th == untrusted_h { return nil } } if (err != ErrTooMuchChange) { return err } } @@ -348,26 +385,26 @@ func CanTrust(h1,h2,trustThreshold) error { ``` ```go -func CanTrustBisection(h1,h2,trustThreshold) error { - assume h1.Header.Height < h2.header.Height +func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { + assume trusted_h.Header.Height < untrusted_h.header.Height - err = CheckSupport(h1,h2,trustThreshold) + err = CheckSupport(trusted_h,untrusted_h,trustThreshold) if err == nil { - Store.Add(h2) + Store.Add(untrusted_h) return nil } if err != ErrTooMuchChange return err - pivot := (h1.Header.height + h2.Header.height) / 2 + pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 hp := Commit(pivot) if !verify(hp) return ErrInvalidHeader(hp) - err = CanTrustBisection(h1,hp,trustThreshold) + err = CanTrustBisection(trusted_h,hp,trustThreshold) if err == nil { Store.Add(hp) - err2 = CanTrustBisection(hp,h2,trustThreshold) + err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) if err2 == nil { - Store.Add(h2) + Store.Add(untrusted_h) return nil } return err2 @@ -382,7 +419,7 @@ func CanTrustBisection(h1,h2,trustThreshold) error { *Correctness arguments (sketch)* Lite Client Accuracy: -- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil. +- Assume by contradiction that *untrusted_h* was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil. - CanTrustBisection returns true only if all calls to CheckSupport in the recursion return nil. - Thus we have a sequence of headers that all satisfied the CheckSupport - again a contradiction @@ -399,27 +436,27 @@ With CanTrustBisection, a faulty full node could stall a lite client by creating * We may set a timeout how long bisection may take. -### The case *h2.Header.height < h1.Header.height* +### The case *untrusted_h.Header.height < trusted_h.Header.height* In the use case where someone tells the lite client that application data that is relevant for it can be read in the block of height *k* and the lite 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 lite 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 Backwards(h1,h2) error { - assert (h2.Header.height < h1.Header.height) - if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) +func Backwards(trusted_h,untrusted_h) error { + assert (untrusted_h.Header.height < trusted_h.Header.height) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - old := h1 - for i := h1.Header.height - 1; i > h2.Header.height; i-- { + old := trusted_h + for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { new := Commit(i) if (hash(new) != old.Header.hash) { return ErrInvalidAdjacentHeaders } old := new - if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) } - if hash(h2) != old.Header.hash return ErrInvalidAdjacentHeaders + if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders return nil } ``` From 0adde9d415c398659c40cd9f22ef07d10c0ae5a3 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 26 Dec 2019 13:11:01 +0100 Subject: [PATCH 023/223] Separating algorithm from proofs --- spec/consensus/light-client.md | 617 +++++++++++++++++---------------- 1 file changed, 323 insertions(+), 294 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index e1bc86197..89ec5ec7e 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -1,24 +1,16 @@ # Lite client -A lite client is a process that connects to Tendermint full nodes and then tries to verify application data using the Merkle proofs. - -## Context of this document - -In order to make sure that full nodes have the incentive to follow the protocol, we have to address the following three Issues - -1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. - -2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). - -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). +A lite client is a process that connects to Tendermint full node(s) and then tries to verify application +data using the Merkle proofs. ## Problem statement +We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because +the lite 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*. -We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because the lite 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. The term "trusting" above indicates that the correctness of the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged *inithead* is negligible. - +The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of +Tendermint consensus. ## Definitions @@ -26,63 +18,340 @@ The correctness of the protocol is based on the assumption that *inithead* was g In the following, only the details of the data structures needed for this specification are given. - * header fields - - *Height* - - *Time*: the chain time when the header (block) was generated - - *ValidatorsHash*: hash of the validators for the current block - - *NextValidatorsHash*: hash of the validators for the next block - - *LastCommitHash*: hash commit from validators from the last block - - *commit*: evidence that block with height *height* - 1 was committed by a set of validators (canonical commit). - We will use ```signers(commit)``` to refer to the set of validators that committed the block. + ```go + type Header struct { + Height int64 + Time Time // the chain time when the header (block) was generated - * signed header fields: contains a header and a *commit* for the current header; a "seen commit". - In Tendermint consensus the "canonical commit" is stored in header *height* + 1. + // hashes from the app output from the prev block + ValidatorsHash []byte // hash of the validators for the current block + NextValidatorsHash []byte // hash of the validators for the next block - * For each header *h* it has locally stored, the lite client stores whether - it trusts *h*. We write *trust(h) = true*, if this is the case. + // hashes of block data + LastCommitHash []byte // hash of the commit from validators from the last block + ... + } - * Validator fields. We will write a validator as a tuple *(v,p)* such that - + *v* is the identifier (we assume identifiers are unique in each validator set) - + *p* is its voting power + type SignedHeader struct { + Header Header + Commit Commit // commit for the given header + } + type ValidatorSet struct { + Validators []Validator + } + + type Validator struct { + Address Address // validator address (we assume validator's addresses are unique) + VotingPower int64 // validator's voting power + } + ``` ### Functions For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: ```go - // returns signed header: header (with the fields from above) with Commit - // that include signatures of validators that signed the header + // returns signed header: Header with Commit func Commit(height int64) (SignedHeader, error) // returns validator set for the given height func Validators(height int64) (ValidatorSet, error) - - type SignedHeader struct { - Header Header - Commit Commit - } - - type ValidatorSet struct { - Validators []Validator - } - - type Validator struct { - Address Address - VotingPower int64 - } ``` Furthermore, we assume the following auxiliary functions: ```go - // returns the validator set for the given validator hash func validators(validatorsHash []byte) ValidatorSet - // TODO: define precisely what this functions is supposed to be doing - func signers(commit) []Validator + // returns the set of validators from the given validator set that committed the block + func signers(commit Commit, validatorSet ValidatorSet) []Validator ``` +### Tendermint Failure Model + +If a block `b` is generated at time `Time` (and this time is stored in the block), then 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](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 between clocks and +BFT time. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, +we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and +`TRUSTED_PERIOD` is in the order of weeks. + +*Remark*: This failure model might change to a hybrid version that takes heights into account in the future. + +The specification in this document considers an implementation of the lite client under the Tendermint Failure Model. Issues +like `counter-factual signing`, `fork accountability` and `evidence submission` are mechanisms that justify this assumption by +incentivizing validators to follow the protocol. If they don't, and we have 1/3 (or more) faults, 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](fork-accountability.md). + +### Functions + +**VerifyHeader.** The function `VerifyHeader` captures high level logic, i.e., application call to the lite client module to (optionally download) and +verify header for some height. The core verification logic is captured by `CanTrust` function that iteratively try to establish trust in given header +by relying on `CheckSupport` function. + +```go +func VerifyHeader(height, trustThreshold) error { + if untrusted_h, exists := Store.Get(height); exists { + if isWithinTrustedPeriod(untrusted_h) return nil + return ErrHeaderNotWithinTrustedPeriod(untrusted_h) + } + + untrusted_h := Commit(height) + if !verify(untrusted_h) { return ErrInvalidHeader(untrusted_h) } + if !isWithinTrustedPeriod(untrusted_h) { return ErrHeaderNotWithinTrustedPeriod(untrusted_h) } + + // get the highest trusted headers lower than untrusted_h + trusted_h = Store.HighestTrustedSmallerThan(height) + if trusted_h == nil { return ErrNoTrustedHeader } + + err = CanTrust(trusted_h, untrusted_h, trustThreshold) // or CanTrustBisection((trusted_h, untrusted_h, trustThreshold) + if err != nil { return err } + + if isWithinTrustedPeriod(untrusted_h) { + Store.add(untrusted_h) + // we store only trusted headers, as we assume that only trusted headers + // are influencing end user business decisions. + return nil + } + return ErrHeaderNotTrusted(untrusted_h) +} +``` + +The function `CanTrust` checks whether to trust header `untrusted_h` based on the trusted header `trusted_h` It does so by (potentially) +building transitive trust relation between `trusted_h` and `untrusted_h`, over some intermediate headers. For example, in case we cannot trust +header `untrusted_h` based on the trusted header `trusted_h`, the function `CanTrust` will try to find headers such that we can transition trust +from `trusted_h` over intermediate headers to `untrusted_h`. We will give two implementations of `CanTrust`, the one based +on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier +to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. + +Both implementations of `CanTrust` function are based on `CheckSupport` function that implements the skipping conditions under which we can trust a +header `untrusted_h` given the trust in the header `trusted_h` as a single step, +i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. + + +```go +// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error +// where error captures the nature of the error. +// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. +func CanTrust(trusted_h,untrusted_h,trustThreshold) error { + assume trusted_h.Header.Height < untrusted_h.header.Height + + th := trusted_h // th is trusted header + // untrustedHeader is a list of verified headers that have not passed CheckSupport() + untrustedHeaders := [untrusted_h] + + while true { + for h in untrustedHeaders { + // we assume here that iteration is done in the order of header heights + err = CheckSupport(th,h,trustThreshold) + if err == nil { + th = h + Store.Add(h) + untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) + if th == untrusted_h { return nil } + } + if (err != ErrTooMuchChange) { return err } + } + + endHeight = min(untrustedHeaders) + foundPivot = false + while(!foundPivot) { + pivot := (th.Header.height + endHeight) / 2 + hp := Commit(pivot) + if !verify(hp) { return ErrInvalidHeader(hp) } + // try to move trusted header forward to hp + err = CheckSupport(th,hp,trustThreshold) + if (err != nil and err != ErrTooMuchChange) return err + if err == nil { + th = hp + Store.Add(hp) + foundPivot = true + } + untrustedHeaders.add(hp) + endHeight = pivot + } + } + return nil // this line should never be reached +} +``` + +```go +func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { + assume trusted_h.Header.Height < untrusted_h.header.Height + + err = CheckSupport(trusted_h,untrusted_h,trustThreshold) + if err == nil { + Store.Add(untrusted_h) + return nil + } + if err != ErrTooMuchChange return err + + pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 + hp := Commit(pivot) + if !verify(hp) return ErrInvalidHeader(hp) + + err = CanTrustBisection(trusted_h,hp,trustThreshold) + if err == nil { + Store.Add(hp) + err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) + if err2 == nil { + Store.Add(untrusted_h) + return nil + } + return err2 + } + return err +} +``` + +**Auxiliary Functions.** We will use the function ```votingPowerIn(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; +we will write ```totalVotingPower(V)``` for ```votingPowerIn(V,V)```, which returns the total voting power in V. +We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. + +**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header untrusted_h based on header trusted_h. +Time validity of a header is captured by the ```isWithinTrustedPeriod``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns +true in case the header is within its lite client trusted period. +```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. + +```go + // return true if header is within its lite client trusted period; otherwise it returns false + func isWithinTrustedPeriod(h, now) bool { + return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now + } + + // return true if header is correctly signed by 2/3+ voting power in the corresponding + // validator set; otherwise false. Additional checks should be done in the implementation + // to ensure header is well formed. + func verify(h) bool { + vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h + return votingPowerIn(signers(h.Commit),h.Header.V) > 2/3 * vp_all + } + + // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation + // (function `verify`). + // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. + // ErrHeaderNotWithinTrustedPeriod is used when trusted_h has expired with respect to lite client trusted period, + // ErrInvalidAdjacentHeaders when that adjacent headers are not consistent and + // ErrTooMuchChange when there is not enough intersection between validator sets to have + // skipping condition true. + func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now + + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + + // Although while executing the rest of CheckSupport function, trusted_h can expire based + // on the lite client trusted period, this is not problem as lite client trusted + // period is smaller than trusted period of the header based on Tendermint Failure + // model, i.e., there is a significant time period (measure in days) during which + // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. + + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange + } +``` + + +### The case `untrusted_h.Header.height < trusted_h.Header.height` + +In the use case where someone tells the lite client that application data that is relevant for it +can be read in the block of height `k` and the lite 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 lite 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 Backwards(trusted_h,untrusted_h) error { + assert (untrusted_h.Header.height < trusted_h.Header.height) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) + + old := trusted_h + for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { + new := Commit(i) + if (hash(new) != old.Header.hash) { + return ErrInvalidAdjacentHeaders + } + old := new + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) + } + if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders + return nil + } +``` + + + +In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting +headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability +protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always +operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound +for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula +holds: +```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. + + +*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 lite client communicates with one full node +- the lite client locally stores all the headers that has passed basic verification and that are within lite 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 lite 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 lite client should disconnect and reinitialise with new peer. If the trusted header has expired, + we need to reinitialise lite 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). + + + +## Context of this document + +In order to make sure that full nodes have the incentive to follow the protocol, we have to address the +following three Issues + +1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. + +2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). + +3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). + +The term "trusting" above indicates that the correctness of the protocol depends on +this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk +of trusting a corrupted/forged *inithead* is negligible. + +* For each header *h* it has locally stored, the lite client stores whether + it trusts *h*. We write *trust(h) = true*, if this is the case. + +* signed header fields: contains a header and a *commit* for the current header; a "seen commit". + In Tendermint consensus the "canonical commit" is stored in header *height* + 1. + + + * Validator fields. We will write a validator as a tuple *(v,p)* such that + + *v* is the identifier (we assume identifiers are unique in each validator set) + + *p* is its voting power ### Definitions @@ -91,8 +360,6 @@ Furthermore, we assume the following auxiliary functions: follows the protocol until time *t* (we will see about recovery later). - - ### Tendermint Failure Model If a block *b* is generated at time *Time* (and this time is stored in the block), then a set of validators that @@ -105,17 +372,6 @@ Formally, 2/3 \sum_{(v,p) \in h.Header.NextV} p \] -*Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), while *bfttime* corresponds to the reading of the local clock of a validator (how this time is computed may change when the Tendermint consensus is modified). In this note, we assume that all clocks are synchronized to realtime. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and *tp* is in the order of weeks. - -*Remark*: This failure model might change to a hybrid version that takes heights into account in the future. - -The specification in this document considers an implementation of the lite client under the Tendermint Failure Model. Issues -like *counter-factual signing* and *fork accountability* and *evidence submission* are mechanisms that justify this assumption by -incentivizing validators to follow the protocol. If they don't, and we have more that 1/3 faults, 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 an upcoming document on "Fork accountability". (These safety violations include the lite client wrongly -trusting a header, a fork in the blockchain, etc.) - ## Lite Client Trusting Spec @@ -192,234 +448,31 @@ When we say we trust *h.Header.NextV* we do *not* trust that each individual val 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). -### Functions - -The function *CanTrust* checks whether to trust header *untrusted_h* based on the trusted header *trusted_h*. It does so by (potentially) -building transitive trust relation between *trusted_h* and *untrusted_h*, over some intermediate headers. For example, in case we cannot trust -header *untrusted_h* based on the trusted header *trusted_h*, the function *CanTrust* will try to find headers such that we can transition trust -from *trusted_h* over intermediate headers to *untrusted_h*. We will give two implementations of *CanTrust*, the one based -on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier -to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. - -Both implementations of *CanTrust* function are based on *CheckSupport* function that implements the skipping conditions under which we can trust a -header *untrusted_h* given the trust in the header *trusted_h* as a single step, -i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. - -In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting -headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability -protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always -operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound -for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula -holds: -```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. - - -*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 lite client communicates with one full node -- the lite client locally stores all the headers that has passed basic verification and that are within lite 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 lite 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 lite client should disconnect and reinitialise with new peer. If the trusted header has expired, - we need to reinitialise lite 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). - -**Auxiliary Functions.** We will use the function ```votingPowerIn(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; -we will write ```totalVotingPower(V)``` for ```votingPowerIn(V,V)```, which returns the total voting power in V. -We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. - -**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header untrusted_h based on header trusted_h. -Time validity of a header is captured by the ```isWithinTrustedPeriod``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns -true in case the header is within its lite client trusted period. -```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. - -```go - // return true if header is within its lite client trusted period; otherwise it returns false - func isWithinTrustedPeriod(h, now) bool { - return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now - } - - // return true if header is correctly signed by 2/3+ voting power in the corresponding - // validator set; otherwise false. Additional checks should be done in the implementation - // to ensure header is well formed. - func verify(h) bool { - vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h - return votingPowerIn(signers(h.Commit),h.Header.V) > 2/3 * vp_all - } - - // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation - // (function `verify`). - // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. - // ErrHeaderNotWithinTrustedPeriod is used when trusted_h has expired with respect to lite client trusted period, - // ErrInvalidAdjacentHeaders when that adjacent headers are not consistent and - // ErrTooMuchChange when there is not enough intersection between validator sets to have - // skipping condition true. - func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - - // Although while executing the rest of CheckSupport function, trusted_h can expire based - // on the lite client trusted period, this is not problem as lite client trusted - // period is smaller than trusted period of the header based on Tendermint Failure - // model, i.e., there is a significant time period (measure in days) during which - // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. - - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders - } - - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) - - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange - } -``` *Correctness arguments* Towards Lite Client Accuracy: -- Assume by contradiction that *untrusted_h* was not generated correctly and the lite client sets trust to true because *CheckSupport* returns true. +- Assume by contradiction that `untrusted_h` was not generated correctly and the lite client sets trust to true because `CheckSupport` returns true. - trusted_h is trusted and sufficiently new -- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed *untrusted_h*. -- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing *untrusted_h* => *untrusted_h* was correctly generated, we arrive at the required contradiction. +- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed `untrusted_h`. +- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing `untrusted_h` => `untrusted_h` was correctly generated, we arrive at the required contradiction. Towards Lite Client Completeness: -- The check is successful if sufficiently many validators of *trusted_h* are still validators in *untrusted_h* and signed *untrusted_h*. +- The check is successful if sufficiently many validators of `trusted_h` are still validators in `untrusted_h` and signed `untrusted_h`. - If *untrusted_h.Header.height = trusted_h.Header.height + 1*, and both headers were generated correctly, the test passes *Verification Condition:* We may need a Tendermint invariant stating that if *untrusted_h.Header.height = trusted_h.Header.height + 1* then *signers(untrusted_h.Commit) \subseteq trusted_h.Header.NextV*. *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 CheckSupport returns true for non-adjacent headers. -**VerifyHeader.** The function *VerifyHeader* captures high level logic, i.e., application call to the lite client module to (optionally download) and -verify header for some height. The core verification logic is captured by *CanTrust* function that iteratively try to establish trust in given header -by relying on *CheckSupport* function. - - -```go -func VerifyHeader(height, trustThreshold) error { - if untrusted_h, exists := Store.Get(height); exists { - if isWithinTrustedPeriod(untrusted_h) return nil - return ErrHeaderNotWithinTrustedPeriod(untrusted_h) - } - - untrusted_h := Commit(height) - if !verify(untrusted_h) { return ErrInvalidHeader(untrusted_h) } - if !isWithinTrustedPeriod(untrusted_h) { return ErrHeaderNotWithinTrustedPeriod(untrusted_h) } - - // get the highest trusted headers lower than untrusted_h - trusted_h = Store.HighestTrustedSmallerThan(height) - if trusted_h == nil { return ErrNoTrustedHeader } - - err = CanTrust(trusted_h, untrusted_h, trustThreshold) // or CanTrustBisection((trusted_h, untrusted_h, trustThreshold) - if err != nil { return err } - - if isWithinTrustedPeriod(untrusted_h) { - Store.add(untrusted_h) - // we store only trusted headers, as we assume that only trusted headers - // are influencing end user business decisions. - return nil - } - return ErrHeaderNotTrusted(untrusted_h) -} - - -// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error -// where error captures the nature of the error. -// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. -func CanTrust(trusted_h,untrusted_h,trustThreshold) error { - assume trusted_h.Header.Height < untrusted_h.header.Height - - th := trusted_h // th is trusted header - // untrustedHeader is a list of verified headers that have not passed CheckSupport() - untrustedHeaders := [untrusted_h] - - while true { - for h in untrustedHeaders { - // we assume here that iteration is done in the order of header heights - err = CheckSupport(th,h,trustThreshold) - if err == nil { - th = h - Store.Add(h) - untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if th == untrusted_h { return nil } - } - if (err != ErrTooMuchChange) { return err } - } - - endHeight = min(untrustedHeaders) - foundPivot = false - while(!foundPivot) { - pivot := (th.Header.height + endHeight) / 2 - hp := Commit(pivot) - if !verify(hp) { return ErrInvalidHeader(hp) } - // try to move trusted header forward to hp - err = CheckSupport(th,hp,trustThreshold) - if (err != nil and err != ErrTooMuchChange) return err - if err == nil { - th = hp - Store.Add(hp) - foundPivot = true - } - untrustedHeaders.add(hp) - endHeight = pivot - } - } - return nil // this line should never be reached -} -``` - -```go -func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { - assume trusted_h.Header.Height < untrusted_h.header.Height - - err = CheckSupport(trusted_h,untrusted_h,trustThreshold) - if err == nil { - Store.Add(untrusted_h) - return nil - } - if err != ErrTooMuchChange return err - - pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 - hp := Commit(pivot) - if !verify(hp) return ErrInvalidHeader(hp) - - err = CanTrustBisection(trusted_h,hp,trustThreshold) - if err == nil { - Store.Add(hp) - err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) - if err2 == nil { - Store.Add(untrusted_h) - return nil - } - return err2 - } - return err -} -``` - *Correctness arguments (sketch)* Lite Client Accuracy: -- Assume by contradiction that *untrusted_h* was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil. +- Assume by contradiction that `untrusted_h` was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil. - CanTrustBisection returns true only if all calls to CheckSupport in the recursion return nil. - Thus we have a sequence of headers that all satisfied the CheckSupport - again a contradiction @@ -436,30 +489,6 @@ With CanTrustBisection, a faulty full node could stall a lite client by creating * We may set a timeout how long bisection may take. -### The case *untrusted_h.Header.height < trusted_h.Header.height* - -In the use case where someone tells the lite client that application data that is relevant for it can be read in the block of height *k* and the lite 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 lite 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 Backwards(trusted_h,untrusted_h) error { - assert (untrusted_h.Header.height < trusted_h.Header.height) - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - - old := trusted_h - for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { - new := Commit(i) - if (hash(new) != old.Header.hash) { - return ErrInvalidAdjacentHeaders - } - old := new - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - } - if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders - return nil - } -``` From 4a9eb1f1acf99b73953005ad01e20e8a08b52219 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Tue, 31 Dec 2019 13:31:35 +0100 Subject: [PATCH 024/223] Intermediate commit (aligning spec with the code) --- spec/consensus/light-client.md | 280 +++++++++++++++++++++++++++------ 1 file changed, 230 insertions(+), 50 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 89ec5ec7e..883ac0090 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -29,7 +29,6 @@ In the following, only the details of the data structures needed for this specif // hashes of block data LastCommitHash []byte // hash of the commit from validators from the last block - ... } type SignedHeader struct { @@ -38,13 +37,19 @@ In the following, only the details of the data structures needed for this specif } type ValidatorSet struct { - Validators []Validator + 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 @@ -66,6 +71,16 @@ Furthermore, we assume the following auxiliary functions: // returns the set of validators from the given validator set that committed the block func signers(commit Commit, validatorSet ValidatorSet) []Validator + // return the voting power the validators in v1 have according to their voting power in set V2 + func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 + + // add this state as trusted to the store + func add(store Store, trustedState TrustedState) error + + // retrieve the trusted state at given height if it exists (error = nil) + // return an error if there are no trusted state for the given height + // if height = 0, return the latest trusted state + func get(store Store, height int64) (TrustedState, error) ``` ### Tendermint Failure Model @@ -91,38 +106,185 @@ This is discussed in document on [Fork accountability](fork-accountability.md). ### Functions -**VerifyHeader.** The function `VerifyHeader` captures high level logic, i.e., application call to the lite client module to (optionally download) and -verify header for some height. The core verification logic is captured by `CanTrust` function that iteratively try to establish trust in given header -by relying on `CheckSupport` function. +**VerifyAndUpdateSingle.** The function `VerifyAndUpdateSingle` attempts to update +the (trusted) store with the given untrusted header and the corresponding validator sets. +It ensures that the last trusted header from the store hasn't expired yet (it is still within its trusted period), +and that the untrusted header can be verified using the latest trusted state from the store. +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 VerifyHeader(height, trustThreshold) error { - if untrusted_h, exists := Store.Get(height); exists { - if isWithinTrustedPeriod(untrusted_h) return nil - return ErrHeaderNotWithinTrustedPeriod(untrusted_h) +func VerifyAndUpdateSingle(untrustedSh SignedHeader, + untrustedVs ValidatorSet, + untrustedNextVs ValidatorSet, + trustThreshold TrustThreshold, + trustingPeriod Duration, + now Time, + store Store) error { + + // fetch the latest state and ensure it hasn't expired + trustedState, error = get(store, 0) + if error != nil return error + + trustedSh = trustedState.SignedHeader + if !isWithinTrustedPeriod(trustedSh.Header, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if error != nil return error + + // the untrusted header is now trusted. update the store + newTrustedState = TrustedState(untrustedSh, untrustedNextVs) + return add(store, newTrustedState) +} + +``` + +**VerifyAndUpdateBisection.** The function `VerifyAndUpdateBisection` captures high level logic, i.e., application call +to the lite client module to (optionally download) and verify header for some height. The core verification logic is +captured by `CanTrust` function that iteratively try to establish trust in given header by relying on `CheckSupport` function. + +```go +func VerifyAndUpdateBisection(height int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + now Time, + store Store) error { + + // fetch the latest state and ensure it hasn't expired + trustedState, error = get(store, 0) + if error != nil return error + + trustedSh = trustedState.SignedHeader + assert trustedSh.Header.Height < height + + if !isWithinTrustedPeriod(trustedSh.Header, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod } - untrusted_h := Commit(height) - if !verify(untrusted_h) { return ErrInvalidHeader(untrusted_h) } - if !isWithinTrustedPeriod(untrusted_h) { return ErrHeaderNotWithinTrustedPeriod(untrusted_h) } + untrustedSh, error := Commit(height) + if error != nil return error - // get the highest trusted headers lower than untrusted_h - trusted_h = Store.HighestTrustedSmallerThan(height) - if trusted_h == nil { return ErrNoTrustedHeader } + untrustedH = untrustedSh.Header - err = CanTrust(trusted_h, untrusted_h, trustThreshold) // or CanTrustBisection((trusted_h, untrusted_h, trustThreshold) - if err != nil { return err } + untrustedVs, error := Validators(untrustedH.Height) + if error != nil return error - if isWithinTrustedPeriod(untrusted_h) { - Store.add(untrusted_h) - // we store only trusted headers, as we assume that only trusted headers - // are influencing end user business decisions. - return nil + untrustedNextVs, error := Validators(untrustedH.Height + 1) + if error != nil return error + + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if fatalCheckSupportError(error) return error + + if error == nil { + // the untrusted header is now trusted. update the store + newTrustedState = TrustedState(untrustedSh, untrustedNextVs) + return add(store, newTrustedState) } - return ErrHeaderNotTrusted(untrusted_h) + + // at this point in time we need to do bisection + pivotHeight := ceil((trustedSh.Header.Height + untrustedH.Height) / 2) + + error = VerifyAndUpdateBisection(pivotHeight, trustThreshold, trustingPeriod, now, store) + if error != nil return error + + error = VerifyAndUpdateBisection(untrustedH.Height, trustThreshold, trustingPeriod, now, store) + if error != nil return error + return nil } ``` +```go +func verifySingle(trustedState TrustedState, + untrustedSh SignedHeader, + untrustedVs ValidatorSet, + untrustedNextVs ValidatorSet, + trustThreshold TrustThreshold) error { + + // ensure the new height is higher + untrustedHeight = untrustedSh.Header.Height + trustedHeight = trustedState.SignedHeader.Header.Height + assert untrustedHeight > trustedHeight + + // validate the untrusted header against its commit, vals, and next_vals + untrustedHeader = untrustedSh.Header + untrustedCommit = untrustedSh.Commit + + error = validateHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs) + if error != nil return error + + trustedHeader = trustedState.SignedHeader.Header + trustedVs = trustedState.ValidatorSet + + // check for adjacent headers + if untrustedHeight == trustedHeight + 1 { + if trustedHeader.NextValidatorsHash != untrustedHeader.ValidatorsHash { + return ErrInvalidAdjacentHeaders + } + } else { + error = verifyCommitTrusting(trustedVs, untrustedCommit, trustThreshold) + if error != nil return error + } + + // verify the untrusted commit + return verifyCommitFull(untrustedVs, untrustedCommit) +} + +func validateHeaderAndVals(signedHeader SignedHeader, vs ValidatorSet, nextVs ValidatorSet) error { + if hash(nextVs) != signedHeader.Header.NextValidatorsHash or + hash(vs) != signedHeader.Header.ValidatorsHash or + !matchingCommit(signedHeader) { // commit corresponds to the header + return error + } +} + +func verifyCommitFull(vs ValidatorSet, commit Commit) error { + totalPower := vs.TotalVotingPower; + signed_power := votingPowerIn(signers(commit, vs), vs) + + // check the signers account for +2/3 of the voting power + if signed_power * 3 <= total_power * 2 return error + return nil +} + +func verifyCommitTrusting(vs ValidatorSet, commit Commit, trustLevel TrustThreshold) error { + totalPower := vs.TotalVotingPower; + signed_power := votingPowerIn(signers(commit, vs), vs) + + // check the signers account for more than max(1/3, trustLevel) of the voting power + if signed_power <= max(1/3, trustLevel) * totalPower return error + return nil +} + + +// return true if header is within its lite client trusted period; otherwise it returns false +func isWithinTrustedPeriod(header Header, + trustingPeriod Duration, + now Time) bool { + + return header.Time + trustedPeriod > now +} + +func fatalCheckSupportError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders +} +``` + + + The function `CanTrust` checks whether to trust header `untrusted_h` based on the trusted header `trusted_h` It does so by (potentially) building transitive trust relation between `trusted_h` and `untrusted_h`, over some intermediate headers. For example, in case we cannot trust header `untrusted_h` based on the trusted header `trusted_h`, the function `CanTrust` will try to find headers such that we can transition trust @@ -140,10 +302,10 @@ i.e., it does not assume ensuring transitive trust relation between headers thro // where error captures the nature of the error. // Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. func CanTrust(trusted_h,untrusted_h,trustThreshold) error { - assume trusted_h.Header.Height < untrusted_h.header.Height + assert trusted_h.Header.Height < untrusted_h.header.Height th := trusted_h // th is trusted header - // untrustedHeader is a list of verified headers that have not passed CheckSupport() + // untrustedHeader is a list of (?) verified headers that have not passed CheckSupport() untrustedHeaders := [untrusted_h] while true { @@ -151,27 +313,27 @@ func CanTrust(trusted_h,untrusted_h,trustThreshold) error { // we assume here that iteration is done in the order of header heights err = CheckSupport(th,h,trustThreshold) if err == nil { + if !verify(h) { return ErrInvalidHeader(h) } th = h Store.Add(h) untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) if th == untrusted_h { return nil } } - if (err != ErrTooMuchChange) { return err } + if fatalCheckSupportError(err) { return err } } endHeight = min(untrustedHeaders) - foundPivot = false - while(!foundPivot) { - pivot := (th.Header.height + endHeight) / 2 + while true { + pivot := ceil((th.Header.height + endHeight) / 2) hp := Commit(pivot) - if !verify(hp) { return ErrInvalidHeader(hp) } // try to move trusted header forward to hp err = CheckSupport(th,hp,trustThreshold) - if (err != nil and err != ErrTooMuchChange) return err + if fatalCheckSupportError(err) return err if err == nil { + if !verify(hp) { return ErrInvalidHeader(hp) } th = hp - Store.Add(hp) - foundPivot = true + Store.Add(th) + break } untrustedHeaders.add(hp) endHeight = pivot @@ -179,6 +341,41 @@ func CanTrust(trusted_h,untrusted_h,trustThreshold) error { } return nil // this line should never be reached } + +func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now + + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + + // Although while executing the rest of CheckSupport function, trusted_h can expire based + // on the lite client trusted period, this is not problem as lite client trusted + // period is smaller than trusted period of the header based on Tendermint Failure + // model, i.e., there is a significant time period (measure in days) during which + // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. + + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange +} + +func fatalCheckSupportError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders ``` ```go @@ -210,29 +407,12 @@ func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { } ``` -**Auxiliary Functions.** We will use the function ```votingPowerIn(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2; -we will write ```totalVotingPower(V)``` for ```votingPowerIn(V,V)```, which returns the total voting power in V. -We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. - **CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header untrusted_h based on header trusted_h. Time validity of a header is captured by the ```isWithinTrustedPeriod``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns true in case the header is within its lite client trusted period. ```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. ```go - // return true if header is within its lite client trusted period; otherwise it returns false - func isWithinTrustedPeriod(h, now) bool { - return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now - } - - // return true if header is correctly signed by 2/3+ voting power in the corresponding - // validator set; otherwise false. Additional checks should be done in the implementation - // to ensure header is well formed. - func verify(h) bool { - vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h - return votingPowerIn(signers(h.Commit),h.Header.V) > 2/3 * vp_all - } - // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation // (function `verify`). // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. From 7130c2e68c125f49a3e705597739212dbd93146f Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Mon, 6 Jan 2020 18:30:59 +0100 Subject: [PATCH 025/223] Removing Store from API and providing end-to-end timing guarantees --- spec/consensus/light-client.md | 525 +++++++--------- spec/consensus/non-recursive-light-client.md | 612 +++++++++++++++++++ 2 files changed, 826 insertions(+), 311 deletions(-) create mode 100644 spec/consensus/non-recursive-light-client.md diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 883ac0090..434c0b721 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -32,8 +32,8 @@ In the following, only the details of the data structures needed for this specif } type SignedHeader struct { - Header Header - Commit Commit // commit for the given header + Header Header + Commit Commit // commit for the given header } type ValidatorSet struct { @@ -42,8 +42,8 @@ In the following, only the details of the data structures needed for this specif } type Validator struct { - Address Address // validator address (we assume validator's addresses are unique) - VotingPower int64 // validator's voting power + Address Address // validator address (we assume validator's addresses are unique) + VotingPower int64 // validator's voting power } type TrustedState { @@ -56,7 +56,7 @@ In the following, only the details of the data structures needed for this specif For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: ```go - // returns signed header: Header with Commit + // returns signed header: Header with Commit, for the given height func Commit(height int64) (SignedHeader, error) // returns validator set for the given height @@ -68,10 +68,15 @@ Furthermore, we assume the following auxiliary functions: // returns the validator set for the given validator hash func validators(validatorsHash []byte) ValidatorSet + // returns true if commit corresponds to the block data in 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 + // it does not assume signature verification func signers(commit Commit, validatorSet ValidatorSet) []Validator - // return the voting power the validators in v1 have according to their voting power in set V2 + // return the voting power the validators in v1 have according to their voting power in set v2 + // it assumes signature verification so it can be computationally expensive func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 // add this state as trusted to the store @@ -83,24 +88,26 @@ Furthermore, we assume the following auxiliary functions: func get(store Store, height int64) (TrustedState, error) ``` -### Tendermint Failure Model +### Failure Model -If a block `b` is generated at time `Time` (and this time is stored in the block), then 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`. +The lite client specification is defined with respect to the following failure model: If a block `b` is generated +at time `Time` (and this time is stored in the block), then 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](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 between clocks and -BFT time. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, -we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and -`TRUSTED_PERIOD` is in the order of weeks. +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 process p and every header (correctly generated by the Tendermint consensus) +time (BFT time) the following inequality holds: `Header.Time < now + CLOCK_DRIFT`. + +Furthermore, we assume that trust period is (several) order of magnitude bigger than clock drift (`TRUST_PERIOD >> CLOCK_DRIFT`), +as clock drift (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks. *Remark*: This failure model might change to a hybrid version that takes heights into account in the future. -The specification in this document considers an implementation of the lite client under the Tendermint Failure Model. Issues -like `counter-factual signing`, `fork accountability` and `evidence submission` are mechanisms that justify this assumption by -incentivizing validators to follow the protocol. If they don't, and we have 1/3 (or more) faults, safety may be violated. +The specification in this document considers an implementation of the lite client under the Failure Model defined above. Issues +like `counter-factual slashing`, `fork accountability` and `evidence submission` are mechanisms that justify this assumption by +incentivizing validators to follow the protocol. 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](fork-accountability.md). @@ -114,99 +121,58 @@ Note that this function is not making external (RPC) calls to the full node; the based on the local (given) state. This function is supposed to be used by the IBC handlers. ```go -func VerifyAndUpdateSingle(untrustedSh SignedHeader, - untrustedVs ValidatorSet, - untrustedNextVs ValidatorSet, - trustThreshold TrustThreshold, - trustingPeriod Duration, - now Time, - store Store) error { +func VerifySingle(untrustedSh SignedHeader, + untrustedVs ValidatorSet, + untrustedNextVs ValidatorSet, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + now Time, + trustedState TrustedState) error { - // fetch the latest state and ensure it hasn't expired - trustedState, error = get(store, 0) - if error != nil return error + assert untrustedSh.Header.Time < now + clockDrift - trustedSh = trustedState.SignedHeader - if !isWithinTrustedPeriod(trustedSh.Header, trustingPeriod, now) { - return ErrHeaderNotWithinTrustedPeriod + trustedHeader = trustedState.SignedHeader.Header + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (ErrHeaderNotWithinTrustedPeriod, nil) } + // 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) + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) - if error != nil return error + if error != nil return (error, nil) - // the untrusted header is now trusted. update the store + // the untrusted header is now trusted newTrustedState = TrustedState(untrustedSh, untrustedNextVs) - return add(store, newTrustedState) + return (nil, newTrustedState) } -``` +// return true if header is within its lite client trusted period; otherwise returns false +func isWithinTrustedPeriod(header Header, + trustingPeriod Duration, + now Time) bool { -**VerifyAndUpdateBisection.** The function `VerifyAndUpdateBisection` captures high level logic, i.e., application call -to the lite client module to (optionally download) and verify header for some height. The core verification logic is -captured by `CanTrust` function that iteratively try to establish trust in given header by relying on `CheckSupport` function. - -```go -func VerifyAndUpdateBisection(height int64, - trustThreshold TrustThreshold, - trustingPeriod Duration, - now Time, - store Store) error { - - // fetch the latest state and ensure it hasn't expired - trustedState, error = get(store, 0) - if error != nil return error - - trustedSh = trustedState.SignedHeader - assert trustedSh.Header.Height < height - - if !isWithinTrustedPeriod(trustedSh.Header, trustingPeriod, now) { - return ErrHeaderNotWithinTrustedPeriod - } - - untrustedSh, error := Commit(height) - if error != nil return error - - untrustedH = untrustedSh.Header - - untrustedVs, error := Validators(untrustedH.Height) - if error != nil return error - - untrustedNextVs, error := Validators(untrustedH.Height + 1) - if error != nil return error - - error = verifySingle( - trustedState, - untrustedSh, - untrustedVs, - untrustedNextVs, - trustThreshold) - - if fatalCheckSupportError(error) return error - - if error == nil { - // the untrusted header is now trusted. update the store - newTrustedState = TrustedState(untrustedSh, untrustedNextVs) - return add(store, newTrustedState) - } - - // at this point in time we need to do bisection - pivotHeight := ceil((trustedSh.Header.Height + untrustedH.Height) / 2) - - error = VerifyAndUpdateBisection(pivotHeight, trustThreshold, trustingPeriod, now, store) - if error != nil return error - - error = VerifyAndUpdateBisection(untrustedH.Height, trustThreshold, trustingPeriod, now, store) - if error != nil return error - return nil + return header.Time + trustedPeriod > now } ``` +Note that in case `VerifyAndUpdateSingle` 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`. + + +**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, @@ -214,245 +180,184 @@ func verifySingle(trustedState TrustedState, untrustedNextVs ValidatorSet, trustThreshold TrustThreshold) error { - // ensure the new height is higher - untrustedHeight = untrustedSh.Header.Height - trustedHeight = trustedState.SignedHeader.Header.Height - assert untrustedHeight > trustedHeight + untrustedHeader = untrustedSh.Header + untrustedCommit = untrustedSh.Commit - // validate the untrusted header against its commit, vals, and next_vals - untrustedHeader = untrustedSh.Header - untrustedCommit = untrustedSh.Commit + trustedHeader = trustedState.SignedHeader.Header + trustedVs = trustedState.ValidatorSet - error = validateHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs) - if error != nil return error + assert trustedHeader.Height < untrustedHeader.Height AND + trustedHeader.Time < untrustedHeader.Time - trustedHeader = trustedState.SignedHeader.Header - trustedVs = trustedState.ValidatorSet - - // check for adjacent headers - if untrustedHeight == trustedHeight + 1 { - if trustedHeader.NextValidatorsHash != untrustedHeader.ValidatorsHash { - return ErrInvalidAdjacentHeaders - } - } else { - error = verifyCommitTrusting(trustedVs, untrustedCommit, trustThreshold) + // validate the untrusted header against its commit, vals, and next_vals + error = validateSignedHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs) if error != nil return error - } - - // verify the untrusted commit - return verifyCommitFull(untrustedVs, untrustedCommit) -} - -func validateHeaderAndVals(signedHeader SignedHeader, vs ValidatorSet, nextVs ValidatorSet) error { - if hash(nextVs) != signedHeader.Header.NextValidatorsHash or - hash(vs) != signedHeader.Header.ValidatorsHash or - !matchingCommit(signedHeader) { // commit corresponds to the header - return error - } -} - -func verifyCommitFull(vs ValidatorSet, commit Commit) error { - totalPower := vs.TotalVotingPower; - signed_power := votingPowerIn(signers(commit, vs), vs) - - // check the signers account for +2/3 of the voting power - if signed_power * 3 <= total_power * 2 return error - return nil -} - -func verifyCommitTrusting(vs ValidatorSet, commit Commit, trustLevel TrustThreshold) error { - totalPower := vs.TotalVotingPower; - signed_power := votingPowerIn(signers(commit, vs), vs) - - // check the signers account for more than max(1/3, trustLevel) of the voting power - if signed_power <= max(1/3, trustLevel) * totalPower return error - return nil -} - - -// return true if header is within its lite client trusted period; otherwise it returns false -func isWithinTrustedPeriod(header Header, - trustingPeriod Duration, - now Time) bool { - - return header.Time + trustedPeriod > now -} - -func fatalCheckSupportError(err) bool { - return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders -} -``` - - - -The function `CanTrust` checks whether to trust header `untrusted_h` based on the trusted header `trusted_h` It does so by (potentially) -building transitive trust relation between `trusted_h` and `untrusted_h`, over some intermediate headers. For example, in case we cannot trust -header `untrusted_h` based on the trusted header `trusted_h`, the function `CanTrust` will try to find headers such that we can transition trust -from `trusted_h` over intermediate headers to `untrusted_h`. We will give two implementations of `CanTrust`, the one based -on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier -to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. - -Both implementations of `CanTrust` function are based on `CheckSupport` function that implements the skipping conditions under which we can trust a -header `untrusted_h` given the trust in the header `trusted_h` as a single step, -i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. - - -```go -// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error -// where error captures the nature of the error. -// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. -func CanTrust(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height - - th := trusted_h // th is trusted header - // untrustedHeader is a list of (?) verified headers that have not passed CheckSupport() - untrustedHeaders := [untrusted_h] - - while true { - for h in untrustedHeaders { - // we assume here that iteration is done in the order of header heights - err = CheckSupport(th,h,trustThreshold) - if err == nil { - if !verify(h) { return ErrInvalidHeader(h) } - th = h - Store.Add(h) - untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if th == untrusted_h { return nil } - } - if fatalCheckSupportError(err) { return err } - } - - endHeight = min(untrustedHeaders) - while true { - pivot := ceil((th.Header.height + endHeight) / 2) - hp := Commit(pivot) - // try to move trusted header forward to hp - err = CheckSupport(th,hp,trustThreshold) - if fatalCheckSupportError(err) return err - if err == nil { - if !verify(hp) { return ErrInvalidHeader(hp) } - th = hp - Store.Add(th) - break - } - untrustedHeaders.add(hp) - endHeight = pivot - } - } - return nil // this line should never be reached -} - -func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - - // Although while executing the rest of CheckSupport function, trusted_h can expire based - // on the lite client trusted period, this is not problem as lite client trusted - // period is smaller than trusted period of the header based on Tendermint Failure - // model, i.e., there is a significant time period (measure in days) during which - // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders + if untrustedHeader.Height == trustedHeader.Height + 1 { + if trustedHeader.NextValidatorsHash != untrustedHeader.ValidatorsHash { + return ErrInvalidAdjacentHeaders + } + } else { + error = verifyCommitTrusting(trustedVs, untrustedCommit, trustThreshold) + if error != nil return error } - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) - - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange + // verify the untrusted commit + return verifyCommitFull(untrustedVs, untrustedCommit) } -func fatalCheckSupportError(err) bool { - return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders -``` - -```go -func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { - assume trusted_h.Header.Height < untrusted_h.header.Height - - err = CheckSupport(trusted_h,untrusted_h,trustThreshold) - if err == nil { - Store.Add(untrusted_h) +// 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(nextVs) != header.NextValidatorsHash OR + hash(vs) != header.ValidatorsHash OR + !matchingCommit(header, signedHeader.Commit) { return error } return nil - } - if err != ErrTooMuchChange return err +} - pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 - hp := Commit(pivot) - if !verify(hp) return ErrInvalidHeader(hp) +// returns nil if at least single correst signer signed the commit; otherwise returns error +func verifyCommitTrusting(vs ValidatorSet, commit Commit, trustLevel TrustThreshold) error { - err = CanTrustBisection(trusted_h,hp,trustThreshold) - if err == nil { - Store.Add(hp) - err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) - if err2 == nil { - Store.Add(untrusted_h) - return nil - } - return err2 - } - return err + totalPower := vs.TotalVotingPower + signedPower := votingPowerIn(signers(commit, vs), vs) + + // 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; + signed_power := votingPowerIn(signers(commit, vs), vs) + + // check the signers account for +2/3 of the voting power + if signed_power * 3 <= total_power * 2 return ErrInvalidCommit + return nil } ``` -**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header untrusted_h based on header trusted_h. -Time validity of a header is captured by the ```isWithinTrustedPeriod``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns -true in case the header is within its lite client trusted period. -```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. +**VerifyHeaderAtHeight.** The function `VerifyHeaderAtHeight` captures high level +logic, i.e., application call to the lite client module to download and verify header +for some height. ```go - // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation - // (function `verify`). - // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. - // ErrHeaderNotWithinTrustedPeriod is used when trusted_h has expired with respect to lite client trusted period, - // ErrInvalidAdjacentHeaders when that adjacent headers are not consistent and - // ErrTooMuchChange when there is not enough intersection between validator sets to have - // skipping condition true. - func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now +func VerifyHeaderAtHeight(untrustedHeight int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + trustedState TrustedState) (error, TrustedState)) { - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + trustedHeader := trustedState.SignedHeader.Header - // Although while executing the rest of CheckSupport function, trusted_h can expire based - // on the lite client trusted period, this is not problem as lite client trusted - // period is smaller than trusted period of the header based on Tendermint Failure - // model, i.e., there is a significant time period (measure in days) during which - // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. - - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders + now := System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (ErrHeaderNotWithinTrustedPeriod, trustedState) } - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) + newTrustedState, err := VerifyAndUpdateBisection(trustedState, + untrustedHeight, + trustThreshold, + trustingPeriod, + clockDrift, + now) - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil + if err != nil return (err, trustedState) + + now = System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (ErrHeaderNotWithinTrustedPeriod, trustedState) } - return ErrTooMuchChange - } + + return (nil, newTrustedState) +} ``` +Note that in case `VerifyAndUpdateSingle` 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`. + +**VerifyAndUpdateBisection.** The function `VerifyAndUpdateBisection` 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 VerifyAndUpdateBisection(trustedState TrustedState, + untrustedHeight int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + now Time) (error, TrustedState) { + + trustedHeader = trustedState.SignedHeader.Header + assert trustedHeader.Height < untrustedHeight + + untrustedSh, error := Commit(untrustedHeight) + if error != nil return (error, trustedState) + + untrustedHeader = untrustedSh.Header + assert trustedHeader.Time < untrustedHeader.Time + + // 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. + assert untrustedHeader.Time < now + clockDrift + + untrustedVs, error := Validators(untrustedHeight) + if error != nil return (error, trustedState) + + untrustedNextVs, error := Validators(untrustedHeight + 1) + if error != nil return (error, trustedState) + + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if fatalError(error) return (error, trustedState) + + if error == nil { + // the untrusted header is now trusted. + newTrustedState = TrustedState(untrustedSh, untrustedNextVs) + return (nil, newTrustedState) + } + + // at this point in time we need to do bisection + pivotHeight := ceil((trustedHeader.Height + untrustedHeight) / 2) + + error, newTrustedState = VerifyAndUpdateBisection(trustedState, + pivotHeight, + trustThreshold, + trustingPeriod, + clockDrift, + now) + if error != nil return (error, trustedState) + + error, newTrustedState = verifyAndUpdateBisection(newTrustedState, + untrustedHeight, + trustThreshold, + trustingPeriod, + clockDrift, + now) + if error != nil return (error, trustedState) + return (nil, newTrustedState) +} + +func fatalError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod OR + err == ErrInvalidAdjacentHeaders OR + err == ErrInvalidCommit +} +``` + + ### The case `untrusted_h.Header.height < trusted_h.Header.height` @@ -482,8 +387,6 @@ func Backwards(trusted_h,untrusted_h) error { } ``` - - In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always diff --git a/spec/consensus/non-recursive-light-client.md b/spec/consensus/non-recursive-light-client.md new file mode 100644 index 000000000..085f3f65c --- /dev/null +++ b/spec/consensus/non-recursive-light-client.md @@ -0,0 +1,612 @@ +# Lite client + +A lite client is a process that connects to Tendermint full node(s) and then tries to verify application +data using the Merkle proofs. + +## Problem statement + +We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because +the lite 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. + +## 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 + + // hashes from the app output from the prev block + ValidatorsHash []byte // hash of the validators for the current block + NextValidatorsHash []byte // hash of the validators for the next block + + // hashes of block data + LastCommitHash []byte // hash of the commit from validators from the last 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 lite 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 the validator set for the given validator hash + func validators(validatorsHash []byte) ValidatorSet + + // returns true if commit corresponds to the block data in 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 + // it does not assume signature verification + func signers(commit Commit, validatorSet ValidatorSet) []Validator + + // return the voting power the validators in v1 have according to their voting power in set v2 + // it assumes signature verification so it can be computationally expensive + func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 + + // add this state as trusted to the store + func add(store Store, trustedState TrustedState) error + + // retrieve the trusted state at given height if it exists (error = nil) + // return an error if there are no trusted state for the given height + // if height = 0, return the latest trusted state + func get(store Store, height int64) (TrustedState, error) +``` + + +**VerifyHeaderAtHeight.** TODO. +```go +func VerifyHeaderAtHeight(untrustedHeight int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + store Store) (error, (TrustedState, Time)) { + + now := System.Time() + initTrustedState, newTrustedState, err := VerifyAndUpdateNonRecursive(untrustedHeight, + trustThreshold, + trustingPeriod, + clockDrift, + now, + store Store) + + if err != nil return err + + now = System.Time() + if !isWithinTrustedPeriod(initTrustedState.SignedHeader.Header, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + return nil, (newTrustedState, now) +} +``` + +If we get some trustedState at time t (now = t), + + +**VerifyAndUpdateNonRecursive.** TODO. +```go +func VerifyAndUpdateNonRecursive(untrustedHeight int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + now Time, + store Store) error { + + // fetch the latest state and ensure it hasn't expired + trustedState, error = get(store, 0) + if error != nil return error + + trustedSh = trustedState.SignedHeader + trustedHeader = trustedSh.Header + assert trustedHeader.Height < untrustedHeight AND + trustedHeader.Time < now + + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + + th := trustedHeader // th is trusted header + + untrustedSh, error := Commit(untrustedHeight) + if error != nil return error + + untrustedHeader = untrustedSh.Header + assert untrustedHeader.Time < now + clockDrift + + untrustedVs, error := Validators(untrustedHeight) + if error != nil return error + + untrustedNextVs, error := Validators(untrustedHeight + 1) + if error != nil return error + + // untrustedHeader is a list of headers that have not passed verifySingle + untrustedHeaders := [untrustedHeader] + + while true { + for h in untrustedHeaders { + // we assume here that iteration is done in the order of header heights + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if err == nil { + // the untrusted header is now trusted. update the store + trustedState = TrustedState(untrustedSh, untrustedNextVs) + add(store, trustedState) + + untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) + if trustedState.SignedHeader.Header == untrustedSh.Header { + return nil + } + } + if fatalError(err) { return err } + } + + endHeight = min(untrustedHeaders) + while true { + trustedSh = trustedState.SignedHeader + trustedHeader = trustedSh.Header + pivotHeight := ceil((trustedHeader.Height + endHeight) / 2) + + untrustedSh, error := Commit(pivotHeight) + if error != nil return error + + untrustedHeader = untrustedSh.Header + assert untrustedHeader.Time < now + clockDrift + + untrustedVs, error := Validators(untrustedHeight) + if error != nil return error + + untrustedNextVs, error := Validators(untrustedHeight + 1) + if error != nil return error + + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if fatalError(error) return error + + if err == nil { + trustedState = TrustedState(untrustedSh, untrustedNextVs) + add(store, trustedState) + break + } + + untrustedHeaders.add(untrustedHeader) + endHeight = pivot + } + } + return nil // this line should never be reached +} +``` + + + +The function `CanTrust` checks whether to trust header `untrusted_h` based on the trusted header `trusted_h` It does so by (potentially) +building transitive trust relation between `trusted_h` and `untrusted_h`, over some intermediate headers. For example, in case we cannot trust +header `untrusted_h` based on the trusted header `trusted_h`, the function `CanTrust` will try to find headers such that we can transition trust +from `trusted_h` over intermediate headers to `untrusted_h`. We will give two implementations of `CanTrust`, the one based +on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier +to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. + +Both implementations of `CanTrust` function are based on `CheckSupport` function that implements the skipping conditions under which we can trust a +header `untrusted_h` given the trust in the header `trusted_h` as a single step, +i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. + + +```go +// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error +// where error captures the nature of the error. +// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. +func CanTrust(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height + + th := trusted_h // th is trusted header + // untrustedHeader is a list of (?) verified headers that have not passed CheckSupport() + untrustedHeaders := [untrusted_h] + + while true { + for h in untrustedHeaders { + // we assume here that iteration is done in the order of header heights + err = CheckSupport(th,h,trustThreshold) + if err == nil { + if !verify(h) { return ErrInvalidHeader(h) } + th = h + Store.Add(h) + untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) + if th == untrusted_h { return nil } + } + if fatalCheckSupportError(err) { return err } + } + + endHeight = min(untrustedHeaders) + while true { + pivot := ceil((th.Header.height + endHeight) / 2) + hp := Commit(pivot) + // try to move trusted header forward to hp + err = CheckSupport(th,hp,trustThreshold) + if fatalCheckSupportError(err) return err + if err == nil { + if !verify(hp) { return ErrInvalidHeader(hp) } + th = hp + Store.Add(th) + break + } + untrustedHeaders.add(hp) + endHeight = pivot + } + } + return nil // this line should never be reached +} + +func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now + + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + + // Although while executing the rest of CheckSupport function, trusted_h can expire based + // on the lite client trusted period, this is not problem as lite client trusted + // period is smaller than trusted period of the header based on Tendermint Failure + // model, i.e., there is a significant time period (measure in days) during which + // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. + + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange +} + +func fatalCheckSupportError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders +``` + +```go +func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { + assume trusted_h.Header.Height < untrusted_h.header.Height + + err = CheckSupport(trusted_h,untrusted_h,trustThreshold) + if err == nil { + Store.Add(untrusted_h) + return nil + } + if err != ErrTooMuchChange return err + + pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 + hp := Commit(pivot) + if !verify(hp) return ErrInvalidHeader(hp) + + err = CanTrustBisection(trusted_h,hp,trustThreshold) + if err == nil { + Store.Add(hp) + err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) + if err2 == nil { + Store.Add(untrusted_h) + return nil + } + return err2 + } + return err +} +``` + +**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header untrusted_h based on header trusted_h. +Time validity of a header is captured by the ```isWithinTrustedPeriod``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns +true in case the header is within its lite client trusted period. +```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. + +```go + // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation + // (function `verify`). + // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. + // ErrHeaderNotWithinTrustedPeriod is used when trusted_h has expired with respect to lite client trusted period, + // ErrInvalidAdjacentHeaders when that adjacent headers are not consistent and + // ErrTooMuchChange when there is not enough intersection between validator sets to have + // skipping condition true. + func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now + + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + + // Although while executing the rest of CheckSupport function, trusted_h can expire based + // on the lite client trusted period, this is not problem as lite client trusted + // period is smaller than trusted period of the header based on Tendermint Failure + // model, i.e., there is a significant time period (measure in days) during which + // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. + + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange + } +``` + + +### The case `untrusted_h.Header.height < trusted_h.Header.height` + +In the use case where someone tells the lite client that application data that is relevant for it +can be read in the block of height `k` and the lite 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 lite 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 Backwards(trusted_h,untrusted_h) error { + assert (untrusted_h.Header.height < trusted_h.Header.height) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) + + old := trusted_h + for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { + new := Commit(i) + if (hash(new) != old.Header.hash) { + return ErrInvalidAdjacentHeaders + } + old := new + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) + } + if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders + return nil + } +``` + + + +In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting +headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability +protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always +operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound +for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula +holds: +```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. + + +*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 lite client communicates with one full node +- the lite client locally stores all the headers that has passed basic verification and that are within lite 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 lite 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 lite client should disconnect and reinitialise with new peer. If the trusted header has expired, + we need to reinitialise lite 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). + + + +## Context of this document + +In order to make sure that full nodes have the incentive to follow the protocol, we have to address the +following three Issues + +1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. + +2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). + +3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). + +The term "trusting" above indicates that the correctness of the protocol depends on +this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk +of trusting a corrupted/forged *inithead* is negligible. + +* For each header *h* it has locally stored, the lite client stores whether + it trusts *h*. We write *trust(h) = true*, if this is the case. + +* signed header fields: contains a header and a *commit* for the current header; a "seen commit". + In Tendermint consensus the "canonical commit" is stored in header *height* + 1. + + + * Validator fields. We will write a validator as a tuple *(v,p)* such that + + *v* is the identifier (we assume identifiers are unique in each validator set) + + *p* is its voting power + +### Definitions + +* *TRUSTED_PERIOD*: trusting 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). + + +### Tendermint Failure Model + +If a block *b* is generated at time *Time* (and this time is stored in the block), then 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```. + +Formally, +\[ +\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > +2/3 \sum_{(v,p) \in h.Header.NextV} p +\] + + +## Lite Client Trusting Spec + +The lite 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: + +- 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. + +- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. + +*Remark*: If in the course of the computation, the lite 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.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. + +*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. + +*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. + +To reason about the correctness, we may prove the following invariant. + +*Verification Condition: Lite Client Invariant.* + For each lite client *l* and each header *h*: +if *l* has set *trust(h) = true*, + then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. + + Formally, + \[ + \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > + 2/3 \sum_{(v,p) \in h.Header.NextV} p + \] + +*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. + + +## High Level Solution + +Upon initialization, the lite client is given a header *inithead* it trusts (by +social consensus). It is assumed that *inithead* satisfies the lite client invariant. (If *inithead* has been correctly generated by Tendermint consensus, the invariant follows from the Tendermint Failure Model.) +Note that the *inithead* should be within its trusted period during initialization. + +When a lite clients sees a signed new 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 proof.** If a block is appended to the chain, where the last block +is trusted (and properly committed by the old validator set in the next block), +and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. +Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. + +2. **Trusting period.** Based on a trusted block *h*, and the lite client +invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. +If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. + +3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* 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 *snh*; if not, we may continue recursively. + +## How to use it + +We consider the following use case: + the lite client wants to verify a header for some given height *k*. Thus: + - it requests the signed header for height *k* from a full node + - it tries to verify this header with the methods described here. + +This can be used in several settings: + - someone tells the lite client that application data that is relevant for it can be read in the block of height *k*. + - the lite clients wants the latest state. It asks a full nude for the current height, and uses the response for *k*. + - in case of inter-blockchain communication protocol (IBC) the light client runs on a chain and someone feeds it + signed headers as input and it computes whether it can trust it. + + +## Details + +**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old +validator set *h.Header.NextV*. + +When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* 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). + + + +*Correctness arguments* + +Towards Lite Client Accuracy: +- Assume by contradiction that `untrusted_h` was not generated correctly and the lite client sets trust to true because `CheckSupport` returns true. +- trusted_h is trusted and sufficiently new +- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed `untrusted_h`. +- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing `untrusted_h` => `untrusted_h` was correctly generated, we arrive at the required contradiction. + + +Towards Lite Client Completeness: +- The check is successful if sufficiently many validators of `trusted_h` are still validators in `untrusted_h` and signed `untrusted_h`. +- If *untrusted_h.Header.height = trusted_h.Header.height + 1*, and both headers were generated correctly, the test passes + +*Verification Condition:* We may need a Tendermint invariant stating that if *untrusted_h.Header.height = trusted_h.Header.height + 1* then *signers(untrusted_h.Commit) \subseteq trusted_h.Header.NextV*. + +*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 CheckSupport returns true for non-adjacent headers. + + + + +*Correctness arguments (sketch)* + +Lite Client Accuracy: +- Assume by contradiction that `untrusted_h` was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil. +- CanTrustBisection returns true only if all calls to CheckSupport in the recursion return nil. +- Thus we have a sequence of headers that all satisfied the CheckSupport +- again a contradiction + +Lite Client Completeness: + +This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. + +*Stalling* + +With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite 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 lite 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, Bisection would then be executed at the full node. +* We may set a timeout how long bisection may take. + + + + + From 146e251892977df4ae5af57971a7e123caf55d10 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 8 Jan 2020 17:49:32 +0100 Subject: [PATCH 026/223] Address reviewer comment's. Intermediate commit --- spec/consensus/light-client.md | 236 +++++++++++++++++---------------- 1 file changed, 123 insertions(+), 113 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 434c0b721..8c07dfac8 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -20,15 +20,10 @@ In the following, only the details of the data structures needed for this specif ```go type Header struct { - Height int64 - Time Time // the chain time when the header (block) was generated - - // hashes from the app output from the prev block - ValidatorsHash []byte // hash of the validators for the current block - NextValidatorsHash []byte // hash of the validators for the next block - - // hashes of block data - LastCommitHash []byte // hash of the commit from validators from the last block + Height int64 + Time Time // the chain time when the header (block) was generated + ValidatorsHash []byte // hash of the validators for the current block + NextValidatorsHash []byte // hash of the validators for the next block } type SignedHeader struct { @@ -65,58 +60,67 @@ For the purpose of this lite client specification, we assume that the Tendermint Furthermore, we assume the following auxiliary functions: ```go - // returns the validator set for the given validator hash - func validators(validatorsHash []byte) ValidatorSet - - // returns true if commit corresponds to the block data in the header; otherwise false + // 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 - // it does not assume signature verification + // 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 // return the voting power the validators in v1 have according to their voting power in set v2 - // it assumes signature verification so it can be computationally expensive + // it does not assume signature verification func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 - // add this state as trusted to the store - func add(store Store, trustedState TrustedState) error - - // retrieve the trusted state at given height if it exists (error = nil) - // return an error if there are no trusted state for the given height - // if height = 0, return the latest trusted state - func get(store Store, height int64) (TrustedState, error) + // returns hash of the given validator set + func hash(v2 ValidatorSet) []byte ``` ### Failure Model -The lite client specification is defined with respect to the following failure model: If a block `b` is generated -at time `Time` (and this time is stored in the block), then 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`. +For the purpose of model definitions we assume that there exists a function +`validators` that returns the corresponding validator set for the given hash. +The lite client specification 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](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 process p and every header (correctly generated by the Tendermint consensus) -time (BFT time) the following inequality holds: `Header.Time < now + CLOCK_DRIFT`. +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 lite 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 lite client process. -Furthermore, we assume that trust period is (several) order of magnitude bigger than clock drift (`TRUST_PERIOD >> CLOCK_DRIFT`), -as clock drift (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks. +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 lite 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 `PUNISHMENT_PERIOD`). +Furthermore, we assume that `TRUSTED_PERIOD < PUNISHMENT_PERIOD` and that they are normally of the same order of magnitude, for example +`TRUSTED_PERIOD = PUNISHMENT_PERIOD / 2`. Note that `PUNISHMENT_PERIOD` is often referred to as an +unbonding period due to the "bonding" mechanism in modern proof of stake systems. + +The specification in this document considers an implementation of the lite client under the Failure Model defined above. +Mechanisms like `fork accountability` and `evidence submission` are defined in the context of `PUNISHMENT_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](fork-accountability.md). *Remark*: This failure model might change to a hybrid version that takes heights into account in the future. -The specification in this document considers an implementation of the lite client under the Failure Model defined above. Issues -like `counter-factual slashing`, `fork accountability` and `evidence submission` are mechanisms that justify this assumption by -incentivizing validators to follow the protocol. 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](fork-accountability.md). - ### Functions -**VerifyAndUpdateSingle.** The function `VerifyAndUpdateSingle` attempts to update -the (trusted) store with the given untrusted header and the corresponding validator sets. -It ensures that the last trusted header from the store hasn't expired yet (it is still within its trusted period), -and that the untrusted header can be verified using the latest trusted state from the store. +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 assume `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. @@ -124,33 +128,35 @@ based on the local (given) state. This function is supposed to be used by the IB func VerifySingle(untrustedSh SignedHeader, untrustedVs ValidatorSet, untrustedNextVs ValidatorSet, - trustThreshold TrustThreshold, + trustedState TrustedState, + trustThreshold float, trustingPeriod Duration, clockDrift Duration, - now Time, - trustedState TrustedState) error { + now Time) (TrustedState, error) { - assert untrustedSh.Header.Time < now + clockDrift + if untrustedSh.Header.Time > now + clockDrift { + return (trustedState, ErrInvalidHeaderTime) + } trustedHeader = trustedState.SignedHeader.Header if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { - return (ErrHeaderNotWithinTrustedPeriod, nil) + 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) + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) - if error != nil return (error, nil) + if error != nil return (state, error) // the untrusted header is now trusted newTrustedState = TrustedState(untrustedSh, untrustedNextVs) - return (nil, newTrustedState) + return (newTrustedState, nil) } // return true if header is within its lite client trusted period; otherwise returns false @@ -162,7 +168,7 @@ func isWithinTrustedPeriod(header Header, } ``` -Note that in case `VerifyAndUpdateSingle` returns without an error (untrusted header +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`. @@ -178,7 +184,7 @@ func verifySingle(trustedState TrustedState, untrustedSh SignedHeader, untrustedVs ValidatorSet, untrustedNextVs ValidatorSet, - trustThreshold TrustThreshold) error { + trustThreshold float) error { untrustedHeader = untrustedSh.Header untrustedCommit = untrustedSh.Commit @@ -186,8 +192,8 @@ func verifySingle(trustedState TrustedState, trustedHeader = trustedState.SignedHeader.Header trustedVs = trustedState.ValidatorSet - assert trustedHeader.Height < untrustedHeader.Height AND - trustedHeader.Time < untrustedHeader.Time + 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) @@ -199,7 +205,7 @@ func verifySingle(trustedState TrustedState, return ErrInvalidAdjacentHeaders } } else { - error = verifyCommitTrusting(trustedVs, untrustedCommit, trustThreshold) + error = verifyCommitTrusting(trustedVs, untrustedCommit, untrustedVs, trustThreshold) if error != nil return error } @@ -210,17 +216,20 @@ func verifySingle(trustedState TrustedState, // 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(nextVs) != header.NextValidatorsHash OR - hash(vs) != header.ValidatorsHash OR - !matchingCommit(header, signedHeader.Commit) { return error } + 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(vs ValidatorSet, commit Commit, trustLevel TrustThreshold) error { +func verifyCommitTrusting(trustedVs ValidatorSet, + commit Commit, + untrustedVs ValidatorSet, + trustLevel float) error { - totalPower := vs.TotalVotingPower - signedPower := votingPowerIn(signers(commit, vs), vs) + 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 @@ -232,10 +241,10 @@ func verifyCommitTrusting(vs ValidatorSet, commit Commit, trustLevel TrustThresh // return error otherwise func verifyCommitFull(vs ValidatorSet, commit Commit) error { totalPower := vs.TotalVotingPower; - signed_power := votingPowerIn(signers(commit, vs), vs) + signedPower := votingPowerIn(signers(commit, vs), vs) // check the signers account for +2/3 of the voting power - if signed_power * 3 <= total_power * 2 return ErrInvalidCommit + if signedPower * 3 <= totalPower * 2 return ErrInvalidCommit return nil } ``` @@ -246,73 +255,71 @@ for some height. ```go func VerifyHeaderAtHeight(untrustedHeight int64, - trustThreshold TrustThreshold, + trustedState TrustedState, + trustThreshold float, trustingPeriod Duration, - clockDrift Duration, - trustedState TrustedState) (error, TrustedState)) { + clockDrift Duration) (TrustedState, error)) { trustedHeader := trustedState.SignedHeader.Header now := System.Time() if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { - return (ErrHeaderNotWithinTrustedPeriod, trustedState) + return (trustedState, ErrHeaderNotWithinTrustedPeriod) } - newTrustedState, err := VerifyAndUpdateBisection(trustedState, - untrustedHeight, - trustThreshold, - trustingPeriod, - clockDrift, - now) + newTrustedState, err := VerifyBisection(untrustedHeight, + trustedState, + trustThreshold, + trustingPeriod, + clockDrift, + now) - if err != nil return (err, trustedState) + if err != nil return (trustedState, err) now = System.Time() if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { - return (ErrHeaderNotWithinTrustedPeriod, trustedState) + return (trustedState, ErrHeaderNotWithinTrustedPeriod) } - return (nil, newTrustedState) + return (newTrustedState, err) } ``` -Note that in case `VerifyAndUpdateSingle` returns without an error (untrusted header +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`. -**VerifyAndUpdateBisection.** The function `VerifyAndUpdateBisection` implements +**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 +relationship between `trustedState` and untrusted header at the given height over finite set of (downloaded and verified) headers. ```go -func VerifyAndUpdateBisection(trustedState TrustedState, - untrustedHeight int64, - trustThreshold TrustThreshold, - trustingPeriod Duration, - clockDrift Duration, - now Time) (error, TrustedState) { - - trustedHeader = trustedState.SignedHeader.Header - assert trustedHeader.Height < untrustedHeight +func VerifyBisection(untrustedHeight int64, + trustedState TrustedState, + trustThreshold float, + trustingPeriod Duration, + clockDrift Duration, + now Time) (TrustedState, error) { untrustedSh, error := Commit(untrustedHeight) - if error != nil return (error, trustedState) + if error != nil return (trustedState, ErrRequestFailed) untrustedHeader = untrustedSh.Header - assert trustedHeader.Time < untrustedHeader.Time // 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. - assert untrustedHeader.Time < now + clockDrift + if untrustedHeader.Time > now + clockDrift { + return (trustedState, ErrInvalidHeaderTime) + } untrustedVs, error := Validators(untrustedHeight) - if error != nil return (error, trustedState) + if error != nil return (trustedState, ErrRequestFailed) untrustedNextVs, error := Validators(untrustedHeight + 1) - if error != nil return (error, trustedState) + if error != nil return (trustedState, ErrRequestFailed) error = verifySingle( trustedState, @@ -321,38 +328,41 @@ func VerifyAndUpdateBisection(trustedState TrustedState, untrustedNextVs, trustThreshold) - if fatalError(error) return (error, trustedState) + if fatalError(error) return (trustedState, error) if error == nil { // the untrusted header is now trusted. newTrustedState = TrustedState(untrustedSh, untrustedNextVs) - return (nil, newTrustedState) + return (newTrustedState, nil) } // at this point in time we need to do bisection pivotHeight := ceil((trustedHeader.Height + untrustedHeight) / 2) - error, newTrustedState = VerifyAndUpdateBisection(trustedState, - pivotHeight, - trustThreshold, - trustingPeriod, - clockDrift, - now) - if error != nil return (error, trustedState) + error, newTrustedState = VerifyBisection(pivotHeight, + trustedState, + trustThreshold, + trustingPeriod, + clockDrift, + now) + if error != nil return (newTrustedState, error) - error, newTrustedState = verifyAndUpdateBisection(newTrustedState, - untrustedHeight, - trustThreshold, - trustingPeriod, - clockDrift, - now) - if error != nil return (error, trustedState) - return (nil, newTrustedState) + 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 } ``` From f26eb4ee89234cd5ad85bb316566f109463556bd Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 12:55:31 -0800 Subject: [PATCH 027/223] light client dir and readmes --- spec/consensus/light/README.md | 49 +++++++++++++++++++ .../accountability.md} | 0 .../verification-non-recursive.md} | 0 .../verification.md} | 0 spec/consensus/readme.md | 24 +++++++++ 5 files changed, 73 insertions(+) create mode 100644 spec/consensus/light/README.md rename spec/consensus/{fork-accountability.md => light/accountability.md} (100%) rename spec/consensus/{non-recursive-light-client.md => light/verification-non-recursive.md} (100%) rename spec/consensus/{light-client.md => light/verification.md} (100%) diff --git a/spec/consensus/light/README.md b/spec/consensus/light/README.md new file mode 100644 index 000000000..c45875053 --- /dev/null +++ b/spec/consensus/light/README.md @@ -0,0 +1,49 @@ +# Tendermint Light Client Protocol + +## Contents + +- [Motivation](#motivation) +- [Structure](#structure) +- [Core Verification](./verification.md) +- [Fork Detection](./detection.md) +- [Fork Accountability](./accountability.md) + +## Motivation + +The Tendermint Light Client is motivated by the need for a light weight protocol +to sync with a Tendermint blockchain, with the least processing necessary to +securely verify a recent state. The protocol consists +primarily of checking hashes and verifying Tendermint commit signatures to +update trusted validator sets and committed block headers from the chain. + +Motivating use cases include: + +- Light Node: a daemon that syncs a blockchain to the latest committed header by making RPC requests to full nodes. +- State Sync: a reactor that syncs a blockchain to a recent committed state by making P2P requests to full nodes. +- IBC Client: an ABCI application library that syncs a blockchain to a recent committed header by receiving proof-carrying +transactions from "IBC relayers", who make RPC requests to full nodes on behalf of the IBC clients. + +## Structure + +The Tendermint Light Client consists of three primary components: + +- [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes +- [Fork Detection](./detection.md): talking to multiple peers to detect byzantine behaviour +- [Fork Accountability](./accountability.md): analyzing byzantine behaviour to hold validators accountable. + +While every light client must perform core verification and fork detection +to achieve their prescribed security level, fork accountability is expected to +be done by full nodes and validators, and is thus more accurately a component of +the full node consensus protocol, though it is included here since it is +primarily concerned with providing security to light clients. + +Light clients are fundamentally synchronous protocols, +where security is grounded ultimately in the interval during which a validator can be punished +for byzantine behaviour. We assume here that such intervals have fixed and known minima +referred to commonly as a blockchain's Unbonding Period. + +A secure light client must guarantee that all three components - +core verification, fork detection, and fork accountability - +each with their own synchrony assumptions and fault model, can execute +sequentially and to completion within the given Unbonding Period. + diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/light/accountability.md similarity index 100% rename from spec/consensus/fork-accountability.md rename to spec/consensus/light/accountability.md diff --git a/spec/consensus/non-recursive-light-client.md b/spec/consensus/light/verification-non-recursive.md similarity index 100% rename from spec/consensus/non-recursive-light-client.md rename to spec/consensus/light/verification-non-recursive.md diff --git a/spec/consensus/light-client.md b/spec/consensus/light/verification.md similarity index 100% rename from spec/consensus/light-client.md rename to spec/consensus/light/verification.md diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md index 0ffc4b0bf..32d5579be 100644 --- a/spec/consensus/readme.md +++ b/spec/consensus/readme.md @@ -3,3 +3,27 @@ cards: true --- # 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) - 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](/spec/reactors/consensus). + +There is also a [stale markdown description](consensus.md) of the consensus state machine +(TODO update this). From eb9e1f961ce581d404f457d35a8df1f1d8f6e84f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:06:49 -0800 Subject: [PATCH 028/223] titles --- spec/consensus/light/README.md | 3 +++ spec/consensus/light/accountability.md | 2 +- spec/consensus/light/verification.md | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/consensus/light/README.md b/spec/consensus/light/README.md index c45875053..7a4d8840b 100644 --- a/spec/consensus/light/README.md +++ b/spec/consensus/light/README.md @@ -1,5 +1,8 @@ # Tendermint Light Client Protocol +NOTE: This specification is under heavy development and is not yet complete nor +accurate. + ## Contents - [Motivation](#motivation) diff --git a/spec/consensus/light/accountability.md b/spec/consensus/light/accountability.md index 2eb662aef..b605018d2 100644 --- a/spec/consensus/light/accountability.md +++ b/spec/consensus/light/accountability.md @@ -1,4 +1,4 @@ -# Fork accountability -- Problem statement and attacks +# Fork accountability ## Problem Statement diff --git a/spec/consensus/light/verification.md b/spec/consensus/light/verification.md index 8c07dfac8..66da23f12 100644 --- a/spec/consensus/light/verification.md +++ b/spec/consensus/light/verification.md @@ -1,4 +1,4 @@ -# Lite client +# Core Verification A lite client is a process that connects to Tendermint full node(s) and then tries to verify application data using the Merkle proofs. From e342c21336ad9e0dda0baabeff135c9bbfc362b0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:10:08 -0800 Subject: [PATCH 029/223] add redirects --- spec/consensus/fork-accountability.md | 3 +++ spec/consensus/light-client.md | 3 +++ spec/consensus/non-recursive-light-client.md | 3 +++ 3 files changed, 9 insertions(+) create mode 100644 spec/consensus/fork-accountability.md create mode 100644 spec/consensus/light-client.md create mode 100644 spec/consensus/non-recursive-light-client.md diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/fork-accountability.md new file mode 100644 index 000000000..7509819d4 --- /dev/null +++ b/spec/consensus/fork-accountability.md @@ -0,0 +1,3 @@ +# Fork Accountability - MOVED! + +Fork Accountability has moved to [light](./light). diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md new file mode 100644 index 000000000..db20d49b4 --- /dev/null +++ b/spec/consensus/light-client.md @@ -0,0 +1,3 @@ +# Light Client - MOVED! + +Light Client has moved to [light](./light). diff --git a/spec/consensus/non-recursive-light-client.md b/spec/consensus/non-recursive-light-client.md new file mode 100644 index 000000000..b95815f47 --- /dev/null +++ b/spec/consensus/non-recursive-light-client.md @@ -0,0 +1,3 @@ +# Non Recursive Verification - MOVED! + +Non Recursive verification has moved to [light](./light). From 035838901e5031033a69c33d34aa5229f473d8b4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:17:16 -0800 Subject: [PATCH 030/223] add diagram --- spec/consensus/light/README.md | 16 +++++++++++++++- .../consensus/light/assets/light-node-image.png | Bin 0 -> 31450 bytes 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 spec/consensus/light/assets/light-node-image.png diff --git a/spec/consensus/light/README.md b/spec/consensus/light/README.md index 7a4d8840b..79f04e9d0 100644 --- a/spec/consensus/light/README.md +++ b/spec/consensus/light/README.md @@ -28,6 +28,8 @@ transactions from "IBC relayers", who make RPC requests to full nodes on behalf ## Structure +### Components + The Tendermint Light Client consists of three primary components: - [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes @@ -40,8 +42,17 @@ be done by full nodes and validators, and is thus more accurately a component of the full node consensus protocol, though it is included here since it is primarily concerned with providing security to light clients. +A schematic of the core verification and fork detection components in +a Light Node are depicted below. The schematic is quite similar for other use cases. +Note fork accountability is not depicted, as it is the responsibility of the +full nodes. + +![Light Client Diagram](assets/light-node-image.png). + +### Synchrony + Light clients are fundamentally synchronous protocols, -where security is grounded ultimately in the interval during which a validator can be punished +where security is restricted by the interval during which a validator can be punished for byzantine behaviour. We assume here that such intervals have fixed and known minima referred to commonly as a blockchain's Unbonding Period. @@ -50,3 +61,6 @@ core verification, fork detection, and fork accountability - each with their own synchrony assumptions and fault model, can execute sequentially and to completion within the given Unbonding Period. +TODO: define all the synchrony parameters used in the protocol and their +relation to the Unbonding Period. + diff --git a/spec/consensus/light/assets/light-node-image.png b/spec/consensus/light/assets/light-node-image.png new file mode 100644 index 0000000000000000000000000000000000000000..24d80d71de3935459fba1cd1cff00601dbc41800 GIT binary patch literal 31450 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 D#fwd) literal 0 HcmV?d00001 From d1bd98d5e0561d3453821e47f548cf7a6d173f02 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:19:24 -0800 Subject: [PATCH 031/223] detection TODO --- spec/consensus/light/detection.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 spec/consensus/light/detection.md diff --git a/spec/consensus/light/detection.md b/spec/consensus/light/detection.md new file mode 100644 index 000000000..d0a0ad1da --- /dev/null +++ b/spec/consensus/light/detection.md @@ -0,0 +1,3 @@ +# Detection + +TODO From bd2f41bf798325a2dffa81b96fd10a3a34fc8138 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:49:09 -0800 Subject: [PATCH 032/223] fix image --- .../light/assets/light-node-image.png | Bin 31450 -> 122270 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/spec/consensus/light/assets/light-node-image.png b/spec/consensus/light/assets/light-node-image.png index 24d80d71de3935459fba1cd1cff00601dbc41800..f0b93c6e415ebc39bc2313c885faacb35aa7af9a 100644 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 31450 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 D#fwd) From c35d6e706f7a2affac0d7c6514f4bb49c4e163c7 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:52:54 -0800 Subject: [PATCH 033/223] update readme --- spec/consensus/light/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/consensus/light/README.md b/spec/consensus/light/README.md index 79f04e9d0..1747f9b6e 100644 --- a/spec/consensus/light/README.md +++ b/spec/consensus/light/README.md @@ -47,7 +47,7 @@ a Light Node are depicted below. The schematic is quite similar for other use ca Note fork accountability is not depicted, as it is the responsibility of the full nodes. -![Light Client Diagram](assets/light-node-image.png). +![Light Client Diagram](./assets/light-node-image.png). ### Synchrony From 026fddee4fcdd5fdf5d5dababfcfd45bbd2dece2 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 23 Jan 2020 14:45:47 +0100 Subject: [PATCH 034/223] Aligh the correctness arguments with the pseudocode changes --- spec/consensus/fork-accountability.md | 2 +- spec/consensus/light-client.md | 2 +- .../{light => light-client}/README.md | 14 +- .../{light => light-client}/accountability.md | 0 .../assets/light-node-image.png | Bin .../{light => light-client}/detection.md | 0 .../{light => light-client}/verification.md | 380 ++++++----- .../light/verification-non-recursive.md | 612 ------------------ spec/consensus/non-recursive-light-client.md | 3 - 9 files changed, 195 insertions(+), 818 deletions(-) rename spec/consensus/{light => light-client}/README.md (83%) rename spec/consensus/{light => light-client}/accountability.md (100%) rename spec/consensus/{light => light-client}/assets/light-node-image.png (100%) rename spec/consensus/{light => light-client}/detection.md (100%) rename spec/consensus/{light => light-client}/verification.md (51%) delete mode 100644 spec/consensus/light/verification-non-recursive.md delete mode 100644 spec/consensus/non-recursive-light-client.md diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/fork-accountability.md index 7509819d4..ea77fe87a 100644 --- a/spec/consensus/fork-accountability.md +++ b/spec/consensus/fork-accountability.md @@ -1,3 +1,3 @@ # Fork Accountability - MOVED! -Fork Accountability has moved to [light](./light). +Fork Accountability has moved to [light-client](./light-client/accountability). diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index db20d49b4..c3da2f79c 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -1,3 +1,3 @@ # Light Client - MOVED! -Light Client has moved to [light](./light). +Light Client has moved to [light-client](./light-client). diff --git a/spec/consensus/light/README.md b/spec/consensus/light-client/README.md similarity index 83% rename from spec/consensus/light/README.md rename to spec/consensus/light-client/README.md index 1747f9b6e..fe42e4972 100644 --- a/spec/consensus/light/README.md +++ b/spec/consensus/light-client/README.md @@ -15,9 +15,9 @@ accurate. The Tendermint Light Client is motivated by the need for a light weight protocol to sync with a Tendermint blockchain, with the least processing necessary to -securely verify a recent state. The protocol consists -primarily of checking hashes and verifying Tendermint commit signatures to -update trusted validator sets and committed block headers from the chain. +securely verify a recent state. The protocol consists of managing trusted validator +sets and trusted block headers, and is based primarily on checking hashes +and verifying Tendermint commit signatures. Motivating use cases include: @@ -33,8 +33,8 @@ transactions from "IBC relayers", who make RPC requests to full nodes on behalf The Tendermint Light Client consists of three primary components: - [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes -- [Fork Detection](./detection.md): talking to multiple peers to detect byzantine behaviour -- [Fork Accountability](./accountability.md): analyzing byzantine behaviour to hold validators accountable. +- [Fork Detection](./detection.md): talking to multiple peers to detect Byzantine behaviour +- [Fork Accountability](./accountability.md): analyzing Byzantine behaviour to hold validators accountable. While every light client must perform core verification and fork detection to achieve their prescribed security level, fork accountability is expected to @@ -44,7 +44,7 @@ primarily concerned with providing security to light clients. A schematic of the core verification and fork detection components in a Light Node are depicted below. The schematic is quite similar for other use cases. -Note fork accountability is not depicted, as it is the responsibility of the +Note that fork accountability is not depicted, as it is the responsibility of the full nodes. ![Light Client Diagram](./assets/light-node-image.png). @@ -53,7 +53,7 @@ full nodes. Light clients are fundamentally synchronous protocols, where security is restricted by the interval during which a validator can be punished -for byzantine behaviour. We assume here that such intervals have fixed and known minima +for Byzantine behaviour. We assume here that such intervals have fixed and known minimal duration referred to commonly as a blockchain's Unbonding Period. A secure light client must guarantee that all three components - diff --git a/spec/consensus/light/accountability.md b/spec/consensus/light-client/accountability.md similarity index 100% rename from spec/consensus/light/accountability.md rename to spec/consensus/light-client/accountability.md diff --git a/spec/consensus/light/assets/light-node-image.png b/spec/consensus/light-client/assets/light-node-image.png similarity index 100% rename from spec/consensus/light/assets/light-node-image.png rename to spec/consensus/light-client/assets/light-node-image.png diff --git a/spec/consensus/light/detection.md b/spec/consensus/light-client/detection.md similarity index 100% rename from spec/consensus/light/detection.md rename to spec/consensus/light-client/detection.md diff --git a/spec/consensus/light/verification.md b/spec/consensus/light-client/verification.md similarity index 51% rename from spec/consensus/light/verification.md rename to spec/consensus/light-client/verification.md index 66da23f12..55e87cd7c 100644 --- a/spec/consensus/light/verification.md +++ b/spec/consensus/light-client/verification.md @@ -1,17 +1,74 @@ # Core Verification -A lite client is a process that connects to Tendermint full node(s) and then tries to verify application -data using the Merkle proofs. - ## Problem statement -We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because -the lite 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*. +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 +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](./../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 @@ -22,6 +79,8 @@ In the following, only the details of the data structures needed for this specif 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 } @@ -49,7 +108,8 @@ In the following, only the details of the data structures needed for this specif ### Functions -For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: +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) @@ -69,7 +129,7 @@ Furthermore, we assume the following auxiliary functions: // it assumes signature verification so it can be computationally expensive func signers(commit Commit, validatorSet ValidatorSet) []Validator - // return the voting power the validators in v1 have according to their voting power in set v2 + // 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 @@ -77,50 +137,15 @@ Furthermore, we assume the following auxiliary functions: func hash(v2 ValidatorSet) []byte ``` -### Failure Model - -For the purpose of model definitions we assume that there exists a function -`validators` that returns the corresponding validator set for the given hash. -The lite client specification 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](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 lite 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 lite 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 lite 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 `PUNISHMENT_PERIOD`). -Furthermore, we assume that `TRUSTED_PERIOD < PUNISHMENT_PERIOD` and that they are normally of the same order of magnitude, for example -`TRUSTED_PERIOD = PUNISHMENT_PERIOD / 2`. Note that `PUNISHMENT_PERIOD` is often referred to as an -unbonding period due to the "bonding" mechanism in modern proof of stake systems. - -The specification in this document considers an implementation of the lite client under the Failure Model defined above. -Mechanisms like `fork accountability` and `evidence submission` are defined in the context of `PUNISHMENT_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](fork-accountability.md). - -*Remark*: This failure model might change to a hybrid version that takes heights into account in the future. - ### Functions 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 +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 assume `clockDrift` bound of the passed time `now`. +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. @@ -159,7 +184,7 @@ func VerifySingle(untrustedSh SignedHeader, return (newTrustedState, nil) } -// return true if header is within its lite client trusted period; otherwise returns false +// return true if header is within its light client trusted period; otherwise returns false func isWithinTrustedPeriod(header Header, trustingPeriod Duration, now Time) bool { @@ -173,6 +198,7 @@ is successfully verified) then we have a guarantee that the transition of the tr 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. @@ -250,7 +276,7 @@ func verifyCommitFull(vs ValidatorSet, commit Commit) error { ``` **VerifyHeaderAtHeight.** The function `VerifyHeaderAtHeight` captures high level -logic, i.e., application call to the lite client module to download and verify header +logic, i.e., application call to the light client module to download and verify header for some height. ```go @@ -290,6 +316,12 @@ is successfully verified) then we have a guarantee that the transition of the tr 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 @@ -369,217 +401,177 @@ func fatalError(err) bool { -### The case `untrusted_h.Header.height < trusted_h.Header.height` +### The case `untrustedHeader.Height < trustedHeader.Height` -In the use case where someone tells the lite client that application data that is relevant for it -can be read in the block of height `k` and the lite client trusts a more recent header, we can use the +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 lite client trusts two headers `i` and `j` with `i < k < j`, we should +*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 Backwards(trusted_h,untrusted_h) error { - assert (untrusted_h.Header.height < trusted_h.Header.height) - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) +func VerifyHeaderBackwards(trustedHeader Header, + untrustedHeader Header, + trustingPeriod Duration, + clockDrift Duration) error { - old := trusted_h - for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { - new := Commit(i) - if (hash(new) != old.Header.hash) { + 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 := new - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) + + old := untrustedSh.Header } - if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders + + if hash(untrustedHeader) != old.LastBlockID.Hash { + return ErrInvalidAdjacentHeaders + } + + now := System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + return nil } ``` -In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting -headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability -protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always -operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound -for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula -holds: -```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. - *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 lite client communicates with one full node -- the lite client locally stores all the headers that has passed basic verification and that are within lite client trust period. In the pseudo code below we +- 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 lite 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 lite client should disconnect and reinitialise with new peer. If the trusted header has expired, - we need to reinitialise lite client with new trusted header (that is within its trusted period), but we don't necessarily need to disconnect from the full node +- 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). - -## Context of this document - -In order to make sure that full nodes have the incentive to follow the protocol, we have to address the -following three Issues - -1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. - -2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). - -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). - -The term "trusting" above indicates that the correctness of the protocol depends on -this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk -of trusting a corrupted/forged *inithead* is negligible. - -* For each header *h* it has locally stored, the lite client stores whether - it trusts *h*. We write *trust(h) = true*, if this is the case. - -* signed header fields: contains a header and a *commit* for the current header; a "seen commit". - In Tendermint consensus the "canonical commit" is stored in header *height* + 1. - - - * Validator fields. We will write a validator as a tuple *(v,p)* such that - + *v* is the identifier (we assume identifiers are unique in each validator set) - + *p* is its voting power +## Correctness of the Light Client Protocols ### Definitions -* *TRUSTED_PERIOD*: trusting 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). +* `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 -### Tendermint Failure Model - -If a block *b* is generated at time *Time* (and this time is stored in the block), then 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```. +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 h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > -2/3 \sum_{(v,p) \in h.Header.NextV} p +\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 \] -## Lite Client Trusting Spec +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: -The lite 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`. -- 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. +- *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. -- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite 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*: If in the course of the computation, the lite 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*: In Completeness we use "eventually", while in practice *trust(h)* should be set to true before *h.Header.bfttime + tp*. If not, the block 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`. -*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. - -*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. +*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: Lite Client Invariant.* - For each lite client *l* and each header *h*: -if *l* has set *trust(h) = true*, - then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. +*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 h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > - 2/3 \sum_{(v,p) \in h.Header.NextV} p + \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 lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. - - -## High Level Solution - -Upon initialization, the lite client is given a header *inithead* it trusts (by -social consensus). It is assumed that *inithead* satisfies the lite client invariant. (If *inithead* has been correctly generated by Tendermint consensus, the invariant follows from the Tendermint Failure Model.) -Note that the *inithead* should be within its trusted period during initialization. - -When a lite clients sees a signed new 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 proof.** If a block is appended to the chain, where the last block -is trusted (and properly committed by the old validator set in the next block), -and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. -Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. - -2. **Trusting period.** Based on a trusted block *h*, and the lite client -invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. -If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. - -3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* 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 *snh*; if not, we may continue recursively. - -## How to use it - -We consider the following use case: - the lite client wants to verify a header for some given height *k*. Thus: - - it requests the signed header for height *k* from a full node - - it tries to verify this header with the methods described here. - -This can be used in several settings: - - someone tells the lite client that application data that is relevant for it can be read in the block of height *k*. - - the lite clients wants the latest state. It asks a full nude for the current height, and uses the response for *k*. - - in case of inter-blockchain communication protocol (IBC) the light client runs on a chain and someone feeds it - signed headers as input and it computes whether it can trust it. - +*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.Header.bfttime + tp > now*, we trust the old -validator set *h.Header.NextV*. +**Observation 1.** If `h.Time + TRUSTED_PERIOD > now`, we trust the validator set `validators(h.NextValidatorsHash)`. -When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* 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). +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. -*Correctness arguments* +*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)`. -Towards Lite Client Accuracy: -- Assume by contradiction that `untrusted_h` was not generated correctly and the lite client sets trust to true because `CheckSupport` returns true. -- trusted_h is trusted and sufficiently new -- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed `untrusted_h`. -- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing `untrusted_h` => `untrusted_h` was correctly generated, we arrive at the required contradiction. +*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. -Towards Lite Client Completeness: -- The check is successful if sufficiently many validators of `trusted_h` are still validators in `untrusted_h` and signed `untrusted_h`. -- If *untrusted_h.Header.height = trusted_h.Header.height + 1*, and both headers were generated correctly, the test passes +* `VerifyBisection` correctness arguments (sketch)* -*Verification Condition:* We may need a Tendermint invariant stating that if *untrusted_h.Header.height = trusted_h.Header.height + 1* then *signers(untrusted_h.Commit) \subseteq trusted_h.Header.NextV*. - -*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 CheckSupport returns true for non-adjacent headers. - - - - -*Correctness arguments (sketch)* - -Lite Client Accuracy: -- Assume by contradiction that `untrusted_h` was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil. -- CanTrustBisection returns true only if all calls to CheckSupport in the recursion return nil. -- Thus we have a sequence of headers that all satisfied the CheckSupport +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 -Lite Client Completeness: +light Client Completeness: -This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. +This is only ensured if upon `Commit(pivot)` the light client is always provided with a correctly generated header. *Stalling* -With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite 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 lite 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, Bisection would then be executed at the full node. -* We may set a timeout how long bisection may take. +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/consensus/light/verification-non-recursive.md b/spec/consensus/light/verification-non-recursive.md deleted file mode 100644 index 085f3f65c..000000000 --- a/spec/consensus/light/verification-non-recursive.md +++ /dev/null @@ -1,612 +0,0 @@ -# Lite client - -A lite client is a process that connects to Tendermint full node(s) and then tries to verify application -data using the Merkle proofs. - -## Problem statement - -We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because -the lite 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. - -## 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 - - // hashes from the app output from the prev block - ValidatorsHash []byte // hash of the validators for the current block - NextValidatorsHash []byte // hash of the validators for the next block - - // hashes of block data - LastCommitHash []byte // hash of the commit from validators from the last 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 lite 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 the validator set for the given validator hash - func validators(validatorsHash []byte) ValidatorSet - - // returns true if commit corresponds to the block data in 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 - // it does not assume signature verification - func signers(commit Commit, validatorSet ValidatorSet) []Validator - - // return the voting power the validators in v1 have according to their voting power in set v2 - // it assumes signature verification so it can be computationally expensive - func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 - - // add this state as trusted to the store - func add(store Store, trustedState TrustedState) error - - // retrieve the trusted state at given height if it exists (error = nil) - // return an error if there are no trusted state for the given height - // if height = 0, return the latest trusted state - func get(store Store, height int64) (TrustedState, error) -``` - - -**VerifyHeaderAtHeight.** TODO. -```go -func VerifyHeaderAtHeight(untrustedHeight int64, - trustThreshold TrustThreshold, - trustingPeriod Duration, - clockDrift Duration, - store Store) (error, (TrustedState, Time)) { - - now := System.Time() - initTrustedState, newTrustedState, err := VerifyAndUpdateNonRecursive(untrustedHeight, - trustThreshold, - trustingPeriod, - clockDrift, - now, - store Store) - - if err != nil return err - - now = System.Time() - if !isWithinTrustedPeriod(initTrustedState.SignedHeader.Header, trustingPeriod, now) { - return ErrHeaderNotWithinTrustedPeriod - } - return nil, (newTrustedState, now) -} -``` - -If we get some trustedState at time t (now = t), - - -**VerifyAndUpdateNonRecursive.** TODO. -```go -func VerifyAndUpdateNonRecursive(untrustedHeight int64, - trustThreshold TrustThreshold, - trustingPeriod Duration, - clockDrift Duration, - now Time, - store Store) error { - - // fetch the latest state and ensure it hasn't expired - trustedState, error = get(store, 0) - if error != nil return error - - trustedSh = trustedState.SignedHeader - trustedHeader = trustedSh.Header - assert trustedHeader.Height < untrustedHeight AND - trustedHeader.Time < now - - if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { - return ErrHeaderNotWithinTrustedPeriod - } - - th := trustedHeader // th is trusted header - - untrustedSh, error := Commit(untrustedHeight) - if error != nil return error - - untrustedHeader = untrustedSh.Header - assert untrustedHeader.Time < now + clockDrift - - untrustedVs, error := Validators(untrustedHeight) - if error != nil return error - - untrustedNextVs, error := Validators(untrustedHeight + 1) - if error != nil return error - - // untrustedHeader is a list of headers that have not passed verifySingle - untrustedHeaders := [untrustedHeader] - - while true { - for h in untrustedHeaders { - // we assume here that iteration is done in the order of header heights - error = verifySingle( - trustedState, - untrustedSh, - untrustedVs, - untrustedNextVs, - trustThreshold) - - if err == nil { - // the untrusted header is now trusted. update the store - trustedState = TrustedState(untrustedSh, untrustedNextVs) - add(store, trustedState) - - untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if trustedState.SignedHeader.Header == untrustedSh.Header { - return nil - } - } - if fatalError(err) { return err } - } - - endHeight = min(untrustedHeaders) - while true { - trustedSh = trustedState.SignedHeader - trustedHeader = trustedSh.Header - pivotHeight := ceil((trustedHeader.Height + endHeight) / 2) - - untrustedSh, error := Commit(pivotHeight) - if error != nil return error - - untrustedHeader = untrustedSh.Header - assert untrustedHeader.Time < now + clockDrift - - untrustedVs, error := Validators(untrustedHeight) - if error != nil return error - - untrustedNextVs, error := Validators(untrustedHeight + 1) - if error != nil return error - - error = verifySingle( - trustedState, - untrustedSh, - untrustedVs, - untrustedNextVs, - trustThreshold) - - if fatalError(error) return error - - if err == nil { - trustedState = TrustedState(untrustedSh, untrustedNextVs) - add(store, trustedState) - break - } - - untrustedHeaders.add(untrustedHeader) - endHeight = pivot - } - } - return nil // this line should never be reached -} -``` - - - -The function `CanTrust` checks whether to trust header `untrusted_h` based on the trusted header `trusted_h` It does so by (potentially) -building transitive trust relation between `trusted_h` and `untrusted_h`, over some intermediate headers. For example, in case we cannot trust -header `untrusted_h` based on the trusted header `trusted_h`, the function `CanTrust` will try to find headers such that we can transition trust -from `trusted_h` over intermediate headers to `untrusted_h`. We will give two implementations of `CanTrust`, the one based -on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier -to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. - -Both implementations of `CanTrust` function are based on `CheckSupport` function that implements the skipping conditions under which we can trust a -header `untrusted_h` given the trust in the header `trusted_h` as a single step, -i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. - - -```go -// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error -// where error captures the nature of the error. -// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. -func CanTrust(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height - - th := trusted_h // th is trusted header - // untrustedHeader is a list of (?) verified headers that have not passed CheckSupport() - untrustedHeaders := [untrusted_h] - - while true { - for h in untrustedHeaders { - // we assume here that iteration is done in the order of header heights - err = CheckSupport(th,h,trustThreshold) - if err == nil { - if !verify(h) { return ErrInvalidHeader(h) } - th = h - Store.Add(h) - untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if th == untrusted_h { return nil } - } - if fatalCheckSupportError(err) { return err } - } - - endHeight = min(untrustedHeaders) - while true { - pivot := ceil((th.Header.height + endHeight) / 2) - hp := Commit(pivot) - // try to move trusted header forward to hp - err = CheckSupport(th,hp,trustThreshold) - if fatalCheckSupportError(err) return err - if err == nil { - if !verify(hp) { return ErrInvalidHeader(hp) } - th = hp - Store.Add(th) - break - } - untrustedHeaders.add(hp) - endHeight = pivot - } - } - return nil // this line should never be reached -} - -func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - - // Although while executing the rest of CheckSupport function, trusted_h can expire based - // on the lite client trusted period, this is not problem as lite client trusted - // period is smaller than trusted period of the header based on Tendermint Failure - // model, i.e., there is a significant time period (measure in days) during which - // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. - - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders - } - - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) - - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange -} - -func fatalCheckSupportError(err) bool { - return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders -``` - -```go -func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { - assume trusted_h.Header.Height < untrusted_h.header.Height - - err = CheckSupport(trusted_h,untrusted_h,trustThreshold) - if err == nil { - Store.Add(untrusted_h) - return nil - } - if err != ErrTooMuchChange return err - - pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 - hp := Commit(pivot) - if !verify(hp) return ErrInvalidHeader(hp) - - err = CanTrustBisection(trusted_h,hp,trustThreshold) - if err == nil { - Store.Add(hp) - err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) - if err2 == nil { - Store.Add(untrusted_h) - return nil - } - return err2 - } - return err -} -``` - -**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header untrusted_h based on header trusted_h. -Time validity of a header is captured by the ```isWithinTrustedPeriod``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns -true in case the header is within its lite client trusted period. -```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set. - -```go - // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation - // (function `verify`). - // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. - // ErrHeaderNotWithinTrustedPeriod is used when trusted_h has expired with respect to lite client trusted period, - // ErrInvalidAdjacentHeaders when that adjacent headers are not consistent and - // ErrTooMuchChange when there is not enough intersection between validator sets to have - // skipping condition true. - func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - - // Although while executing the rest of CheckSupport function, trusted_h can expire based - // on the lite client trusted period, this is not problem as lite client trusted - // period is smaller than trusted period of the header based on Tendermint Failure - // model, i.e., there is a significant time period (measure in days) during which - // validator set that has signed trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. - - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders - } - - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) - - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange - } -``` - - -### The case `untrusted_h.Header.height < trusted_h.Header.height` - -In the use case where someone tells the lite client that application data that is relevant for it -can be read in the block of height `k` and the lite 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 lite 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 Backwards(trusted_h,untrusted_h) error { - assert (untrusted_h.Header.height < trusted_h.Header.height) - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - - old := trusted_h - for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { - new := Commit(i) - if (hash(new) != old.Header.hash) { - return ErrInvalidAdjacentHeaders - } - old := new - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - } - if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders - return nil - } -``` - - - -In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting -headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability -protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always -operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound -for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula -holds: -```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. - - -*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 lite client communicates with one full node -- the lite client locally stores all the headers that has passed basic verification and that are within lite 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 lite 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 lite client should disconnect and reinitialise with new peer. If the trusted header has expired, - we need to reinitialise lite 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). - - - -## Context of this document - -In order to make sure that full nodes have the incentive to follow the protocol, we have to address the -following three Issues - -1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. - -2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). - -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). - -The term "trusting" above indicates that the correctness of the protocol depends on -this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk -of trusting a corrupted/forged *inithead* is negligible. - -* For each header *h* it has locally stored, the lite client stores whether - it trusts *h*. We write *trust(h) = true*, if this is the case. - -* signed header fields: contains a header and a *commit* for the current header; a "seen commit". - In Tendermint consensus the "canonical commit" is stored in header *height* + 1. - - - * Validator fields. We will write a validator as a tuple *(v,p)* such that - + *v* is the identifier (we assume identifiers are unique in each validator set) - + *p* is its voting power - -### Definitions - -* *TRUSTED_PERIOD*: trusting 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). - - -### Tendermint Failure Model - -If a block *b* is generated at time *Time* (and this time is stored in the block), then 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```. - -Formally, -\[ -\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > -2/3 \sum_{(v,p) \in h.Header.NextV} p -\] - - -## Lite Client Trusting Spec - -The lite 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: - -- 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. - -- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. - -*Remark*: If in the course of the computation, the lite 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.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. - -*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. - -*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. - -To reason about the correctness, we may prove the following invariant. - -*Verification Condition: Lite Client Invariant.* - For each lite client *l* and each header *h*: -if *l* has set *trust(h) = true*, - then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. - - Formally, - \[ - \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > - 2/3 \sum_{(v,p) \in h.Header.NextV} p - \] - -*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. - - -## High Level Solution - -Upon initialization, the lite client is given a header *inithead* it trusts (by -social consensus). It is assumed that *inithead* satisfies the lite client invariant. (If *inithead* has been correctly generated by Tendermint consensus, the invariant follows from the Tendermint Failure Model.) -Note that the *inithead* should be within its trusted period during initialization. - -When a lite clients sees a signed new 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 proof.** If a block is appended to the chain, where the last block -is trusted (and properly committed by the old validator set in the next block), -and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. -Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. - -2. **Trusting period.** Based on a trusted block *h*, and the lite client -invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. -If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. - -3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* 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 *snh*; if not, we may continue recursively. - -## How to use it - -We consider the following use case: - the lite client wants to verify a header for some given height *k*. Thus: - - it requests the signed header for height *k* from a full node - - it tries to verify this header with the methods described here. - -This can be used in several settings: - - someone tells the lite client that application data that is relevant for it can be read in the block of height *k*. - - the lite clients wants the latest state. It asks a full nude for the current height, and uses the response for *k*. - - in case of inter-blockchain communication protocol (IBC) the light client runs on a chain and someone feeds it - signed headers as input and it computes whether it can trust it. - - -## Details - -**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old -validator set *h.Header.NextV*. - -When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* 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). - - - -*Correctness arguments* - -Towards Lite Client Accuracy: -- Assume by contradiction that `untrusted_h` was not generated correctly and the lite client sets trust to true because `CheckSupport` returns true. -- trusted_h is trusted and sufficiently new -- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed `untrusted_h`. -- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing `untrusted_h` => `untrusted_h` was correctly generated, we arrive at the required contradiction. - - -Towards Lite Client Completeness: -- The check is successful if sufficiently many validators of `trusted_h` are still validators in `untrusted_h` and signed `untrusted_h`. -- If *untrusted_h.Header.height = trusted_h.Header.height + 1*, and both headers were generated correctly, the test passes - -*Verification Condition:* We may need a Tendermint invariant stating that if *untrusted_h.Header.height = trusted_h.Header.height + 1* then *signers(untrusted_h.Commit) \subseteq trusted_h.Header.NextV*. - -*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 CheckSupport returns true for non-adjacent headers. - - - - -*Correctness arguments (sketch)* - -Lite Client Accuracy: -- Assume by contradiction that `untrusted_h` was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil. -- CanTrustBisection returns true only if all calls to CheckSupport in the recursion return nil. -- Thus we have a sequence of headers that all satisfied the CheckSupport -- again a contradiction - -Lite Client Completeness: - -This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. - -*Stalling* - -With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite 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 lite 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, Bisection would then be executed at the full node. -* We may set a timeout how long bisection may take. - - - - - diff --git a/spec/consensus/non-recursive-light-client.md b/spec/consensus/non-recursive-light-client.md deleted file mode 100644 index b95815f47..000000000 --- a/spec/consensus/non-recursive-light-client.md +++ /dev/null @@ -1,3 +0,0 @@ -# Non Recursive Verification - MOVED! - -Non Recursive verification has moved to [light](./light). From 603364bdaaa4448b6a4f9618e0bab5ee22fc8b1f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 23 Jan 2020 09:22:40 -0800 Subject: [PATCH 035/223] lite->light --- spec/consensus/light-client/accountability.md | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/spec/consensus/light-client/accountability.md b/spec/consensus/light-client/accountability.md index b605018d2..29884cec5 100644 --- a/spec/consensus/light-client/accountability.md +++ b/spec/consensus/light-client/accountability.md @@ -14,11 +14,11 @@ 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 (lite clients) that the blocks had been correctly generated. In addition, Tendermint agreement might be violated in the case where more than 1/3 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. +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 more than 1/3 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 Lite 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 Lite Client 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. @@ -49,9 +49,9 @@ Independently of a fork happening, punishing this behavior might be important to * 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 lite clients. However, even without breaking this system invariant, lite clients can be subject to a fork: +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-Lite. 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 lite client). +* 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 @@ -77,7 +77,7 @@ Some correct validators might have decided on *v* in *r*, and other correct vali * 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-Lite). +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). @@ -85,7 +85,7 @@ However, faulty validators may use the correct precommit messages from round *r* ## 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 lite clients. +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). @@ -98,24 +98,24 @@ We consider three types of potential attack victims: - FN: full node -- LCS: lite client with sequential header verification -- LCB: lite client with bisection based header verification +- 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 lite clients. +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, lite 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-Lite), as they trust a header that is signed by at least one correct validator (trusting period method). +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 lite client (LCS or LCB) contacts a full node to obtain headers (or blocks), the corrupted state may propagate to the lite client. +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). @@ -130,7 +130,7 @@ F4 and F5 are *off-chain*, that is, these attacks cannot be used to corrupt the -**Q:** Lite 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? +**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. @@ -143,7 +143,7 @@ contaminated by an attack if the blockchain itself violates its invariant (one b ### 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 lite clients. It requires more than 1/3 of voting power to be executed. +round of some height. This attack can be executed on both full nodes and light clients. It requires more than 1/3 of voting power to be executed. #### Scenario 1: Equivocation on the main chain @@ -175,10 +175,10 @@ Consequences: * 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 lite clients, +* It extends also to the light clients, * For both we need a detection and recovery mechanism. -#### Scenario 2: Equivocation to a lite client (LCS) +#### Scenario 2: Equivocation to a light client (LCS) Validators: @@ -187,24 +187,24 @@ Validators: 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 lite clients obtains B and trusts at as it is signed by more than 2/3 of the voting power. +* 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 lite 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 lite client is fooled by a fork, that means that an attacker can change application state and validator set arbitrarily. +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 lite 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). +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 lite 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 lite 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 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 lite client about arbitrary state. In case of LCB where security threshold is at minimum, an attacker can arbitrarily modify application state with more than 1/3 of voting power, while in case of LCS it requires more than 2/3 of the voting power. +*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 more than 1/3 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 lite clients. +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 @@ -226,7 +226,7 @@ Execution: Detecting faulty validators in the case of such an attack can be done by the fork accountability mechanism described in: https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit?usp=sharing. -If a lite client is attacked using this attack with more than 1/3 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 lite client trusts deviates from the one on the main chain. +If a light client is attacked using this attack with more than 1/3 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 @@ -255,7 +255,7 @@ Only in case they signed something which conflicts with the application this can ### 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 lite clients. +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: @@ -287,7 +287,7 @@ Consequences: ### 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 lite clients. +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: @@ -300,7 +300,7 @@ Execution: - VS2 on the main chain - forged header VS2', signed by F (and others) -* a lite client has a trust in a header for height *h* (and the corresponding validator set VS1). +* 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: @@ -312,7 +312,7 @@ Consequences: ### Lunatic validator -Lunatic validator agrees to sign commit messages for arbitrary application state. It is used to attack lite clients. +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. From 576e40eabdb1ed07b399c64322bf8785c80109b1 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Fri, 24 Jan 2020 15:17:04 +0100 Subject: [PATCH 036/223] Fix link in readme ./light -> ./light-client --- spec/consensus/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md index 32d5579be..85e27074c 100644 --- a/spec/consensus/readme.md +++ b/spec/consensus/readme.md @@ -15,7 +15,7 @@ Specification of the Tendermint consensus protocol. 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) - A protocol for light weight 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. From 452f0b775addf18a52eabf76b0889b09b86fe8f6 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Sun, 26 Jan 2020 23:34:53 -0800 Subject: [PATCH 037/223] 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 --- spec/p2p/peer.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/spec/p2p/peer.md b/spec/p2p/peer.md index f5c2e7bf2..ec449027e 100644 --- a/spec/p2p/peer.md +++ b/spec/p2p/peer.md @@ -28,22 +28,33 @@ Both handshakes have configurable timeouts (they should complete quickly). Tendermint implements the Station-to-Station protocol using X25519 keys for Diffie-Helman key-exchange and chacha20poly1305 for encryption. + +Previous versions of this protocol suffered from malleability attacks whereas an active man +in the middle attacker could compromise confidentiality as decribed 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 96 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 the last 32 bytes of output for the challenge + - 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 1024 byte frames, 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 From 56ffcf709aa91157091e8cd78472e5d4f23c1a6c Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 3 Feb 2020 18:37:09 +0100 Subject: [PATCH 038/223] 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 --- spec/abci/abci.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index e48b711b3..492629dbb 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -64,26 +64,26 @@ Example: Events: []abci.Event{ { Type: "validator.provisions", - Attributes: cmn.KVPairs{ - cmn.KVPair{Key: []byte("address"), Value: []byte("...")}, - cmn.KVPair{Key: []byte("amount"), Value: []byte("...")}, - cmn.KVPair{Key: []byte("balance"), Value: []byte("...")}, + Attributes: kv.Pairs{ + kv.Pair{Key: []byte("address"), Value: []byte("...")}, + kv.Pair{Key: []byte("amount"), Value: []byte("...")}, + kv.Pair{Key: []byte("balance"), Value: []byte("...")}, }, }, { Type: "validator.provisions", - Attributes: cmn.KVPairs{ - cmn.KVPair{Key: []byte("address"), Value: []byte("...")}, - cmn.KVPair{Key: []byte("amount"), Value: []byte("...")}, - cmn.KVPair{Key: []byte("balance"), Value: []byte("...")}, + Attributes: kv.Pairs{ + kv.Pair{Key: []byte("address"), Value: []byte("...")}, + kv.Pair{Key: []byte("amount"), Value: []byte("...")}, + kv.Pair{Key: []byte("balance"), Value: []byte("...")}, }, }, { Type: "validator.slashed", - Attributes: cmn.KVPairs{ - cmn.KVPair{Key: []byte("address"), Value: []byte("...")}, - cmn.KVPair{Key: []byte("amount"), Value: []byte("...")}, - cmn.KVPair{Key: []byte("reason"), Value: []byte("...")}, + Attributes: kv.Pairs{ + kv.Pair{Key: []byte("address"), Value: []byte("...")}, + kv.Pair{Key: []byte("amount"), Value: []byte("...")}, + kv.Pair{Key: []byte("reason"), Value: []byte("...")}, }, }, // ... @@ -282,7 +282,7 @@ Commit are included in the header of the next block. - `ByzantineValidators ([]Evidence)`: List of evidence of validators that acted maliciously. - **Response**: - - `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing + - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing - **Usage**: - Signals the beginning of a new block. Called prior to any DeliverTxs. @@ -309,7 +309,7 @@ Commit are included in the header of the next block. be non-deterministic. - `GasWanted (int64)`: Amount of gas requested for transaction. - `GasUsed (int64)`: Amount of gas consumed by transaction. - - `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing + - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing transactions (eg. by account). - `Codespace (string)`: Namespace for the `Code`. - **Usage**: @@ -337,7 +337,7 @@ Commit are included in the header of the next block. be non-deterministic. - `GasWanted (int64)`: Amount of gas requested for transaction. - `GasUsed (int64)`: Amount of gas consumed by transaction. - - `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing + - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing transactions (eg. by account). - `Codespace (string)`: Namespace for the `Code`. - **Usage**: @@ -354,7 +354,7 @@ Commit are included in the header of the next block. voting power to 0 to remove). - `ConsensusParamUpdates (ConsensusParams)`: Changes to consensus-critical time, size, and other parameters. - - `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing + - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing - **Usage**: - Signals the end of a block. - Called after all transactions, prior to each Commit. From b0f35a64d9ab146b0510c73a0bf111960fb5f7e4 Mon Sep 17 00:00:00 2001 From: Marko Date: Sun, 23 Feb 2020 14:09:58 +0100 Subject: [PATCH 039/223] 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 --- spec/abci/abci.md | 7 ++++--- spec/blockchain/state.md | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 492629dbb..74cf4d0c4 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -501,11 +501,12 @@ Commit are included in the header of the next block. ### EvidenceParams - **Fields**: - - `MaxAge (int64)`: Max age of evidence, in blocks. Evidence older than this - is considered stale and ignored. + - `MaxAgeNumBlocks (int64)`: Max age of evidence, in blocks. + - `MaxAgeDuration (time.Duration)`: Max age of evidence, in time. + Evidence older than this is considered stale and ignored. + - This should correspond with an app's "unbonding period" or other similar mechanism for handling Nothing-At-Stake attacks. - - NOTE: this should change to time (instead of blocks)! ### ValidatorParams diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md index 0430819fa..535eee8b2 100644 --- a/spec/blockchain/state.md +++ b/spec/blockchain/state.md @@ -110,7 +110,8 @@ type BlockParams struct { } type EvidenceParams struct { - MaxAge int64 + MaxAgeNumBlocks int64 + MaxAgeDuration time.Duration } type ValidatorParams struct { @@ -135,7 +136,8 @@ The minimal time between consecutive blocks is controlled by the For evidence in a block to be valid, it must satisfy: ``` -block.Header.Height - evidence.Height < ConsensusParams.Evidence.MaxAge +block.Header.Height - evidence.Height < ConsensusParams.Evidence.MaxAgeNumBlocks +block.Header.Time - evidence.Time < ConsensusParams.Evidence.MaxAgeDuration ``` #### Validator From edb49283571087de6aaabad6fabc0b5e5abb716c Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 10 Mar 2020 14:41:17 +0100 Subject: [PATCH 040/223] update link to the pex reactor --- spec/p2p/peer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/p2p/peer.md b/spec/p2p/peer.md index ec449027e..df3221fab 100644 --- a/spec/p2p/peer.md +++ b/spec/p2p/peer.md @@ -2,7 +2,7 @@ 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/tendermint/blob/master/docs/spec/reactors/pex/pex.md). +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 From ee5c79087820e5272c5bb1bda38d42ef0c50bca8 Mon Sep 17 00:00:00 2001 From: Callum Michael Waters Date: Tue, 10 Mar 2020 15:32:51 +0100 Subject: [PATCH 041/223] add markdown link checker --- .github/workflows/action.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/action.yml diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml new file mode 100644 index 000000000..ee2fd4899 --- /dev/null +++ b/.github/workflows/action.yml @@ -0,0 +1,10 @@ +name: Check Markdown links +on: push +jobs: + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: gaurav-nelson/github-action-markdown-link-check@0.6.0 + with: + folder-path: "spec" From e963deff5a00bbe08ddd7c8a99a4cf9ca2129ba9 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 10 Mar 2020 15:48:31 +0100 Subject: [PATCH 042/223] changed tab spacing --- .github/workflows/action.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index ee2fd4899..085332724 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -1,10 +1,12 @@ name: Check Markdown links + on: push + jobs: - markdown-link-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: gaurav-nelson/github-action-markdown-link-check@0.6.0 - with: - folder-path: "spec" + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: gaurav-nelson/github-action-markdown-link-check@0.6.0 + with: + folder-path: 'spec' From 4f0fb3325ad640ecaa2d56ff28e3766a8c6c432f Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 10 Mar 2020 15:51:23 +0100 Subject: [PATCH 043/223] removed folder-path flag --- .github/workflows/action.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 085332724..26993168d 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -8,5 +8,3 @@ jobs: steps: - uses: actions/checkout@master - uses: gaurav-nelson/github-action-markdown-link-check@0.6.0 - with: - folder-path: 'spec' From c9a664a2f842d2c9862ce993212261043bf81ab2 Mon Sep 17 00:00:00 2001 From: Callum Michael Waters Date: Tue, 10 Mar 2020 16:15:59 +0100 Subject: [PATCH 044/223] first attempt at fixing all links --- spec/README.md | 2 +- spec/consensus/fork-accountability.md | 3 --- spec/consensus/light-client.md | 3 --- spec/consensus/readme.md | 2 +- spec/p2p/node.md | 3 ++- spec/reactors/consensus/consensus.md | 7 +++++-- spec/reactors/mempool/reactor.md | 2 +- 7 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 spec/consensus/fork-accountability.md delete mode 100644 spec/consensus/light-client.md diff --git a/spec/README.md b/spec/README.md index a0a67f7b5..4ed12aef3 100644 --- a/spec/README.md +++ b/spec/README.md @@ -31,7 +31,7 @@ please submit them to our [bug bounty](https://tendermint.com/security)! - [Consensus Algorithm](./consensus/consensus.md) - [Creating a proposal](./consensus/creating-proposal.md) - [Time](./consensus/bft-time.md) -- [Light-Client](./consensus/light-client.md) +- [Light-Client](./consensus/light-client/README.md) ### P2P and Network Protocols diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/fork-accountability.md deleted file mode 100644 index ea77fe87a..000000000 --- a/spec/consensus/fork-accountability.md +++ /dev/null @@ -1,3 +0,0 @@ -# Fork Accountability - MOVED! - -Fork Accountability has moved to [light-client](./light-client/accountability). diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md deleted file mode 100644 index c3da2f79c..000000000 --- a/spec/consensus/light-client.md +++ /dev/null @@ -1,3 +0,0 @@ -# Light Client - MOVED! - -Light Client has moved to [light-client](./light-client). diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md index 85e27074c..6c0bb466a 100644 --- a/spec/consensus/readme.md +++ b/spec/consensus/readme.md @@ -23,7 +23,7 @@ Specification of the Tendermint consensus protocol. 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](/spec/reactors/consensus). +for liveness, is described in the [reactors section](/spec/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/p2p/node.md b/spec/p2p/node.md index 6d37eeb78..0bbcf0b5b 100644 --- a/spec/p2p/node.md +++ b/spec/p2p/node.md @@ -12,7 +12,8 @@ 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/tendermint/blob/master/docs/spec/reactors/pex/pex.md)for details on peer quality. +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 diff --git a/spec/reactors/consensus/consensus.md b/spec/reactors/consensus/consensus.md index 55960874a..ff15af746 100644 --- a/spec/reactors/consensus/consensus.md +++ b/spec/reactors/consensus/consensus.md @@ -16,7 +16,8 @@ explained in a forthcoming document. For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the block as the block size is big, i.e., they don't embed the block inside `Proposal` and `VoteMessage`. Instead, they reach agreement on the `BlockID` (see `BlockID` definition in -[Blockchain](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#blockid) section) that uniquely identifies each block. The block itself is +[Blockchain](https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.md#blockid) section) +that uniquely identifies each block. The block itself is disseminated to validator processes using peer-to-peer gossiping protocol. It starts by having a proposer first splitting a block into a number of block parts, that are then gossiped between processes using `BlockPartMessage`. @@ -65,7 +66,9 @@ type Proposal struct { ## VoteMessage VoteMessage 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/tendermint/blob/master/docs/spec/blockchain/blockchain.md#blockid) section and contains validator's +current round). Vote is defined in the +[Blockchain](https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.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. diff --git a/spec/reactors/mempool/reactor.md b/spec/reactors/mempool/reactor.md index 7e9a2d8fe..66e5d826b 100644 --- a/spec/reactors/mempool/reactor.md +++ b/spec/reactors/mempool/reactor.md @@ -7,7 +7,7 @@ See [this issue](https://github.com/tendermint/tendermint/issues/1503) Mempool maintains a cache of the last 10000 transactions to prevent replaying old transactions (plus transactions coming from other validators, who are continually exchanging transactions). Read [Replay -Protection](../../../app-dev/app-development.md#replay-protection) +Protection](https://github.com/tendermint/tendermint/blob/master/docs/app-dev/app-development.md#replay-protection) for details. Sending incorrectly encoded data or data exceeding `maxMsgSize` will result From 3f04e8bbce9a863c580275d0becd0514d24afa1e Mon Sep 17 00:00:00 2001 From: Callum Michael Waters Date: Tue, 10 Mar 2020 16:22:14 +0100 Subject: [PATCH 045/223] second attempt at fixing all links --- spec/consensus/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md index 6c0bb466a..8bf4e68bd 100644 --- a/spec/consensus/readme.md +++ b/spec/consensus/readme.md @@ -23,7 +23,7 @@ Specification of the Tendermint consensus protocol. 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](/spec/reactors/consensus/consensus.md). +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). From d45389e2b01439c34be32d0356a2247e12de8a6c Mon Sep 17 00:00:00 2001 From: Marko Date: Sun, 15 Mar 2020 17:37:25 +0100 Subject: [PATCH 046/223] 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 --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..7a801d67b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# CODEOWNERS: https://help.github.com/articles/about-codeowners/ + +* @milosevic @ebuchman @josef-widder @konnov From 1152120dea3fb8dc46f323da9e5c3cf156493f48 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 19 Mar 2020 12:20:44 +0100 Subject: [PATCH 047/223] remove .idea dir (#83) Signed-off-by: Marko Baricevic --- .idea/vcs.xml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfb..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 3ab6026ad70b55ba499c10314701507d0c1b005d Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 2 Apr 2020 15:38:00 +0200 Subject: [PATCH 048/223] 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 --- rfc/001-block-retention.md | 108 +++++++++++++++++++++++++++++++++ rfc/images/block-retention.png | Bin 0 -> 53718 bytes 2 files changed, 108 insertions(+) create mode 100644 rfc/001-block-retention.md create mode 100644 rfc/images/block-retention.png diff --git a/rfc/001-block-retention.md b/rfc/001-block-retention.md new file mode 100644 index 000000000..43cfa9d14 --- /dev/null +++ b/rfc/001-block-retention.md @@ -0,0 +1,108 @@ +# RFC 001: Configurable Block Retention + +## Changelog + +- 2020-03-23: Initial draft (@erikgrinaker) +- 2020-03-25: Use local config for snapshot interval (@erikgrinaker) +- 2020-03-31: Use ABCI commit response for block retention hint +- 2020-04-02: Resolved open questions + +## Author(s) + +- Erik Grinaker (@erikgrinaker) + +## Context + +Currently, all Tendermint nodes contain the complete sequence of blocks from genesis up to some height (typically the latest chain height). This will no longer be true when the following features are released: + +* [Block pruning](https://github.com/tendermint/tendermint/issues/3652): removes historical blocks and associated data (e.g. validator sets) up to some height, keeping only the most recent blocks. + +* [State sync](https://github.com/tendermint/tendermint/issues/828): bootstraps a new node by syncing state machine snapshots at a given height, but not historical blocks and associated data. + +To maintain the integrity of the chain, the use of these features must be coordinated such that necessary historical blocks will not become unavailable or lost forever. In particular: + +* Some nodes should have complete block histories, for auditability, querying, and bootstrapping. + +* The majority of nodes should retain blocks longer than the Cosmos SDK unbonding period, for light client verification. + +* Some nodes must take and serve state sync snapshots with snapshot intervals less than the block retention periods, to allow new nodes to state sync and then replay blocks to catch up. + +* Applications may not persist their state on commit, and require block replay on restart. + +* Only a minority of nodes can be state synced within the unbonding period, for light client verification and to serve block histories for catch-up. + +However, it is unclear if and how we should enforce this. It may not be possible to technically enforce all of these without knowing the state of the entire network, but it may also be unrealistic to expect this to be enforced entirely through social coordination. This is especially unfortunate since the consequences of misconfiguration can be permanent chain-wide data loss. + +## Proposal + +Add a new field `retain_height` to the ABCI `ResponseCommit` message: + +```proto +service ABCIApplication { + rpc Commit(RequestCommit) returns (ResponseCommit); +} + +message RequestCommit {} + +message ResponseCommit { + // reserve 1 + bytes data = 2; // the Merkle root hash + uint64 retain_height = 3; // the oldest block height to retain +} +``` + +Upon ABCI `Commit`, which finalizes execution of a block in the state machine, Tendermint removes all data for heights lower than `retain_height`. This allows the state machine to control block retention, which is preferable since only it can determine the significance of historical blocks. By default (i.e. with `retain_height=0`) all historical blocks are retained. + +Removed data includes not only blocks, but also headers, commit info, consensus params, validator sets, and so on. In the first iteration this will be done synchronously, since the number of heights removed for each run is assumed to be small (often 1) in the typical case. It can be made asynchronous at a later time if this is shown to be necessary. + +Since `retain_height` is dynamic, it is possible for it to refer to a height which has already been removed. For example, commit at height 100 may return `retain_height=90` while commit at height 101 may return `retain_height=80`. This is allowed, and will be ignored - it is the application's responsibility to return appropriate values. + +State sync will eventually support backfilling heights, via e.g. a snapshot metadata field `backfill_height`, but in the initial version it will have a fully truncated block history. + +## Cosmos SDK Example + +As an example, we'll consider how the Cosmos SDK might make use of this. The specific details should be discussed in a separate SDK proposal. + +The returned `retain_height` would be the lowest height that satisfies: + +* Unbonding time: the time interval in which validators can be economically punished for misbehavior. Blocks in this interval must be auditable e.g. by the light client. + +* IAVL snapshot interval: the block interval at which the underlying IAVL database is persisted to disk, e.g. every 10000 heights. Blocks since the last IAVL snapshot must be available for replay on application restart. + +* State sync snapshots: blocks since the _oldest_ available snapshot must be available for state sync nodes to catch up (oldest because a node may be restoring an old snapshot while a new snapshot was taken). + +* Local config: archive nodes may want to retain more or all blocks, e.g. via a local config option `min-retain-blocks`. There may also be a need to vary rentention for other nodes, e.g. sentry nodes which do not need historical blocks. + +![Cosmos SDK block retention diagram](images/block-retention.png) + +## Status + +Proposed + +## Consequences + +### Positive + +* Application-specified block retention allows the application to take all relevant factors into account and prevent necessary blocks from being accidentally removed. + +* Node operators can independently decide whether they want to provide complete block histories (if local configuration for this is provided) and snapshots. + +### Negative + +* Social coordination is required to run archival nodes, failure to do so may lead to permanent loss of historical blocks. + +* Social coordination is required to run snapshot nodes, failure to do so may lead to inability to run state sync, and inability to bootstrap new nodes at all if no archival nodes are online. + +### Neutral + +* Reduced block retention requires application changes, and cannot be controlled directly in Tendermint. + +* Application-specified block retention may set a lower bound on disk space requirements for all nodes. + +## References + +- State sync ADR: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-053-state-sync-prototype.md + +- State sync issue: https://github.com/tendermint/tendermint/issues/828 + +- Block pruning issue: https://github.com/tendermint/tendermint/issues/3652 diff --git a/rfc/images/block-retention.png b/rfc/images/block-retention.png new file mode 100644 index 0000000000000000000000000000000000000000..e013e1ab383a3b1c7b54246cbfef6ae353758f88 GIT binary patch literal 53718 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 literal 0 HcmV?d00001 From 15b15d206051eff1168e1c9bedca6a39ed75d554 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 3 Apr 2020 13:07:57 +0200 Subject: [PATCH 049/223] accept RFC-001 (#86) --- rfc/001-block-retention.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/001-block-retention.md b/rfc/001-block-retention.md index 43cfa9d14..f2b7de7ee 100644 --- a/rfc/001-block-retention.md +++ b/rfc/001-block-retention.md @@ -77,7 +77,7 @@ The returned `retain_height` would be the lowest height that satisfies: ## Status -Proposed +Accepted ## Consequences From ebda9dcac59d4bfd9d6949343dd505d8fb6839ad Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 14 Apr 2020 08:54:25 +0200 Subject: [PATCH 050/223] 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. --- spec/abci/abci.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 74cf4d0c4..3c6526a91 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -369,6 +369,8 @@ Commit are included in the header of the next block. - **Response**: - `Data ([]byte)`: The Merkle root hash of the application state + - `RetainHeight (int64)`: Blocks below this height may be removed. Defaults + to `0` (retain all). - **Usage**: - Persist the application state. - Return an (optional) Merkle root hash of the application state @@ -380,6 +382,11 @@ Commit are included in the header of the next block. 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. ## Data Types From 7a0cdd53d5b9cc585214b70ec91c4f9760eb8c27 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 14 Apr 2020 14:46:21 +0400 Subject: [PATCH 051/223] abci: add MaxAgeNumBlocks/MaxAgeDuration to EvidenceParams (#87) --- spec/abci/apps.md | 29 +++++++++++++++++++++-------- spec/blockchain/state.md | 10 +++++----- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/spec/abci/apps.md b/spec/abci/apps.md index 528f92634..9989deecd 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -233,7 +233,7 @@ 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. -### Block.MaxBytes +### BlockParams.MaxBytes The maximum size of a complete Amino encoded block. This is enforced by Tendermint consensus. @@ -243,7 +243,7 @@ the header, the validator set, and any included evidence in the block. Must have `0 < MaxBytes < 100 MB`. -### Block.MaxGas +### BlockParams.MaxGas The maximum of the sum of `GasWanted` in a proposed block. This is *not* enforced by Tendermint consensus. @@ -254,21 +254,34 @@ txs included in a proposed block. Must have `MaxGas >= -1`. If `MaxGas == -1`, no limit is enforced. -### Block.TimeIotaMs +### BlockParams.TimeIotaMs The minimum time between consecutive blocks (in milliseconds). This is enforced by Tendermint consensus. Must have `TimeIotaMs > 0` to ensure time monotonicity. -### EvidenceParams.MaxAge +### EvidenceParams.MaxAgeDuration -This is the maximum age of evidence. +This is the maximum age of evidence in time units. This is enforced by Tendermint consensus. -If a block includes evidence older than this, the block will be rejected -(validators won't vote for it). -Must have `MaxAge > 0`. +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`. ### Updates diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md index 535eee8b2..01f430c15 100644 --- a/spec/blockchain/state.md +++ b/spec/blockchain/state.md @@ -110,8 +110,8 @@ type BlockParams struct { } type EvidenceParams struct { - MaxAgeNumBlocks int64 - MaxAgeDuration time.Duration + MaxAgeNumBlocks int64 + MaxAgeDuration time.Duration } type ValidatorParams struct { @@ -135,9 +135,9 @@ The minimal time between consecutive blocks is controlled by the For evidence in a block to be valid, it must satisfy: -``` -block.Header.Height - evidence.Height < ConsensusParams.Evidence.MaxAgeNumBlocks -block.Header.Time - evidence.Time < ConsensusParams.Evidence.MaxAgeDuration +```go +block.Header.Time-evidence.Time < ConsensusParams.Evidence.MaxAgeDuration && + block.Header.Height-evidence.Height < ConsensusParams.Evidence.MaxAgeNumBlocks ``` #### Validator From f399abd7ac1a2d3a32f030a1a801f7781618690d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 21 Apr 2020 19:15:26 +0400 Subject: [PATCH 052/223] abci: update MaxAgeNumBlocks & MaxAgeDuration docs (#88) --- spec/abci/abci.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 3c6526a91..8137138e0 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -510,10 +510,15 @@ Commit are included in the header of the next block. - **Fields**: - `MaxAgeNumBlocks (int64)`: Max age of evidence, in blocks. - `MaxAgeDuration (time.Duration)`: Max age of evidence, in time. - Evidence older than this is considered stale and ignored. - - - This should correspond with an app's "unbonding period" or other - similar mechanism for handling Nothing-At-Stake attacks. + 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). + + - Evidence older than `MaxAgeNumBlocks` && `MaxAgeDuration` is considered + stale and ignored. + - In Cosmos-SDK based blockchains, `MaxAgeDuration` is usually equal to the + unbonding period. `MaxAgeNumBlocks` is calculated by dividing the unboding + period by the average block time (e.g. 2 weeks / 6s per block = 2d8h). ### ValidatorParams From 9842b4b0fb703e609ad233af9683adc773bc95ef Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 5 May 2020 14:40:00 +0200 Subject: [PATCH 053/223] document state sync ABCI interface and P2P protocol (#90) The corresponding Tendermint PRs are tendermint/tendermint#4704 and tendermint/tendermint#4705. --- spec/abci/abci.md | 131 ++++++++++++++++++++++-- spec/abci/apps.md | 152 +++++++++++++++++++++++++++- spec/abci/client-server.md | 2 +- spec/reactors/state_sync/reactor.md | 77 ++++++++++++++ 4 files changed, 353 insertions(+), 9 deletions(-) create mode 100644 spec/reactors/state_sync/reactor.md diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 8137138e0..475ee342f 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -5,17 +5,22 @@ The ABCI message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto). -ABCI methods are split across 3 separate ABCI _connections_: +ABCI methods are split across four separate ABCI _connections_: -- `Consensus Connection`: `InitChain, BeginBlock, DeliverTx, EndBlock, Commit` -- `Mempool Connection`: `CheckTx` -- `Info Connection`: `Info, SetOption, Query` +- Consensus connection: `InitChain`, `BeginBlock`, `DeliverTx`, `EndBlock`, `Commit` +- Mempool connection: `CheckTx` +- Info connection: `Info`, `SetOption`, `Query` +- Snapshot connection: `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, `ApplySnapshotChunk` -The `Consensus Connection` is driven by a consensus protocol and is responsible +The consensus connection is driven by a consensus protocol and is responsible for block execution. -The `Mempool Connection` is for validating new transactions, before they're + +The mempool connection is for validating new transactions, before they're shared or included in a block. -The `Info Connection` is for initialization and for queries from the user. + +The info connection is for initialization and for queries from the user. + +The snapshot connection is for serving and restoring [state sync snapshots](apps.md#state-sync). Additionally, there is a `Flush` method that is called on every connection, and an `Echo` method that is just for debugging. @@ -151,6 +156,22 @@ 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](apps.md#state-sync). + +When a new node is discovering snapshots in the P2P network, existing nodes will call +`ListSnapshots` on the application to retrieve any local state snapshots. The new node will +offer these snapshots to its local application via `OfferSnapshot`. + +Once the application accepts a snapshot and begins restoring it, Tendermint will fetch snapshot +chunks from existing nodes via `LoadSnapshotChunk` and apply them sequentially to the local +application with `ApplySnapshotChunk`. When all chunks have been applied, the application +`AppHash` is retrieved via an `Info` query and compared to the blockchain's `AppHash` verified +via light client. + ## Messages ### Echo @@ -388,6 +409,83 @@ Commit are included in the header of the next block. other purposes, e.g. auditing, replay of non-persisted heights, light client verification, and so on. +### ListSnapshots + +- **Response**: + - `Snapshots ([]Snapshot)`: List of local state snapshots. +- **Usage**: + - Used during state sync to discover available snapshots on peers. + - See `Snapshot` data type for details. + +### LoadSnapshotChunk + +- **Request**: + - `Height (uint64)`: The height of the snapshot the chunks belongs to. + - `Format (uint32)`: The application-specific format of the snapshot the chunk belongs to. + - `Chunk (uint32)`: The chunk index, starting from `0` for the initial chunk. +- **Response**: + - `Chunk ([]byte)`: 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. +- **Usage**: + - Used during state sync to retrieve snapshot chunks from peers. + +### OfferSnapshot + +- **Request**: + - `Snapshot (Snapshot)`: The snapshot offered for restoration. + - `AppHash ([]byte)`: The light client-verified app hash for this height, from the blockchain. +- **Response**: + - `Result (Result)`: The result of the snapshot offer. + - `accept`: Snapshot is accepted, start applying chunks. + - `abort`: Abort snapshot restoration, and don't try any other snapshots. + - `reject`: Reject this specific snapshot, try others. + - `reject_format`: Reject all snapshots with this `format`, try others. + - `reject_senders`: 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](apps.md#state-sync). + +### ApplySnapshotChunk + +- **Request**: + - `Index (uint32)`: The chunk index, starting from `0`. Tendermint applies chunks sequentially. + - `Chunk ([]byte)`: The binary chunk contents, as returned by `LoadSnapshotChunk`. + - `Sender (string)`: The P2P ID of the node who sent this chunk. +- **Response**: + - `Result (Result)`: The result of applying this chunk. + - `accept`: The chunk was accepted. + - `abort`: Abort snapshot restoration, and don't try any other snapshots. + - `retry`: Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate. + - `retry_snapshot`: Restart this snapshot from `OfferSnapshot`, reusing chunks unless + instructed otherwise. + - `reject_snapshot`: Reject this snapshot, try a different one. + - `RefetchChunks ([]uint32)`: Refetch and reapply the given chunks, regardless of `Result`. Only + the listed chunks will be refetched, and reapplied in sequential order. + - `RejectSenders ([]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. +- **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 ### Header @@ -540,3 +638,22 @@ Commit are included in the header of the next block. - `Type (string)`: Type of Merkle proof and how it's encoded. - `Key ([]byte)`: Key in the Merkle tree that this proof is for. - `Data ([]byte)`: Encoded Merkle proof for the key. + +### Snapshot + +- **Fields**: + - `Height (uint64)`: The height at which the snapshot was taken (after commit). + - `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. + - `Chunks (uint32)`: The number of chunks in the snapshot. Must be at least 1 (even if empty). + - `Hash (bytes)`: An arbitrary snapshot hash. Must be equal only for identical snapshots across + nodes. Tendermint does not interpret the hash, it only compares them. + - `Metadata (bytes)`: Arbitrary application metadata, for example chunk hashes or other + verification data. + +- **Usage**: + - Used for state sync snapshots, see [separate section](apps.md#state-sync) 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 index 9989deecd..4c44e85e6 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -14,10 +14,11 @@ Here we cover the following components of ABCI applications: 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 ## State -Since Tendermint maintains three concurrent ABCI connections, it is typical +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`. @@ -92,6 +93,11 @@ QueryState should be set to the latest `DeliverTxState` at the end of every `Com ie. after the full block has been processed and the state committed to disk. Otherwise it should never be modified. +### 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. + ## Transaction Results `ResponseCheckTx` and `ResponseDeliverTx` contain the same fields. @@ -464,3 +470,147 @@ 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 weeks or months. + +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 should provide the following guarantees: + +* **Consistent:** A snapshot should be taken at a single isolated height, unaffected by + concurrent writes. This can e.g. be accomplished by using a data store that supports ACID + transactions with snapshot isolation. + +* **Asynchronous:** Taking a snapshot can be time-consuming, so it should not halt chain progress, + for example by running in a separate thread. + +* **Deterministic:** A snapshot taken at the same height in the same format should 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, and 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. + +As with taking snapshots, the method for restoring them is entirely up to the application, but will +generally be the inverse of how they are taken. + +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 snapshot has 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 node is bootstrapped with this information and the restored state machine, it transitions +to fast sync (if enabled) to fetch any remaining blocks up the 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 index 94485f0d9..57924f7c2 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -85,7 +85,7 @@ it is the standard way to encode integers in Protobuf. It is also generally shor As noted above, this prefixing does not apply for GRPC. An ABCI server must also be able to support multiple connections, as -Tendermint uses three connections. +Tendermint uses four connections. ### Async vs Sync diff --git a/spec/reactors/state_sync/reactor.md b/spec/reactors/state_sync/reactor.md new file mode 100644 index 000000000..d6dbaeb95 --- /dev/null +++ b/spec/reactors/state_sync/reactor.md @@ -0,0 +1,77 @@ +# State Sync Reactor + +State sync allows new nodes to rapidly bootstrap and join the network by discovering, fetching, +and restoring state machine snapshots. For more information, see the [state sync ABCI section](../abci/apps.md#state-sync). + +The state sync reactor has two main responsibilites: + +* Serving state machine snapshots taken by the local ABCI application to new nodes joining the + network. + +* Discovering existing snapshots and fetching snapshot chunks for an empty local application + being bootstrapped. + +The state sync process for bootstrapping a new node is described in detail in the section linked +above. While technically part of the reactor (see `statesync/syncer.go` and related components), +this document will only cover the P2P reactor component. + +For details on the ABCI methods and data types, see the [ABCI documentation](../abci/abci.md). + +## State Sync P2P Protocol + +When a new node begin state syncing, it will ask all peers it encounters if it has any +available snapshots: + +```go +type snapshotsRequestMessage struct{} +``` + +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: + +```go +type snapshotsResponseMessage struct { + Height uint64 + Format uint32 + Chunks uint32 + Hash []byte + Metadata []byte +} +``` + +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: + +```go +type chunkRequestMessage struct { + Height uint64 + Format uint32 + Index uint32 +} +``` + +The receiver will load the requested chunk from its local application via `LoadSnapshotChunk`, +and respond with it (limited to 16 MB): + +```go +type chunkResponseMessage struct { + Height uint64 + Format uint32 + Index uint32 + Chunk []byte + Missing bool +} +``` + +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. + +If no state sync is in progress (i.e. during normal operation), any unsolicited response messages +are discarded. \ No newline at end of file From 90797cef90da762db7f7a14ce834c745140f7bcd Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Tue, 5 May 2020 14:53:49 +0200 Subject: [PATCH 054/223] Revert "document state sync ABCI interface and P2P protocol (#90)" (#92) This reverts commit 9842b4b0fb703e609ad233af9683adc773bc95ef. --- spec/abci/abci.md | 131 ++---------------------- spec/abci/apps.md | 152 +--------------------------- spec/abci/client-server.md | 2 +- spec/reactors/state_sync/reactor.md | 77 -------------- 4 files changed, 9 insertions(+), 353 deletions(-) delete mode 100644 spec/reactors/state_sync/reactor.md diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 475ee342f..8137138e0 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -5,22 +5,17 @@ The ABCI message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto). -ABCI methods are split across four separate ABCI _connections_: +ABCI methods are split across 3 separate ABCI _connections_: -- Consensus connection: `InitChain`, `BeginBlock`, `DeliverTx`, `EndBlock`, `Commit` -- Mempool connection: `CheckTx` -- Info connection: `Info`, `SetOption`, `Query` -- Snapshot connection: `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, `ApplySnapshotChunk` +- `Consensus Connection`: `InitChain, BeginBlock, DeliverTx, EndBlock, Commit` +- `Mempool Connection`: `CheckTx` +- `Info Connection`: `Info, SetOption, Query` -The consensus connection is driven by a consensus protocol and is responsible +The `Consensus Connection` is driven by a consensus protocol and is responsible for block execution. - -The mempool connection is for validating new transactions, before they're +The `Mempool Connection` is for validating new transactions, before they're shared or included in a block. - -The info connection is for initialization and for queries from the user. - -The snapshot connection is for serving and restoring [state sync snapshots](apps.md#state-sync). +The `Info Connection` is for initialization and for queries from the user. Additionally, there is a `Flush` method that is called on every connection, and an `Echo` method that is just for debugging. @@ -156,22 +151,6 @@ 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](apps.md#state-sync). - -When a new node is discovering snapshots in the P2P network, existing nodes will call -`ListSnapshots` on the application to retrieve any local state snapshots. The new node will -offer these snapshots to its local application via `OfferSnapshot`. - -Once the application accepts a snapshot and begins restoring it, Tendermint will fetch snapshot -chunks from existing nodes via `LoadSnapshotChunk` and apply them sequentially to the local -application with `ApplySnapshotChunk`. When all chunks have been applied, the application -`AppHash` is retrieved via an `Info` query and compared to the blockchain's `AppHash` verified -via light client. - ## Messages ### Echo @@ -409,83 +388,6 @@ via light client. other purposes, e.g. auditing, replay of non-persisted heights, light client verification, and so on. -### ListSnapshots - -- **Response**: - - `Snapshots ([]Snapshot)`: List of local state snapshots. -- **Usage**: - - Used during state sync to discover available snapshots on peers. - - See `Snapshot` data type for details. - -### LoadSnapshotChunk - -- **Request**: - - `Height (uint64)`: The height of the snapshot the chunks belongs to. - - `Format (uint32)`: The application-specific format of the snapshot the chunk belongs to. - - `Chunk (uint32)`: The chunk index, starting from `0` for the initial chunk. -- **Response**: - - `Chunk ([]byte)`: 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. -- **Usage**: - - Used during state sync to retrieve snapshot chunks from peers. - -### OfferSnapshot - -- **Request**: - - `Snapshot (Snapshot)`: The snapshot offered for restoration. - - `AppHash ([]byte)`: The light client-verified app hash for this height, from the blockchain. -- **Response**: - - `Result (Result)`: The result of the snapshot offer. - - `accept`: Snapshot is accepted, start applying chunks. - - `abort`: Abort snapshot restoration, and don't try any other snapshots. - - `reject`: Reject this specific snapshot, try others. - - `reject_format`: Reject all snapshots with this `format`, try others. - - `reject_senders`: 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](apps.md#state-sync). - -### ApplySnapshotChunk - -- **Request**: - - `Index (uint32)`: The chunk index, starting from `0`. Tendermint applies chunks sequentially. - - `Chunk ([]byte)`: The binary chunk contents, as returned by `LoadSnapshotChunk`. - - `Sender (string)`: The P2P ID of the node who sent this chunk. -- **Response**: - - `Result (Result)`: The result of applying this chunk. - - `accept`: The chunk was accepted. - - `abort`: Abort snapshot restoration, and don't try any other snapshots. - - `retry`: Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate. - - `retry_snapshot`: Restart this snapshot from `OfferSnapshot`, reusing chunks unless - instructed otherwise. - - `reject_snapshot`: Reject this snapshot, try a different one. - - `RefetchChunks ([]uint32)`: Refetch and reapply the given chunks, regardless of `Result`. Only - the listed chunks will be refetched, and reapplied in sequential order. - - `RejectSenders ([]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. -- **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 ### Header @@ -638,22 +540,3 @@ via light client. - `Type (string)`: Type of Merkle proof and how it's encoded. - `Key ([]byte)`: Key in the Merkle tree that this proof is for. - `Data ([]byte)`: Encoded Merkle proof for the key. - -### Snapshot - -- **Fields**: - - `Height (uint64)`: The height at which the snapshot was taken (after commit). - - `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. - - `Chunks (uint32)`: The number of chunks in the snapshot. Must be at least 1 (even if empty). - - `Hash (bytes)`: An arbitrary snapshot hash. Must be equal only for identical snapshots across - nodes. Tendermint does not interpret the hash, it only compares them. - - `Metadata (bytes)`: Arbitrary application metadata, for example chunk hashes or other - verification data. - -- **Usage**: - - Used for state sync snapshots, see [separate section](apps.md#state-sync) 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 index 4c44e85e6..9989deecd 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -14,11 +14,10 @@ Here we cover the following components of ABCI applications: 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 ## State -Since Tendermint maintains four concurrent ABCI connections, it is typical +Since Tendermint maintains three 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`. @@ -93,11 +92,6 @@ QueryState should be set to the latest `DeliverTxState` at the end of every `Com ie. after the full block has been processed and the state committed to disk. Otherwise it should never be modified. -### 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. - ## Transaction Results `ResponseCheckTx` and `ResponseDeliverTx` contain the same fields. @@ -470,147 +464,3 @@ 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 weeks or months. - -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 should provide the following guarantees: - -* **Consistent:** A snapshot should be taken at a single isolated height, unaffected by - concurrent writes. This can e.g. be accomplished by using a data store that supports ACID - transactions with snapshot isolation. - -* **Asynchronous:** Taking a snapshot can be time-consuming, so it should not halt chain progress, - for example by running in a separate thread. - -* **Deterministic:** A snapshot taken at the same height in the same format should 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, and 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. - -As with taking snapshots, the method for restoring them is entirely up to the application, but will -generally be the inverse of how they are taken. - -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 snapshot has 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 node is bootstrapped with this information and the restored state machine, it transitions -to fast sync (if enabled) to fetch any remaining blocks up the 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 index 57924f7c2..94485f0d9 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -85,7 +85,7 @@ it is the standard way to encode integers in Protobuf. It is also generally shor 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. +Tendermint uses three connections. ### Async vs Sync diff --git a/spec/reactors/state_sync/reactor.md b/spec/reactors/state_sync/reactor.md deleted file mode 100644 index d6dbaeb95..000000000 --- a/spec/reactors/state_sync/reactor.md +++ /dev/null @@ -1,77 +0,0 @@ -# State Sync Reactor - -State sync allows new nodes to rapidly bootstrap and join the network by discovering, fetching, -and restoring state machine snapshots. For more information, see the [state sync ABCI section](../abci/apps.md#state-sync). - -The state sync reactor has two main responsibilites: - -* Serving state machine snapshots taken by the local ABCI application to new nodes joining the - network. - -* Discovering existing snapshots and fetching snapshot chunks for an empty local application - being bootstrapped. - -The state sync process for bootstrapping a new node is described in detail in the section linked -above. While technically part of the reactor (see `statesync/syncer.go` and related components), -this document will only cover the P2P reactor component. - -For details on the ABCI methods and data types, see the [ABCI documentation](../abci/abci.md). - -## State Sync P2P Protocol - -When a new node begin state syncing, it will ask all peers it encounters if it has any -available snapshots: - -```go -type snapshotsRequestMessage struct{} -``` - -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: - -```go -type snapshotsResponseMessage struct { - Height uint64 - Format uint32 - Chunks uint32 - Hash []byte - Metadata []byte -} -``` - -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: - -```go -type chunkRequestMessage struct { - Height uint64 - Format uint32 - Index uint32 -} -``` - -The receiver will load the requested chunk from its local application via `LoadSnapshotChunk`, -and respond with it (limited to 16 MB): - -```go -type chunkResponseMessage struct { - Height uint64 - Format uint32 - Index uint32 - Chunk []byte - Missing bool -} -``` - -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. - -If no state sync is in progress (i.e. during normal operation), any unsolicited response messages -are discarded. \ No newline at end of file From c3cd54a8e0dac3c2ad893156e17c739c9f9fab79 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 May 2020 11:02:20 +0400 Subject: [PATCH 055/223] 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 --- spec/abci/abci.md | 10 +++++----- spec/blockchain/blockchain.md | 4 ++-- spec/blockchain/state.md | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 8137138e0..5fb1d3902 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -216,17 +216,17 @@ Commit are included in the header of the next block. - `Time (google.protobuf.Timestamp)`: Genesis time. - `ChainID (string)`: ID of the blockchain. - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters. - - `Validators ([]ValidatorUpdate)`: Initial genesis validators. + - `Validators ([]ValidatorUpdate)`: Initial genesis validators, sorted by voting power. - `AppStateBytes ([]byte)`: Serialized initial application state. Amino-encoded JSON bytes. - **Response**: - `ConsensusParams (ConsensusParams)`: Initial - consensus-critical parameters. - - `Validators ([]ValidatorUpdate)`: Initial validator set (if non empty). + consensus-critical parameters (optional). + - `Validators ([]ValidatorUpdate)`: Initial validator set (optional). - **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, the initial validator set will be the - ResponseInitChain.Validators (regardless of what is in 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 diff --git a/spec/blockchain/blockchain.md b/spec/blockchain/blockchain.md index 39802bcd5..6f29186dd 100644 --- a/spec/blockchain/blockchain.md +++ b/spec/blockchain/blockchain.md @@ -343,7 +343,7 @@ block.ValidatorsHash == MerkleRoot(state.Validators) MerkleRoot of the current validator set that is committing the block. This can be used to validate the `LastCommit` included in the next block. -Note the validators are sorted by their address before computing the MerkleRoot. +Note the validators are sorted by their voting power before computing the MerkleRoot. ### NextValidatorsHash @@ -354,7 +354,7 @@ block.NextValidatorsHash == MerkleRoot(state.NextValidators) MerkleRoot of the next validator set that will be the validator set that commits the next block. This is included so that the current validator set gets a chance to sign the next validator sets Merkle root. -Note the validators are sorted by their address before computing the MerkleRoot. +Note the validators are sorted by their voting power before computing the MerkleRoot. ### ConsensusHash diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md index 01f430c15..7a2a899d1 100644 --- a/spec/blockchain/state.md +++ b/spec/blockchain/state.md @@ -62,8 +62,9 @@ type Validator struct { When hashing the Validator struct, the address is not included, because it is redundant with the pubkey. -The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, must always be sorted by validator address, -so that there is a canonical order for computing the MerkleRoot. +The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, +must always be sorted by voting power, so that there is a canonical order for +computing the MerkleRoot. We also define a `TotalVotingPower` function, to return the total voting power: From 3c27335db346dcbe07ab394e566a896e99201e93 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 8 May 2020 22:15:08 +0400 Subject: [PATCH 056/223] reactors/pex: specify hash function (#94) https://github.com/tendermint/tendermint/pull/4810/files --- spec/reactors/pex/pex.md | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/spec/reactors/pex/pex.md b/spec/reactors/pex/pex.md index 68a50c51b..0d0fb24f1 100644 --- a/spec/reactors/pex/pex.md +++ b/spec/reactors/pex/pex.md @@ -33,8 +33,8 @@ connections die, or we fail to dial, we will redial every 5s for a few minutes, then switch to an exponential backoff schedule, and after about a day of trying, stop dialing the peer. This behavior is when `persistent_peers_max_dial_period` is configured to zero. -But If `persistent_peers_max_dial_period` is set greater than zero, terms between each dial to each persistent peer -will not exceed `persistent_peers_max_dial_period` during exponential backoff. +But If `persistent_peers_max_dial_period` is set greater than zero, terms between each dial to each persistent peer +will not exceed `persistent_peers_max_dial_period` during exponential backoff. Therefore, `dial_period` = min(`persistent_peers_max_dial_period`, `exponential_backoff_dial_period`) and we keep trying again regardless of `maxAttemptsToDial` @@ -54,9 +54,33 @@ Peers are added to the address book from the PEX when they first connect to us o when we hear about them from other peers. The address book is arranged in sets of buckets, and distinguishes between -vetted (old) and unvetted (new) peers. It keeps different sets of buckets for vetted and -unvetted peers. Buckets provide randomization over peer selection. Peers are put -in buckets according to their IP groups. +vetted (old) and unvetted (new) peers. It keeps different sets of buckets for +vetted and unvetted peers. Buckets provide randomization over peer selection. +Peers are put in buckets according to their IP groups. + +IP group can be an IP block (e.g. `/16` for IPv4, `/32` for IPv6) or `local` +for local addresses or `unroutable` for unroutable addresses. Each group has a + limited number of buckets to prevent DoS attacks coming from that group (e.g. + an attacker buying a `/16` block of IPs and launching a DoS attack). + +[highwayhash](https://arxiv.org/abs/1612.06257) is used as a hashing function +when calculating a bucket. + +When placing a peer into a new bucket: + +``` +hash(key + sourcegroup + int64(hash(key + group + sourcegroup)) % bucket_per_group) % num_new_buckets +``` + +When placing a peer into an old bucket: + +``` +hash(key + group + int64(hash(key + addr)) % buckets_per_group) % num_old_buckets +``` + +where `key` - random 24 HEX string, `group` - IP group of the peer (e.g. `/16`), +`sourcegroup` - IP group of the sender (peer who sent us this address) (e.g. `/16`), +`addr` - string representation of the peer's address (e.g. `174.11.10.2:26656`). A vetted peer can only be in one bucket. An unvetted peer can be in multiple buckets, and each instance of the peer can have a different IP:PORT. From d65205ecada8f73c2f75030675c1457e865a489f Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Mon, 11 May 2020 07:27:37 +0200 Subject: [PATCH 057/223] 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 --- spec/abci/abci.md | 131 ++++++++++++++++++++++-- spec/abci/apps.md | 152 +++++++++++++++++++++++++++- spec/abci/client-server.md | 2 +- spec/reactors/state_sync/reactor.md | 77 ++++++++++++++ 4 files changed, 353 insertions(+), 9 deletions(-) create mode 100644 spec/reactors/state_sync/reactor.md diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 5fb1d3902..f53784b4a 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -5,17 +5,22 @@ The ABCI message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto). -ABCI methods are split across 3 separate ABCI _connections_: +ABCI methods are split across four separate ABCI _connections_: -- `Consensus Connection`: `InitChain, BeginBlock, DeliverTx, EndBlock, Commit` -- `Mempool Connection`: `CheckTx` -- `Info Connection`: `Info, SetOption, Query` +- Consensus connection: `InitChain`, `BeginBlock`, `DeliverTx`, `EndBlock`, `Commit` +- Mempool connection: `CheckTx` +- Info connection: `Info`, `SetOption`, `Query` +- Snapshot connection: `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, `ApplySnapshotChunk` -The `Consensus Connection` is driven by a consensus protocol and is responsible +The consensus connection is driven by a consensus protocol and is responsible for block execution. -The `Mempool Connection` is for validating new transactions, before they're + +The mempool connection is for validating new transactions, before they're shared or included in a block. -The `Info Connection` is for initialization and for queries from the user. + +The info connection is for initialization and for queries from the user. + +The snapshot connection is for serving and restoring [state sync snapshots](apps.md#state-sync). Additionally, there is a `Flush` method that is called on every connection, and an `Echo` method that is just for debugging. @@ -151,6 +156,22 @@ 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](apps.md#state-sync). + +When a new node is discovering snapshots in the P2P network, existing nodes will call +`ListSnapshots` on the application to retrieve any local state snapshots. The new node will +offer these snapshots to its local application via `OfferSnapshot`. + +Once the application accepts a snapshot and begins restoring it, Tendermint will fetch snapshot +chunks from existing nodes via `LoadSnapshotChunk` and apply them sequentially to the local +application with `ApplySnapshotChunk`. When all chunks have been applied, the application +`AppHash` is retrieved via an `Info` query and compared to the blockchain's `AppHash` verified +via light client. + ## Messages ### Echo @@ -388,6 +409,83 @@ Commit are included in the header of the next block. other purposes, e.g. auditing, replay of non-persisted heights, light client verification, and so on. +### ListSnapshots + +- **Response**: + - `Snapshots ([]Snapshot)`: List of local state snapshots. +- **Usage**: + - Used during state sync to discover available snapshots on peers. + - See `Snapshot` data type for details. + +### LoadSnapshotChunk + +- **Request**: + - `Height (uint64)`: The height of the snapshot the chunks belongs to. + - `Format (uint32)`: The application-specific format of the snapshot the chunk belongs to. + - `Chunk (uint32)`: The chunk index, starting from `0` for the initial chunk. +- **Response**: + - `Chunk ([]byte)`: 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. +- **Usage**: + - Used during state sync to retrieve snapshot chunks from peers. + +### OfferSnapshot + +- **Request**: + - `Snapshot (Snapshot)`: The snapshot offered for restoration. + - `AppHash ([]byte)`: The light client-verified app hash for this height, from the blockchain. +- **Response**: + - `Result (Result)`: The result of the snapshot offer. + - `ACCEPT`: Snapshot is accepted, start applying chunks. + - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. + - `REJECT`: Reject this specific snapshot, try others. + - `REJECT_FORMAT`: Reject all snapshots with this `format`, try others. + - `REJECT_SENDERS`: 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](apps.md#state-sync). + +### ApplySnapshotChunk + +- **Request**: + - `Index (uint32)`: The chunk index, starting from `0`. Tendermint applies chunks sequentially. + - `Chunk ([]byte)`: The binary chunk contents, as returned by `LoadSnapshotChunk`. + - `Sender (string)`: The P2P ID of the node who sent this chunk. +- **Response**: + - `Result (Result)`: The result of applying this chunk. + - `ACCEPT`: The chunk was accepted. + - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. + - `RETRY`: Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate. + - `RETRY_SNAPSHOT`: Restart this snapshot from `OfferSnapshot`, reusing chunks unless + instructed otherwise. + - `REJECT_SNAPSHOT`: Reject this snapshot, try a different one. + - `RefetchChunks ([]uint32)`: Refetch and reapply the given chunks, regardless of `Result`. Only + the listed chunks will be refetched, and reapplied in sequential order. + - `RejectSenders ([]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. +- **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 ### Header @@ -540,3 +638,22 @@ Commit are included in the header of the next block. - `Type (string)`: Type of Merkle proof and how it's encoded. - `Key ([]byte)`: Key in the Merkle tree that this proof is for. - `Data ([]byte)`: Encoded Merkle proof for the key. + +### Snapshot + +- **Fields**: + - `Height (uint64)`: The height at which the snapshot was taken (after commit). + - `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. + - `Chunks (uint32)`: The number of chunks in the snapshot. Must be at least 1 (even if empty). + - `Hash (bytes)`: An arbitrary snapshot hash. Must be equal only for identical snapshots across + nodes. Tendermint does not interpret the hash, it only compares them. + - `Metadata (bytes)`: Arbitrary application metadata, for example chunk hashes or other + verification data. + +- **Usage**: + - Used for state sync snapshots, see [separate section](apps.md#state-sync) 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 index 9989deecd..4c44e85e6 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -14,10 +14,11 @@ Here we cover the following components of ABCI applications: 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 ## State -Since Tendermint maintains three concurrent ABCI connections, it is typical +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`. @@ -92,6 +93,11 @@ QueryState should be set to the latest `DeliverTxState` at the end of every `Com ie. after the full block has been processed and the state committed to disk. Otherwise it should never be modified. +### 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. + ## Transaction Results `ResponseCheckTx` and `ResponseDeliverTx` contain the same fields. @@ -464,3 +470,147 @@ 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 weeks or months. + +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 should provide the following guarantees: + +* **Consistent:** A snapshot should be taken at a single isolated height, unaffected by + concurrent writes. This can e.g. be accomplished by using a data store that supports ACID + transactions with snapshot isolation. + +* **Asynchronous:** Taking a snapshot can be time-consuming, so it should not halt chain progress, + for example by running in a separate thread. + +* **Deterministic:** A snapshot taken at the same height in the same format should 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, and 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. + +As with taking snapshots, the method for restoring them is entirely up to the application, but will +generally be the inverse of how they are taken. + +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 snapshot has 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 node is bootstrapped with this information and the restored state machine, it transitions +to fast sync (if enabled) to fetch any remaining blocks up the 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 index 94485f0d9..57924f7c2 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -85,7 +85,7 @@ it is the standard way to encode integers in Protobuf. It is also generally shor As noted above, this prefixing does not apply for GRPC. An ABCI server must also be able to support multiple connections, as -Tendermint uses three connections. +Tendermint uses four connections. ### Async vs Sync diff --git a/spec/reactors/state_sync/reactor.md b/spec/reactors/state_sync/reactor.md new file mode 100644 index 000000000..48beabc09 --- /dev/null +++ b/spec/reactors/state_sync/reactor.md @@ -0,0 +1,77 @@ +# State Sync Reactor + +State sync allows new nodes to rapidly bootstrap and join the network by discovering, fetching, +and restoring state machine snapshots. For more information, see the [state sync ABCI section](../../abci/apps.md#state-sync). + +The state sync reactor has two main responsibilites: + +* Serving state machine snapshots taken by the local ABCI application to new nodes joining the + network. + +* Discovering existing snapshots and fetching snapshot chunks for an empty local application + being bootstrapped. + +The state sync process for bootstrapping a new node is described in detail in the section linked +above. While technically part of the reactor (see `statesync/syncer.go` and related components), +this document will only cover the P2P reactor component. + +For details on the ABCI methods and data types, see the [ABCI documentation](../../abci/abci.md). + +## State Sync P2P Protocol + +When a new node begin state syncing, it will ask all peers it encounters if it has any +available snapshots: + +```go +type snapshotsRequestMessage struct{} +``` + +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: + +```go +type snapshotsResponseMessage struct { + Height uint64 + Format uint32 + Chunks uint32 + Hash []byte + Metadata []byte +} +``` + +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: + +```go +type chunkRequestMessage struct { + Height uint64 + Format uint32 + Index uint32 +} +``` + +The receiver will load the requested chunk from its local application via `LoadSnapshotChunk`, +and respond with it (limited to 16 MB): + +```go +type chunkResponseMessage struct { + Height uint64 + Format uint32 + Index uint32 + Chunk []byte + Missing bool +} +``` + +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. + +If no state sync is in progress (i.e. during normal operation), any unsolicited response messages +are discarded. \ No newline at end of file From 5acd1540c02b1c33d3eefa0fa0abed84c8e111dc Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 12 May 2020 13:41:44 +0200 Subject: [PATCH 058/223] 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 --- spec/abci/abci.md | 1 + spec/abci/apps.md | 9 +++++++++ spec/abci/client-server.md | 1 - spec/blockchain/state.md | 3 ++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index f53784b4a..8e5a5f588 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -617,6 +617,7 @@ via light client. - In Cosmos-SDK based blockchains, `MaxAgeDuration` is usually equal to the unbonding period. `MaxAgeNumBlocks` is calculated by dividing the unboding period by the average block time (e.g. 2 weeks / 6s per block = 2d8h). + - `MaxNum (uint32)`: The maximum number of evidence that can be committed to a single block ### ValidatorParams diff --git a/spec/abci/apps.md b/spec/abci/apps.md index 4c44e85e6..74d0985b2 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -289,6 +289,15 @@ 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`) + +The amount must be a positive number + ### Updates The application may set the ConsensusParams during InitChain, and update them during diff --git a/spec/abci/client-server.md b/spec/abci/client-server.md index 57924f7c2..95b9238b5 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -35,7 +35,6 @@ 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), -[Python](https://github.com/tendermint/tendermint/tree/master/abci/example/python3/abci), [C++](https://github.com/mdyring/cpp-tmsp), and [Java](https://github.com/jTendermint/jabci). diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md index 7a2a899d1..33eec48da 100644 --- a/spec/blockchain/state.md +++ b/spec/blockchain/state.md @@ -107,12 +107,13 @@ func (params ConsensusParams) Hash() []byte { type BlockParams struct { MaxBytes int64 MaxGas int64 - TimeIotaMs int64 + TimeIotaMs int64 } type EvidenceParams struct { MaxAgeNumBlocks int64 MaxAgeDuration time.Duration + MaxNum uint32 } type ValidatorParams struct { From 0d5f212f30e27a1764c91b94209b507976555542 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 25 May 2020 09:32:55 +0400 Subject: [PATCH 059/223] reactors/pex: masked IP is used as group key (#96) --- spec/reactors/pex/pex.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/reactors/pex/pex.md b/spec/reactors/pex/pex.md index 0d0fb24f1..77f1407e8 100644 --- a/spec/reactors/pex/pex.md +++ b/spec/reactors/pex/pex.md @@ -58,10 +58,12 @@ vetted (old) and unvetted (new) peers. It keeps different sets of buckets for vetted and unvetted peers. Buckets provide randomization over peer selection. Peers are put in buckets according to their IP groups. -IP group can be an IP block (e.g. `/16` for IPv4, `/32` for IPv6) or `local` -for local addresses or `unroutable` for unroutable addresses. Each group has a - limited number of buckets to prevent DoS attacks coming from that group (e.g. - an attacker buying a `/16` block of IPs and launching a DoS attack). +IP group can be a masked IP (e.g. `1.2.0.0` or `2602:100::`) or `local` for +local addresses or `unroutable` for unroutable addresses. The mask which +corresponds to the `/16` subnet is used for IPv4, `/32` subnet - for IPv6. +Each group has a limited number of buckets to prevent DoS attacks coming from +that group (e.g. an attacker buying a `/16` block of IPs and launching a DoS +attack). [highwayhash](https://arxiv.org/abs/1612.06257) is used as a hashing function when calculating a bucket. @@ -78,8 +80,8 @@ When placing a peer into an old bucket: hash(key + group + int64(hash(key + addr)) % buckets_per_group) % num_old_buckets ``` -where `key` - random 24 HEX string, `group` - IP group of the peer (e.g. `/16`), -`sourcegroup` - IP group of the sender (peer who sent us this address) (e.g. `/16`), +where `key` - random 24 HEX string, `group` - IP group of the peer (e.g. `1.2.0.0`), +`sourcegroup` - IP group of the sender (peer who sent us this address) (e.g. `174.11.0.0`), `addr` - string representation of the peer's address (e.g. `174.11.10.2:26656`). A vetted peer can only be in one bucket. An unvetted peer can be in multiple buckets, and From 9c0754e61764241d537fdd6887b23288f2154ca7 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 11 Jun 2020 10:10:06 +0200 Subject: [PATCH 060/223] spec: add ProofTrialPeriod to EvidenceParam (#99) --- spec/abci/abci.md | 2 ++ spec/abci/apps.md | 14 +++++++++++--- spec/blockchain/state.md | 7 ++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 8e5a5f588..4a0680871 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -618,6 +618,8 @@ via light client. unbonding period. `MaxAgeNumBlocks` is calculated by dividing the unboding period by the average block time (e.g. 2 weeks / 6s per block = 2d8h). - `MaxNum (uint32)`: The maximum number of evidence that can be committed to a single block + - `ProofTrialPeriod (int64)`: The duration in terms of blocks that an indicted node has to + provide proof of correctly executing a lock change in the event of amnesia evidence. ### ValidatorParams diff --git a/spec/abci/apps.md b/spec/abci/apps.md index 74d0985b2..3adead3a4 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -291,12 +291,20 @@ Must have `MaxAgeNumBlocks > 0`. ### EvidenceParams.MaxNum -This is the maximum number of evidence that can be committed to a single block +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`) +a block minus it's overhead ( ~ `MaxBytes`). -The amount must be a positive number +The amount must be a positive number. + +### EvidenceParams.ProofTrialPeriod + +This is the duration in terms of blocks that an indicted validator has to prove a +correct lock change in the event of amnesia evidence when a validator voted more +than once across different rounds. + +This must be positive and less then `EvidenceParams.MaxAgeNumBlocks`. ### Updates diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md index 33eec48da..b503d5718 100644 --- a/spec/blockchain/state.md +++ b/spec/blockchain/state.md @@ -111,9 +111,10 @@ type BlockParams struct { } type EvidenceParams struct { - MaxAgeNumBlocks int64 - MaxAgeDuration time.Duration - MaxNum uint32 + MaxAgeNumBlocks int64 + MaxAgeDuration time.Duration + MaxNum uint32 + ProofTrialPeriod int64 } type ValidatorParams struct { From 199124048e4c39e102242a081457069b45e2e8ec Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 12 Jun 2020 14:41:51 +0400 Subject: [PATCH 061/223] spec: modify Header.LastResultsHash (#97) Refs: https://github.com/tendermint/tendermint/issues/1007 PR: https://github.com/tendermint/tendermint/pull/4845 --- spec/abci/abci.md | 26 +++++++++++++++----------- spec/blockchain/blockchain.md | 9 ++++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 4a0680871..90f67b9cb 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -464,12 +464,12 @@ via light client. - `ACCEPT`: The chunk was accepted. - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. - `RETRY`: Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate. - - `RETRY_SNAPSHOT`: Restart this snapshot from `OfferSnapshot`, reusing chunks unless + - `RETRY_SNAPSHOT`: Restart this snapshot from `OfferSnapshot`, reusing chunks unless instructed otherwise. - `REJECT_SNAPSHOT`: Reject this snapshot, try a different one. - - `RefetchChunks ([]uint32)`: Refetch and reapply the given chunks, regardless of `Result`. Only + - `RefetchChunks ([]uint32)`: Refetch and reapply the given chunks, regardless of `Result`. Only the listed chunks will be refetched, and reapplied in sequential order. - - `RejectSenders ([]string)`: Reject the given P2P senders, regardless of `Result`. Any chunks + - `RejectSenders ([]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. - **Usage**: - The application can choose to refetch chunks and/or ban P2P peers as appropriate. Tendermint @@ -478,13 +478,13 @@ via light client. `Snapshot.Metadata` and/or incrementally verifying contents against `AppHash`. - When all chunks have been accepted, Tendermint will make an ABCI `Info` call to verify that `LastBlockAppHash` and `LastBlockHeight` matches the expected values, and record the - `AppVersion` in the node state. It then switches to fast sync or consensus and joins the + `AppVersion` in the node state. It then switches to 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 @@ -506,7 +506,7 @@ via light client. - `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the Merkle root of the application state after executing the previous block's transactions - - `LastResultsHash ([]byte)`: Hash of the ABCI results returned by the last block + - `LastResultsHash ([]byte)`: Root hash of `BeginBlock` events, root hash of all results from the txs from the previous block, and `EndBlock` events - `EvidenceHash ([]byte)`: Hash of the evidence included in this block - `ProposerAddress ([]byte)`: Original proposer for the block - **Usage**: @@ -515,6 +515,10 @@ via light client. especially height and time. - Provides the proposer of the current block, for use in proposer-based reward mechanisms. + - `LastResultsHash` is the root hash of a Merkle tree w/ 3 leafs: + proto-encoded `ResponseBeginBlock#Events`, root hash of a Merkle tree build + from `ResponseDeliverTx` responses (Log, Info and Codespace fields are + ignored), and proto-encoded `ResponseEndBlock#Events`. ### Version @@ -646,17 +650,17 @@ via light client. - **Fields**: - `Height (uint64)`: The height at which the snapshot was taken (after commit). - - `Format (uint32)`: An application-specific snapshot format, allowing applications to version - their snapshot data format and make backwards-incompatible changes. Tendermint does not + - `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. - `Chunks (uint32)`: The number of chunks in the snapshot. Must be at least 1 (even if empty). - - `Hash (bytes)`: An arbitrary snapshot hash. Must be equal only for identical snapshots across + - `Hash (bytes)`: An arbitrary snapshot hash. Must be equal only for identical snapshots across nodes. Tendermint does not interpret the hash, it only compares them. - - `Metadata (bytes)`: Arbitrary application metadata, for example chunk hashes or other + - `Metadata (bytes)`: Arbitrary application metadata, for example chunk hashes or other verification data. - **Usage**: - Used for state sync snapshots, see [separate section](apps.md#state-sync) for details. - - A snapshot is considered identical across nodes only if _all_ fields are equal (including + - 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/blockchain/blockchain.md b/spec/blockchain/blockchain.md index 6f29186dd..2fad0f14e 100644 --- a/spec/blockchain/blockchain.md +++ b/spec/blockchain/blockchain.md @@ -56,7 +56,7 @@ type Header struct { NextValidatorsHash []byte // validators for the next block ConsensusHash []byte // consensus params for current block AppHash []byte // state after txs from the previous block - LastResultsHash []byte // root hash of all results from the txs from the previous block + LastResultsHash []byte // root hash of BeginBlock events, root hash of all results from the txs from the previous block, and EndBlock events // consensus info EvidenceHash []byte // evidence included in the block @@ -377,10 +377,13 @@ The first block has `block.Header.AppHash == []byte{}`. ### LastResultsHash ```go -block.ResultsHash == MerkleRoot(state.LastResults) +block.LastResultsHash == MerkleRoot(ResponseBeginBlock.Events, MerkleRoot([]ResponseDeliverTx), ResponseEndBlock.Events) ``` -MerkleRoot of the results of the transactions in the previous block. +`LastResultsHash` is the root hash of a Merkle tree w/ 3 leafs: proto-encoded +`ResponseBeginBlock#Events`, root hash of a Merkle tree build from +`ResponseDeliverTx` responses (Log, Info and Codespace fields are ignored), and +proto-encoded `ResponseEndBlock#Events`. The first block has `block.Header.ResultsHash == []byte{}`. From 30ef12d0bb2a75265b838aff75d55804cf521d85 Mon Sep 17 00:00:00 2001 From: Alexander Simmerl Date: Mon, 15 Jun 2020 10:42:48 +0200 Subject: [PATCH 062/223] spec: link to abci server implementations (#100) --- spec/abci/client-server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/abci/client-server.md b/spec/abci/client-server.md index 95b9238b5..39dcc658a 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -25,7 +25,7 @@ responses. 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 only) +- In-process ([Golang](https://github.com/tendermint/tendermint/tree/master/abci), [Rust](https://github.com/tendermint/rust-abci)) - ABCI-socket - GRPC From 1bd2aacb56812117f8837b76ddfe7b94896a7a36 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Fri, 19 Jun 2020 15:50:26 +0200 Subject: [PATCH 063/223] spec: update evidence in blockchain.md (#108) now evidence reflects the actual evidence present in the tendermint repo --- spec/abci/README.md | 2 +- spec/abci/abci.md | 2 +- spec/abci/client-server.md | 4 +- spec/blockchain/blockchain.md | 72 +++++++++++++++++++++++++++++------ 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/spec/abci/README.md b/spec/abci/README.md index a8293e4bb..4bf311119 100644 --- a/spec/abci/README.md +++ b/spec/abci/README.md @@ -10,7 +10,7 @@ _methods_, where each method has a corresponding `Request` and `Response` message type. Tendermint calls the ABCI methods on the ABCI application by sending the `Request*` messages and receiving the `Response*` messages in return. -All message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto). +All message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/proto/abci/types.proto). This allows Tendermint to run applications written in any programming language. This specification is split as follows: diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 90f67b9cb..71b94e6c0 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -3,7 +3,7 @@ ## Overview The ABCI message types are defined in a [protobuf -file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto). +file](https://github.com/tendermint/tendermint/blob/master/proto/abci/types.proto). ABCI methods are split across four separate ABCI _connections_: diff --git a/spec/abci/client-server.md b/spec/abci/client-server.md index 39dcc658a..c5c77be89 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -9,7 +9,7 @@ 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/abci/types/types.proto). +[protobuf file](https://github.com/tendermint/tendermint/blob/master/proto/abci/types.proto). Some messages have no fields, while others may include byte-arrays, strings, integers, or custom protobuf types. @@ -49,7 +49,7 @@ 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/abci/types/types.proto) +file](https://github.com/tendermint/tendermint/blob/master/proto/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/). diff --git a/spec/blockchain/blockchain.md b/spec/blockchain/blockchain.md index 2fad0f14e..aebb492b0 100644 --- a/spec/blockchain/blockchain.md +++ b/spec/blockchain/blockchain.md @@ -203,22 +203,72 @@ type EvidenceData struct { ## Evidence -Evidence in Tendermint is implemented as an interface. -This means any evidence is encoded using its Amino prefix. -There is currently only a single type, the `DuplicateVoteEvidence`. +Evidence in Tendermint is used to indicate breaches in the consensus by a validator. +It is implemented as the following interface. -``` -// amino name: "tendermint/DuplicateVoteEvidence" -type DuplicateVoteEvidence struct { - PubKey PubKey - VoteA Vote - VoteB Vote +```go +type Evidence interface { + Height() int64 // height of the equivocation + Time() time.Time // time of the equivocation + Address() []byte // address of the equivocating validator + Bytes() []byte // bytes which comprise the evidence + Hash() []byte // hash of the evidence + Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence + Equal(Evidence) bool // check equality of evidence + + ValidateBasic() error + String() string } ``` -Votes are lexicographically sorted on `BlockID`. +All evidence can be encoded and decoded to and from Protobuf with the `EvidenceToProto()` +and `EvidenceFromProto()` functions. The [Fork Accountability](../consensus/light-client/accountability.md) +document provides a good overview for the types of evidence and how they occur. -See the [pubkey spec](./encoding.md#key-types) for more. +`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`. + +```go +type DuplicateVoteEvidence struct { + VoteA *Vote + VoteB *Vote +} +``` + +`AmnesiaEvidence` represents a validator that has incorrectly voted for another block in a +different round to the the block that the validator was previously locked on. This form +of evidence is generated differently from the rest. See this +[ADR](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-056-proving-amnesia-attacks.md). + +```go +type AmnesiaEvidence struct { + *PotentialAmnesiaEvidence + Polc *ProofOfLockChange +} +``` + +`LunaticValidatorEvidence` represents a validator that has signed for an arbitrary application state. +This attack only applies to Light clients. + +```go +type LunaticValidatorEvidence struct { + Header *Header + Vote *Vote + InvalidHeaderField string +} +``` + +`PhantomValidatorEvidence` represents a validator that has signed for a block where it was not part of the validator set. +This attack also only applies to Light clients. Phantom validators must still be staked. `LastHeightValidatorWasInSet` +indicated the height that they last voted. + + +```go +type PhantomValidatorEvidence struct { + Vote *Vote + LastHeightValidatorWasInSet int64 +} +``` ## Validation From 89922df77560fd3bb34a2e25e87ad8fd2c4c20b2 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 23 Jun 2020 12:19:37 +0400 Subject: [PATCH 064/223] abci: add AppVersion to ConsensusParams (#106) --- spec/abci/abci.md | 8 +++++- spec/blockchain/blockchain.md | 46 ++++++++++++++++++-------------- spec/blockchain/state.md | 50 ++++++++++++++++++++++++++--------- 3 files changed, 70 insertions(+), 34 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 71b94e6c0..ea5b6f708 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -596,7 +596,8 @@ via light client. - `Block (BlockParams)`: Parameters limiting the size of a block and time between consecutive blocks. - `Evidence (EvidenceParams)`: Parameters limiting the validity of evidence of byzantine behaviour. - - `Validator (ValidatorParams)`: Parameters limitng the types of pubkeys validators can use. + - `Validator (ValidatorParams)`: Parameters limiting the types of pubkeys validators can use. + - `Version (VersionParams)`: The ABCI application version. ### BlockParams @@ -631,6 +632,11 @@ via light client. - `PubKeyTypes ([]string)`: List of accepted pubkey types. Uses same naming as `PubKey.Type`. +### VersionParams + +- **Fields**: + - `AppVersion (uint64)`: The ABCI application version. + ### Proof - **Fields**: diff --git a/spec/blockchain/blockchain.md b/spec/blockchain/blockchain.md index aebb492b0..38b87eabc 100644 --- a/spec/blockchain/blockchain.md +++ b/spec/blockchain/blockchain.md @@ -67,16 +67,16 @@ Further details on each of these fields is described below. ## Version -The `Version` contains the protocol version for the blockchain and the -application as two `uint64` values: - ```go type Version struct { - Block uint64 - App uint64 + Block uint64 + App uint64 } ``` +The `Version` contains the protocol version for the blockchain and the +application as two `uint64` values. + ## BlockID The `BlockID` contains two distinct Merkle roots of the block. @@ -296,11 +296,11 @@ A Header is valid if its corresponding fields are valid. ### Version ``` -block.Version.Block == state.Version.Block -block.Version.App == state.Version.App +block.Version.Block == state.Version.Consensus.Block +block.Version.App == state.Version.Consensus.App ``` -The block version must match the state version. +The block version must match consensus version from the state. ### ChainID @@ -551,18 +551,24 @@ and `ABCIApp` is an ABCI application that can return results and changes to the set (TODO). Execute is defined as: ```go -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) +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) - return State{ - LastResults: abciResponses.DeliverTxResults, - AppHash: AppHash, - LastValidators: state.Validators, - Validators: state.NextValidators, - NextValidators: UpdateValidators(state.NextValidators, ValidatorChanges), - ConsensusParams: UpdateConsensusParams(state.ConsensusParams, ConsensusParamChanges), - } + nextConsensusParams := UpdateConsensusParams(state.ConsensusParams, ConsensusParamChanges) + return State{ + LastResults: abciResponses.DeliverTxResults, + AppHash: AppHash, + LastValidators: state.Validators, + Validators: state.NextValidators, + NextValidators: UpdateValidators(state.NextValidators, ValidatorChanges), + ConsensusParams: nextConsensusParams, + Version: { + Consensus: { + AppVersion: nextConsensusParams.Version.AppVersion, + }, + }, + } } ``` diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md index b503d5718..09b583cb5 100644 --- a/spec/blockchain/state.md +++ b/spec/blockchain/state.md @@ -30,6 +30,25 @@ type State struct { Note there is a hard-coded limit of 10000 validators. This is inherited from the limit on the number of votes in a commit. +### Version + +```go +type Version struct { + consensus Consensus + software string +} +``` + +The `Consensus` contains the protocol version for the blockchain and the +application as two `uint64` values: + +```go +type Consensus struct { + Block uint64 + App uint64 +} +``` + ### Result ```go @@ -90,36 +109,41 @@ type ConsensusParams struct { Block Evidence Validator + Version } type hashedParams struct { - BlockMaxBytes int64 - BlockMaxGas int64 + BlockMaxBytes int64 + BlockMaxGas int64 } func (params ConsensusParams) Hash() []byte { - SHA256(hashedParams{ - BlockMaxBytes: params.Block.MaxBytes, - BlockMaxGas: params.Block.MaxGas, - }) + SHA256(hashedParams{ + BlockMaxBytes: params.Block.MaxBytes, + BlockMaxGas: params.Block.MaxGas, + }) } type BlockParams struct { - MaxBytes int64 - MaxGas int64 - TimeIotaMs int64 + MaxBytes int64 + MaxGas int64 + TimeIotaMs int64 } type EvidenceParams struct { - MaxAgeNumBlocks int64 - MaxAgeDuration time.Duration - MaxNum uint32 - ProofTrialPeriod int64 + MaxAgeNumBlocks int64 + MaxAgeDuration time.Duration + MaxNum uint32 + ProofTrialPeriod int64 } type ValidatorParams struct { PubKeyTypes []string } + +type VersionParams struct { + AppVersion uint64 +} ``` #### Block From 6b570e2111d8e53fe4b22b86063e800d18ca7978 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Fri, 10 Jul 2020 20:16:13 +0200 Subject: [PATCH 065/223] abci: tweak node sync estimate (#115) --- spec/abci/apps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/abci/apps.md b/spec/abci/apps.md index 3adead3a4..e4208d402 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -491,7 +491,7 @@ This happens if we crashed after the app finished Commit but before Tendermint s 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 weeks or months. +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 From b10ff00e1b6a62535ca9057f39c527e6df3f25b3 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 22 Jul 2020 16:50:24 +0400 Subject: [PATCH 066/223] spec/abci: expand on Validator#Address (#118) Refs https://github.com/tendermint/tendermint/issues/3732 --- spec/abci/abci.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index ea5b6f708..2188c07bf 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -532,7 +532,7 @@ via light client. ### Validator - **Fields**: - - `Address ([]byte)`: Address of the validator (hash of the public key) + - `Address ([]byte)`: Address of the validator (the first 20 bytes of SHA256(public key)) - `Power (int64)`: Voting power of the validator - **Usage**: - Validator identified by address From 8ff136c7167f4bc51d3ca53c9b349bd0a724ed1b Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 28 Jul 2020 16:51:14 +0200 Subject: [PATCH 067/223] blockchain: rename to core (#123) --- rfc/README.md | 2 ++ spec/README.md | 6 +++--- spec/abci/README.md | 2 +- spec/abci/abci.md | 2 +- spec/abci/client-server.md | 4 ++-- spec/blockchain/readme.md | 5 ----- spec/consensus/consensus.md | 4 ++-- .../blockchain.md => core/data_structures.md} | 4 +--- spec/{blockchain => core}/encoding.md | 0 spec/core/readme.md | 11 +++++++++++ spec/{blockchain => core}/state.md | 0 11 files changed, 23 insertions(+), 17 deletions(-) delete mode 100644 spec/blockchain/readme.md rename spec/{blockchain/blockchain.md => core/data_structures.md} (99%) rename spec/{blockchain => core}/encoding.md (100%) create mode 100644 spec/core/readme.md rename spec/{blockchain => core}/state.md (100%) diff --git a/rfc/README.md b/rfc/README.md index 9682b9f1a..ef91e9d30 100644 --- a/rfc/README.md +++ b/rfc/README.md @@ -21,3 +21,5 @@ If recorded decisions turned out to be lacking, convene a discussion, record the Some RFC's will be presented at a Tendermint Dev Session. If you are an outside contributor and have submitted a RFC, you may be invited to present your RFC at one of these calls. ## Table of Contents + +[001-block-retention](./001-block-retention.md) diff --git a/spec/README.md b/spec/README.md index 4ed12aef3..fcec18f63 100644 --- a/spec/README.md +++ b/spec/README.md @@ -22,9 +22,9 @@ please submit them to our [bug bounty](https://tendermint.com/security)! ### Data Structures -- [Encoding and Digests](./blockchain/encoding.md) -- [Blockchain](./blockchain/blockchain.md) -- [State](./blockchain/state.md) +- [Encoding and Digests](./core/encoding.md) +- [Blockchain](./core/data_structures.md) +- [State](./core/state.md) ### Consensus Protocol diff --git a/spec/abci/README.md b/spec/abci/README.md index 4bf311119..128f24551 100644 --- a/spec/abci/README.md +++ b/spec/abci/README.md @@ -10,7 +10,7 @@ _methods_, where each method has a corresponding `Request` and `Response` message type. Tendermint calls the ABCI methods on the ABCI application by sending the `Request*` messages and receiving the `Response*` messages in return. -All message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/proto/abci/types.proto). +All message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/abci/types.proto). This allows Tendermint to run applications written in any programming language. This specification is split as follows: diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 2188c07bf..663c51df7 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -3,7 +3,7 @@ ## Overview The ABCI message types are defined in a [protobuf -file](https://github.com/tendermint/tendermint/blob/master/proto/abci/types.proto). +file](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/abci/types.proto). ABCI methods are split across four separate ABCI _connections_: diff --git a/spec/abci/client-server.md b/spec/abci/client-server.md index c5c77be89..967199455 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -9,7 +9,7 @@ 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/abci/types.proto). +[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. @@ -49,7 +49,7 @@ 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/abci/types.proto) +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/). diff --git a/spec/blockchain/readme.md b/spec/blockchain/readme.md deleted file mode 100644 index bd9b7e4f4..000000000 --- a/spec/blockchain/readme.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -cards: true ---- - -# Blockchain diff --git a/spec/consensus/consensus.md b/spec/consensus/consensus.md index 9c43ea615..93696ab31 100644 --- a/spec/consensus/consensus.md +++ b/spec/consensus/consensus.md @@ -17,7 +17,7 @@ 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](../blockchain/blockchain.md#vote). + 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 @@ -251,7 +251,7 @@ 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](../blockchain/blockchain.md#commit), the + 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 diff --git a/spec/blockchain/blockchain.md b/spec/core/data_structures.md similarity index 99% rename from spec/blockchain/blockchain.md rename to spec/core/data_structures.md index 38b87eabc..86c51d704 100644 --- a/spec/blockchain/blockchain.md +++ b/spec/core/data_structures.md @@ -1,9 +1,7 @@ -# Blockchain +# Data Structures Here we describe the data structures in the Tendermint blockchain and the rules for validating them. -## Data Structures - The Tendermint blockchains consists of a short list of basic data types: - `Block` diff --git a/spec/blockchain/encoding.md b/spec/core/encoding.md similarity index 100% rename from spec/blockchain/encoding.md rename to spec/core/encoding.md diff --git a/spec/core/readme.md b/spec/core/readme.md new file mode 100644 index 000000000..5302239c7 --- /dev/null +++ b/spec/core/readme.md @@ -0,0 +1,11 @@ +--- +cards: true +--- + +# Core + +This section describes the core types and functionality of the Tendermint protocol implementation. + +[Core Data Structures](./data_structures.md) +[Encoding](./encoding.md) +[State](./state.md) diff --git a/spec/blockchain/state.md b/spec/core/state.md similarity index 100% rename from spec/blockchain/state.md rename to spec/core/state.md From 2bd673c8eb00acb6ba4ae0e1c6e556e757c6306e Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 29 Jul 2020 08:24:42 +0200 Subject: [PATCH 068/223] blockchain: remove duplicate evidence sections (#124) --- spec/core/data_structures.md | 11 ----------- spec/reactors/consensus/consensus.md | 6 +++--- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 86c51d704..c92395831 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -522,17 +522,6 @@ func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { where `pubKey.Verify` performs the appropriate digital signature verification of the `pubKey` against the given signature and message bytes. -## Evidence - -There is currently only one kind of evidence, `DuplicateVoteEvidence`. - -DuplicateVoteEvidence `ev` is valid if - -- `ev.VoteA` and `ev.VoteB` can be verified with `ev.PubKey` -- `ev.VoteA` and `ev.VoteB` have the same `Height, Round, Address, Index, Type` -- `ev.VoteA.BlockID != ev.VoteB.BlockID` -- `(block.Height - ev.VoteA.Height) < MAX_EVIDENCE_AGE` - # Execution Once a block is validated, it can be executed against the state. diff --git a/spec/reactors/consensus/consensus.md b/spec/reactors/consensus/consensus.md index ff15af746..c85b3b8f2 100644 --- a/spec/reactors/consensus/consensus.md +++ b/spec/reactors/consensus/consensus.md @@ -16,7 +16,7 @@ explained in a forthcoming document. For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the block as the block size is big, i.e., they don't embed the block inside `Proposal` and `VoteMessage`. Instead, they reach agreement on the `BlockID` (see `BlockID` definition in -[Blockchain](https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.md#blockid) section) +[Blockchain](https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md#blockid) section) that uniquely identifies each block. The block itself is disseminated to validator processes using peer-to-peer gossiping protocol. It starts by having a proposer first splitting a block into a number of block parts, that are then gossiped between @@ -67,7 +67,7 @@ type Proposal struct { VoteMessage 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/blockchain/blockchain.md#blockidd) +[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 @@ -81,7 +81,7 @@ type VoteMessage struct { ## BlockPartMessage -BlockPartMessage is sent when gossipping a piece of the proposed block. It contains height, round +BlockPartMessage is sent when gossiping a piece of the proposed block. It contains height, round and the block part. ```go From 3a295218480c361b75fc769efb50e85c4b171ee4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 29 Jul 2020 15:13:15 +0400 Subject: [PATCH 069/223] spec/consensus: canonical vs subjective commit Refs https://github.com/tendermint/tendermint/issues/2769 --- spec/consensus/consensus.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/consensus/consensus.md b/spec/consensus/consensus.md index 93696ab31..c7bedd60c 100644 --- a/spec/consensus/consensus.md +++ b/spec/consensus/consensus.md @@ -337,3 +337,13 @@ 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. The latter is what +each validator sees locally when they decide to commit a block. The former 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 everyone agrees on it, +even if its different from the first +2/3 votes they saw that caused them to +commit a block. Each block contains a canonical +2/3 commit for the previous +block. From 0445156ed91b4e561ca20f3e0cd2ae1b9d997cdd Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 29 Jul 2020 15:45:46 +0400 Subject: [PATCH 070/223] Apply suggestions from code review Co-authored-by: Igor Konnov --- spec/consensus/consensus.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/consensus/consensus.md b/spec/consensus/consensus.md index c7bedd60c..a409c86d4 100644 --- a/spec/consensus/consensus.md +++ b/spec/consensus/consensus.md @@ -340,10 +340,10 @@ attacks](#censorship-attacks). ### Canonical vs subjective commit -We distinguish between "canonical" and "subjective" commits. The latter is what -each validator sees locally when they decide to commit a block. The former is +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 everyone agrees on it, -even if its different from the first +2/3 votes they saw that caused them to -commit a block. Each block contains a canonical +2/3 commit for the previous +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. From 31b182b7aad2be587073b918e172a05f8dfc4723 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Fri, 31 Jul 2020 12:03:44 +0200 Subject: [PATCH 071/223] update spec with the removal of phantom validator evidence (#126) --- spec/consensus/light-client/accountability.md | 6 ++++++ spec/core/data_structures.md | 14 +------------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/spec/consensus/light-client/accountability.md b/spec/consensus/light-client/accountability.md index 29884cec5..9731f3aba 100644 --- a/spec/consensus/light-client/accountability.md +++ b/spec/consensus/light-client/accountability.md @@ -310,6 +310,12 @@ Consequences: **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. diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index c92395831..7cc4fdff6 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -236,7 +236,7 @@ type DuplicateVoteEvidence struct { `AmnesiaEvidence` represents a validator that has incorrectly voted for another block in a different round to the the block that the validator was previously locked on. This form of evidence is generated differently from the rest. See this -[ADR](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-056-proving-amnesia-attacks.md). +[ADR](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-056-proving-amnesia-attacks.md) for more information. ```go type AmnesiaEvidence struct { @@ -256,18 +256,6 @@ type LunaticValidatorEvidence struct { } ``` -`PhantomValidatorEvidence` represents a validator that has signed for a block where it was not part of the validator set. -This attack also only applies to Light clients. Phantom validators must still be staked. `LastHeightValidatorWasInSet` -indicated the height that they last voted. - - -```go -type PhantomValidatorEvidence struct { - Vote *Vote - LastHeightValidatorWasInSet int64 -} -``` - ## Validation Here we describe the validation rules for every element in a block. From c5e45ecb48a060b79f03fad4f38c324b07d04379 Mon Sep 17 00:00:00 2001 From: Marko Baricevic Date: Mon, 3 Aug 2020 10:48:38 +0200 Subject: [PATCH 072/223] bring blockchain back --- spec/blockchain/blockchain.md | 3 +++ spec/blockchain/encoding.md | 3 +++ spec/blockchain/readme.md | 11 +++++++++++ spec/blockchain/state.md | 3 +++ 4 files changed, 20 insertions(+) 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 diff --git a/spec/blockchain/blockchain.md b/spec/blockchain/blockchain.md new file mode 100644 index 000000000..fcc080ee7 --- /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 000000000..aa2c9ab3f --- /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 000000000..0d5666e3e --- /dev/null +++ b/spec/blockchain/readme.md @@ -0,0 +1,11 @@ +--- +cards: false +--- + +# Blockchain + +This section describes the core types and functionality of the Tendermint protocol implementation. + +[Core Data Structures](./data_structures.md) +[Encoding](./encoding.md) +[State](./state.md) diff --git a/spec/blockchain/state.md b/spec/blockchain/state.md new file mode 100644 index 000000000..f4f1d9525 --- /dev/null +++ b/spec/blockchain/state.md @@ -0,0 +1,3 @@ +# State + +Deprecated see [core/state.md](../core/state.md) From ef1e0ff886cdbe1168c810d57281587b2cd3a34a Mon Sep 17 00:00:00 2001 From: Marko Baricevic Date: Mon, 3 Aug 2020 10:52:05 +0200 Subject: [PATCH 073/223] add correct links --- spec/blockchain/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/blockchain/readme.md b/spec/blockchain/readme.md index 0d5666e3e..724524d6a 100644 --- a/spec/blockchain/readme.md +++ b/spec/blockchain/readme.md @@ -6,6 +6,6 @@ cards: false This section describes the core types and functionality of the Tendermint protocol implementation. -[Core Data Structures](./data_structures.md) -[Encoding](./encoding.md) -[State](./state.md) +[Core Data Structures](../core/data_structures.md) +[Encoding](../core/encoding.md) +[State](../core/state.md) From e96921822ddc40794f8221fa552741a24c82131a Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 6 Aug 2020 11:10:11 +0200 Subject: [PATCH 074/223] spec: revert event hashing (#132) --- spec/abci/abci.md | 7 ++----- spec/core/data_structures.md | 9 +++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 663c51df7..cdaec75f7 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -506,7 +506,7 @@ via light client. - `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the Merkle root of the application state after executing the previous block's transactions - - `LastResultsHash ([]byte)`: Root hash of `BeginBlock` events, root hash of all results from the txs from the previous block, and `EndBlock` events + - `LastResultsHash ([]byte)`: Root hash of all results from the txs from the previous block. - `EvidenceHash ([]byte)`: Hash of the evidence included in this block - `ProposerAddress ([]byte)`: Original proposer for the block - **Usage**: @@ -515,10 +515,7 @@ via light client. especially height and time. - Provides the proposer of the current block, for use in proposer-based reward mechanisms. - - `LastResultsHash` is the root hash of a Merkle tree w/ 3 leafs: - proto-encoded `ResponseBeginBlock#Events`, root hash of a Merkle tree build - from `ResponseDeliverTx` responses (Log, Info and Codespace fields are - ignored), and proto-encoded `ResponseEndBlock#Events`. + - `LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`, `Info`, `Codespace` and `Events` fields are ignored). ### Version diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 7cc4fdff6..798e1a94b 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -54,7 +54,7 @@ type Header struct { NextValidatorsHash []byte // validators for the next block ConsensusHash []byte // consensus params for current block AppHash []byte // state after txs from the previous block - LastResultsHash []byte // root hash of BeginBlock events, root hash of all results from the txs from the previous block, and EndBlock events + LastResultsHash []byte // root hash of all results from the txs from the previous block // consensus info EvidenceHash []byte // evidence included in the block @@ -413,13 +413,10 @@ The first block has `block.Header.AppHash == []byte{}`. ### LastResultsHash ```go -block.LastResultsHash == MerkleRoot(ResponseBeginBlock.Events, MerkleRoot([]ResponseDeliverTx), ResponseEndBlock.Events) +block.LastResultsHash == MerkleRoot([]ResponseDeliverTx) ``` -`LastResultsHash` is the root hash of a Merkle tree w/ 3 leafs: proto-encoded -`ResponseBeginBlock#Events`, root hash of a Merkle tree build from -`ResponseDeliverTx` responses (Log, Info and Codespace fields are ignored), and -proto-encoded `ResponseEndBlock#Events`. +`LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`,`Info`, `Codespace` and `Events` fields are ignored). The first block has `block.Header.ResultsHash == []byte{}`. From 713a773c818e0cba3f4cb8ea40686fb322f293ec Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Mon, 10 Aug 2020 21:20:24 +0200 Subject: [PATCH 075/223] Evidence time is sourced from block time (#138) --- spec/abci/abci.md | 5 ++--- spec/core/data_structures.md | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index cdaec75f7..781469247 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -574,9 +574,8 @@ via light client. - `Type (string)`: Type of the evidence. A hierarchical path like "duplicate/vote". - `Validator (Validator`: The offending validator - - `Height (int64)`: Height when the offense was committed - - `Time (google.protobuf.Timestamp)`: Time of the block at height `Height`. - It is the proposer's local time when block was created. + - `Height (int64)`: Height when the offense occured + - `Time (google.protobuf.Timestamp)`: Time of the block that was committed at the height that the offense occured - `TotalVotingPower (int64)`: Total voting power of the validator set at height `Height` diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 798e1a94b..583b22cf7 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -221,7 +221,8 @@ type Evidence interface { All evidence can be encoded and decoded to and from Protobuf with the `EvidenceToProto()` and `EvidenceFromProto()` functions. The [Fork Accountability](../consensus/light-client/accountability.md) -document provides a good overview for the types of evidence and how they occur. +document provides a good overview for the types of evidence and how they occur. Each evidence uses +the timestamp of the block that the evidence occured at to indicate the age of the evidence. `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`. @@ -230,6 +231,8 @@ in the same round of the same height. Votes are lexicographically sorted on `Blo type DuplicateVoteEvidence struct { VoteA *Vote VoteB *Vote + + Timestamp time.Time } ``` @@ -253,6 +256,8 @@ type LunaticValidatorEvidence struct { Header *Header Vote *Vote InvalidHeaderField string + + Timestamp time.Time } ``` From 604923e034d2847bb7468ed6354e506a889bcfcb Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 11 Aug 2020 13:28:59 +0200 Subject: [PATCH 076/223] RFC-002: non-zero genesis (#119) --- rfc/002-nonzero-genesis.md | 81 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 rfc/002-nonzero-genesis.md diff --git a/rfc/002-nonzero-genesis.md b/rfc/002-nonzero-genesis.md new file mode 100644 index 000000000..a188ec455 --- /dev/null +++ b/rfc/002-nonzero-genesis.md @@ -0,0 +1,81 @@ +# RFC 002: Non-Zero Genesis + +## Changelog + +- 2020-07-26: Initial draft (@erikgrinaker) +- 2020-07-28: Use weak chain linking, i.e. `predecessor` field (@erikgrinaker) +- 2020-07-31: Drop chain linking (@erikgrinaker) +- 2020-08-03: Add `State.InitialHeight` (@erikgrinaker) + +## Author(s) + +- Erik Grinaker (@erikgrinaker) + +## Context + +The recommended upgrade path for block protocol-breaking upgrades is currently to hard fork the +chain (see e.g. [`cosmoshub-3` upgrade](https://blog.cosmos.network/cosmos-hub-3-upgrade-announcement-39c9da941aee)). +This is done by halting all validators at a predetermined height, exporting the application +state via application-specific tooling, and creating an entirely new chain using the exported +application state. + +As far as Tendermint is concerned, the upgraded chain is a completely separate chain, with e.g. +a new chain ID and genesis file. Notably, the new chain starts at height 1, and has none of the +old chain's block history. This causes problems for integrators, e.g. coin exchanges and +wallets, that assume a monotonically increasing height for a given blockchain. Users also find +it confusing that a given height can now refer to distinct states depending on the chain +version. + +An ideal solution would be to always retain block backwards compatibility in such a way that chain +history is never lost on upgrades. However, this may require a significant amount of engineering +work that is not viable for the planned Stargate release (Tendermint 0.34), and may prove too +restrictive for future development. + +As a first step, allowing the new chain to start from an initial height specified in the genesis +file would at least provide monotonically increasing heights. There was a proposal to include the +last block header of the previous chain as well, but since the genesis file is not verified and +hashed (only specific fields are) this would not be trustworthy. + +External tooling will be required to map historical heights onto e.g. archive nodes that contain +blocks from previous chain version. Tendermint will not include any such functionality. + +## Proposal + +Tendermint will allow chains to start from an arbitrary initial height: + +* A new field `initial_height` is added to the genesis file, defaulting to `1`. It can be set to any +non-negative integer, and `0` is considered equivalent to `1`. + +* A new field `InitialHeight` is added to the ABCI `RequestInitChain` message, with the same value +and semantics as the genesis field. + +* A new field `InitialHeight` is added to the `state.State` struct, where `0` is considered invalid. + Including the field here simplifies implementation, since the genesis value does not have to be + propagated throughout the code base separately, but it is not strictly necessary. + +ABCI applications may have to be updated to handle arbitrary initial heights, otherwise the initial +block may fail. + +## Status + +Proposed + +## Consequences + +### Positive + +* Heights can be unique throughout the history of a "logical" chain, across hard fork upgrades. + +### Negative + +* Upgrades still cause loss of block history. + +* Integrators will have to map height ranges to specific archive nodes/networks to query history. + +### Neutral + +* There is no explicit link to the last block of the previous chain. + +## References + +- [#2543: Allow genesis file to start from non-zero height w/ prev block header](https://github.com/tendermint/tendermint/issues/2543) \ No newline at end of file From 95acfdead1d57f9f6b4aa1a75a47efa36c0ce425 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 11 Aug 2020 16:48:22 +0200 Subject: [PATCH 077/223] abci: add ResponseInitChain.app_hash (#140) --- spec/abci/abci.md | 3 +++ spec/core/data_structures.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 781469247..8ce0bf815 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -243,6 +243,7 @@ via light client. - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters (optional). - `Validators ([]ValidatorUpdate)`: Initial validator set (optional). + - `AppHash ([]byte)`: Initial application hash. - **Usage**: - Called once upon genesis. - If ResponseInitChain.Validators is empty, the initial validator set will be the RequestInitChain.Validators @@ -252,6 +253,8 @@ via light client. 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). + - The returned `AppHash` must match the hash specified in the genesis file, and will be + recorded in the initial genesis block. ### Query diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 583b22cf7..b4f2dd0ce 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -413,7 +413,7 @@ block.AppHash == state.AppHash 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 has `block.Header.AppHash == []byte{}`. +The first block's `block.Header.AppHash` is given by `ResponseInitChain.app_hash`, which must match the app hash specified in the genesis file. ### LastResultsHash From 89ac8f6e622d641097425461e1c10f4cd2490e2d Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 11 Aug 2020 16:52:23 +0200 Subject: [PATCH 078/223] update hashing of empty inputs, and initial block LastResultsHash (#141) --- spec/core/data_structures.md | 2 +- spec/core/encoding.md | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index b4f2dd0ce..421ab283a 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -423,7 +423,7 @@ block.LastResultsHash == MerkleRoot([]ResponseDeliverTx) `LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`,`Info`, `Codespace` and `Events` fields are ignored). -The first block has `block.Header.ResultsHash == []byte{}`. +The first block has `block.Header.ResultsHash == MerkleRoot(nil)`, i.e. the hash of an empty input, for RFC-6962 conformance. ## EvidenceHash diff --git a/spec/core/encoding.md b/spec/core/encoding.md index 31b9c0625..649d91e67 100644 --- a/spec/core/encoding.md +++ b/spec/core/encoding.md @@ -216,6 +216,11 @@ h0 h1 h2 h3 h0 h1 h2 h3 h4 h5 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...)) @@ -232,7 +237,7 @@ func getSplitPoint(k int) { ... } func MerkleRoot(items [][]byte) []byte{ switch len(items) { case 0: - return nil + return empthHash() case 1: return leafHash(items[0]) default: From 430a4d05041655863f1610abffc5b7fba94f912c Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Wed, 12 Aug 2020 14:19:09 +0200 Subject: [PATCH 079/223] update evidence verification (#139) --- spec/core/data_structures.md | 46 +++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 421ab283a..3ced4de04 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -221,9 +221,11 @@ type Evidence interface { All evidence can be encoded and decoded to and from Protobuf with the `EvidenceToProto()` and `EvidenceFromProto()` functions. The [Fork Accountability](../consensus/light-client/accountability.md) -document provides a good overview for the types of evidence and how they occur. Each evidence uses +document provides a good overview for the types of evidence and how they occur. For evidence to be committed onchain, it must adhere to the validation rules of each evidence and must not be expired. The expiration age, measured in both block height and time is set in `EvidenceParams`. Each evidence uses the timestamp of the block that the evidence occured at to indicate the age of the evidence. +### 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`. @@ -236,6 +238,20 @@ type DuplicateVoteEvidence struct { } ``` +Valid Duplicate Vote Evidence must adhere to the following rules: + +- Validator Address, Height, Round and Type of vote 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 valid (using the chainID) + +- Time must be equal to the block time + +### AmensiaEvidence + `AmnesiaEvidence` represents a validator that has incorrectly voted for another block in a different round to the the block that the validator was previously locked on. This form of evidence is generated differently from the rest. See this @@ -248,6 +264,22 @@ type AmnesiaEvidence struct { } ``` +Valid Amnesia Evidence must adhere to the following rules: + +- Validator Address and Height must be the same and the Round must be different. + +- The BlockID's must be different and the BlockID of the first vote must not be nil. + +- The vote signature must be valid (using the chainID). + +- The evidence either must have a complete and valid `ProofOfLockChange` or have stood in the nodes evidence pool for the `ProofTrialPeriod`. + +- The validator must have been in the validator set. + +- Time must be equal to the corresponding block time. + +### LunaticValidatorEvidence + `LunaticValidatorEvidence` represents a validator that has signed for an arbitrary application state. This attack only applies to Light clients. @@ -261,6 +293,18 @@ type LunaticValidatorEvidence struct { } ``` +Valid Lunatic Validator Evidence must adhere to the following rules: + +- Header must have an invalid field compared to the correctly derived header that the node has in either the `ValidatorHash`, `NextValidatorHash`, `ConsensusHash`, `AppHash`, or `LastResultsHash`. + +- The vote must be for the incorrect header (BlockID of the vote must match the hash of the header). + +- The vote must be correcly signed (using the chainID). + +- The validator must have been in a correct validator set within the bonding period. + +- Time must be equal to the corresponding block time of the header that the node has (not the incorrect header). + ## Validation Here we describe the validation rules for every element in a block. From a84c59734f58f16ce073e5191ead86ee50c2110d Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 13 Aug 2020 10:44:27 +0200 Subject: [PATCH 080/223] accept RFC-002 (#142) --- rfc/002-nonzero-genesis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/002-nonzero-genesis.md b/rfc/002-nonzero-genesis.md index a188ec455..3545d710c 100644 --- a/rfc/002-nonzero-genesis.md +++ b/rfc/002-nonzero-genesis.md @@ -58,7 +58,7 @@ block may fail. ## Status -Proposed +Accepted ## Consequences From f3207cee52b182f64b0b6f8d9fd6190e2e172c44 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 13 Aug 2020 10:45:05 +0200 Subject: [PATCH 081/223] add description of arbitrary initial height (#135) --- spec/abci/abci.md | 6 +++--- spec/core/data_structures.md | 12 ++++++++---- spec/core/state.md | 10 ++++++++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 8ce0bf815..afe3a4729 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -239,6 +239,7 @@ via light client. - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters. - `Validators ([]ValidatorUpdate)`: Initial genesis validators, sorted by voting power. - `AppStateBytes ([]byte)`: Serialized initial application state. Amino-encoded JSON bytes. + - `InitialHeight (int64)`: Height of the initial block (typically `1`). - **Response**: - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters (optional). @@ -498,9 +499,8 @@ via light client. - `ChainID (string)`: ID of the blockchain - `Height (int64)`: Height of the block in the chain - `Time (google.protobuf.Timestamp)`: Time of the previous block. - For heights > 1, it's the weighted median of the timestamps of the valid - votes in the block.LastCommit. - For height == 1, it's genesis time. + For most blocks it's the weighted median of the timestamps of the valid votes in the + block.LastCommit, except for the initial height where it's the genesis time. - `LastBlockID (BlockID)`: Hash of the previous (parent) block - `LastCommitHash ([]byte)`: Hash of the previous block's commit - `ValidatorsHash ([]byte)`: Hash of the validator set for this block diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 3ced4de04..3ffcf3321 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -349,10 +349,11 @@ ChainID must be less than 50 bytes. ```go block.Header.Height > 0 +block.Header.Height >= state.InitialHeight block.Header.Height == prevBlock.Header.Height + 1 ``` -The height is an incrementing integer. The first block has `block.Header.Height == 1`. +The height is an incrementing integer. The first block has `block.Header.Height == state.InitialHeight`, derived from the genesis file. ### Time @@ -371,7 +372,7 @@ The timestamp of the first block must be equal to the genesis time (since there's no votes to compute the median). ``` -if block.Header.Height == 1 { +if block.Header.Height == state.InitialHeight { block.Header.Timestamp == genesisTime } ``` @@ -494,7 +495,7 @@ Arbitrary length array of arbitrary length byte-arrays. The first height is an exception - it requires the `LastCommit` to be empty: ```go -if block.Header.Height == 1 { +if block.Header.Height == state.InitialHeight { len(b.LastCommit) == 0 } ``` @@ -563,7 +564,7 @@ Once a block is validated, it can be executed against the state. The state follows this recursive equation: ```go -state(1) = InitialState +state(initialHeight) = InitialState state(h+1) <- Execute(state(h), ABCIApp, block(h)) ``` @@ -579,8 +580,11 @@ func Execute(s State, app ABCIApp, block Block) State { 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), diff --git a/spec/core/state.md b/spec/core/state.md index 09b583cb5..446a74be2 100644 --- a/spec/core/state.md +++ b/spec/core/state.md @@ -15,18 +15,24 @@ validation. ```go type State struct { + ChainID string + InitialHeight int64 + Version Version LastResults []Result - AppHash []byte + AppHash []byte LastValidators []Validator - Validators []Validator + Validators []Validator NextValidators []Validator 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. From 8dd2ed4c6fe12459edeb9b783bdaaaeb590ec15c Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 13 Aug 2020 13:05:12 +0200 Subject: [PATCH 082/223] update ResponseInitChain.app_hash description (#143) --- spec/abci/abci.md | 2 -- spec/core/data_structures.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index afe3a4729..b297e2b99 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -254,8 +254,6 @@ via light client. 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). - - The returned `AppHash` must match the hash specified in the genesis file, and will be - recorded in the initial genesis block. ### Query diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 3ffcf3321..e95a39f21 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -458,7 +458,7 @@ block.AppHash == state.AppHash 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`, which must match the app hash specified in the genesis file. +The first block's `block.Header.AppHash` is given by `ResponseInitChain.app_hash`. ### LastResultsHash From c9d35646342ab1b7dbac1c7aaef6c721216fef58 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Fri, 21 Aug 2020 12:40:31 +0200 Subject: [PATCH 083/223] 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. --- README.md | 24 ++++++++++++++++++++++-- papers/README.md | 5 ----- research/README.md | 5 ----- 3 files changed, 22 insertions(+), 12 deletions(-) delete mode 100644 papers/README.md delete mode 100644 research/README.md diff --git a/README.md b/README.md index 9056750ea..1e4159a19 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,25 @@ # Tendermint Spec -This repository contains specs of the current and future implementation of Tendermint the protocol, papers that have been published by the ICF or AIB and ongoing research in a variety of areas. -- For the pdf, see the [latest release](https://github.com/tendermint/spec/releases). +This repository contains specifications for the Tendermint protocol. For the pdf, see the [latest release](https://github.com/tendermint/spec/releases). + +There are currently two implementations of the Tendermint protocol, +maintained by two separate-but-collaborative entities: +One in [Go](https://github.com/tendermint/tendermint), +maintained by Interchain GmbH, +and one in [Rust](https://github.com/informalsystems/tendermint-rs), +maintained by Informal Systems. + +There have been inadvertent divergences in the specs followed +by the Go implementation and the Rust implementation respectively. +However, we are worked to reconverge these specs into a single unified spec. +Consequently, this repository is in a bit of a state of flux. + +At the moment, the spec followed by the Go implementation +(tendermint/tendermint) is in the [spec](spec) directory, +while the spec followed by the Rust implementation +(informalsystems/tendermint-rs) is in the rust-spec +directory. TLA+ specifications are also in the rust-spec directory. + +Over time, these specs will converge in the spec directory. +Once they have fully converged, we will version the spec moving forward. \ No newline at end of file diff --git a/papers/README.md b/papers/README.md deleted file mode 100644 index dd8835681..000000000 --- a/papers/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Papers - -This folder houses Papers that have either been published or are a work in progress. - -## Table of Contents diff --git a/research/README.md b/research/README.md deleted file mode 100644 index dcb51a4bc..000000000 --- a/research/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Research - -This folder holds ongoing research and topics of interest. The [Interchain Foundation](https://interchain.io) has research topics that they are willing fund the research for, to find out more you can go to there [funding repo](https://github.com/interchainio/funding/blob/master/research.md). - -You can apply for a research grant [here](https://docs.google.com/forms/d/e/1FAIpQLSclH4R5G7WgNpKPvXxPPFRA7rAoyX8nNvsJAQJpZNZwWWjFmA/viewform) From efbbc9462ffe6a1ff455e88aaa1051cd91dd2b76 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 24 Aug 2020 11:47:31 +0200 Subject: [PATCH 084/223] ci: add markdown linter (#146) --- .github/workflows/linter.yml | 28 + .markdownlint.yml | 7 + README.md | 25 +- rfc/001-block-retention.md | 40 +- rfc/002-nonzero-genesis.md | 20 +- spec/abci/abci.md | 512 +++++++++--------- spec/abci/apps.md | 48 +- spec/consensus/bft-time.md | 55 +- spec/consensus/consensus-paper/README.md | 7 +- spec/consensus/consensus.md | 4 +- spec/consensus/creating-proposal.md | 20 +- spec/consensus/light-client/README.md | 9 +- spec/consensus/light-client/accountability.md | 71 +-- spec/consensus/light-client/verification.md | 50 +- spec/consensus/readme.md | 2 +- spec/consensus/signing.md | 60 +- spec/core/data_structures.md | 212 ++++---- spec/core/encoding.md | 86 +-- spec/core/state.md | 44 +- spec/p2p/config.md | 3 +- spec/p2p/connection.md | 8 +- spec/p2p/peer.md | 20 +- spec/reactors/block_sync/bcv1/impl-v1.md | 149 ++--- spec/reactors/block_sync/impl.md | 39 +- spec/reactors/block_sync/reactor.md | 32 +- spec/reactors/consensus/consensus-reactor.md | 94 ++-- spec/reactors/consensus/consensus.md | 14 +- spec/reactors/consensus/proposer-selection.md | 130 +++-- spec/reactors/mempool/config.md | 2 +- spec/reactors/mempool/functionality.md | 2 +- spec/reactors/mempool/reactor.md | 2 +- spec/reactors/pex/pex.md | 4 +- spec/reactors/state_sync/reactor.md | 38 +- 33 files changed, 942 insertions(+), 895 deletions(-) create mode 100644 .github/workflows/linter.yml create mode 100644 .markdownlint.yml diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 000000000..0a6c546c6 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,28 @@ +name: Lint +on: + push: + branches: + - master + paths: + - "**.md" + pull_request: + branches: [master] + paths: + - "**.md" + +jobs: + build: + name: Super linter + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + - name: Lint Code Base + uses: docker://github/super-linter:v3 + env: + LINTER_RULES_PATH: . + VALIDATE_ALL_CODEBASE: true + DEFAULT_BRANCH: master + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VALIDATE_MD: true + MARKDOWN_CONFIG_FILE: .markdownlint.yml diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 000000000..9f8a8d689 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,7 @@ +default: true +MD007: { indent: 4 } +MD013: false +MD024: { siblings_only: true } +MD025: false +MD033: false +MD036: false diff --git a/README.md b/README.md index 1e4159a19..d93d06404 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,24 @@ # Tendermint Spec - This repository contains specifications for the Tendermint protocol. For the pdf, see the [latest release](https://github.com/tendermint/spec/releases). -There are currently two implementations of the Tendermint protocol, +There are currently two implementations of the Tendermint protocol, maintained by two separate-but-collaborative entities: -One in [Go](https://github.com/tendermint/tendermint), -maintained by Interchain GmbH, +One in [Go](https://github.com/tendermint/tendermint), +maintained by Interchain GmbH, and one in [Rust](https://github.com/informalsystems/tendermint-rs), -maintained by Informal Systems. +maintained by Informal Systems. -There have been inadvertent divergences in the specs followed -by the Go implementation and the Rust implementation respectively. -However, we are worked to reconverge these specs into a single unified spec. +There have been inadvertent divergences in the specs followed +by the Go implementation and the Rust implementation respectively. +However, we are worked to reconverge these specs into a single unified spec. Consequently, this repository is in a bit of a state of flux. -At the moment, the spec followed by the Go implementation -(tendermint/tendermint) is in the [spec](spec) directory, -while the spec followed by the Rust implementation +At the moment, the spec followed by the Go implementation +(tendermint/tendermint) is in the [spec](spec) directory, +while the spec followed by the Rust implementation (informalsystems/tendermint-rs) is in the rust-spec directory. TLA+ specifications are also in the rust-spec directory. -Over time, these specs will converge in the spec directory. -Once they have fully converged, we will version the spec moving forward. \ No newline at end of file +Over time, these specs will converge in the spec directory. +Once they have fully converged, we will version the spec moving forward. diff --git a/rfc/001-block-retention.md b/rfc/001-block-retention.md index f2b7de7ee..80377145e 100644 --- a/rfc/001-block-retention.md +++ b/rfc/001-block-retention.md @@ -15,21 +15,21 @@ Currently, all Tendermint nodes contain the complete sequence of blocks from genesis up to some height (typically the latest chain height). This will no longer be true when the following features are released: -* [Block pruning](https://github.com/tendermint/tendermint/issues/3652): removes historical blocks and associated data (e.g. validator sets) up to some height, keeping only the most recent blocks. +- [Block pruning](https://github.com/tendermint/tendermint/issues/3652): removes historical blocks and associated data (e.g. validator sets) up to some height, keeping only the most recent blocks. -* [State sync](https://github.com/tendermint/tendermint/issues/828): bootstraps a new node by syncing state machine snapshots at a given height, but not historical blocks and associated data. +- [State sync](https://github.com/tendermint/tendermint/issues/828): bootstraps a new node by syncing state machine snapshots at a given height, but not historical blocks and associated data. To maintain the integrity of the chain, the use of these features must be coordinated such that necessary historical blocks will not become unavailable or lost forever. In particular: -* Some nodes should have complete block histories, for auditability, querying, and bootstrapping. +- Some nodes should have complete block histories, for auditability, querying, and bootstrapping. -* The majority of nodes should retain blocks longer than the Cosmos SDK unbonding period, for light client verification. +- The majority of nodes should retain blocks longer than the Cosmos SDK unbonding period, for light client verification. -* Some nodes must take and serve state sync snapshots with snapshot intervals less than the block retention periods, to allow new nodes to state sync and then replay blocks to catch up. +- Some nodes must take and serve state sync snapshots with snapshot intervals less than the block retention periods, to allow new nodes to state sync and then replay blocks to catch up. -* Applications may not persist their state on commit, and require block replay on restart. +- Applications may not persist their state on commit, and require block replay on restart. -* Only a minority of nodes can be state synced within the unbonding period, for light client verification and to serve block histories for catch-up. +- Only a minority of nodes can be state synced within the unbonding period, for light client verification and to serve block histories for catch-up. However, it is unclear if and how we should enforce this. It may not be possible to technically enforce all of these without knowing the state of the entire network, but it may also be unrealistic to expect this to be enforced entirely through social coordination. This is especially unfortunate since the consequences of misconfiguration can be permanent chain-wide data loss. @@ -65,13 +65,13 @@ As an example, we'll consider how the Cosmos SDK might make use of this. The spe The returned `retain_height` would be the lowest height that satisfies: -* Unbonding time: the time interval in which validators can be economically punished for misbehavior. Blocks in this interval must be auditable e.g. by the light client. +- Unbonding time: the time interval in which validators can be economically punished for misbehavior. Blocks in this interval must be auditable e.g. by the light client. -* IAVL snapshot interval: the block interval at which the underlying IAVL database is persisted to disk, e.g. every 10000 heights. Blocks since the last IAVL snapshot must be available for replay on application restart. +- IAVL snapshot interval: the block interval at which the underlying IAVL database is persisted to disk, e.g. every 10000 heights. Blocks since the last IAVL snapshot must be available for replay on application restart. -* State sync snapshots: blocks since the _oldest_ available snapshot must be available for state sync nodes to catch up (oldest because a node may be restoring an old snapshot while a new snapshot was taken). +- State sync snapshots: blocks since the _oldest_ available snapshot must be available for state sync nodes to catch up (oldest because a node may be restoring an old snapshot while a new snapshot was taken). -* Local config: archive nodes may want to retain more or all blocks, e.g. via a local config option `min-retain-blocks`. There may also be a need to vary rentention for other nodes, e.g. sentry nodes which do not need historical blocks. +- Local config: archive nodes may want to retain more or all blocks, e.g. via a local config option `min-retain-blocks`. There may also be a need to vary rentention for other nodes, e.g. sentry nodes which do not need historical blocks. ![Cosmos SDK block retention diagram](images/block-retention.png) @@ -83,26 +83,26 @@ Accepted ### Positive -* Application-specified block retention allows the application to take all relevant factors into account and prevent necessary blocks from being accidentally removed. +- Application-specified block retention allows the application to take all relevant factors into account and prevent necessary blocks from being accidentally removed. -* Node operators can independently decide whether they want to provide complete block histories (if local configuration for this is provided) and snapshots. +- Node operators can independently decide whether they want to provide complete block histories (if local configuration for this is provided) and snapshots. ### Negative -* Social coordination is required to run archival nodes, failure to do so may lead to permanent loss of historical blocks. +- Social coordination is required to run archival nodes, failure to do so may lead to permanent loss of historical blocks. -* Social coordination is required to run snapshot nodes, failure to do so may lead to inability to run state sync, and inability to bootstrap new nodes at all if no archival nodes are online. +- Social coordination is required to run snapshot nodes, failure to do so may lead to inability to run state sync, and inability to bootstrap new nodes at all if no archival nodes are online. ### Neutral -* Reduced block retention requires application changes, and cannot be controlled directly in Tendermint. +- Reduced block retention requires application changes, and cannot be controlled directly in Tendermint. -* Application-specified block retention may set a lower bound on disk space requirements for all nodes. +- Application-specified block retention may set a lower bound on disk space requirements for all nodes. ## References -- State sync ADR: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-053-state-sync-prototype.md +- State sync ADR: -- State sync issue: https://github.com/tendermint/tendermint/issues/828 +- State sync issue: -- Block pruning issue: https://github.com/tendermint/tendermint/issues/3652 +- Block pruning issue: diff --git a/rfc/002-nonzero-genesis.md b/rfc/002-nonzero-genesis.md index 3545d710c..8773ae74f 100644 --- a/rfc/002-nonzero-genesis.md +++ b/rfc/002-nonzero-genesis.md @@ -26,7 +26,7 @@ wallets, that assume a monotonically increasing height for a given blockchain. U it confusing that a given height can now refer to distinct states depending on the chain version. -An ideal solution would be to always retain block backwards compatibility in such a way that chain +An ideal solution would be to always retain block backwards compatibility in such a way that chain history is never lost on upgrades. However, this may require a significant amount of engineering work that is not viable for the planned Stargate release (Tendermint 0.34), and may prove too restrictive for future development. @@ -36,20 +36,20 @@ file would at least provide monotonically increasing heights. There was a propos last block header of the previous chain as well, but since the genesis file is not verified and hashed (only specific fields are) this would not be trustworthy. -External tooling will be required to map historical heights onto e.g. archive nodes that contain +External tooling will be required to map historical heights onto e.g. archive nodes that contain blocks from previous chain version. Tendermint will not include any such functionality. ## Proposal Tendermint will allow chains to start from an arbitrary initial height: -* A new field `initial_height` is added to the genesis file, defaulting to `1`. It can be set to any +- A new field `initial_height` is added to the genesis file, defaulting to `1`. It can be set to any non-negative integer, and `0` is considered equivalent to `1`. -* A new field `InitialHeight` is added to the ABCI `RequestInitChain` message, with the same value +- A new field `InitialHeight` is added to the ABCI `RequestInitChain` message, with the same value and semantics as the genesis field. -* A new field `InitialHeight` is added to the `state.State` struct, where `0` is considered invalid. +- A new field `InitialHeight` is added to the `state.State` struct, where `0` is considered invalid. Including the field here simplifies implementation, since the genesis value does not have to be propagated throughout the code base separately, but it is not strictly necessary. @@ -64,18 +64,18 @@ Accepted ### Positive -* Heights can be unique throughout the history of a "logical" chain, across hard fork upgrades. +- Heights can be unique throughout the history of a "logical" chain, across hard fork upgrades. ### Negative -* Upgrades still cause loss of block history. +- Upgrades still cause loss of block history. -* Integrators will have to map height ranges to specific archive nodes/networks to query history. +- Integrators will have to map height ranges to specific archive nodes/networks to query history. ### Neutral -* There is no explicit link to the last block of the previous chain. +- There is no explicit link to the last block of the previous chain. ## References -- [#2543: Allow genesis file to start from non-zero height w/ prev block header](https://github.com/tendermint/tendermint/issues/2543) \ No newline at end of file +- [#2543: Allow genesis file to start from non-zero height w/ prev block header](https://github.com/tendermint/tendermint/issues/2543) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index b297e2b99..5c704be27 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -65,34 +65,34 @@ Example: ```go abci.ResponseDeliverTx{ - // ... - Events: []abci.Event{ - { - Type: "validator.provisions", - Attributes: kv.Pairs{ - kv.Pair{Key: []byte("address"), Value: []byte("...")}, - kv.Pair{Key: []byte("amount"), Value: []byte("...")}, - kv.Pair{Key: []byte("balance"), Value: []byte("...")}, - }, - }, - { - Type: "validator.provisions", - Attributes: kv.Pairs{ - kv.Pair{Key: []byte("address"), Value: []byte("...")}, - kv.Pair{Key: []byte("amount"), Value: []byte("...")}, - kv.Pair{Key: []byte("balance"), Value: []byte("...")}, - }, - }, - { - Type: "validator.slashed", - Attributes: kv.Pairs{ - kv.Pair{Key: []byte("address"), Value: []byte("...")}, - kv.Pair{Key: []byte("amount"), Value: []byte("...")}, - kv.Pair{Key: []byte("reason"), Value: []byte("...")}, - }, - }, - // ... - }, + // ... + Events: []abci.Event{ + { + Type: "validator.provisions", + Attributes: kv.Pairs{ + kv.Pair{Key: []byte("address"), Value: []byte("...")}, + kv.Pair{Key: []byte("amount"), Value: []byte("...")}, + kv.Pair{Key: []byte("balance"), Value: []byte("...")}, + }, + }, + { + Type: "validator.provisions", + Attributes: kv.Pairs{ + kv.Pair{Key: []byte("address"), Value: []byte("...")}, + kv.Pair{Key: []byte("amount"), Value: []byte("...")}, + kv.Pair{Key: []byte("balance"), Value: []byte("...")}, + }, + }, + { + Type: "validator.slashed", + Attributes: kv.Pairs{ + kv.Pair{Key: []byte("address"), Value: []byte("...")}, + kv.Pair{Key: []byte("amount"), Value: []byte("...")}, + kv.Pair{Key: []byte("reason"), Value: []byte("...")}, + }, + }, + // ... + }, } ``` @@ -120,19 +120,19 @@ non-determinism must be fixed and the nodes restarted. Sources of non-determinism in applications may include: - Hardware failures - - Cosmic rays, overheating, etc. + - Cosmic rays, overheating, etc. - Node-dependent state - - Random numbers - - Time + - Random numbers + - Time - Underspecification - - Library version changes - - Race conditions - - Floating point numbers - - JSON serialization - - Iterating through hash-tables/maps/dictionaries + - 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) + - Filesystem + - Network calls (eg. some external REST API service) See [#56](https://github.com/tendermint/abci/issues/56) for original discussion. @@ -177,16 +177,16 @@ via light client. ### Echo - **Request**: - - `Message (string)`: A string to echo back + - `Message (string)`: A string to echo back - **Response**: - - `Message (string)`: The input string + - `Message (string)`: The input string - **Usage**: - - Echo a string to test an abci client/server implementation + - Echo a string to test an abci client/server implementation ### Flush - **Usage**: - - Signals that messages queued on the client should be flushed to + - 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, @@ -195,62 +195,62 @@ via light client. ### Info - **Request**: - - `Version (string)`: The Tendermint software semantic version - - `BlockVersion (uint64)`: The Tendermint Block Protocol version - - `P2PVersion (uint64)`: The Tendermint P2P Protocol version + - `Version (string)`: The Tendermint software semantic version + - `BlockVersion (uint64)`: The Tendermint Block Protocol version + - `P2PVersion (uint64)`: The Tendermint P2P Protocol version - **Response**: - - `Data (string)`: Some arbitrary information - - `Version (string)`: The application software semantic version - - `AppVersion (uint64)`: The application protocol version - - `LastBlockHeight (int64)`: Latest block for which the app has + - `Data (string)`: Some arbitrary information + - `Version (string)`: The application software semantic version + - `AppVersion (uint64)`: The application protocol version + - `LastBlockHeight (int64)`: Latest block for which the app has called Commit - - `LastBlockAppHash ([]byte)`: Latest result of Commit + - `LastBlockAppHash ([]byte)`: Latest result of Commit - **Usage**: - - Return information about the application state. - - Used to sync Tendermint with the application during a handshake + - Return information about the application state. + - Used to sync Tendermint with the application during a handshake that happens on startup. - - The returned `AppVersion` will be included in the Header of every block. - - Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to + - The returned `AppVersion` will be included in the Header of every block. + - Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to be updated during `Commit`, ensuring that `Commit` is never called twice for the same block height. ### SetOption - **Request**: - - `Key (string)`: Key to set - - `Value (string)`: Value to set for key + - `Key (string)`: Key to set + - `Value (string)`: Value to set for key - **Response**: - - `Code (uint32)`: Response code - - `Log (string)`: The output of the application's logger. May + - `Code (uint32)`: Response code + - `Log (string)`: The output of the application's logger. May be non-deterministic. - - `Info (string)`: Additional information. May + - `Info (string)`: Additional information. May be non-deterministic. - **Usage**: - - Set non-consensus critical application specific options. - - e.g. Key="min-fee", Value="100fermion" could set the minimum fee + - Set non-consensus critical application specific options. + - e.g. Key="min-fee", Value="100fermion" could set the minimum fee required for CheckTx (but not DeliverTx - that would be consensus critical). ### InitChain - **Request**: - - `Time (google.protobuf.Timestamp)`: Genesis time. - - `ChainID (string)`: ID of the blockchain. - - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters. - - `Validators ([]ValidatorUpdate)`: Initial genesis validators, sorted by voting power. - - `AppStateBytes ([]byte)`: Serialized initial application state. Amino-encoded JSON bytes. - - `InitialHeight (int64)`: Height of the initial block (typically `1`). + - `Time (google.protobuf.Timestamp)`: Genesis time. + - `ChainID (string)`: ID of the blockchain. + - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters. + - `Validators ([]ValidatorUpdate)`: Initial genesis validators, sorted by voting power. + - `AppStateBytes ([]byte)`: Serialized initial application state. Amino-encoded JSON bytes. + - `InitialHeight (int64)`: Height of the initial block (typically `1`). - **Response**: - - `ConsensusParams (ConsensusParams)`: Initial + - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters (optional). - - `Validators ([]ValidatorUpdate)`: Initial validator set (optional). - - `AppHash ([]byte)`: Initial application hash. + - `Validators ([]ValidatorUpdate)`: Initial validator set (optional). + - `AppHash ([]byte)`: Initial application hash. - **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 + - 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 + - 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). @@ -258,154 +258,154 @@ via light client. ### Query - **Request**: - - `Data ([]byte)`: Raw query bytes. Can be used with or in lieu + - `Data ([]byte)`: Raw query bytes. Can be used with or in lieu of Path. - - `Path (string)`: Path of request, like an HTTP GET path. Can be + - `Path (string)`: Path of request, like an HTTP GET path. Can be used with or in liue of Data. - - Apps MUST interpret '/store' as a query by key on the + - 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 + - Apps SHOULD allow queries over specific types like '/accounts/...' or '/votes/...' - - `Height (int64)`: The block height for which you want the query + - `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 - - `Prove (bool)`: Return Merkle proof with response if possible + - `Prove (bool)`: Return Merkle proof with response if possible - **Response**: - - `Code (uint32)`: Response code. - - `Log (string)`: The output of the application's logger. May + - `Code (uint32)`: Response code. + - `Log (string)`: The output of the application's logger. May be non-deterministic. - - `Info (string)`: Additional information. May + - `Info (string)`: Additional information. May be non-deterministic. - - `Index (int64)`: The index of the key in the tree. - - `Key ([]byte)`: The key of the matching data. - - `Value ([]byte)`: The value of the matching data. - - `Proof (Proof)`: Serialized proof for the value data, if requested, to be + - `Index (int64)`: The index of the key in the tree. + - `Key ([]byte)`: The key of the matching data. + - `Value ([]byte)`: The value of the matching data. + - `Proof (Proof)`: Serialized proof for the value data, if requested, to be verified against the `AppHash` for the given Height. - - `Height (int64)`: The block height from which data was derived. + - `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 - - `Codespace (string)`: Namespace for the `Code`. + - `Codespace (string)`: Namespace for the `Code`. - **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 + - 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**: - - `Hash ([]byte)`: The block's hash. This can be derived from the + - `Hash ([]byte)`: The block's hash. This can be derived from the block header. - - `Header (struct{})`: The block header. - - `LastCommitInfo (LastCommitInfo)`: Info about the last commit, including the + - `Header (struct{})`: The block header. + - `LastCommitInfo (LastCommitInfo)`: Info about the last commit, including the round, and the list of validators and which ones signed the last block. - - `ByzantineValidators ([]Evidence)`: List of evidence of + - `ByzantineValidators ([]Evidence)`: List of evidence of validators that acted maliciously. - **Response**: - - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing + - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing - **Usage**: - - Signals the beginning of a new block. Called prior to + - Signals the beginning of a new block. Called prior to any DeliverTxs. - - The header contains the height, timestamp, and more - it exactly matches the + - 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 + - The `LastCommitInfo` and `ByzantineValidators` can be used to determine rewards and punishments for the validators. NOTE validators here do not include pubkeys. ### CheckTx - **Request**: - - `Tx ([]byte)`: The request transaction bytes - - `Type (CheckTxType)`: What type of `CheckTx` request is this? At present, + - `Tx ([]byte)`: The request transaction bytes + - `Type (CheckTxType)`: What type of `CheckTx` request is this? At present, there are two possible values: `CheckTx_New` (the default, which says that a full check is required), and `CheckTx_Recheck` (when the mempool is initiating a normal recheck of a transaction). - **Response**: - - `Code (uint32)`: Response code - - `Data ([]byte)`: Result bytes, if any. - - `Log (string)`: The output of the application's logger. May + - `Code (uint32)`: Response code + - `Data ([]byte)`: Result bytes, if any. + - `Log (string)`: The output of the application's logger. May be non-deterministic. - - `Info (string)`: Additional information. May + - `Info (string)`: Additional information. May be non-deterministic. - - `GasWanted (int64)`: Amount of gas requested for transaction. - - `GasUsed (int64)`: Amount of gas consumed by transaction. - - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing + - `GasWanted (int64)`: Amount of gas requested for transaction. + - `GasUsed (int64)`: Amount of gas consumed by transaction. + - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing transactions (eg. by account). - - `Codespace (string)`: Namespace for the `Code`. + - `Codespace (string)`: Namespace for the `Code`. - **Usage**: - - Technically optional - not involved in processing blocks. - - Guardian of the mempool: every node runs CheckTx before letting a + - 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 need not execute the transaction in full, but rather a light-weight + - The transaction may come from an external user or another node + - CheckTx need not execute the transaction in full, but rather a light-weight yet stateful validation, like checking signatures and account balances, but not running code in a virtual machine. - - Transactions where `ResponseCheckTx.Code != 0` will be rejected - they will not be broadcast to + - 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 + - Tendermint attributes no other value to the response code ### DeliverTx - **Request**: - - `Tx ([]byte)`: The request transaction bytes. + - `Tx ([]byte)`: The request transaction bytes. - **Response**: - - `Code (uint32)`: Response code. - - `Data ([]byte)`: Result bytes, if any. - - `Log (string)`: The output of the application's logger. May + - `Code (uint32)`: Response code. + - `Data ([]byte)`: Result bytes, if any. + - `Log (string)`: The output of the application's logger. May be non-deterministic. - - `Info (string)`: Additional information. May + - `Info (string)`: Additional information. May be non-deterministic. - - `GasWanted (int64)`: Amount of gas requested for transaction. - - `GasUsed (int64)`: Amount of gas consumed by transaction. - - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing + - `GasWanted (int64)`: Amount of gas requested for transaction. + - `GasUsed (int64)`: Amount of gas consumed by transaction. + - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing transactions (eg. by account). - - `Codespace (string)`: Namespace for the `Code`. + - `Codespace (string)`: Namespace for the `Code`. - **Usage**: - - The workhorse of the application - non-optional. - - Execute the transaction in full. - - `ResponseDeliverTx.Code == 0` only if the transaction is fully valid. + - The workhorse of the application - non-optional. + - Execute the transaction in full. + - `ResponseDeliverTx.Code == 0` only if the transaction is fully valid. ### EndBlock - **Request**: - - `Height (int64)`: Height of the block just executed. + - `Height (int64)`: Height of the block just executed. - **Response**: - - `ValidatorUpdates ([]ValidatorUpdate)`: Changes to validator set (set + - `ValidatorUpdates ([]ValidatorUpdate)`: Changes to validator set (set voting power to 0 to remove). - - `ConsensusParamUpdates (ConsensusParams)`: Changes to + - `ConsensusParamUpdates (ConsensusParams)`: Changes to consensus-critical time, size, and other parameters. - - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing + - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing - **Usage**: - - Signals the end of a block. - - Called after all transactions, prior to each Commit. - - Validator updates returned by block `H` impact blocks `H+1`, `H+2`, and + - Signals the end of a block. + - Called after all transactions, prior to each Commit. + - Validator updates returned by block `H` impact blocks `H+1`, `H+2`, and `H+3`, but only effects changes on the validator set of `H+2`: - - `H+1`: NextValidatorsHash - - `H+2`: ValidatorsHash (and thus the validator set) - - `H+3`: LastCommitInfo (ie. the last validator set) - - Consensus params returned for block `H` apply for block `H+1` + - `H+1`: NextValidatorsHash + - `H+2`: ValidatorsHash (and thus the validator set) + - `H+3`: LastCommitInfo (ie. the last validator set) + - Consensus params returned for block `H` apply for block `H+1` ### Commit - **Response**: - - `Data ([]byte)`: The Merkle root hash of the application state - - `RetainHeight (int64)`: Blocks below this height may be removed. Defaults + - `Data ([]byte)`: The Merkle root hash of the application state + - `RetainHeight (int64)`: Blocks below this height may be removed. Defaults to `0` (retain all). - **Usage**: - - 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 + - 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 + - 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 + - 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 @@ -414,256 +414,254 @@ via light client. ### ListSnapshots - **Response**: - - `Snapshots ([]Snapshot)`: List of local state snapshots. + - `Snapshots ([]Snapshot)`: List of local state snapshots. - **Usage**: - - Used during state sync to discover available snapshots on peers. - - See `Snapshot` data type for details. + - Used during state sync to discover available snapshots on peers. + - See `Snapshot` data type for details. ### LoadSnapshotChunk - **Request**: - - `Height (uint64)`: The height of the snapshot the chunks belongs to. - - `Format (uint32)`: The application-specific format of the snapshot the chunk belongs to. - - `Chunk (uint32)`: The chunk index, starting from `0` for the initial chunk. + - `Height (uint64)`: The height of the snapshot the chunks belongs to. + - `Format (uint32)`: The application-specific format of the snapshot the chunk belongs to. + - `Chunk (uint32)`: The chunk index, starting from `0` for the initial chunk. - **Response**: - - `Chunk ([]byte)`: The binary chunk contents, in an arbitray format. Chunk messages cannot be + - `Chunk ([]byte)`: 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. - **Usage**: - - Used during state sync to retrieve snapshot chunks from peers. + - Used during state sync to retrieve snapshot chunks from peers. ### OfferSnapshot - **Request**: - - `Snapshot (Snapshot)`: The snapshot offered for restoration. - - `AppHash ([]byte)`: The light client-verified app hash for this height, from the blockchain. + - `Snapshot (Snapshot)`: The snapshot offered for restoration. + - `AppHash ([]byte)`: The light client-verified app hash for this height, from the blockchain. - **Response**: - - `Result (Result)`: The result of the snapshot offer. - - `ACCEPT`: Snapshot is accepted, start applying chunks. - - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. - - `REJECT`: Reject this specific snapshot, try others. - - `REJECT_FORMAT`: Reject all snapshots with this `format`, try others. - - `REJECT_SENDERS`: Reject all snapshots from all senders of this snapshot, try others. + - `Result (Result)`: The result of the snapshot offer. + - `ACCEPT`: Snapshot is accepted, start applying chunks. + - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. + - `REJECT`: Reject this specific snapshot, try others. + - `REJECT_FORMAT`: Reject all snapshots with this `format`, try others. + - `REJECT_SENDERS`: 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 + - `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 + - 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](apps.md#state-sync). + - For more information, see the `Snapshot` data type or the [state sync section](apps.md#state-sync). ### ApplySnapshotChunk - **Request**: - - `Index (uint32)`: The chunk index, starting from `0`. Tendermint applies chunks sequentially. - - `Chunk ([]byte)`: The binary chunk contents, as returned by `LoadSnapshotChunk`. - - `Sender (string)`: The P2P ID of the node who sent this chunk. + - `Index (uint32)`: The chunk index, starting from `0`. Tendermint applies chunks sequentially. + - `Chunk ([]byte)`: The binary chunk contents, as returned by `LoadSnapshotChunk`. + - `Sender (string)`: The P2P ID of the node who sent this chunk. - **Response**: - - `Result (Result)`: The result of applying this chunk. - - `ACCEPT`: The chunk was accepted. - - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. - - `RETRY`: Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate. - - `RETRY_SNAPSHOT`: Restart this snapshot from `OfferSnapshot`, reusing chunks unless + - `Result (Result)`: The result of applying this chunk. + - `ACCEPT`: The chunk was accepted. + - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. + - `RETRY`: Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate. + - `RETRY_SNAPSHOT`: Restart this snapshot from `OfferSnapshot`, reusing chunks unless instructed otherwise. - - `REJECT_SNAPSHOT`: Reject this snapshot, try a different one. - - `RefetchChunks ([]uint32)`: Refetch and reapply the given chunks, regardless of `Result`. Only + - `REJECT_SNAPSHOT`: Reject this snapshot, try a different one. + - `RefetchChunks ([]uint32)`: Refetch and reapply the given chunks, regardless of `Result`. Only the listed chunks will be refetched, and reapplied in sequential order. - - `RejectSenders ([]string)`: Reject the given P2P senders, regardless of `Result`. Any chunks + - `RejectSenders ([]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. - **Usage**: - - The application can choose to refetch chunks and/or ban P2P peers as appropriate. Tendermint + - 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 + - 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 + - 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 + - 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 ### Header - **Fields**: - - `Version (Version)`: Version of the blockchain and the application - - `ChainID (string)`: ID of the blockchain - - `Height (int64)`: Height of the block in the chain - - `Time (google.protobuf.Timestamp)`: Time of the previous block. + - `Version (Version)`: Version of the blockchain and the application + - `ChainID (string)`: ID of the blockchain + - `Height (int64)`: Height of the block in the chain + - `Time (google.protobuf.Timestamp)`: Time of the previous block. For most blocks it's the weighted median of the timestamps of the valid votes in the block.LastCommit, except for the initial height where it's the genesis time. - - `LastBlockID (BlockID)`: Hash of the previous (parent) block - - `LastCommitHash ([]byte)`: Hash of the previous block's commit - - `ValidatorsHash ([]byte)`: Hash of the validator set for this block - - `NextValidatorsHash ([]byte)`: Hash of the validator set for the next block - - `ConsensusHash ([]byte)`: Hash of the consensus parameters for this block - - `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the + - `LastBlockID (BlockID)`: Hash of the previous (parent) block + - `LastCommitHash ([]byte)`: Hash of the previous block's commit + - `ValidatorsHash ([]byte)`: Hash of the validator set for this block + - `NextValidatorsHash ([]byte)`: Hash of the validator set for the next block + - `ConsensusHash ([]byte)`: Hash of the consensus parameters for this block + - `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the Merkle root of the application state after executing the previous block's transactions - - `LastResultsHash ([]byte)`: Root hash of all results from the txs from the previous block. - - `EvidenceHash ([]byte)`: Hash of the evidence included in this block - - `ProposerAddress ([]byte)`: Original proposer for the block + - `LastResultsHash ([]byte)`: Root hash of all results from the txs from the previous block. + - `EvidenceHash ([]byte)`: Hash of the evidence included in this block + - `ProposerAddress ([]byte)`: Original proposer for the block - **Usage**: - - Provided in RequestBeginBlock - - Provides important context about the current state of the blockchain - + - Provided in RequestBeginBlock + - Provides important context about the current state of the blockchain - especially height and time. - - Provides the proposer of the current block, for use in proposer-based + - Provides the proposer of the current block, for use in proposer-based reward mechanisms. - - `LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`, `Info`, `Codespace` and `Events` fields are ignored). + - `LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`, `Info`, `Codespace` and `Events` fields are ignored). ### Version - **Fields**: - - `Block (uint64)`: Protocol version of the blockchain data structures. - - `App (uint64)`: Protocol version of the application. + - `Block (uint64)`: Protocol version of the blockchain data structures. + - `App (uint64)`: Protocol version of the application. - **Usage**: - - Block version should be static in the life of a blockchain. - - App version may be updated over time by the application. + - Block version should be static in the life of a blockchain. + - App version may be updated over time by the application. ### Validator - **Fields**: - - `Address ([]byte)`: Address of the validator (the first 20 bytes of SHA256(public key)) - - `Power (int64)`: Voting power of the validator + - `Address ([]byte)`: Address of the validator (the first 20 bytes of SHA256(public key)) + - `Power (int64)`: Voting power of the validator - **Usage**: - - Validator identified by address - - Used in RequestBeginBlock as part of VoteInfo - - Does not include PubKey to avoid sending potentially large quantum pubkeys + - 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**: - - `PubKey (PubKey)`: Public key of the validator - - `Power (int64)`: Voting power of the validator + - `PubKey (PubKey)`: Public key of the validator + - `Power (int64)`: Voting power of the validator - **Usage**: - - Validator identified by PubKey - - Used to tell Tendermint to update the validator set + - Validator identified by PubKey + - Used to tell Tendermint to update the validator set ### VoteInfo - **Fields**: - - `Validator (Validator)`: A validator - - `SignedLastBlock (bool)`: Indicates whether or not the validator signed + - `Validator (Validator)`: A validator + - `SignedLastBlock (bool)`: Indicates whether or not the validator signed the last block - **Usage**: - - Indicates whether a validator signed the last block, allowing for rewards + - Indicates whether a validator signed the last block, allowing for rewards based on validator availability ### PubKey - **Fields**: - - `Type (string)`: Type of the public key. A simple string like `"ed25519"`. + - `Type (string)`: Type of the public key. A simple string like `"ed25519"`. In the future, may indicate a serialization algorithm to parse the `Data`, for instance `"amino"`. - - `Data ([]byte)`: Public key data. For a simple public key, it's just the + - `Data ([]byte)`: Public key data. For a simple public key, it's just the raw bytes. If the `Type` indicates an encoding algorithm, this is the encoded public key. - **Usage**: - - A generic and extensible typed public key + - A generic and extensible typed public key ### Evidence - **Fields**: - - `Type (string)`: Type of the evidence. A hierarchical path like + - `Type (string)`: Type of the evidence. A hierarchical path like "duplicate/vote". - - `Validator (Validator`: The offending validator - - `Height (int64)`: Height when the offense occured - - `Time (google.protobuf.Timestamp)`: Time of the block that was committed at the height that the offense occured - - `TotalVotingPower (int64)`: Total voting power of the validator set at + - `Validator (Validator`: The offending validator + - `Height (int64)`: Height when the offense occured + - `Time (google.protobuf.Timestamp)`: Time of the block that was committed at the height that the offense occured + - `TotalVotingPower (int64)`: Total voting power of the validator set at height `Height` ### LastCommitInfo - **Fields**: - - `Round (int32)`: Commit round. - - `Votes ([]VoteInfo)`: List of validators addresses in the last validator set + - `Round (int32)`: Commit round. + - `Votes ([]VoteInfo)`: List of validators addresses in the last validator set with their voting power and whether or not they signed a vote. ### ConsensusParams - **Fields**: - - `Block (BlockParams)`: Parameters limiting the size of a block and time between consecutive blocks. - - `Evidence (EvidenceParams)`: Parameters limiting the validity of + - `Block (BlockParams)`: Parameters limiting the size of a block and time between consecutive blocks. + - `Evidence (EvidenceParams)`: Parameters limiting the validity of evidence of byzantine behaviour. - - `Validator (ValidatorParams)`: Parameters limiting the types of pubkeys validators can use. - - `Version (VersionParams)`: The ABCI application version. + - `Validator (ValidatorParams)`: Parameters limiting the types of pubkeys validators can use. + - `Version (VersionParams)`: The ABCI application version. ### BlockParams - **Fields**: - - `MaxBytes (int64)`: Max size of a block, in bytes. - - `MaxGas (int64)`: Max sum of `GasWanted` in a proposed block. - - NOTE: blocks that violate this may be committed if there are Byzantine proposers. + - `MaxBytes (int64)`: Max size of a block, in bytes. + - `MaxGas (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! ### EvidenceParams - **Fields**: - - `MaxAgeNumBlocks (int64)`: Max age of evidence, in blocks. - - `MaxAgeDuration (time.Duration)`: Max age of evidence, in time. + - `MaxAgeNumBlocks (int64)`: Max age of evidence, in blocks. + - `MaxAgeDuration (time.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). - - Evidence older than `MaxAgeNumBlocks` && `MaxAgeDuration` is considered + - Evidence older than `MaxAgeNumBlocks` && `MaxAgeDuration` is considered stale and ignored. - - In Cosmos-SDK based blockchains, `MaxAgeDuration` is usually equal to the + - In Cosmos-SDK based blockchains, `MaxAgeDuration` is usually equal to the unbonding period. `MaxAgeNumBlocks` is calculated by dividing the unboding period by the average block time (e.g. 2 weeks / 6s per block = 2d8h). - - `MaxNum (uint32)`: The maximum number of evidence that can be committed to a single block - - `ProofTrialPeriod (int64)`: The duration in terms of blocks that an indicted node has to - provide proof of correctly executing a lock change in the event of amnesia evidence. + - `MaxNum (uint32)`: The maximum number of evidence that can be committed to a single block + - `ProofTrialPeriod (int64)`: The duration in terms of blocks that an indicted node has to + provide proof of correctly executing a lock change in the event of amnesia evidence. ### ValidatorParams - **Fields**: - - `PubKeyTypes ([]string)`: List of accepted pubkey types. Uses same + - `PubKeyTypes ([]string)`: List of accepted pubkey types. Uses same naming as `PubKey.Type`. ### VersionParams - **Fields**: - - `AppVersion (uint64)`: The ABCI application version. + - `AppVersion (uint64)`: The ABCI application version. ### Proof - **Fields**: - - `Ops ([]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 + - `Ops ([]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. ### ProofOp - **Fields**: - - `Type (string)`: Type of Merkle proof and how it's encoded. - - `Key ([]byte)`: Key in the Merkle tree that this proof is for. - - `Data ([]byte)`: Encoded Merkle proof for the key. + - `Type (string)`: Type of Merkle proof and how it's encoded. + - `Key ([]byte)`: Key in the Merkle tree that this proof is for. + - `Data ([]byte)`: Encoded Merkle proof for the key. ### Snapshot - **Fields**: - - `Height (uint64)`: The height at which the snapshot was taken (after commit). - - `Format (uint32)`: An application-specific snapshot format, allowing applications to version + - `Height (uint64)`: The height at which the snapshot was taken (after commit). + - `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. - - `Chunks (uint32)`: The number of chunks in the snapshot. Must be at least 1 (even if empty). - - `Hash (bytes)`: An arbitrary snapshot hash. Must be equal only for identical snapshots across + - `Chunks (uint32)`: The number of chunks in the snapshot. Must be at least 1 (even if empty). + - `Hash (bytes)`: An arbitrary snapshot hash. Must be equal only for identical snapshots across nodes. Tendermint does not interpret the hash, it only compares them. - - `Metadata (bytes)`: Arbitrary application metadata, for example chunk hashes or other + - `Metadata (bytes)`: Arbitrary application metadata, for example chunk hashes or other verification data. - **Usage**: - - Used for state sync snapshots, see [separate section](apps.md#state-sync) for details. - - A snapshot is considered identical across nodes only if _all_ fields are equal (including + - Used for state sync snapshots, see [separate section](apps.md#state-sync) 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. + - 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 index e4208d402..5a6567c88 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -203,7 +203,7 @@ blockchain. Updates to the Tendermint validator set can be made by returning `ValidatorUpdate` objects in the `ResponseEndBlock`: -``` +```proto message ValidatorUpdate { PubKey pub_key int64 power @@ -226,9 +226,9 @@ following rules: - 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 + - 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 + - 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`. @@ -293,14 +293,14 @@ Must have `MaxAgeNumBlocks > 0`. 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 +The product of this and the `MaxEvidenceBytes` must not exceed the size of a block minus it's overhead ( ~ `MaxBytes`). The amount must be a positive number. ### EvidenceParams.ProofTrialPeriod -This is the duration in terms of blocks that an indicted validator has to prove a +This is the duration in terms of blocks that an indicted validator has to prove a correct lock change in the event of amnesia evidence when a validator voted more than once across different rounds. @@ -381,7 +381,7 @@ Some applications (eg. Ethereum, Cosmos-SDK) have multiple "levels" of Merkle tr 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: -``` +```proto message Proof { repeated ProofOp ops } @@ -437,7 +437,7 @@ failed during the Commit of block H, then `last_block_height = H-1` and 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 @@ -497,8 +497,8 @@ State sync is an alternative mechanism for bootstrapping a new node, where it fe 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 +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). @@ -509,20 +509,20 @@ Applications that want to support state syncing must take state snapshots at reg 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 (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 +- `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 +- `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 +- `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 +- `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 @@ -533,15 +533,15 @@ application via the ABCI `ListSnapshots` method to discover available snapshots, snapshot chunks via `LoadSnapshotChunk`. The application is free to choose how to implement this and which formats to use, but should provide the following guarantees: -* **Consistent:** A snapshot should be taken at a single isolated height, unaffected by - concurrent writes. This can e.g. be accomplished by using a data store that supports ACID +- **Consistent:** A snapshot should be taken at a single isolated height, unaffected by + concurrent writes. This can e.g. be accomplished by using a data store that supports ACID transactions with snapshot isolation. -* **Asynchronous:** Taking a snapshot can be time-consuming, so it should not halt chain progress, +- **Asynchronous:** Taking a snapshot can be time-consuming, so it should not halt chain progress, for example by running in a separate thread. -* **Deterministic:** A snapshot taken at the same height in the same format should be identical - (at the byte level) across nodes, including all metadata. This ensures good availability of +- **Deterministic:** A snapshot taken at the same height in the same format should 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), @@ -583,7 +583,7 @@ 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 +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. @@ -603,7 +603,7 @@ 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 +`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. @@ -623,8 +623,8 @@ P2P configuration options to whitelist a set of trusted peers that can provide v #### Transition to Consensus Once the snapshot has 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` +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 node is bootstrapped with this information and the restored state machine, it transitions diff --git a/spec/consensus/bft-time.md b/spec/consensus/bft-time.md index 8e02f6abe..b2571f9cb 100644 --- a/spec/consensus/bft-time.md +++ b/spec/consensus/bft-time.md @@ -1,54 +1,53 @@ # 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. +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 +- 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., +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 +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 +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: +- 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 +- 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: +- 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 + config.BlockTimeIota, time.Now())`, where `time.Now()` + `vote.Time = max(rs.LockedBlock.Timestamp + config.BlockTimeIota, time.Now())`, where `time.Now()` denotes local Unix time in milliseconds, and `config.BlockTimeIota` is a configuration parameter that corresponds to the minimum timestamp increment of the next block. - - - else if `rs.Proposal` is defined then + + - else if `rs.Proposal` is defined then `vote.Time = max(rs.Proposal.Timestamp + config.BlockTimeIota, 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. - + - 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/README.md b/spec/consensus/consensus-paper/README.md index e67db6cf0..33e395806 100644 --- a/spec/consensus/consensus-paper/README.md +++ b/spec/consensus/consensus-paper/README.md @@ -7,12 +7,12 @@ consensus protocol. 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 +Popular IDE for Latex-based projects is TexStudio. It can be downloaded [here](https://www.texstudio.org/). -## How to build project +## How to build project -In order to compile the latex files (and write bibliography), execute +In order to compile the latex files (and write bibliography), execute `$ pdflatex paper`
`$ bibtex paper`
@@ -22,4 +22,3 @@ In order to compile the latex files (and write bibliography), execute The generated file is paper.pdf. You can open it with `$ open paper.pdf` - diff --git a/spec/consensus/consensus.md b/spec/consensus/consensus.md index a409c86d4..755587cb8 100644 --- a/spec/consensus/consensus.md +++ b/spec/consensus/consensus.md @@ -32,7 +32,7 @@ determine the next block. Each round is composed of three _steps_ In the optimal scenario, the order of steps is: -``` +```md NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->... ``` @@ -59,7 +59,7 @@ parameters over each successive round. ## State Machine Diagram -``` +```md +-------------------------------------+ v |(Wait til `CommmitTime+timeoutCommit`) +-----------+ +-----+-----+ diff --git a/spec/consensus/creating-proposal.md b/spec/consensus/creating-proposal.md index 831d16571..49798a41b 100644 --- a/spec/consensus/creating-proposal.md +++ b/spec/consensus/creating-proposal.md @@ -16,11 +16,11 @@ we account for amino overhead for each transaction. ```go func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 { - return maxBytes - - MaxAminoOverheadForBlock - - MaxHeaderBytes - - int64(valsCount)*MaxVoteBytes - - int64(evidenceCount)*MaxEvidenceBytes + return maxBytes - + MaxAminoOverheadForBlock - + MaxHeaderBytes - + int64(valsCount)*MaxVoteBytes - + int64(evidenceCount)*MaxEvidenceBytes } ``` @@ -33,10 +33,10 @@ maximum evidence size (1/10th of the maximum block size). ```go func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 { - return maxBytes - - MaxAminoOverheadForBlock - - MaxHeaderBytes - - int64(valsCount)*MaxVoteBytes - - MaxEvidenceBytesPerBlock(maxBytes) + return maxBytes - + MaxAminoOverheadForBlock - + MaxHeaderBytes - + int64(valsCount)*MaxVoteBytes - + MaxEvidenceBytesPerBlock(maxBytes) } ``` diff --git a/spec/consensus/light-client/README.md b/spec/consensus/light-client/README.md index fe42e4972..78b4a2f2e 100644 --- a/spec/consensus/light-client/README.md +++ b/spec/consensus/light-client/README.md @@ -1,7 +1,7 @@ # Tendermint Light Client Protocol NOTE: This specification is under heavy development and is not yet complete nor -accurate. +accurate. ## Contents @@ -51,16 +51,15 @@ full nodes. ### Synchrony -Light clients are fundamentally synchronous protocols, +Light clients are fundamentally synchronous protocols, where security is restricted by the interval during which a validator can be punished for Byzantine behaviour. We assume here that such intervals have fixed and known minimal duration referred to commonly as a blockchain's Unbonding Period. -A secure light client must guarantee that all three components - -core verification, fork detection, and fork accountability - +A secure light client must guarantee that all three components - +core verification, fork detection, and fork accountability - each with their own synchrony assumptions and fault model, can execute sequentially and to completion within the given Unbonding Period. TODO: define all the synchrony parameters used in the protocol and their relation to the Unbonding Period. - diff --git a/spec/consensus/light-client/accountability.md b/spec/consensus/light-client/accountability.md index 9731f3aba..0374b9c3c 100644 --- a/spec/consensus/light-client/accountability.md +++ b/spec/consensus/light-client/accountability.md @@ -1,8 +1,9 @@ -# Fork accountability +# 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, @@ -13,7 +14,6 @@ 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 more than 1/3 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. @@ -24,7 +24,6 @@ We say that a fork is a case in which there are two commits for different blocks *Remark.* In the case more than 1/3 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: @@ -37,13 +36,11 @@ Forks are the result of faulty validators deviating from the protocol. In princi *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. +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 at most 1/3 of the voting power is faulty, this misbehavior is detectable but will not lead to a safety violation. Thus, unless they have more than 1/3 (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. - +Independently of a fork happening, punishing this behavior might be important to prevent forks altogether. This should keep attackers from misbehaving: if at most 1/3 of the voting power is faulty, this misbehavior is detectable but will not lead to a safety violation. Thus, unless they have more than 1/3 (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 @@ -53,7 +50,6 @@ As in this case we have two different blocks (both having the same right/no righ * 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 @@ -64,25 +60,17 @@ There are several scenarios in which forks might happen. The first is double sig * 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. @@ -96,10 +84,9 @@ Similarly, without actually interfering with the main chain, we can have the fol 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 +* 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. @@ -110,15 +97,9 @@ F3 is similar to F1, except that no two correct validators decide on different b 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). - +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 | |:------:|:------:|:------:|:------:| @@ -128,16 +109,11 @@ F4 and F5 are *off-chain*, that is, these attacks cannot be used to corrupt the | 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 @@ -148,6 +124,7 @@ round of some height. This attack can be executed on both full nodes and light c #### 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 @@ -162,14 +139,15 @@ Execution: * 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 + * 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. @@ -180,11 +158,12 @@ Consequences: #### 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. @@ -202,8 +181,6 @@ In order to detect such (equivocation-based attack), the light client would need ### 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 @@ -215,7 +192,7 @@ 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 +* 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'*. @@ -224,7 +201,7 @@ Execution: *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: https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit?usp=sharing. +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 more than 1/3 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. @@ -249,7 +226,7 @@ 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 https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit?usp=sharing as it only assumes at most 2/3 of faulty validators. +* 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. @@ -257,9 +234,10 @@ Only in case they signed something which conflicts with the application this can 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: +#### Scenario 5 Validators: + * C1 - a set of correct validators with 1/3 of the voting power * C2 - a set of correct validators with 1/3 of the voting power * C1 and C2 are disjoint @@ -267,7 +245,6 @@ Validators: * 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, @@ -278,7 +255,6 @@ Execution: * 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. @@ -289,16 +265,17 @@ Consequences: 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: +#### 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) + * 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'. @@ -314,7 +291,7 @@ Consequences: 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. +punishing the 1/3+ lunatic cabal, that is the root cause of the attack. ### Lunatic validator diff --git a/spec/consensus/light-client/verification.md b/spec/consensus/light-client/verification.md index 55e87cd7c..7d13d8871 100644 --- a/spec/consensus/light-client/verification.md +++ b/spec/consensus/light-client/verification.md @@ -68,7 +68,6 @@ get trust for `hp`, and `hp` can be used to get trust for `snh`. If this is the 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 @@ -110,6 +109,7 @@ In the following, only the details of the data structures needed for this specif 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) @@ -119,6 +119,7 @@ exposes the following functions over Tendermint RPC: ``` 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 @@ -137,8 +138,6 @@ Furthermore, we assume the following auxiliary functions: func hash(v2 ValidatorSet) []byte ``` -### Functions - 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. @@ -399,15 +398,13 @@ func fatalError(err) bool { } ``` - - ### 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 +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 +*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 @@ -449,31 +446,30 @@ func VerifyHeaderBackwards(trustedHeader Header, } ``` - *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, + - 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` +- `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`. +- 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 @@ -487,7 +483,6 @@ Formally, 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), @@ -532,14 +527,15 @@ is correct, but we only trust the fact that less than `1/3` of them are faulty ( *`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. @@ -550,10 +546,10 @@ Light Client Completeness: 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)* +- `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`). @@ -568,12 +564,8 @@ This is only ensured if upon `Commit(pivot)` the light client is always provided 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 + +- 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. - - - - - +- We may set a timeout how long `VerifyBisection` may take. diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md index 8bf4e68bd..1eae3738e 100644 --- a/spec/consensus/readme.md +++ b/spec/consensus/readme.md @@ -4,7 +4,7 @@ cards: true # Consensus -Specification of the Tendermint consensus protocol. +Specification of the Tendermint consensus protocol. ## Contents diff --git a/spec/consensus/signing.md b/spec/consensus/signing.md index 574a38305..907a5a01a 100644 --- a/spec/consensus/signing.md +++ b/spec/consensus/signing.md @@ -14,12 +14,12 @@ being signed. It is defined in Go as follows: type SignedMsgType byte const ( - // Votes - PrevoteType SignedMsgType = 0x01 - PrecommitType SignedMsgType = 0x02 + // Votes + PrevoteType SignedMsgType = 0x01 + PrecommitType SignedMsgType = 0x02 - // Proposals - ProposalType SignedMsgType = 0x20 + // Proposals + ProposalType SignedMsgType = 0x20 ) ``` @@ -48,13 +48,13 @@ BlockID is the structure used to represent the block: ```go type BlockID struct { - Hash []byte - PartsHeader PartSetHeader + Hash []byte + PartsHeader PartSetHeader } type PartSetHeader struct { - Hash []byte - Total int + Hash []byte + Total int } ``` @@ -64,7 +64,7 @@ We introduce two methods, `BlockID.IsZero()` and `BlockID.IsComplete()` for thes `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 @@ -73,7 +73,7 @@ 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 @@ -85,13 +85,13 @@ 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 + 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 } ``` @@ -115,18 +115,18 @@ 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 + 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 @@ -157,9 +157,9 @@ Assume the signer keeps the following state, `s`: ```go type LastSigned struct { - Height int64 - Round int64 - Type SignedMsgType // byte + Height int64 + Round int64 + Type SignedMsgType // byte } ``` @@ -175,7 +175,7 @@ s.Type = m.Type 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 ``` @@ -187,7 +187,7 @@ Once a proposal or vote has been signed for a given height and round, a proposal 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 diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index e95a39f21..1b7a9033f 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -36,29 +36,29 @@ the data in the current block, the previous block, and the results returned by t ```go type Header struct { - // basic block info - Version Version - ChainID string - Height int64 - Time Time + // basic block info + Version Version + ChainID string + Height int64 + Time Time - // prev block info - LastBlockID BlockID + // prev block info + LastBlockID BlockID - // hashes of block data - LastCommitHash []byte // commit from validators from the last block - DataHash []byte // MerkleRoot of transaction hashes + // hashes of block data + LastCommitHash []byte // commit from validators from the last block + DataHash []byte // MerkleRoot of transaction hashes - // hashes from the app output from the prev block - ValidatorsHash []byte // validators for the current block - NextValidatorsHash []byte // validators for the next block - ConsensusHash []byte // consensus params for current block - AppHash []byte // state after txs from the previous block - LastResultsHash []byte // root hash of all results from the txs from the previous block + // hashes from the app output from the prev block + ValidatorsHash []byte // validators for the current block + NextValidatorsHash []byte // validators for the next block + ConsensusHash []byte // consensus params for current block + AppHash []byte // state after txs from the previous block + LastResultsHash []byte // root hash of all results from the txs from the previous block - // consensus info - EvidenceHash []byte // evidence included in the block - ProposerAddress []byte // original proposer of the block + // consensus info + EvidenceHash []byte // evidence included in the block + ProposerAddress []byte // original proposer of the block ``` Further details on each of these fields is described below. @@ -67,8 +67,8 @@ Further details on each of these fields is described below. ```go type Version struct { - Block uint64 - App uint64 + Block uint64 + App uint64 } ``` @@ -111,7 +111,7 @@ format, which uses two integers, one for Seconds and for Nanoseconds. Data is just a wrapper for a list of transactions, where transactions are arbitrary byte arrays: -``` +```go type Data struct { Txs [][]byte } @@ -124,10 +124,10 @@ validator. It also contains the relevant BlockID, height and round: ```go type Commit struct { - Height int64 - Round int - BlockID BlockID - Signatures []CommitSig + Height int64 + Round int + BlockID BlockID + Signatures []CommitSig } ``` @@ -141,19 +141,19 @@ to reconstruct the vote set given the validator set. type BlockIDFlag byte const ( - // BlockIDFlagAbsent - no vote was received from a validator. - BlockIDFlagAbsent BlockIDFlag = 0x01 - // BlockIDFlagCommit - voted for the Commit.BlockID. - BlockIDFlagCommit = 0x02 - // BlockIDFlagNil - voted for nil. - BlockIDFlagNil = 0x03 + // BlockIDFlagAbsent - no vote was received from a validator. + BlockIDFlagAbsent BlockIDFlag = 0x01 + // BlockIDFlagCommit - voted for the Commit.BlockID. + BlockIDFlagCommit = 0x02 + // BlockIDFlagNil - voted for nil. + BlockIDFlagNil = 0x03 ) type CommitSig struct { - BlockIDFlag BlockIDFlag - ValidatorAddress Address - Timestamp time.Time - Signature []byte + BlockIDFlag BlockIDFlag + ValidatorAddress Address + Timestamp time.Time + Signature []byte } ``` @@ -168,14 +168,14 @@ The vote includes information about the validator signing it. ```go type Vote struct { - Type byte - Height int64 - Round int - BlockID BlockID - Timestamp Time - ValidatorAddress []byte - ValidatorIndex int - Signature []byte + Type byte + Height int64 + Round int + BlockID BlockID + Timestamp Time + ValidatorAddress []byte + ValidatorIndex int + Signature []byte } ``` @@ -193,7 +193,7 @@ See the [signature spec](./encoding.md#key-types) for more. EvidenceData is a simple wrapper for a list of evidence: -``` +```go type EvidenceData struct { Evidence []Evidence } @@ -201,40 +201,40 @@ type EvidenceData struct { ## Evidence -Evidence in Tendermint is used to indicate breaches in the consensus by a validator. +Evidence in Tendermint is used to indicate breaches in the consensus by a validator. It is implemented as the following interface. ```go type Evidence interface { - Height() int64 // height of the equivocation - Time() time.Time // time of the equivocation - Address() []byte // address of the equivocating validator - Bytes() []byte // bytes which comprise the evidence - Hash() []byte // hash of the evidence - Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence - Equal(Evidence) bool // check equality of evidence + Height() int64 // height of the equivocation + Time() time.Time // time of the equivocation + Address() []byte // address of the equivocating validator + Bytes() []byte // bytes which comprise the evidence + Hash() []byte // hash of the evidence + Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence + Equal(Evidence) bool // check equality of evidence - ValidateBasic() error - String() string + ValidateBasic() error + String() string } ``` -All evidence can be encoded and decoded to and from Protobuf with the `EvidenceToProto()` -and `EvidenceFromProto()` functions. The [Fork Accountability](../consensus/light-client/accountability.md) +All evidence can be encoded and decoded to and from Protobuf with the `EvidenceToProto()` +and `EvidenceFromProto()` functions. The [Fork Accountability](../consensus/light-client/accountability.md) document provides a good overview for the types of evidence and how they occur. For evidence to be committed onchain, it must adhere to the validation rules of each evidence and must not be expired. The expiration age, measured in both block height and time is set in `EvidenceParams`. Each evidence uses -the timestamp of the block that the evidence occured at to indicate the age of the evidence. +the timestamp of the block that the evidence occured at to indicate the age of the evidence. -### DuplicateVoteEvidence +### DuplicateVoteEvidence -`DuplicateVoteEvidence` represents a validator that has voted for two different blocks +`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`. ```go type DuplicateVoteEvidence struct { - VoteA *Vote - VoteB *Vote - - Timestamp time.Time + VoteA *Vote + VoteB *Vote + + Timestamp time.Time } ``` @@ -252,15 +252,15 @@ Valid Duplicate Vote Evidence must adhere to the following rules: ### AmensiaEvidence -`AmnesiaEvidence` represents a validator that has incorrectly voted for another block in a +`AmnesiaEvidence` represents a validator that has incorrectly voted for another block in a different round to the the block that the validator was previously locked on. This form -of evidence is generated differently from the rest. See this +of evidence is generated differently from the rest. See this [ADR](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-056-proving-amnesia-attacks.md) for more information. ```go type AmnesiaEvidence struct { - *PotentialAmnesiaEvidence - Polc *ProofOfLockChange + *PotentialAmnesiaEvidence + Polc *ProofOfLockChange } ``` @@ -280,16 +280,16 @@ Valid Amnesia Evidence must adhere to the following rules: ### LunaticValidatorEvidence -`LunaticValidatorEvidence` represents a validator that has signed for an arbitrary application state. +`LunaticValidatorEvidence` represents a validator that has signed for an arbitrary application state. This attack only applies to Light clients. ```go type LunaticValidatorEvidence struct { - Header *Header - Vote *Vote - InvalidHeaderField string - - Timestamp time.Time + Header *Header + Vote *Vote + InvalidHeaderField string + + Timestamp time.Time } ``` @@ -330,7 +330,7 @@ A Header is valid if its corresponding fields are valid. ### Version -``` +```go block.Version.Block == state.Version.Consensus.Block block.Version.App == state.Version.Consensus.App ``` @@ -339,7 +339,7 @@ The block version must match consensus version from the state. ### ChainID -``` +```go len(block.ChainID) < 50 ``` @@ -357,7 +357,7 @@ The height is an incrementing integer. The first block has `block.Header.Height ### Time -``` +```go block.Header.Timestamp >= prevBlock.Header.Timestamp + state.consensusParams.Block.TimeIotaMs block.Header.Timestamp == MedianTime(block.LastCommit, state.LastValidators) ``` @@ -371,7 +371,7 @@ block being voted on. The timestamp of the first block must be equal to the genesis time (since there's no votes to compute the median). -``` +```go if block.Header.Height == state.InitialHeight { block.Header.Timestamp == genesisTime } @@ -543,21 +543,21 @@ 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 !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) { + return ErrVoteInvalidValidatorAddress + } - if !pubKey.VerifyBytes(vote.SignBytes(chainID), vote.Signature) { - return ErrVoteInvalidSignature - } - return nil + if !pubKey.VerifyBytes(vote.SignBytes(chainID), vote.Signature) { + return ErrVoteInvalidSignature + } + return nil } ``` where `pubKey.Verify` performs the appropriate digital signature verification of the `pubKey` against the given signature and message bytes. -# Execution +## Execution Once a block is validated, it can be executed against the state. @@ -574,26 +574,26 @@ 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) + // 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, - }, - }, - } + 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, + }, + }, + } } ``` diff --git a/spec/core/encoding.md b/spec/core/encoding.md index 649d91e67..ae6e4e42b 100644 --- a/spec/core/encoding.md +++ b/spec/core/encoding.md @@ -86,7 +86,7 @@ TODO: pubkey The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key: -``` +```go address = SHA256(pubkey)[:20] ``` @@ -98,7 +98,7 @@ TODO: pubkey The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key: -``` +```go address = SHA256(pubkey)[:20] ``` @@ -110,7 +110,7 @@ TODO: pubkey The address is the RIPEMD160 hash of the SHA256 hash of the OpenSSL compressed public key: -``` +```go address = RIPEMD160(SHA256(pubkey)) ``` @@ -194,7 +194,7 @@ The differences between RFC 6962 and the simplest form a merkle tree are that: (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 * * @@ -223,29 +223,29 @@ func emptyHash() []byte { // SHA256(0x00 || leaf) func leafHash(leaf []byte) []byte { - return tmhash.Sum(append(0x00, leaf...)) + 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...)...)) + 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) - } + 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) + } } ``` @@ -253,7 +253,7 @@ 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 } @@ -281,31 +281,31 @@ Which is verified as follows: ```golang func (proof SimpleProof) Verify(rootHash []byte, leaf []byte) bool { - assert(proof.LeafHash, leafHash(leaf) + assert(proof.LeafHash, leafHash(leaf) - computedHash := computeHashFromAunts(proof.Index, proof.Total, proof.LeafHash, proof.Aunts) + 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) + assert(index < total && index >= 0 && total > 0) - if total == 1{ - assert(len(proof.Aunts) == 0) - return leafHash - } + if total == 1{ + assert(len(proof.Aunts) == 0) + return leafHash + } - assert(len(innerHashes) > 0) + 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) + 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) } ``` @@ -323,7 +323,7 @@ Because Tendermint only uses a Simple Merkle Tree, application developers are ex Amino also supports JSON encoding - registered types are simply encoded as: -``` +```json { "type": "", "value": @@ -332,7 +332,7 @@ Amino also supports JSON encoding - registered types are simply encoded as: For instance, an ED25519 PubKey would look like: -``` +```json { "type": "tendermint/PubKeyEd25519", "value": "uZ4h63OFWuQ36ZZ4Bd6NF+/w9fWUwrOncrQsackrsTk=" @@ -353,12 +353,12 @@ We call this encoding the SignBytes. For instance, SignBytes for a vote is the A ```go type CanonicalVote struct { - Type byte - Height int64 `binary:"fixed64"` - Round int64 `binary:"fixed64"` - BlockID CanonicalBlockID - Timestamp time.Time - ChainID string + Type byte + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + BlockID CanonicalBlockID + Timestamp time.Time + ChainID string } ``` diff --git a/spec/core/state.md b/spec/core/state.md index 446a74be2..375118b23 100644 --- a/spec/core/state.md +++ b/spec/core/state.md @@ -50,8 +50,8 @@ application as two `uint64` values: ```go type Consensus struct { - Block uint64 - App uint64 + Block uint64 + App uint64 } ``` @@ -112,43 +112,43 @@ evolve without breaking the header. ```go type ConsensusParams struct { - Block - Evidence - Validator - Version + Block + Evidence + Validator + Version } type hashedParams struct { - BlockMaxBytes int64 - BlockMaxGas int64 + BlockMaxBytes int64 + BlockMaxGas int64 } func (params ConsensusParams) Hash() []byte { - SHA256(hashedParams{ - BlockMaxBytes: params.Block.MaxBytes, - BlockMaxGas: params.Block.MaxGas, - }) + SHA256(hashedParams{ + BlockMaxBytes: params.Block.MaxBytes, + BlockMaxGas: params.Block.MaxGas, + }) } type BlockParams struct { - MaxBytes int64 - MaxGas int64 - TimeIotaMs int64 + MaxBytes int64 + MaxGas int64 + TimeIotaMs int64 } type EvidenceParams struct { - MaxAgeNumBlocks int64 - MaxAgeDuration time.Duration - MaxNum uint32 - ProofTrialPeriod int64 + MaxAgeNumBlocks int64 + MaxAgeDuration time.Duration + MaxNum uint32 + ProofTrialPeriod int64 } type ValidatorParams struct { - PubKeyTypes []string + PubKeyTypes []string } type VersionParams struct { - AppVersion uint64 + AppVersion uint64 } ``` @@ -170,7 +170,7 @@ 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 + block.Header.Height-evidence.Height < ConsensusParams.Evidence.MaxAgeNumBlocks ``` #### Validator diff --git a/spec/p2p/config.md b/spec/p2p/config.md index bddda4a4b..b63c04f28 100644 --- a/spec/p2p/config.md +++ b/spec/p2p/config.md @@ -41,10 +41,9 @@ and that the node may not be able to keep the connection persistent. 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 +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 index fd2e7bc4d..6babff589 100644 --- a/spec/p2p/connection.md +++ b/spec/p2p/connection.md @@ -30,11 +30,11 @@ If a pong or message is not received in sufficient time after a ping, the peer i 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 + ChannelID byte + EOF byte // 1 means message ends here. + Bytes []byte } ``` diff --git a/spec/p2p/peer.md b/spec/p2p/peer.md index df3221fab..ca791d799 100644 --- a/spec/p2p/peer.md +++ b/spec/p2p/peer.md @@ -29,10 +29,10 @@ Both handshakes have configurable timeouts (they should complete quickly). Tendermint implements the Station-to-Station protocol using X25519 keys for Diffie-Helman key-exchange and chacha20poly1305 for encryption. -Previous versions of this protocol suffered from malleability attacks whereas an active man +Previous versions of this protocol suffered from malleability attacks whereas an active man in the middle attacker could compromise confidentiality as decribed in [Prime, Order Please! Revisiting Small Subgroup and Invalid Curve Attacks on -Protocols using Diffie-Hellman](https://eprint.iacr.org/2019/526.pdf). +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. @@ -46,10 +46,10 @@ It goes as follows: - 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 + - 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. + - 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 1024 byte frames, using the respective secret and nonce. Each nonce is incremented by one after each use. @@ -99,14 +99,14 @@ type NodeInfo struct { } type Version struct { - P2P uint64 - Block uint64 - App uint64 + P2P uint64 + Block uint64 + App uint64 } type NodeInfoOther struct { - TxIndex string - RPCAddress string + TxIndex string + RPCAddress string } ``` diff --git a/spec/reactors/block_sync/bcv1/impl-v1.md b/spec/reactors/block_sync/bcv1/impl-v1.md index 0ffaaea69..e039f3d59 100644 --- a/spec/reactors/block_sync/bcv1/impl-v1.md +++ b/spec/reactors/block_sync/bcv1/impl-v1.md @@ -1,11 +1,13 @@ # Blockchain Reactor v1 -### Data Structures +## Data Structures + The data structures used are illustrated below. ![Data Structures](img/bc-reactor-new-datastructs.png) -#### BlockchainReactor +### BlockchainReactor + - is a `p2p.BaseReactor`. - has a `store.BlockStore` for persistence. - executes blocks using an `sm.BlockExecutor`. @@ -17,33 +19,34 @@ The data structures used are illustrated below. ```go type BlockchainReactor struct { - p2p.BaseReactor + p2p.BaseReactor - initialState sm.State // immutable - state sm.State + initialState sm.State // immutable + state sm.State - blockExec *sm.BlockExecutor - store *store.BlockStore + blockExec *sm.BlockExecutor + store *store.BlockStore - fastSync bool + fastSync bool - fsm *BcReactorFSM - blocksSynced int + fsm *BcReactorFSM + blocksSynced int - // Receive goroutine forwards messages to this channel to be processed in the context of the poolRoutine. - messagesForFSMCh chan bcReactorMessage + // Receive goroutine forwards messages to this channel to be processed in the context of the poolRoutine. + messagesForFSMCh chan bcReactorMessage - // Switch goroutine may send RemovePeer to the blockchain reactor. This is an error message that is relayed - // to this channel to be processed in the context of the poolRoutine. - errorsForFSMCh chan bcReactorMessage + // Switch goroutine may send RemovePeer to the blockchain reactor. This is an error message that is relayed + // to this channel to be processed in the context of the poolRoutine. + errorsForFSMCh chan bcReactorMessage - // This channel is used by the FSM and indirectly the block pool to report errors to the blockchain reactor and - // the switch. - eventsFromFSMCh chan bcFsmMessage + // This channel is used by the FSM and indirectly the block pool to report errors to the blockchain reactor and + // the switch. + eventsFromFSMCh chan bcFsmMessage } ``` #### BcReactorFSM + - implements a simple finite state machine. - has a state and a state timer. - has a `BlockPool` to keep track of block requests sent to peers and blocks received from peers. @@ -51,49 +54,53 @@ type BlockchainReactor struct { ```go type BcReactorFSM struct { - logger log.Logger - mtx sync.Mutex + logger log.Logger + mtx sync.Mutex - startTime time.Time + startTime time.Time - state *bcReactorFSMState - stateTimer *time.Timer - pool *BlockPool + state *bcReactorFSMState + stateTimer *time.Timer + pool *BlockPool - // interface used to call the Blockchain reactor to send StatusRequest, BlockRequest, reporting errors, etc. - toBcR bcReactor + // interface used to call the Blockchain reactor to send StatusRequest, BlockRequest, reporting errors, etc. + toBcR bcReactor } ``` #### BlockPool + - maintains a peer set, implemented as a map of peer ID to `BpPeer`. - maintains a set of requests made to peers, implemented as a map of block request heights to peer IDs. -- maintains a list of future block requests needed to advance the fast-sync. This is a list of block heights. +- maintains a list of future block requests needed to advance the fast-sync. This is a list of block heights. - keeps track of the maximum height of the peers in the set. - uses an interface to send requests and report errors to the reactor (via FSM). ```go type BlockPool struct { - logger log.Logger - // Set of peers that have sent status responses, with height bigger than pool.Height - peers map[p2p.ID]*BpPeer - // Set of block heights and the corresponding peers from where a block response is expected or has been received. - blocks map[int64]p2p.ID + logger log.Logger + // Set of peers that have sent status responses, with height bigger than pool.Height + peers map[p2p.ID]*BpPeer + // Set of block heights and the corresponding peers from where a block response is expected or has been received. + blocks map[int64]p2p.ID - plannedRequests map[int64]struct{} // list of blocks to be assigned peers for blockRequest - nextRequestHeight int64 // next height to be added to plannedRequests + plannedRequests map[int64]struct{} // list of blocks to be assigned peers for blockRequest + nextRequestHeight int64 // next height to be added to plannedRequests - Height int64 // height of next block to execute - MaxPeerHeight int64 // maximum height of all peers - toBcR bcReactor + Height int64 // height of next block to execute + MaxPeerHeight int64 // maximum height of all peers + toBcR bcReactor } ``` + Some reasons for the `BlockPool` data structure content: + 1. If a peer is removed by the switch fast access is required to the peer and the block requests made to that peer in order to redo them. 2. When block verification fails fast access is required from the block height to the peer and the block requests made to that peer in order to redo them. 3. The `BlockchainReactor` main routine decides when the block pool is running low and asks the `BlockPool` (via FSM) to make more requests. The `BlockPool` creates a list of requests and triggers the sending of the block requests (via the interface). The reason it maintains a list of requests is the redo operations that may occur during error handling. These are redone when the `BlockchainReactor` requires more blocks. #### BpPeer + - keeps track of a single peer, with height bigger than the initial height. - maintains the block requests made to the peer and the blocks received from the peer until they are executed. - monitors the peer speed when there are pending requests. @@ -101,17 +108,17 @@ Some reasons for the `BlockPool` data structure content: ```go type BpPeer struct { - logger log.Logger - ID p2p.ID + logger log.Logger + ID p2p.ID - Height int64 // the peer reported height - NumPendingBlockRequests int // number of requests still waiting for block responses - blocks map[int64]*types.Block // blocks received or expected to be received from this peer - blockResponseTimer *time.Timer - recvMonitor *flow.Monitor - params *BpPeerParams // parameters for timer and monitor + Height int64 // the peer reported height + NumPendingBlockRequests int // number of requests still waiting for block responses + blocks map[int64]*types.Block // blocks received or expected to be received from this peer + blockResponseTimer *time.Timer + recvMonitor *flow.Monitor + params *BpPeerParams // parameters for timer and monitor - onErr func(err error, peerID p2p.ID) // function to call on error + onErr func(err error, peerID p2p.ID) // function to call on error } ``` @@ -120,61 +127,73 @@ type BpPeer struct { The diagram below shows the goroutines (depicted by the gray blocks), timers (shown on the left with their values) and channels (colored rectangles). The FSM box shows some of the functionality and it is not a separate goroutine. The interface used by the FSM is shown in light red with the `IF` block. This is used to: -- send block requests + +- send block requests - report peer errors to the switch - this results in the reactor calling `switch.StopPeerForError()` and, if triggered by the peer timeout routine, a `removePeerEv` is sent to the FSM and action is taken from the context of the `poolRoutine()` - ask the reactor to reset the state timers. The timers are owned by the FSM while the timeout routine is defined by the reactor. This was done in order to avoid running timers in tests and will change in the next revision. - + There are two main goroutines implemented by the blockchain reactor. All I/O operations are performed from the `poolRoutine()` context while the CPU intensive operations related to the block execution are performed from the context of the `executeBlocksRoutine()`. All goroutines are detailed in the next sections. ![Go Routines Diagram](img/bc-reactor-new-goroutines.png) #### Receive() + Fast-sync messages from peers are received by this goroutine. It performs basic validation and: + - in helper mode (i.e. for request message) it replies immediately. This is different than the proposal in adr-040 that specifies having the FSM handling these. - forwards response messages to the `poolRoutine()`. #### poolRoutine() -(named kept as in the previous reactor). + +(named kept as in the previous reactor). It starts the `executeBlocksRoutine()` and the FSM. It then waits in a loop for events. These are received from the following channels: + - `sendBlockRequestTicker.C` - every 10msec the reactor asks FSM to make more block requests up to a maximum. Note: currently this value is constant but could be changed based on low/ high watermark thresholds for the number of blocks received and waiting to be processed, the number of blockResponse messages waiting in messagesForFSMCh, etc. - `statusUpdateTicker.C` - every 10 seconds the reactor broadcasts status requests to peers. While adr-040 specifies this to run within the FSM, at this point this functionality is kept in the reactor. - `messagesForFSMCh` - the `Receive()` goroutine sends status and block response messages to this channel and the reactor calls FSM to handle them. -- `errorsForFSMCh` - this channel receives the following events: +- `errorsForFSMCh` - this channel receives the following events: - peer remove - when the switch removes a peer - sate timeout event - when FSM state timers trigger The reactor forwards this messages to the FSM. - `eventsFromFSMCh` - there are two type of events sent over this channel: - `syncFinishedEv` - triggered when FSM enters `finished` state and calls the switchToConsensus() interface function. - `peerErrorEv`- peer timer expiry goroutine sends this event over the channel for processing from poolRoutine() context. - -#### executeBlocksRoutine() -Started by the `poolRoutine()`, it retrieves blocks from the pool and executes them: -- `processReceivedBlockTicker.C` - a ticker event is received over the channel every 10msec and its handling results in a signal being sent to the doProcessBlockCh channel. -- doProcessBlockCh - events are received on this channel as described as above and upon processing blocks are retrieved from the pool and executed. +#### executeBlocksRoutine() + +Started by the `poolRoutine()`, it retrieves blocks from the pool and executes them: + +- `processReceivedBlockTicker.C` - a ticker event is received over the channel every 10msec and its handling results in a signal being sent to the doProcessBlockCh channel. +- doProcessBlockCh - events are received on this channel as described as above and upon processing blocks are retrieved from the pool and executed. ### FSM ![fsm](img/bc-reactor-new-fsm.png) #### States + ##### init (aka unknown) + The FSM is created in `unknown` state. When started, by the reactor (`startFSMEv`), it broadcasts Status requests and transitions to `waitForPeer` state. ##### waitForPeer + In this state, the FSM waits for a Status responses from a "tall" peer. A timer is running in this state to allow the FSM to finish if there are no useful peers. If the timer expires, it moves to `finished` state and calls the reactor to switch to consensus. If a Status response is received from a peer within the timeout, the FSM transitions to `waitForBlock` state. ##### waitForBlock + In this state the FSM makes Block requests (triggered by a ticker in reactor) and waits for Block responses. There is a timer running in this state to detect if a peer is not sending the block at current processing height. If the timer expires, the FSM removes the peer where the request was sent and all requests made to that peer are redone. As blocks are received they are stored by the pool. Block execution is independently performed by the reactor and the result reported to the FSM: + - if there are no errors, the FSM increases the pool height and resets the state timer. - if there are errors, the peers that delivered the two blocks (at height and height+1) are removed and the requests redone. -In this state the FSM may receive peer remove events in any of the following scenarios: +In this state the FSM may receive peer remove events in any of the following scenarios: + - the switch is removing a peer - a peer is penalized because it has not responded to some block requests for a long time - a peer is penalized for being slow @@ -183,6 +202,7 @@ When processing of the last block (the one with height equal to the highest peer If after a peer update or removal the pool height is same as maxPeerHeight, the FSM transitions to `finished` state. ##### finished + When entering this state, the FSM calls the reactor to switch to consensus and performs cleanup. #### Events @@ -191,18 +211,19 @@ The following events are handled by the FSM: ```go const ( - startFSMEv = iota + 1 - statusResponseEv - blockResponseEv - processedBlockEv - makeRequestsEv - stopFSMEv - peerRemoveEv = iota + 256 - stateTimeoutEv + startFSMEv = iota + 1 + statusResponseEv + blockResponseEv + processedBlockEv + makeRequestsEv + stopFSMEv + peerRemoveEv = iota + 256 + stateTimeoutEv ) ``` ### Examples of Scenarios and Termination Handling + A few scenarios are covered in this section together with the current/ proposed handling. In general, the scenarios involving faulty peers are made worse by the fact that they may quickly be re-added. diff --git a/spec/reactors/block_sync/impl.md b/spec/reactors/block_sync/impl.md index 35a37debb..4f39658e7 100644 --- a/spec/reactors/block_sync/impl.md +++ b/spec/reactors/block_sync/impl.md @@ -1,6 +1,6 @@ -## Blockchain Reactor v0 Modules +# Blockchain Reactor v0 Module -### Blockchain Reactor +## Blockchain Reactor - coordinates the pool for syncing - coordinates the store for persistence @@ -10,35 +10,34 @@ - starts the pool.Start() and its poolRoutine() - registers all the concrete types and interfaces for serialisation -#### poolRoutine +### poolRoutine - listens to these channels: - - pool requests blocks from a specific peer by posting to requestsCh, block reactor then sends + - pool requests blocks from a specific peer by posting to requestsCh, block reactor then sends a &bcBlockRequestMessage for a specific height - - pool signals timeout of a specific peer by posting to timeoutsCh - - switchToConsensusTicker to periodically try and switch to consensus - - trySyncTicker to periodically check if we have fallen behind and then catch-up sync - - if there aren't any new blocks available on the pool it skips syncing + - pool signals timeout of a specific peer by posting to timeoutsCh + - switchToConsensusTicker to periodically try and switch to consensus + - trySyncTicker to periodically check if we have fallen behind and then catch-up sync + - if there aren't any new blocks available on the pool it skips syncing - tries to sync the app by taking downloaded blocks from the pool, gives them to the app and stores them on disk - implements Receive which is called by the switch/peer - - calls AddBlock on the pool when it receives a new block from a peer + - calls AddBlock on the pool when it receives a new block from a peer -### Block Pool +## Block Pool - responsible for downloading blocks from peers - makeRequestersRoutine() - - removes timeout peers - - starts new requesters by calling makeNextRequester() + - removes timeout peers + - starts new requesters by calling makeNextRequester() - requestRoutine(): - - picks a peer and sends the request, then blocks until: - - pool is stopped by listening to pool.Quit - - requester is stopped by listening to Quit - - request is redone - - we receive a block - - gotBlockCh is strange + - picks a peer and sends the request, then blocks until: + - pool is stopped by listening to pool.Quit + - requester is stopped by listening to Quit + - request is redone + - we receive a block + - gotBlockCh is strange - -### Go Routines in Blockchain Reactor +## Go Routines in Blockchain Reactor ![Go Routines Diagram](img/bc-reactor-routines.png) diff --git a/spec/reactors/block_sync/reactor.md b/spec/reactors/block_sync/reactor.md index 91fd79b0b..9c34ab49c 100644 --- a/spec/reactors/block_sync/reactor.md +++ b/spec/reactors/block_sync/reactor.md @@ -186,7 +186,7 @@ fetchBlock(height, pool): mtx.Lock() pool.numPending++ redo = true - mtx.UnLock() + mtx.UnLock() } } } @@ -251,23 +251,23 @@ main(pool): while true do select { - upon receiving BlockRequest(Height, Peer) on pool.requestsChannel: - try to send bcBlockRequestMessage(Height) to Peer + upon receiving BlockRequest(Height, Peer) on pool.requestsChannel: + try to send bcBlockRequestMessage(Height) to Peer - upon receiving error(peer) on errorsChannel: - stop peer for error + upon receiving error(peer) on errorsChannel: + stop peer for error - upon receiving message on statusUpdateTickerChannel: - broadcast bcStatusRequestMessage(bcR.store.Height) // message sent in a separate routine + upon receiving message on statusUpdateTickerChannel: + broadcast bcStatusRequestMessage(bcR.store.Height) // message sent in a separate routine - upon receiving message on switchToConsensusTickerChannel: - pool.mtx.Lock() - receivedBlockOrTimedOut = pool.height > 0 || (time.Now() - pool.startTime) > 5 Seconds - ourChainIsLongestAmongPeers = pool.maxPeerHeight == 0 || pool.height >= pool.maxPeerHeight - haveSomePeers = size of pool.peers > 0 - pool.mtx.Unlock() - if haveSomePeers && receivedBlockOrTimedOut && ourChainIsLongestAmongPeers then - switch to consensus mode + upon receiving message on switchToConsensusTickerChannel: + pool.mtx.Lock() + receivedBlockOrTimedOut = pool.height > 0 || (time.Now() - pool.startTime) > 5 Seconds + ourChainIsLongestAmongPeers = pool.maxPeerHeight == 0 || pool.height >= pool.maxPeerHeight + haveSomePeers = size of pool.peers > 0 + pool.mtx.Unlock() + if haveSomePeers && receivedBlockOrTimedOut && ourChainIsLongestAmongPeers then + switch to consensus mode upon receiving message on trySyncTickerChannel: for i = 0; i < 10; i++ do @@ -294,7 +294,7 @@ main(pool): redoRequestsForPeer(pool, peerId): for each requester in pool.requesters do if requester.getPeerID() == peerID - enqueue msg on redoChannel for requester + enqueue msg on redoChannel for requester ``` ## Channels diff --git a/spec/reactors/consensus/consensus-reactor.md b/spec/reactors/consensus/consensus-reactor.md index 7ac9d20b0..c228d2dd3 100644 --- a/spec/reactors/consensus/consensus-reactor.md +++ b/spec/reactors/consensus/consensus-reactor.md @@ -42,19 +42,19 @@ received votes and last commit and last validators set. ```go type RoundState struct { - Height int64 - Round int - Step RoundStepType - Validators ValidatorSet - Proposal Proposal - ProposalBlock Block - ProposalBlockParts PartSet - LockedRound int - LockedBlock Block - LockedBlockParts PartSet - Votes HeightVoteSet - LastCommit VoteSet - LastValidators ValidatorSet + Height int64 + Round int + Step RoundStepType + Validators ValidatorSet + Proposal Proposal + ProposalBlock Block + ProposalBlockParts PartSet + LockedRound int + LockedBlock Block + LockedBlockParts PartSet + Votes HeightVoteSet + LastCommit VoteSet + LastValidators ValidatorSet } ``` @@ -77,20 +77,20 @@ Consensus Reactor and by the gossip routines upon sending a message to the peer. ```golang type PeerRoundState struct { - Height int64 // Height peer is at - Round int // Round peer is at, -1 if unknown. - Step RoundStepType // Step peer is at - Proposal bool // True if peer has proposal for this round - ProposalBlockPartsHeader PartSetHeader - ProposalBlockParts BitArray - ProposalPOLRound int // Proposal's POL round. -1 if none. - ProposalPOL BitArray // nil until ProposalPOLMessage received. - Prevotes BitArray // All votes peer has for this round - Precommits BitArray // All precommits peer has for this round - LastCommitRound int // Round of commit for last height. -1 if none. - LastCommit BitArray // All commit precommits of commit for last height. - CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. - CatchupCommit BitArray // All commit precommits peer has for this height & CatchupCommitRound + Height int64 // Height peer is at + Round int // Round peer is at, -1 if unknown. + Step RoundStepType // Step peer is at + Proposal bool // True if peer has proposal for this round + ProposalBlockPartsHeader PartSetHeader + ProposalBlockParts BitArray + ProposalPOLRound int // Proposal's POL round. -1 if none. + ProposalPOL BitArray // nil until ProposalPOLMessage received. + Prevotes BitArray // All votes peer has for this round + Precommits BitArray // All precommits peer has for this round + LastCommitRound int // Round of commit for last height. -1 if none. + LastCommit BitArray // All commit precommits of commit for last height. + CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. + CatchupCommit BitArray // All commit precommits peer has for this height & CatchupCommitRound } ``` @@ -106,7 +106,7 @@ respectively. ### NewRoundStepMessage handler -``` +```go handleMessage(msg): if msg is from smaller height/round/step then return // Just remember these values. @@ -123,17 +123,17 @@ handleMessage(msg): if prs.Height has been updated then if prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound then prs.LastCommitRound = msg.LastCommitRound - prs.LastCommit = prs.Precommits + prs.LastCommit = prs.Precommits } else { prs.LastCommitRound = msg.LastCommitRound - prs.LastCommit = nil + prs.LastCommit = nil } Reset prs.CatchupCommitRound and prs.CatchupCommit ``` ### NewValidBlockMessage handler -``` +```go handleMessage(msg): if prs.Height != msg.Height then return @@ -148,7 +148,7 @@ protect the node against DOS attacks. ### HasVoteMessage handler -``` +```go handleMessage(msg): if prs.Height == msg.Height then prs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index) @@ -156,7 +156,7 @@ handleMessage(msg): ### VoteSetMaj23Message handler -``` +```go handleMessage(msg): if prs.Height == msg.Height then Record in rs that a peer claim to have ⅔ majority for msg.BlockID @@ -165,7 +165,7 @@ handleMessage(msg): ### ProposalMessage handler -``` +```go handleMessage(msg): if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return prs.Proposal = true @@ -178,7 +178,7 @@ handleMessage(msg): ### ProposalPOLMessage handler -``` +```go handleMessage(msg): if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then return prs.ProposalPOL = msg.ProposalPOL @@ -189,7 +189,7 @@ node against DOS attacks. ### BlockPartMessage handler -``` +```go handleMessage(msg): if prs.Height != msg.Height || prs.Round != msg.Round then return Record in prs that peer has block part msg.Part.Index @@ -198,7 +198,7 @@ handleMessage(msg): ### VoteMessage handler -``` +```go handleMessage(msg): Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and round Send msg trough internal peerMsgQueue to ConsensusState service @@ -206,7 +206,7 @@ handleMessage(msg): ### VoteSetBitsMessage handler -``` +```go handleMessage(msg): Update prs for the bit-array of votes peer claims to have for the msg.BlockID ``` @@ -220,12 +220,12 @@ It is used to send the following messages to the peer: `BlockPartMessage`, `Prop `ProposalPOLMessage` on the DataChannel. The gossip data routine is based on the local RoundState (`rs`) and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below: -``` +```go 1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts then Part = pick a random proposal block part the peer does not have Send BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannel if send returns true, record that the peer knows the corresponding block Part - Continue + Continue 1b) if (0 < prs.Height) and (prs.Height < rs.Height) then help peer catch up using gossipDataForCatchup function @@ -239,8 +239,8 @@ and the known PeerRoundState (`prs`). The routine repeats forever the logic show 1d) if (rs.Proposal != nil and !prs.Proposal) then Send ProposalMessage(rs.Proposal) to the peer if send returns true, record that the peer knows Proposal - if 0 <= rs.Proposal.POLRound then - polRound = rs.Proposal.POLRound + if 0 <= rs.Proposal.POLRound then + polRound = rs.Proposal.POLRound prevotesBitArray = rs.Votes.Prevotes(polRound).BitArray() Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray) Continue @@ -253,16 +253,18 @@ and the known PeerRoundState (`prs`). The routine repeats forever the logic show This function is responsible for helping peer catch up if it is at the smaller height (prs.Height < rs.Height). The function executes the following logic: +```go if peer does not have all block parts for prs.ProposalBlockPart then blockMeta = Load Block Metadata for height prs.Height from blockStore if (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) then Sleep PeerGossipSleepDuration - return + return Part = pick a random proposal block part the peer does not have Send BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannel if send returns true, record that the peer knows the corresponding block Part return else Sleep PeerGossipSleepDuration +``` ## Gossip Votes Routine @@ -270,7 +272,7 @@ It is used to send the following message: `VoteMessage` on the VoteChannel. The gossip votes routine is based on the local RoundState (`rs`) and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below: -``` +```go 1a) if rs.Height == prs.Height then if prs.Step == RoundStepNewHeight then vote = random vote from rs.LastCommit the peer does not have @@ -284,7 +286,7 @@ and the known PeerRoundState (`prs`). The routine repeats forever the logic show if send returns true, continue if prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round then - Precommits = rs.Votes.Precommits(prs.Round) + Precommits = rs.Votes.Precommits(prs.Round) vote = random vote from Precommits the peer does not have Send VoteMessage(vote) to the peer if send returns true, continue @@ -315,7 +317,7 @@ It is used to send the following message: `VoteSetMaj23Message`. `VoteSetMaj23Me BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs`) and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below. -``` +```go 1a) if rs.Height == prs.Height then Prevotes = rs.Votes.Prevotes(prs.Round) if there is a ⅔ majority for some blockId in Prevotes then diff --git a/spec/reactors/consensus/consensus.md b/spec/reactors/consensus/consensus.md index c85b3b8f2..459e4afbd 100644 --- a/spec/reactors/consensus/consensus.md +++ b/spec/reactors/consensus/consensus.md @@ -16,7 +16,7 @@ explained in a forthcoming document. For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the block as the block size is big, i.e., they don't embed the block inside `Proposal` and `VoteMessage`. Instead, they reach agreement on the `BlockID` (see `BlockID` definition in -[Blockchain](https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md#blockid) section) +[Blockchain](https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md#blockid) section) that uniquely identifies each block. The block itself is disseminated to validator processes using peer-to-peer gossiping protocol. It starts by having a proposer first splitting a block into a number of block parts, that are then gossiped between @@ -49,7 +49,7 @@ type ProposalMessage struct { 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 +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. ```go @@ -66,8 +66,8 @@ type Proposal struct { ## VoteMessage VoteMessage 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) +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 @@ -110,9 +110,9 @@ type NewRoundStepMessage struct { ## NewValidBlockMessage -NewValidBlockMessage is sent when a validator observes a valid block B in some round r, +NewValidBlockMessage 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 +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. @@ -121,7 +121,7 @@ In case the block is also committed, then IsCommit flag is set to true. ```go type NewValidBlockMessage struct { Height int64 - Round int + Round int BlockPartsHeader PartSetHeader BlockParts BitArray IsCommit bool diff --git a/spec/reactors/consensus/proposer-selection.md b/spec/reactors/consensus/proposer-selection.md index 6cb596ec0..6b2f3d76d 100644 --- a/spec/reactors/consensus/proposer-selection.md +++ b/spec/reactors/consensus/proposer-selection.md @@ -4,41 +4,46 @@ This document specifies the Proposer Selection Procedure that is used in Tenderm 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. +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 +### 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 +### 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 +- C ~ k when there are validator changes *[this needs more work]* -### Basic Algorithm +## 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 +- 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 @@ -49,7 +54,7 @@ Notation: Simple view at the Selection Algorithm: -``` +```md def ProposerSelection (vset): // compute priorities and elect proposer @@ -59,16 +64,16 @@ Simple view at the Selection Algorithm: A(prop) -= P ``` -### Stable Set +## Stable Set Consider the validator set: -Validator | p1| p2 +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. +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 |--------------- |---|---|---- |---|---- |---|---|---|-------- @@ -83,20 +88,23 @@ Each row shows the priority queue and the process place in it. The proposer is t | | | |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 +- 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 +### Voting Power Change + Consider again the earlier example and assume that the voting power of p1 is changed to 4: -Validator | p1| p2 +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. +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 |--------------| ---|--- |------|--- |--- |-------- @@ -107,20 +115,22 @@ Let's also assume that before this change the proposer priorites were as shown i 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 +### 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). +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. +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 |--------------- |--- | ---|--- |--- |--- |--- |---|-------- @@ -132,6 +142,7 @@ For this reason, the selection procedure adds another __new step__ that centers The modified selection algorithm is: +```md def ProposerSelection (vset): // center priorities around zero @@ -144,18 +155,23 @@ The modified selection algorithm is: 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 +### 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. +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. @@ -169,7 +185,9 @@ 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. @@ -183,7 +201,8 @@ In the next run, p3 will still be ahead in the queue, elected as proposer and mo | | | | | | p3 | | | | p2| | p1|A(i)+=VP(i) | | | | p1 | | p3 | | | | p2| | |A(p1)-=P -### Proposer Priority Range +## 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: @@ -198,83 +217,90 @@ Then execute the following steps: 1. Add a new validator p3: -Validator | p1 | p2 | p3 -----------|-----|--- |---- -VP | 80k | 10 | 10 + 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 - + |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__ + 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. +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: + if diff > threshold: scale = diff/threshold for each validator i in vset: - A(i) = A(i)/scale + 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. +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 +## Wrinkles + +### Validator Power Overflow Conditions -#### 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 +### 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]__ +## Requirement Fulfillment Claims -The proposer algorithm is deterministic giving consistent results across executions with same transactions and validator set modifications. +__[R1]__ + +The proposer algorithm is deterministic giving consistent results across executions with same transactions and validator set modifications. [WIP - needs more detail] -__[R2]__ +__[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 +Validator | p1| p2 ----------|---|--- VP | 1 | 3 @@ -286,6 +312,8 @@ Assigning priorities to each validator based on the voting power and updating th 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/reactors/mempool/config.md b/spec/reactors/mempool/config.md index 77deded42..0f5366fe2 100644 --- a/spec/reactors/mempool/config.md +++ b/spec/reactors/mempool/config.md @@ -12,7 +12,7 @@ Environment: `TM_MEMPOOL_RECHECK=false` Config: -``` +```toml [mempool] recheck = false ``` diff --git a/spec/reactors/mempool/functionality.md b/spec/reactors/mempool/functionality.md index ea902225d..416ffad97 100644 --- a/spec/reactors/mempool/functionality.md +++ b/spec/reactors/mempool/functionality.md @@ -35,7 +35,7 @@ What guarantees does it need from the ABCI app? The implementation within this library also implements a tx cache. This is so that signatures don't have to be reverified if the tx has -already been seen before. +already been seen before. However, we only store valid txs in the cache, not invalid ones. This is because invalid txs could become good later. Txs that are included in a block aren't removed from the cache, diff --git a/spec/reactors/mempool/reactor.md b/spec/reactors/mempool/reactor.md index 66e5d826b..2ad8134bd 100644 --- a/spec/reactors/mempool/reactor.md +++ b/spec/reactors/mempool/reactor.md @@ -7,7 +7,7 @@ See [this issue](https://github.com/tendermint/tendermint/issues/1503) Mempool maintains a cache of the last 10000 transactions to prevent replaying old transactions (plus transactions coming from other validators, who are continually exchanging transactions). Read [Replay -Protection](https://github.com/tendermint/tendermint/blob/master/docs/app-dev/app-development.md#replay-protection) +Protection](https://github.com/tendermint/tendermint/blob/8cdaa7f515a9d366bbc9f0aff2a263a1a6392ead/docs/app-dev/app-development.md#replay-protection) for details. Sending incorrectly encoded data or data exceeding `maxMsgSize` will result diff --git a/spec/reactors/pex/pex.md b/spec/reactors/pex/pex.md index 77f1407e8..ccd4c836c 100644 --- a/spec/reactors/pex/pex.md +++ b/spec/reactors/pex/pex.md @@ -70,13 +70,13 @@ when calculating a bucket. When placing a peer into a new bucket: -``` +```md hash(key + sourcegroup + int64(hash(key + group + sourcegroup)) % bucket_per_group) % num_new_buckets ``` When placing a peer into an old bucket: -``` +```md hash(key + group + int64(hash(key + addr)) % buckets_per_group) % num_old_buckets ``` diff --git a/spec/reactors/state_sync/reactor.md b/spec/reactors/state_sync/reactor.md index 48beabc09..d3c043087 100644 --- a/spec/reactors/state_sync/reactor.md +++ b/spec/reactors/state_sync/reactor.md @@ -5,14 +5,14 @@ and restoring state machine snapshots. For more information, see the [state sync The state sync reactor has two main responsibilites: -* Serving state machine snapshots taken by the local ABCI application to new nodes joining the +* Serving state machine snapshots taken by the local ABCI application to new nodes joining the network. * Discovering existing snapshots and fetching snapshot chunks for an empty local application being bootstrapped. The state sync process for bootstrapping a new node is described in detail in the section linked -above. While technically part of the reactor (see `statesync/syncer.go` and related components), +above. While technically part of the reactor (see `statesync/syncer.go` and related components), this document will only cover the P2P reactor component. For details on the ABCI methods and data types, see the [ABCI documentation](../../abci/abci.md). @@ -26,16 +26,16 @@ available snapshots: type snapshotsRequestMessage struct{} ``` -The receiver will query the local ABCI application via `ListSnapshots`, and send a message +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: ```go type snapshotsResponseMessage struct { - Height uint64 - Format uint32 - Chunks uint32 - Hash []byte - Metadata []byte + Height uint64 + Format uint32 + Chunks uint32 + Hash []byte + Metadata []byte } ``` @@ -45,9 +45,9 @@ is accepted, the state syncer will request snapshot chunks from appropriate peer ```go type chunkRequestMessage struct { - Height uint64 - Format uint32 - Index uint32 + Height uint64 + Format uint32 + Index uint32 } ``` @@ -56,16 +56,16 @@ and respond with it (limited to 16 MB): ```go type chunkResponseMessage struct { - Height uint64 - Format uint32 - Index uint32 - Chunk []byte - Missing bool + Height uint64 + Format uint32 + Index uint32 + Chunk []byte + Missing bool } ``` 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. +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, @@ -73,5 +73,5 @@ possibly from a different peer. The ABCI application is able to request peer bans and chunk refetching as part of the ABCI protocol. -If no state sync is in progress (i.e. during normal operation), any unsolicited response messages -are discarded. \ No newline at end of file +If no state sync is in progress (i.e. during normal operation), any unsolicited response messages +are discarded. From 9dbf818055e3332261aeffbe099fae5b84e2f3cf Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 24 Aug 2020 13:52:22 +0200 Subject: [PATCH 085/223] ci: add dependabot config (#148) --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..72b46a650 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + time: "11:00" + open-pull-requests-limit: 10 From cda8006569da1b95b24a3cc5f956fdf131dc3aa3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2020 13:54:15 +0200 Subject: [PATCH 086/223] 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> --- .github/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 26993168d..787bf46ed 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -7,4 +7,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - - uses: gaurav-nelson/github-action-markdown-link-check@0.6.0 + - uses: gaurav-nelson/github-action-markdown-link-check@1.0.7 From 6140847bba485f83f73feff9d016c9040e477c91 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 31 Aug 2020 15:50:07 +0200 Subject: [PATCH 087/223] docs: add sections to abci (#150) --- spec/abci/apps.md | 59 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/spec/abci/apps.md b/spec/abci/apps.md index 5a6567c88..da13962eb 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -22,6 +22,16 @@ 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`. +### 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`. @@ -50,9 +60,14 @@ 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. +- [BeginBlock](#beginblock) +- [EndBlock](#endblock) +- [Deliver Tx](#delivertx) +- [Commit](#commit) + ### Mempool Connection -The Mempool Connection should maintain a `CheckTxState` +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`. @@ -81,7 +96,23 @@ a sort of weak filter to keep invalid transactions out of the blockchain. It's weak, because a Byzantine node doesn't care about CheckTx; it can propose a block full of invalid transactions if it wants. -### Info Connection +#### Replay Protection + +To prevent old transactions from being replayed, CheckTx must implement +replay protection. + +Tendermint provides the first defense layer by keeping a lightweight +in-memory cache of 100k (`[mempool] cache_size`) last transactions in +the mempool. If Tendermint is just started or the clients sent more than +100k transactions, old transactions may be sent to the application. So +it is important CheckTx implements some logic to handle them. + +If there are cases in your application where a transaction may become invalid in some +future state, you probably want to disable Tendermint's +cache. You can do that by setting `[mempool] cache_size = 0` in the +config. + +### 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 @@ -93,6 +124,17 @@ QueryState should be set to the latest `DeliverTxState` at the end of every `Com ie. 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 @@ -159,22 +201,27 @@ 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 returns a `abci.Result`, which includes a Code, Data, and Log. + `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. -`Tags` include any tags for the execution, which Tendermint will use to index +`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. -See issue [#1007](https://github.com/tendermint/tendermint/issues/1007) for how -the tags will be hashed into the next block header. - ## Validator Updates The application may set the validator set during InitChain, and update it during From 45bbbb6317260ac6d70a450746e3219b485c3d65 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 1 Sep 2020 10:05:34 +0200 Subject: [PATCH 088/223] spec: update abci events (#151) --- spec/abci/abci.md | 32 ++++++++++++++++---------------- spec/abci/apps.md | 2 +- spec/core/state.md | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 5c704be27..10170c4e3 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -69,26 +69,26 @@ Example: Events: []abci.Event{ { Type: "validator.provisions", - Attributes: kv.Pairs{ - kv.Pair{Key: []byte("address"), Value: []byte("...")}, - kv.Pair{Key: []byte("amount"), Value: []byte("...")}, - kv.Pair{Key: []byte("balance"), Value: []byte("...")}, + 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: kv.Pairs{ - kv.Pair{Key: []byte("address"), Value: []byte("...")}, - kv.Pair{Key: []byte("amount"), Value: []byte("...")}, - kv.Pair{Key: []byte("balance"), Value: []byte("...")}, + 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: kv.Pairs{ - kv.Pair{Key: []byte("address"), Value: []byte("...")}, - kv.Pair{Key: []byte("amount"), Value: []byte("...")}, - kv.Pair{Key: []byte("reason"), Value: []byte("...")}, + 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}, }, }, // ... @@ -305,7 +305,7 @@ via light client. - `ByzantineValidators ([]Evidence)`: List of evidence of validators that acted maliciously. - **Response**: - - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing + - `Events ([]abci.Event)`: Type & Key-Value events for indexing - **Usage**: - Signals the beginning of a new block. Called prior to any DeliverTxs. @@ -332,7 +332,7 @@ via light client. be non-deterministic. - `GasWanted (int64)`: Amount of gas requested for transaction. - `GasUsed (int64)`: Amount of gas consumed by transaction. - - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing + - `Events ([]abci.Event)`: Type & Key-Value events for indexing transactions (eg. by account). - `Codespace (string)`: Namespace for the `Code`. - **Usage**: @@ -360,7 +360,7 @@ via light client. be non-deterministic. - `GasWanted (int64)`: Amount of gas requested for transaction. - `GasUsed (int64)`: Amount of gas consumed by transaction. - - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing + - `Events ([]abci.Event)`: Type & Key-Value events for indexing transactions (eg. by account). - `Codespace (string)`: Namespace for the `Code`. - **Usage**: @@ -377,7 +377,7 @@ via light client. voting power to 0 to remove). - `ConsensusParamUpdates (ConsensusParams)`: Changes to consensus-critical time, size, and other parameters. - - `Tags ([]kv.Pair)`: Key-Value tags for filtering and indexing + - `Events ([]abci.Event)`: Type & Key-Value events for indexing - **Usage**: - Signals the end of a block. - Called after all transactions, prior to each Commit. diff --git a/spec/abci/apps.md b/spec/abci/apps.md index da13962eb..860404096 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -196,7 +196,7 @@ 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. -`Tags` include any tags for the execution, though since the transaction has not +`Events` include any events for the execution, though since the transaction has not been committed yet, they are effectively ignored by Tendermint. ### DeliverTx diff --git a/spec/core/state.md b/spec/core/state.md index 375118b23..6b12ba4a9 100644 --- a/spec/core/state.md +++ b/spec/core/state.md @@ -68,7 +68,7 @@ type Result struct { It returns a result code and an arbitrary byte array (ie. a return value). NOTE: the Result needs to be updated to include more fields returned from -processing transactions, like gas variables and tags - see +processing transactions, like gas variables and events - see [issue 1007](https://github.com/tendermint/tendermint/issues/1007). ### Validator From 1075f77cc3977e512662f6351c79682f827afb27 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 1 Sep 2020 10:47:14 +0200 Subject: [PATCH 089/223] spec: extract light-client to its own directory (#152) Co-authored-by: Callum Waters --- spec/consensus/light-client/README.md | 64 +- spec/consensus/light-client/accountability.md | 301 +-------- spec/consensus/light-client/detection.md | 2 +- spec/consensus/light-client/verification.md | 570 +---------------- spec/light-client/README.md | 65 ++ spec/light-client/accountability.md | 302 +++++++++ spec/light-client/assets/light-node-image.png | Bin 0 -> 122270 bytes spec/light-client/detection.md | 3 + spec/light-client/verification.md | 571 ++++++++++++++++++ 9 files changed, 945 insertions(+), 933 deletions(-) create mode 100644 spec/light-client/README.md create mode 100644 spec/light-client/accountability.md create mode 100644 spec/light-client/assets/light-node-image.png create mode 100644 spec/light-client/detection.md create mode 100644 spec/light-client/verification.md diff --git a/spec/consensus/light-client/README.md b/spec/consensus/light-client/README.md index 78b4a2f2e..b25a6463d 100644 --- a/spec/consensus/light-client/README.md +++ b/spec/consensus/light-client/README.md @@ -1,65 +1,3 @@ # Tendermint Light Client Protocol -NOTE: This specification is under heavy development and is not yet complete nor -accurate. - -## Contents - -- [Motivation](#motivation) -- [Structure](#structure) -- [Core Verification](./verification.md) -- [Fork Detection](./detection.md) -- [Fork Accountability](./accountability.md) - -## Motivation - -The Tendermint Light Client is motivated by the need for a light weight protocol -to sync with a Tendermint blockchain, with the least processing necessary to -securely verify a recent state. The protocol consists of managing trusted validator -sets and trusted block headers, and is based primarily on checking hashes -and verifying Tendermint commit signatures. - -Motivating use cases include: - -- Light Node: a daemon that syncs a blockchain to the latest committed header by making RPC requests to full nodes. -- State Sync: a reactor that syncs a blockchain to a recent committed state by making P2P requests to full nodes. -- IBC Client: an ABCI application library that syncs a blockchain to a recent committed header by receiving proof-carrying -transactions from "IBC relayers", who make RPC requests to full nodes on behalf of the IBC clients. - -## Structure - -### Components - -The Tendermint Light Client consists of three primary components: - -- [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes -- [Fork Detection](./detection.md): talking to multiple peers to detect Byzantine behaviour -- [Fork Accountability](./accountability.md): analyzing Byzantine behaviour to hold validators accountable. - -While every light client must perform core verification and fork detection -to achieve their prescribed security level, fork accountability is expected to -be done by full nodes and validators, and is thus more accurately a component of -the full node consensus protocol, though it is included here since it is -primarily concerned with providing security to light clients. - -A schematic of the core verification and fork detection components in -a Light Node are depicted below. The schematic is quite similar for other use cases. -Note that fork accountability is not depicted, as it is the responsibility of the -full nodes. - -![Light Client Diagram](./assets/light-node-image.png). - -### Synchrony - -Light clients are fundamentally synchronous protocols, -where security is restricted by the interval during which a validator can be punished -for Byzantine behaviour. We assume here that such intervals have fixed and known minimal duration -referred to commonly as a blockchain's Unbonding Period. - -A secure light client must guarantee that all three components - -core verification, fork detection, and fork accountability - -each with their own synchrony assumptions and fault model, can execute -sequentially and to completion within the given Unbonding Period. - -TODO: define all the synchrony parameters used in the protocol and their -relation to the Unbonding Period. +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 index 0374b9c3c..5cf46b0b4 100644 --- a/spec/consensus/light-client/accountability.md +++ b/spec/consensus/light-client/accountability.md @@ -1,302 +1,3 @@ # 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 at most 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 more than 1/3 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 more than 1/3 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 at most 1/3 of the voting power is faulty, this misbehavior is detectable but will not lead to a safety violation. Thus, unless they have more than 1/3 (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 more than 1/3 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 more than 1/3 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 more than 1/3 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 more than 1/3 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 more than 1/3 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 more than 1/3 of the voting power -* a set F2 of faulty validators with at most 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 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 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? +Deprecated, please see [light-client/accountability](../../light-client/accountability.md). diff --git a/spec/consensus/light-client/detection.md b/spec/consensus/light-client/detection.md index d0a0ad1da..5c87562ba 100644 --- a/spec/consensus/light-client/detection.md +++ b/spec/consensus/light-client/detection.md @@ -1,3 +1,3 @@ # Detection -TODO +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 index 7d13d8871..1f0104a40 100644 --- a/spec/consensus/light-client/verification.md +++ b/spec/consensus/light-client/verification.md @@ -1,571 +1,3 @@ # 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](./../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. +Deprecated, please see [light-client/accountability](../../light-client/verification.md). diff --git a/spec/light-client/README.md b/spec/light-client/README.md new file mode 100644 index 000000000..78b4a2f2e --- /dev/null +++ b/spec/light-client/README.md @@ -0,0 +1,65 @@ +# Tendermint Light Client Protocol + +NOTE: This specification is under heavy development and is not yet complete nor +accurate. + +## Contents + +- [Motivation](#motivation) +- [Structure](#structure) +- [Core Verification](./verification.md) +- [Fork Detection](./detection.md) +- [Fork Accountability](./accountability.md) + +## Motivation + +The Tendermint Light Client is motivated by the need for a light weight protocol +to sync with a Tendermint blockchain, with the least processing necessary to +securely verify a recent state. The protocol consists of managing trusted validator +sets and trusted block headers, and is based primarily on checking hashes +and verifying Tendermint commit signatures. + +Motivating use cases include: + +- Light Node: a daemon that syncs a blockchain to the latest committed header by making RPC requests to full nodes. +- State Sync: a reactor that syncs a blockchain to a recent committed state by making P2P requests to full nodes. +- IBC Client: an ABCI application library that syncs a blockchain to a recent committed header by receiving proof-carrying +transactions from "IBC relayers", who make RPC requests to full nodes on behalf of the IBC clients. + +## Structure + +### Components + +The Tendermint Light Client consists of three primary components: + +- [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes +- [Fork Detection](./detection.md): talking to multiple peers to detect Byzantine behaviour +- [Fork Accountability](./accountability.md): analyzing Byzantine behaviour to hold validators accountable. + +While every light client must perform core verification and fork detection +to achieve their prescribed security level, fork accountability is expected to +be done by full nodes and validators, and is thus more accurately a component of +the full node consensus protocol, though it is included here since it is +primarily concerned with providing security to light clients. + +A schematic of the core verification and fork detection components in +a Light Node are depicted below. The schematic is quite similar for other use cases. +Note that fork accountability is not depicted, as it is the responsibility of the +full nodes. + +![Light Client Diagram](./assets/light-node-image.png). + +### Synchrony + +Light clients are fundamentally synchronous protocols, +where security is restricted by the interval during which a validator can be punished +for Byzantine behaviour. We assume here that such intervals have fixed and known minimal duration +referred to commonly as a blockchain's Unbonding Period. + +A secure light client must guarantee that all three components - +core verification, fork detection, and fork accountability - +each with their own synchrony assumptions and fault model, can execute +sequentially and to completion within the given Unbonding Period. + +TODO: define all the synchrony parameters used in the protocol and their +relation to the Unbonding Period. diff --git a/spec/light-client/accountability.md b/spec/light-client/accountability.md new file mode 100644 index 000000000..0374b9c3c --- /dev/null +++ b/spec/light-client/accountability.md @@ -0,0 +1,302 @@ +# 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 at most 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 more than 1/3 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 more than 1/3 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 at most 1/3 of the voting power is faulty, this misbehavior is detectable but will not lead to a safety violation. Thus, unless they have more than 1/3 (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 more than 1/3 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 more than 1/3 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 more than 1/3 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 more than 1/3 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 more than 1/3 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 more than 1/3 of the voting power +* a set F2 of faulty validators with at most 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 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 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/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/detection.md b/spec/light-client/detection.md new file mode 100644 index 000000000..d0a0ad1da --- /dev/null +++ b/spec/light-client/detection.md @@ -0,0 +1,3 @@ +# Detection + +TODO diff --git a/spec/light-client/verification.md b/spec/light-client/verification.md new file mode 100644 index 000000000..7d13d8871 --- /dev/null +++ b/spec/light-client/verification.md @@ -0,0 +1,571 @@ +# 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](./../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. From d8a2c8f6f1f35307ea3c8ce2aae46662a692d08a Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 2 Sep 2020 18:17:11 +0200 Subject: [PATCH 090/223] spec: remove evidences (#153) --- spec/abci/abci.md | 26 ++++++++++++--- spec/core/data_structures.md | 55 ------------------------------- spec/light-client/README.md | 2 +- spec/light-client/verification.md | 2 +- 4 files changed, 24 insertions(+), 61 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 10170c4e3..f6bdf8e96 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -96,6 +96,27 @@ Example: } ``` +## EvidenceType + +A part of Tendermint's security model is the use of evidence which serves as proof of +malicious behaviour by a network participant. It is the responsibility of Tendermint +to detect such malicious behaviour, to gossip this and commit it to the chain and once +verified by all validators to pass it on to the application through the ABCI. It is the +responsibility of the application then 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 [here](https://github.com/tendermint/spec/blob/master/spec/light-client/accountability.md) + ## Determinism ABCI applications must implement deterministic finite-state machines to be @@ -570,8 +591,7 @@ via light client. ### Evidence - **Fields**: - - `Type (string)`: Type of the evidence. A hierarchical path like - "duplicate/vote". + - `Type (EvidenceType)`: Type of the evidence. An enum of possible evidence's. - `Validator (Validator`: The offending validator - `Height (int64)`: Height when the offense occured - `Time (google.protobuf.Timestamp)`: Time of the block that was committed at the height that the offense occured @@ -618,8 +638,6 @@ via light client. unbonding period. `MaxAgeNumBlocks` is calculated by dividing the unboding period by the average block time (e.g. 2 weeks / 6s per block = 2d8h). - `MaxNum (uint32)`: The maximum number of evidence that can be committed to a single block - - `ProofTrialPeriod (int64)`: The duration in terms of blocks that an indicted node has to - provide proof of correctly executing a lock change in the event of amnesia evidence. ### ValidatorParams diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 1b7a9033f..ee7b4af83 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -250,61 +250,6 @@ Valid Duplicate Vote Evidence must adhere to the following rules: - Time must be equal to the block time -### AmensiaEvidence - -`AmnesiaEvidence` represents a validator that has incorrectly voted for another block in a -different round to the the block that the validator was previously locked on. This form -of evidence is generated differently from the rest. See this -[ADR](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-056-proving-amnesia-attacks.md) for more information. - -```go -type AmnesiaEvidence struct { - *PotentialAmnesiaEvidence - Polc *ProofOfLockChange -} -``` - -Valid Amnesia Evidence must adhere to the following rules: - -- Validator Address and Height must be the same and the Round must be different. - -- The BlockID's must be different and the BlockID of the first vote must not be nil. - -- The vote signature must be valid (using the chainID). - -- The evidence either must have a complete and valid `ProofOfLockChange` or have stood in the nodes evidence pool for the `ProofTrialPeriod`. - -- The validator must have been in the validator set. - -- Time must be equal to the corresponding block time. - -### LunaticValidatorEvidence - -`LunaticValidatorEvidence` represents a validator that has signed for an arbitrary application state. -This attack only applies to Light clients. - -```go -type LunaticValidatorEvidence struct { - Header *Header - Vote *Vote - InvalidHeaderField string - - Timestamp time.Time -} -``` - -Valid Lunatic Validator Evidence must adhere to the following rules: - -- Header must have an invalid field compared to the correctly derived header that the node has in either the `ValidatorHash`, `NextValidatorHash`, `ConsensusHash`, `AppHash`, or `LastResultsHash`. - -- The vote must be for the incorrect header (BlockID of the vote must match the hash of the header). - -- The vote must be correcly signed (using the chainID). - -- The validator must have been in a correct validator set within the bonding period. - -- Time must be equal to the corresponding block time of the header that the node has (not the incorrect header). - ## Validation Here we describe the validation rules for every element in a block. diff --git a/spec/light-client/README.md b/spec/light-client/README.md index 78b4a2f2e..3dfba8c2b 100644 --- a/spec/light-client/README.md +++ b/spec/light-client/README.md @@ -1,4 +1,4 @@ -# Tendermint Light Client Protocol +# Light Client Protocol NOTE: This specification is under heavy development and is not yet complete nor accurate. diff --git a/spec/light-client/verification.md b/spec/light-client/verification.md index 7d13d8871..9166923e8 100644 --- a/spec/light-client/verification.md +++ b/spec/light-client/verification.md @@ -21,7 +21,7 @@ Given a known bound `TRUSTED_PERIOD`, and a block `b` with header `h` generated 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](./../bft-time.md). In this note, we assume that clocks of correct processes +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`, From dd325bb1919802073eaaa080a3886719b63abaef Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 10 Sep 2020 10:41:28 +0200 Subject: [PATCH 091/223] add a stale bot (#134) --- .github/workflows/stale.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..ba3701d60 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,18 @@ +name: "Close stale pull requests" +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-pr-message: "This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions." + days-before-stale: 30 + days-before-close: 6 + exempt-pr-labels: "pinned, proposal" From b74b1c2b68eabc3fa4ca8fdf0dc732e75cdac11a Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Thu, 10 Sep 2020 12:56:15 +0200 Subject: [PATCH 092/223] 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 --- .markdownlint.yml | 1 + rust-spec/lightclient/README.md | 92 ++ .../lightclient/attacks/evidence-handling.md | 219 ++++ rust-spec/lightclient/detection/README.md | 69 + rust-spec/lightclient/detection/detection.md | 696 ++++++++++ .../lightclient/detection/discussions.md | 178 +++ .../lightclient/detection/draft-functions.md | 289 ++++ .../detection/req-ibc-detection.md | 345 +++++ rust-spec/lightclient/experiments.png | Bin 0 -> 83681 bytes .../verification/001bmc-apalache.csv | 49 + .../verification/Blockchain_A_1.tla | 171 +++ .../verification/Lightclient_A_1.tla | 440 +++++++ .../verification/MC4_3_correct.tla | 15 + .../lightclient/verification/MC4_3_faulty.tla | 15 + .../lightclient/verification/MC4_4_faulty.tla | 15 + .../verification/MC4_5_correct.tla | 15 + .../lightclient/verification/MC4_5_faulty.tla | 15 + .../lightclient/verification/MC4_6_faulty.tla | 15 + .../lightclient/verification/MC4_7_faulty.tla | 15 + .../verification/MC5_5_correct.tla | 15 + .../lightclient/verification/MC5_5_faulty.tla | 15 + .../lightclient/verification/MC5_7_faulty.tla | 15 + .../lightclient/verification/MC7_5_faulty.tla | 15 + .../lightclient/verification/MC7_7_faulty.tla | 15 + .../lightclient/verification/verification.md | 1162 +++++++++++++++++ 25 files changed, 3891 insertions(+) create mode 100644 rust-spec/lightclient/README.md create mode 100644 rust-spec/lightclient/attacks/evidence-handling.md create mode 100644 rust-spec/lightclient/detection/README.md create mode 100644 rust-spec/lightclient/detection/detection.md create mode 100644 rust-spec/lightclient/detection/discussions.md create mode 100644 rust-spec/lightclient/detection/draft-functions.md create mode 100644 rust-spec/lightclient/detection/req-ibc-detection.md create mode 100644 rust-spec/lightclient/experiments.png create mode 100644 rust-spec/lightclient/verification/001bmc-apalache.csv create mode 100644 rust-spec/lightclient/verification/Blockchain_A_1.tla create mode 100644 rust-spec/lightclient/verification/Lightclient_A_1.tla create mode 100644 rust-spec/lightclient/verification/MC4_3_correct.tla create mode 100644 rust-spec/lightclient/verification/MC4_3_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC4_4_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC4_5_correct.tla create mode 100644 rust-spec/lightclient/verification/MC4_5_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC4_6_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC4_7_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC5_5_correct.tla create mode 100644 rust-spec/lightclient/verification/MC5_5_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC5_7_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC7_5_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC7_7_faulty.tla create mode 100644 rust-spec/lightclient/verification/verification.md diff --git a/.markdownlint.yml b/.markdownlint.yml index 9f8a8d689..6b4e78a33 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,4 +1,5 @@ default: true +MD001: false MD007: { indent: 4 } MD013: false MD024: { siblings_only: true } diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md new file mode 100644 index 000000000..a0e14e380 --- /dev/null +++ b/rust-spec/lightclient/README.md @@ -0,0 +1,92 @@ +# 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 three components: + +- Commit Verification - verify signed headers and associated validator set changes from a single full node +- Fork Detection - verify commits across multiple full nodes and detect conflicts (ie. the existence of forks) +- Fork Accountability - given a fork, which validators are responsible for it. + +## Commit Verification + +The [English specification](verification/verification.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.md#lcv-dist-safe1) and +[LCV-DIST-LIVE.1](https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification.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](verification/Lightclient_A_1.tla) is a formal description of the +commit verification protocol executed by a client, including the safety and +liveness properties, which can be model checked with Apalache. + +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, and correctness of the primary node: + +```tla +AllNodes == {"n1", "n2", "n3", "n4"} +TRUSTED_HEIGHT == 1 +TARGET_HEIGHT == 3 +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +IS_PRIMARY_CORRECT == FALSE +``` + +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 001bmc-apalache.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 . +``` + +The following table summarizes the experimental results. 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) + +## Fork Detection + +This is a work-in-progress draft. + +The [English specification](detection/detection.md) defines blockchain forks and describes +the problem of a light client detecting them from communication with a network +of full nodes, where at least one is correct. + +There is no TLA+ yet. + +## Fork Accountability + +There is no English specification yet. TODO: Jovan's work? + +TODO: there is a WIP [TLA+ +specification](https://github.com/informalsystems/verification/pull/13) in the +verification repo that should be moved over here. diff --git a/rust-spec/lightclient/attacks/evidence-handling.md b/rust-spec/lightclient/attacks/evidence-handling.md new file mode 100644 index 000000000..4b7d81919 --- /dev/null +++ b/rust-spec/lightclient/attacks/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/rust-spec/lightclient/detection/README.md b/rust-spec/lightclient/detection/README.md new file mode 100644 index 000000000..8f92bdaa2 --- /dev/null +++ b/rust-spec/lightclient/detection/README.md @@ -0,0 +1,69 @@ + +# 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/rust-spec/lightclient/detection/detection.md b/rust-spec/lightclient/detection/detection.md new file mode 100644 index 000000000..4faa0f4ca --- /dev/null +++ b/rust-spec/lightclient/detection/detection.md @@ -0,0 +1,696 @@ +# Fork detector + +> ***This an unfinished draft. Comments are welcome!*** + +A detector (or detector for short) is a mechanism that expects as +input a header with some height *h*, connects to different Tendermint +full nodes, requests the header of height *h* from them, and then +cross-checks the headers and the input header. + +There are two foreseeable use cases: + +1) strengthen the light client: If a light client accepts a header +*hd* (after performing skipping or sequential verification), it can +use the detector to probe the system for conflicting headers and +increase the trust in *hd*. Instead of communicating with a single +full node, communicating with several full nodes shall increase the +likelihood to be aware of a fork (see [[accountability]] for +discussion about forks) in case there is one. + +2) to support fork accountability: In the case when more than 1/3 of +the voting power is held by faulty validators, faulty nodes may +generate two conflicting headers for the same height. The goal of the +detector is to learn about the conflicting headers by probing +different full nodes. Once a detector has two conflicting headers, +these headers are evidence of misbehavior. A natural extension is to +use the detector within a monitor process (on a full node) that calls +the detector on a sample (or all) headers (in parallel). (If the +sample is chosen at random, this adds a level of probabilistic +reasoning.) If conflicting headers are found, they are evidence that +can be used for punishing processes. + +In this document we will focus onn strengthening the light client, and +leave other uses of the detection mechanism (e.g., when run on a full +node) to the future. + +## Context of this document + +The light client verification specification [[verification]] is +designed for the Tendermint failure model (1/3 assumption) +[TMBC-FM-2THIRDS]. 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 +this assumption is violated, the light client can be fooled to trust a +header that was not generated by Tendermint consensus. + +This specification, the fork detector, is a "second line of defense", +in case the 1/3 assumption is violated. Its goal is to detect fork (conflicting headers) 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 header, the light client first does [verification][verification] +with the primary, and then cross-checks the header with the +secondaries using this specification. + +### Tendermint Consensus and Forks + +#### **[TMBC-GENESIS.1]** + +Let *Genesis* be the agreed-upon initial block (file). + +#### **[TMBC-FUNC.1]** + +> **TODO** be more precise. +2/3 of b.NextV = c.Val signed c. For now +> the following will do: + +Let b and c be two light blocks +with *b.Header.Height + 1 = c.Header.Height*. We define **signs(b,c)** + iff `ValidAndVerified(b, c)` + +> **TODO** be more precise. +1/3 of b.NextV signed c. For now +> the following will do: + +Let *b* and *c* be two light blocks. We define **supports(b,c,t)** + iff `ValidAndVerified(b, c)` at time *t* + +> 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-ROOT.1]** + +Let *b* and *c* be light blocks. We define *skip-root(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*. + +> **TODO** In the above we might use a sequence of times t(i). Not sure. +> **TODO:** I believe the following definition +> corresponds to **Slashable fork** in +> [forks][tendermintfork]. Please confirm! +> Observe that sign-skip-match is even defined if there is a fork on +> the chain. + +#### **[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 + +- *sequ-rooted(a)* and + +- *b.Header.Height = c.Header.Height* and +- *skip-root(a,b,t)* +- *skip-root(a,c,t)* + +implies *b = c*. + +#### **[TMBC-SIGN-FORK.1]** + +If there exists three light blocks a, b, and c, with +*sign-skip-match(a,b,c,t) = +false* then we have a *slashable fork*. +We call *a* a bifurcation block of the fork. +We say we have **a fork at height** *b.Header.Height*. + +> The lightblock *a* need not be unique, that is, a there may be +> several blocks that satisfy the above requirement for blocks *b* and +> *c*. +> **TODO:** I think the following definition is +> the intuition behind **main chain forks** +> in the document on [forks][tendermintfork]. However, main chain +> forks were defined more operational "forks that are observed by +> full nodes as part of normal Tendermint consensus protocol". Please +> confirm! + +#### **[TMBC-SIGN-UNIQUE.1]** + +Let *b* and *c* be light blocks, we define *sign-unique(b,c) = +true* iff + +- *b.Header.Height = c.Header.Height* and +- *sequ-rooted(b)* and +- *sequ-rooted(c)* + +implies *b = c*. + +If there exists two light blocks b and c, with *sign-unique(b,c) = +false* then we have a *fork on the chain*. + +> The following captures what I believe is called a light client fork +> in our discussions. There is no fork on the chain up to the height +> of block b. However, c is of that height, is different, and passes skipping +> verification +> Observe that a light client fork is a special case of a slashable +> fork. + +#### **[TMBC-LC-FORK.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's also define bogus blocks that have no support. +> Observe that bogus is even defined if there is a fork on the chain. +> 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-root(a,b,t) = false* + +> Relation to [fork accountability][accountability]: F1, F2, and F3 +> (equivocation, amnesia, back to the past) can lead to a fork on the +> chain and to a light client fork. +> F4 and F5 (phantom validators, lunatic) cannot lead to a fork on the +> chain but to a light client +> fork if *t+1 < f < 2t+1*. +> F4 and F5 can also lead to bogus blocks + +### Informal Problem statement + +> We put tags to informal problem statements as there is no sequential +> specification. + +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. + +#### **[LCD-IP-STATE.1]** + +The detector works on a LightStore that contains LightBlocks in one of +the state `StateUnverified`, `StateVerified`, `StateFailed`, or +`StateTrusted`. + +#### **[LCD-IP-Q.1]** + +Whenever the light client verifier performs `VerifyToTarget` with the +primary and returns with +`(lightStore, ResultSuccess)`, the + detector should query the secondaries by calling `FetchLightBlock` for height + *LightStore.LatestVerified().Height* remotely. +Then, +the detector returns the set of all headers *h'* downloaded from +secondaries that satisfy + +- *h'* is different from *LightStore.LatestVerified()* +- *h'* is a (light) fork. + +#### **[LCD-IP-PEERSET.1]** + +Whenever the detector observes *detectable misbehavior* of a full node +from the set of Secondaries it should be replaced by a fresh full +node. (A full node that has not been primary or secondary +before). Detectable misbehavior can be + +- a timeout +- The case where *h'* is different +from *LightStore.LatestVerified()* but *h'* is not a fork, that is, if +*h'* is bogus. In other words, the +secondary cannot provide a sequence of light blocks that constitutes +proof of *h'*. + +## 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. + +**Remark:** Check whether [LCD-A-CorrFull.1] is not needed in the end because +the verification conditions [LCD-DIST-*] have preconditions on specific +cases where primary and/or secondaries are faulty. + +#### **[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. + +## (Distributed) Problem statement + +> As the fork detector from the beginning is there to reduce the +> impact of faulty nodes, and faulty nodes imply that there is a +> distributed system, there is no sequential specification. + +The detector gets as input a lightstore *lightStore*. +Let *h-verified = lightStore.LatestVerified().Height* and + *h-trust=lightStore.LatestTrusted().Height* (see + [LCV-DATA-LIGHTSTORE]). +It queries the secondaries for headers at height *h-verified*. +The detector returns a set *PoF* of *Proof of Forks*, and should satisfy the following + temporal formulas: + +#### **[LCD-DIST-INV.1]** + +If there is no fork at height *h-verified* ([TMBC-SIGN-FORK.1]), +then the detector should return the empty set. + +> If the empty set is returned the supervisor will change the state of +> the header at height *h-verified* to *stateTrusted*. + +#### **[LCD-DIST-LIVE-FORK.1]** + +If there is a fork at height *h-verified*, and +there are two correct full nodes *i* and *j* that are + +- on different branches, and +- *i* is primary and +- *j* is secondary, + +then the detector eventually outputs the fork. + +#### **[LCD-DIST-LIVE-FORK-FAULTY.1]** + +If there is a fork at height *h-verified*, and +there is a correct secondary that is on a different branch than the +primary reported, +then the detector eventually outputs the fork. + +> The above property is quite operational ("Than the primary +> reported"), but it captures quite closely the requirement. As the +> fork detector only makes sense in a distributed setting, and does +> not have a sequential specification, less "pure" +> specification are acceptable. +> These properties capture the following operational requirement: +> +> **[LCD-REQ-REP.1]** +> If the detector observes two conflicting headers for height *h*, +> it should try to verify both. If both are verified it should report evidence. +> If the primary reports header *h* and a secondary reports header *h'*, +> and if *h'* can be verified based on common root of trust, then +> evidence should be generated; +> By verifying we mean calling `VerifyToTarget` from the +> [[verification]] specification. + +## Definitions + +- 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*. +- A set *FaultyNodes* of nodes that the light client suspects of being faulty; it is initially empty + +- *Lightstore* as defined in the [verification specification][verification]. + +#### **[LCD-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* + +> The following invariant is very useful for reasoning, and underlies +> many intuition when we + +#### **[LCD-INV-TRUSTED-AGREED.1]:** + +It is always the case the light client has downloaded a lightblock for height +*lightStore.LatestTrusted().Height* +from each of the current primary and the secondary, that all reported +the identical lightblock for that height. + +> In the above, I guess "the identical" might be replaced with "a +> matching" to cover commits that might be different. +> The above requires us that before we pick a new secondary, we have to +> query the secondary for the header of height +> *lightStore.LatestTrusted().Height*. + +## Solution + +### Data Structures + +Lightblocks and LightStores are +defined at [LCV-DATA-LIGHTBLOCK.1] and [LCV-DATA-LIGHTSTORE.1]. See the [verification specification][verification] for details. + +> The following data structure [LCV-DATA-POF.1] +> defines a **proof of fork**. Following +> [TMBC-SIGN-FORK.1], we require two blocks *b* and *c* for the same +> height that can both be verified from a common root block *a* (using +> the skipping or the sequential method). +> [LCV-DATA-POF.1] mirrors the definition [TMBC-SIGN-FORK.1]: +> *TrustedBlock* corresponds to *a*, and *PrimaryTrace* and *SecondaryTrace* +> are traces to two blocks *b* and *c*. The traces establish that both +> *skip-root(a,b,t)* and *skip-root(a,c,t)* are satisfied. + +#### **[LCV-DATA-POF.1]** + +```go +type LightNodeProofOfFork struct { + TrustedBlock LightBlock + PrimaryTrace []LightBlock + SecondaryTrace []LightBlock +} +``` + + + + + + + + + +#### **[LCV-DATA-POFSTORE.1]** + +Proofs of Forks are stored in a structure which stores all proofs +generated during detection. + +```go +type PoFStore struct { + ... +} +``` + +In additions to the functions defined in +the [verification specification][verification], the +LightStore exposes the following function + +#### **[LCD-FUNC-SUBTRACE.1]:** + +```go +func (ls LightStore) Subtrace(from int, to int) LightStore +``` + +- Expected postcondition + - returns a lightstore that contains all lightblocks *b* from *ls* + that satisfy: *from < b.Header.Height <= to* + +---- + +### Inter Process Communication + +```go +func FetchLightBlock(peer PeerID, height Height) LightBlock +``` + +See the [verification specification][verification] for details. + +#### **[LCD-FUNC-SUBMIT.1]:** + +```go +func SubmitProofOfFork(pof LightNodeProofOfFork) Result +``` + +**TODO:** finalize what this should do, and what detail of + specification we need. + +- Implementation remark +- Expected precondition + - none +- Expected postcondition + - submit evidence to primary and the secondary in *pof*, that is, + to + - `pof.PrimaryTrace[1].Provider` + - `pof.SecondaryTrace[1].Provider` + - **QUESTION** minimize data? We could submit to the primary only + the trace of the secondary, and vice versa. Do we need to spell + that out here? (Also, by [LCD-INV-TRUSTED-AGREED.1], we do not + need to send `pof.TrustedBlock`) + - **FUTURE WORK:** we might send *pof* to primary or all + secondaries or broadcast to all full nodes. However, in evidence + detection this might need that a full node has to check a *pof* + where both traces are not theirs. This leads to more complicated + logic at the full node, which we do not need right now. + +- Error condition + - none + +### Auxiliary Functions (Local) + +#### **[LCD-FUNC-CROSS-CHECK.1]:** + +```go +func CrossCheck(peer PeerID, testedLB LightBlock) (result) { + sh := FetchLightBlock(peer, testedLB.Height); + // as the check below only needs the header, it is sufficient + // to download the header rather than the LighBlock + if testedLB.Header == sh.Header { + return OK + } + else { + return DoesNotMatch + } +} +``` + +- Implementation remark + - download block and compare to previously downloaded one. +- Expected precondition +- Expected postcondition +- Error condition + +#### **[LCD-FUNC-REPLACE-PRIMARY.1]:** + +```go +Replace_Primary() +``` + +**TODO:** formalize conditions + +- Implementation remark + - the primary is replaced by a secondary, and lightblocks above + trusted blocks are removed + - to maintain a constant size of secondaries, at this point we + might need to + - pick a new secondary *nsec* + - maintain [LCD-INV-TRUSTED-AGREED.1], that is, + - call `CrossCheck(nsec,lightStore.LatestTrusted()`. + If it matches we are OK, otherwise + - we repeat with another full node as new + secondary candidate + - **FUTURE:** try to do fork detection from some possibly old + lightblock in store. (Might be the approach for the + light node that assumes to be connected to correct + full nodes only from time to time) + +- Expected precondition + - *FullNodes* is nonempty + +- Expected postcondition + - *primary* is moved to *FaultyNodes* + - all lightblocks with height greater than + lightStore.LatestTrusted().Height are removed from *lightStore*. + - a secondary *s* is moved from *Secondaries* to primary + +> this ensures that *s* agrees on the Last Trusted State + +- Error condition + - if precondition is violated + +#### **[LCD-FUNC-REPLACE-SECONDARY.1]:** + +```go +Replace_Secondary(addr Address) +``` + +**TODO:** formalize conditions + +- Implementation remark + - maintain [LCD-INV-TRUSTED-AGREED.1], that is, + - call `CrossCheck(nsec,lightStore.LatestTrusted()`. + If it matches we are OK, otherwise + - we might just repeat with another full node as new secondary + - **FUTURE:** try to do fork detection from some possibly old + lightblock in store. (Might be the approach for the + light node that assumes to be connected to correct + full nodes only from time to time) + +- Expected precondition + - *FullNodes* is nonempty +- Expected postcondition + - addr is moved from *Secondaries* to *FaultyNodes* + - an address *a* is moved from *FullNodes* to *Secondaries* +- Error condition + - if precondition is violated + +### From the verifier + +```go +func VerifyToTarget(primary PeerID, lightStore LightStore, + targetHeight Height) (LightStore, Result) +``` + +See the [verification specification][verification] for details. + +## Protocol + +### Shared data of the light client + +- a pool of full nodes *FullNodes* that have not been contacted before +- peer set called *Secondaries* +- primary +- lightStore + +### Outline + +The problem laid out is solved by calling the function `ForkDetector` + with a lightstore that contains a light block that has just been + verified by the verifier. + +- **TODO:** 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. + +- **TODO:** clarify EXPIRED case. Can we always punish? Can we give sufficient + conditions. + +### Fork Detector + +#### **[LCD-FUNC-DETECTOR.1]:** + +```go +func ForkDetector(ls LightStore, PoFs PoFStore) +{ + testedLB := LightStore.LatestVerified() + for i, secondary range Secondaries { + if OK = CrossCheck(secondary, testedLB) { + // header matches. we do nothing. + } + else { + // [LCD-REQ-REP] + // header does not match. there is a situation. + // we try to verify sh by querying s + // we set up an auxiliary lightstore with the highest + // trusted lightblock and the lightblock we want to verify + auxLS.Init + auxLS.Update(LightStore.LatestTrusted(), StateVerified); + auxLS.Update(sh,StateUnverified); + LS,result := VerifyToTarget(secondary, auxLS, sh.Header.Height) + if (result = ResultSuccess || result = EXPIRED) { + // we verified header sh which is conflicting to hd + // there is a fork on the main blockchain. + // If return code was EXPIRED it might be too late + // to punish, we still report it. + pof = new LightNodeProofOfFork; + pof.TrustedBlock := LightStore.LatestTrusted() + pof.PrimaryTrace := + LightStore.Subtrace(LightStore.LatestTrusted().Height, + testedLB.Height); + pof.SecondaryTrace := + auxLS.Subtrace(LightStore.LatestTrusted().Height, + testedLB.Height); + PoFs.Add(pof); + } + else { + // secondary might be faulty or unreachable + // it might fail to provide a trace that supports sh + // or time out + Replace_Secondary(secondary) + } + } + } + return PoFs +} +``` + +**TODO:** formalize conditions + +- Expected precondition + - Secondaries initialized and non-empty + - `PoFs` initialized and empty + - *lightStore.LatestTrusted().Height < lightStore.LatestVerified().Height* +- Expected postcondition + - satisfies [LCD-DIST-INV.1], [LCD-DIST-LIFE-FORK.1] + - removes faulty secondary if it reports wrong header + - **TODO** submit proof of fork +- Error condition + - fails if precondition is violated + - fails if [LCV-INV-TP] is violated (no trusted header within + trusting period + +---- + +## Correctness arguments + +#### Argument for [LCD-DIST-INV] + +**TODO** + +#### Argument for [LCD-DIST-LIFE-FORK] + +**TODO** + +# References + +> links to other specifications/ADRs this document refers to + +[[verification]] The specification of the light client verification. + +[[tendermintfork]] Tendermint fork detection and accountability + +[[accountability]] Fork accountability + +[TMBC-FM-2THIRDS-linkVDD]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#**[TMBC-FM-2THIRDS-link]**: + +[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/verification.md + +[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md + +[blockchain]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md + +[lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md + +[verificationVDD]: https://github.com/informalsystems/VDD/blob/master/lightclient/failuredetector.md + +[verification]: https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification.md + +[accountability]: https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md + +[tendermintfork]: https://docs.google.com/document/d/1xjyp5JOPt7QfHem1AFEaowBH2Plk0IHACWtYFXFvO7E/edit#heading=h.th2369ptc2ve diff --git a/rust-spec/lightclient/detection/discussions.md b/rust-spec/lightclient/detection/discussions.md new file mode 100644 index 000000000..82702dd69 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/draft-functions.md b/rust-spec/lightclient/detection/draft-functions.md new file mode 100644 index 000000000..c56594a53 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/req-ibc-detection.md b/rust-spec/lightclient/detection/req-ibc-detection.md new file mode 100644 index 000000000..439ca26b6 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/experiments.png b/rust-spec/lightclient/experiments.png new file mode 100644 index 0000000000000000000000000000000000000000..94166ffa31ee93970b8b97cafe375ba580c1d319 GIT binary patch literal 83681 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 z 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 + +(* 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/rust-spec/lightclient/verification/Lightclient_A_1.tla b/rust-spec/lightclient/verification/Lightclient_A_1.tla new file mode 100644 index 000000000..2be9b8788 --- /dev/null +++ b/rust-spec/lightclient/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: + * + * 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 == <> + +(******************* 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/rust-spec/lightclient/verification/MC4_3_correct.tla b/rust-spec/lightclient/verification/MC4_3_correct.tla new file mode 100644 index 000000000..951a471da --- /dev/null +++ b/rust-spec/lightclient/verification/MC4_3_correct.tla @@ -0,0 +1,15 @@ +---------------------------- 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 :-) +IS_PRIMARY_CORRECT == TRUE + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================== diff --git a/rust-spec/lightclient/verification/MC4_3_faulty.tla b/rust-spec/lightclient/verification/MC4_3_faulty.tla new file mode 100644 index 000000000..c711ee9b8 --- /dev/null +++ b/rust-spec/lightclient/verification/MC4_3_faulty.tla @@ -0,0 +1,15 @@ +---------------------------- 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 :-) +IS_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================== diff --git a/rust-spec/lightclient/verification/MC4_4_faulty.tla b/rust-spec/lightclient/verification/MC4_4_faulty.tla new file mode 100644 index 000000000..8b8d25ece --- /dev/null +++ b/rust-spec/lightclient/verification/MC4_4_faulty.tla @@ -0,0 +1,15 @@ +---------------------------- 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 :-) +IS_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================== diff --git a/rust-spec/lightclient/verification/MC4_5_correct.tla b/rust-spec/lightclient/verification/MC4_5_correct.tla new file mode 100644 index 000000000..04489ec7f --- /dev/null +++ b/rust-spec/lightclient/verification/MC4_5_correct.tla @@ -0,0 +1,15 @@ +------------------------- 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 :-) +IS_PRIMARY_CORRECT == TRUE + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/MC4_5_faulty.tla b/rust-spec/lightclient/verification/MC4_5_faulty.tla new file mode 100644 index 000000000..11dbd2ec6 --- /dev/null +++ b/rust-spec/lightclient/verification/MC4_5_faulty.tla @@ -0,0 +1,15 @@ +------------------------- 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_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, + latestVerified, nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/MC4_6_faulty.tla b/rust-spec/lightclient/verification/MC4_6_faulty.tla new file mode 100644 index 000000000..d0d98444f --- /dev/null +++ b/rust-spec/lightclient/verification/MC4_6_faulty.tla @@ -0,0 +1,15 @@ +------------------------- 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_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/MC4_7_faulty.tla b/rust-spec/lightclient/verification/MC4_7_faulty.tla new file mode 100644 index 000000000..60dcd3c5d --- /dev/null +++ b/rust-spec/lightclient/verification/MC4_7_faulty.tla @@ -0,0 +1,15 @@ +------------------------- 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 :-) +IS_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/MC5_5_correct.tla b/rust-spec/lightclient/verification/MC5_5_correct.tla new file mode 100644 index 000000000..926501761 --- /dev/null +++ b/rust-spec/lightclient/verification/MC5_5_correct.tla @@ -0,0 +1,15 @@ +------------------------- 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 :-) +IS_PRIMARY_CORRECT == TRUE + +VARIABLES + state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, + latestVerified, nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/MC5_5_faulty.tla b/rust-spec/lightclient/verification/MC5_5_faulty.tla new file mode 100644 index 000000000..abf5e8c72 --- /dev/null +++ b/rust-spec/lightclient/verification/MC5_5_faulty.tla @@ -0,0 +1,15 @@ +------------------------- 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 :-) +IS_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, + latestVerified, nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/MC5_7_faulty.tla b/rust-spec/lightclient/verification/MC5_7_faulty.tla new file mode 100644 index 000000000..5d3d265ab --- /dev/null +++ b/rust-spec/lightclient/verification/MC5_7_faulty.tla @@ -0,0 +1,15 @@ +------------------------- 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 :-) +IS_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, + latestVerified, nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/MC7_5_faulty.tla b/rust-spec/lightclient/verification/MC7_5_faulty.tla new file mode 100644 index 000000000..ae8af692a --- /dev/null +++ b/rust-spec/lightclient/verification/MC7_5_faulty.tla @@ -0,0 +1,15 @@ +------------------------- 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 :-) +IS_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, + latestVerified, nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/MC7_7_faulty.tla b/rust-spec/lightclient/verification/MC7_7_faulty.tla new file mode 100644 index 000000000..84065214e --- /dev/null +++ b/rust-spec/lightclient/verification/MC7_7_faulty.tla @@ -0,0 +1,15 @@ +------------------------- 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 :-) +IS_PRIMARY_CORRECT == FALSE + +VARIABLES + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + now, blockchain, Faulty + +INSTANCE Lightclient_A_1 +============================================================================ diff --git a/rust-spec/lightclient/verification/verification.md b/rust-spec/lightclient/verification/verification.md new file mode 100644 index 000000000..db68c74db --- /dev/null +++ b/rust-spec/lightclient/verification/verification.md @@ -0,0 +1,1162 @@ +# 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 + NextValidators ValidatorSet + Provider PeerID +} +``` + +#### **[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. + +```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. + +```go +func (ls LightStore) LatestVerified() LightBlock +``` + +- Expected postcondition + - returns the highest light block whose state is `StateVerified` + or `StateTrusted` + +#### **[LCV-FUNC-UPDATE.1]** + +```go +func (ls LightStore) Update(lightBlock LightBlock, verfiedState VerifiedState) +``` + +- Expected postcondition + - The state of the LightBlock is set to *verifiedState*. + +> The following function is used only in the detector specification +> listed here for completeness. + +```go +func (ls LightStore) LatestTrusted() LightBlock +``` + +- Expected postcondition + - returns the highest light block that has been verified and + checked by the detector. + +### 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 == OK { + lightStore.Update(current, StateVerified) + } + else if 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. + } + 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 `OK`: + - 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 `CANNOT_VERIFY` 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 `OK`, 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 `OK`, 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 From 606abc7fc008d794f38c500c10db7930113c2986 Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Thu, 10 Sep 2020 13:03:50 +0200 Subject: [PATCH 093/223] 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 --- rust-spec/fastsync/README.md | 50 + rust-spec/fastsync/Tinychain.tla | 113 ++ rust-spec/fastsync/fastsync.md | 1216 +++++++++++++++++ rust-spec/fastsync/fastsync.tla | 825 +++++++++++ rust-spec/fastsync/scheduler.tla | 606 ++++++++ .../fastsync/verification/001bmc-apalache.csv | 13 + .../fastsync/verification/002tlc-tlc.csv | 9 + .../verification/MC-CorrectBlocksInv.cfg | 10 + .../MC-CorrectNeverSuspectedInv.cfg | 10 + .../fastsync/verification/MC-Sync1AsInv.cfg | 10 + .../fastsync/verification/MC-Sync2AsInv.cfg | 10 + .../fastsync/verification/MC-Sync3AsInv.cfg | 10 + .../verification/MC-SyncFromCorrectInv.cfg | 10 + .../fastsync/verification/MC-Termination.cfg | 10 + .../verification/MC-TerminationByTO.cfg | 10 + .../verification/MC-TerminationCorr.cfg | 10 + rust-spec/fastsync/verification/MC_1_0_4.tla | 17 + rust-spec/fastsync/verification/MC_1_1_4.tla | 15 + rust-spec/fastsync/verification/MC_1_2_4.tla | 15 + rust-spec/fastsync/verification/MC_1_2_5.tla | 15 + rust-spec/fastsync/verification/MC_1_3_5.tla | 15 + rust-spec/fastsync/verification/MC_2_0_4.tla | 17 + rust-spec/fastsync/verification/Tinychain.tla | 110 ++ .../verification/fastsync_apalache.tla | 822 +++++++++++ 24 files changed, 3948 insertions(+) create mode 100644 rust-spec/fastsync/README.md create mode 100644 rust-spec/fastsync/Tinychain.tla create mode 100644 rust-spec/fastsync/fastsync.md create mode 100644 rust-spec/fastsync/fastsync.tla create mode 100644 rust-spec/fastsync/scheduler.tla create mode 100644 rust-spec/fastsync/verification/001bmc-apalache.csv create mode 100644 rust-spec/fastsync/verification/002tlc-tlc.csv create mode 100644 rust-spec/fastsync/verification/MC-CorrectBlocksInv.cfg create mode 100644 rust-spec/fastsync/verification/MC-CorrectNeverSuspectedInv.cfg create mode 100644 rust-spec/fastsync/verification/MC-Sync1AsInv.cfg create mode 100644 rust-spec/fastsync/verification/MC-Sync2AsInv.cfg create mode 100644 rust-spec/fastsync/verification/MC-Sync3AsInv.cfg create mode 100644 rust-spec/fastsync/verification/MC-SyncFromCorrectInv.cfg create mode 100644 rust-spec/fastsync/verification/MC-Termination.cfg create mode 100644 rust-spec/fastsync/verification/MC-TerminationByTO.cfg create mode 100644 rust-spec/fastsync/verification/MC-TerminationCorr.cfg create mode 100644 rust-spec/fastsync/verification/MC_1_0_4.tla create mode 100644 rust-spec/fastsync/verification/MC_1_1_4.tla create mode 100644 rust-spec/fastsync/verification/MC_1_2_4.tla create mode 100644 rust-spec/fastsync/verification/MC_1_2_5.tla create mode 100644 rust-spec/fastsync/verification/MC_1_3_5.tla create mode 100644 rust-spec/fastsync/verification/MC_2_0_4.tla create mode 100644 rust-spec/fastsync/verification/Tinychain.tla create mode 100644 rust-spec/fastsync/verification/fastsync_apalache.tla diff --git a/rust-spec/fastsync/README.md b/rust-spec/fastsync/README.md new file mode 100644 index 000000000..9e2f91796 --- /dev/null +++ b/rust-spec/fastsync/README.md @@ -0,0 +1,50 @@ +# Fast Sync Subprotocol Specification + +This directory contains English and TLA+ specifications for the FastSync +protocol as it is currently implemented in the Tendermint Core codebase. + +## English Specification + +The [English Specification](fastsync.md) provides a detailed description of the +fast sync problem and the properties a correct protocol must satisfy. It also +includes a detailed description of the protocol as currently implemented in Go, +and an anlaysis of the implementation with respect to the properties. + +It was found that the current implementation does not satisfy certain +properties, and is therefore not a correct solution to the fast sync problem. +The issue discovered holds for all previous implementations of the protocol. A +fix is described which is straight forward to implement. + +## TLA+ Specification + +Two TLA+ specifications are provided: a high level [specification +of the protocol](fastsync.tla) and a low level [specification of the scheduler +component of the implementation](scheduler.tla). Both specifications contain +properties that may be checked by the TLC model checker, though only for small +values of the relevant parameters. + +We will continue to refine these specifications in our research work, +to deduplicate +the redundancies between them, improve their utility to researchers and +engineers, and to improve their verifiability. For now, they provide a complete +description of the fast sync protocol in TLA+; especially the +[scheduler.tla](scheduler.tla), which maps very closely to the current +implementation of the [scheduler in Go](https://github.com/tendermint/tendermint/blob/master/blockchain/v2/scheduler.go). + +The [scheduler.tla](scheduler.tla) can be model checked in TLC with the following +parameters: + +- Constants: + - numRequests <- 2 + - PeerIDs <- 0..2 + - ultimateHeight <- 3 +- Invariants: + - TypeOK +- Properties: + - TerminationWhenNoAdvance + - TerminationGoodPeers + - TerminationAllCases +- Proofs that properties are not vacuously true: + - TerminationGoodPeersPre + - TerminationAllCases + - SchedulerIncreasePre diff --git a/rust-spec/fastsync/Tinychain.tla b/rust-spec/fastsync/Tinychain.tla new file mode 100644 index 000000000..26200c4f6 --- /dev/null +++ b/rust-spec/fastsync/Tinychain.tla @@ -0,0 +1,113 @@ +-------------------------- MODULE Tinychain ---------------------------------- +(* A very abstract model of Tendermint blockchain. Its only purpose is to highlight + the relation between validator sets, next validator sets, and last commits. + *) + +EXTENDS Integers + +\* type annotation +a <: b == a + +\* the type of validator sets, e.g., STRING +VST == STRING + +\* LastCommit type. +\* It contains: +\* 1) the flag of whether the block id equals to the hash of the previous block, and +\* 2) the set of the validators who have committed the block. +\* In the implementation, blockId is the hash of the previous block, which cannot be forged. +\* We abstract block id into whether it equals to the hash of the previous block or not. +LCT == [blockIdEqRef |-> BOOLEAN, committers |-> VST] + +\* Block type. +\* A block contains its height, validator set, next validator set, and last commit. +\* Moreover, it contains the flag that tells us whether the block is equal to the one +\* on the reference chain (this is an abstraction of hash). +BT == [height |-> Int, hashEqRef |-> BOOLEAN, wellFormed |-> BOOLEAN, + VS |-> VST, NextVS |-> VST, lastCommit |-> LCT] + +CONSTANTS + (* + A set of abstract values, each value representing a set of validators. + For the purposes of this specification, they can be any values, + e.g., "s1", "s2", etc. + *) + VALIDATOR_SETS, + (* a nil validator set that is outside of VALIDATOR_SETS *) + NIL_VS, + (* The maximal height, up to which the blockchain may grow *) + MAX_HEIGHT + +Heights == 1..MAX_HEIGHT + +\* the set of all potential commits +Commits == [blockIdEqRef: BOOLEAN, committers: VALIDATOR_SETS] + +\* the set of all potential blocks, not necessarily coming from the blockchain +Blocks == + [height: Heights, hashEqRef: BOOLEAN, wellFormed: BOOLEAN, + VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: Commits] + +\* Does the chain contain a sound sequence of blocks that could be produced by +\* a 2/3 of faulty validators. This operator can be used to initialise the chain! +\* Since we are abstracting validator sets with VALIDATOR_SETS, which are +\* 2/3 quorums, we just compare committers to those sets. In a more detailed +\* specification, one would write the \subseteq operator instead of equality. +IsCorrectChain(chain) == + \* restrict the structure of the blocks, to decrease the TLC search space + LET OkCommits == [blockIdEqRef: {TRUE}, committers: VALIDATOR_SETS] + OkBlocks == [height: Heights, hashEqRef: {TRUE}, wellFormed: {TRUE}, + VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: OkCommits] + IN + /\ chain \in [1..MAX_HEIGHT -> OkBlocks] + /\ \A h \in 1..MAX_HEIGHT: + LET b == chain[h] IN + /\ b.height = h \* the height is correct + /\ h > 1 => + LET p == chain[h - 1] IN + /\ b.VS = p.NextVS \* the validators propagate from the previous block + /\ b.lastCommit.committers = p.VS \* and they are the committers + + +\* The basic properties of blocks on the blockchain: +\* They should pass the validity check and they may verify the next block. + +\* Does the block pass the consistency check against the next validators of the previous block +IsMatchingValidators(block, nextVS) == + \* simply check that the validator set is propagated correctly. + \* (the implementation tests hashes and the application state) + block.VS = nextVS + +\* Does the block verify the commit (of the next block) +PossibleCommit(block, commit) == + \* the commits are signed by the block validators + /\ commit.committers = block.VS + \* The block id in the commit matches the block hash (abstract comparison). + \* (The implementation has extensive tests for that.) + \* this is an abstraction of: commit.blockId = hash(block) + \* + \* These are possible scenarios on the concrete hashes: + \* + \* scenario 1: commit.blockId = 10 /\ hash(block) = 10 /\ hash(ref) = 10 + \* scenario 2: commit.blockId = 20 /\ hash(block) = 20 /\ block.VS /= ref.VS + \* scenario 3: commit.blockId = 50 /\ hash(block) = 100 + \* scenario 4: commit.blockId = 10 /\ hash(block) = 100 + \* scenario 5: commit.blockId = 100 /\ hash(block) = 10 + /\ commit.blockIdEqRef = block.hashEqRef + \* the following test would be cheating, as we do not have access to the + \* reference chain: + \* /\ commit.blockIdEqRef + +\* Basic invariants + +\* every block has the validator set that is chosen by its predecessor +ValidBlockInv(chain) == + \A h \in 2..MAX_HEIGHT: + IsMatchingValidators(chain[h], chain[h - 1].NextVS) + +\* last commit of every block is signed by the validators of the predecessor +VerifiedBlockInv(chain) == + \A h \in 2..MAX_HEIGHT: + PossibleCommit(chain[h - 1], chain[h].lastCommit) + +================================================================================== diff --git a/rust-spec/fastsync/fastsync.md b/rust-spec/fastsync/fastsync.md new file mode 100644 index 000000000..aaf0e7334 --- /dev/null +++ b/rust-spec/fastsync/fastsync.md @@ -0,0 +1,1216 @@ +# Fastsync + +Fastsync is a protocol that is used by a node to catch-up to the +current state of a Tendermint blockchain. Its typical use case is a +node that was disconnected from the system for some time. The +recovering node locally has a copy of a prefix of the blockchain, +and the corresponding application state that is slightly outdated. It +then queries its peers for the blocks that were decided on by the +Tendermint blockchain during the period the full node was +disconnected. After receiving these blocks, it executes the +transactions in the blocks in order to catch-up to the current height +of the blockchain and the corresponding application state. + +In practice it is sufficient to catch-up only close to the current +height: The Tendermint consensus reactor implements its own catch-up +functionality and can synchronize a node that is close to the current height, +perhaps within 10 blocks away from the current height of the blockchain. +Fastsync should bring a node within this range. + +## Outline + +- [Part I](#part-i---tendermint-blockchain): Introduction of Tendermint +blockchain terms that are relevant for FastSync protocol. + +- [Part II](#part-ii---sequential-definition-of-fastsync-problem): Introduction +of the problem addressed by the Fastsync protocol. + - [Fastsync Informal Problem + statement](#Fastsync-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---fastsync-as-distributed-system): Distributed + aspects of the fast sync problem, system assumptions and temporal + logic specifications. + + - [Computational Model](#Computational-Model): + timing and correctness assumptions. + + - [Distributed Problem Statement](#Distributed-Problem-Statement): + temporal properties that formalize safety and liveness + properties of fast sync in distributed setting. + +- [Part IV](#part-iv---fastsync-protocol): Specification of Fastsync V2 + (the protocol underlying the current Golang implementation). + + - [Definitions](#Definitions): Describes inputs, outputs, + variables used by the protocol, auxiliary functions + + - [FastSync V2](#FastSync-V2): gives an outline of the solution, + and details of the functions used (with preconditions, + postconditions, error conditions). + + - [Algorithm Invariants](#Algorithm-Invariants): invariants over + the protocol variables that the implementation should maintain. + +- [Part V](#part-v---analysis-and-improvements): Analysis + of Fastsync V2 that highlights several issues that prevent achieving + some of the desired fault-tolerance properties. We also give some + suggestions on how to address the issues in the future. + + - [Analysis of Fastsync V2](#Analysis-of-Fastsync-V2): describes + undesirable scenarios of Fastsync V2, and why they violate + desirable temporal logic specification in an unreliable + distributed system. + + - [Suggestions](#Suggestions-for-an-Improved-Fastsync-Implementation) to address the issues discussed in the analysis. + +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 +- FS: Fastsync +- LIVE: liveness +- SAFE: safety +- INV: invariant +- A: assumption +- V2: refers to specifics of Fastsync V2 +- FS-VAR: refers to properties of Fastsync protocol variables +- NewFS: refers to improved future Fastsync implementations + +# Part I - Tendermint Blockchain + +We will briefly list some of the notions of Tendermint blockchains that are +required for this specification. More details can be found [here][block]. + +#### **[TMBC-HEADER]** + +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-SEQ]** + +The Tendermint blockchain is a list *chain* of headers. + +#### **[TMBC-SEQ-GROW]** + +During operation, new headers may be appended to the list one by one. + +> In the following, *ETIME* is a lower bound +> on the time interval between the times at which two +> successor blocks are added. + +#### **[TMBC-SEQ-APPEND-E]** + +If a header is appended at time *t* then no additional header will be +appended before time *t + ETIME*. + +#### **[TMBC-AUTH-BYZ]** + +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. + + + + + +> We observed that in the existing documentation the term +> *validator* refers to both a data structure and a full node that +> participates in the distributed computation. Therefore, we introduce +> the notions *validator pair* and *validator node*, respectively, to +> distinguish these notions in the cases where they are not clear from +> the context. + +#### **[TMBC-VALIDATOR-PAIR]** + +Given a full node, a +*validator pair* is a pair *(address, voting_power)*, where + +- *address* is the address (public key) of a full node, +- *voting_power* is an integer (representing the full node's + voting power in a given consensus instance). + +> In the Golang implementation the data type for *validator +> pair* is called `Validator`. + +#### **[TMBC-VALIDATOR-SET]** + +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-CORRECT]** + +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-TIME-PARAMS]** + +A blockchain has the following configuration parameters: + +- *unbondingPeriod*: a time duration. +- *trustingPeriod*: a time duration smaller than *unbondingPeriod*. + +#### **[TMBC-FM-2THIRDS]** + +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)*; +- For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n, + h.Time + trustingPeriod)*. + +#### **[TMBC-CORR-FULL]** + +Every correct full node locally stores a prefix of the +current list of headers from [**[TMBC-SEQ]**][TMBC-SEQ-link]. + +# Part II - Sequential Definition of Fastsync Problem + +## Fastsync Informal Problem statement + +A full node has as input a block of the blockchain at height *h* and +the corresponding application state (or the prefix of the current +blockchain until height *h*). It has access to a set *peerIDs* of full +nodes called *peers* that it knows of. The full node uses the peers +to read blocks of the Tendermint blockchain (in a safe way, that is, +it checks the soundness conditions), until it has read the most recent +block and then terminates. + +## Sequential Problem statement + +*Fastsync* gets as input a block of height *h* and the corresponding +application state *s* that corresponds to the block and state of that +height of the blockchain, and produces +as output (i) a list *L* of blocks starting at height *h* to some height +*terminationHeight*, and (ii) the application state when applying the +transactions of the list *L* to *s*. + +> In Tendermint, the commit for block of height *h* is contained in block *h + 1*, +> and thus the block of height *h + 1* is needed to verify the block of +> height *h*. Let us therefore clarify the following on the +> termination height: +> The returned value *terminationHeight* is the height of the block with the largest +> height that could be verified. In order to do so, *Fastsync* needs the +> block at height *terminationHeight + 1* of the blockchain. + +Fastsync has to satisfy the following properties: + +#### **[FS-SEQ-SAFE-START]** + +Let *bh* be the height of the blockchain at the time *Fastsync* +starts. By assumption we have *bh >= h*. +When *Fastsync* terminates, it outputs a list of all blocks from +height *h* to some height *terminationHeight >= bh - 1*. + +> The above property is independent of how many blocks are added to the +> blockchain while Fastsync is running. It links the target height to the +> initial state. If Fastsync has to catch-up many blocks, it would be +> better to link the target height to a time close to the +> termination. This is captured by the following specification: + +#### **[FS-SEQ-SAFE-SYNC]** + +Let *eh* be the height of the blockchain at the time *Fastsync* +terminates. There is a constant *D >= 1* such that when *Fastsync* +terminates, it outputs a list of all blocks from height *h* to some +height *terminationHeight >= eh - D*. + +#### **[FS-SEQ-SAFE-STATE]** + +Upon termination, the application state is the one that corresponds to +the blockchain at height *terminationHeight*. + +#### **[FS-SEQ-LIVE]** + +*Fastsync* eventually terminates. + +# Part III - FastSync as Distributed System + +## Computational Model + +#### **[FS-A-NODE]** + +We consider a node *FS* that performs *Fastsync*. + +#### **[FS-A-PEER-IDS]** + +*FS* has access to a set *peerIDs* of IDs (public keys) of peers + . During the execution of *Fastsync*, another protocol (outside + of this specification) may add new IDs to *peerIDs*. + +#### **[FS-A-PEER]** + +Peers can be faulty, and we do not make any assumptions about the number or +ratio of correct/faulty nodes. Faulty processes may be Byzantine +according to [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link]. + +#### **[FS-A-VAL]** + +The system satisfies [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] and +[**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link]. Thus, there is a +blockchain that satisfies the soundness requirements (that is, the +validation rules in [[block]]). + +#### **[FS-A-COMM]** + +Communication between the node *FS* and all correct peers is reliable and +bounded in time: there is a message end-to-end delay *Delta* such that +if a message is sent at time *t* by a correct process to a correct +process, then it will be 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. + +## Distributed Problem Statement + +### Two Kinds of Termination + +We do not assume that there is a correct full node in +*peerIDs*. Under this assumption no protocol can guarantee the combination +of the properties [FS-SEQ-LIVE] and +[FS-SEQ-SAFE-START] and [FS-SEQ-SAFE-SYNC] described in the sequential +specification above. Thus, in the (unreliable) distributed setting, we +consider two kinds of termination (successful and failure) and we will +specify below under what (favorable) conditions *Fastsync* ensures to +terminate successfully, and satisfy the requirements of the sequential +problem statement: + +#### **[FS-DIST-LIVE]** + +*Fastsync* eventually terminates: it either *terminates successfully* or +it *terminates with failure*. + +### Fairness + +As mentioned above, without assumptions on the correctness of some +peers, no protocol can achieve the required specifications. Therefore, +we consider the following (fairness) constraint in the +safety and liveness properties below: + +#### **[FS-SOME-CORR-PEER]** + +Initially, the set *peerIDs* contains at least one correct full node. + +> While in principle the above condition [FS-SOME-CORR-PEER] +> can be part of a sufficient +> condition to solve [FS-SEQ-LIVE] and +> [FS-SEQ-SAFE-START] and [FS-SEQ-SAFE-SYNC] in the distributed +> setting (their corresponding properties are given below), we will discuss in +> [Part V](#part-v---analysis-and-improvements) that the +> current implementation of Fastsync (V2) requires the much +> stronger requirement [**[FS-ALL-CORR-PEER]**](#FS-ALL-CORR-PEER) +> given in Part V. + +### Safety + +> As this specification does +> not assume that a correct peer is at the most recent height +> of the blockchain (it might lag behind), the property [FS-SEQ-SAFE-START] +> cannot be ensured in an unreliable distributed setting. We consider +> the following relaxation. (Which is typically sufficient for +> Tendermint, as the consensus reactor then synchronizes from that +> height.) + +#### **[FS-DIST-SAFE-START]** + +Let *maxh* be the maximum +height of a correct peer [**[TMBC-CORR-FULL]**][TMBC-CORR-FULL-link] +in *peerIDs* at the time *Fastsync* starts. If *FastSync* terminates +successfully, it is at some height *terminationHeight >= maxh - 1*. + +> To address [FS-SEQ-SAFE-SYNC] we consider the following property in +> the distributed setting. See the comments below on the relation to +> the sequential version. + +#### **[FS-DIST-SAFE-SYNC]** + +Under [FS-SOME-CORR-PEER], there exists a constant time interval *TD*, such +that if *term* is the time *Fastsync* terminates and +*maxh* is the maximum height of a correct peer +[**[TMBC-CORR-FULL]**][TMBC-CORR-FULL-link] in *peerIDs* at the time +*term - TD*, then if *FastSync* terminates successfully, it is at +some height *terminationHeight >= maxh - 1*. + +> *TD* might depend on timeouts etc. We suggest that an acceptable +> value for *TD* is in the range of approx. 10 sec., that is the +> interval between two calls `QueryStatus()`; see below. +> We use *term - TD* as reference time, as we have to account +> for communication delay between the peer and *FS*. After the peer sent +> the last message to *FS*, the peer and *FS* run concurrently and +> independently. There is no assumption on the rate at which a peer can +> add blocks (e.g., it might be in the process of catching up +> itself). Hence, without additional assumption we cannot link +> [FS-DIST-SAFE-SYNC] to +> [**[FS-SEQ-SAFE-SYNC]**](#FS-SEQ-SAFE-SYNC), in particular to the +> parameter *D*. We discuss a +> way to achieve this below: +> **Relation to [FS-SEQ-SAFE-SYNC]:** +> Under [FS-SOME-CORR-PEER], if *peerIDs* contains a full node that is +> "synchronized with the blockchain", and *blockchainheight* is the height +> of the blockchain at time *term*, then *terminationHeight* may even +> achieve +> *blockchainheight - TD / ETIME*; +> cf. [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link], that is, +> the parameter *D* from [FS-SEQ-SAFE-SYNC] is in the range of *TD / ETIME*. + +#### **[FS-DIST-SAFE-STATE]** + +It is the same as the sequential version +[**[FS-SEQ-SAFE-STATE]**](#FS-SEQ-SAFE-STATE). + +#### **[FS-DIST-NONABORT]** + +If there is one correct process in *peerIDs* [FS-SOME-CORR-PEER], +*Fastsync* never terminates with failure. (Together with [FS-DIST-LIVE] + that means it will terminate successfully.) + +# Part IV - Fastsync protocol + +Here we provide a specification of the FastSync V2 protocol as it is currently +implemented. The V2 design is the result of significant refactoring to improve +the testability and determinism in the implementation. The architecture is +detailed in +[ADR-43](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-043-blockchain-riri-org.md). + +In the original design, a go-routine (thread of execution) was spawned for each block requested, and +was responsible for both protocol logic and IO. In the V2 design, protocol logic +is decoupled from IO by using three total threads of execution: a scheduler, a +processer, and a demuxer. + +The scheduler contains the business logic for managing +peers and requesting blocks from them, while the processor handles the +computationally expensive block execution. Both the scheduler and processor +are structured as finite state machines that receive input events and emit +output events. The demuxer is responsible for all IO, including translating +between internal events and IO messages, and routing events between components. + +Protocols in Tendermint can be considered to consist of two +components: a "core" state machine and a "peer" state machine. The core state +machine refers to the internal state managed by the node, while the peer state +machine determines what messages to send to peers. In the FastSync design, the +core and peer state machines correspond to the processor and scheduler, +respectively. + +In the case of FastSync, the core state machine (the processor) is effectively +just the Tendermint block execution function, while virtually all protocol logic +is contained in the peer state machine (the scheduler). The processor is +only implemented as a separate component due to the computationally expensive nature +of block execution. We therefore focus our specification here on the peer state machine +(the scheduler component), capturing the core state machine (the processor component) +in the single `Execute` function, defined below. + +While the internal details of the `Execute` function are not relevant for the +FastSync protocol and are thus not part of this specification, they will be +defined in detail at a later date in a separate Block Execution specification. + +## Definitions + +> We now introduce variables and auxiliary functions used by the protocol. + +### Inputs + +- *startBlock*: the block Fastsync starts from +- *startState*: application state corresponding to *startBlock.Height* + +#### **[FS-A-V2-INIT]** + +- *startBlock* is from the blockchain +- *startState* is the application state of the blockchain at Height *startBlock.Height*. + +### Variables + +- *height*: kinitially *startBlock.Height + 1* + > height should be thought of the "height of the next block we need to download" +- *state*: initially *startState* +- *peerIDs*: peer addresses [FS-A-PEER-IDS](#fs-a-peer-ids) +- *peerHeights*: stores for each peer the height it reported. initially 0 +- *pendingBlocks*: stores for each height which peer was + queried. initially nil for each height +- *receivedBlocks*: stores for each height which peer returned + it. initially nil +- *blockstore*: stores for each height greater than + *startBlock.Height*, the block of that height. initially nil for + all heights +- *peerTimeStamp*: stores for each peer the last time a block was + received + +- *pendingTime*: stores for a given height the time a block was requested +- *peerRate*: stores for each peer the rate of received data in Bytes/second + +### Auxiliary Functions + +#### **[FS-FUNC-TARGET]** + +- *TargetHeight = max {peerHeigts(addr): addr in peerIDs} union {height}* + +#### **[FS-FUNC-MATCH]** + +```go +func VerifyCommit(b Block, c Commit) Boolean +``` + +- Comment + - Corresponds to `verifyCommit(chainID string, blockID + types.BlockID, height int64, commit *types.Commit) error` in the + current Golang implementation, which expects blockID and height + (from the first block) and the + corresponding commit from the following block. We use the + simplified form for ease in presentation. + +- Implementation remark + + + + - implements the check that *c* is a valid commit for block *b* +- Expected precondition + - *c* is a valid commit for block *b* +- Expected postcondition + - *true* if precondition holds + - *false* if precondition is violated +- Error condition + - none + +---- + +### Messages + +Peers participating in FastSync exchange the following set of messages. Messages are +encoded using the Amino serialization protocol. We define each message here +using Go syntax, annoted with the Amino type name. The prefix `bc` refers to +`blockchain`, which is the name of the FastSync reactor in the Go +implementation. + +#### bcBlockRequestMessage + +```go +// type: "tendermint/blockchain/BlockRequest" +type bcBlockRequestMessage struct { + Height int64 +} +``` + +Remark: + +- `msg.Height` > 0 + +#### bcNoBlockResponseMessage + +```go +// type: "tendermint/blockchain/NoBlockResponse" +type bcNoBlockResponseMessage struct { + Height int64 +} +``` + +Remark: + +- `msg.Height` > 0 +- This message type is included in the protocol for convenience and is not expected to be sent between two correct peers + +#### bcBlockResponseMessage + +```go +// type: "tendermint/blockchain/BlockResponse" +type bcBlockResponseMessage struct { + Block *types.Block +} +``` + +Remark: + +- `msg.Block` is a Tendermint block as defined in [[block]]. +- `msg.Block` != nil + +#### bcStatusRequestMessage + +```go +// type: "tendermint/blockchain/StatusRequest" +type bcStatusRequestMessage struct { + Height int64 +} +``` + +Remark: + +- `msg.Height` > 0 + +#### bcStatusResponseMessage + +```go +// type: "tendermint/blockchain/StatusResponse" +type bcStatusResponseMessage struct { + Height int64 +} +``` + +Remark: + +- `msg.Height` > 0 + +### Remote Functions + +Peers expose the following functions over +remote procedure calls. The "Expected precondition" are only expected for +correct peers (as no assumption is made on internals of faulty +processes [FS-A-PEER]). These functions are implemented using the above defined message types. + +> In this document we describe the communication with peers +via asynchronous RPCs. + +```go +func Status(addr Address) (int64, error) +``` + +- Implementation remark + - RPC to full node *addr* + - Request message: `bcStatusRequestMessage`. + - Response message: `bcStatusResponseMessage`. +- Expected precondition + - none +- Expected postcondition + - if *addr* is correct: Returns the current height `height` of the + peer. [FS-A-COMM] + - if *addr* is faulty: Returns an arbitrary height. [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] +- Error condition + - if *addr* is correct: none. By [FS-A-COMM] we assume communication is reliable and timely. + - if *addr* is faulty: arbitrary error (including timeout). [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] + +---- + + ```go +func Block(addr Address, height int64) (Block, error) +``` + +- Implementation remark + - RPC to full node *addr* + - Request message: `bcBlockRequestMessage`. + - Response message: `bcBlockResponseMessage` or `bcNoBlockResponseMessage`. +- Expected precondition + - 'height` is less than or equal to height of the peer +- Expected postcondition + - if *addr* is correct: Returns the block of height `height` + from the blockchain. [FS-A-COMM] + - if *addr* is faulty: Returns arbitrary or no block [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] +- Error condition + - if *addr* is correct: precondition violated (returns `bcNoBlockResponseMessage`). [FS-A-COMM] + - if *addr* is faulty: arbitrary error (including timeout). [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] + +---- + +## FastSync V2 + +### Outline + +The protocol is described in terms of functions that are triggered by +(external) events. The implementation uses a scheduler and a +de-multiplexer to deal with communicating with peers and to +trigger the execution of these functions: + +- `QueryStatus()`: regularly (currently every 10sec; necessarily + interval greater than *2 Delta*) queries all peers from *peerIDs* + for their current height [TMBC-CORR-FULL]. It does so + by calling `Status(n)` remotely on all peers *n*. + +- `CreateRequest`: regularly checks whether certain blocks have no + open request. If a block does not have an open request, it requests + one from a peer. It does so by calling `Block(n,h)` remotely on one + peer *n* for a missing height *h*. + +> We have left the strategy how peers are selected unspecified, and +> the currently existing different implementations of Fastsync differ +> in this aspect. In V2, a peer *p* is selected with the minimum number of +> pending requests that can serve the required height *h*, that is +> with *peerHeight(p) >= h*. + +The functions `Status` and `Block` are called by asynchronous +RPC. When they return, the following functions are called: + +- `OnStatusResponse(addr Address, height int64)`: The full node with + address *addr* returns its current height. The function updates the height + information about *addr*, and may also increase *TargetHeight*. + +- `OnBlockResponse(addr Address, b Block)`. The full node with + address *addr* returns a block. It is added to *blockstore*. Then + the auxiliary function `Execute` is called. + +- `Execute()`: Iterates over the *blockstore*. Checks soundness of + the blocks, and + executes the transactions of a sound block and updates *state*. + +> In addition to the functions above, the following two features are +> implemented in Fastsync V2 + +#### **[FS-V2-PEER-REMOVE]** + +Periodically, *peerTimeStamp* and *peerRate* and *pendingTime* are +analyzed. +If a peer *p* +has not provided a block recently (check of *peerTimeStamp[p]*) or it +has not provided sufficiently many data (check of *peerRate[p]*), then +*p* is removed from *peerIDs*. In addition, *pendingTime* is used to +estimate whether the peer that is responsible for the current height +has provided the corresponding block on time. + +#### **[FS-V2-TIMEOUT]** + +*Fastsync V2* starts a timeout whenever a block is +executed (that is, when the height is incremented). If the timeout expires +before the next block is executed, *Fastsync* terminates. +If this happens, then *Fastsync* terminates +with failure. + +### Details + + + +```go +func QueryStatus() +``` + +- Expected precondition + - peerIDs initialized and non-empty +- Expected postcondition + - call asynchronously `Status(n)` at each peer *n* in *peerIDs*. +- Error condition + - fails if precondition is violated + +---- + +```go +func OnStatusResponse(addr Address, ht int64) +``` + +- Comment + - *ht* is a height + - peers can provide the status without being called +- Expected precondition + - *peerHeights(addr) <= ht* +- Expected postcondition + - *peerHeights(addr) = ht* + - *TargetHeight* is updated +- Error condition + - if precondition is violated: *addr* not in *peerIDs* (that is, + *addr* is removed from *peerIDs*) +- Timeout condition + - if `OnStatusResponse(addr, ht)` was not invoked within *2 Delta* after + `Status(addr)` was called: *addr* not in *peerIDs* + +---- + +```go +func CreateRequest +``` + +- Expected precondition + - *height < TargetHeight* + - *peerIDs* nonempty +- Expected postcondition + - Function `Block` is called remotely at a peer *addr* in peerIDs + for a missing height *h* + *Remark:* different implementations may have different + strategies to balance the load over the peers + - *pendingblocks(h) = addr* + +---- + +```go +func OnBlockResponse(addr Address, b Block) +``` + +- Comment + - if after adding block *b*, blocks of heights *height* and + *height + 1* are in *blockstore*, then `Execute` is called +- Expected precondition + - *pendingblocks(b.Height) = addr* + - *b* satisfies basic soundness +- Expected postcondition + - if function `Execute` has been executed without error or was not + executed: + - *receivedBlocks(b.Height) = addr* + - *blockstore(b.Height) = b* + - *peerTimeStamp[addr]* is set to a time between invocation and + return of the function. + - *peerRate[addr]* is updated according to size of received + block and time it has passed between current time and last block received from this peer (addr) +- Error condition + - if precondition is violated: *addr* not in *peerIDs*; reset + *pendingblocks(b.Height)* to nil; +- Timeout condition + - if `OnBlockResponse(addr, b)` was not invoked within *2 Delta* after + `Block(addr,h)` was called for *b.Height = h*: *addr* not in *peerIDs* + +---- + +```go +func Execute() +``` + +- Comments + - none +- Expected precondition + - application state is the one of the blockchain at height + *height - 1* + - **[FS-V2-Verif]** for any two blocks *a* and *b* from + *receivedBlocks*: if + *a.Height + 1 = b.Height* then *VerifyCommit (a,b.Commit) = true* +- Expected postcondition + - Any two blocks *a* and *b* violating [FS-V2-Verif]: + *a* and *b* not in *blockstore*; nodes with Address + receivedBlocks(a.Height) and receivedBlocks(b.Height) not in peerIDs + - height is updated height of complete prefix that matches the blockchain + - state is the one of the blockchain at height *height - 1* + - if the new value of *height* is equal to *TargetHeight*, then + Fastsync + **terminates + successfully**. +- Error condition + - none + +---- + +## Algorithm Invariants + +> In contrast to the temporal properties above that define the problem +> statement, the following are invariants on the solution to the +> problem, that is on the algorithm. These invariants are useful for +> the verification, but can also guide the implementation. + +#### **[FS-VAR-STATE-INV]** + +It is always the case that *state* corresponds to the application state of the +blockchain of that height, that is, *state = chain[height - +1].AppState*; *chain* is defined in +[**[TMBC-SEQ]**][TMBC-SEQ-link]. + +#### **[FS-VAR-PEER-INV]** + +It is always the case that the set *peerIDs* only contains nodes that +have not yet misbehaved (by sending wrong data or timing out). + +#### **[FS-VAR-BLOCK-INV]** + +For *startBlock.Height <= i < height - 1*, let *b(i)* be the block with +height *i* in *blockstore*, it always holds that +*VerifyCommit(b(i), b(i+1).Commit) = true*. This means that *height* +can only be incremented if all blocks with lower height have been verified. + +# Part V - Analysis and Improvements + +## Analysis of Fastsync V2 + +#### **[FS-ISSUE-KILL]** + +If two blocks are not matching [FS-V2-Verif], `Execute` dismisses both +blocks and removes the peers that provided these blocks from +*peerIDs*. If block *a* was correct and provided by a correct peer *p*, +and block b was faulty and provided by a faulty peer, the protocol + +- removes the correct peer *p*, although it might be useful to + download blocks from it in the future +- removes the block *a*, so that a fresh copy of *a* needs to be downloaded + again from another peer + +By [FS-A-PEER] we do not put a restriction on the number + of faulty peers, so that faulty peers can make *FS* to remove all + correct peers from *peerIDs*. As a result, this version of + *Fastsync* violates [FS-DIST-SAFE-SYNC]. + +#### **[FS-ISSUE-NON-TERM]** + +Due to [**[FS-ISSUE-KILL]**](#fs-issue-kill), from some point on, only +faulty peers may be in *peerIDs*. They can thus control at which rate +*Fastsync* gets blocks. If the timeout duration from [FS-V2-TIMEOUT] +is greater than the time it takes to add a block to the blockchain +(LTIME in [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link]), the +protocol may never terminate and thus violate [FS-DIST-LIVE]. This +scenario is even possible if a correct peer is always in *peerIDs*, +but faulty peers are regularly asked for blocks. + +### Consequence + +The issues [FS-ISSUE-KILL] and [FS-ISSUE-NON-TERM] explain why +does not satisfy the property [FS-DIST-LIVE] relevant for termination. +As a result, V2 only solves the specifications in a restricted form, +namely, when all peers are correct: + +#### **[FS-ALL-CORR-PEER]** + +At all times, the set *peerIDs* contains only correct full nodes. + +With this restriction we can give the achieved properties: + +#### **[FS-VC-ALL-CORR-NONABORT]** + +Under [FS-ALL-CORR-PEER], *Fastsync* never terminates with failure. + +#### **[FS-VC-ALL-CORR-LIVE]** + +Under [FS-ALL-CORR-PEER], *Fastsync* eventually terminates successfully. + +> In a fault tolerance context this is problematic, +> as it means that faulty peers can prevent *FastSync* from termination. +> We observe that this also touches other properties, namely, +> [FS-DIST-SAFE-START] and [FS-DIST-SAFE-SYNC]: +> Termination at an acceptable height are all conditional under +> "successful termination". The properties above severely restrict +> under which circumstances FastSync (V2) terminates successfully. +> As a result, large parts of the current +> implementation of are not fault-tolerant. We will +> discuss this, and suggestions how to solve this after the +> description of the current protocol. + +## Suggestions for an Improved Fastsync Implementation + +### Solution for [FS-ISSUE-KILL] + +To avoid [FS-ISSUE-KILL], we observe that +[**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link] ensures that from the +point a block was created, we assume that more than two thirds of the +validator nodes are correct until the *trustingPeriod* expires. Under +this assumption, assume the trusting period of *startBlock* is not +expired by the time *FastSync* checks a block *b1* with height +*startBlock.Height + 1*. To do so, we first need to check whether the +Commit in the block *b2* with *startBlock.Height + 2* contains more +than 2/3 of the voting power in *startBlock.NextValidators*. If this +is the case we can check *VerifyCommit (b1,b2.Commit)*. If we perform +checks in this order we observe: + +- By assumption, *startBlock* is OK, +- If the first check (2/3 of voting power) fails, + the peer that provided block *b2* is faulty, +- If the first check passes and the second check + fails (*VerifyCommit*), then the peer that provided *b1* is + faulty. +- If both checks pass, we can trust *b1* + +Based on this reasoning, we can ensure to only remove faulty peers +from *peerIDs*. That is, if +we sequentially verify blocks starting with *startBlock*, we will +never remove a correct peer from *peerIDs* and we will be able to +ensure the following invariant: + +#### **[NewFS-VAR-PEER-INV]** + +If a peer never misbehaves, it is never removed from *peerIDs*. It +follows that under [FS-SOME-CORR-PEER], *peerIDs* is always non-empty. + +> To ensure this, we suggest to change the protocol as follows: + +#### Fastsync has the following configuration parameters + +- *trustingPeriod*: a time duration; cf. + [**[TMBC-TIME-PARAMS]**][TMBC-TIME-PARAMS-link]. + +> [NewFS-A-INIT] is the suggested replacement of [FS-A-V2-INIT]. This will +> allow us to use the established trust to understand precisely which +> peer reported an invalid block in order to ensure the +> invariant [NewFS-VAR-TRUST-INV] below: + +#### **[NewFS-A-INIT]** + +- *startBlock* is from the blockchain, and within *trustingPeriod* +(possible with some extra margin to ensure termination before +*trustingPeriod* expired) +- *startState* is the application state of the blockchain at Height + *startBlock.Height*. +- *startHeight = startBlock.Height* + +#### Additional Variables + +- *trustedBlockstore*: stores for each height greater than or equal to + *startBlock.Height*, the block of that height. Initially it + contains only *startBlock* + +#### **[NewFS-VAR-TRUST-INV]** + +Let *b(i)* be the block in *trustedBlockstore* +with b(i).Height = i. It holds that +for *startHeight < i < height - 1*, +*VerifyCommit (b(i),b(i+1).Commit) = true*. + +> We propose to update the function `Execute`. To do so, we first +> define the following helper functions: + +```go +func ValidCommit(VS ValidatorSet, C Commit) Boolean +``` + +- Comments + - checks validator set based on [**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link] +- Expected precondition + - The validators in *C* + - are a subset of VS + - have more than 2/3 of the voting power in VS +- Expected postcondition + - returns *true* if precondition holds, and *false* otherwise +- Error condition + - none + +---- + +```go +func SequentialVerify { + while (true) { + b1 = blockstore[height]; + b2 = blockstore[height+1]; + if b1 == nil or b2 == nil { + exit; + } + if ValidCommit(trustedBlockstore[height - 1].NextValidators, b2.commit) { + // we trust b2 + if VerifyCommit(b1, b2.commit) { + trustedBlockstore.Add(b1); + height = height + 1; + } + else { + // as we trust b2, b1 must be faulty + blockstore.RemoveFromPeer(receivedBlocks[height]); + // we remove all blocks received from the faulty peer + peerIDs.Remove(receivedBlocks(bnew.Height)); + exit; + + } + } else { + // b2 is faulty + blockstore.RemoveFromPeer(receivedBlocks[height + 1]); + // we remove all blocks received from the faulty peer + peerIDs.Remove(receivedBlocks(bnew.Height)); + exit; } + } +} +``` + +- Comments + - none +- Expected precondition + - [NewFS-VAR-TRUST-INV] +- Expected postcondition + - [NewFS-VAR-TRUST-INV] + - there is no block *bnew* with *bnew.Height = height + 1* in + *blockstore* +- Error condition + - none + +---- + +> Then `Execute` just consists in calling `SequentialVerify` and then +> updating the application state to the (new) height. + +```go +func Execute() +``` + +- Comments + - first `SequentialVerify` is executed +- Expected precondition + - application state is the one of the blockchain at height + *height - 1* + - [NewFS-NOT-EXP] *trustedBlockstore[height-1].Time > now - trustingPeriod* +- Expected postcondition + - there is no block *bnew* with *bnew.Height = height + 1* in + *blockstore* + - state is the one of the blockchain at height *height - 1* + - if height = TargetHeight: **terminate successfully** +- Error condition + - fails if [NewFS-NOT-EXP] is violated + +---- + +### Solution for [FS-ISSUE-NON-TERM] + +As discussed above, the advantageous termination requirement is the +combination of [FS-DIST-LIVE] and [FS-DIST-NONABORT], that is, *Fastsync* +should terminate successfully in case there is at least one correct +peer in *peerIDs*. For this we have to ensure that faulty processes +cannot slow us down and provide blocks at a lower rate than the +blockchain may grow. To ensure that we will have to add an assumption +on message delays. + +#### **[NewFS-A-DELTA]** + +*2 Delta < ETIME*; cf. [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link]. + +> This assumption implies that the timeouts for `OnBlockResponse` and +> `OnStatusResponse` are such that a faulty peer that tries to respond +> slower than *2 Delta* will be removed. In the following we will +> provide a rough estimate on termination time in a fault-prone +> scenario. +> In the following +> we assume that during a "long enough" finite good period no new +> faulty peers are added to *peerIDs*. Below we will sketch how "long +> enough" can be estimated based on the timing assumption in this +> specification. + +#### **[NewFS-A-STATUS-INTERVAL]** + +Let Sigma be the (upper bound on the) +time between two calls of `QueryStatus()`. + +#### **[NewFS-A-GOOD-PERIOD]** + +A time interval *[begin,end]* is *good period* if: + +- *fmax* is the number of faulty peers in *peerIDs* at time *begin* +- *end >= begin + 2 Delta (fmax + 3)* +- no faulty peer is added before time *end* + +> In the analysis below we assume that the termination condition of +> *Fastsync* is +> *height = TargetHeight* in the postcondition of +> `Execute`. Therefore, [NewFS-A-STATUS-INTERVAL] does not interfere +> with this analysis. If a correct peer reports a new height "shortly +> before termination" this leads to an additional round trip to +> request and add the new block. Then [NewFS-A-DELTA] ensures that +> *Fastsync* catches up. + +Arguments: + +1. If a faulty peer *p* reports a faulty block, `SequentialVerify` will + eventually remove *p* from *peerIDs* + +2. By `SequentialVerify`, if a faulty peer *p* reports multiple faulty + blocks, *p* will be removed upon trying to check the block with the + smallest height received from *p*. + +3. Assume whenever a block does not have an open request, `CreateRequest` is + called immediately, which calls `Block(n)` on a peer. Say this + happens at time *t*. There are two cases: + + - by t + 2 Delta a block is added to *blockStore* + - at t + 2 Delta `Block(n)` timed out and *n* is removed from + peer. + +4. Let *f(t)* be the number of faulty peers in *peerIDs* at time *t*; + *f(begin) = fmax*. + +5. Let t_i be the sequence of times `OnBlockResponse(addr,b)` is + invoked or times out with *b.Height = height + 1*. + +6. By 3., + - (a). *t_1 <= begin + 2 Delta* + - (b). *t_{i+1} <= t_i + 2 Delta* + +7. By an inductive argument we prove for *i > 0* that + + - (a). *height(t_{i+1}) > height(t_i)*, or + - (b). *f(t_{i+1}) < f(t_i))* and *height(t_{i+1}) = height(t_i)* + + Argument: if the peer is faulty and does not return a block, the + peer is removed, if it is faulty and returns a faulty block + `SequentialVerify` removes the peer (b). If the returned block is OK, + height is increased (a). + +8. By 2. and 7., faulty peers can delay incrementing the height at + most *fmax* times, where each time "costs" *2 Delta* seconds. We + have additional *2 Delta* initial offset (3a) plus *2 Delta* to get + all missing blocks after the last fault showed itself. (This + assumes that an arbitrary number of blocks can be obtained and + checked within one round-trip 2 Delta; which either needs + conservative estimation of Delta, or a more refined analysis). Thus + we reach the *targetHeight* and terminate by time *end*. + +# References + + + +[[block]] Specification of the block data structure. + + + +[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md + + + +[TMBC-HEADER-link]: #tmbc-header + +[TMBC-SEQ-link]: #tmbc-seq + +[TMBC-CORR-FULL-link]: #tmbc-corrfull + +[TMBC-CORRECT-link]: #tmbc-correct + +[TMBC-Sign-link]: #tmbc-sign + +[TMBC-FaultyFull-link]: #tmbc-faultyfull + +[TMBC-TIME-PARAMS-link]: #tmbc-time-params + +[TMBC-SEQ-APPEND-E-link]: #tmbc-seq-append-e + +[TMBC-FM-2THIRDS-link]: #tmbc-fm-2thirds + +[TMBC-Auth-Byz-link]: #tmbc-auth-byz + +[TMBC-INV-SIGN-link]: #tmbc-inv-sign + +[TMBC-SOUND-DISTR-PossCommit--link]: #tmbc-sound-distr-posscommit + +[TMBC-INV-VALID-link]: #tmbc-inv-valid + + + + + + + + + + + + + + + + + + + + + + + + + +[LCV-VC-LIVE-link]: https://github.com/informalsystems/VDD/tree/master/lightclient/verification.md#lcv-vc-live + +[lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md + +[failuredetector]: https://github.com/informalsystems/VDD/blob/master/liteclient/failuredetector.md + +[fullnode]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md + +[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/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.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 diff --git a/rust-spec/fastsync/fastsync.tla b/rust-spec/fastsync/fastsync.tla new file mode 100644 index 000000000..e1b7b812b --- /dev/null +++ b/rust-spec/fastsync/fastsync.tla @@ -0,0 +1,825 @@ +----------------------------- MODULE fastsync ----------------------------- +(* + In this document we give the high level specification of the fast sync + protocol as implemented here: + https://github.com/tendermint/tendermint/tree/master/blockchain/v2. + +We assume a system in which one node is trying to sync with the blockchain +(replicated state machine) by downloading blocks from the set of full nodes +(we call them peers) that are block providers, and executing transactions +(part of the block) against the application. + +Peers can be faulty, and we don't make any assumption about the rate of correct/faulty +nodes in the node peerset (i.e., they can all be faulty). Correct peers are part +of the replicated state machine, i.e., they manage blockchain and execute +transactions against the same deterministic application. We don't make any +assumptions about the behavior of faulty processes. Processes (client and peers) +communicate by message passing. + + In this specification, we model this system with two parties: + - the node (state machine) that is doing fastsync and + - the environment with which node interacts. + +The environment consists of the set of (correct and faulty) peers with +which node interacts as part of fast sync protocol, but also contains some +aspects (adding/removing peers, timeout mechanism) that are part of the node +local environment (could be seen as part of the runtime in which node +executes). + +As part of the fast sync protocol a node and the peers exchange the following messages: + +- StatusRequest +- StatusResponse +- BlockRequest +- BlockResponse +- NoBlockResponse. + +A node is periodically issuing StatusRequests to query peers for their current height (to decide what +blocks to ask from what peers). Based on StatusResponses (that are sent by peers), the node queries +blocks for some height(s) by sending peers BlockRequest messages. A peer provides a requested block by +BlockResponse message. If a peer does not want to provide a requested block, then it sends NoBlockResponse message. +In addition to those messages, a node in this spec receives additional input messages (events): + +- AddPeer +- RemovePeer +- SyncTimeout. + +These are the control messages that are provided to the node by its execution enviornment. AddPeer +is for the case when a connection is established with a peer; similarly RemovePeer is for the case +a connection with the peer is terminated. Finally SyncTimeout is used to model a timeout trigger. + +We assume that fast sync protocol starts when connections with some number of peers +are established. Therefore, peer set is initialised with non-empty set of peer ids. Note however +that node does not know initially the peer heights. +*) + +EXTENDS Integers, FiniteSets, Sequences + + +CONSTANTS MAX_HEIGHT, \* the maximal height of blockchain + VALIDATOR_SETS, \* abstract set of validators + NIL_VS, \* a nil validator set + CORRECT, \* set of correct peers + FAULTY, \* set of faulty peers + TARGET_PENDING, \* maximum number of pending requests + downloaded blocks that are not yet processed + PEER_MAX_REQUESTS \* maximum number of pending requests per peer + +ASSUME CORRECT \intersect FAULTY = {} +ASSUME TARGET_PENDING > 0 +ASSUME PEER_MAX_REQUESTS > 0 + +\* the blockchain, see Tinychain +VARIABLE chain + +\* introduce tiny chain as the source of blocks for the correct nodes +INSTANCE Tinychain + +\* a special value for an undefined height +NilHeight == 0 + +\* the height of the genesis block +TrustedHeight == 1 + +\* the set of all peer ids the node can receive a message from +AllPeerIds == CORRECT \union FAULTY + +\* Correct last commit have enough voting power, i.e., +2/3 of the voting power of +\* the corresponding validator set (enoughVotingPower = TRUE) that signs blockId. +\* BlockId defines correct previous block (in the implementation it is the hash of the block). +\* Instead of blockId, we encode blockIdEqRef, which is true, if the block id is equal +\* to the hash of the previous block, see Tinychain. +CorrectLastCommit(h) == chain[h].lastCommit + +NilCommit == [blockIdEqRef |-> FALSE, committers |-> NIL_VS] + +\* correct node always supplies the blocks from the blockchain +CorrectBlock(h) == chain[h] + +NilBlock == + [height |-> 0, hashEqRef |-> FALSE, wellFormed |-> FALSE, + lastCommit |-> NilCommit, VS |-> NIL_VS, NextVS |-> NIL_VS] + +\* a special value for an undefined peer +NilPeer == "Nil" \* STRING for apalache efficiency + +\* control the state of the syncing node +States == { "running", "finished"} + +NoMsg == [type |-> "None"] + +\* the variables of the node running fastsync +VARIABLES + state, \* running or finished + (* + blockPool [ + height, \* current height we are trying to sync. Last block executed is height - 1 + peerIds, \* set of peers node is connected to + peerHeights, \* map of peer ids to its (stated) height + blockStore, \* map of heights to (received) blocks + receivedBlocks, \* map of heights to peer that has sent us the block (stored in blockStore) + pendingBlocks, \* map of heights to peer to which block request has been sent + syncHeight, \* height at the point syncTimeout was triggered last time + syncedBlocks \* number of blocks synced since last syncTimeout. If it is 0 when the next timeout occurs, then protocol terminates. + ] + *) + blockPool + + +\* the variables of the peers providing blocks +VARIABLES + (* + peersState [ + peerHeights, \* track peer heights + statusRequested, \* boolean set to true when StatusRequest is received. Models periodic sending of StatusRequests. + blocksRequested \* set of BlockRequests received that are not answered yet + ] + *) + peersState + + \* the variables for the network and scheduler +VARIABLES + turn, \* who is taking the turn: "Peers" or "Node" + inMsg, \* a node receives message by this variable + outMsg \* a node sends a message by this variable + + +(* the variables of the node *) +nvars == <> + +(*************** Type definitions for Apalache (model checker) **********************) +AsIntSet(S) == S <: {Int} + +\* type of process ids +PIDT == STRING +AsPidSet(S) == S <: {PIDT} + +\* ControlMessage type +CMT == [type |-> STRING, peerId |-> PIDT] \* type of control messages + +\* InMsg type +IMT == [type |-> STRING, peerId |-> PIDT, height |-> Int, block |-> BT] +AsInMsg(m) == m <: IMT +AsInMsgSet(S) == S <: {IMT} + +\* OutMsg type +OMT == [type |-> STRING, peerId |-> PIDT, height |-> Int] +AsOutMsg(m) == m <: OMT +AsOutMsgSet(S) == S <: {OMT} + +\* block pool type +BPT == [height |-> Int, peerIds |-> {PIDT}, peerHeights |-> [PIDT -> Int], + blockStore |-> [Int -> BT], receivedBlocks |-> [Int -> PIDT], + pendingBlocks |-> [Int -> PIDT], syncedBlocks |-> Int, syncHeight |-> Int] + +AsBlockPool(bp) == bp <: BPT + +(******************** Sets of messages ********************************) + +\* Control messages +ControlMsgs == + AsInMsgSet([type: {"addPeer"}, peerId: AllPeerIds]) + \union + AsInMsgSet([type: {"removePeer"}, peerId: AllPeerIds]) + \union + AsInMsgSet([type: {"syncTimeout"}]) + +\* All messages (and events) received by a node +InMsgs == + AsInMsgSet({NoMsg}) + \union + AsInMsgSet([type: {"blockResponse"}, peerId: AllPeerIds, block: Blocks]) + \union + AsInMsgSet([type: {"noBlockResponse"}, peerId: AllPeerIds, height: Heights]) + \union + AsInMsgSet([type: {"statusResponse"}, peerId: AllPeerIds, height: Heights]) + \union + ControlMsgs + +\* Messages sent by a node and received by peers (environment in our case) +OutMsgs == + AsOutMsgSet({NoMsg}) + \union + AsOutMsgSet([type: {"statusRequest"}]) \* StatusRequest is broadcast to the set of connected peers. + \union + AsOutMsgSet([type: {"blockRequest"}, peerId: AllPeerIds, height: Heights]) + + +(********************************** NODE ***********************************) + +InitNode == + \E pIds \in SUBSET AllPeerIds: \* set of peers node established initial connections with + /\ pIds \subseteq CORRECT \* this line is not necessary + /\ pIds /= AsPidSet({}) \* apalache better checks non-emptiness than subtracts from SUBSET + /\ blockPool = AsBlockPool([ + height |-> TrustedHeight + 1, \* the genesis block is at height 1 + syncHeight |-> TrustedHeight + 1, \* and we are synchronized to it + peerIds |-> pIds, + peerHeights |-> [p \in AllPeerIds |-> NilHeight], \* no peer height is known + blockStore |-> + [h \in Heights |-> + IF h > TrustedHeight THEN NilBlock ELSE chain[1]], + receivedBlocks |-> [h \in Heights |-> NilPeer], + pendingBlocks |-> [h \in Heights |-> NilPeer], + syncedBlocks |-> -1 + ]) + /\ state = "running" + +\* Remove faulty peers. +\* Returns new block pool. +\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L222 +RemovePeers(rmPeers, bPool) == + LET keepPeers == bPool.peerIds \ rmPeers IN + LET pHeights == + [p \in AllPeerIds |-> IF p \in rmPeers THEN NilHeight ELSE bPool.peerHeights[p]] IN + + LET failedRequests == + {h \in Heights: /\ h >= bPool.height + /\ \/ bPool.pendingBlocks[h] \in rmPeers + \/ bPool.receivedBlocks[h] \in rmPeers} IN + LET pBlocks == + [h \in Heights |-> IF h \in failedRequests THEN NilPeer ELSE bPool.pendingBlocks[h]] IN + LET rBlocks == + [h \in Heights |-> IF h \in failedRequests THEN NilPeer ELSE bPool.receivedBlocks[h]] IN + LET bStore == + [h \in Heights |-> IF h \in failedRequests THEN NilBlock ELSE bPool.blockStore[h]] IN + + IF keepPeers /= bPool.peerIds + THEN [bPool EXCEPT + !.peerIds = keepPeers, + !.peerHeights = pHeights, + !.pendingBlocks = pBlocks, + !.receivedBlocks = rBlocks, + !.blockStore = bStore + ] + ELSE bPool + +\* Add a peer. +\* see https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L198 +AddPeer(peer, bPool) == + [bPool EXCEPT !.peerIds = bPool.peerIds \union {peer}] + + +(* +Handle StatusResponse message. +If valid status response, update peerHeights. +If invalid (height is smaller than the current), then remove peer. +Returns new block pool. +See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L667 +*) +HandleStatusResponse(msg, bPool) == + LET peerHeight == bPool.peerHeights[msg.peerId] IN + + IF /\ msg.peerId \in bPool.peerIds + /\ msg.height >= peerHeight + THEN \* a correct response + LET pHeights == [bPool.peerHeights EXCEPT ![msg.peerId] = msg.height] IN + [bPool EXCEPT !.peerHeights = pHeights] + ELSE RemovePeers({msg.peerId}, bPool) \* the peer has sent us message with smaller height or peer is not in our peer list + + +(* +Handle BlockResponse message. +If valid block response, update blockStore, pendingBlocks and receivedBlocks. +If invalid (unsolicited response or malformed block), then remove peer. +Returns new block pool. +See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L522 +*) +HandleBlockResponse(msg, bPool) == + LET h == msg.block.height IN + + IF /\ msg.peerId \in bPool.peerIds + /\ bPool.blockStore[h] = NilBlock + /\ bPool.pendingBlocks[h] = msg.peerId + /\ msg.block.wellFormed + THEN + [bPool EXCEPT + !.blockStore = [bPool.blockStore EXCEPT ![h] = msg.block], + !.receivedBlocks = [bPool.receivedBlocks EXCEPT![h] = msg.peerId], + !.pendingBlocks = [bPool.pendingBlocks EXCEPT![h] = NilPeer] + ] + ELSE RemovePeers({msg.peerId}, bPool) + + HandleNoBlockResponse(msg, bPool) == + RemovePeers({msg.peerId}, bPool) + + +\* Compute max peer height. +\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L440 +MaxPeerHeight(bPool) == + IF bPool.peerIds = AsPidSet({}) + THEN 0 \* no peers, just return 0 + ELSE LET Hts == {bPool.peerHeights[p] : p \in bPool.peerIds} IN + CHOOSE max \in Hts: \A h \in Hts: h <= max + +(* Returns next height for which request should be sent. + Returns NilHeight in case there is no height for which request can be sent. + See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L454 *) +FindNextRequestHeight(bPool) == + LET S == {i \in Heights: + /\ i >= bPool.height + /\ i <= MaxPeerHeight(bPool) + /\ bPool.blockStore[i] = NilBlock + /\ bPool.pendingBlocks[i] = NilPeer} IN + IF S = AsIntSet({}) + THEN NilHeight + ELSE + CHOOSE min \in S: \A h \in S: h >= min + +\* Returns number of pending requests for a given peer. +NumOfPendingRequests(bPool, peer) == + LET peerPendingRequests == + {h \in Heights: + /\ h >= bPool.height + /\ bPool.pendingBlocks[h] = peer + } + IN + Cardinality(peerPendingRequests) + +(* Returns peer that can serve block for a given height. + Returns NilPeer in case there are no such peer. + See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L477 *) +FindPeerToServe(bPool, h) == + LET peersThatCanServe == { p \in bPool.peerIds: + /\ bPool.peerHeights[p] >= h + /\ NumOfPendingRequests(bPool, p) < PEER_MAX_REQUESTS } IN + + LET pendingBlocks == + {i \in Heights: + /\ i >= bPool.height + /\ \/ bPool.pendingBlocks[i] /= NilPeer + \/ bPool.blockStore[i] /= NilBlock + } IN + + IF \/ peersThatCanServe = AsPidSet({}) + \/ Cardinality(pendingBlocks) >= TARGET_PENDING + THEN NilPeer + \* pick a peer that can serve request for height h that has minimum number of pending requests + ELSE CHOOSE p \in peersThatCanServe: \A q \in peersThatCanServe: + /\ NumOfPendingRequests(bPool, p) <= NumOfPendingRequests(bPool, q) + + +\* Make a request for a block (if possible) and return a request message and block poool. +CreateRequest(bPool) == + LET nextHeight == FindNextRequestHeight(bPool) IN + + IF nextHeight = NilHeight THEN [msg |-> AsOutMsg(NoMsg), pool |-> bPool] + ELSE + LET peer == FindPeerToServe(bPool, nextHeight) IN + IF peer = NilPeer THEN [msg |-> AsOutMsg(NoMsg), pool |-> bPool] + ELSE + LET m == [type |-> "blockRequest", peerId |-> peer, height |-> nextHeight] IN + LET newPool == [bPool EXCEPT + !.pendingBlocks = [bPool.pendingBlocks EXCEPT ![nextHeight] = peer] + ] IN + [msg |-> m, pool |-> newPool] + + +\* Returns node state, i.e., defines termination condition. +\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L432 +ComputeNextState(bPool) == + IF bPool.syncedBlocks = 0 \* corresponds to the syncTimeout in case no progress has been made for a period of time. + THEN "finished" + ELSE IF /\ bPool.height > 1 + /\ bPool.height >= MaxPeerHeight(bPool) \* see https://github.com/tendermint/tendermint/blob/61057a8b0af2beadee106e47c4616b279e83c920/blockchain/v2/scheduler.go#L566 + THEN "finished" + ELSE "running" + +(* Verify if commit is for the given block id and if commit has enough voting power. + See https://github.com/tendermint/tendermint/blob/61057a8b0af2beadee106e47c4616b279e83c920/blockchain/v2/processor_context.go#L12 *) +VerifyCommit(block, lastCommit) == + PossibleCommit(block, lastCommit) + +(* Tries to execute next block in the pool, i.e., defines block validation logic. + Returns new block pool (peers that has send invalid blocks are removed). + See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/processor.go#L135 *) +ExecuteBlocks(bPool) == + LET bStore == bPool.blockStore IN + LET block0 == bStore[bPool.height - 1] IN + \* blockPool is initialized with height = TrustedHeight + 1, + \* so bStore[bPool.height - 1] is well defined + LET block1 == bStore[bPool.height] IN + LET block2 == bStore[bPool.height + 1] IN + + IF block1 = NilBlock \/ block2 = NilBlock + THEN bPool \* we don't have two next consecutive blocks + + ELSE IF ~IsMatchingValidators(block1, block0.NextVS) + \* Check that block1.VS = block0.Next. + \* Otherwise, CorrectBlocksInv fails. + \* In the implementation NextVS is part of the application state, + \* so a mismatch can be found without access to block0.NextVS. + THEN \* the block does not have the expected validator set + RemovePeers({bPool.receivedBlocks[bPool.height]}, bPool) + ELSE IF ~VerifyCommit(block1, block2.lastCommit) + \* Verify commit of block2 based on block1. + \* Interestingly, we do not have to call IsMatchingValidators. + THEN \* remove the peers of block1 and block2, as they are considered faulty + RemovePeers({bPool.receivedBlocks[bPool.height], + bPool.receivedBlocks[bPool.height + 1]}, + bPool) + ELSE \* all good, execute block at position height + [bPool EXCEPT !.height = bPool.height + 1] + + +\* Defines logic for pruning peers. +\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L613 +TryPrunePeer(bPool, suspectedSet, isTimedOut) == + (* -----------------------------------------------------------------------------------------------------------------------*) + (* Corresponds to function prunablePeers in scheduler.go file. Note that this function only checks if block has been *) + (* received from a peer during peerTimeout period. *) + (* Note that in case no request has been scheduled to a correct peer, or a request has been scheduled *) + (* recently, so the peer hasn't responded yet, a peer will be removed as no block is received within peerTimeout. *) + (* In case of faulty peers, we don't have any guarantee that they will respond. *) + (* Therefore, we model this with nondeterministic behavior as it could lead to peer removal, for both correct and faulty. *) + (* See scheduler.go *) + (* https://github.com/tendermint/tendermint/blob/4298bbcc4e25be78e3c4f21979d6aa01aede6e87/blockchain/v2/scheduler.go#L335 *) + LET toRemovePeers == bPool.peerIds \intersect suspectedSet IN + + (* + Corresponds to logic for pruning a peer that is responsible for delivering block for the next height. + The pruning logic for the next height is based on the time when a BlockRequest is sent. Therefore, if a request is sent + to a correct peer for the next height (blockPool.height), it should never be removed by this check as we assume that + correct peers respond timely and reliably. However, if a request is sent to a faulty peer then we + might get response on time or not, which is modelled with nondeterministic isTimedOut flag. + See scheduler.go + https://github.com/tendermint/tendermint/blob/4298bbcc4e25be78e3c4f21979d6aa01aede6e87/blockchain/v2/scheduler.go#L617 + *) + LET nextHeightPeer == bPool.pendingBlocks[bPool.height] IN + LET prunablePeers == + IF /\ nextHeightPeer /= NilPeer + /\ nextHeightPeer \in FAULTY + /\ isTimedOut + THEN toRemovePeers \union {nextHeightPeer} + ELSE toRemovePeers + IN + RemovePeers(prunablePeers, bPool) + + +\* Handle SyncTimeout. It models if progress has been made (height has increased) since the last SyncTimeout event. +HandleSyncTimeout(bPool) == + [bPool EXCEPT + !.syncedBlocks = bPool.height - bPool.syncHeight, + !.syncHeight = bPool.height + ] + +HandleResponse(msg, bPool) == + IF msg.type = "blockResponse" THEN + HandleBlockResponse(msg, bPool) + ELSE IF msg.type = "noBlockResponse" THEN + HandleNoBlockResponse(msg, bPool) + ELSE IF msg.type = "statusResponse" THEN + HandleStatusResponse(msg, bPool) + ELSE IF msg.type = "addPeer" THEN + AddPeer(msg.peerId, bPool) + ELSE IF msg.type = "removePeer" THEN + RemovePeers({msg.peerId}, bPool) + ELSE IF msg.type = "syncTimeout" THEN + HandleSyncTimeout(bPool) + ELSE + bPool + + +(* + At every node step we executed the following steps (atomically): + 1) input message is consumed and the corresponding handler is called, + 2) pruning logic is called + 3) block execution is triggered (we try to execute block at next height) + 4) a request to a peer is made (if possible) and + 5) we decide if termination condition is satisifed so we stop. +*) +NodeStep == + \E suspectedSet \in SUBSET AllPeerIds: \* suspectedSet is a nondeterministic set of peers + \E isTimedOut \in BOOLEAN: + LET bPool == HandleResponse(inMsg, blockPool) IN + LET bp == TryPrunePeer(bPool, suspectedSet, isTimedOut) IN + LET nbPool == ExecuteBlocks(bp) IN + LET msgAndPool == CreateRequest(nbPool) IN + LET nstate == ComputeNextState(msgAndPool.pool) IN + + /\ state' = nstate + /\ blockPool' = msgAndPool.pool + /\ outMsg' = msgAndPool.msg + /\ inMsg' = AsInMsg(NoMsg) + + +\* If node is running, then in every step we try to create blockRequest. +\* In addition, input message (if exists) is consumed and processed. +NextNode == + \/ /\ state = "running" + /\ NodeStep + + \/ /\ state = "finished" + /\ UNCHANGED <> + + +(********************************** Peers ***********************************) + +InitPeers == + \E pHeights \in [AllPeerIds -> Heights]: + peersState = [ + peerHeights |-> pHeights, + statusRequested |-> FALSE, + blocksRequested |-> AsOutMsgSet({}) + ] + +HandleStatusRequest(msg, pState) == + [pState EXCEPT + !.statusRequested = TRUE + ] + +HandleBlockRequest(msg, pState) == + [pState EXCEPT + !.blocksRequested = pState.blocksRequested \union AsOutMsgSet({msg}) + ] + +HandleRequest(msg, pState) == + IF msg = AsOutMsg(NoMsg) + THEN pState + ELSE IF msg.type = "statusRequest" + THEN HandleStatusRequest(msg, pState) + ELSE HandleBlockRequest(msg, pState) + +CreateStatusResponse(peer, pState, anyHeight) == + LET m == + IF peer \in CORRECT + THEN AsInMsg([type |-> "statusResponse", peerId |-> peer, height |-> pState.peerHeights[peer]]) + ELSE AsInMsg([type |-> "statusResponse", peerId |-> peer, height |-> anyHeight]) IN + + [msg |-> m, peers |-> pState] + +CreateBlockResponse(msg, pState, arbitraryBlock) == + LET m == + IF msg.peerId \in CORRECT + THEN AsInMsg([type |-> "blockResponse", peerId |-> msg.peerId, block |-> CorrectBlock(msg.height)]) + ELSE AsInMsg([type |-> "blockResponse", peerId |-> msg.peerId, block |-> arbitraryBlock]) IN + LET npState == + [pState EXCEPT + !.blocksRequested = pState.blocksRequested \ {msg} + ] IN + [msg |-> m, peers |-> npState] + +GrowPeerHeight(pState) == + \E p \in CORRECT: + /\ pState.peerHeights[p] < MAX_HEIGHT + /\ peersState' = [pState EXCEPT !.peerHeights[p] = @ + 1] + /\ inMsg' = AsInMsg(NoMsg) + +SendStatusResponseMessage(pState) == + /\ \E arbitraryHeight \in Heights: + \E peer \in AllPeerIds: + LET msgAndPeers == CreateStatusResponse(peer, pState, arbitraryHeight) IN + /\ peersState' = msgAndPeers.peers + /\ inMsg' = msgAndPeers.msg + + +SendAddPeerMessage == + \E peer \in AllPeerIds: + inMsg' = AsInMsg([type |-> "addPeer", peerId |-> peer]) + +SendRemovePeerMessage == + \E peer \in AllPeerIds: + inMsg' = AsInMsg([type |-> "removePeer", peerId |-> peer]) + +SendSyncTimeoutMessage == + inMsg' = AsInMsg([type |-> "syncTimeout"]) + + +SendControlMessage == + \/ SendAddPeerMessage + \/ SendRemovePeerMessage + \/ SendSyncTimeoutMessage + +\* An extremely important property of block hashes (blockId): +\* If the block hash coincides with the hash of the reference block, +\* then the blocks should be equal. +UnforgeableBlockId(height, block) == + block.hashEqRef => block = chain[height] + +\* A faulty peer cannot forge enough of the validators signatures. +\* In other words: If a commit contains enough signatures from the validators (in reality 2/3, in the model all), +\* then the blockID points to the block on the chain, encoded as block.lastCommit.blockIdEqRef being true +\* A more precise rule should have checked that the commiters have over 2/3 of the VS's voting power. +NoFork(height, block) == + (height > 1 /\ block.lastCommit.committers = chain[height - 1].VS) + => block.lastCommit.blockIdEqRef + +\* Can be block produced by a faulty peer, assuming it cannot generate forks (basic assumption of the protocol) +IsBlockByFaulty(height, block) == + /\ block.height = height + /\ UnforgeableBlockId(height, block) + /\ NoFork(height, block) + +SendBlockResponseMessage(pState) == + \* a response to a requested block: either by a correct, or by a faulty peer + \/ /\ pState.blocksRequested /= AsOutMsgSet({}) + /\ \E msg \in pState.blocksRequested: + \E block \in Blocks: + /\ IsBlockByFaulty(msg.height, block) + /\ LET msgAndPeers == CreateBlockResponse(msg, pState, block) IN + /\ peersState' = msgAndPeers.peers + /\ inMsg' = msgAndPeers.msg + + \* a faulty peer can always send an unsolicited block + \/ \E peerId \in FAULTY: + \E block \in Blocks: + /\ IsBlockByFaulty(block.height, block) + /\ peersState' = pState + /\ inMsg' = AsInMsg([type |-> "blockResponse", + peerId |-> peerId, block |-> block]) + +SendNoBlockResponseMessage(pState) == + /\ peersState' = pState + /\ inMsg' \in AsInMsgSet([type: {"noBlockResponse"}, peerId: FAULTY, height: Heights]) + + +SendResponseMessage(pState) == + \/ SendBlockResponseMessage(pState) + \/ SendNoBlockResponseMessage(pState) + \/ SendStatusResponseMessage(pState) + + +NextEnvStep(pState) == + \/ SendResponseMessage(pState) + \/ GrowPeerHeight(pState) + \/ SendControlMessage /\ peersState' = pState + \* note that we propagate pState that was missing in the previous version + + +\* Peers consume a message and update it's local state. It then makes a single step, i.e., it sends at most single message. +\* Message sent could be either a response to a request or faulty message (sent by faulty processes). +NextPeers == + LET pState == HandleRequest(outMsg, peersState) IN + /\ outMsg' = AsOutMsg(NoMsg) + /\ NextEnvStep(pState) + + +\* the composition of the node, the peers, the network and scheduler +Init == + /\ IsCorrectChain(chain) \* initialize the blockchain + /\ InitNode + /\ InitPeers + /\ turn = "Peers" + /\ inMsg = AsInMsg(NoMsg) + /\ outMsg = AsOutMsg([type |-> "statusRequest"]) + +Next == + IF turn = "Peers" + THEN + /\ NextPeers + /\ turn' = "Node" + /\ UNCHANGED <> + ELSE + /\ NextNode + /\ turn' = "Peers" + /\ UNCHANGED <> + + +FlipTurn == + turn' = + IF turn = "Peers" THEN + "Node" + ELSE + "Peers" + +\* Compute max peer height. Used as a helper operator in properties. +MaxCorrectPeerHeight(bPool) == + LET correctPeers == {p \in bPool.peerIds: p \in CORRECT} IN + IF correctPeers = AsPidSet({}) + THEN 0 \* no peers, just return 0 + ELSE LET Hts == {bPool.peerHeights[p] : p \in correctPeers} IN + CHOOSE max \in Hts: \A h \in Hts: h <= max + +\* properties to check +TypeOK == + /\ state \in States + /\ inMsg \in InMsgs + /\ outMsg \in OutMsgs + /\ turn \in {"Peers", "Node"} + /\ peersState \in [ + peerHeights: [AllPeerIds -> Heights \union {NilHeight}], + statusRequested: BOOLEAN, + blocksRequested: + SUBSET + [type: {"blockRequest"}, peerId: AllPeerIds, height: Heights] + + ] + /\ blockPool \in [ + height: Heights, + peerIds: SUBSET AllPeerIds, + peerHeights: [AllPeerIds -> Heights \union {NilHeight}], + blockStore: [Heights -> Blocks \union {NilBlock}], + receivedBlocks: [Heights -> AllPeerIds \union {NilPeer}], + pendingBlocks: [Heights -> AllPeerIds \union {NilPeer}], + syncedBlocks: Heights \union {NilHeight, -1}, + syncHeight: Heights + ] + +(* Incorrect synchronization: The last block may be never received *) +Sync1 == + [](state = "finished" => + blockPool.height >= MaxCorrectPeerHeight(blockPool)) + +Sync1AsInv == + state = "finished" => blockPool.height >= MaxCorrectPeerHeight(blockPool) + +(* Incorrect synchronization, as there may be a timeout *) +Sync2 == + \A p \in CORRECT: + \/ p \notin blockPool.peerIds + \/ [] (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) + +Sync2AsInv == + \A p \in CORRECT: + \/ p \notin blockPool.peerIds + \/ (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) + +(* Correct synchronization *) +Sync3 == + \A p \in CORRECT: + \/ p \notin blockPool.peerIds + \/ blockPool.syncedBlocks <= 0 \* timeout + \/ [] (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) + +Sync3AsInv == + \A p \in CORRECT: + \/ p \notin blockPool.peerIds + \/ blockPool.syncedBlocks <= 0 \* timeout + \/ (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) + +(* Naive termination *) +\* This property is violated, as the faulty peers may produce infinitely many responses +Termination == + WF_turn(FlipTurn) => <>(state = "finished") + +(* Termination by timeout: the protocol terminates, if there is a timeout *) +\* the precondition: fair flip turn and eventual timeout when no new blocks were synchronized +TerminationByTOPre == + /\ WF_turn(FlipTurn) + /\ <>(inMsg.type = "syncTimeout" /\ blockPool.height <= blockPool.syncHeight) + +TerminationByTO == + TerminationByTOPre => <>(state = "finished") + +(* The termination property when we only have correct peers *) +\* as correct peers may spam the node with addPeer, removePeer, and statusResponse, +\* we have to enforce eventual response (there are no queues in our spec) +CorrBlockResponse == + \A h \in Heights: + [](outMsg.type = "blockRequest" /\ outMsg.height = h + => <>(inMsg.type = "blockResponse" /\ inMsg.block.height = h)) + +\* a precondition for termination in presence of only correct processes +TerminationCorrPre == + /\ FAULTY = AsPidSet({}) + /\ WF_turn(FlipTurn) + /\ CorrBlockResponse + +\* termination when there are only correct processes +TerminationCorr == + TerminationCorrPre => <>(state = "finished") + +\* All synchronized blocks (but the last one) are exactly like in the reference chain +CorrectBlocksInv == + \/ state /= "finished" + \/ \A h \in 1..(blockPool.height - 1): + blockPool.blockStore[h] = chain[h] + +\* A false expectation that the protocol only finishes with the blocks +\* from the processes that had not been suspected in being faulty +SyncFromCorrectInv == + \/ state /= "finished" + \/ \A h \in 1..blockPool.height: + blockPool.receivedBlocks[h] \in blockPool.peerIds \union {NilPeer} + +\* A false expectation that a correct process is never removed from the set of peer ids. +\* A correct process may reply too late and then gets evicted. +CorrectNeverSuspectedInv == + CORRECT \subseteq blockPool.peerIds + +BlockPoolInvariant == + \A h \in Heights: + \* waiting for a block to arrive + \/ /\ blockPool.receivedBlocks[h] = NilPeer + /\ blockPool.blockStore[h] = NilBlock + \* valid block is received and is present in the store + \/ /\ blockPool.receivedBlocks[h] /= NilPeer + /\ blockPool.blockStore[h] /= NilBlock + /\ blockPool.pendingBlocks[h] = NilPeer + +(* a few simple properties that trigger counterexamples *) + +\* Shows execution in which peer set is empty +PeerSetIsNeverEmpty == blockPool.peerIds /= AsPidSet({}) + +\* Shows execution in which state = "finished" and MaxPeerHeight is not equal to 1 +StateNotFinished == + state /= "finished" \/ MaxPeerHeight(blockPool) = 1 + + +============================================================================= + +\*============================================================================= +\* Modification History +\* Last modified Fri May 29 20:41:53 CEST 2020 by igor +\* Last modified Thu Apr 16 16:57:22 CEST 2020 by zarkomilosevic +\* Created Tue Feb 04 10:36:18 CET 2020 by zarkomilosevic diff --git a/rust-spec/fastsync/scheduler.tla b/rust-spec/fastsync/scheduler.tla new file mode 100644 index 000000000..1de22e997 --- /dev/null +++ b/rust-spec/fastsync/scheduler.tla @@ -0,0 +1,606 @@ +------------------------------- MODULE scheduler ------------------------------- +(* + A specification of the fast sync scheduler that is introduced in blockchain/v2: + + https://github.com/tendermint/tendermint/tree/brapse/blockchain-v2-riri-reactor-2 + + The model includes: + - a scheduler that maintains the peers and blocks that it receives from the peers, and + - one environment simulating a correct peer + + This specification focuses on the events that are received and produced by the scheduler. + Communication between the scheduler and the other fastsync components is not specified. +*) + +EXTENDS Integers, FiniteSets + +\* the protocol parameters +CONSTANTS + PeerIDs, \* potential peer ids, a set of integers, e.g. 0..2 + ultimateHeight, \* the maximum height of the blockchain, an integer, e.g. 3 + numRequests \* the maximum number of requests made when scheduling new blocks, e.g. 2 + +\* a few definitions +None == -1 \* an undefined value +Heights == 0..ultimateHeight \* potential heights +noErr == "errNone" +Errors == { + noErr, "errPeerNotFound", "errDelRemovedPeer", "errAddDuplicatePeer", + "errUpdateRemovedPeer", "errAfterPeerRemove", "errBadPeer", "errBadPeerState", + "errProcessedBlockEv", "finished", "timeout", "errAddRemovedPeer", "errPeerNoBlock", "errBadBlockState"} + +PeerStates == {"peerStateUnknown", "peerStateNew", "peerStateReady", "peerStateRemoved"} + +BlockStates == { + "blockStateUnknown", "blockStateNew", "blockStatePending", + "blockStateReceived", "blockStateProcessed"} + +\* basic stuff +Min(a, b) == IF a < b THEN a ELSE b +Max(a, b) == IF a > b THEN a ELSE b + +\* the state of the scheduler: +VARIABLE turn \* who makes a step: the scheduler or the environment + +\* the state of the reactor: +VARIABLES inEvent, \* an event from the environment to the scheduler + envRunning \* a Boolean, negation of stopProcessing in the implementation + +\* the state of the scheduler: +VARIABLES outEvent, \* an event from the scheduler to the environment + scRunning + +\* the block pool: +VARIABLE scheduler + (* + scheduler is a record that contains: + height: Int, + height of the next block to collect + peers: PeerIDs, + the set of peers that have connected in the past, may include removed peers + peerHeights: [PeerIDs -> Heights], + a map to collect the peer heights, >0 if peer in ready state, None (-1) otherwise + peerStates: [PeerIDs -> PeerStates], + a map to record the peer states + blockStates: [Heights -> BlockStates] + a set of heights for which blocks are to be scheduled, pending or received + pendingBlocks: [Heights -> PeerIDs], + a set of heights for which blocks are to be scheduled or pending + receivedBlocks: [Heights -> PeerIDs], + a set of heights for which blocks were received but not yet processed + blocks: Heights, + the full set of blocks requested or downloaded by the scheduler + *) + +vars == <> + +\* for now just keep the height in the block +Blocks == [ height: Heights ] + +noEvent == [type |-> "NoEvent"] + +InEvents == + {noEvent} \cup + [type: {"rTrySchedule", "tNoAdvanceExp"}] \cup + [type: {"bcStatusResponse"}, peerID: PeerIDs, height: Heights] \cup + [type: {"bcBlockResponse"}, peerID: PeerIDs, height: Heights, block: Blocks] \cup + [type: {"bcNoBlockResponse"}, peerID: PeerIDs, height: Heights] \cup + [type: {"pcBlockProcessed"}, peerID: PeerIDs, height: Heights] \cup + [type: {"pcBlockVerificationFailure"}, height: Heights, firstPeerID: PeerIDs, secondPeerID: PeerIDs] \cup + [type: {"bcAddNewPeer"}, peerID: PeerIDs] \cup + [type: {"bcRemovePeer"}, peerID: PeerIDs] + +\* Output events produced by the scheduler. +\* Note: in v2 the status request is done by the reactor/ environment +OutEvents == + {noEvent} \cup + [type: {"scPeerError"}, peerID: PeerIDs, error: Errors] \cup + [type: {"scSchedulerFail"}, error: Errors] \cup + [type: {"scBlockRequest"}, peerID: PeerIDs, height: Heights] \cup + [type: {"scBlockReceived"}, peerID: PeerIDs, block: Blocks] \cup + [type: {"scPeersPruned"}, pruned: SUBSET [peerID: PeerIDs]] \cup + [type: {"scFinishedEv"}, error: Errors] + +(* ----------------------------------------------------------------------------------------------*) +(* The behavior of the scheduler that keeps track of peers, block requests and responses, etc. *) +(* See scheduler.go *) +(* https://github.com/tendermint/tendermint/blob/v0.33.3/blockchain/v2/scheduler.go *) +(* ----------------------------------------------------------------------------------------------*) + +addPeer(sc, peerID) == + IF peerID \in sc.peers THEN + [err |-> "errAddDuplicatePeer", val |-> sc] + ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN + [err |-> "errAddRemovedPeer", val |-> sc] + ELSE + LET newPeers == sc.peers \cup { peerID } IN + LET newPeerHeights == [sc.peerHeights EXCEPT ![peerID] = None] IN + LET newPeerStates == [sc.peerStates EXCEPT ![peerID] = "peerStateNew"] IN + LET newSc == [sc EXCEPT + !.peers = newPeers, + !.peerHeights = newPeerHeights, + !.peerStates = newPeerStates] IN + [err |-> noErr, val |-> newSc] + +maxHeight(states, heights) == + LET activePeers == {p \in DOMAIN states: states[p] = "peerStateReady"} IN + IF activePeers = {} THEN + 0 \* no peers, just return 0 + ELSE + CHOOSE max \in { heights[p] : p \in activePeers }: + \A p \in activePeers: heights[p] <= max \* max is the maximum + +maxHeightScheduler(sc) == + maxHeight(sc.peerStates, sc.peerHeights) + +removePeer(sc, peerID) == + IF peerID \notin sc.peers THEN + [err |-> "errPeerNotFound", val |-> sc] + ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN + [err |-> "errDelRemovedPeer", val |-> sc] + ELSE + LET newSc == [sc EXCEPT + !.peerHeights[peerID] = None, + !.peerStates[peerID] = "peerStateRemoved", + \* remove all blocks from peerID and block requests to peerID, see scheduler.removePeer + !.blockStates = [h \in Heights |-> + IF sc.pendingBlocks[h] = peerID \/ sc.receivedBlocks[h] = peerID THEN "blockStateNew" + ELSE sc.blockStates[h]], + !.pendingBlocks = [h \in Heights |-> IF sc.pendingBlocks[h] = peerID THEN None ELSE sc.pendingBlocks[h]], + !.receivedBlocks = [h \in Heights |-> IF sc.receivedBlocks[h] = peerID THEN None ELSE sc.receivedBlocks[h]] + ] IN + [err |-> noErr, val |-> newSc] + +addNewBlocks(sc, newMph) == + \* add new blocks to be requested (e.g. when overall max peer height has changed) + IF Cardinality(sc.blocks) >= numRequests THEN + [newBlocks |-> sc.blocks, newBlockStates |-> sc.blockStates] + ELSE + LET requestSet == sc.height.. Min(numRequests+sc.height, newMph) IN + LET heightsToRequest == {h \in requestSet: sc.blockStates[h] = "blockStateUnknown"} IN + LET newBlockStates == [ + h \in Heights |-> IF h \in heightsToRequest THEN "blockStateNew" + ELSE sc.blockStates[h]] IN + [newBlocks |-> heightsToRequest, newBlockStates |-> newBlockStates] + +\* Update the peer height (the peer should have been previously added) +setPeerHeight(sc, peerID, height) == + IF peerID \notin sc.peers THEN + [err |-> "errPeerNotFound", val |-> sc] + ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN + [err |-> "errUpdateRemovedPeer", val |-> sc] + ELSE IF height < sc.peerHeights[peerID] THEN (* The peer is corrupt? Remove the peer. *) + removePeer(sc, peerID) + ELSE + LET newPeerHeights == [sc.peerHeights EXCEPT ![peerID] = height] IN \* set the peer's height + LET newPeerStates == [sc.peerStates EXCEPT ![peerID] = "peerStateReady"] IN \* set the peer's state + LET newMph == maxHeight(newPeerStates, newPeerHeights) IN + LET res == addNewBlocks(sc, newMph) IN + LET newSc == [sc EXCEPT + !.peerHeights = newPeerHeights, !.peerStates = newPeerStates, + !.blocks = res.newBlocks, !.blockStates = res.newBlockStates] IN + [err |-> noErr, val |-> newSc] + +nextHeightToSchedule(sc) == + LET toBeScheduled == {h \in DOMAIN sc.blockStates: sc.blockStates[h] = "blockStateNew"} \cup {ultimateHeight+1} IN + CHOOSE minH \in toBeScheduled: \A h \in toBeScheduled: h >= minH + +getStateAtHeight(sc, h) == + IF h < sc.height THEN + "blockStateProcessed" + ELSE IF h \in DOMAIN sc.blockStates THEN + sc.blockStates[h] + ELSE + "blockStateUnknown" + +markPending(sc, peerID, h) == + IF getStateAtHeight(sc, h) /= "blockStateNew" THEN + [err |-> "errBadBlockState", val |-> sc] + ELSE IF peerID \notin sc.peers \/ sc.peerStates[peerID] /= "peerStateReady" THEN + [err |-> "errBadPeerState", val |-> sc] + ELSE IF h > sc.peerHeights[peerID] THEN + [err |-> "errPeerTooShort", val |-> sc] + ELSE + LET newSc == [sc EXCEPT + !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStatePending"], + !.pendingBlocks = [sc.pendingBlocks EXCEPT ![h] = peerID]] IN + [err |-> noErr, val |-> newSc] + +markReceived(sc, peerID, h) == + IF peerID \notin sc.peers \/ sc.peerStates[peerID] /= "peerStateReady" THEN + [err |-> "errBadPeerState", val |-> sc] + ELSE IF getStateAtHeight(sc, h) /= "blockStatePending" \/ sc.pendingBlocks[h] /= peerID THEN + [err |-> "errBadPeer", val |-> sc] + ELSE + LET newSc == [sc EXCEPT + !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStateReceived"], + !.pendingBlocks = [sc.pendingBlocks EXCEPT ![h] = None], + !.receivedBlocks = [sc.receivedBlocks EXCEPT ![h] = peerID]] IN + [err |-> noErr, val |-> newSc] + +markProcessed(sc, h) == + IF getStateAtHeight(sc, h) /= "blockStateReceived" THEN + [err |-> "errProcessedBlockEv", val |-> sc] + ELSE + LET newSc == [sc EXCEPT + !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStateProcessed"], + !.receivedBlocks = [sc.receivedBlocks EXCEPT ![h] = None], + !.height = sc.height + 1] IN + [err |-> noErr, val |-> newSc] + +reachedMaxHeight(sc) == + IF sc.peers = {} THEN + FALSE + ELSE + LET maxH == maxHeightScheduler(sc) IN + maxH > 0 /\ (sc.height >= maxH) + +highPeers(sc, minH) == {p \in sc.peers: sc.peerHeights[p] >= minH} + +(* ----------------------------------------------------------------------------------------------*) +(* The behavior of the scheduler state machine *) +(* See scheduler.go *) +(* https://github.com/tendermint/tendermint/tree/brapse/blockchain-v2-riri-reactor-2/scheduler.go*) +(* ----------------------------------------------------------------------------------------------*) +blStateInit(h, start) == + IF h <= start THEN + "blockStateProcessed" + ELSE "blockStateUnknown" + +InitSc == + /\ scRunning = TRUE + /\ outEvent = noEvent + /\ \E startHeight \in Heights: + scheduler = [ + initHeight |-> startHeight, + height |-> startHeight + 1, + peers |-> {}, + peerHeights |-> [p \in PeerIDs |-> None], + peerStates |-> [p \in PeerIDs |-> "peerStateUnknown"], + blocks |-> {}, + blockStates |-> [h \in Heights |-> blStateInit(h, startHeight)], + pendingBlocks |-> [h \in Heights |-> None], + receivedBlocks |-> [h \in Heights |-> None] + ] + +handleAddNewPeer == + /\ inEvent.type = "bcAddNewPeer" + /\ LET res == addPeer(scheduler, inEvent.peerID) IN + IF res.err /= noErr THEN + /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err] + /\ UNCHANGED <> + ELSE + /\ scheduler' = res.val + /\ UNCHANGED outEvent + /\ UNCHANGED scRunning + +finishSc(event) == + event.type = "scFinishedEv" /\ event.error = "finished" + +handleRemovePeer == + /\ inEvent.type = "bcRemovePeer" + /\ LET res == removePeer(scheduler, inEvent.peerID) IN + IF res.err /= noErr THEN + /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err] + /\ UNCHANGED scheduler + ELSE + /\ scheduler' = res.val + /\ IF reachedMaxHeight(scheduler') THEN + outEvent' = [type |-> "scFinishedEv", error |-> "errAfterPeerRemove"] + ELSE + UNCHANGED outEvent + /\ IF finishSc(outEvent') THEN + scRunning' = FALSE + ELSE UNCHANGED scRunning + +handleStatusResponse == + /\ inEvent.type = "bcStatusResponse" + /\ LET res == setPeerHeight(scheduler, inEvent.peerID, inEvent.height) IN + IF res.err /= noErr THEN + /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> res.err] + /\ UNCHANGED scheduler + ELSE + /\ scheduler' = res.val + /\ UNCHANGED outEvent + /\ UNCHANGED scRunning + +handleTrySchedule == \* every 10 ms, but our spec is asynchronous + /\ inEvent.type = "rTrySchedule" + /\ LET minH == nextHeightToSchedule(scheduler) IN + IF minH = ultimateHeight+1 THEN + /\ outEvent' = noEvent + /\ UNCHANGED scheduler + ELSE IF minH = ultimateHeight+1 THEN + /\ outEvent' = noEvent + /\ UNCHANGED scheduler + ELSE + /\ LET hp == highPeers(scheduler, minH) IN + IF hp = {} THEN + /\ outEvent' = noEvent + /\ UNCHANGED scheduler + ELSE \E bestPeerID \in hp: + /\ LET res == markPending(scheduler, bestPeerID, minH) IN + /\ IF res.err /= noErr THEN + outEvent' = [type |-> "scSchedulerFail", error|-> res.err] + ELSE + outEvent' = [type |-> "scBlockRequest", peerID |-> bestPeerID, height |-> minH] + /\ scheduler' = res.val + /\ UNCHANGED scRunning + +handleBlockResponse == + /\ inEvent.type = "bcBlockResponse" + /\ LET res == markReceived(scheduler, inEvent.peerID, inEvent.height) IN + IF res.err /= noErr THEN + LET res1 == removePeer(scheduler, inEvent.peerID) IN + /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> res.err] + /\ scheduler' = res1.val + ELSE + /\ outEvent' = [type |-> "scBlockReceived", peerID |-> inEvent.peerID, block |-> inEvent.block] + /\ scheduler' = res.val + /\ UNCHANGED scRunning + +handleNoBlockResponse == + /\ inEvent.type = "bcNoBlockResponse" + /\ IF (scheduler.peers = {} \/ scheduler.peerStates[inEvent.peerID] = "peerStateRemoved") THEN + /\ outEvent' = noEvent + /\ UNCHANGED scheduler + ELSE + LET res == removePeer(scheduler, inEvent.peerID) IN + /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> "errPeerNoBlock"] + /\ scheduler' = res.val + /\ UNCHANGED scRunning + +handleBlockProcessed == + /\ inEvent.type = "pcBlockProcessed" + /\ IF inEvent.height /= scheduler.height THEN + /\ outEvent' = [type |-> "scSchedulerFail", error |-> "errProcessedBlockEv"] + /\ UNCHANGED scheduler + ELSE + LET res == markProcessed(scheduler, inEvent.height) IN + IF res.err /= noErr THEN + /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err] + /\ UNCHANGED scheduler + ELSE + /\ scheduler' = res.val + /\ IF reachedMaxHeight(scheduler') THEN + outEvent' = [type |-> "scFinishedEv", error |-> "finished"] + ELSE + outEvent' = noEvent + /\ IF finishSc(outEvent') THEN + scRunning' = FALSE + ELSE UNCHANGED scRunning + +handleBlockProcessError == + /\ inEvent.type = "pcBlockVerificationFailure" + /\ IF scheduler.peers = {} THEN + /\ outEvent' = noEvent + /\ UNCHANGED scheduler + ELSE + LET res1 == removePeer(scheduler, inEvent.firstPeerID) IN + LET res2 == removePeer(res1.val, inEvent.secondPeerID) IN + /\ IF reachedMaxHeight(res2.val) THEN + outEvent' = [type |-> "scFinishedEv", error |-> "finished"] + ELSE + outEvent' = noEvent + /\ scheduler' = res2.val + /\ IF finishSc(outEvent') THEN + scRunning' = FALSE + ELSE UNCHANGED scRunning + +handleNoAdvanceExp == + /\ inEvent.type = "tNoAdvanceExp" + /\ outEvent' = [type |-> "scFinishedEv", error |-> "timeout"] + /\ scRunning' = FALSE + /\ UNCHANGED <> + +NextSc == + IF ~scRunning THEN + UNCHANGED <> + ELSE + \/ handleStatusResponse + \/ handleAddNewPeer + \/ handleRemovePeer + \/ handleTrySchedule + \/ handleBlockResponse + \/ handleNoBlockResponse + \/ handleBlockProcessed + \/ handleBlockProcessError + \/ handleNoAdvanceExp + +(* ----------------------------------------------------------------------------------------------*) +(* The behavior of the environment. *) +(* ----------------------------------------------------------------------------------------------*) + +InitEnv == + /\ inEvent = noEvent + /\ envRunning = TRUE + +OnGlobalTimeoutTicker == + /\ inEvent' = [type |-> "tNoAdvanceExp"] + /\ envRunning' = FALSE + +OnTrySchedule == + /\ inEvent' = [type |-> "rTrySchedule"] + /\ UNCHANGED envRunning + +OnAddPeerEv == + /\ inEvent' \in [type: {"bcAddNewPeer"}, peerID: PeerIDs] + /\ UNCHANGED envRunning + +OnStatusResponseEv == + \* any status response can come from the blockchain, pick one non-deterministically + /\ inEvent' \in [type: {"bcStatusResponse"}, peerID: PeerIDs, height: Heights] + /\ UNCHANGED envRunning + +OnBlockResponseEv == + \* any block response can come from the blockchain, pick one non-deterministically + /\ inEvent' \in [type: {"bcBlockResponse"}, peerID: PeerIDs, height: Heights, block: Blocks] + /\ UNCHANGED envRunning + +OnNoBlockResponseEv == + \* any no block response can come from the blockchain, pick one non-deterministically + /\ inEvent' \in [type: {"bcNoBlockResponse"}, peerID: PeerIDs, height: Heights] + /\ UNCHANGED envRunning + +OnRemovePeerEv == + \* although bcRemovePeer admits an arbitrary set, we produce just a singleton + /\ inEvent' \in [type: {"bcRemovePeer"}, peerID: PeerIDs] + /\ UNCHANGED envRunning + +OnPcBlockProcessed == + /\ inEvent' \in [type: {"pcBlockProcessed"}, peerID: PeerIDs, height: Heights] + /\ UNCHANGED envRunning + +OnPcBlockVerificationFailure == + /\ inEvent' \in [type: {"pcBlockVerificationFailure"}, firstPeerID: PeerIDs, secondPeerID: PeerIDs, height: Heights] + /\ UNCHANGED envRunning + +\* messages from scheduler +OnScFinishedEv == + /\ outEvent.type = "scFinishedEv" + /\ envRunning' = FALSE \* stop the env + /\ UNCHANGED inEvent + +NextEnv == + IF ~envRunning THEN + UNCHANGED <> + ELSE + \/ OnScFinishedEv + \/ OnGlobalTimeoutTicker + \/ OnAddPeerEv + \/ OnTrySchedule + \/ OnStatusResponseEv + \/ OnBlockResponseEv + \/ OnNoBlockResponseEv + \/ OnRemovePeerEv + \/ OnPcBlockProcessed + \/ OnPcBlockVerificationFailure + +(* ----------------------------------------------------------------------------------------------*) +(* The system is the composition of the environment and the schedule *) +(* ----------------------------------------------------------------------------------------------*) +Init == turn = "environment" /\ InitEnv /\ InitSc + +FlipTurn == + turn' = ( + IF turn = "scheduler" THEN + "environment" + ELSE + "scheduler" + ) + +\* scheduler and environment alternate their steps (synchronous composition introduces more states) +Next == +/\ FlipTurn +/\ IF turn = "scheduler" THEN + /\ NextSc + /\ inEvent' = noEvent + /\ UNCHANGED envRunning + ELSE + /\ NextEnv + /\ outEvent' = noEvent + /\ UNCHANGED <> + +Spec == Init /\ [][Next]_vars /\ WF_turn(FlipTurn) + +(* ----------------------------------------------------------------------------------------------*) +(* Invariants *) +(* ----------------------------------------------------------------------------------------------*) +TypeOK == + /\ turn \in {"scheduler", "environment"} + /\ inEvent \in InEvents + /\ envRunning \in BOOLEAN + /\ outEvent \in OutEvents + /\ scheduler \in [ + initHeight: Heights, + height: Heights \cup {ultimateHeight + 1}, + peers: SUBSET PeerIDs, + peerHeights: [PeerIDs -> Heights \cup {None}], + peerStates: [PeerIDs -> PeerStates], + blocks: SUBSET Heights, + blockStates: [Heights -> BlockStates], + pendingBlocks: [Heights -> PeerIDs \cup {None}], + receivedBlocks: [Heights -> PeerIDs \cup {None}] + ] + +(* ----------------------------------------------------------------------------------------------*) +(* Helpers for Properties *) +(* ----------------------------------------------------------------------------------------------*) +NoFailuresAndTimeouts == + /\ inEvent.type /= "bcRemovePeer" + /\ inEvent.type /= "bcNoBlockResponse" + /\ inEvent.type /= "pcBlockVerificationFailure" + +\* simulate good peer behavior using this formula. Useful to show termination in the presence of good peers. +GoodResponse == + \/ inEvent.type + \in {"bcAddNewPeer", "bcStatusResponse", "bcBlockResponse", "pcBlockProcessed", "rTrySchedule"} + \/ ~envRunning + +\* all blocks from initHeight up to max peer height have been processed +AllRequiredBlocksProcessed == + LET maxH == Max(scheduler.height, maxHeightScheduler(scheduler)) IN + LET processedBlocks == {h \in scheduler.initHeight.. maxH-1: scheduler.blockStates[h] = "blockStateProcessed"} IN + scheduler.height >= maxH /\ Cardinality(processedBlocks) = scheduler.height - scheduler.initHeight + +IncreaseHeight == + (scheduler'.height > scheduler.height) \/ (scheduler.height >= ultimateHeight) + +(* ----------------------------------------------------------------------------------------------*) +(* Expected properties *) +(* ----------------------------------------------------------------------------------------------*) +(* *) +(* 1. TerminationWhenNoAdvance - termination if there are no correct peers. *) +(* The model assumes the "noAdvance" timer expires and the "tNoAdvanceExp" event is received *) +(* *) +TerminationWhenNoAdvance == + (inEvent.type = "tNoAdvanceExp") + => <>(outEvent.type = "scFinishedEv" /\ outEvent.error = "timeout") + +(* ----------------------------------------------------------------------------------------------*) +(* *) +(* 2. TerminationGoodPeers - *) +(* termination when IncreaseHeight holds true, fastsync is progressing, all blocks processed *) +(* *) +TerminationGoodPeers == + (/\ scheduler.height < ultimateHeight + /\ <>[]GoodResponse + /\[]<>(<>_<>) + ) + => <>(outEvent.type = "scFinishedEv" /\ AllRequiredBlocksProcessed) + +(* This property is violated. It shows that the precondition of TerminationGoodPeers is not *) +(* always FALSE *) +TerminationGoodPeersPre == + (/\ scheduler.height < ultimateHeight + /\ <>[]GoodResponse + /\[]<>(<>_<>) + ) + => FALSE + +(* ----------------------------------------------------------------------------------------------*) +(* 3. TerminationAllCases - *) +(* any peer behavior, either terminates with all blocks processed or times out *) +TerminationAllCases == + (/\ scheduler.height < ultimateHeight + /\(([]<> (<>_<>)) \/ <>(inEvent.type = "tNoAdvanceExp")) + ) + => <>(outEvent.type = "scFinishedEv" /\ (AllRequiredBlocksProcessed \/ outEvent.error = "timeout")) + +(* This property is violated. It shows that the precondition of TerminationAllCases is not *) +(* always FALSE *) +TerminationAllCasesPre == + (/\ scheduler.height < ultimateHeight + /\(([]<> (<>_<>)) \/ <>(inEvent.type = "tNoAdvanceExp")) + ) + => FALSE + +(* This property is violated. TLC output shows an example of increasing heights in the scheduler *) +SchedulerIncreasePre == +[]<>(<>_<>) + => FALSE + +============================================================================= +\* Modification History +\* Last modified Thu Apr 16 13:21:33 CEST 2020 by ancaz +\* Created Sat Feb 08 13:12:30 CET 2020 by ancaz diff --git a/rust-spec/fastsync/verification/001bmc-apalache.csv b/rust-spec/fastsync/verification/001bmc-apalache.csv new file mode 100644 index 000000000..e83c4c96f --- /dev/null +++ b/rust-spec/fastsync/verification/001bmc-apalache.csv @@ -0,0 +1,13 @@ +no,filename,tool,timeout,init,inv,next,args +1,MC_1_1_4.tla,apalache,1h,,Sync1AsInv,,--length=20 +2,MC_1_1_4.tla,apalache,1h,,Sync2AsInv,,--length=20 +3,MC_1_1_4.tla,apalache,4h,,Sync3AsInv,,--length=20 +4,MC_1_1_4.tla,apalache,8h,,CorrectBlocksInv,,--length=16 +5,MC_1_1_4.tla,apalache,1h,,SyncFromCorrectInv,,--length=20 +6,MC_1_1_4.tla,apalache,1h,,CorrectNeverSuspectedInv,,--length=20 +7,MC_1_2_4.tla,apalache,1h,,Sync1AsInv,,--length=20 +8,MC_1_2_4.tla,apalache,1h,,Sync2AsInv,,--length=20 +9,MC_1_2_4.tla,apalache,4h,,Sync3AsInv,,--length=20 +10,MC_1_2_4.tla,apalache,8h,,CorrectBlocksInv,,--length=16 +11,MC_1_2_4.tla,apalache,1h,,SyncFromCorrectInv,,--length=20 +12,MC_1_2_4.tla,apalache,1h,,CorrectNeverSuspectedInv,,--length=20 diff --git a/rust-spec/fastsync/verification/002tlc-tlc.csv b/rust-spec/fastsync/verification/002tlc-tlc.csv new file mode 100644 index 000000000..2e5429d48 --- /dev/null +++ b/rust-spec/fastsync/verification/002tlc-tlc.csv @@ -0,0 +1,9 @@ +no,filename,tool,timeout,init,inv,next,args +1,MC_1_1_4.tla,tlc,1h,,,,-workers 4 -config MC-Sync1AsInv.cfg +2,MC_1_1_4.tla,tlc,1h,,,,-workers 4 -config MC-Sync2AsInv.cfg +3,MC_1_1_4.tla,tlc,24h,,,,-workers 4 -config MC-Sync3AsInv.cfg +4,MC_1_1_4.tla,tlc,2h,,Termination,,-workers 4 -config MC-Termination.cfg +5,MC_1_1_4.tla,tlc,24h,,TerminationByTO,,-workers 4 -config MC-TerminationByTO.cfg +6,MC_1_1_4.tla,tlc,24h,,,,-workers 4 -config MC-CorrectBlocksInv.cfg +7,MC_1_1_4.tla,tlc,1h,,,,-workers 4 -config MC-SyncFromCorrectInv.cfg +8,MC_1_1_4.tla,tlc,1h,,,,-workers 4 -config MC-CorrectNeverSuspectedInv.cfg diff --git a/rust-spec/fastsync/verification/MC-CorrectBlocksInv.cfg b/rust-spec/fastsync/verification/MC-CorrectBlocksInv.cfg new file mode 100644 index 000000000..3e80ef165 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-CorrectBlocksInv.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* INVARIANT definition +INVARIANT +CorrectBlocksInv +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-CorrectNeverSuspectedInv.cfg b/rust-spec/fastsync/verification/MC-CorrectNeverSuspectedInv.cfg new file mode 100644 index 000000000..8a4606a86 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-CorrectNeverSuspectedInv.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* INVARIANT definition +INVARIANT +CorrectNeverSuspectedInv +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-Sync1AsInv.cfg b/rust-spec/fastsync/verification/MC-Sync1AsInv.cfg new file mode 100644 index 000000000..b557fb8b1 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-Sync1AsInv.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* INVARIANT definition +INVARIANT +Sync1AsInv +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-Sync2AsInv.cfg b/rust-spec/fastsync/verification/MC-Sync2AsInv.cfg new file mode 100644 index 000000000..3308dc805 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-Sync2AsInv.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* INVARIANT definition +INVARIANT +Sync2AsInv +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-Sync3AsInv.cfg b/rust-spec/fastsync/verification/MC-Sync3AsInv.cfg new file mode 100644 index 000000000..e2d3db6e4 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-Sync3AsInv.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* INVARIANT definition +INVARIANT +Sync3AsInv +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-SyncFromCorrectInv.cfg b/rust-spec/fastsync/verification/MC-SyncFromCorrectInv.cfg new file mode 100644 index 000000000..b17317633 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-SyncFromCorrectInv.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* INVARIANT definition +INVARIANT +SyncFromCorrectInv +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-Termination.cfg b/rust-spec/fastsync/verification/MC-Termination.cfg new file mode 100644 index 000000000..00e8236e7 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-Termination.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* PROPERTY definition +PROPERTY +Termination +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-TerminationByTO.cfg b/rust-spec/fastsync/verification/MC-TerminationByTO.cfg new file mode 100644 index 000000000..3a871ccf9 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-TerminationByTO.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* PROPERTY definition +PROPERTY +TerminationByTO +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-TerminationCorr.cfg b/rust-spec/fastsync/verification/MC-TerminationCorr.cfg new file mode 100644 index 000000000..bea493637 --- /dev/null +++ b/rust-spec/fastsync/verification/MC-TerminationCorr.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +Init +\* NEXT definition +NEXT +Next +\* PROPERTY definition +PROPERTY +TerminationCorr +\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC_1_0_4.tla b/rust-spec/fastsync/verification/MC_1_0_4.tla new file mode 100644 index 000000000..b4ab71228 --- /dev/null +++ b/rust-spec/fastsync/verification/MC_1_0_4.tla @@ -0,0 +1,17 @@ +--------------------------- MODULE MC_1_0_4 ------------------------------------ + +\*a <: b == a \* type annotation + +VALIDATOR_SETS == {"vs1", "vs2"} +NIL_VS == "NilVS" +CORRECT == {"c1"} +FAULTY == {"f2"} \ {"f2"} +MAX_HEIGHT == 4 +PEER_MAX_REQUESTS == 2 +TARGET_PENDING == 3 + +VARIABLES + state, blockPool, peersState, chain, turn, inMsg, outMsg + +INSTANCE fastsync_apalache +================================================================================ diff --git a/rust-spec/fastsync/verification/MC_1_1_4.tla b/rust-spec/fastsync/verification/MC_1_1_4.tla new file mode 100644 index 000000000..9797fabfc --- /dev/null +++ b/rust-spec/fastsync/verification/MC_1_1_4.tla @@ -0,0 +1,15 @@ +--------------------------- MODULE MC_1_1_4 ------------------------------------ + +VALIDATOR_SETS == {"vs1", "vs2"} +NIL_VS == "NilVS" +CORRECT == {"c1"} +FAULTY == {"f2"} +MAX_HEIGHT == 4 +PEER_MAX_REQUESTS == 2 +TARGET_PENDING == 3 + +VARIABLES + state, blockPool, peersState, chain, turn, inMsg, outMsg + +INSTANCE fastsync_apalache +================================================================================ diff --git a/rust-spec/fastsync/verification/MC_1_2_4.tla b/rust-spec/fastsync/verification/MC_1_2_4.tla new file mode 100644 index 000000000..8e9dc2cc9 --- /dev/null +++ b/rust-spec/fastsync/verification/MC_1_2_4.tla @@ -0,0 +1,15 @@ +-------------------------- MODULE MC_1_2_4 ------------------------------------ + +VALIDATOR_SETS == {"vs1", "vs2"} +NIL_VS == "NilVS" +CORRECT == {"c1"} +FAULTY == {"f2", "f3"} +MAX_HEIGHT == 4 +PEER_MAX_REQUESTS == 2 +TARGET_PENDING == 3 + +VARIABLES + state, blockPool, peersState, chain, turn, inMsg, outMsg + +INSTANCE fastsync_apalache +=============================================================================== diff --git a/rust-spec/fastsync/verification/MC_1_2_5.tla b/rust-spec/fastsync/verification/MC_1_2_5.tla new file mode 100644 index 000000000..1bd335082 --- /dev/null +++ b/rust-spec/fastsync/verification/MC_1_2_5.tla @@ -0,0 +1,15 @@ +-------------------------- MODULE MC_1_2_5 ------------------------------------ + +VALIDATOR_SETS == {"vs1", "vs2"} +NIL_VS == "NilVS" +CORRECT == {"c1"} +FAULTY == {"f2", "f3"} +MAX_HEIGHT == 5 +PEER_MAX_REQUESTS == 2 +TARGET_PENDING == 3 + +VARIABLES + state, blockPool, peersState, chain, turn, inMsg, outMsg + +INSTANCE fastsync_apalache +=============================================================================== diff --git a/rust-spec/fastsync/verification/MC_1_3_5.tla b/rust-spec/fastsync/verification/MC_1_3_5.tla new file mode 100644 index 000000000..81dadb470 --- /dev/null +++ b/rust-spec/fastsync/verification/MC_1_3_5.tla @@ -0,0 +1,15 @@ +------------------------------- MODULE MC_1_3_5 ------------------------------------ + +MAX_HEIGHT == 5 +VALIDATOR_SETS == {"vs1", "vs2"} +NIL_VS == "NilVS" +CORRECT == {"c1"} +FAULTY == {"f2", "f3", "f4"} +PEER_MAX_REQUESTS == 2 +TARGET_PENDING == 3 + +VARIABLES + state, blockPool, peersState, chain, turn, inMsg, outMsg + +INSTANCE fastsync_apalache +======================================================================================== diff --git a/rust-spec/fastsync/verification/MC_2_0_4.tla b/rust-spec/fastsync/verification/MC_2_0_4.tla new file mode 100644 index 000000000..b39dcd608 --- /dev/null +++ b/rust-spec/fastsync/verification/MC_2_0_4.tla @@ -0,0 +1,17 @@ +--------------------------- MODULE MC_2_0_4 ------------------------------------ + +a <: b == a \* type annotation + +VALIDATOR_SETS == {"vs1", "vs2"} +NIL_VS == "NilVS" +CORRECT == {"c1", "c2"} +FAULTY == {} <: {STRING} +MAX_HEIGHT == 4 +PEER_MAX_REQUESTS == 2 +TARGET_PENDING == 3 + +VARIABLES + state, blockPool, peersState, chain, turn, inMsg, outMsg + +INSTANCE fastsync_apalache +================================================================================ diff --git a/rust-spec/fastsync/verification/Tinychain.tla b/rust-spec/fastsync/verification/Tinychain.tla new file mode 100644 index 000000000..b8a212996 --- /dev/null +++ b/rust-spec/fastsync/verification/Tinychain.tla @@ -0,0 +1,110 @@ +-------------------------- MODULE Tinychain ---------------------------------- +(* A very abstract model of Tendermint blockchain. Its only purpose is to highlight + the relation between validator sets, next validator sets, and last commits. + *) + +EXTENDS Integers + +\* type annotation +a <: b == a + +\* the type of validator sets, e.g., STRING +VST == STRING + +\* LastCommit type. +\* It contains: +\* 1) the flag of whether the block id equals to the hash of the previous block, and +\* 2) the set of the validators who have committed the block. +\* In the implementation, blockId is the hash of the previous block, which cannot be forged. +\* We abstract block id into whether it equals to the hash of the previous block or not. +LCT == [blockIdEqRef |-> BOOLEAN, committers |-> VST] + +\* Block type. +\* A block contains its height, validator set, next validator set, and last commit. +\* Moreover, it contains the flag that tells us whether the block equals to the one +\* on the reference chain (this is an abstraction of hash). +BT == [height |-> Int, hashEqRef |-> BOOLEAN, wellFormed |-> BOOLEAN, + VS |-> VST, NextVS |-> VST, lastCommit |-> LCT] + +CONSTANTS + (* + A set of abstract values, each value representing a set of validators. + For the purposes of this specification, they can be any values, + e.g., "s1", "s2", etc. + *) + VALIDATOR_SETS, + (* a nil validator set that is outside of VALIDATOR_SETS *) + NIL_VS, + (* The maximal height, up to which the blockchain may grow *) + MAX_HEIGHT + +Heights == 1..MAX_HEIGHT + +\* the set of all possible commits +Commits == [blockIdEqRef: BOOLEAN, committers: VALIDATOR_SETS] + +\* the set of all possible blocks, not necessarily valid ones +Blocks == + [height: Heights, hashEqRef: BOOLEAN, wellFormed: BOOLEAN, + VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: Commits] + +\* Does the chain contain a sound sequence of blocks that could be produced by +\* a 2/3 of faulty validators. This operator can be used to initialise the chain! +IsCorrectChain(chain) == + \* restrict the structure of the blocks, to decrease the TLC search space + LET OkCommits == [blockIdEqRef: {TRUE}, committers: VALIDATOR_SETS] + OkBlocks == [height: Heights, hashEqRef: {TRUE}, wellFormed: {TRUE}, + VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: OkCommits] + IN + /\ chain \in [1..MAX_HEIGHT -> OkBlocks] + /\ \A h \in 1..MAX_HEIGHT: + LET b == chain[h] IN + /\ b.height = h \* the height is correct + /\ h > 1 => + LET p == chain[h - 1] IN + /\ b.VS = p.NextVS \* the validators propagate from the previous block + /\ b.lastCommit.committers = p.VS \* and they are the committers + + +\* The basic properties of blocks on the blockchain: +\* They should pass the validity check and they may verify the next block. + +\* Does the block pass the consistency check against the next validators of the previous block +IsMatchingValidators(block, nextVS) == + \* simply check that the validator set is propagated correctly. + \* (the implementation tests hashes and the application state) + block.VS = nextVS + +\* Does the block verify the commit (of the next block) +PossibleCommit(block, commit) == + \* the commits are signed by the block validators + /\ commit.committers = block.VS + \* The block id in the commit matches the block hash (abstract comparison). + \* (The implementation has extensive tests for that.) + \* this is an abstraction of: commit.blockId = hash(block) + \* + \* These are possible scenarios on the concrete hashes: + \* + \* scenario 1: commit.blockId = 10 /\ hash(block) = 10 /\ hash(ref) = 10 + \* scenario 2: commit.blockId = 20 /\ hash(block) = 20 /\ block.VS /= ref.VS + \* scenario 3: commit.blockId = 50 /\ hash(block) = 100 + \* scenario 4: commit.blockId = 10 /\ hash(block) = 100 + \* scenario 5: commit.blockId = 100 /\ hash(block) = 10 + /\ commit.blockIdEqRef = block.hashEqRef + \* the following test would be cheating, as we do not have access to the + \* reference chain: + \* /\ commit.blockIdEqRef + +\* Basic invariants + +\* every block has the validator set that is chosen by its predecessor +ValidBlockInv(chain) == + \A h \in 2..MAX_HEIGHT: + IsMatchingValidators(chain[h], chain[h - 1].NextVS) + +\* last commit of every block is signed by the validators of the predecessor +VerifiedBlockInv(chain) == + \A h \in 2..MAX_HEIGHT: + PossibleCommit(chain[h - 1], chain[h].lastCommit) + +================================================================================== diff --git a/rust-spec/fastsync/verification/fastsync_apalache.tla b/rust-spec/fastsync/verification/fastsync_apalache.tla new file mode 100644 index 000000000..51dd54e57 --- /dev/null +++ b/rust-spec/fastsync/verification/fastsync_apalache.tla @@ -0,0 +1,822 @@ +----------------------------- MODULE fastsync_apalache ----------------------------- +(* + In this document we give the high level specification of the fast sync + protocol as implemented here: + https://github.com/tendermint/tendermint/tree/master/blockchain/v2. + +We assume a system in which one node is trying to sync with the blockchain +(replicated state machine) by downloading blocks from the set of full nodes +(we call them peers) that are block providers, and executing transactions +(part of the block) against the application. + +Peers can be faulty, and we don't make any assumption about the rate of correct/faulty +nodes in the node peerset (i.e., they can all be faulty). Correct peers are part +of the replicated state machine, i.e., they manage blockchain and execute +transactions against the same deterministic application. We don't make any +assumptions about the behavior of faulty processes. Processes (client and peers) +communicate by message passing. + + In this specification, we model this system with two parties: + - the node (state machine) that is doing fastsync and + - the environment with which node interacts. + +The environment consists of the set of (correct and faulty) peers with +which node interacts as part of fast sync protocol, but also contains some +aspects (adding/removing peers, timeout mechanism) that are part of the node +local environment (could be seen as part of the runtime in which node +executes). + +As part of the fast sync protocol a node and the peers exchange the following messages: + +- StatusRequest +- StatusResponse +- BlockRequest +- BlockResponse +- NoBlockResponse. + +A node is periodically issuing StatusRequests to query peers for their current height (to decide what +blocks to ask from what peers). Based on StatusResponses (that are sent by peers), the node queries +blocks for some height(s) by sending peers BlockRequest messages. A peer provides a requested block by +BlockResponse message. If a peer does not want to provide a requested block, then it sends NoBlockResponse message. +In addition to those messages, a node in this spec receives additional input messages (events): + +- AddPeer +- RemovePeer +- SyncTimeout. + +These are the control messages that are provided to the node by its execution enviornment. AddPeer +is for the case when a connection is established with a peer; similarly RemovePeer is for the case +a connection with the peer is terminated. Finally SyncTimeout is used to model a timeout trigger. + +We assume that fast sync protocol starts when connections with some number of peers +are established. Therefore, peer set is initialised with non-empty set of peer ids. Note however +that node does not know initially the peer heights. +*) + +EXTENDS Integers, FiniteSets, Sequences + + +CONSTANTS MAX_HEIGHT, \* the maximal height of blockchain + VALIDATOR_SETS, \* abstract set of validators + NIL_VS, \* a nil validator set + CORRECT, \* set of correct peers + FAULTY, \* set of faulty peers + TARGET_PENDING, \* maximum number of pending requests + downloaded blocks that are not yet processed + PEER_MAX_REQUESTS \* maximum number of pending requests per peer + +ASSUME CORRECT \intersect FAULTY = {} +ASSUME TARGET_PENDING > 0 +ASSUME PEER_MAX_REQUESTS > 0 + +\* the blockchain, see Tinychain +VARIABLE chain + +\* introduce tiny chain as the source of blocks for the correct nodes +INSTANCE Tinychain + +\* a special value for an undefined height +NilHeight == 0 + +\* the height of the genesis block +TrustedHeight == 1 + +\* the set of all peer ids the node can receive a message from +AllPeerIds == CORRECT \union FAULTY + +\* Correct last commit have enough voting power, i.e., +2/3 of the voting power of +\* the corresponding validator set (enoughVotingPower = TRUE) that signs blockId. +\* BlockId defines correct previous block (in the implementation it is the hash of the block). +\* Instead of blockId, we encode blockIdEqRef, which is true, if the block id is equal +\* to the hash of the previous block, see Tinychain. +CorrectLastCommit(h) == chain[h].lastCommit + +NilCommit == [blockIdEqRef |-> FALSE, committers |-> NIL_VS] + +\* correct node always supplies the blocks from the blockchain +CorrectBlock(h) == chain[h] + +NilBlock == + [height |-> 0, hashEqRef |-> FALSE, wellFormed |-> FALSE, + lastCommit |-> NilCommit, VS |-> NIL_VS, NextVS |-> NIL_VS] + +\* a special value for an undefined peer +NilPeer == "Nil" \* STRING for apalache efficiency + +\* control the state of the syncing node +States == { "running", "finished"} + +NoMsg == [type |-> "None"] + +\* the variables of the node running fastsync +VARIABLES + state, \* running or finished + (* + blockPool [ + height, \* current height we are trying to sync. Last block executed is height - 1 + peerIds, \* set of peers node is connected to + peerHeights, \* map of peer ids to its (stated) height + blockStore, \* map of heights to (received) blocks + receivedBlocks, \* map of heights to peer that has sent us the block (stored in blockStore) + pendingBlocks, \* map of heights to peer to which block request has been sent + syncHeight, \* height at the point syncTimeout was triggered last time + syncedBlocks \* number of blocks synced since last syncTimeout. If it is 0 when the next timeout occurs, then protocol terminates. + ] + *) + blockPool + + +\* the variables of the peers providing blocks +VARIABLES + (* + peersState [ + peerHeights, \* track peer heights + statusRequested, \* boolean set to true when StatusRequest is received. Models periodic sending of StatusRequests. + blocksRequested \* set of BlockRequests received that are not answered yet + ] + *) + peersState + + \* the variables for the network and scheduler +VARIABLES + turn, \* who is taking the turn: "Peers" or "Node" + inMsg, \* a node receives message by this variable + outMsg \* a node sends a message by this variable + + +(* the variables of the node *) +nvars == <> + +(*************** Type definitions for Apalache (model checker) **********************) +AsIntSet(S) == S <: {Int} + +\* type of process ids +PIDT == STRING +AsPidSet(S) == S <: {PIDT} + +\* ControlMessage type +CMT == [type |-> STRING, peerId |-> PIDT] \* type of control messages + +\* InMsg type +IMT == [type |-> STRING, peerId |-> PIDT, height |-> Int, block |-> BT] +AsInMsg(m) == m <: IMT +AsInMsgSet(S) == S <: {IMT} + +\* OutMsg type +OMT == [type |-> STRING, peerId |-> PIDT, height |-> Int] +AsOutMsg(m) == m <: OMT +AsOutMsgSet(S) == S <: {OMT} + +\* block pool type +BPT == [height |-> Int, peerIds |-> {PIDT}, peerHeights |-> [PIDT -> Int], + blockStore |-> [Int -> BT], receivedBlocks |-> [Int -> PIDT], + pendingBlocks |-> [Int -> PIDT], syncedBlocks |-> Int, syncHeight |-> Int] + +AsBlockPool(bp) == bp <: BPT + +(******************** Sets of messages ********************************) + +\* Control messages +ControlMsgs == + AsInMsgSet([type: {"addPeer"}, peerId: AllPeerIds]) + \union + AsInMsgSet([type: {"removePeer"}, peerId: AllPeerIds]) + \union + AsInMsgSet([type: {"syncTimeout"}]) + +\* All messages (and events) received by a node +InMsgs == + AsInMsgSet({NoMsg}) + \union + AsInMsgSet([type: {"blockResponse"}, peerId: AllPeerIds, block: Blocks]) + \union + AsInMsgSet([type: {"noBlockResponse"}, peerId: AllPeerIds, height: Heights]) + \union + AsInMsgSet([type: {"statusResponse"}, peerId: AllPeerIds, height: Heights]) + \union + ControlMsgs + +\* Messages sent by a node and received by peers (environment in our case) +OutMsgs == + AsOutMsgSet({NoMsg}) + \union + AsOutMsgSet([type: {"statusRequest"}]) \* StatusRequest is broadcast to the set of connected peers. + \union + AsOutMsgSet([type: {"blockRequest"}, peerId: AllPeerIds, height: Heights]) + + +(********************************** NODE ***********************************) + +InitNode == + \E pIds \in SUBSET AllPeerIds: \* set of peers node established initial connections with + /\ pIds \subseteq CORRECT + /\ pIds /= AsPidSet({}) \* apalache better checks non-emptiness than subtracts from SUBSET + /\ blockPool = AsBlockPool([ + height |-> TrustedHeight + 1, \* the genesis block is at height 1 + syncHeight |-> TrustedHeight + 1, \* and we are synchronized to it + peerIds |-> pIds, + peerHeights |-> [p \in AllPeerIds |-> NilHeight], \* no peer height is known + blockStore |-> + [h \in Heights |-> + IF h > TrustedHeight THEN NilBlock ELSE chain[1]], + receivedBlocks |-> [h \in Heights |-> NilPeer], + pendingBlocks |-> [h \in Heights |-> NilPeer], + syncedBlocks |-> -1 + ]) + /\ state = "running" + +\* Remove faulty peers. +\* Returns new block pool. +\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L222 +RemovePeers(rmPeers, bPool) == + LET keepPeers == bPool.peerIds \ rmPeers IN + LET pHeights == + [p \in AllPeerIds |-> IF p \in rmPeers THEN NilHeight ELSE bPool.peerHeights[p]] IN + + LET failedRequests == + {h \in Heights: /\ h >= bPool.height + /\ \/ bPool.pendingBlocks[h] \in rmPeers + \/ bPool.receivedBlocks[h] \in rmPeers} IN + LET pBlocks == + [h \in Heights |-> IF h \in failedRequests THEN NilPeer ELSE bPool.pendingBlocks[h]] IN + LET rBlocks == + [h \in Heights |-> IF h \in failedRequests THEN NilPeer ELSE bPool.receivedBlocks[h]] IN + LET bStore == + [h \in Heights |-> IF h \in failedRequests THEN NilBlock ELSE bPool.blockStore[h]] IN + + IF keepPeers /= bPool.peerIds + THEN [bPool EXCEPT + !.peerIds = keepPeers, + !.peerHeights = pHeights, + !.pendingBlocks = pBlocks, + !.receivedBlocks = rBlocks, + !.blockStore = bStore + ] + ELSE bPool + +\* Add a peer. +\* see https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L198 +AddPeer(peer, bPool) == + [bPool EXCEPT !.peerIds = bPool.peerIds \union {peer}] + + +(* +Handle StatusResponse message. +If valid status response, update peerHeights. +If invalid (height is smaller than the current), then remove peer. +Returns new block pool. +See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L667 +*) +HandleStatusResponse(msg, bPool) == + LET peerHeight == bPool.peerHeights[msg.peerId] IN + + IF /\ msg.peerId \in bPool.peerIds + /\ msg.height >= peerHeight + THEN \* a correct response + LET pHeights == [bPool.peerHeights EXCEPT ![msg.peerId] = msg.height] IN + [bPool EXCEPT !.peerHeights = pHeights] + ELSE RemovePeers({msg.peerId}, bPool) \* the peer has sent us message with smaller height or peer is not in our peer list + + +(* +Handle BlockResponse message. +If valid block response, update blockStore, pendingBlocks and receivedBlocks. +If invalid (unsolicited response or malformed block), then remove peer. +Returns new block pool. +See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L522 +*) +HandleBlockResponse(msg, bPool) == + LET h == msg.block.height IN + + IF /\ msg.peerId \in bPool.peerIds + /\ bPool.blockStore[h] = NilBlock + /\ bPool.pendingBlocks[h] = msg.peerId + /\ msg.block.wellFormed + THEN + [bPool EXCEPT + !.blockStore = [bPool.blockStore EXCEPT ![h] = msg.block], + !.receivedBlocks = [bPool.receivedBlocks EXCEPT![h] = msg.peerId], + !.pendingBlocks = [bPool.pendingBlocks EXCEPT![h] = NilPeer] + ] + ELSE RemovePeers({msg.peerId}, bPool) + + HandleNoBlockResponse(msg, bPool) == + RemovePeers({msg.peerId}, bPool) + + +\* Compute max peer height. +\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L440 +MaxPeerHeight(bPool) == + IF bPool.peerIds = AsPidSet({}) + THEN 0 \* no peers, just return 0 + ELSE LET Hts == {bPool.peerHeights[p] : p \in bPool.peerIds} IN + CHOOSE max \in Hts: \A h \in Hts: h <= max + +(* Returns next height for which request should be sent. + Returns NilHeight in case there is no height for which request can be sent. + See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L454 *) +FindNextRequestHeight(bPool) == + LET S == {i \in Heights: + /\ i >= bPool.height + /\ i <= MaxPeerHeight(bPool) + /\ bPool.blockStore[i] = NilBlock + /\ bPool.pendingBlocks[i] = NilPeer} IN + IF S = AsIntSet({}) + THEN NilHeight + ELSE + CHOOSE min \in S: \A h \in S: h >= min + +\* Returns number of pending requests for a given peer. +NumOfPendingRequests(bPool, peer) == + LET peerPendingRequests == + {h \in Heights: + /\ h >= bPool.height + /\ bPool.pendingBlocks[h] = peer + } + IN + Cardinality(peerPendingRequests) + +(* Returns peer that can serve block for a given height. + Returns NilPeer in case there are no such peer. + See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L477 *) +FindPeerToServe(bPool, h) == + LET peersThatCanServe == { p \in bPool.peerIds: + /\ bPool.peerHeights[p] >= h + /\ NumOfPendingRequests(bPool, p) < PEER_MAX_REQUESTS } IN + + LET pendingBlocks == + {i \in Heights: + /\ i >= bPool.height + /\ \/ bPool.pendingBlocks[i] /= NilPeer + \/ bPool.blockStore[i] /= NilBlock + } IN + + IF \/ peersThatCanServe = AsPidSet({}) + \/ Cardinality(pendingBlocks) >= TARGET_PENDING + THEN NilPeer + \* pick a peer that can serve request for height h that has minimum number of pending requests + ELSE CHOOSE p \in peersThatCanServe: \A q \in peersThatCanServe: + /\ NumOfPendingRequests(bPool, p) <= NumOfPendingRequests(bPool, q) + + +\* Make a request for a block (if possible) and return a request message and block poool. +CreateRequest(bPool) == + LET nextHeight == FindNextRequestHeight(bPool) IN + + IF nextHeight = NilHeight THEN [msg |-> AsOutMsg(NoMsg), pool |-> bPool] + ELSE + LET peer == FindPeerToServe(bPool, nextHeight) IN + IF peer = NilPeer THEN [msg |-> AsOutMsg(NoMsg), pool |-> bPool] + ELSE + LET m == [type |-> "blockRequest", peerId |-> peer, height |-> nextHeight] IN + LET newPool == [bPool EXCEPT + !.pendingBlocks = [bPool.pendingBlocks EXCEPT ![nextHeight] = peer] + ] IN + [msg |-> m, pool |-> newPool] + + +\* Returns node state, i.e., defines termination condition. +\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L432 +ComputeNextState(bPool) == + IF bPool.syncedBlocks = 0 \* corresponds to the syncTimeout in case no progress has been made for a period of time. + THEN "finished" + ELSE IF /\ bPool.height > 1 + /\ bPool.height >= MaxPeerHeight(bPool) \* see https://github.com/tendermint/tendermint/blob/61057a8b0af2beadee106e47c4616b279e83c920/blockchain/v2/scheduler.go#L566 + THEN "finished" + ELSE "running" + +(* Verify if commit is for the given block id and if commit has enough voting power. + See https://github.com/tendermint/tendermint/blob/61057a8b0af2beadee106e47c4616b279e83c920/blockchain/v2/processor_context.go#L12 *) +VerifyCommit(block, lastCommit) == + PossibleCommit(block, lastCommit) + +(* Tries to execute next block in the pool, i.e., defines block validation logic. + Returns new block pool (peers that has send invalid blocks are removed). + See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/processor.go#L135 *) +ExecuteBlocks(bPool) == + LET bStore == bPool.blockStore IN + LET block0 == bStore[bPool.height - 1] IN + \* blockPool is initialized with height = TrustedHeight + 1, + \* so bStore[bPool.height - 1] is well defined + LET block1 == bStore[bPool.height] IN + LET block2 == bStore[bPool.height + 1] IN + + IF block1 = NilBlock \/ block2 = NilBlock + THEN bPool \* we don't have two next consecutive blocks + + ELSE IF ~IsMatchingValidators(block1, block0.NextVS) + \* Check that block1.VS = block0.Next. + \* Otherwise, CorrectBlocksInv fails. + \* In the implementation NextVS is part of the application state, + \* so a mismatch can be found without access to block0.NextVS. + THEN \* the block does not have the expected validator set + RemovePeers({bPool.receivedBlocks[bPool.height]}, bPool) + ELSE IF ~VerifyCommit(block1, block2.lastCommit) + \* Verify commit of block2 based on block1. + \* Interestingly, we do not have to call IsMatchingValidators. + THEN \* remove the peers of block1 and block2, as they are considered faulty + RemovePeers({bPool.receivedBlocks[bPool.height], + bPool.receivedBlocks[bPool.height + 1]}, + bPool) + ELSE \* all good, execute block at position height + [bPool EXCEPT !.height = bPool.height + 1] + + +\* Defines logic for pruning peers. +\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L613 +TryPrunePeer(bPool, suspectedSet, isTimedOut) == + (* -----------------------------------------------------------------------------------------------------------------------*) + (* Corresponds to function prunablePeers in scheduler.go file. Note that this function only checks if block has been *) + (* received from a peer during peerTimeout period. *) + (* Note that in case no request has been scheduled to a correct peer, or a request has been scheduled *) + (* recently, so the peer hasn't responded yet, a peer will be removed as no block is received within peerTimeout. *) + (* In case of faulty peers, we don't have any guarantee that they will respond. *) + (* Therefore, we model this with nondeterministic behavior as it could lead to peer removal, for both correct and faulty. *) + (* See scheduler.go *) + (* https://github.com/tendermint/tendermint/blob/4298bbcc4e25be78e3c4f21979d6aa01aede6e87/blockchain/v2/scheduler.go#L335 *) + LET toRemovePeers == bPool.peerIds \intersect suspectedSet IN + + (* + Corresponds to logic for pruning a peer that is responsible for delivering block for the next height. + The pruning logic for the next height is based on the time when a BlockRequest is sent. Therefore, if a request is sent + to a correct peer for the next height (blockPool.height), it should never be removed by this check as we assume that + correct peers respond timely and reliably. However, if a request is sent to a faulty peer then we + might get response on time or not, which is modelled with nondeterministic isTimedOut flag. + See scheduler.go + https://github.com/tendermint/tendermint/blob/4298bbcc4e25be78e3c4f21979d6aa01aede6e87/blockchain/v2/scheduler.go#L617 + *) + LET nextHeightPeer == bPool.pendingBlocks[bPool.height] IN + LET prunablePeers == + IF /\ nextHeightPeer /= NilPeer + /\ nextHeightPeer \in FAULTY + /\ isTimedOut + THEN toRemovePeers \union {nextHeightPeer} + ELSE toRemovePeers + IN + RemovePeers(prunablePeers, bPool) + + +\* Handle SyncTimeout. It models if progress has been made (height has increased) since the last SyncTimeout event. +HandleSyncTimeout(bPool) == + [bPool EXCEPT + !.syncedBlocks = bPool.height - bPool.syncHeight, + !.syncHeight = bPool.height + ] + +HandleResponse(msg, bPool) == + IF msg.type = "blockResponse" THEN + HandleBlockResponse(msg, bPool) + ELSE IF msg.type = "noBlockResponse" THEN + HandleNoBlockResponse(msg, bPool) + ELSE IF msg.type = "statusResponse" THEN + HandleStatusResponse(msg, bPool) + ELSE IF msg.type = "addPeer" THEN + AddPeer(msg.peerId, bPool) + ELSE IF msg.type = "removePeer" THEN + RemovePeers({msg.peerId}, bPool) + ELSE IF msg.type = "syncTimeout" THEN + HandleSyncTimeout(bPool) + ELSE + bPool + + +(* + At every node step we executed the following steps (atomically): + 1) input message is consumed and the corresponding handler is called, + 2) pruning logic is called + 3) block execution is triggered (we try to execute block at next height) + 4) a request to a peer is made (if possible) and + 5) we decide if termination condition is satisifed so we stop. +*) +NodeStep == + \E suspectedSet \in SUBSET AllPeerIds: \* suspectedSet is a nondeterministic set of peers + \E isTimedOut \in BOOLEAN: + LET bPool == HandleResponse(inMsg, blockPool) IN + LET bp == TryPrunePeer(bPool, suspectedSet, isTimedOut) IN + LET nbPool == ExecuteBlocks(bp) IN + LET msgAndPool == CreateRequest(nbPool) IN + LET nstate == ComputeNextState(msgAndPool.pool) IN + + /\ state' = nstate + /\ blockPool' = msgAndPool.pool + /\ outMsg' = msgAndPool.msg + /\ inMsg' = AsInMsg(NoMsg) + + +\* If node is running, then in every step we try to create blockRequest. +\* In addition, input message (if exists) is consumed and processed. +NextNode == + \/ /\ state = "running" + /\ NodeStep + + \/ /\ state = "finished" + /\ UNCHANGED <> + + +(********************************** Peers ***********************************) + +InitPeers == + \E pHeights \in [AllPeerIds -> Heights]: + peersState = [ + peerHeights |-> pHeights, + statusRequested |-> FALSE, + blocksRequested |-> AsOutMsgSet({}) + ] + +HandleStatusRequest(msg, pState) == + [pState EXCEPT + !.statusRequested = TRUE + ] + +HandleBlockRequest(msg, pState) == + [pState EXCEPT + !.blocksRequested = pState.blocksRequested \union AsOutMsgSet({msg}) + ] + +HandleRequest(msg, pState) == + IF msg = AsOutMsg(NoMsg) + THEN pState + ELSE IF msg.type = "statusRequest" + THEN HandleStatusRequest(msg, pState) + ELSE HandleBlockRequest(msg, pState) + +CreateStatusResponse(peer, pState, anyHeight) == + LET m == + IF peer \in CORRECT + THEN AsInMsg([type |-> "statusResponse", peerId |-> peer, height |-> pState.peerHeights[peer]]) + ELSE AsInMsg([type |-> "statusResponse", peerId |-> peer, height |-> anyHeight]) IN + + [msg |-> m, peers |-> pState] + +CreateBlockResponse(msg, pState, arbitraryBlock) == + LET m == + IF msg.peerId \in CORRECT + THEN AsInMsg([type |-> "blockResponse", peerId |-> msg.peerId, block |-> CorrectBlock(msg.height)]) + ELSE AsInMsg([type |-> "blockResponse", peerId |-> msg.peerId, block |-> arbitraryBlock]) IN + LET npState == + [pState EXCEPT + !.blocksRequested = pState.blocksRequested \ {msg} + ] IN + [msg |-> m, peers |-> npState] + +GrowPeerHeight(pState) == + \E p \in CORRECT: + /\ pState.peerHeights[p] < MAX_HEIGHT + /\ peersState' = [pState EXCEPT !.peerHeights[p] = @ + 1] + /\ inMsg' = AsInMsg(NoMsg) + +SendStatusResponseMessage(pState) == + /\ \E arbitraryHeight \in Heights: + \E peer \in AllPeerIds: + LET msgAndPeers == CreateStatusResponse(peer, pState, arbitraryHeight) IN + /\ peersState' = msgAndPeers.peers + /\ inMsg' = msgAndPeers.msg + + +SendAddPeerMessage == + \E peer \in AllPeerIds: + inMsg' = AsInMsg([type |-> "addPeer", peerId |-> peer]) + +SendRemovePeerMessage == + \E peer \in AllPeerIds: + inMsg' = AsInMsg([type |-> "removePeer", peerId |-> peer]) + +SendSyncTimeoutMessage == + inMsg' = AsInMsg([type |-> "syncTimeout"]) + + +SendControlMessage == + \/ SendAddPeerMessage + \/ SendRemovePeerMessage + \/ SendSyncTimeoutMessage + +\* An extremely important property of block hashes (blockId): +\* If a commit is correct, then the previous block in the block store must be also correct. +UnforgeableBlockId(height, block) == + block.hashEqRef => block = chain[height] + +\* A faulty peer cannot forge enough of the validators signatures. +\* A more precise rule should have checked that the commiters have over 2/3 of the VS's voting power. +NoFork(height, block) == + (height > 1 /\ block.lastCommit.committers = chain[height - 1].VS) + => block.lastCommit.blockIdEqRef + +\* Can be block produced by a faulty peer +IsBlockByFaulty(height, block) == + /\ block.height = height + /\ UnforgeableBlockId(height, block) + /\ NoFork(height, block) + +SendBlockResponseMessage(pState) == + \* a response to a requested block: either by a correct, or by a faulty peer + \/ /\ pState.blocksRequested /= AsOutMsgSet({}) + /\ \E msg \in pState.blocksRequested: + \E block \in Blocks: + /\ IsBlockByFaulty(msg.height, block) + /\ LET msgAndPeers == CreateBlockResponse(msg, pState, block) IN + /\ peersState' = msgAndPeers.peers + /\ inMsg' = msgAndPeers.msg + + \* a faulty peer can always send an unsolicited block + \/ \E peerId \in FAULTY: + \E block \in Blocks: + /\ IsBlockByFaulty(block.height, block) + /\ peersState' = pState + /\ inMsg' = AsInMsg([type |-> "blockResponse", + peerId |-> peerId, block |-> block]) + +SendNoBlockResponseMessage(pState) == + /\ peersState' = pState + /\ inMsg' \in AsInMsgSet([type: {"noBlockResponse"}, peerId: FAULTY, height: Heights]) + + +SendResponseMessage(pState) == + \/ SendBlockResponseMessage(pState) + \/ SendNoBlockResponseMessage(pState) + \/ SendStatusResponseMessage(pState) + + +NextEnvStep(pState) == + \/ SendResponseMessage(pState) + \/ GrowPeerHeight(pState) + \/ SendControlMessage /\ peersState' = pState + \* note that we propagate pState that was missing in the previous version + + +\* Peers consume a message and update it's local state. It then makes a single step, i.e., it sends at most single message. +\* Message sent could be either a response to a request or faulty message (sent by faulty processes). +NextPeers == + LET pState == HandleRequest(outMsg, peersState) IN + /\ outMsg' = AsOutMsg(NoMsg) + /\ NextEnvStep(pState) + + +\* the composition of the node, the peers, the network and scheduler +Init == + /\ IsCorrectChain(chain) \* initialize the blockchain + /\ InitNode + /\ InitPeers + /\ turn = "Peers" + /\ inMsg = AsInMsg(NoMsg) + /\ outMsg = AsOutMsg([type |-> "statusRequest"]) + +Next == + IF turn = "Peers" + THEN + /\ NextPeers + /\ turn' = "Node" + /\ UNCHANGED <> + ELSE + /\ NextNode + /\ turn' = "Peers" + /\ UNCHANGED <> + + +FlipTurn == + turn' = + IF turn = "Peers" THEN + "Node" + ELSE + "Peers" + +\* Compute max peer height. Used as a helper operator in properties. +MaxCorrectPeerHeight(bPool) == + LET correctPeers == {p \in bPool.peerIds: p \in CORRECT} IN + IF correctPeers = AsPidSet({}) + THEN 0 \* no peers, just return 0 + ELSE LET Hts == {bPool.peerHeights[p] : p \in correctPeers} IN + CHOOSE max \in Hts: \A h \in Hts: h <= max + +\* properties to check +TypeOK == + /\ state \in States + /\ inMsg \in InMsgs + /\ outMsg \in OutMsgs + /\ turn \in {"Peers", "Node"} + /\ peersState \in [ + peerHeights: [AllPeerIds -> Heights \union {NilHeight}], + statusRequested: BOOLEAN, + blocksRequested: + SUBSET + [type: {"blockRequest"}, peerId: AllPeerIds, height: Heights] + + ] + /\ blockPool \in [ + height: Heights, + peerIds: SUBSET AllPeerIds, + peerHeights: [AllPeerIds -> Heights \union {NilHeight}], + blockStore: [Heights -> Blocks \union {NilBlock}], + receivedBlocks: [Heights -> AllPeerIds \union {NilPeer}], + pendingBlocks: [Heights -> AllPeerIds \union {NilPeer}], + syncedBlocks: Heights \union {NilHeight, -1}, + syncHeight: Heights + ] + +(* Incorrect synchronization: The last block may be never received *) +Sync1 == + [](state = "finished" => + blockPool.height >= MaxCorrectPeerHeight(blockPool)) + +Sync1AsInv == + state = "finished" => blockPool.height >= MaxCorrectPeerHeight(blockPool) + +(* Incorrect synchronization, as there may be a timeout *) +Sync2 == + \A p \in CORRECT: + \/ p \notin blockPool.peerIds + \/ [] (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) + +Sync2AsInv == + \A p \in CORRECT: + \/ p \notin blockPool.peerIds + \/ (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) + +(* Correct synchronization *) +Sync3 == + \A p \in CORRECT: + \/ p \notin blockPool.peerIds + \/ blockPool.syncedBlocks <= 0 \* timeout + \/ [] (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) + +Sync3AsInv == + \A p \in CORRECT: + \/ p \notin blockPool.peerIds + \/ blockPool.syncedBlocks <= 0 \* timeout + \/ (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) + +(* Naive termination *) +\* This property is violated, as the faulty peers may produce infinitely many responses +Termination == + WF_turn(FlipTurn) => <>(state = "finished") + +(* Termination by timeout: the protocol terminates, if there is a timeout *) +\* the precondition: fair flip turn and eventual timeout when no new blocks were synchronized +TerminationByTOPre == + /\ WF_turn(FlipTurn) + /\ <>(inMsg.type = "syncTimeout" /\ blockPool.height <= blockPool.syncHeight) + +TerminationByTO == + TerminationByTOPre => <>(state = "finished") + +(* The termination property when we only have correct peers *) +\* as correct peers may spam the node with addPeer, removePeer, and statusResponse, +\* we have to enforce eventual response (there are no queues in our spec) +CorrBlockResponse == + \A h \in Heights: + [](outMsg.type = "blockRequest" /\ outMsg.height = h + => <>(inMsg.type = "blockResponse" /\ inMsg.block.height = h)) + +\* a precondition for termination in presence of only correct processes +TerminationCorrPre == + /\ FAULTY = AsPidSet({}) + /\ WF_turn(FlipTurn) + /\ CorrBlockResponse + +\* termination when there are only correct processes +TerminationCorr == + TerminationCorrPre => <>(state = "finished") + +\* All synchronized blocks (but the last one) are exactly like in the reference chain +CorrectBlocksInv == + \/ state /= "finished" + \/ \A h \in 1..(blockPool.height - 1): + blockPool.blockStore[h] = chain[h] + +\* A false expectation that the protocol only finishes with the blocks +\* from the processes that had not been suspected in being faulty +SyncFromCorrectInv == + \/ state /= "finished" + \/ \A h \in 1..blockPool.height: + blockPool.receivedBlocks[h] \in blockPool.peerIds \union {NilPeer} + +\* A false expectation that a correct process is never removed from the set of peer ids. +\* A correct process may reply too late and then gets evicted. +CorrectNeverSuspectedInv == + CORRECT \subseteq blockPool.peerIds + +BlockPoolInvariant == + \A h \in Heights: + \* waiting for a block to arrive + \/ /\ blockPool.receivedBlocks[h] = NilPeer + /\ blockPool.blockStore[h] = NilBlock + \* valid block is received and is present in the store + \/ /\ blockPool.receivedBlocks[h] /= NilPeer + /\ blockPool.blockStore[h] /= NilBlock + /\ blockPool.pendingBlocks[h] = NilPeer + +(* a few simple properties that trigger counterexamples *) + +\* Shows execution in which peer set is empty +PeerSetIsNeverEmpty == blockPool.peerIds /= AsPidSet({}) + +\* Shows execution in which state = "finished" and MaxPeerHeight is not equal to 1 +StateNotFinished == + state /= "finished" \/ MaxPeerHeight(blockPool) = 1 + + +============================================================================= + +\*============================================================================= +\* Modification History +\* Last modified Fri May 29 20:41:53 CEST 2020 by igor +\* Last modified Thu Apr 16 16:57:22 CEST 2020 by zarkomilosevic +\* Created Tue Feb 04 10:36:18 CET 2020 by zarkomilosevic From a66bb37e320a5cae71b32f834711bd43c00d8610 Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Thu, 10 Sep 2020 15:07:24 +0200 Subject: [PATCH 094/223] Update README.md (#160) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d93d06404..cd6c82981 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Consequently, this repository is in a bit of a state of flux. At the moment, the spec followed by the Go implementation (tendermint/tendermint) is in the [spec](spec) directory, while the spec followed by the Rust implementation -(informalsystems/tendermint-rs) is in the rust-spec +(informalsystems/tendermint-rs) is in the [rust-spec](rust-spec) directory. TLA+ specifications are also in the rust-spec directory. Over time, these specs will converge in the spec directory. From 6c95c3f2504b9d311287ad4da82ec771f2e349b5 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 10 Sep 2020 23:56:00 +0400 Subject: [PATCH 095/223] 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/reactors/mempool/messages.md | 19 +++++-------------- spec/reactors/mempool/reactor.md | 5 +++++ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/spec/reactors/mempool/messages.md b/spec/reactors/mempool/messages.md index 9c583ac0f..a71caf590 100644 --- a/spec/reactors/mempool/messages.md +++ b/spec/reactors/mempool/messages.md @@ -2,25 +2,16 @@ ## P2P Messages -There is currently only one message that Mempool broadcasts -and receives over the p2p gossip network (via the reactor): -`TxMessage` +There is currently only one message that Mempool broadcasts and receives over +the p2p gossip network (via the reactor): `TxsMessage` ```go -// TxMessage is a MempoolMessage containing a transaction. -type TxMessage struct { - Tx types.Tx +// TxsMessage is a MempoolMessage containing a list of transactions. +type TxsMessage struct { + Txs []types.Tx } ``` -TxMessage is go-amino encoded and prepended with `0x1` as a -"type byte". This is followed by a go-amino encoded byte-slice. -Prefix of 40=0x28 byte tx is: `0x010128...` followed by -the actual 40-byte tx. Prefix of 350=0x015e byte tx is: -`0x0102015e...` followed by the actual 350 byte tx. - -(Please see the [go-amino repo](https://github.com/tendermint/go-amino#an-interface-example) for more information) - ## RPC Messages Mempool exposes `CheckTx([]byte)` over the RPC interface. diff --git a/spec/reactors/mempool/reactor.md b/spec/reactors/mempool/reactor.md index 2ad8134bd..03fa1b7bd 100644 --- a/spec/reactors/mempool/reactor.md +++ b/spec/reactors/mempool/reactor.md @@ -13,6 +13,11 @@ for details. Sending incorrectly encoded data or data exceeding `maxMsgSize` will result in stopping the peer. +`maxMsgSize` equals `MaxBatchBytes` (10MB) + 4 (proto overhead). +`MaxBatchBytes` is a mempool config parameter -> defined locally. The reactor +sends transactions to the connected peers in batches. The maximum size of one +batch is `MaxBatchBytes`. + The mempool will not send a tx back to any peer which it received it from. The reactor assigns an `uint16` number for each peer and maintains a map from From f3033c551556b1c47fcc05e18c18ea80be3791bc Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Thu, 24 Sep 2020 09:53:50 +0200 Subject: [PATCH 096/223] 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 --- rust-spec/lightclient/README.md | 5 +- rust-spec/lightclient/detection/detection.md | 696 --------------- .../detection/detection_001_reviewed.md | 793 ++++++++++++++++++ 3 files changed, 796 insertions(+), 698 deletions(-) delete mode 100644 rust-spec/lightclient/detection/detection.md create mode 100644 rust-spec/lightclient/detection/detection_001_reviewed.md diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md index a0e14e380..1958dc007 100644 --- a/rust-spec/lightclient/README.md +++ b/rust-spec/lightclient/README.md @@ -73,11 +73,12 @@ no bug is reported up to depth k. ![Experimental results](experiments.png) -## Fork Detection +## Attack Detection This is a work-in-progress draft. -The [English specification](detection/detection.md) defines blockchain forks and describes +The [English specification](detection/detection_001_reviewed.md) +defines blockchain forks and light client attacks, and describes the problem of a light client detecting them from communication with a network of full nodes, where at least one is correct. diff --git a/rust-spec/lightclient/detection/detection.md b/rust-spec/lightclient/detection/detection.md deleted file mode 100644 index 4faa0f4ca..000000000 --- a/rust-spec/lightclient/detection/detection.md +++ /dev/null @@ -1,696 +0,0 @@ -# Fork detector - -> ***This an unfinished draft. Comments are welcome!*** - -A detector (or detector for short) is a mechanism that expects as -input a header with some height *h*, connects to different Tendermint -full nodes, requests the header of height *h* from them, and then -cross-checks the headers and the input header. - -There are two foreseeable use cases: - -1) strengthen the light client: If a light client accepts a header -*hd* (after performing skipping or sequential verification), it can -use the detector to probe the system for conflicting headers and -increase the trust in *hd*. Instead of communicating with a single -full node, communicating with several full nodes shall increase the -likelihood to be aware of a fork (see [[accountability]] for -discussion about forks) in case there is one. - -2) to support fork accountability: In the case when more than 1/3 of -the voting power is held by faulty validators, faulty nodes may -generate two conflicting headers for the same height. The goal of the -detector is to learn about the conflicting headers by probing -different full nodes. Once a detector has two conflicting headers, -these headers are evidence of misbehavior. A natural extension is to -use the detector within a monitor process (on a full node) that calls -the detector on a sample (or all) headers (in parallel). (If the -sample is chosen at random, this adds a level of probabilistic -reasoning.) If conflicting headers are found, they are evidence that -can be used for punishing processes. - -In this document we will focus onn strengthening the light client, and -leave other uses of the detection mechanism (e.g., when run on a full -node) to the future. - -## Context of this document - -The light client verification specification [[verification]] is -designed for the Tendermint failure model (1/3 assumption) -[TMBC-FM-2THIRDS]. 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 -this assumption is violated, the light client can be fooled to trust a -header that was not generated by Tendermint consensus. - -This specification, the fork detector, is a "second line of defense", -in case the 1/3 assumption is violated. Its goal is to detect fork (conflicting headers) 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 header, the light client first does [verification][verification] -with the primary, and then cross-checks the header with the -secondaries using this specification. - -### Tendermint Consensus and Forks - -#### **[TMBC-GENESIS.1]** - -Let *Genesis* be the agreed-upon initial block (file). - -#### **[TMBC-FUNC.1]** - -> **TODO** be more precise. +2/3 of b.NextV = c.Val signed c. For now -> the following will do: - -Let b and c be two light blocks -with *b.Header.Height + 1 = c.Header.Height*. We define **signs(b,c)** - iff `ValidAndVerified(b, c)` - -> **TODO** be more precise. +1/3 of b.NextV signed c. For now -> the following will do: - -Let *b* and *c* be two light blocks. We define **supports(b,c,t)** - iff `ValidAndVerified(b, c)` at time *t* - -> 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-ROOT.1]** - -Let *b* and *c* be light blocks. We define *skip-root(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*. - -> **TODO** In the above we might use a sequence of times t(i). Not sure. -> **TODO:** I believe the following definition -> corresponds to **Slashable fork** in -> [forks][tendermintfork]. Please confirm! -> Observe that sign-skip-match is even defined if there is a fork on -> the chain. - -#### **[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 - -- *sequ-rooted(a)* and - -- *b.Header.Height = c.Header.Height* and -- *skip-root(a,b,t)* -- *skip-root(a,c,t)* - -implies *b = c*. - -#### **[TMBC-SIGN-FORK.1]** - -If there exists three light blocks a, b, and c, with -*sign-skip-match(a,b,c,t) = -false* then we have a *slashable fork*. -We call *a* a bifurcation block of the fork. -We say we have **a fork at height** *b.Header.Height*. - -> The lightblock *a* need not be unique, that is, a there may be -> several blocks that satisfy the above requirement for blocks *b* and -> *c*. -> **TODO:** I think the following definition is -> the intuition behind **main chain forks** -> in the document on [forks][tendermintfork]. However, main chain -> forks were defined more operational "forks that are observed by -> full nodes as part of normal Tendermint consensus protocol". Please -> confirm! - -#### **[TMBC-SIGN-UNIQUE.1]** - -Let *b* and *c* be light blocks, we define *sign-unique(b,c) = -true* iff - -- *b.Header.Height = c.Header.Height* and -- *sequ-rooted(b)* and -- *sequ-rooted(c)* - -implies *b = c*. - -If there exists two light blocks b and c, with *sign-unique(b,c) = -false* then we have a *fork on the chain*. - -> The following captures what I believe is called a light client fork -> in our discussions. There is no fork on the chain up to the height -> of block b. However, c is of that height, is different, and passes skipping -> verification -> Observe that a light client fork is a special case of a slashable -> fork. - -#### **[TMBC-LC-FORK.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's also define bogus blocks that have no support. -> Observe that bogus is even defined if there is a fork on the chain. -> 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-root(a,b,t) = false* - -> Relation to [fork accountability][accountability]: F1, F2, and F3 -> (equivocation, amnesia, back to the past) can lead to a fork on the -> chain and to a light client fork. -> F4 and F5 (phantom validators, lunatic) cannot lead to a fork on the -> chain but to a light client -> fork if *t+1 < f < 2t+1*. -> F4 and F5 can also lead to bogus blocks - -### Informal Problem statement - -> We put tags to informal problem statements as there is no sequential -> specification. - -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. - -#### **[LCD-IP-STATE.1]** - -The detector works on a LightStore that contains LightBlocks in one of -the state `StateUnverified`, `StateVerified`, `StateFailed`, or -`StateTrusted`. - -#### **[LCD-IP-Q.1]** - -Whenever the light client verifier performs `VerifyToTarget` with the -primary and returns with -`(lightStore, ResultSuccess)`, the - detector should query the secondaries by calling `FetchLightBlock` for height - *LightStore.LatestVerified().Height* remotely. -Then, -the detector returns the set of all headers *h'* downloaded from -secondaries that satisfy - -- *h'* is different from *LightStore.LatestVerified()* -- *h'* is a (light) fork. - -#### **[LCD-IP-PEERSET.1]** - -Whenever the detector observes *detectable misbehavior* of a full node -from the set of Secondaries it should be replaced by a fresh full -node. (A full node that has not been primary or secondary -before). Detectable misbehavior can be - -- a timeout -- The case where *h'* is different -from *LightStore.LatestVerified()* but *h'* is not a fork, that is, if -*h'* is bogus. In other words, the -secondary cannot provide a sequence of light blocks that constitutes -proof of *h'*. - -## 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. - -**Remark:** Check whether [LCD-A-CorrFull.1] is not needed in the end because -the verification conditions [LCD-DIST-*] have preconditions on specific -cases where primary and/or secondaries are faulty. - -#### **[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. - -## (Distributed) Problem statement - -> As the fork detector from the beginning is there to reduce the -> impact of faulty nodes, and faulty nodes imply that there is a -> distributed system, there is no sequential specification. - -The detector gets as input a lightstore *lightStore*. -Let *h-verified = lightStore.LatestVerified().Height* and - *h-trust=lightStore.LatestTrusted().Height* (see - [LCV-DATA-LIGHTSTORE]). -It queries the secondaries for headers at height *h-verified*. -The detector returns a set *PoF* of *Proof of Forks*, and should satisfy the following - temporal formulas: - -#### **[LCD-DIST-INV.1]** - -If there is no fork at height *h-verified* ([TMBC-SIGN-FORK.1]), -then the detector should return the empty set. - -> If the empty set is returned the supervisor will change the state of -> the header at height *h-verified* to *stateTrusted*. - -#### **[LCD-DIST-LIVE-FORK.1]** - -If there is a fork at height *h-verified*, and -there are two correct full nodes *i* and *j* that are - -- on different branches, and -- *i* is primary and -- *j* is secondary, - -then the detector eventually outputs the fork. - -#### **[LCD-DIST-LIVE-FORK-FAULTY.1]** - -If there is a fork at height *h-verified*, and -there is a correct secondary that is on a different branch than the -primary reported, -then the detector eventually outputs the fork. - -> The above property is quite operational ("Than the primary -> reported"), but it captures quite closely the requirement. As the -> fork detector only makes sense in a distributed setting, and does -> not have a sequential specification, less "pure" -> specification are acceptable. -> These properties capture the following operational requirement: -> -> **[LCD-REQ-REP.1]** -> If the detector observes two conflicting headers for height *h*, -> it should try to verify both. If both are verified it should report evidence. -> If the primary reports header *h* and a secondary reports header *h'*, -> and if *h'* can be verified based on common root of trust, then -> evidence should be generated; -> By verifying we mean calling `VerifyToTarget` from the -> [[verification]] specification. - -## Definitions - -- 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*. -- A set *FaultyNodes* of nodes that the light client suspects of being faulty; it is initially empty - -- *Lightstore* as defined in the [verification specification][verification]. - -#### **[LCD-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* - -> The following invariant is very useful for reasoning, and underlies -> many intuition when we - -#### **[LCD-INV-TRUSTED-AGREED.1]:** - -It is always the case the light client has downloaded a lightblock for height -*lightStore.LatestTrusted().Height* -from each of the current primary and the secondary, that all reported -the identical lightblock for that height. - -> In the above, I guess "the identical" might be replaced with "a -> matching" to cover commits that might be different. -> The above requires us that before we pick a new secondary, we have to -> query the secondary for the header of height -> *lightStore.LatestTrusted().Height*. - -## Solution - -### Data Structures - -Lightblocks and LightStores are -defined at [LCV-DATA-LIGHTBLOCK.1] and [LCV-DATA-LIGHTSTORE.1]. See the [verification specification][verification] for details. - -> The following data structure [LCV-DATA-POF.1] -> defines a **proof of fork**. Following -> [TMBC-SIGN-FORK.1], we require two blocks *b* and *c* for the same -> height that can both be verified from a common root block *a* (using -> the skipping or the sequential method). -> [LCV-DATA-POF.1] mirrors the definition [TMBC-SIGN-FORK.1]: -> *TrustedBlock* corresponds to *a*, and *PrimaryTrace* and *SecondaryTrace* -> are traces to two blocks *b* and *c*. The traces establish that both -> *skip-root(a,b,t)* and *skip-root(a,c,t)* are satisfied. - -#### **[LCV-DATA-POF.1]** - -```go -type LightNodeProofOfFork struct { - TrustedBlock LightBlock - PrimaryTrace []LightBlock - SecondaryTrace []LightBlock -} -``` - - - - - - - - - -#### **[LCV-DATA-POFSTORE.1]** - -Proofs of Forks are stored in a structure which stores all proofs -generated during detection. - -```go -type PoFStore struct { - ... -} -``` - -In additions to the functions defined in -the [verification specification][verification], the -LightStore exposes the following function - -#### **[LCD-FUNC-SUBTRACE.1]:** - -```go -func (ls LightStore) Subtrace(from int, to int) LightStore -``` - -- Expected postcondition - - returns a lightstore that contains all lightblocks *b* from *ls* - that satisfy: *from < b.Header.Height <= to* - ----- - -### Inter Process Communication - -```go -func FetchLightBlock(peer PeerID, height Height) LightBlock -``` - -See the [verification specification][verification] for details. - -#### **[LCD-FUNC-SUBMIT.1]:** - -```go -func SubmitProofOfFork(pof LightNodeProofOfFork) Result -``` - -**TODO:** finalize what this should do, and what detail of - specification we need. - -- Implementation remark -- Expected precondition - - none -- Expected postcondition - - submit evidence to primary and the secondary in *pof*, that is, - to - - `pof.PrimaryTrace[1].Provider` - - `pof.SecondaryTrace[1].Provider` - - **QUESTION** minimize data? We could submit to the primary only - the trace of the secondary, and vice versa. Do we need to spell - that out here? (Also, by [LCD-INV-TRUSTED-AGREED.1], we do not - need to send `pof.TrustedBlock`) - - **FUTURE WORK:** we might send *pof* to primary or all - secondaries or broadcast to all full nodes. However, in evidence - detection this might need that a full node has to check a *pof* - where both traces are not theirs. This leads to more complicated - logic at the full node, which we do not need right now. - -- Error condition - - none - -### Auxiliary Functions (Local) - -#### **[LCD-FUNC-CROSS-CHECK.1]:** - -```go -func CrossCheck(peer PeerID, testedLB LightBlock) (result) { - sh := FetchLightBlock(peer, testedLB.Height); - // as the check below only needs the header, it is sufficient - // to download the header rather than the LighBlock - if testedLB.Header == sh.Header { - return OK - } - else { - return DoesNotMatch - } -} -``` - -- Implementation remark - - download block and compare to previously downloaded one. -- Expected precondition -- Expected postcondition -- Error condition - -#### **[LCD-FUNC-REPLACE-PRIMARY.1]:** - -```go -Replace_Primary() -``` - -**TODO:** formalize conditions - -- Implementation remark - - the primary is replaced by a secondary, and lightblocks above - trusted blocks are removed - - to maintain a constant size of secondaries, at this point we - might need to - - pick a new secondary *nsec* - - maintain [LCD-INV-TRUSTED-AGREED.1], that is, - - call `CrossCheck(nsec,lightStore.LatestTrusted()`. - If it matches we are OK, otherwise - - we repeat with another full node as new - secondary candidate - - **FUTURE:** try to do fork detection from some possibly old - lightblock in store. (Might be the approach for the - light node that assumes to be connected to correct - full nodes only from time to time) - -- Expected precondition - - *FullNodes* is nonempty - -- Expected postcondition - - *primary* is moved to *FaultyNodes* - - all lightblocks with height greater than - lightStore.LatestTrusted().Height are removed from *lightStore*. - - a secondary *s* is moved from *Secondaries* to primary - -> this ensures that *s* agrees on the Last Trusted State - -- Error condition - - if precondition is violated - -#### **[LCD-FUNC-REPLACE-SECONDARY.1]:** - -```go -Replace_Secondary(addr Address) -``` - -**TODO:** formalize conditions - -- Implementation remark - - maintain [LCD-INV-TRUSTED-AGREED.1], that is, - - call `CrossCheck(nsec,lightStore.LatestTrusted()`. - If it matches we are OK, otherwise - - we might just repeat with another full node as new secondary - - **FUTURE:** try to do fork detection from some possibly old - lightblock in store. (Might be the approach for the - light node that assumes to be connected to correct - full nodes only from time to time) - -- Expected precondition - - *FullNodes* is nonempty -- Expected postcondition - - addr is moved from *Secondaries* to *FaultyNodes* - - an address *a* is moved from *FullNodes* to *Secondaries* -- Error condition - - if precondition is violated - -### From the verifier - -```go -func VerifyToTarget(primary PeerID, lightStore LightStore, - targetHeight Height) (LightStore, Result) -``` - -See the [verification specification][verification] for details. - -## Protocol - -### Shared data of the light client - -- a pool of full nodes *FullNodes* that have not been contacted before -- peer set called *Secondaries* -- primary -- lightStore - -### Outline - -The problem laid out is solved by calling the function `ForkDetector` - with a lightstore that contains a light block that has just been - verified by the verifier. - -- **TODO:** 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. - -- **TODO:** clarify EXPIRED case. Can we always punish? Can we give sufficient - conditions. - -### Fork Detector - -#### **[LCD-FUNC-DETECTOR.1]:** - -```go -func ForkDetector(ls LightStore, PoFs PoFStore) -{ - testedLB := LightStore.LatestVerified() - for i, secondary range Secondaries { - if OK = CrossCheck(secondary, testedLB) { - // header matches. we do nothing. - } - else { - // [LCD-REQ-REP] - // header does not match. there is a situation. - // we try to verify sh by querying s - // we set up an auxiliary lightstore with the highest - // trusted lightblock and the lightblock we want to verify - auxLS.Init - auxLS.Update(LightStore.LatestTrusted(), StateVerified); - auxLS.Update(sh,StateUnverified); - LS,result := VerifyToTarget(secondary, auxLS, sh.Header.Height) - if (result = ResultSuccess || result = EXPIRED) { - // we verified header sh which is conflicting to hd - // there is a fork on the main blockchain. - // If return code was EXPIRED it might be too late - // to punish, we still report it. - pof = new LightNodeProofOfFork; - pof.TrustedBlock := LightStore.LatestTrusted() - pof.PrimaryTrace := - LightStore.Subtrace(LightStore.LatestTrusted().Height, - testedLB.Height); - pof.SecondaryTrace := - auxLS.Subtrace(LightStore.LatestTrusted().Height, - testedLB.Height); - PoFs.Add(pof); - } - else { - // secondary might be faulty or unreachable - // it might fail to provide a trace that supports sh - // or time out - Replace_Secondary(secondary) - } - } - } - return PoFs -} -``` - -**TODO:** formalize conditions - -- Expected precondition - - Secondaries initialized and non-empty - - `PoFs` initialized and empty - - *lightStore.LatestTrusted().Height < lightStore.LatestVerified().Height* -- Expected postcondition - - satisfies [LCD-DIST-INV.1], [LCD-DIST-LIFE-FORK.1] - - removes faulty secondary if it reports wrong header - - **TODO** submit proof of fork -- Error condition - - fails if precondition is violated - - fails if [LCV-INV-TP] is violated (no trusted header within - trusting period - ----- - -## Correctness arguments - -#### Argument for [LCD-DIST-INV] - -**TODO** - -#### Argument for [LCD-DIST-LIFE-FORK] - -**TODO** - -# References - -> links to other specifications/ADRs this document refers to - -[[verification]] The specification of the light client verification. - -[[tendermintfork]] Tendermint fork detection and accountability - -[[accountability]] Fork accountability - -[TMBC-FM-2THIRDS-linkVDD]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#**[TMBC-FM-2THIRDS-link]**: - -[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/verification.md - -[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md - -[blockchain]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md - -[lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md - -[verificationVDD]: https://github.com/informalsystems/VDD/blob/master/lightclient/failuredetector.md - -[verification]: https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification.md - -[accountability]: https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md - -[tendermintfork]: https://docs.google.com/document/d/1xjyp5JOPt7QfHem1AFEaowBH2Plk0IHACWtYFXFvO7E/edit#heading=h.th2369ptc2ve diff --git a/rust-spec/lightclient/detection/detection_001_reviewed.md b/rust-spec/lightclient/detection/detection_001_reviewed.md new file mode 100644 index 000000000..379a05313 --- /dev/null +++ b/rust-spec/lightclient/detection/detection_001_reviewed.md @@ -0,0 +1,793 @@ +# ***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 +<<<<<<< HEAD:rust-spec/lightclient/detection/detection_001_reviewed.md + // the next iterationt + common := trace[i].Header +======= + // the next iteration + common := trace[i] +>>>>>>> bc3d1aff5ba358afb68f1698e5834995662ba74c:rust-spec/lightclient/detection/detection.md + } + } + } + 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 From 80747a0872c610c8dd42d107a831ff99fc390f98 Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Thu, 24 Sep 2020 09:59:24 +0200 Subject: [PATCH 097/223] fixed an overlooked conflict (#167) --- rust-spec/lightclient/detection/detection_001_reviewed.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rust-spec/lightclient/detection/detection_001_reviewed.md b/rust-spec/lightclient/detection/detection_001_reviewed.md index 379a05313..db8c29a14 100644 --- a/rust-spec/lightclient/detection/detection_001_reviewed.md +++ b/rust-spec/lightclient/detection/detection_001_reviewed.md @@ -695,13 +695,8 @@ func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) else { // the peer agrees with the trace, we move common forward // we could delete auxLS as it will be overwritten in -<<<<<<< HEAD:rust-spec/lightclient/detection/detection_001_reviewed.md - // the next iterationt - common := trace[i].Header -======= // the next iteration common := trace[i] ->>>>>>> bc3d1aff5ba358afb68f1698e5834995662ba74c:rust-spec/lightclient/detection/detection.md } } } From 109a73f6724e83d88e821f3a6ffa63c3491bf6de Mon Sep 17 00:00:00 2001 From: Andrey Kuprianov <59489470+andrey-kuprianov@users.noreply.github.com> Date: Fri, 25 Sep 2020 09:22:14 +0200 Subject: [PATCH 098/223] describe valset sorting according to v0.34 requirements (#169) --- spec/core/data_structures.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index ee7b4af83..1bb10f0c7 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -374,7 +374,8 @@ block.ValidatorsHash == MerkleRoot(state.Validators) MerkleRoot of the current validator set that is committing the block. This can be used to validate the `LastCommit` included in the next block. -Note the validators are sorted by their voting power before computing the MerkleRoot. +Note that before computing the MerkleRoot the validators are sorted +first by voting power (descending), then by address (ascending). ### NextValidatorsHash @@ -385,7 +386,9 @@ block.NextValidatorsHash == MerkleRoot(state.NextValidators) MerkleRoot of the next validator set that will be the validator set that commits the next block. This is included so that the current validator set gets a chance to sign the next validator sets Merkle root. -Note the validators are sorted by their voting power before computing the MerkleRoot. +Note that before computing the MerkleRoot the validators are sorted +first by voting power (descending), then by address (ascending). + ### ConsensusHash From 733b0208998fefaf8d105f14cbd42fe1cabd62f4 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 29 Sep 2020 14:05:44 +0200 Subject: [PATCH 099/223] evidence: update data structures (#165) --- .../lightclient/verification/verification.md | 10 ++--- spec/abci/abci.md | 3 +- spec/abci/apps.md | 8 ---- spec/consensus/creating-proposal.md | 10 ++--- spec/core/data_structures.md | 45 +++++++++++++------ 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/rust-spec/lightclient/verification/verification.md b/rust-spec/lightclient/verification/verification.md index db68c74db..f9ccc2160 100644 --- a/rust-spec/lightclient/verification/verification.md +++ b/rust-spec/lightclient/verification/verification.md @@ -1147,16 +1147,16 @@ func Main (primary PeerID, lightStore LightStore, targetHeight Height) [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 +[fork-detector]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_001_reviewed.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 +[fullnode-data-structures]: https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md -[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/abci/abci.md b/spec/abci/abci.md index f6bdf8e96..04bdc935d 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -115,7 +115,8 @@ enum EvidenceType { ``` There are two forms of evidence: Duplicate Vote and Light Client Attack. More -information can be found [here](https://github.com/tendermint/spec/blob/master/spec/light-client/accountability.md) +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.md) ## Determinism diff --git a/spec/abci/apps.md b/spec/abci/apps.md index 860404096..b594de4f9 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -345,14 +345,6 @@ a block minus it's overhead ( ~ `MaxBytes`). The amount must be a positive number. -### EvidenceParams.ProofTrialPeriod - -This is the duration in terms of blocks that an indicted validator has to prove a -correct lock change in the event of amnesia evidence when a validator voted more -than once across different rounds. - -This must be positive and less then `EvidenceParams.MaxAgeNumBlocks`. - ### Updates The application may set the ConsensusParams during InitChain, and update them during diff --git a/spec/consensus/creating-proposal.md b/spec/consensus/creating-proposal.md index 49798a41b..52cdb1ce6 100644 --- a/spec/consensus/creating-proposal.md +++ b/spec/consensus/creating-proposal.md @@ -17,7 +17,7 @@ we account for amino overhead for each transaction. ```go func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 { return maxBytes - - MaxAminoOverheadForBlock - + MaxOverheadForBlock - MaxHeaderBytes - int64(valsCount)*MaxVoteBytes - int64(evidenceCount)*MaxEvidenceBytes @@ -28,15 +28,13 @@ func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 { 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 because the evidence size is unknown at the moment, we subtract -maximum evidence size (1/10th of the maximum block size). +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 - - MaxAminoOverheadForBlock - + MaxOverheadForBlock - MaxHeaderBytes - - int64(valsCount)*MaxVoteBytes - - MaxEvidenceBytesPerBlock(maxBytes) + (maxNumEvidence * MaxEvidenceBytes) } ``` diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 1bb10f0c7..9da784a58 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -207,15 +207,10 @@ It is implemented as the following interface. ```go type Evidence interface { Height() int64 // height of the equivocation - Time() time.Time // time of the equivocation - Address() []byte // address of the equivocating validator Bytes() []byte // bytes which comprise the evidence - Hash() []byte // hash of the evidence - Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence - Equal(Evidence) bool // check equality of evidence - - ValidateBasic() error - String() string + Hash() []byte // hash of the evidence (this is also used for equality) + ValidateBasic() error // consistency check of the data + String() string // string representation of the evidence } ``` @@ -231,16 +226,14 @@ in the same round of the same height. Votes are lexicographically sorted on `Blo ```go type DuplicateVoteEvidence struct { - VoteA *Vote - VoteB *Vote - - Timestamp time.Time + VoteA *Vote + VoteB *Vote } ``` Valid Duplicate Vote Evidence must adhere to the following rules: -- Validator Address, Height, Round and Type of vote must be the same for both votes +- 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) @@ -248,7 +241,31 @@ Valid Duplicate Vote Evidence must adhere to the following rules: - Vote signature must be valid (using the chainID) -- Time must be equal to the block time +- Evidence must not have expired: either age in terms of height or time must be + less than the age stated in the consensus params. Time is the block time that the + votes were a part of. + +### LightClientAttackEvidence + +```go +type LightClientAttackEvidence struct { + ConflictingBlock *LightBlock + CommonHeight int64 +} +``` + +Valid Light Client Attack Evidence encompasses three types of attack and 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 trusted header of the node at the same height as the conflicting header must have a different hash to + the conflicting header. + +- Evidence must not have expired. The height (and thus the time) is taken from the common height. ## Validation From 3e56eb5fe31b20b18005cb95b691b4549d5b21db Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 29 Sep 2020 14:23:40 +0200 Subject: [PATCH 100/223] fix markdown linter (#172) --- spec/core/data_structures.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 9da784a58..51061867e 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -391,7 +391,7 @@ block.ValidatorsHash == MerkleRoot(state.Validators) MerkleRoot of the current validator set that is committing the block. This can be used to validate the `LastCommit` included in the next block. -Note that before computing the MerkleRoot the validators are sorted +Note that before computing the MerkleRoot the validators are sorted first by voting power (descending), then by address (ascending). ### NextValidatorsHash @@ -403,10 +403,9 @@ block.NextValidatorsHash == MerkleRoot(state.NextValidators) MerkleRoot of the next validator set that will be the validator set that commits the next block. This is included so that the current validator set gets a chance to sign the next validator sets Merkle root. -Note that before computing the MerkleRoot the validators are sorted +Note that before computing the MerkleRoot the validators are sorted first by voting power (descending), then by address (ascending). - ### ConsensusHash ```go From 8391fa0b898c1184de379c32b740621ff070f459 Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Thu, 1 Oct 2020 11:31:53 +0200 Subject: [PATCH 101/223] TLA+ specs from MBT revision (#173) --- .../verification/Blockchain_002_draft.tla | 171 +++++++ .../verification/Lightclient_002_draft.tla | 465 ++++++++++++++++++ 2 files changed, 636 insertions(+) create mode 100644 rust-spec/lightclient/verification/Blockchain_002_draft.tla create mode 100644 rust-spec/lightclient/verification/Lightclient_002_draft.tla diff --git a/rust-spec/lightclient/verification/Blockchain_002_draft.tla b/rust-spec/lightclient/verification/Blockchain_002_draft.tla new file mode 100644 index 000000000..f2ca5aba5 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/Lightclient_002_draft.tla b/rust-spec/lightclient/verification/Lightclient_002_draft.tla new file mode 100644 index 000000000..32c807f6e --- /dev/null +++ b/rust-spec/lightclient/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 From 04fb20e33daf61432bbaf4987dd48a9442f3304a Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 5 Oct 2020 08:54:41 +0200 Subject: [PATCH 102/223] remove setOption (#181) --- spec/abci/abci.md | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 04bdc935d..21662fc5f 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -9,7 +9,7 @@ ABCI methods are split across four separate ABCI _connections_: - Consensus connection: `InitChain`, `BeginBlock`, `DeliverTx`, `EndBlock`, `Commit` - Mempool connection: `CheckTx` -- Info connection: `Info`, `SetOption`, `Query` +- Info connection: `Info`, `Query` - Snapshot connection: `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, `ApplySnapshotChunk` The consensus connection is driven by a consensus protocol and is responsible @@ -35,7 +35,7 @@ don't return errors because an error would indicate a critical failure in the application and there's nothing Tendermint can do. The problem should be addressed and both Tendermint and the application restarted. -All other methods (`SetOption, Query, CheckTx, DeliverTx`) return an +All other methods (`Query, CheckTx, DeliverTx`) return an application-specific response `Code uint32`, where only `0` is reserved for `OK`. @@ -158,7 +158,7 @@ Sources of non-determinism in applications may include: See [#56](https://github.com/tendermint/abci/issues/56) for original discussion. -Note that some methods (`SetOption, Query, CheckTx, DeliverTx`) return +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 @@ -236,23 +236,6 @@ via light client. be updated during `Commit`, ensuring that `Commit` is never called twice for the same block height. -### SetOption - -- **Request**: - - `Key (string)`: Key to set - - `Value (string)`: Value to set for key -- **Response**: - - `Code (uint32)`: Response code - - `Log (string)`: The output of the application's logger. May - be non-deterministic. - - `Info (string)`: Additional information. May - be non-deterministic. -- **Usage**: - - Set non-consensus critical application specific options. - - e.g. Key="min-fee", Value="100fermion" could set the minimum fee - required for CheckTx (but not DeliverTx - that would be - consensus critical). - ### InitChain - **Request**: From c5576dfa6900152179987cd332f9664c996443a6 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 6 Oct 2020 12:40:25 +0200 Subject: [PATCH 103/223] spec: protobuf changes (#156) Co-authored-by: Anton Kaliaev --- spec/abci/abci.md | 28 +++++-- spec/abci/apps.md | 30 ++++---- spec/abci/client-server.md | 2 +- spec/core/data_structures.md | 31 +++++--- spec/core/encoding.md | 145 +++++++++-------------------------- spec/core/state.md | 70 +++++++++-------- spec/p2p/connection.md | 2 +- spec/p2p/peer.md | 6 +- 8 files changed, 137 insertions(+), 177 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 21662fc5f..729cada0f 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -61,6 +61,23 @@ Each event has a `type` which is meant to categorize the event for a particular 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`, `value` and a `index`. The index field notifies the indexer within Tendermint to index the event. This field is non-deterministic and will vary across different nodes in the network. + +```protobuf +message EventAttribute { + bytes key = 1; + bytes value = 2; + bool index = 3; // nondeterministic +} +``` + Example: ```go @@ -563,12 +580,7 @@ via light client. ### PubKey - **Fields**: - - `Type (string)`: Type of the public key. A simple string like `"ed25519"`. - In the future, may indicate a serialization algorithm to parse the `Data`, - for instance `"amino"`. - - `Data ([]byte)`: Public key data. For a simple public key, it's just the - raw bytes. If the `Type` indicates an encoding algorithm, this is the - encoded public key. + - `Sum (oneof PublicKey)`: This field is a Protobuf [`oneof`](https://developers.google.com/protocol-buffers/docs/proto#oneof) - **Usage**: - A generic and extensible typed public key @@ -626,8 +638,8 @@ via light client. ### ValidatorParams - **Fields**: - - `PubKeyTypes ([]string)`: List of accepted pubkey types. Uses same - naming as `PubKey.Type`. + - `PubKeyTypes ([]string)`: List of accepted public key types. + - Uses same naming as `PubKey.Type`. ### VersionParams diff --git a/spec/abci/apps.md b/spec/abci/apps.md index b594de4f9..68aa542a1 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -250,21 +250,21 @@ blockchain. Updates to the Tendermint validator set can be made by returning `ValidatorUpdate` objects in the `ResponseEndBlock`: -```proto +```protobuf message ValidatorUpdate { - PubKey pub_key + tendermint.crypto.keys.PublicKey pub_key int64 power } -message PubKey { - string type - bytes data -} +message PublicKey { + oneof { + ed25519 bytes = 1; + } ``` The `pub_key` currently supports only one type: -- `type = "ed25519"` and `data = ` +- `type = "ed25519"` The `power` is the new voting power for the validator, with the following rules: @@ -288,7 +288,7 @@ evidence. They can be set in InitChain and updated in EndBlock. ### BlockParams.MaxBytes -The maximum size of a complete Amino encoded block. +The maximum size of a complete Protobuf encoded block. This is enforced by Tendermint consensus. This implies a maximum tx size that is this MaxBytes, less the expected size of @@ -314,6 +314,8 @@ This is enforced by Tendermint consensus. Must have `TimeIotaMs > 0` to ensure time monotonicity. +> *Note: This is not exposed to the application* + ### EvidenceParams.MaxAgeDuration This is the maximum age of evidence in time units. @@ -383,7 +385,7 @@ 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 lite-clients. +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 @@ -402,9 +404,9 @@ 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 lite-client proofs. +For such applications, the `AppHash` provides a much more efficient way to verify light-client proofs. -ABCI applications can take advantage of more efficient lite-client proofs for +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 @@ -413,15 +415,15 @@ their state as follows: - 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 lite-client to verify proofs of +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: -```proto -message Proof { +```protobuf +message ProofOps { repeated ProofOp ops } diff --git a/spec/abci/client-server.md b/spec/abci/client-server.md index 967199455..865957267 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -41,7 +41,7 @@ See examples, in various stages of maintenance, in ### In Process The simplest implementation uses function calls within Golang. -This means ABCI applications written in Golang can be compiled with TendermintCore and run as a single binary. +This means ABCI applications written in Golang can be compiled with Tendermint Core and run as a single binary. ### GRPC diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 51061867e..fc535954c 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -89,11 +89,11 @@ parts (ie. `len(MakeParts(block))`) ```go type BlockID struct { Hash []byte - PartsHeader PartSetHeader + PartSetHeader PartSetHeader } type PartSetHeader struct { - Total int32 + Total uint32 Hash []byte } ``` @@ -125,7 +125,7 @@ validator. It also contains the relevant BlockID, height and round: ```go type Commit struct { Height int64 - Round int + Round int32 BlockID BlockID Signatures []CommitSig } @@ -168,17 +168,28 @@ The vote includes information about the validator signing it. ```go type Vote struct { - Type byte + Type SignedMsgType Height int64 - Round int + Round int32 BlockID BlockID Timestamp Time ValidatorAddress []byte - ValidatorIndex int + ValidatorIndex int32 Signature []byte } ``` +```protobuf +enum SignedMsgType { + SIGNED_MSG_TYPE_UNKNOWN = 0; + // Votes + PREVOTE_TYPE = 1; + PRECOMMIT_TYPE = 2; + // Proposals + PROPOSAL_TYPE = 32; +} +``` + There are two types of votes: a _prevote_ has `vote.Type == 1` and a _precommit_ has `vote.Type == 2`. @@ -412,7 +423,7 @@ first by voting power (descending), then by address (ascending). block.ConsensusHash == state.ConsensusParams.Hash() ``` -Hash of the amino-encoding of a subset of the consensus parameters. +Hash of the protobuf-encoding of a subset of the consensus parameters. ### AppHash @@ -497,9 +508,9 @@ The number of votes in a commit is limited to 10000 (see `types.MaxVotesCount`). ### Vote A vote is a signed message broadcast in the consensus for a particular block at a particular height and round. -When stored in the blockchain or propagated over the network, votes are encoded in Amino. -For signing, votes are represented via `CanonicalVote` and also encoded using amino (protobuf compatible) via -`Vote.SignBytes` which includes the `ChainID`, and uses a different ordering of +When stored in the blockchain or propagated over the network, votes are encoded in Protobuf. +For signing, votes are represented via `CanonicalVote` and also encoded using Protobuf via +`VoteSignBytes` 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` diff --git a/spec/core/encoding.md b/spec/core/encoding.md index ae6e4e42b..7015f3e34 100644 --- a/spec/core/encoding.md +++ b/spec/core/encoding.md @@ -1,22 +1,10 @@ # Encoding -## Amino +## Protocol Buffers -Tendermint uses the proto3 derivative [Amino](https://github.com/tendermint/go-amino) for all data structures. -Think of Amino as an object-oriented proto3 with native JSON support. -The goal of the Amino encoding protocol is to bring parity between application -logic objects and persistence objects. +Tendermint uses [Protocol Buffers](https://developers.google.com/protocol-buffers), specifically proto3, for all data structures. -Please see the [Amino -specification](https://github.com/tendermint/go-amino#amino-encoding-for-go) for -more details. - -Notably, every object that satisfies an interface (eg. a particular kind of p2p message, -or a particular kind of pubkey) is registered with a global name, the hash of -which is included in the object's encoding as the so-called "prefix bytes". - -We define the `func AminoEncode(obj interface{}) []byte` function to take an -arbitrary object and return the Amino encoded bytes. +Please see the [Proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3) for more details. ## Byte Arrays @@ -34,47 +22,18 @@ be encoded as `0xAC020A0B...` where `0xAC02` is the UVarint encoding of 300. Tendermint uses `SHA256` as its hash function. Objects are always Amino encoded before being hashed. -So `SHA256(obj)` is short for `SHA256(AminoEncode(obj))`. +So `SHA256(obj)` is short for `SHA256(ProtoEncoding(obj))`. ## Public Key Cryptography -Tendermint uses Amino to distinguish between different types of private keys, -public keys, and signatures. Additionally, for each public key, Tendermint +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, as they can be -derived the same way as the others using Amino. - -All registered objects are encoded by Amino using a 4-byte PrefixBytes that -uniquely identifies the object and includes information about its underlying -type. For details on how PrefixBytes are computed, see the [Amino -spec](https://github.com/tendermint/go-amino#computing-the-prefix-and-disambiguation-bytes). - -In what follows, we provide the type names and prefix bytes directly. -Notice that when encoding byte-arrays, the length of the byte-array is appended -to the PrefixBytes. Thus the encoding of a byte array becomes ` `. In other words, to encode any type listed below you do not need to be -familiar with amino encoding. -You can simply use below table and concatenate Prefix || Length (of raw bytes) || raw bytes -( while || stands for byte concatenation here). - -| Type | Name | Prefix | Length | Notes | -| ----------------------- | ---------------------------------- | ---------- | -------- | ----- | -| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | | -| PubKeySr25519 | tendermint/PubKeySr25519 | 0x0DFB1005 | 0x20 | | -| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | | -| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | | -| PrivKeySr25519 | tendermint/PrivKeySr25519 | 0x2F82D78B | 0x20 | | -| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | | -| PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | | - -### Example - -For example, the 33-byte (or 0x21-byte in hex) Secp256k1 pubkey -`020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` -would be encoded as -`EB5AE98721020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` +include details of the private keys beyond their type and name. ### Key Types @@ -92,39 +51,6 @@ address = SHA256(pubkey)[:20] The signature is the raw 64-byte ED25519 signature. -#### Sr25519 - -TODO: pubkey - -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. - -#### Secp256k1 - -TODO: pubkey - -The address is the RIPEMD160 hash of the SHA256 hash of the OpenSSL compressed public key: - -```go -address = RIPEMD160(SHA256(pubkey)) -``` - -This is the same as Bitcoin. - -The signature is the 64-byte concatenation of ECDSA `r` and `s` (ie. `r || s`), -where `s` is lexicographically less than its inverse, to prevent malleability. -This is like Ethereum, but without the extra byte for pubkey recovery, since -Tendermint assumes the pubkey is always provided anyway. - -#### Multisig - -TODO - ## Other Common Types ### BitArray @@ -136,7 +62,7 @@ encoded in base64 (`Elems`). ```go type BitArray struct { - Bits int + Bits int64 Elems []uint64 } ``` @@ -158,7 +84,7 @@ the set (`Proof`). ```go type Part struct { - Index int + Index uint32 Bytes []byte Proof SimpleProof } @@ -168,7 +94,7 @@ See details of SimpleProof, below. ### MakeParts -Encode an object using Amino and slice it into parts. +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. @@ -260,27 +186,27 @@ func Hashes(items [][]byte) [][]byte { ``` 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 amino encoding of each +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 amino encoding the individual `struct` elements. +For `[]struct` arguments, we compute a `[][]byte` by protobuf encoding the individual `struct` elements. -### Simple Merkle Proof +### Merkle Proof Proof that a leaf is in a Merkle tree is composed as follows: ```golang -type SimpleProof struct { - Total int - Index int - LeafHash []byte - Aunts [][]byte +type Proof struct { + Total int + Index int + LeafHash []byte + Aunts [][]byte } ``` Which is verified as follows: ```golang -func (proof SimpleProof) Verify(rootHash []byte, leaf []byte) bool { +func (proof Proof) Verify(rootHash []byte, leaf []byte) bool { assert(proof.LeafHash, leafHash(leaf) computedHash := computeHashFromAunts(proof.Index, proof.Total, proof.LeafHash, proof.Aunts) @@ -319,13 +245,13 @@ Because Tendermint only uses a Simple Merkle Tree, application developers are ex ## JSON -### Amino +Tendermint has its own JSON encoding in order to keep backwards compatibility with the prvious RPC layer. -Amino also supports JSON encoding - registered types are simply encoded as: +Registered types are encoded as: ```json { - "type": "", + "type": "", "value": } ``` @@ -340,30 +266,33 @@ For instance, an ED25519 PubKey would look like: ``` Where the `"value"` is the base64 encoding of the raw pubkey bytes, and the -`"type"` is the amino name for Ed25519 pubkeys. +`"type"` is the type name for Ed25519 pubkeys. ### Signed Messages -Signed messages (eg. votes, proposals) in the consensus are encoded using Amino. +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 Amino encoding of the following struct: +We call this encoding the SignBytes. For instance, SignBytes for a vote is the protobuf encoding of the following struct: -```go -type CanonicalVote struct { - Type byte - Height int64 `binary:"fixed64"` - Round int64 `binary:"fixed64"` - BlockID CanonicalBlockID - Timestamp time.Time - ChainID string +```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/state.md b/spec/core/state.md index 6b12ba4a9..eb020e489 100644 --- a/spec/core/state.md +++ b/spec/core/state.md @@ -7,7 +7,7 @@ necessary for validating new blocks. For instance, the validators set and the re transactions are never included in blocks, but their Merkle roots are - the state keeps track of them. Note that the `State` object itself is an implementation detail, since it is never -included in a block or gossipped over the network, and we never compute +included in a block or gossiped over the network, and we never compute its hash. Thus we do not include here details of how the `State` object is persisted or queried. That said, the types it contains are part of the specification, since their Merkle roots are included in blocks and their values are used in @@ -55,21 +55,24 @@ type Consensus struct { } ``` -### Result +### ResponseDeliverTx -```go -type Result struct { - Code uint32 - Data []byte +```protobuf +message ResponseDeliverTx { + uint32 code = 1; + bytes data = 2; + string log = 3; // nondeterministic + string info = 4; // nondeterministic + int64 gas_wanted = 5; + int64 gas_used = 6; + repeated Event events = 7 + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; + string codespace = 8; } ``` -`Result` is the result of executing a transaction against the application. -It returns a result code and an arbitrary byte array (ie. a return value). - -NOTE: the Result needs to be updated to include more fields returned from -processing transactions, like gas variables and events - see -[issue 1007](https://github.com/tendermint/tendermint/issues/1007). +`ResponseDeliverTx` is the result of executing a transaction against the application. +It returns a result code (`uint32`), an arbitrary byte array (`[]byte`) (ie. a return value), Log (`string`), Info (`string`), GasWanted (`int64`), GasUsed (`int64`), Events (`[]Events`) and a Codespace (`string`). ### Validator @@ -110,45 +113,48 @@ Like validator sets, they are set during genesis and can be updated by the appli When hashed, only a subset of the params are included, to allow the params to evolve without breaking the header. -```go -type ConsensusParams struct { - Block - Evidence - Validator - Version +```protobuf +message ConsensusParams { + BlockParams block = 1; + EvidenceParams evidence = 2; + ValidatorParams validator = 3; + VersionParams version = 4; } +``` +```go type hashedParams struct { BlockMaxBytes int64 BlockMaxGas int64 } -func (params ConsensusParams) Hash() []byte { +func HashConsensusParams() []byte { SHA256(hashedParams{ BlockMaxBytes: params.Block.MaxBytes, BlockMaxGas: params.Block.MaxGas, }) } +``` -type BlockParams struct { - MaxBytes int64 - MaxGas int64 - TimeIotaMs int64 +```protobuf +message BlockParams { + int64 max_bytes = 1; + int64 max_gas = 2; + int64 time_iota_ms = 3; // not exposed to the application } -type EvidenceParams struct { - MaxAgeNumBlocks int64 - MaxAgeDuration time.Duration - MaxNum uint32 - ProofTrialPeriod int64 +message EvidenceParams { + int64 max_age_num_blocks = 1; + google.protobuf.Duration max_age_duration = 2; + uint32 max_num = 3; } -type ValidatorParams struct { - PubKeyTypes []string +message ValidatorParams { + repeated string pub_key_types = 1; } -type VersionParams struct { - AppVersion uint64 +message VersionParams { + uint64 AppVersion = 1; } ``` diff --git a/spec/p2p/connection.md b/spec/p2p/connection.md index 6babff589..33178f479 100644 --- a/spec/p2p/connection.md +++ b/spec/p2p/connection.md @@ -38,7 +38,7 @@ type msgPacket struct { } ``` -The `msgPacket` is serialized using [go-amino](https://github.com/tendermint/go-amino) and prefixed with 0x3. +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. diff --git a/spec/p2p/peer.md b/spec/p2p/peer.md index ca791d799..a81b2a7bd 100644 --- a/spec/p2p/peer.md +++ b/spec/p2p/peer.md @@ -21,7 +21,7 @@ corresponding to ``. This prevents man-in-the-middle attacks on the peer lay All p2p connections use TCP. Upon establishing a successful TCP connection with a peer, -two handhsakes are performed: one for authenticated encryption, and one for Tendermint versioning. +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 @@ -29,8 +29,8 @@ Both handshakes have configurable timeouts (they should complete quickly). Tendermint implements the Station-to-Station protocol using X25519 keys for Diffie-Helman key-exchange and chacha20poly1305 for encryption. -Previous versions of this protocol suffered from malleability attacks whereas an active man -in the middle attacker could compromise confidentiality as decribed in [Prime, Order Please! +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). From 0794fc8ff2374efa0aade716d8a275915c4609bf Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Thu, 8 Oct 2020 09:51:00 +0200 Subject: [PATCH 104/223] first check latest with secondary (#184) --- .../detection/detection_002_draft.md | 797 ++++++++++++++++++ 1 file changed, 797 insertions(+) create mode 100644 rust-spec/lightclient/detection/detection_002_draft.md diff --git a/rust-spec/lightclient/detection/detection_002_draft.md b/rust-spec/lightclient/detection/detection_002_draft.md new file mode 100644 index 000000000..6eab312d8 --- /dev/null +++ b/rust-spec/lightclient/detection/detection_002_draft.md @@ -0,0 +1,797 @@ +# ***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.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 + // 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 + // 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 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 From 792767d1cb279ddeac95b7c32e5e750572026914 Mon Sep 17 00:00:00 2001 From: Igor Konnov Date: Thu, 8 Oct 2020 10:42:00 +0200 Subject: [PATCH 105/223] Extending the blockchain specification (in the light client) to produce different ratios of faults (#183) * cleaning unused definitions * introduced the ratio of faulty processes --- .../verification/Blockchain_003_draft.tla | 164 ++++++ .../verification/Lightclient_003_draft.tla | 476 ++++++++++++++++++ .../verification/MC4_3_correct.tla | 10 +- .../lightclient/verification/MC4_3_faulty.tla | 10 +- .../lightclient/verification/MC4_4_faulty.tla | 10 +- .../verification/MC4_5_correct.tla | 10 +- .../lightclient/verification/MC4_5_faulty.tla | 10 +- .../lightclient/verification/MC4_6_faulty.tla | 10 +- .../lightclient/verification/MC4_7_faulty.tla | 10 +- .../verification/MC5_5_correct.tla | 12 +- .../MC5_5_correct_peer_two_thirds_faulty.tla | 23 + .../lightclient/verification/MC5_5_faulty.tla | 12 +- .../MC5_5_faulty_peer_two_thirds_faulty.tla | 23 + .../lightclient/verification/MC5_7_faulty.tla | 10 +- .../lightclient/verification/MC7_5_faulty.tla | 10 +- .../lightclient/verification/MC7_7_faulty.tla | 10 +- 16 files changed, 796 insertions(+), 14 deletions(-) create mode 100644 rust-spec/lightclient/verification/Blockchain_003_draft.tla create mode 100644 rust-spec/lightclient/verification/Lightclient_003_draft.tla create mode 100644 rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla create mode 100644 rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla diff --git a/rust-spec/lightclient/verification/Blockchain_003_draft.tla b/rust-spec/lightclient/verification/Blockchain_003_draft.tla new file mode 100644 index 000000000..e80fbae56 --- /dev/null +++ b/rust-spec/lightclient/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 + 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. + + 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]: + \* 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 + \* 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 >= now /\ now' = 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/rust-spec/lightclient/verification/Lightclient_003_draft.tla b/rust-spec/lightclient/verification/Lightclient_003_draft.tla new file mode 100644 index 000000000..873f76d30 --- /dev/null +++ b/rust-spec/lightclient/verification/Lightclient_003_draft.tla @@ -0,0 +1,476 @@ +-------------------------- MODULE Lightclient_003_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? *) + 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 *) + 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_003_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(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 + /\ 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] + +(** + + *) +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" = 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/rust-spec/lightclient/verification/MC4_3_correct.tla b/rust-spec/lightclient/verification/MC4_3_correct.tla index 951a471da..534f9963e 100644 --- a/rust-spec/lightclient/verification/MC4_3_correct.tla +++ b/rust-spec/lightclient/verification/MC4_3_correct.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 3 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================== diff --git a/rust-spec/lightclient/verification/MC4_3_faulty.tla b/rust-spec/lightclient/verification/MC4_3_faulty.tla index c711ee9b8..ae82cb7e7 100644 --- a/rust-spec/lightclient/verification/MC4_3_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_3_faulty.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 3 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================== diff --git a/rust-spec/lightclient/verification/MC4_4_faulty.tla b/rust-spec/lightclient/verification/MC4_4_faulty.tla index 8b8d25ece..8836a1359 100644 --- a/rust-spec/lightclient/verification/MC4_4_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_4_faulty.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 4 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================== diff --git a/rust-spec/lightclient/verification/MC4_5_correct.tla b/rust-spec/lightclient/verification/MC4_5_correct.tla index 04489ec7f..dd1b1298a 100644 --- a/rust-spec/lightclient/verification/MC4_5_correct.tla +++ b/rust-spec/lightclient/verification/MC4_5_correct.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 5 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ diff --git a/rust-spec/lightclient/verification/MC4_5_faulty.tla b/rust-spec/lightclient/verification/MC4_5_faulty.tla index 11dbd2ec6..a7a5ca8f8 100644 --- a/rust-spec/lightclient/verification/MC4_5_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_5_faulty.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 5 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ diff --git a/rust-spec/lightclient/verification/MC4_6_faulty.tla b/rust-spec/lightclient/verification/MC4_6_faulty.tla index d0d98444f..14eced4d9 100644 --- a/rust-spec/lightclient/verification/MC4_6_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_6_faulty.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 6 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ diff --git a/rust-spec/lightclient/verification/MC4_7_faulty.tla b/rust-spec/lightclient/verification/MC4_7_faulty.tla index 60dcd3c5d..d6214b868 100644 --- a/rust-spec/lightclient/verification/MC4_7_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_7_faulty.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 7 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ diff --git a/rust-spec/lightclient/verification/MC5_5_correct.tla b/rust-spec/lightclient/verification/MC5_5_correct.tla index 926501761..f78c60eb5 100644 --- a/rust-spec/lightclient/verification/MC5_5_correct.tla +++ b/rust-spec/lightclient/verification/MC5_5_correct.tla @@ -3,13 +3,21 @@ AllNodes == {"n1", "n2", "n3", "n4", "n5"} TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 5 -TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) +TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ diff --git a/rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla b/rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla new file mode 100644 index 000000000..41b906660 --- /dev/null +++ b/rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla @@ -0,0 +1,23 @@ +------------------- 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 :-) +IS_PRIMARY_CORRECT == TRUE +FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators + +VARIABLES + state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, + latestVerified, nprobes, + now, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/rust-spec/lightclient/verification/MC5_5_faulty.tla b/rust-spec/lightclient/verification/MC5_5_faulty.tla index abf5e8c72..5a124959f 100644 --- a/rust-spec/lightclient/verification/MC5_5_faulty.tla +++ b/rust-spec/lightclient/verification/MC5_5_faulty.tla @@ -1,15 +1,23 @@ -------------------------- MODULE MC5_5_faulty --------------------------- +----------------- MODULE MC5_5_faulty_peers_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 :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ diff --git a/rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla b/rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla new file mode 100644 index 000000000..9dadbcb31 --- /dev/null +++ b/rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla @@ -0,0 +1,23 @@ +----------------- 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 :-) +IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<2, 3>> \* < 2 / 3 faulty validators + +VARIABLES + state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, + latestVerified, nprobes, + now, blockchain, Faulty + +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft +============================================================================ diff --git a/rust-spec/lightclient/verification/MC5_7_faulty.tla b/rust-spec/lightclient/verification/MC5_7_faulty.tla index 5d3d265ab..e8cde75e6 100644 --- a/rust-spec/lightclient/verification/MC5_7_faulty.tla +++ b/rust-spec/lightclient/verification/MC5_7_faulty.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 7 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ diff --git a/rust-spec/lightclient/verification/MC7_5_faulty.tla b/rust-spec/lightclient/verification/MC7_5_faulty.tla index ae8af692a..855415b26 100644 --- a/rust-spec/lightclient/verification/MC7_5_faulty.tla +++ b/rust-spec/lightclient/verification/MC7_5_faulty.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 5 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ diff --git a/rust-spec/lightclient/verification/MC7_7_faulty.tla b/rust-spec/lightclient/verification/MC7_7_faulty.tla index 84065214e..ea6fcec1f 100644 --- a/rust-spec/lightclient/verification/MC7_7_faulty.tla +++ b/rust-spec/lightclient/verification/MC7_7_faulty.tla @@ -5,11 +5,19 @@ TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 7 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) IS_PRIMARY_CORRECT == FALSE +FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators VARIABLES state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, nprobes, now, blockchain, Faulty -INSTANCE Lightclient_A_1 +(* the light client previous state components, used for monitoring *) +VARIABLES + prevVerified, + prevCurrent, + prevNow, + prevVerdict + +INSTANCE Lightclient_003_draft ============================================================================ From 01622f81e99914ae31663950efc368c0f9b28c23 Mon Sep 17 00:00:00 2001 From: Sam Hart Date: Wed, 14 Oct 2020 17:08:20 +0200 Subject: [PATCH 106/223] Update README.md (#185) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd6c82981..678574873 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ maintained by Informal Systems. There have been inadvertent divergences in the specs followed by the Go implementation and the Rust implementation respectively. -However, we are worked to reconverge these specs into a single unified spec. +However, we are working toward reconverging these specs into a single unified spec. Consequently, this repository is in a bit of a state of flux. At the moment, the spec followed by the Go implementation From a3fadb7c1a17e9c6764a37eb956f2f6f860064d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 13:15:32 +0200 Subject: [PATCH 107/223] 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> --- .github/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 787bf46ed..dec3618d8 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -7,4 +7,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - - uses: gaurav-nelson/github-action-markdown-link-check@1.0.7 + - uses: gaurav-nelson/github-action-markdown-link-check@1.0.8 From ec8af314cce85fc0f4bad16086187314b859c61a Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Thu, 22 Oct 2020 14:26:56 +0200 Subject: [PATCH 108/223] 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 --- rust-spec/lightclient/README.md | 6 +- .../verification/Lightclient_A_1.tla | 2 +- .../lightclient/verification/verification.md | 1166 +--------------- .../verification_001_published.md | 1181 +++++++++++++++++ .../verification/verification_002_draft.md | 1061 +++++++++++++++ 5 files changed, 2250 insertions(+), 1166 deletions(-) create mode 100644 rust-spec/lightclient/verification/verification_001_published.md create mode 100644 rust-spec/lightclient/verification/verification_002_draft.md diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md index 1958dc007..4f30ed5db 100644 --- a/rust-spec/lightclient/README.md +++ b/rust-spec/lightclient/README.md @@ -18,10 +18,10 @@ The light client is decomposed into three components: ## Commit Verification -The [English specification](verification/verification.md) describes the light client +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.md#lcv-dist-safe1) and -[LCV-DIST-LIVE.1](https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification.md#lcv-dist-live1). +[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. diff --git a/rust-spec/lightclient/verification/Lightclient_A_1.tla b/rust-spec/lightclient/verification/Lightclient_A_1.tla index 2be9b8788..70e6caf00 100644 --- a/rust-spec/lightclient/verification/Lightclient_A_1.tla +++ b/rust-spec/lightclient/verification/Lightclient_A_1.tla @@ -2,7 +2,7 @@ (** * 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 + * ./verification_001_published.md *) EXTENDS Integers, FiniteSets diff --git a/rust-spec/lightclient/verification/verification.md b/rust-spec/lightclient/verification/verification.md index f9ccc2160..09226ab64 100644 --- a/rust-spec/lightclient/verification/verification.md +++ b/rust-spec/lightclient/verification/verification.md @@ -1,1162 +1,4 @@ -# 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 - NextValidators ValidatorSet - Provider PeerID -} -``` - -#### **[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. - -```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. - -```go -func (ls LightStore) LatestVerified() LightBlock -``` - -- Expected postcondition - - returns the highest light block whose state is `StateVerified` - or `StateTrusted` - -#### **[LCV-FUNC-UPDATE.1]** - -```go -func (ls LightStore) Update(lightBlock LightBlock, verfiedState VerifiedState) -``` - -- Expected postcondition - - The state of the LightBlock is set to *verifiedState*. - -> The following function is used only in the detector specification -> listed here for completeness. - -```go -func (ls LightStore) LatestTrusted() LightBlock -``` - -- Expected postcondition - - returns the highest light block that has been verified and - checked by the detector. - -### 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 == OK { - lightStore.Update(current, StateVerified) - } - else if 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. - } - 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 `OK`: - - 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 `CANNOT_VERIFY` 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 `OK`, 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 `OK`, 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/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_001_reviewed.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/core/data_structures.md - - - -[arXiv]: https://arxiv.org/abs/1807.04938 +We changed the naming convention and versioning of specifications. +See [verification_001_published.md](./verification_001_published.md) +for the file that used to be called `verification.md`. There are also newer +versions of this specification in this directory. \ No newline at end of file diff --git a/rust-spec/lightclient/verification/verification_001_published.md b/rust-spec/lightclient/verification/verification_001_published.md new file mode 100644 index 000000000..05baedb3f --- /dev/null +++ b/rust-spec/lightclient/verification/verification_001_published.md @@ -0,0 +1,1181 @@ +# 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 + NextValidators ValidatorSet + Provider PeerID +} +``` + +#### **[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 diff --git a/rust-spec/lightclient/verification/verification_002_draft.md b/rust-spec/lightclient/verification/verification_002_draft.md new file mode 100644 index 000000000..80a1890c4 --- /dev/null +++ b/rust-spec/lightclient/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 + NextValidators ValidatorSet + Provider PeerID +} +``` + +#### **[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.2]** + +It is always the case that *LightStore.LatestVerified.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 From 97928e190a090ad749b3994b015fdeb6b74b40b2 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 2 Nov 2020 16:06:17 +0100 Subject: [PATCH 109/223] github: issue template for proposals (#190) --- .github/ISSUE_TEMPLATE/proposal.md | 37 ++++++++++++++++++++++++++++++ .github/workflows/linter.yml | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/proposal.md diff --git a/.github/ISSUE_TEMPLATE/proposal.md b/.github/ISSUE_TEMPLATE/proposal.md new file mode 100644 index 000000000..45f0bff42 --- /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/.github/workflows/linter.yml b/.github/workflows/linter.yml index 0a6c546c6..0f12df470 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -21,7 +21,7 @@ jobs: uses: docker://github/super-linter:v3 env: LINTER_RULES_PATH: . - VALIDATE_ALL_CODEBASE: true + VALIDATE_ALL_CODEBASE: false DEFAULT_BRANCH: master GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_MD: true From 9ad6440bc09d46821f8894e81f2cc0f33a6040bb Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Mon, 2 Nov 2020 17:35:25 +0100 Subject: [PATCH 110/223] 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 --- .../supervisor/supervisor_001_draft.md | 648 ++++++++++++++++++ .../supervisor/supervisor_001_draft.tla | 71 ++ 2 files changed, 719 insertions(+) create mode 100644 rust-spec/lightclient/supervisor/supervisor_001_draft.md create mode 100644 rust-spec/lightclient/supervisor/supervisor_001_draft.tla diff --git a/rust-spec/lightclient/supervisor/supervisor_001_draft.md b/rust-spec/lightclient/supervisor/supervisor_001_draft.md new file mode 100644 index 000000000..cdd291436 --- /dev/null +++ b/rust-spec/lightclient/supervisor/supervisor_001_draft.md @@ -0,0 +1,648 @@ +# Draft of Light Client Supervisor for discussion + +## TODOs + +This specification in done in parallel with updates on the +verification specification. So some hyperlinks have to be placed to +the correct files eventually. + + +# Light Client Sequential Supervisor + +The light client implements a read operation of a +[header](TMBC-HEADER-link) from the [blockchain](TMBC-SEQ-link), by +communicating with full nodes, a so-called primary and several +so-called witnesses. 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*. + +[Light Client Verification](https://informal.systems) implements the fault-tolerant read +operation designed for this security model. That is, it is safe if the +model assumptions are satisfied and makes progress if it communicates +to a correct primary. + +However, if the [security model](TMBC-FM-2THIRDS-link) is violated, +faulty peers (that have been validators at some point in the past) may +launch attacks on the Tendermint network, and on the light +client. These attacks as well as an axiomatization of blocks in +general are defined in [a document that contains the definitions that +are currently in detection.md](https://informal.systems). + +If there is a light client attack (but no +successful attack on the network), the safety of the verification step +may be violated (as we operate outside its basic assumption). +The light client also +contains a defense mechanism against light clients attacks, called detection. + +[Light Client Detection](https://informal.systems) implements a cross check of the result +of the verification step. If there is a light client attack, and the +light client is connected to a correct peer, the light client as a +whole is safe, that is, it will not operate on invalid +blocks. However, in this case it cannot successfully read, as +inconsistent blocks are in the system. However, in this case the +detection performs a distributed computation that results in so-called +evidence. Evidence can be used to prove +to a correct full node that there has been a +light client attack. + +[Light Client Evidence Accountability](https://informal.systems) is a protocol run on a +full node to check whether submitted evidence indeed proves the +existence of a light client attack. Further, from the evidence and its +own knowledge about the blockchain, the full node computes a set of +bonded full nodes (that at some point had more than one third of the +voting power) that participated in the attack that will be reported +via ABCI to the application. + +In this document we specify + +- Initialization of the Light Client +- The interaction of [verification](https://informal.systems) and [detection](https://informal.systems) + +The details of these two protocols are captured in their own +documents, as is the [accountability](https://informal.systems) protocol. + +> 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*. +- 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 [LCD-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 + 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 + +---- + diff --git a/rust-spec/lightclient/supervisor/supervisor_001_draft.tla b/rust-spec/lightclient/supervisor/supervisor_001_draft.tla new file mode 100644 index 000000000..949a7c020 --- /dev/null +++ b/rust-spec/lightclient/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 From d31a4a4b345e68bc8657f7f744e8eac9c25ee025 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 5 Nov 2020 09:57:08 +0100 Subject: [PATCH 111/223] RFC: adopt zip 215 (#144) Co-authored-by: Robert Zaremba --- rfc/003-ed25519-verification.md | 56 +++++++++++++++++++++++++++++++++ rfc/README.md | 2 ++ 2 files changed, 58 insertions(+) create mode 100644 rfc/003-ed25519-verification.md diff --git a/rfc/003-ed25519-verification.md b/rfc/003-ed25519-verification.md new file mode 100644 index 000000000..140717b0a --- /dev/null +++ b/rfc/003-ed25519-verification.md @@ -0,0 +1,56 @@ +# RFC 003: Ed25519 Verification + +## Changelog + +- August 21, 2020: initialized + +## Author(s) + +- Marko (@marbar3778) + +## Context + +Ed25519 keys are the only supported key types for Tendermint validators currently. Tendermint-Go wraps the ed25519 key implementation from the go standard library. As more clients are implemented to communicate with the canonical Tendermint implementation (Tendermint-Go) different implementations of ed25519 will be used. Due to [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032.html) not guaranteeing implementation compatibility, Tendermint clients must to come to an agreement of how to guarantee implementation compatibility. [Zcash](https://z.cash/) has multiple implementations of their client and have identified this as a problem as well. The team at Zcash has made a proposal to address this issue, [Zcash improvement proposal 215](https://zips.z.cash/zip-0215). + +## Proposal + +- Tendermint-Go would adopt [hdevalence/ed25519consensus](https://github.com/hdevalence/ed25519consensus). + - This library is implements `ed25519.Verify()` in accordance to zip-215. Tendermint-go will continue to use `crypto/ed25519` for signing and key generation. + +- Tendermint-rs would adopt [ed25519-zebra](https://github.com/ZcashFoundation/ed25519-zebra) + - related [issue](https://github.com/informalsystems/tendermint-rs/issues/355) + +Signature verification is one of the major bottlenecks of Tendermint-go, batch verification can not be used unless it has the same consensus rules, ZIP 215 makes verification safe in consensus critical areas. + +This change constitutes a breaking changes, therefore must be done in a major release. No changes to validator keys or operations will be needed for this change to be enabled. + +This change has no impact on signature aggregation. To enable this signature aggregation Tendermint will have to use different signature schema (Schnorr, BLS, ...). Secondly, this change will enable safe batch verification for the Tendermint-Go client. Batch verification for the rust client is already supported in the library being used. + +As part of the acceptance of this proposal it would be best to contract or discuss with a third party the process of conducting a security review of the go library. + +## Status + +Proposed + +## Consequences + +### Positive + +- Consistent signature verification across implementations +- Enable safe batch verification + +### Negative + +#### Tendermint-Go + +- Third_party dependency + - library has not gone through a security review. + - unclear maintenance schedule +- Fragmentation of the ed25519 key for the go implementation, verification is done using a third party library while the rest + uses the go standard library + +### Neutral + +## References + +[It’s 255:19AM. Do you know what your validation criteria are?](https://hdevalence.ca/blog/2020-10-04-its-25519am) diff --git a/rfc/README.md b/rfc/README.md index ef91e9d30..9405b3fb0 100644 --- a/rfc/README.md +++ b/rfc/README.md @@ -23,3 +23,5 @@ Some RFC's will be presented at a Tendermint Dev Session. If you are an outside ## Table of Contents [001-block-retention](./001-block-retention.md) +[002-nonzero-genesis](./002-nonzero-genesis.md) +[003-ed25519-verification](./003-ed25519-verification.md) From 9fce8480b08502f8d5cfab9b00cf2a600da130a4 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 5 Nov 2020 10:09:59 +0100 Subject: [PATCH 112/223] Core: move validation & data structures together (#176) Co-authored-by: Callum Waters --- spec/core/data_structures.md | 858 ++++++++++++++--------------------- 1 file changed, 336 insertions(+), 522 deletions(-) diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index fc535954c..566be7fbe 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -2,535 +2,42 @@ 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 basic data types: +The Tendermint blockchains consists of a short list of data types: -- `Block` -- `Header` -- `Version` -- `BlockID` -- `Time` -- `Data` (for transactions) -- `Commit` and `Vote` -- `EvidenceData` and `Evidence` +- [`Block`](#block) +- [`Header`](#header) +- [`Version`](#version) +- [`BlockID`](#blockid) +- [`PartSetHeader`](#partsetheader) +- [`Time`](#time) +- [`Data` (for transactions)](#data) +- [`Commit`](#commit) +- [`CommitSig`](#commitsig) +- [`BlockIDFlag`](#blockidflag) +- [`Vote`](#vote) +- [`CanonicalVote`](#canonicalvote) +- [`SignedMsgType`](#signedmsgtype) +- [`EvidenceData`](#evidence_data) +- [`Evidence`](#evidence) +- [`DuplicateVoteEvidence`](#duplicatevoteevidence) +- [`LightClientAttackEvidence`](#lightclientattackevidence) +- [`LightBlock](#lightblock) +- [`SignedHeader`](#signedheader) +- [`Validator`](#validator) +- [`ValidatorSet`](#validatorset) +- [`Address`](#address) ## Block A block consists of a header, transactions, votes (the commit), and a list of evidence of malfeasance (ie. signing conflicting votes). -```go -type Block struct { - Header Header - Txs Data - Evidence EvidenceData - LastCommit Commit -} -``` - -Note the `LastCommit` is the set of signatures of validators that committed the last block. - -## 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: - -```go -type Header struct { - // basic block info - Version Version - ChainID string - Height int64 - Time Time - - // prev block info - LastBlockID BlockID - - // hashes of block data - LastCommitHash []byte // commit from validators from the last block - DataHash []byte // MerkleRoot of transaction hashes - - // hashes from the app output from the prev block - ValidatorsHash []byte // validators for the current block - NextValidatorsHash []byte // validators for the next block - ConsensusHash []byte // consensus params for current block - AppHash []byte // state after txs from the previous block - LastResultsHash []byte // root hash of all results from the txs from the previous block - - // consensus info - EvidenceHash []byte // evidence included in the block - ProposerAddress []byte // original proposer of the block -``` - -Further details on each of these fields is described below. - -## Version - -```go -type Version struct { - Block uint64 - App uint64 -} -``` - -The `Version` contains the protocol version for the blockchain and the -application as two `uint64` values. - -## BlockID - -The `BlockID` contains two distinct Merkle roots of the block. -The first, used as the block's main hash, is the MerkleRoot -of all the fields in the header (ie. `MerkleRoot(header)`. -The second, used for secure gossipping of the block during consensus, -is the MerkleRoot of the complete serialized block -cut into parts (ie. `MerkleRoot(MakeParts(block))`). -The `BlockID` includes these two hashes, as well as the number of -parts (ie. `len(MakeParts(block))`) - -```go -type BlockID struct { - Hash []byte - PartSetHeader PartSetHeader -} - -type PartSetHeader struct { - Total uint32 - Hash []byte -} -``` - -See [MerkleRoot](./encoding.md#MerkleRoot) for details. - -## Time - -Tendermint uses the -[Google.Protobuf.WellKnownTypes.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp) -format, which uses two integers, one for Seconds and for Nanoseconds. - -## Data - -Data is just a wrapper for a list of transactions, where transactions are -arbitrary byte arrays: - -```go -type Data struct { - Txs [][]byte -} -``` - -## 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: - -```go -type Commit struct { - Height int64 - Round int32 - BlockID BlockID - Signatures []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. - -```go -type BlockIDFlag byte - -const ( - // BlockIDFlagAbsent - no vote was received from a validator. - BlockIDFlagAbsent BlockIDFlag = 0x01 - // BlockIDFlagCommit - voted for the Commit.BlockID. - BlockIDFlagCommit = 0x02 - // BlockIDFlagNil - voted for nil. - BlockIDFlagNil = 0x03 -) - -type CommitSig struct { - BlockIDFlag BlockIDFlag - ValidatorAddress Address - Timestamp time.Time - Signature []byte -} -``` - -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)). - -## Vote - -A vote is a signed message from a validator for a particular block. -The vote includes information about the validator signing it. - -```go -type Vote struct { - Type SignedMsgType - Height int64 - Round int32 - BlockID BlockID - Timestamp Time - ValidatorAddress []byte - ValidatorIndex int32 - Signature []byte -} -``` - -```protobuf -enum SignedMsgType { - SIGNED_MSG_TYPE_UNKNOWN = 0; - // Votes - PREVOTE_TYPE = 1; - PRECOMMIT_TYPE = 2; - // Proposals - PROPOSAL_TYPE = 32; -} -``` - -There are two types of votes: -a _prevote_ has `vote.Type == 1` and -a _precommit_ has `vote.Type == 2`. - -## Signature - -Signatures in Tendermint are raw bytes representing the underlying signature. - -See the [signature spec](./encoding.md#key-types) for more. - -## EvidenceData - -EvidenceData is a simple wrapper for a list of evidence: - -```go -type EvidenceData struct { - Evidence []Evidence -} -``` - -## Evidence - -Evidence in Tendermint is used to indicate breaches in the consensus by a validator. -It is implemented as the following interface. - -```go -type Evidence interface { - Height() int64 // height of the equivocation - Bytes() []byte // bytes which comprise the evidence - Hash() []byte // hash of the evidence (this is also used for equality) - ValidateBasic() error // consistency check of the data - String() string // string representation of the evidence -} -``` - -All evidence can be encoded and decoded to and from Protobuf with the `EvidenceToProto()` -and `EvidenceFromProto()` functions. The [Fork Accountability](../consensus/light-client/accountability.md) -document provides a good overview for the types of evidence and how they occur. For evidence to be committed onchain, it must adhere to the validation rules of each evidence and must not be expired. The expiration age, measured in both block height and time is set in `EvidenceParams`. Each evidence uses -the timestamp of the block that the evidence occured at to indicate the age of the evidence. - -### 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`. - -```go -type DuplicateVoteEvidence struct { - VoteA *Vote - VoteB *Vote -} -``` - -Valid Duplicate Vote Evidence 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 valid (using the chainID) - -- Evidence must not have expired: either age in terms of height or time must be - less than the age stated in the consensus params. Time is the block time that the - votes were a part of. - -### LightClientAttackEvidence - -```go -type LightClientAttackEvidence struct { - ConflictingBlock *LightBlock - CommonHeight int64 -} -``` - -Valid Light Client Attack Evidence encompasses three types of attack and 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 trusted header of the node at the same height as the conflicting header must have a different hash to - the conflicting header. - -- Evidence must not have expired. The height (and thus the time) is taken from the common height. - -## Validation - -Here we describe the validation rules for every element in a block. -Blocks which do not satisfy these rules are considered invalid. - -We abuse notation by using something that looks like Go, supplemented with English. -A statement such as `x == y` is an assertion - if it fails, the item is invalid. - -We refer to certain globally available objects: -`block` is the block under consideration, -`prevBlock` is the `block` at the previous height, -and `state` keeps track of the validator set, the consensus parameters -and other results from the application. At the point when `block` is the block under consideration, -the current version of the `state` corresponds to the state -after executing transactions from the `prevBlock`. -Elements of an object are accessed as expected, -ie. `block.Header`. -See the [definition of `State`](./state.md). - -### Header - -A Header is valid if its corresponding fields are valid. - -### Version - -```go -block.Version.Block == state.Version.Consensus.Block -block.Version.App == state.Version.Consensus.App -``` - -The block version must match consensus version from the state. - -### ChainID - -```go -len(block.ChainID) < 50 -``` - -ChainID must be less than 50 bytes. - -### Height - -```go -block.Header.Height > 0 -block.Header.Height >= state.InitialHeight -block.Header.Height == prevBlock.Header.Height + 1 -``` - -The height is an incrementing integer. The first block has `block.Header.Height == state.InitialHeight`, derived from the genesis file. - -### Time - -```go -block.Header.Timestamp >= prevBlock.Header.Timestamp + state.consensusParams.Block.TimeIotaMs -block.Header.Timestamp == MedianTime(block.LastCommit, state.LastValidators) -``` - -The block timestamp must be monotonic. -It must equal the weighted median of the timestamps of the valid signatures in the block.LastCommit. - -Note: the timestamp of a vote must be greater by at least one millisecond than that of the -block being voted on. - -The timestamp of the first block must be equal to the genesis time (since -there's no votes to compute the median). - -```go -if block.Header.Height == state.InitialHeight { - block.Header.Timestamp == genesisTime -} -``` - -See the section on [BFT time](../consensus/bft-time.md) for more details. - -### LastBlockID - -LastBlockID is the previous block's BlockID: - -```go -prevBlockParts := MakeParts(prevBlock) -block.Header.LastBlockID == BlockID { - Hash: MerkleRoot(prevBlock.Header), - PartsHeader{ - Hash: MerkleRoot(prevBlockParts), - Total: len(prevBlockParts), - }, -} -``` - -The first block has `block.Header.LastBlockID == BlockID{}`. - -### LastCommitHash - -```go -block.Header.LastCommitHash == MerkleRoot(block.LastCommit.Signatures) -``` - -MerkleRoot of the signatures included in the block. -These are the commit signatures of the validators that committed the previous -block. - -The first block has `block.Header.LastCommitHash == []byte{}` - -### DataHash - -```go -block.Header.DataHash == MerkleRoot(Hashes(block.Txs.Txs)) -``` - -MerkleRoot of the hashes of transactions included in the block. - -Note the transactions are hashed before being included in the Merkle tree, -so the leaves of the Merkle tree are the hashes, not the transactions -themselves. This is because transaction hashes are regularly used as identifiers for -transactions. - -### ValidatorsHash - -```go -block.ValidatorsHash == MerkleRoot(state.Validators) -``` - -MerkleRoot of the current validator set that is committing the block. -This can be used to validate the `LastCommit` included in the next block. -Note that before computing the MerkleRoot the validators are sorted -first by voting power (descending), then by address (ascending). - -### NextValidatorsHash - -```go -block.NextValidatorsHash == MerkleRoot(state.NextValidators) -``` - -MerkleRoot of the next validator set that will be the validator set that commits the next block. -This is included so that the current validator set gets a chance to sign the -next validator sets Merkle root. -Note that before computing the MerkleRoot the validators are sorted -first by voting power (descending), then by address (ascending). - -### ConsensusHash - -```go -block.ConsensusHash == state.ConsensusParams.Hash() -``` - -Hash of the protobuf-encoding of a subset of the consensus parameters. - -### AppHash - -```go -block.AppHash == state.AppHash -``` - -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`. - -### LastResultsHash - -```go -block.LastResultsHash == MerkleRoot([]ResponseDeliverTx) -``` - -`LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`,`Info`, `Codespace` and `Events` fields are ignored). - -The first block has `block.Header.ResultsHash == MerkleRoot(nil)`, i.e. the hash of an empty input, for RFC-6962 conformance. - -## EvidenceHash - -```go -block.EvidenceHash == MerkleRoot(block.Evidence) -``` - -MerkleRoot of the evidence of Byzantine behaviour included in this block. - -### ProposerAddress - -```go -block.Header.ProposerAddress in state.Validators -``` - -Address of the original proposer of the block. Must be a current validator. - -## Txs - -Arbitrary length array of arbitrary length byte-arrays. - -## LastCommit - -The first height is an exception - it requires the `LastCommit` to be empty: - -```go -if block.Header.Height == state.InitialHeight { - len(b.LastCommit) == 0 -} -``` - -Otherwise, we require: - -```go -len(block.LastCommit) == len(state.LastValidators) - -talliedVotingPower := 0 -for i, commitSig := range block.LastCommit.Signatures { - if commitSig.Absent() { - continue - } - - vote.BlockID == block.LastBlockID - - val := state.LastValidators[i] - vote.Verify(block.ChainID, val.PubKey) == true - - talliedVotingPower += val.VotingPower -} - -talliedVotingPower > (2/3)*TotalVotingPower(state.LastValidators) -``` - -Includes one vote for every current validator. -All votes must either be for the previous block, nil or absent. -All votes must have a valid signature from the corresponding validator. -The sum total 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`). - -### Vote - -A vote is a signed message broadcast in the consensus for a particular block at a particular height and round. -When stored in the blockchain or propagated over the network, votes are encoded in Protobuf. -For signing, votes are represented via `CanonicalVote` and also encoded using Protobuf via -`VoteSignBytes` 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(vote.SignBytes(chainID), vote.Signature) { - return ErrVoteInvalidSignature - } - return nil -} -``` - -where `pubKey.Verify` performs the appropriate digital signature verification of the `pubKey` -against the given signature and message bytes. +| 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 | [EvidenceData](#evidence_data) | Evidence contains a list of infractions committed by validators. | Can be empty, but when populated the validations rules from [evidenceData](#evidence_data) 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 @@ -572,3 +79,310 @@ func Execute(s State, app ABCIApp, block Block) State { } } ``` + +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 verion 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 | int64 | 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 + +| 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 | + +## Time + +Tendermint uses the [Google.Protobuf.WellKnownTypes.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp) +format, which uses two integers, one for Seconds and 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 | int64 | 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 | int64 | 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; + sfixed64 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 +} +``` + +## 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. + +## EvidenceData + +EvidenceData 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. + +The [Fork Accountability](../light-client/accountability.md) document provides a good overview of evidence and how they occur. For evidence to be committed onchain, it must adhere to the validation rules of each evidence and must not be expired. The expiration age, measured in both block height and time is set in `EvidenceParams`. Each evidence uses +the timestamp of the block that the evidence occurred at to indicate the age of the evidence. + +### 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 | + +Valid Duplicate Vote Evidence 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 valid (using the chainID) + +- For DuplicateVoteEvidence to be included in a block it must be within the time period outlined in [evidenceParams](../abci/abci.md#evidenceparams). Evidence expiration is measured as a combination of age in terms of height and time. + +### 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 | + +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 trusted header of the node at the same height as the conflicting header must have a different hash to + the conflicting header. + +- For LightClientAttackEvidence to be included in a block it must be within the time period outlined in [evidenceParams](../abci/abci.md#evidenceparams). Evidence expiration is measured as a combination of age in terms of height and time. + +## 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] +} +``` From cf03759ff575a694dc40308c88a20c62122220d4 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 5 Nov 2020 12:17:11 +0100 Subject: [PATCH 113/223] docs: make blockchain not viewable (#211) --- spec/blockchain/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/blockchain/readme.md b/spec/blockchain/readme.md index 724524d6a..b133b066e 100644 --- a/spec/blockchain/readme.md +++ b/spec/blockchain/readme.md @@ -1,5 +1,5 @@ --- -cards: false +order: false --- # Blockchain From 819e89ac7a756b3ef0bd43ba51a0ab4be3ffae01 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Fri, 6 Nov 2020 09:41:24 +0100 Subject: [PATCH 114/223] evidence: update data structures to reflect added support of abci evidence (#213) --- spec/core/data_structures.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 566be7fbe..32d57b01c 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -21,7 +21,7 @@ The Tendermint blockchains consists of a short list of data types: - [`Evidence`](#evidence) - [`DuplicateVoteEvidence`](#duplicatevoteevidence) - [`LightClientAttackEvidence`](#lightclientattackevidence) -- [`LightBlock](#lightblock) +- [`LightBlock`](#lightblock) - [`SignedHeader`](#signedheader) - [`Validator`](#validator) - [`ValidatorSet`](#validatorset) @@ -294,10 +294,13 @@ the timestamp of the block that the evidence occurred at to indicate the age of `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 | +| 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 | Valid Duplicate Vote Evidence must adhere to the following rules: @@ -311,6 +314,8 @@ Valid Duplicate Vote Evidence must adhere to the following rules: - For DuplicateVoteEvidence to be included in a block it must be within the time period outlined in [evidenceParams](../abci/abci.md#evidenceparams). Evidence expiration is measured as a combination of age in terms of height and time. +- Information required to form ABCI evidence (`TotalVotingPower`, `ValidatorPower` and `Timestamp`) must all match the nodes own state at that height. + ### LightClientAttackEvidence LightClientAttackEvidence is a generalized evidence that captures all forms of known attacks on @@ -318,10 +323,13 @@ a light client such that a full node can verify, propose and commit the evidence 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 | +| 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 | Valid Light Client Attack Evidence must adhere to the following rules: @@ -329,13 +337,17 @@ Valid Light Client Attack Evidence must adhere to the following rules: 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 + or amnesia. We therefore check that 2/3 of the validator set also signed the conflicting header. - The trusted header of the node at the same height as the conflicting header must have a different hash to the conflicting header. +- The `ByzantineValidators` provided must be the overlap between validators in the common validator set and those that voted in the commit of the conflicting block. + - For LightClientAttackEvidence to be included in a block it must be within the time period outlined in [evidenceParams](../abci/abci.md#evidenceparams). Evidence expiration is measured as a combination of age in terms of height and time. +- Information required to form ABCI evidence (`TotalVotingPower` and `Timestamp`) must all match the nodes own state at that height. + ## 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)). From 32b811a1fb6e8b40bae270339e31a8bc5e8dea31 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 9 Nov 2020 09:53:00 +0100 Subject: [PATCH 115/223] encoding: add secp, ref zip215, tables (#212) --- spec/core/encoding.md | 54 ++++++++++++++++++++++--------------------- spec/core/readme.md | 10 +++----- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/spec/core/encoding.md b/spec/core/encoding.md index 7015f3e34..f100c5147 100644 --- a/spec/core/encoding.md +++ b/spec/core/encoding.md @@ -41,8 +41,6 @@ Each type specifies it's own pubkey, address, and signature format. #### Ed25519 -TODO: pubkey - The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key: ```go @@ -51,6 +49,18 @@ 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 @@ -60,14 +70,10 @@ 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`). -```go -type BitArray struct { - Bits int64 - Elems []uint64 -} -``` - -This type is easily encoded directly by Amino. +| 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 @@ -82,13 +88,11 @@ 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`). -```go -type Part struct { - Index uint32 - Bytes []byte - Proof SimpleProof -} -``` +| Name | Type | +|-------|---------------------------| +| index | uint32 | +| bytes | slice of bytes (`[]byte`) | +| proof | [proof](#merkle-proof) | See details of SimpleProof, below. @@ -194,14 +198,12 @@ For `[]struct` arguments, we compute a `[][]byte` by protobuf encoding the indiv Proof that a leaf is in a Merkle tree is composed as follows: -```golang -type Proof struct { - Total int - Index int - LeafHash []byte - Aunts [][]byte -} -``` +| Name | Type | +|----------|----------------------------| +| total | int64 | +| index | int64 | +| leafHash | slice of bytes (`[]byte`) | +| aunts | Matrix of bytes ([][]byte) | Which is verified as follows: @@ -245,7 +247,7 @@ Because Tendermint only uses a Simple Merkle Tree, application developers are ex ## JSON -Tendermint has its own JSON encoding in order to keep backwards compatibility with the prvious RPC layer. +Tendermint has its own JSON encoding in order to keep backwards compatibility with the previous RPC layer. Registered types are encoded as: diff --git a/spec/core/readme.md b/spec/core/readme.md index 5302239c7..1accd23be 100644 --- a/spec/core/readme.md +++ b/spec/core/readme.md @@ -1,11 +1,7 @@ ---- -cards: true ---- - # Core This section describes the core types and functionality of the Tendermint protocol implementation. -[Core Data Structures](./data_structures.md) -[Encoding](./encoding.md) -[State](./state.md) +- [Core Data Structures](./data_structures.md) +- [Encoding](./encoding.md) +- [State](./state.md) From d5e0294003c684fb9492392b09b0c534b901f2e6 Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Mon, 9 Nov 2020 11:03:20 +0100 Subject: [PATCH 116/223] Detector English Spec ready (#215) Add detector English spec --- rust-spec/lightclient/README.md | 26 +- ...002_draft.md => detection_003_reviewed.md} | 269 ++++++++++-------- .../verification/verification_002_draft.md | 4 +- 3 files changed, 174 insertions(+), 125 deletions(-) rename rust-spec/lightclient/detection/{detection_002_draft.md => detection_003_reviewed.md} (79%) diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md index 4f30ed5db..9dbc81f3b 100644 --- a/rust-spec/lightclient/README.md +++ b/rust-spec/lightclient/README.md @@ -12,8 +12,9 @@ verifying a minimal set of data from a network of full nodes (at least one of wh The light client is decomposed into three components: -- Commit Verification - verify signed headers and associated validator set changes from a single full node -- Fork Detection - verify commits across multiple full nodes and detect conflicts (ie. the existence of forks) +- Commit Verification - verify signed headers and associated validator + set changes from a single full node, called primary +- Fork Detection - verify commits across multiple full nodes (called secondaries) and detect conflicts (ie. the existence of forks) - Fork Accountability - given a fork, which validators are responsible for it. ## Commit Verification @@ -75,12 +76,21 @@ no bug is reported up to depth k. ## Attack Detection -This is a work-in-progress draft. - -The [English specification](detection/detection_001_reviewed.md) -defines blockchain forks and light client attacks, and describes -the problem of a light client detecting them from communication with a network -of full nodes, where at least one is correct. +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. There is no TLA+ yet. diff --git a/rust-spec/lightclient/detection/detection_002_draft.md b/rust-spec/lightclient/detection/detection_003_reviewed.md similarity index 79% rename from rust-spec/lightclient/detection/detection_002_draft.md rename to rust-spec/lightclient/detection/detection_003_reviewed.md index 6eab312d8..89e920f19 100644 --- a/rust-spec/lightclient/detection/detection_002_draft.md +++ b/rust-spec/lightclient/detection/detection_003_reviewed.md @@ -1,25 +1,3 @@ -# ***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 @@ -28,15 +6,16 @@ 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). +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 +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 @@ -45,20 +24,22 @@ 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. +(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 +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 +[[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. +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 @@ -73,21 +54,54 @@ 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 +[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 + +# 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 +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)), +[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link]), and then define different -deviations that correspond to attack scenarios. +deviations that correspond to attack scenarios. We consider the notion +of [light blocks][LCV-LB-link] and [headers][LVC-HD-link]. #### **[TMBC-GENESIS.1]** @@ -98,11 +112,11 @@ Let *Genesis* be the agreed-upon initial block (file). 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). +[[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? +> these Validators signed c. #### **[TMBC-FUNC-SUPPORT.1]** @@ -113,11 +127,11 @@ Let *b* and *c* be two light blocks. We define the predicate - 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) +> 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). +> [[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 +> Tendermint; *b* can be traced back to genesis. #### **[TMBC-SEQ-ROOTED.1]** @@ -136,7 +150,7 @@ there exist light blocks *a(i)* s.t. #### **[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. +time t there exists an integer *h* and a sequence *a(1)*, ... *a(h)* s.t. - *a(1) = b* and - *a(h) = c* and @@ -147,7 +161,7 @@ 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 +> 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. @@ -199,7 +213,7 @@ 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 +## Node-based characterization of attacks #### **[TMBC-MC-FORK.1]** @@ -238,7 +252,7 @@ We consider the following case of a light client attack - *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 +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 @@ -250,19 +264,6 @@ of v(i) and v(i+1) such that > - 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]** @@ -274,7 +275,7 @@ submit This information is *evidence for height v(i).Height*. -### Block-based characterization of attacks +## 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 @@ -336,7 +337,9 @@ 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 +# 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. @@ -358,7 +361,7 @@ also be processed by the Tendermint blockchain. The detector is designed under the assumption that -- [[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link) may be violated +- [[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 @@ -369,7 +372,7 @@ 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 +The detector is called in the [supervisor][supervisor] as follows ```go Evidences := AttackDetector(root_of_trust, verifiedLS);` @@ -389,7 +392,10 @@ agreed on in the past), and #### **[LCD-IP-STATEMENT.1]** Whenever AttackDetector is called, the detector should for each -secondary try to replay the verification trace `verifiedLS` with the +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 @@ -399,7 +405,7 @@ secondary proof for an attack. Block *b* may be bogus. In this case the secondary is faulty and it should be replaced. -## Assumptions/Incentives/Environment +## 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 @@ -414,7 +420,6 @@ 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]** @@ -487,10 +492,11 @@ func submitEvidence(Evidences []InternalEvidence) ### LightStore Lightblocks and LightStores are defined in the verification -specification [LCV-DATA-LIGHTBLOCK.1] and [LCV-DATA-LIGHTSTORE.1]. See +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 +## 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, @@ -557,34 +563,33 @@ If 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. +> 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. -# Protocol +# Part III - Protocol ## Functions and Data defined in other Specifications -### From the supervisor +### From the [supervisor][supervisor] +[[LC-FUNC-REPLACE-SECONDARY.1]][repl] ```go Replace_Secondary(addr Address, root-of-trust LightBlock) ``` -### From the verifier +### From the [verifier][verification] +[[LCV-FUNC-MAIN.2]][vtt] ```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). +via the function [FetchLightBlock][fetch]. ### Shared data of the light client @@ -594,14 +599,14 @@ via the function [FetchLightBlock](fetch). > Note that the lightStore is not needed to be shared. -## Outline +## 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, +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 @@ -618,15 +623,15 @@ func AttackDetector(root LightBlock, primary_trace []LightBlock) 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 { + 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 - // secodary. We return the evidence + the trace 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 := @@ -641,9 +646,9 @@ func AttackDetector(root LightBlock, primary_trace []LightBlock) Evidences.Add(EvidenceForSecondary); // we replay the secondary trace with the primary, ... EvidenceForPrimary, _, result := - CreateEvidenceForPeer(primary, - newroot, - secondary_trace); + CreateEvidenceForPeer(primary, + newroot, + secondary_trace); if result == FoundEvidence { Evidences.Add(EvidenceForPrimary); } @@ -656,10 +661,10 @@ func AttackDetector(root LightBlock, primary_trace []LightBlock) } // In the case where the secondary reports NoEvidence // after initially it reported a conflicting header. - // secondary is faulty - Replace_Secondary(root); + // secondary is faulty + Replace_Secondary(root); } - } + } return Evidences; } ``` @@ -670,7 +675,7 @@ func AttackDetector(root LightBlock, primary_trace []LightBlock) - 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) + 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 @@ -686,7 +691,7 @@ func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) auxLS, result := VerifyToTarget(peer, common, trace[i].Header.Height) if result != ResultSuccess { - // something went wrong; peer did not provide a verifyable block + // something went wrong; peer did not provide a verifiable block return (nil, nil, nil, FaultyPeer) } else { @@ -702,7 +707,7 @@ func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) return (ev, common, auxLS, FoundEvidence) } else { - // the peer agrees with the trace, we move common forward + // 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] @@ -719,7 +724,7 @@ func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) - 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) + period) [[LCV-INV-TP.1]][LCV-INV-TP1-link] - If `VerifyToTarget` returns error but root is not expired then return `FaultyPeer` @@ -727,25 +732,44 @@ func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) ## 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 the detector creates +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 +`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 +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, +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) @@ -775,23 +799,38 @@ Once a bogus block is recognized as such the secondary is removed. [[supervisor]] The specification of the light client supervisor. -[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md +[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.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.md#tmbc-fm-2thirds1 +[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.md#tmbc-sound-distr-poss-commit1 +[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.md#lcv-seq-safe1 +[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.md#tmbc-val-contains-corr1 +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.md#lcv-func-fetch1 +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.md#lcv-inv-tp1 +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/rust-spec/lightclient/verification/verification_002_draft.md b/rust-spec/lightclient/verification/verification_002_draft.md index 80a1890c4..08bf0a807 100644 --- a/rust-spec/lightclient/verification/verification_002_draft.md +++ b/rust-spec/lightclient/verification/verification_002_draft.md @@ -646,9 +646,9 @@ func (ls LightStore) TraceTo(lightBlock LightBlock) (LightBlock, LightStore) ### Invariants -#### **[LCV-INV-TP.2]** +#### **[LCV-INV-TP.1]** -It is always the case that *LightStore.LatestVerified.Header.Time > now - trustingPeriod*. +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, From 66e9106b4dbacdbf7911f3da9b3b5836f7313ba7 Mon Sep 17 00:00:00 2001 From: Giuliano Date: Mon, 9 Nov 2020 02:05:26 -0800 Subject: [PATCH 117/223] add Ivy proofs (#210) * add Ivy proofs * fix docker-compose command --- ivy-proofs/Dockerfile | 37 +++ ivy-proofs/README.md | 27 ++ ivy-proofs/abstract_tendermint.ivy | 173 +++++++++++ ivy-proofs/accountable_safety_1.ivy | 144 ++++++++++ ivy-proofs/accountable_safety_2.ivy | 52 ++++ ivy-proofs/check_proofs.sh | 38 +++ ivy-proofs/classic_safety.ivy | 85 ++++++ ivy-proofs/docker-compose.yml | 8 + ivy-proofs/domain_model.ivy | 90 ++++++ ivy-proofs/network_shim.ivy | 140 +++++++++ ivy-proofs/output/.gitignore | 4 + ivy-proofs/tendermint.ivy | 430 ++++++++++++++++++++++++++++ 12 files changed, 1228 insertions(+) create mode 100644 ivy-proofs/Dockerfile create mode 100644 ivy-proofs/README.md create mode 100644 ivy-proofs/abstract_tendermint.ivy create mode 100644 ivy-proofs/accountable_safety_1.ivy create mode 100644 ivy-proofs/accountable_safety_2.ivy create mode 100755 ivy-proofs/check_proofs.sh create mode 100644 ivy-proofs/classic_safety.ivy create mode 100644 ivy-proofs/docker-compose.yml create mode 100644 ivy-proofs/domain_model.ivy create mode 100644 ivy-proofs/network_shim.ivy create mode 100644 ivy-proofs/output/.gitignore create mode 100644 ivy-proofs/tendermint.ivy diff --git a/ivy-proofs/Dockerfile b/ivy-proofs/Dockerfile new file mode 100644 index 000000000..be60151fd --- /dev/null +++ b/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/ivy-proofs/README.md b/ivy-proofs/README.md new file mode 100644 index 000000000..80dc0ce7b --- /dev/null +++ b/ivy-proofs/README.md @@ -0,0 +1,27 @@ +``` +Copyright (c) 2020 Galois, Inc. +SPDX-License-Identifier: Apache-2.0 +``` + +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/ivy-proofs/abstract_tendermint.ivy b/ivy-proofs/abstract_tendermint.ivy new file mode 100644 index 000000000..d0c0e8d5f --- /dev/null +++ b/ivy-proofs/abstract_tendermint.ivy @@ -0,0 +1,173 @@ +#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 + + 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 + } + } + +# 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. + 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/ivy-proofs/accountable_safety_1.ivy b/ivy-proofs/accountable_safety_1.ivy new file mode 100644 index 000000000..dc083bb4d --- /dev/null +++ b/ivy-proofs/accountable_safety_1.ivy @@ -0,0 +1,144 @@ +#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 | 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)) + proof { + apply lemma_2.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 = { + +# 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 + 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 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))) -> 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)) + } + proof { + apply complete_induction # the two subgoals (base case and inductive case) are then discharged automatically + } + } with this, round, nset, accountable_bft.max_2f_byzantine, defs.observed_equivocation_def, defs.observed_unlawful_prevote_def + +# Now we put lemma_1 in a form that matches exactly the accountability property +# we want to prove. This is a bit cumbersome and could probably be improved. + isolate lemma_2 = { + + 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 | 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) + } + proof { + assume lemma_1.thm + } + + } with this, round, defs.agreement_def, lemma_1, nset, accountable_bft.max_2f_byzantine + +} 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 | exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & forall N . nset.member(N,Q1) & nset.member(N,Q2) -> abstract_accountable_safety.observed_equivocation(N) | abstract_accountable_safety.observed_unlawful_prevote(N) + +} with value, round, proposers, shim, abstract_accountable_safety, abstract_accountable_safety.defs.agreement_def, accountable_safety_1.agreement_def diff --git a/ivy-proofs/accountable_safety_2.ivy b/ivy-proofs/accountable_safety_2.ivy new file mode 100644 index 000000000..5dd0a5bec --- /dev/null +++ b/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 +# every 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 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/ivy-proofs/check_proofs.sh b/ivy-proofs/check_proofs.sh new file mode 100755 index 000000000..2663eb894 --- /dev/null +++ b/ivy-proofs/check_proofs.sh @@ -0,0 +1,38 @@ +#!/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) +mkdir -p output/$log_dir + +echo "Checking classic safety:" +res=$(ivy_check 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=$(ivy_check complete=fo 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=$(ivy_check 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/ivy-proofs/classic_safety.ivy b/ivy-proofs/classic_safety.ivy new file mode 100644 index 000000000..b422a2c17 --- /dev/null +++ b/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/ivy-proofs/docker-compose.yml b/ivy-proofs/docker-compose.yml new file mode 100644 index 000000000..1d4a8ffe1 --- /dev/null +++ b/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/ivy-proofs/domain_model.ivy b/ivy-proofs/domain_model.ivy new file mode 100644 index 000000000..3de98da03 --- /dev/null +++ b/ivy-proofs/domain_model.ivy @@ -0,0 +1,90 @@ +#lang ivy1.7 +# --- +# layout: page +# title: Proof of Classic Safety +# --- + +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: Use only in the proof! Nodes do know know that. + +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) + 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 { + # here we prove that the abstraction is sound + function f(R:round):node + 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 { + definition valid(V) = V ~= nil + } +} + +object nset = { # the type of node sets + type this # a set of N=3f+i nodes for 0 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/ivy-proofs/output/.gitignore b/ivy-proofs/output/.gitignore new file mode 100644 index 000000000..5e7d2734c --- /dev/null +++ b/ivy-proofs/output/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/ivy-proofs/tendermint.ivy b/ivy-proofs/tendermint.ivy new file mode 100644 index 000000000..950ce28b1 --- /dev/null +++ b/ivy-proofs/tendermint.ivy @@ -0,0 +1,430 @@ +#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: + parameter 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 = { + assume ~_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) = { + assume _has_started; + assume _recved_proposal(proposers.get_proposer(round_p), round_p, v, round.minus_one); + assume 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) = { + assume _has_started; + assume r = round_p; + assume _recved_proposal(proposers.get_proposer(r), r, v, vr); + assume nset.is_quorum(q); + assume nset.member(N,q) -> _recved_prevote(N,vr,v); + assume step = step_t.propose; + assume 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) { + assume msg.m_vround = round.minus_one; + _recved_prevote(msg.m_src, msg.m_round, msg.m_value) := true; + } + + + # line 34-35 + export action l_34(r:round, q:nset) = { + assume _has_started; + assume round_p = r; + assume nset.is_quorum(q); + assume exists V . nset.member(N,q) -> _recved_prevote(N,r,V); + assume step = step_t.prevote; + assume ~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) = { + assume _has_started; + assume r = round_p; + assume exists VR . _recved_proposal(proposers.get_proposer(r), r, v, VR); + assume nset.is_quorum(q); + assume nset.member(N,q) -> _recved_prevote(N,r,v); + assume value.valid(v); + assume step = step_t.prevote | step = step_t.precommit; + + assume ~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) = { + assume _has_started; + assume r = round_p; + assume nset.is_quorum(q); + assume nset.member(N,q) -> _recved_prevote(N,r,value.nil); + assume 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; + m.m_vround := round.minus_one; + call shim.broadcast(n,m); + } + + implement shim.precommit_handler.handle(msg:msg) { + assume msg.m_vround = round.minus_one; + _recved_precommit(msg.m_src, msg.m_round, msg.m_value) := true; + } + + + # line 47-48 + export action l_47(r:round, q:nset) = { + assume _has_started; + assume round_p = r; + assume nset.is_quorum(q); + assume nset.member(N,q) -> exists V . _recved_precommit(N,r,V); + assume ~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) = { + assume _has_started; + assume exists VR . _recved_proposal(proposers.get_proposer(r), r, v, VR); + assume nset.is_quorum(q); + assume nset.member(N,q) -> _recved_precommit(N,r,v); + assume 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) = { + assume _has_started; + assume nset.is_blocking(b); + assume nset.member(N,b) -> exists V,VR . _recved_proposal(N,r,V,VR) | _recved_prevote(N,r,V) | _recved_precommit(N,r,V); + assume r > round_p; + call startRound(r); # line 56 + } + + # line 57-60 + export action onTimeoutPropose(r:round) = { + assume _has_started; + assume propose_timer_scheduled(r); + assume r = round_p; + assume 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) = { + assume _has_started; + assume prevote_timer_scheduled(r); + assume r = round_p; + assume 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) = { + assume _has_started; + assume precommit_timer_scheduled(r); + assume 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 cryprography (e.g. public-key cryptography). + + export action byzantine_send = { + assume ~well_behaved(n); + var m:msg; + var dst:node; + assume ~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. + + + 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) + } +} From c1ff62fe446a3c7a0ed9f609d540ca1d3dc0c865 Mon Sep 17 00:00:00 2001 From: Igor Konnov Date: Mon, 9 Nov 2020 11:08:33 +0100 Subject: [PATCH 118/223] Light client detector spec in TLA+ and refactoring of light client verification TLA+ spec (#216) Add light client detector spec in TLA+ --- rust-spec/lightclient/README.md | 95 ++++- .../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 +++++++++ .../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_003_draft.tla | 14 +- .../LCVerificationApi_003_draft.tla | 192 +++++++++ .../verification/Lightclient_003_draft.tla | 183 +++++---- .../verification/MC4_3_correct.tla | 7 +- .../lightclient/verification/MC4_3_faulty.tla | 7 +- .../verification/MC4_4_correct.tla | 26 ++ .../verification/MC4_4_correct_drifted.tla | 26 ++ .../lightclient/verification/MC4_4_faulty.tla | 7 +- .../verification/MC4_4_faulty_drifted.tla | 26 ++ .../verification/MC4_5_correct.tla | 7 +- .../lightclient/verification/MC4_5_faulty.tla | 13 +- .../lightclient/verification/MC4_6_faulty.tla | 9 +- .../lightclient/verification/MC4_7_faulty.tla | 7 +- .../verification/MC5_5_correct.tla | 11 +- .../MC5_5_correct_peer_two_thirds_faulty.tla | 11 +- .../lightclient/verification/MC5_5_faulty.tla | 13 +- .../MC5_5_faulty_peer_two_thirds_faulty.tla | 11 +- .../lightclient/verification/MC5_7_faulty.tla | 11 +- .../lightclient/verification/MC7_5_faulty.tla | 11 +- .../lightclient/verification/MC7_7_faulty.tla | 7 +- 34 files changed, 1517 insertions(+), 142 deletions(-) create mode 100644 rust-spec/lightclient/detection/004bmc-apalache-ok.csv create mode 100644 rust-spec/lightclient/detection/005bmc-apalache-error.csv create mode 100644 rust-spec/lightclient/detection/Blockchain_003_draft.tla create mode 100644 rust-spec/lightclient/detection/LCD_MC3_3_faulty.tla create mode 100644 rust-spec/lightclient/detection/LCD_MC3_4_faulty.tla create mode 100644 rust-spec/lightclient/detection/LCD_MC4_4_faulty.tla create mode 100644 rust-spec/lightclient/detection/LCD_MC5_5_faulty.tla create mode 100644 rust-spec/lightclient/detection/LCDetector_003_draft.tla create mode 100644 rust-spec/lightclient/detection/LCVerificationApi_003_draft.tla create mode 100644 rust-spec/lightclient/verification/002bmc-apalache-ok.csv create mode 100644 rust-spec/lightclient/verification/003bmc-apalache-error.csv create mode 100644 rust-spec/lightclient/verification/004bmc-apalache-ok.csv create mode 100644 rust-spec/lightclient/verification/005bmc-apalache-error.csv create mode 100644 rust-spec/lightclient/verification/LCVerificationApi_003_draft.tla create mode 100644 rust-spec/lightclient/verification/MC4_4_correct.tla create mode 100644 rust-spec/lightclient/verification/MC4_4_correct_drifted.tla create mode 100644 rust-spec/lightclient/verification/MC4_4_faulty_drifted.tla diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md index 9dbc81f3b..6441e8ed6 100644 --- a/rust-spec/lightclient/README.md +++ b/rust-spec/lightclient/README.md @@ -34,27 +34,36 @@ many intermediate headers by exploiting overlap in trusted and untrusted validat 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](verification/Lightclient_A_1.tla) is a formal description of the +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 -liveness properties, which can be model checked with Apalache. +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, and correctness of the primary node: +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 \* two weeks, one day is 100 time units :-) +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 001bmc-apalache.csv $DIR/apalache . out +$DIR/apalache-tests/scripts/mk-run.py --memlimit 28 002bmc-apalache-ok.csv $DIR/apalache . out ./out/run-all.sh ``` @@ -65,7 +74,23 @@ cd ./out $DIR/apalache-tests/scripts/parse-logs.py --human . ``` -The following table summarizes the experimental results. The TLA+ properties can be found in the +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. @@ -74,6 +99,8 @@ 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) @@ -92,7 +119,61 @@ and generates of misbehavior that can be submitted to a full node so that the faulty validators can be punished. -There is no TLA+ yet. +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. ## Fork Accountability diff --git a/rust-spec/lightclient/detection/004bmc-apalache-ok.csv b/rust-spec/lightclient/detection/004bmc-apalache-ok.csv new file mode 100644 index 000000000..bf4f53ea2 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/005bmc-apalache-error.csv b/rust-spec/lightclient/detection/005bmc-apalache-error.csv new file mode 100644 index 000000000..1b9dd05ca --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/Blockchain_003_draft.tla b/rust-spec/lightclient/detection/Blockchain_003_draft.tla new file mode 100644 index 000000000..2b37c1b18 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/LCD_MC3_3_faulty.tla b/rust-spec/lightclient/detection/LCD_MC3_3_faulty.tla new file mode 100644 index 000000000..cef1df4d3 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/LCD_MC3_4_faulty.tla b/rust-spec/lightclient/detection/LCD_MC3_4_faulty.tla new file mode 100644 index 000000000..06bcdee13 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/LCD_MC4_4_faulty.tla b/rust-spec/lightclient/detection/LCD_MC4_4_faulty.tla new file mode 100644 index 000000000..fdb97d961 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/LCD_MC5_5_faulty.tla b/rust-spec/lightclient/detection/LCD_MC5_5_faulty.tla new file mode 100644 index 000000000..fdbd87b8b --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/LCDetector_003_draft.tla b/rust-spec/lightclient/detection/LCDetector_003_draft.tla new file mode 100644 index 000000000..e2d32e996 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/detection/LCVerificationApi_003_draft.tla b/rust-spec/lightclient/detection/LCVerificationApi_003_draft.tla new file mode 100644 index 000000000..909eab92b --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/002bmc-apalache-ok.csv b/rust-spec/lightclient/verification/002bmc-apalache-ok.csv new file mode 100644 index 000000000..eb26aa89e --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/003bmc-apalache-error.csv b/rust-spec/lightclient/verification/003bmc-apalache-error.csv new file mode 100644 index 000000000..ad5ef9654 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/004bmc-apalache-ok.csv b/rust-spec/lightclient/verification/004bmc-apalache-ok.csv new file mode 100644 index 000000000..bf4f53ea2 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/005bmc-apalache-error.csv b/rust-spec/lightclient/verification/005bmc-apalache-error.csv new file mode 100644 index 000000000..1b9dd05ca --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/Blockchain_003_draft.tla b/rust-spec/lightclient/verification/Blockchain_003_draft.tla index e80fbae56..2b37c1b18 100644 --- a/rust-spec/lightclient/verification/Blockchain_003_draft.tla +++ b/rust-spec/lightclient/verification/Blockchain_003_draft.tla @@ -44,8 +44,8 @@ BlockHeaders == [ LightBlocks == [header: BlockHeaders, Commits: Commits] VARIABLES - now, - (* the current global time in integer units *) + 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 @@ -54,7 +54,7 @@ VARIABLES connect using a different id. *) (* all variables, to be used with UNCHANGED *) -vars == <> +vars == <> (* The set of all correct nodes in a state *) Corr == AllNodes \ Faulty @@ -75,7 +75,7 @@ LBT == [header |-> BT, Commits |-> {NT}] (* the header is still within the trusting period *) InTrustingPeriod(header) == - now < header.time + TRUSTING_PERIOD + refClock < header.time + TRUSTING_PERIOD (* Given a function pVotingPower \in D -> Powers for some D \subseteq AllNodes @@ -128,8 +128,8 @@ InitToHeight(pMaxFaultyRatioExclusive) == \* 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] + \* 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 @@ -155,7 +155,7 @@ InitToHeight(pMaxFaultyRatioExclusive) == Advance the clock by zero or more time units. *) AdvanceTime == - \E tm \in Int: tm >= now /\ now' = tm + /\ \E tm \in Int: tm >= refClock /\ refClock' = tm /\ UNCHANGED <> ============================================================================= diff --git a/rust-spec/lightclient/verification/LCVerificationApi_003_draft.tla b/rust-spec/lightclient/verification/LCVerificationApi_003_draft.tla new file mode 100644 index 000000000..909eab92b --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/Lightclient_003_draft.tla b/rust-spec/lightclient/verification/Lightclient_003_draft.tla index 873f76d30..e17a88491 100644 --- a/rust-spec/lightclient/verification/Lightclient_003_draft.tla +++ b/rust-spec/lightclient/verification/Lightclient_003_draft.tla @@ -1,6 +1,7 @@ -------------------------- MODULE Lightclient_003_draft ---------------------------- (** - * A state-machine specification of the lite client, following the English spec: + * 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 *) @@ -15,6 +16,11 @@ CONSTANTS (* 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 @@ -22,6 +28,7 @@ CONSTANTS 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 *) @@ -33,25 +40,26 @@ VARIABLES latestVerified (* the latest verified block *) (* the variables of the lite client *) -lcvars == <> +lcvars == <> (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict -InitMonitor(verified, current, now, verdict) == +InitMonitor(verified, current, pLocalClock, verdict) == /\ prevVerified = verified /\ prevCurrent = current - /\ prevNow = now + /\ prevLocalClock = pLocalClock /\ prevVerdict = verdict -NextMonitor(verified, current, now, verdict) == +NextMonitor(verified, current, pLocalClock, verdict) == /\ prevVerified' = verified /\ prevCurrent' = current - /\ prevNow' = now + /\ prevLocalClock' = pLocalClock /\ prevVerdict' = verdict @@ -63,10 +71,10 @@ CONSTANTS (* 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 +VARIABLES refClock, blockchain, Faulty \* All the variables of Blockchain. For some reason, BC!vars does not work -bcvars == <> +bcvars == <> (* Create an instance of Blockchain. We could write EXTENDS Blockchain, but then all the constants and state variables @@ -75,7 +83,7 @@ bcvars == <> ULTIMATE_HEIGHT == TARGET_HEIGHT + 1 BC == INSTANCE Blockchain_003_draft WITH - now <- now, blockchain <- blockchain, Faulty <- Faulty + refClock <- refClock, blockchain <- blockchain, Faulty <- Faulty (************************** Lite client ************************************) @@ -85,69 +93,17 @@ 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) - *) +\* The verification functions are implemented in the API +API == INSTANCE LCVerificationApi_003_draft -(** - * 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 == + /\ \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 @@ -160,7 +116,7 @@ LCInit == /\ lightBlockStatus = [h \in {TRUSTED_HEIGHT} |-> "StateVerified"] \* the latest verified block the the trusted block /\ latestVerified = trustedLightBlock - /\ InitMonitor(trustedLightBlock, trustedLightBlock, now, "SUCCESS") + /\ InitMonitor(trustedLightBlock, trustedLightBlock, localClock, "SUCCESS") \* block should contain a copy of the block from the reference chain, with a matching commit CopyLightBlockFromChain(block, height) == @@ -232,8 +188,8 @@ VerifyToTargetLoop == \* 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) /\ + /\ 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") @@ -272,7 +228,22 @@ VerifyToTargetDone == /\ latestVerified.header.height >= TARGET_HEIGHT /\ state' = "finishedSuccess" /\ UNCHANGED <> - /\ 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 == @@ -290,11 +261,13 @@ Init == Next == /\ state = "working" /\ VerifyToTargetLoop \/ VerifyToTargetDone - /\ BC!AdvanceTime \* the global clock is advanced by zero or more time units + /\ AdvanceClocks (************************* Types ******************************************) TypeOK == /\ state \in States + /\ localClock \in Nat + /\ refClock \in Nat /\ nextHeight \in HEIGHTS /\ latestVerified \in BC!LightBlocks /\ \E HS \in SUBSET HEIGHTS: @@ -316,7 +289,6 @@ NeverFinishNegative == \* 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" @@ -324,6 +296,15 @@ NeverFinishNegativeWhenTrusted == 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. @@ -338,7 +319,8 @@ CorrectnessInv == 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" @@ -358,11 +340,16 @@ StoredHeadersAreVerifiedInv == \/ \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]) + \/ "SUCCESS" = API!ValidAndVerified(fetchedLightBlocks[lh], + fetchedLightBlocks[rh], FALSE) -\* An improved version of StoredHeadersAreSound, assuming that a header may be not trusted. +\* 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" => @@ -372,14 +359,15 @@ StoredHeadersAreVerifiedOrNotTrustedInv == \/ \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]) + \/ "SUCCESS" = API!ValidAndVerified(fetchedLightBlocks[lh], + fetchedLightBlocks[rh], FALSE) \* or the left header is outside the trusting period, so no guarantees - \/ ~BC!InTrustingPeriod(fetchedLightBlocks[lh].header) + \/ ~API!InTrustingPeriodLocal(fetchedLightBlocks[lh].header) (** * An improved version of StoredHeadersAreSoundOrNotTrusted, * checking the property only for the verified headers. - * This invariant holds true. + * This invariant holds true if CLOCK_DRIFT <= REAL_CLOCK_DRIFT. *) ProofOfChainOfTrustInv == state = "finishedSuccess" @@ -393,9 +381,10 @@ ProofOfChainOfTrustInv == \/ \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)) + \/ ~(API!InTrustingPeriodLocal(fetchedLightBlocks[lh].header)) \* or we can verify the right one using the left one - \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) + \/ "SUCCESS" = API!ValidAndVerified(fetchedLightBlocks[lh], + fetchedLightBlocks[rh], FALSE) (** * When the light client terminates, there are no failed blocks. (Otherwise, someone lied to us.) @@ -409,10 +398,12 @@ NoFailedBlocksOnSuccessInv == \* 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]) + (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" +\* then whenever the algorithm terminates, it reports "success". +\* This property fails. CorrectPrimaryAndTimeliness == (BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) /\ state /= "working" /\ IS_PRIMARY_CORRECT) => @@ -421,10 +412,25 @@ CorrectPrimaryAndTimeliness == (** 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] *) -SuccessOnCorrectPrimaryAndChainOfTrust == +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) => @@ -439,6 +445,8 @@ SuccessOnCorrectPrimaryAndChainOfTrust == \* 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 @@ -464,6 +472,15 @@ Complexity == 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. diff --git a/rust-spec/lightclient/verification/MC4_3_correct.tla b/rust-spec/lightclient/verification/MC4_3_correct.tla index 534f9963e..a27d8de05 100644 --- a/rust-spec/lightclient/verification/MC4_3_correct.tla +++ b/rust-spec/lightclient/verification/MC4_3_correct.tla @@ -4,19 +4,22 @@ 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, - now, blockchain, Faulty + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC4_3_faulty.tla b/rust-spec/lightclient/verification/MC4_3_faulty.tla index ae82cb7e7..74b278900 100644 --- a/rust-spec/lightclient/verification/MC4_3_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_3_faulty.tla @@ -4,19 +4,22 @@ 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, - now, blockchain, Faulty + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC4_4_correct.tla b/rust-spec/lightclient/verification/MC4_4_correct.tla new file mode 100644 index 000000000..0a8d96b59 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/MC4_4_correct_drifted.tla b/rust-spec/lightclient/verification/MC4_4_correct_drifted.tla new file mode 100644 index 000000000..7fefe349e --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/MC4_4_faulty.tla b/rust-spec/lightclient/verification/MC4_4_faulty.tla index 8836a1359..167fa61fb 100644 --- a/rust-spec/lightclient/verification/MC4_4_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_4_faulty.tla @@ -4,19 +4,22 @@ 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, - now, blockchain, Faulty + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC4_4_faulty_drifted.tla b/rust-spec/lightclient/verification/MC4_4_faulty_drifted.tla new file mode 100644 index 000000000..e37c3cb40 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/verification/MC4_5_correct.tla b/rust-spec/lightclient/verification/MC4_5_correct.tla index dd1b1298a..cffb22cc8 100644 --- a/rust-spec/lightclient/verification/MC4_5_correct.tla +++ b/rust-spec/lightclient/verification/MC4_5_correct.tla @@ -4,19 +4,22 @@ 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, - now, blockchain, Faulty + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC4_5_faulty.tla b/rust-spec/lightclient/verification/MC4_5_faulty.tla index a7a5ca8f8..3d3a00290 100644 --- a/rust-spec/lightclient/verification/MC4_5_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_5_faulty.tla @@ -4,19 +4,22 @@ AllNodes == {"n1", "n2", "n3", "n4"} TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 5 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) -IS_PRIMARY_CORRECT == FALSE +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, nprobes, fetchedLightBlocks, lightBlockStatus, - latestVerified, nprobes, - now, blockchain, Faulty + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC4_6_faulty.tla b/rust-spec/lightclient/verification/MC4_6_faulty.tla index 14eced4d9..64f164854 100644 --- a/rust-spec/lightclient/verification/MC4_6_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_6_faulty.tla @@ -4,19 +4,22 @@ AllNodes == {"n1", "n2", "n3", "n4"} TRUSTED_HEIGHT == 1 TARGET_HEIGHT == 6 TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) -IS_PRIMARY_CORRECT == FALSE +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, - now, blockchain, Faulty + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC4_7_faulty.tla b/rust-spec/lightclient/verification/MC4_7_faulty.tla index d6214b868..dc6a94eb1 100644 --- a/rust-spec/lightclient/verification/MC4_7_faulty.tla +++ b/rust-spec/lightclient/verification/MC4_7_faulty.tla @@ -4,19 +4,22 @@ 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, - now, blockchain, Faulty + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC5_5_correct.tla b/rust-spec/lightclient/verification/MC5_5_correct.tla index f78c60eb5..00b4151f7 100644 --- a/rust-spec/lightclient/verification/MC5_5_correct.tla +++ b/rust-spec/lightclient/verification/MC5_5_correct.tla @@ -4,19 +4,22 @@ 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, nprobes, fetchedLightBlocks, lightBlockStatus, - latestVerified, nprobes, - now, blockchain, Faulty + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla b/rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla index 41b906660..d4212032f 100644 --- a/rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla +++ b/rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla @@ -4,19 +4,22 @@ 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, nprobes, fetchedLightBlocks, lightBlockStatus, - latestVerified, nprobes, - now, blockchain, Faulty + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC5_5_faulty.tla b/rust-spec/lightclient/verification/MC5_5_faulty.tla index 5a124959f..f63d175a1 100644 --- a/rust-spec/lightclient/verification/MC5_5_faulty.tla +++ b/rust-spec/lightclient/verification/MC5_5_faulty.tla @@ -1,22 +1,25 @@ ------------------ MODULE MC5_5_faulty_peers_two_thirds_faulty --------------------- +----------------- 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, nprobes, fetchedLightBlocks, lightBlockStatus, - latestVerified, nprobes, - now, blockchain, Faulty + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla b/rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla index 9dadbcb31..ef9974d06 100644 --- a/rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla +++ b/rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla @@ -4,19 +4,22 @@ 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, nprobes, fetchedLightBlocks, lightBlockStatus, - latestVerified, nprobes, - now, blockchain, Faulty + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC5_7_faulty.tla b/rust-spec/lightclient/verification/MC5_7_faulty.tla index e8cde75e6..63461b0c8 100644 --- a/rust-spec/lightclient/verification/MC5_7_faulty.tla +++ b/rust-spec/lightclient/verification/MC5_7_faulty.tla @@ -4,19 +4,22 @@ 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, nprobes, fetchedLightBlocks, lightBlockStatus, - latestVerified, nprobes, - now, blockchain, Faulty + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC7_5_faulty.tla b/rust-spec/lightclient/verification/MC7_5_faulty.tla index 855415b26..860f9c0aa 100644 --- a/rust-spec/lightclient/verification/MC7_5_faulty.tla +++ b/rust-spec/lightclient/verification/MC7_5_faulty.tla @@ -4,19 +4,22 @@ 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, nprobes, fetchedLightBlocks, lightBlockStatus, - latestVerified, nprobes, - now, blockchain, Faulty + state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, + nprobes, + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft diff --git a/rust-spec/lightclient/verification/MC7_7_faulty.tla b/rust-spec/lightclient/verification/MC7_7_faulty.tla index ea6fcec1f..79e328f14 100644 --- a/rust-spec/lightclient/verification/MC7_7_faulty.tla +++ b/rust-spec/lightclient/verification/MC7_7_faulty.tla @@ -4,19 +4,22 @@ 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, - now, blockchain, Faulty + localClock, + refClock, blockchain, Faulty (* the light client previous state components, used for monitoring *) VARIABLES prevVerified, prevCurrent, - prevNow, + prevLocalClock, prevVerdict INSTANCE Lightclient_003_draft From 871d0514cd284cee76d286935d9f8e99ebd9dcd8 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 17 Nov 2020 10:33:01 +0100 Subject: [PATCH 119/223] abci: lastcommitinfo.round extra sentence (#221) --- spec/abci/abci.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 729cada0f..67088ea20 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -597,7 +597,7 @@ via light client. ### LastCommitInfo - **Fields**: - - `Round (int32)`: Commit round. + - `Round (int32)`: Commit round. Reflects the total amount of rounds it took to come to consensus for the current block. - `Votes ([]VoteInfo)`: List of validators addresses in the last validator set with their voting power and whether or not they signed a vote. From 033608bbf138f2c1567fb2f43c33edc4d57503b2 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 24 Nov 2020 15:15:08 +0100 Subject: [PATCH 120/223] abci: add abci_version to requestInfo (#223) --- spec/abci/abci.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 67088ea20..16ea939b2 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -237,6 +237,7 @@ via light client. - `Version (string)`: The Tendermint software semantic version - `BlockVersion (uint64)`: The Tendermint Block Protocol version - `P2PVersion (uint64)`: The Tendermint P2P Protocol version + - `ABCIVersion (string)`: The Tendermint ABCI semantic version - **Response**: - `Data (string)`: Some arbitrary information - `Version (string)`: The application software semantic version @@ -253,6 +254,8 @@ via light client. be updated during `Commit`, ensuring that `Commit` is never called twice for the same block height. +> Note: Semantic version is reference to [semantic versioning](https://semver.org/). Semantic versions in info will be displayed as X.X.x. + ### InitChain - **Request**: From 6abcb13dab6ef82f9ebf65b02ae7b861289d5742 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Wed, 2 Dec 2020 06:01:18 -0500 Subject: [PATCH 121/223] BFT requires _less than_ 1/3 faulty validators (#228) Thanks fo spotting the imprecision in the text, @shahankhatch ! --- spec/light-client/accountability.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/light-client/accountability.md b/spec/light-client/accountability.md index 0374b9c3c..c46495553 100644 --- a/spec/light-client/accountability.md +++ b/spec/light-client/accountability.md @@ -9,12 +9,12 @@ Tendermint consensus guarantees the following specifications for all heights: * termination -- all correct full nodes eventually decide, if the -faulty validators have at most 1/3 of voting power in the current validator set. In the case where this assumption +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 more than 1/3 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. +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. @@ -22,7 +22,7 @@ We say that a fork is a case in which there are two commits for different blocks **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 more than 1/3 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. +*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 @@ -40,7 +40,7 @@ Forks are the result of faulty validators deviating from the protocol. In princi 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 at most 1/3 of the voting power is faulty, this misbehavior is detectable but will not lead to a safety violation. Thus, unless they have more than 1/3 (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. +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 @@ -119,7 +119,7 @@ contaminated by an attack if the blockchain itself violates its invariant (one b ### 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 more than 1/3 of voting power to be executed. +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 @@ -128,7 +128,7 @@ 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 more than 1/3 voting power +* F - a set of faulty validators with 1/3 or more voting power Observe that this setting violates the Tendermint failure model. @@ -177,7 +177,7 @@ In order to detect such (equivocation-based attack), the light client would need *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 more than 1/3 of voting power, while in case of LCS it requires more than 2/3 of the voting power. +*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 @@ -187,7 +187,7 @@ In case of amnesia, faulty validators lock some value *v* in some round *r*, and Validators: -* a set F of faulty validators with more than 1/3 but at most 2/3 of the voting power +* 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: @@ -203,7 +203,7 @@ 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 more than 1/3 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. +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 @@ -211,8 +211,8 @@ In case there is an attack with more than 2/3 of the voting power, an attacker c Validators: -* a set F1 of faulty validators with more than 1/3 of the voting power -* a set F2 of faulty validators with at most 1/3 of the voting power +* 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 @@ -238,10 +238,10 @@ In this kind of attack, faulty validators take advantage of the fact that they d Validators: -* C1 - a set of correct validators with 1/3 of the voting power +* 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 1/3 voting power +* 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. From 26ef2ccddb28e1bef5deed0546b9d1732efa79bb Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Fri, 11 Dec 2020 14:34:16 +0100 Subject: [PATCH 122/223] 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 --- .../attacks/isolate-attackers_001_draft.md | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 rust-spec/lightclient/attacks/isolate-attackers_001_draft.md diff --git a/rust-spec/lightclient/attacks/isolate-attackers_001_draft.md b/rust-spec/lightclient/attacks/isolate-attackers_001_draft.md new file mode 100644 index 000000000..54f02cccd --- /dev/null +++ b/rust-spec/lightclient/attacks/isolate-attackers_001_draft.md @@ -0,0 +1,217 @@ +*** This is the beginning of an unfinished draft. Don't continue reading! *** + +# 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 + +**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 From 31cfa530821ed2ea71cd0d2c889108c1480f8a64 Mon Sep 17 00:00:00 2001 From: Igor Konnov Date: Fri, 11 Dec 2020 15:03:48 +0100 Subject: [PATCH 123/223] The TLA+ specification of the attackers detection (#231) * the working attackers isolation spec, needs more comments * the TLA+ spec of the attackers isolation --- .../attacks/Blockchain_003_draft.tla | 166 +++++++++++++++ .../attacks/Isolation_001_draft.tla | 159 +++++++++++++++ .../attacks/LCVerificationApi_003_draft.tla | 192 ++++++++++++++++++ rust-spec/lightclient/attacks/MC_5_3.tla | 18 ++ 4 files changed, 535 insertions(+) create mode 100644 rust-spec/lightclient/attacks/Blockchain_003_draft.tla create mode 100644 rust-spec/lightclient/attacks/Isolation_001_draft.tla create mode 100644 rust-spec/lightclient/attacks/LCVerificationApi_003_draft.tla create mode 100644 rust-spec/lightclient/attacks/MC_5_3.tla diff --git a/rust-spec/lightclient/attacks/Blockchain_003_draft.tla b/rust-spec/lightclient/attacks/Blockchain_003_draft.tla new file mode 100644 index 000000000..fb6e6e8e8 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/attacks/Isolation_001_draft.tla b/rust-spec/lightclient/attacks/Isolation_001_draft.tla new file mode 100644 index 000000000..7406b8942 --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/attacks/LCVerificationApi_003_draft.tla b/rust-spec/lightclient/attacks/LCVerificationApi_003_draft.tla new file mode 100644 index 000000000..909eab92b --- /dev/null +++ b/rust-spec/lightclient/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/rust-spec/lightclient/attacks/MC_5_3.tla b/rust-spec/lightclient/attacks/MC_5_3.tla new file mode 100644 index 000000000..552de49ae --- /dev/null +++ b/rust-spec/lightclient/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 +============================================================================ From acb9a7d734eb04dc0a0ee4eb7520e648f911945e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Dec 2020 21:53:43 +0100 Subject: [PATCH 124/223] 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> --- .github/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index dec3618d8..5684c4ee6 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -7,4 +7,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - - uses: gaurav-nelson/github-action-markdown-link-check@1.0.8 + - uses: gaurav-nelson/github-action-markdown-link-check@1.0.11 From 42751ea4f3e17db3908bb1d2c6b01c5a3bbb46d6 Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Tue, 15 Dec 2020 18:45:26 +0100 Subject: [PATCH 125/223] Computing attack types (#232) Add light attack evidence handling --- rust-spec/lightclient/README.md | 34 +- .../attacks/isolate-attackers_002_reviewed.md | 221 ++++ ...dling.md => notes-on-evidence-handling.md} | 0 .../001indinv-apalache.csv | 13 + .../tendermint-accountability/MC_n4_f1.tla | 22 + .../tendermint-accountability/MC_n4_f2.tla | 22 + .../MC_n4_f2_amnesia.tla | 40 + .../tendermint-accountability/MC_n4_f3.tla | 22 + .../tendermint-accountability/MC_n5_f1.tla | 22 + .../tendermint-accountability/MC_n5_f2.tla | 22 + .../tendermint-accountability/MC_n6_f1.tla | 22 + rust-spec/tendermint-accountability/README.md | 106 ++ .../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 | 62 + .../results/001indinv-apalache-time-log.svg | 1134 ++++++++++++++++ .../results/001indinv-apalache-time.svg | 957 ++++++++++++++ .../results/001indinv-apalache-unstable.csv | 13 + rust-spec/tendermint-accountability/run.sh | 9 + 25 files changed, 8041 insertions(+), 9 deletions(-) create mode 100644 rust-spec/lightclient/attacks/isolate-attackers_002_reviewed.md rename rust-spec/lightclient/attacks/{evidence-handling.md => notes-on-evidence-handling.md} (100%) create mode 100644 rust-spec/tendermint-accountability/001indinv-apalache.csv create mode 100644 rust-spec/tendermint-accountability/MC_n4_f1.tla create mode 100644 rust-spec/tendermint-accountability/MC_n4_f2.tla create mode 100644 rust-spec/tendermint-accountability/MC_n4_f2_amnesia.tla create mode 100644 rust-spec/tendermint-accountability/MC_n4_f3.tla create mode 100644 rust-spec/tendermint-accountability/MC_n5_f1.tla create mode 100644 rust-spec/tendermint-accountability/MC_n5_f2.tla create mode 100644 rust-spec/tendermint-accountability/MC_n6_f1.tla create mode 100644 rust-spec/tendermint-accountability/README.md create mode 100644 rust-spec/tendermint-accountability/TendermintAccDebug_004_draft.tla create mode 100644 rust-spec/tendermint-accountability/TendermintAccInv_004_draft.tla create mode 100644 rust-spec/tendermint-accountability/TendermintAccTrace_004_draft.tla create mode 100644 rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla create mode 100644 rust-spec/tendermint-accountability/results/001indinv-apalache-mem-log.svg create mode 100644 rust-spec/tendermint-accountability/results/001indinv-apalache-mem.svg create mode 100644 rust-spec/tendermint-accountability/results/001indinv-apalache-ncells.svg create mode 100644 rust-spec/tendermint-accountability/results/001indinv-apalache-nclauses.svg create mode 100644 rust-spec/tendermint-accountability/results/001indinv-apalache-report.md create mode 100644 rust-spec/tendermint-accountability/results/001indinv-apalache-time-log.svg create mode 100644 rust-spec/tendermint-accountability/results/001indinv-apalache-time.svg create mode 100644 rust-spec/tendermint-accountability/results/001indinv-apalache-unstable.csv create mode 100755 rust-spec/tendermint-accountability/run.sh diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md index 6441e8ed6..e673cab5b 100644 --- a/rust-spec/lightclient/README.md +++ b/rust-spec/lightclient/README.md @@ -10,12 +10,15 @@ 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 three components: +The light client is decomposed into two main components: -- Commit Verification - verify signed headers and associated validator +- [Commit Verification](#Commit-Verification) - verify signed headers and associated validator set changes from a single full node, called primary -- Fork Detection - verify commits across multiple full nodes (called secondaries) and detect conflicts (ie. the existence of forks) -- Fork Accountability - given a fork, which validators are responsible for it. +- [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 @@ -175,10 +178,23 @@ All lines in `results.csv` should report `Error`. The detailed experimental results are to be added soon. -## Fork Accountability +## Accountability -There is no English specification yet. TODO: Jovan's work? -TODO: there is a WIP [TLA+ -specification](https://github.com/informalsystems/verification/pull/13) in the -verification repo that should be moved over here. +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 + +As is discussed in the [last part](attacks/isolate-attackers_002_reviewed.md#Part-III---Completeness) of the English specification, computer-aided analysis of [Tendermint Consensus in TLA+][tendermint-accountability] shows that these three types capture all possible 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]: +https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md diff --git a/rust-spec/lightclient/attacks/isolate-attackers_002_reviewed.md b/rust-spec/lightclient/attacks/isolate-attackers_002_reviewed.md new file mode 100644 index 000000000..c4e270efa --- /dev/null +++ b/rust-spec/lightclient/attacks/isolate-attackers_002_reviewed.md @@ -0,0 +1,221 @@ +# 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/rust-spec/lightclient/attacks/evidence-handling.md b/rust-spec/lightclient/attacks/notes-on-evidence-handling.md similarity index 100% rename from rust-spec/lightclient/attacks/evidence-handling.md rename to rust-spec/lightclient/attacks/notes-on-evidence-handling.md diff --git a/rust-spec/tendermint-accountability/001indinv-apalache.csv b/rust-spec/tendermint-accountability/001indinv-apalache.csv new file mode 100644 index 000000000..37c6aeda2 --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/MC_n4_f1.tla b/rust-spec/tendermint-accountability/MC_n4_f1.tla new file mode 100644 index 000000000..7a828b498 --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/MC_n4_f2.tla b/rust-spec/tendermint-accountability/MC_n4_f2.tla new file mode 100644 index 000000000..893f18db6 --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/MC_n4_f2_amnesia.tla b/rust-spec/tendermint-accountability/MC_n4_f2_amnesia.tla new file mode 100644 index 000000000..434fffaeb --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/MC_n4_f3.tla b/rust-spec/tendermint-accountability/MC_n4_f3.tla new file mode 100644 index 000000000..b794fff5e --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/MC_n5_f1.tla b/rust-spec/tendermint-accountability/MC_n5_f1.tla new file mode 100644 index 000000000..d65673a58 --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/MC_n5_f2.tla b/rust-spec/tendermint-accountability/MC_n5_f2.tla new file mode 100644 index 000000000..c19aa98cc --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/MC_n6_f1.tla b/rust-spec/tendermint-accountability/MC_n6_f1.tla new file mode 100644 index 000000000..2e992974f --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/README.md b/rust-spec/tendermint-accountability/README.md new file mode 100644 index 000000000..ac321d457 --- /dev/null +++ b/rust-spec/tendermint-accountability/README.md @@ -0,0 +1,106 @@ +# 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: https://arxiv.org/abs/1807.04938 + + 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/rust-spec/tendermint-accountability/TendermintAccDebug_004_draft.tla b/rust-spec/tendermint-accountability/TendermintAccDebug_004_draft.tla new file mode 100644 index 000000000..deaa990ea --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/TendermintAccInv_004_draft.tla b/rust-spec/tendermint-accountability/TendermintAccInv_004_draft.tla new file mode 100644 index 000000000..5dd15396d --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/TendermintAccTrace_004_draft.tla b/rust-spec/tendermint-accountability/TendermintAccTrace_004_draft.tla new file mode 100644 index 000000000..436c2275a --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla b/rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla new file mode 100644 index 000000000..bb214bf9b --- /dev/null +++ b/rust-spec/tendermint-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] = "PREVOTE"] + /\ 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/rust-spec/tendermint-accountability/results/001indinv-apalache-mem-log.svg b/rust-spec/tendermint-accountability/results/001indinv-apalache-mem-log.svg new file mode 100644 index 000000000..5821418da --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/results/001indinv-apalache-mem.svg b/rust-spec/tendermint-accountability/results/001indinv-apalache-mem.svg new file mode 100644 index 000000000..dc7213eae --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/results/001indinv-apalache-ncells.svg b/rust-spec/tendermint-accountability/results/001indinv-apalache-ncells.svg new file mode 100644 index 000000000..20c49f4f1 --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/results/001indinv-apalache-nclauses.svg b/rust-spec/tendermint-accountability/results/001indinv-apalache-nclauses.svg new file mode 100644 index 000000000..86d19143b --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/results/001indinv-apalache-report.md b/rust-spec/tendermint-accountability/results/001indinv-apalache-report.md new file mode 100644 index 000000000..d63a93598 --- /dev/null +++ b/rust-spec/tendermint-accountability/results/001indinv-apalache-report.md @@ -0,0 +1,62 @@ +# 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/rust-spec/tendermint-accountability/results/001indinv-apalache-time-log.svg b/rust-spec/tendermint-accountability/results/001indinv-apalache-time-log.svg new file mode 100644 index 000000000..458d67c6c --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/results/001indinv-apalache-time.svg b/rust-spec/tendermint-accountability/results/001indinv-apalache-time.svg new file mode 100644 index 000000000..a5db5a8b5 --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/results/001indinv-apalache-unstable.csv b/rust-spec/tendermint-accountability/results/001indinv-apalache-unstable.csv new file mode 100644 index 000000000..db1a06093 --- /dev/null +++ b/rust-spec/tendermint-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/rust-spec/tendermint-accountability/run.sh b/rust-spec/tendermint-accountability/run.sh new file mode 100755 index 000000000..75e57a5f8 --- /dev/null +++ b/rust-spec/tendermint-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 From accd7ffe189593376940ee3d09c82b957acb70c4 Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Wed, 16 Dec 2020 13:27:23 +0100 Subject: [PATCH 126/223] Update README.md (#234) --- rust-spec/lightclient/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md index e673cab5b..cdecb8fa4 100644 --- a/rust-spec/lightclient/README.md +++ b/rust-spec/lightclient/README.md @@ -187,7 +187,9 @@ defines the protocol that is executed on a full node upon receiving attack [evid - equivocation - amnesia -As is discussed in the [last part](attacks/isolate-attackers_002_reviewed.md#Part-III---Completeness) of the English specification, computer-aided analysis of [Tendermint Consensus in TLA+][tendermint-accountability] shows that these three types capture all possible attacks. +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+][tendermint-accountability] shows that equivocation and amnesia capture all non-lunatic attacks. The [TLA+ specification](attacks/Isolation_001_draft.tla) From 439a5bcacb5ef6ef1118566d7b0cd68fff3553d4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 18 Dec 2020 15:19:38 +0400 Subject: [PATCH 127/223] 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 --- spec/p2p/peer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/p2p/peer.md b/spec/p2p/peer.md index a81b2a7bd..52d00b584 100644 --- a/spec/p2p/peer.md +++ b/spec/p2p/peer.md @@ -51,7 +51,7 @@ It goes as follows: - 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 1024 byte frames, +- 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" From ce146d00d7ac9b9462db46517375e74b8807cba5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 12:12:49 +0100 Subject: [PATCH 128/223] 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> --- .github/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 5684c4ee6..ea8fd2480 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -7,4 +7,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - - uses: gaurav-nelson/github-action-markdown-link-check@1.0.11 + - uses: gaurav-nelson/github-action-markdown-link-check@1.0.12 From fc569173a1f0980aa8a49e4d6cca232ba0fc6879 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 11 Jan 2021 18:23:18 +0100 Subject: [PATCH 129/223] layout: add section titles (#240) --- spec/README.md | 2 +- spec/abci/README.md | 5 ++++- spec/blockchain/readme.md | 5 ++++- spec/consensus/readme.md | 5 ++++- spec/core/readme.md | 7 ++++++- spec/light-client/README.md | 7 ++++++- spec/p2p/readme.md | 7 ++++--- 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/spec/README.md b/spec/README.md index fcec18f63..cf7d1c45d 100644 --- a/spec/README.md +++ b/spec/README.md @@ -2,7 +2,7 @@ order: 1 title: Overview parent: - title: Tendermint Spec + title: Spec order: 7 --- diff --git a/spec/abci/README.md b/spec/abci/README.md index 128f24551..55bfc1e2f 100644 --- a/spec/abci/README.md +++ b/spec/abci/README.md @@ -1,5 +1,8 @@ --- -cards: true +order: 1 +parent: + title: ABCI + order: 2 --- # ABCI diff --git a/spec/blockchain/readme.md b/spec/blockchain/readme.md index b133b066e..10ad46690 100644 --- a/spec/blockchain/readme.md +++ b/spec/blockchain/readme.md @@ -1,5 +1,8 @@ --- -order: false +order: 1 +parent: + title: Blockchain + order: false --- # Blockchain diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md index 1eae3738e..aa79ba192 100644 --- a/spec/consensus/readme.md +++ b/spec/consensus/readme.md @@ -1,5 +1,8 @@ --- -cards: true +order: 1 +parent: + title: Consensus + order: 4 --- # Consensus diff --git a/spec/core/readme.md b/spec/core/readme.md index 1accd23be..87eece472 100644 --- a/spec/core/readme.md +++ b/spec/core/readme.md @@ -1,4 +1,9 @@ -# Core +--- +order: 1 +parent: + title: Core + order: 3 +--- This section describes the core types and functionality of the Tendermint protocol implementation. diff --git a/spec/light-client/README.md b/spec/light-client/README.md index 3dfba8c2b..573eddbaf 100644 --- a/spec/light-client/README.md +++ b/spec/light-client/README.md @@ -1,4 +1,9 @@ -# Light Client Protocol +--- +order: 1 +parent: + title: Light Client + order: 5 +--- NOTE: This specification is under heavy development and is not yet complete nor accurate. diff --git a/spec/p2p/readme.md b/spec/p2p/readme.md index 4d85d382c..96867aad0 100644 --- a/spec/p2p/readme.md +++ b/spec/p2p/readme.md @@ -1,5 +1,6 @@ --- -cards: true +order: 1 +parent: + title: P2P + order: 6 --- - -# P2P From a4672048e7b6d1f1885c56d928d445cb4f23864c Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 11 Jan 2021 18:23:40 +0100 Subject: [PATCH 130/223] reactors: remove bcv1 (#241) --- .../bcv1/img/bc-reactor-new-datastructs.png | Bin 44461 -> 0 bytes .../bcv1/img/bc-reactor-new-fsm.png | Bin 42091 -> 0 bytes .../bcv1/img/bc-reactor-new-goroutines.png | Bin 140946 -> 0 bytes spec/reactors/block_sync/bcv1/impl-v1.md | 258 ------------------ 4 files changed, 258 deletions(-) delete mode 100644 spec/reactors/block_sync/bcv1/img/bc-reactor-new-datastructs.png delete mode 100644 spec/reactors/block_sync/bcv1/img/bc-reactor-new-fsm.png delete mode 100644 spec/reactors/block_sync/bcv1/img/bc-reactor-new-goroutines.png delete mode 100644 spec/reactors/block_sync/bcv1/impl-v1.md diff --git a/spec/reactors/block_sync/bcv1/img/bc-reactor-new-datastructs.png b/spec/reactors/block_sync/bcv1/img/bc-reactor-new-datastructs.png deleted file mode 100644 index 1a92871a5bcebf2f88658623b82c6e49b9cd25bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44461 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 diff --git a/spec/reactors/block_sync/bcv1/img/bc-reactor-new-fsm.png b/spec/reactors/block_sync/bcv1/img/bc-reactor-new-fsm.png deleted file mode 100644 index 87d6fad931c46840ac2f47243e10f9b7978046af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42091 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 S9i>1)ezKCEC91^?gZ>}K2PIPg diff --git a/spec/reactors/block_sync/bcv1/img/bc-reactor-new-goroutines.png b/spec/reactors/block_sync/bcv1/img/bc-reactor-new-goroutines.png deleted file mode 100644 index ee853ea935cb2d7adf35d63fcf576b9df305e0f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140946 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

vg`M)Wq8{7?F**9ln|l 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 E57oWiU;qFB diff --git a/spec/reactors/block_sync/bcv1/impl-v1.md b/spec/reactors/block_sync/bcv1/impl-v1.md deleted file mode 100644 index e039f3d59..000000000 --- a/spec/reactors/block_sync/bcv1/impl-v1.md +++ /dev/null @@ -1,258 +0,0 @@ -# Blockchain Reactor v1 - -## Data Structures - -The data structures used are illustrated below. - -![Data Structures](img/bc-reactor-new-datastructs.png) - -### BlockchainReactor - -- is a `p2p.BaseReactor`. -- has a `store.BlockStore` for persistence. -- executes blocks using an `sm.BlockExecutor`. -- starts the FSM and the `poolRoutine()`. -- relays the fast-sync responses and switch messages to the FSM. -- handles errors from the FSM and when necessarily reports them to the switch. -- implements the blockchain reactor interface used by the FSM to send requests, errors to the switch and state timer resets. -- registers all the concrete types and interfaces for serialisation. - -```go -type BlockchainReactor struct { - p2p.BaseReactor - - initialState sm.State // immutable - state sm.State - - blockExec *sm.BlockExecutor - store *store.BlockStore - - fastSync bool - - fsm *BcReactorFSM - blocksSynced int - - // Receive goroutine forwards messages to this channel to be processed in the context of the poolRoutine. - messagesForFSMCh chan bcReactorMessage - - // Switch goroutine may send RemovePeer to the blockchain reactor. This is an error message that is relayed - // to this channel to be processed in the context of the poolRoutine. - errorsForFSMCh chan bcReactorMessage - - // This channel is used by the FSM and indirectly the block pool to report errors to the blockchain reactor and - // the switch. - eventsFromFSMCh chan bcFsmMessage -} -``` - -#### BcReactorFSM - -- implements a simple finite state machine. -- has a state and a state timer. -- has a `BlockPool` to keep track of block requests sent to peers and blocks received from peers. -- uses an interface to send status requests, block requests and reporting errors. The interface is implemented by the `BlockchainReactor` and tests. - -```go -type BcReactorFSM struct { - logger log.Logger - mtx sync.Mutex - - startTime time.Time - - state *bcReactorFSMState - stateTimer *time.Timer - pool *BlockPool - - // interface used to call the Blockchain reactor to send StatusRequest, BlockRequest, reporting errors, etc. - toBcR bcReactor -} -``` - -#### BlockPool - -- maintains a peer set, implemented as a map of peer ID to `BpPeer`. -- maintains a set of requests made to peers, implemented as a map of block request heights to peer IDs. -- maintains a list of future block requests needed to advance the fast-sync. This is a list of block heights. -- keeps track of the maximum height of the peers in the set. -- uses an interface to send requests and report errors to the reactor (via FSM). - -```go -type BlockPool struct { - logger log.Logger - // Set of peers that have sent status responses, with height bigger than pool.Height - peers map[p2p.ID]*BpPeer - // Set of block heights and the corresponding peers from where a block response is expected or has been received. - blocks map[int64]p2p.ID - - plannedRequests map[int64]struct{} // list of blocks to be assigned peers for blockRequest - nextRequestHeight int64 // next height to be added to plannedRequests - - Height int64 // height of next block to execute - MaxPeerHeight int64 // maximum height of all peers - toBcR bcReactor -} -``` - -Some reasons for the `BlockPool` data structure content: - -1. If a peer is removed by the switch fast access is required to the peer and the block requests made to that peer in order to redo them. -2. When block verification fails fast access is required from the block height to the peer and the block requests made to that peer in order to redo them. -3. The `BlockchainReactor` main routine decides when the block pool is running low and asks the `BlockPool` (via FSM) to make more requests. The `BlockPool` creates a list of requests and triggers the sending of the block requests (via the interface). The reason it maintains a list of requests is the redo operations that may occur during error handling. These are redone when the `BlockchainReactor` requires more blocks. - -#### BpPeer - -- keeps track of a single peer, with height bigger than the initial height. -- maintains the block requests made to the peer and the blocks received from the peer until they are executed. -- monitors the peer speed when there are pending requests. -- it has an active timer when pending requests are present and reports error on timeout. - -```go -type BpPeer struct { - logger log.Logger - ID p2p.ID - - Height int64 // the peer reported height - NumPendingBlockRequests int // number of requests still waiting for block responses - blocks map[int64]*types.Block // blocks received or expected to be received from this peer - blockResponseTimer *time.Timer - recvMonitor *flow.Monitor - params *BpPeerParams // parameters for timer and monitor - - onErr func(err error, peerID p2p.ID) // function to call on error -} -``` - -### Concurrency Model - -The diagram below shows the goroutines (depicted by the gray blocks), timers (shown on the left with their values) and channels (colored rectangles). The FSM box shows some of the functionality and it is not a separate goroutine. - -The interface used by the FSM is shown in light red with the `IF` block. This is used to: - -- send block requests -- report peer errors to the switch - this results in the reactor calling `switch.StopPeerForError()` and, if triggered by the peer timeout routine, a `removePeerEv` is sent to the FSM and action is taken from the context of the `poolRoutine()` -- ask the reactor to reset the state timers. The timers are owned by the FSM while the timeout routine is defined by the reactor. This was done in order to avoid running timers in tests and will change in the next revision. - -There are two main goroutines implemented by the blockchain reactor. All I/O operations are performed from the `poolRoutine()` context while the CPU intensive operations related to the block execution are performed from the context of the `executeBlocksRoutine()`. All goroutines are detailed in the next sections. - -![Go Routines Diagram](img/bc-reactor-new-goroutines.png) - -#### Receive() - -Fast-sync messages from peers are received by this goroutine. It performs basic validation and: - -- in helper mode (i.e. for request message) it replies immediately. This is different than the proposal in adr-040 that specifies having the FSM handling these. -- forwards response messages to the `poolRoutine()`. - -#### poolRoutine() - -(named kept as in the previous reactor). -It starts the `executeBlocksRoutine()` and the FSM. It then waits in a loop for events. These are received from the following channels: - -- `sendBlockRequestTicker.C` - every 10msec the reactor asks FSM to make more block requests up to a maximum. Note: currently this value is constant but could be changed based on low/ high watermark thresholds for the number of blocks received and waiting to be processed, the number of blockResponse messages waiting in messagesForFSMCh, etc. -- `statusUpdateTicker.C` - every 10 seconds the reactor broadcasts status requests to peers. While adr-040 specifies this to run within the FSM, at this point this functionality is kept in the reactor. -- `messagesForFSMCh` - the `Receive()` goroutine sends status and block response messages to this channel and the reactor calls FSM to handle them. -- `errorsForFSMCh` - this channel receives the following events: - - peer remove - when the switch removes a peer - - sate timeout event - when FSM state timers trigger - The reactor forwards this messages to the FSM. -- `eventsFromFSMCh` - there are two type of events sent over this channel: - - `syncFinishedEv` - triggered when FSM enters `finished` state and calls the switchToConsensus() interface function. - - `peerErrorEv`- peer timer expiry goroutine sends this event over the channel for processing from poolRoutine() context. - -#### executeBlocksRoutine() - -Started by the `poolRoutine()`, it retrieves blocks from the pool and executes them: - -- `processReceivedBlockTicker.C` - a ticker event is received over the channel every 10msec and its handling results in a signal being sent to the doProcessBlockCh channel. -- doProcessBlockCh - events are received on this channel as described as above and upon processing blocks are retrieved from the pool and executed. - -### FSM - -![fsm](img/bc-reactor-new-fsm.png) - -#### States - -##### init (aka unknown) - -The FSM is created in `unknown` state. When started, by the reactor (`startFSMEv`), it broadcasts Status requests and transitions to `waitForPeer` state. - -##### waitForPeer - -In this state, the FSM waits for a Status responses from a "tall" peer. A timer is running in this state to allow the FSM to finish if there are no useful peers. - -If the timer expires, it moves to `finished` state and calls the reactor to switch to consensus. -If a Status response is received from a peer within the timeout, the FSM transitions to `waitForBlock` state. - -##### waitForBlock - -In this state the FSM makes Block requests (triggered by a ticker in reactor) and waits for Block responses. There is a timer running in this state to detect if a peer is not sending the block at current processing height. If the timer expires, the FSM removes the peer where the request was sent and all requests made to that peer are redone. - -As blocks are received they are stored by the pool. Block execution is independently performed by the reactor and the result reported to the FSM: - -- if there are no errors, the FSM increases the pool height and resets the state timer. -- if there are errors, the peers that delivered the two blocks (at height and height+1) are removed and the requests redone. - -In this state the FSM may receive peer remove events in any of the following scenarios: - -- the switch is removing a peer -- a peer is penalized because it has not responded to some block requests for a long time -- a peer is penalized for being slow - -When processing of the last block (the one with height equal to the highest peer height minus one) is successful, the FSM transitions to `finished` state. -If after a peer update or removal the pool height is same as maxPeerHeight, the FSM transitions to `finished` state. - -##### finished - -When entering this state, the FSM calls the reactor to switch to consensus and performs cleanup. - -#### Events - -The following events are handled by the FSM: - -```go -const ( - startFSMEv = iota + 1 - statusResponseEv - blockResponseEv - processedBlockEv - makeRequestsEv - stopFSMEv - peerRemoveEv = iota + 256 - stateTimeoutEv -) -``` - -### Examples of Scenarios and Termination Handling - -A few scenarios are covered in this section together with the current/ proposed handling. -In general, the scenarios involving faulty peers are made worse by the fact that they may quickly be re-added. - -#### 1. No Tall Peers - -S: In this scenario a node is started and while there are status responses received, none of the peers are at a height higher than this node. - -H: The FSM times out in `waitForPeer` state, moves to `finished` state where it calls the reactor to switch to consensus. - -#### 2. Typical Fast Sync - -S: A node fast syncs blocks from honest peers and eventually downloads and executes the penultimate block. - -H: The FSM in `waitForBlock` state will receive the processedBlockEv from the reactor and detect that the termination height is achieved. - -#### 3. Peer Claims Big Height but no Blocks - -S: In this scenario a faulty peer claims a big height (for which there are no blocks). - -H: The requests for the non-existing block will timeout, the peer removed and the pool's `MaxPeerHeight` updated. FSM checks if the termination height is achieved when peers are removed. - -#### 4. Highest Peer Removed or Updated to Short - -S: The fast sync node is caught up with all peers except one tall peer. The tall peer is removed or it sends status response with low height. - -H: FSM checks termination condition on peer removal and updates. - -#### 5. Block At Current Height Delayed - -S: A peer can block the progress of fast sync by delaying indefinitely the block response for the current processing height (h1). - -H: Currently, given h1 < h2, there is no enforcement at peer level that the response for h1 should be received before h2. So a peer will timeout only after delivering all blocks except h1. However the `waitForBlock` state timer fires if the block for current processing height is not received within a timeout. The peer is removed and the requests to that peer (including the one for current height) redone. From d260ff3e3764f36c1e144d8aa3e54e91d643c38f Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 12 Jan 2021 15:15:37 +0100 Subject: [PATCH 131/223] abci: rewrite to proto interface (#237) --- spec/abci/abci.md | 515 +++++++++++++++++++---------------- spec/core/data_structures.md | 129 ++++++--- 2 files changed, 364 insertions(+), 280 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 16ea939b2..cef54344c 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -234,23 +234,30 @@ via light client. ### Info - **Request**: - - `Version (string)`: The Tendermint software semantic version - - `BlockVersion (uint64)`: The Tendermint Block Protocol version - - `P2PVersion (uint64)`: The Tendermint P2P Protocol version - - `ABCIVersion (string)`: The Tendermint ABCI semantic version + + | 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**: - - `Data (string)`: Some arbitrary information - - `Version (string)`: The application software semantic version - - `AppVersion (uint64)`: The application protocol version - - `LastBlockHeight (int64)`: Latest block for which the app has - called Commit - - `LastBlockAppHash ([]byte)`: Latest result of Commit + + | 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 `AppVersion` will be included in the Header of every block. - - Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to + - 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. @@ -259,17 +266,24 @@ via light client. ### InitChain - **Request**: - - `Time (google.protobuf.Timestamp)`: Genesis time. - - `ChainID (string)`: ID of the blockchain. - - `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters. - - `Validators ([]ValidatorUpdate)`: Initial genesis validators, sorted by voting power. - - `AppStateBytes ([]byte)`: Serialized initial application state. Amino-encoded JSON bytes. - - `InitialHeight (int64)`: Height of the initial block (typically `1`). + + | 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**: - - `ConsensusParams (ConsensusParams)`: Initial - consensus-critical parameters (optional). - - `Validators ([]ValidatorUpdate)`: Initial validator set (optional). - - `AppHash ([]byte)`: Initial application hash. + + | 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 @@ -283,36 +297,28 @@ via light client. ### Query - **Request**: - - `Data ([]byte)`: Raw query bytes. Can be used with or in lieu - of Path. - - `Path (string)`: Path of request, like an HTTP GET path. Can be - used with or in liue 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/...' - - `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 - - `Prove (bool)`: Return Merkle proof with response if possible + + | Name | Type | Description | Field Number | + |--------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | data | bytes | Raw query bytes. Can be used with or in lieu of Path. | 1 | + | path | string | Path of request, like an HTTP GET path. Can be used with or in liue 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**: - - `Code (uint32)`: Response code. - - `Log (string)`: The output of the application's logger. May - be non-deterministic. - - `Info (string)`: Additional information. May - be non-deterministic. - - `Index (int64)`: The index of the key in the tree. - - `Key ([]byte)`: The key of the matching data. - - `Value ([]byte)`: The value of the matching data. - - `Proof (Proof)`: Serialized proof for the value data, if requested, to be - verified against the `AppHash` for the given Height. - - `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 - - `Codespace (string)`: Namespace for the `Code`. + + | 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. @@ -322,15 +328,20 @@ via light client. ### BeginBlock - **Request**: - - `Hash ([]byte)`: The block's hash. This can be derived from the - block header. - - `Header (struct{})`: The block header. - - `LastCommitInfo (LastCommitInfo)`: Info about the last commit, including the - round, and the list of validators and which ones signed the last block. - - `ByzantineValidators ([]Evidence)`: List of evidence of - validators that acted maliciously. + + | 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**: - - `Events ([]abci.Event)`: Type & Key-Value events for indexing + + | Name | Type | Description | Field Number | + |--------|---------------------------|-------------------------------------|--------------| + | events | repeated [Event](#events) | ype & Key-Value events for indexing | 1 | + - **Usage**: - Signals the beginning of a new block. Called prior to any DeliverTxs. @@ -343,23 +354,25 @@ via light client. ### CheckTx - **Request**: - - `Tx ([]byte)`: The request transaction bytes - - `Type (CheckTxType)`: What type of `CheckTx` request is this? At present, - there are two possible values: `CheckTx_New` (the default, which says - that a full check is required), and `CheckTx_Recheck` (when the mempool is - initiating a normal recheck of a transaction). + + | Name | Type | Description | Field Number | + |------|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| + | tx | bytes | The request transaction bytes | 1 | + | type | CheckTxType | What type of `CheckTx` request is this? At present, there are two possible values: `CheckTx_New` (the default, which says that a full check is required), and `CheckTx_Recheck` (when the mempool is initiating a normal recheck of a transaction). | 2 | + - **Response**: - - `Code (uint32)`: Response code - - `Data ([]byte)`: Result bytes, if any. - - `Log (string)`: The output of the application's logger. May - be non-deterministic. - - `Info (string)`: Additional information. May - be non-deterministic. - - `GasWanted (int64)`: Amount of gas requested for transaction. - - `GasUsed (int64)`: Amount of gas consumed by transaction. - - `Events ([]abci.Event)`: Type & Key-Value events for indexing - transactions (eg. by account). - - `Codespace (string)`: Namespace for the `Code`. + + | 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**: - Technically optional - not involved in processing blocks. - Guardian of the mempool: every node runs CheckTx before letting a @@ -375,19 +388,24 @@ via light client. ### DeliverTx - **Request**: - - `Tx ([]byte)`: The request transaction bytes. + +| Name | Type | Description | Field Number | +|------|-------|--------------------------------|--------------| +| tx | bytes | The request transaction bytes. | 1 | + - **Response**: - - `Code (uint32)`: Response code. - - `Data ([]byte)`: Result bytes, if any. - - `Log (string)`: The output of the application's logger. May - be non-deterministic. - - `Info (string)`: Additional information. May - be non-deterministic. - - `GasWanted (int64)`: Amount of gas requested for transaction. - - `GasUsed (int64)`: Amount of gas consumed by transaction. - - `Events ([]abci.Event)`: Type & Key-Value events for indexing - transactions (eg. by account). - - `Codespace (string)`: Namespace for the `Code`. + + | 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**: - The workhorse of the application - non-optional. - Execute the transaction in full. @@ -396,13 +414,19 @@ via light client. ### EndBlock - **Request**: - - `Height (int64)`: Height of the block just executed. + + | Name | Type | Description | Field Number | + |--------|-------|------------------------------------|--------------| + | height | int64 | Height of the block just executed. | 1 | + - **Response**: - - `ValidatorUpdates ([]ValidatorUpdate)`: Changes to validator set (set - voting power to 0 to remove). - - `ConsensusParamUpdates (ConsensusParams)`: Changes to - consensus-critical time, size, and other parameters. - - `Events ([]abci.Event)`: Type & Key-Value events for indexing + + | 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 transactions, prior to each Commit. @@ -415,10 +439,20 @@ via light client. ### Commit +- **Request**: + + | Name | Type | Description | Field Number | + |--------|-------|------------------------------------|--------------| + + Empty request meant to signal to the app it can write state transitions to state. + - **Response**: - - `Data ([]byte)`: The Merkle root hash of the application state - - `RetainHeight (int64)`: Blocks below this height may be removed. Defaults - to `0` (retain all). + + | 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**: - Persist the application state. - Return an (optional) Merkle root hash of the application state @@ -438,8 +472,19 @@ via light client. ### ListSnapshots +- **Request**: + + | Name | Type | Description | Field Number | + |--------|-------|------------------------------------|--------------| + + Empty request asking the application for a list of snapshots. + - **Response**: - - `Snapshots ([]Snapshot)`: List of local state snapshots. + + | 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. @@ -447,27 +492,50 @@ via light client. ### LoadSnapshotChunk - **Request**: - - `Height (uint64)`: The height of the snapshot the chunks belongs to. - - `Format (uint32)`: The application-specific format of the snapshot the chunk belongs to. - - `Chunk (uint32)`: The chunk index, starting from `0` for the initial chunk. + + | 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**: - - `Chunk ([]byte)`: 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. + + | 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**: - - `Snapshot (Snapshot)`: The snapshot offered for restoration. - - `AppHash ([]byte)`: The light client-verified app hash for this height, from the blockchain. + + | 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**: - - `Result (Result)`: The result of the snapshot offer. - - `ACCEPT`: Snapshot is accepted, start applying chunks. - - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. - - `REJECT`: Reject this specific snapshot, try others. - - `REJECT_FORMAT`: Reject all snapshots with this `format`, try others. - - `REJECT_SENDERS`: Reject all snapshots from all senders of this snapshot, try others. + + | 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 @@ -483,21 +551,32 @@ via light client. ### ApplySnapshotChunk - **Request**: - - `Index (uint32)`: The chunk index, starting from `0`. Tendermint applies chunks sequentially. - - `Chunk ([]byte)`: The binary chunk contents, as returned by `LoadSnapshotChunk`. - - `Sender (string)`: The P2P ID of the node who sent this chunk. + + | 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**: - - `Result (Result)`: The result of applying this chunk. - - `ACCEPT`: The chunk was accepted. - - `ABORT`: Abort snapshot restoration, and don't try any other snapshots. - - `RETRY`: Reapply this chunk, combine with `RefetchChunks` and `RejectSenders` as appropriate. - - `RETRY_SNAPSHOT`: Restart this snapshot from `OfferSnapshot`, reusing chunks unless - instructed otherwise. - - `REJECT_SNAPSHOT`: Reject this snapshot, try a different one. - - `RefetchChunks ([]uint32)`: Refetch and reapply the given chunks, regardless of `Result`. Only - the listed chunks will be refetched, and reapplied in sequential order. - - `RejectSenders ([]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. + + | 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. @@ -513,48 +592,17 @@ via light client. ## Data Types -### Header - -- **Fields**: - - `Version (Version)`: Version of the blockchain and the application - - `ChainID (string)`: ID of the blockchain - - `Height (int64)`: Height of the block in the chain - - `Time (google.protobuf.Timestamp)`: Time of the previous block. - For most blocks it's the weighted median of the timestamps of the valid votes in the - block.LastCommit, except for the initial height where it's the genesis time. - - `LastBlockID (BlockID)`: Hash of the previous (parent) block - - `LastCommitHash ([]byte)`: Hash of the previous block's commit - - `ValidatorsHash ([]byte)`: Hash of the validator set for this block - - `NextValidatorsHash ([]byte)`: Hash of the validator set for the next block - - `ConsensusHash ([]byte)`: Hash of the consensus parameters for this block - - `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the - Merkle root of the application state after executing the previous block's - transactions - - `LastResultsHash ([]byte)`: Root hash of all results from the txs from the previous block. - - `EvidenceHash ([]byte)`: Hash of the evidence included in this block - - `ProposerAddress ([]byte)`: Original proposer for the block -- **Usage**: - - Provided in RequestBeginBlock - - Provides important context about the current state of the blockchain - - especially height and time. - - Provides the proposer of the current block, for use in proposer-based - reward mechanisms. - - `LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`, `Info`, `Codespace` and `Events` fields are ignored). - -### Version - -- **Fields**: - - `Block (uint64)`: Protocol version of the blockchain data structures. - - `App (uint64)`: Protocol version of the application. -- **Usage**: - - Block version should be static in the life of a blockchain. - - App version may be updated over time by the application. +The data types not listed below are the same as the [core data structures](../core/data_structures.md). The ones listed below have specific changes to better accommodate applications. ### Validator - **Fields**: - - `Address ([]byte)`: Address of the validator (the first 20 bytes of SHA256(public key)) - - `Power (int64)`: Voting power of the validator + + | Name | Type | Description | Field Number | + |---------|-------|---------------------------------------------------------------------|--------------| + | address | bytes | Address of the validator (the first 20 bytes of SHA256(public key)) | 1 | + | power | int64 | Voting power of the validator | 3 | + - **Usage**: - Validator identified by address - Used in RequestBeginBlock as part of VoteInfo @@ -564,8 +612,12 @@ via light client. ### ValidatorUpdate - **Fields**: - - `PubKey (PubKey)`: Public key of the validator - - `Power (int64)`: Voting power of the validator + + | 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 @@ -573,109 +625,100 @@ via light client. ### VoteInfo - **Fields**: - - `Validator (Validator)`: A validator - - `SignedLastBlock (bool)`: Indicates whether or not the validator signed - the last block + + | 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 -### PubKey - -- **Fields**: - - `Sum (oneof PublicKey)`: This field is a Protobuf [`oneof`](https://developers.google.com/protocol-buffers/docs/proto#oneof) -- **Usage**: - - A generic and extensible typed public key - ### Evidence - **Fields**: - - `Type (EvidenceType)`: Type of the evidence. An enum of possible evidence's. - - `Validator (Validator`: The offending validator - - `Height (int64)`: Height when the offense occured - - `Time (google.protobuf.Timestamp)`: Time of the block that was committed at the height that the offense occured - - `TotalVotingPower (int64)`: Total voting power of the validator set at - height `Height` + + | 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**: - - `Round (int32)`: Commit round. Reflects the total amount of rounds it took to come to consensus for the current block. - - `Votes ([]VoteInfo)`: List of validators addresses in the last validator set - with their voting power and whether or not they signed a vote. + + | 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**: - - `Block (BlockParams)`: Parameters limiting the size of a block and time between consecutive blocks. - - `Evidence (EvidenceParams)`: Parameters limiting the validity of - evidence of byzantine behaviour. - - `Validator (ValidatorParams)`: Parameters limiting the types of pubkeys validators can use. - - `Version (VersionParams)`: The ABCI application version. + + | Name | Type | Description | Field Number | + |-----------|---------------------------------------------------------------|------------------------------------------------------------------------------|--------------| + | block | [BlockParams](#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 | [BlockParams](../core/data_structures.md#versionparams) | The ABCI application version. | 4 | ### BlockParams - **Fields**: - - `MaxBytes (int64)`: Max size of a block, in bytes. - - `MaxGas (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! -### EvidenceParams + | 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 | + +> Note: time_iota_ms is removed from this data structure. + +### ProofOps - **Fields**: - - `MaxAgeNumBlocks (int64)`: Max age of evidence, in blocks. - - `MaxAgeDuration (time.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). - - Evidence older than `MaxAgeNumBlocks` && `MaxAgeDuration` is considered - stale and ignored. - - In Cosmos-SDK based blockchains, `MaxAgeDuration` is usually equal to the - unbonding period. `MaxAgeNumBlocks` is calculated by dividing the unboding - period by the average block time (e.g. 2 weeks / 6s per block = 2d8h). - - `MaxNum (uint32)`: The maximum number of evidence that can be committed to a single block - -### ValidatorParams - -- **Fields**: - - `PubKeyTypes ([]string)`: List of accepted public key types. - - Uses same naming as `PubKey.Type`. - -### VersionParams - -- **Fields**: - - `AppVersion (uint64)`: The ABCI application version. - -### Proof - -- **Fields**: - - `Ops ([]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. + | 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**: - - `Type (string)`: Type of Merkle proof and how it's encoded. - - `Key ([]byte)`: Key in the Merkle tree that this proof is for. - - `Data ([]byte)`: Encoded Merkle proof for the key. + + | 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**: - - `Height (uint64)`: The height at which the snapshot was taken (after commit). - - `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. - - `Chunks (uint32)`: The number of chunks in the snapshot. Must be at least 1 (even if empty). - - `Hash (bytes)`: An arbitrary snapshot hash. Must be equal only for identical snapshots across - nodes. Tendermint does not interpret the hash, it only compares them. - - `Metadata (bytes)`: Arbitrary application metadata, for example chunk hashes or other - verification data. + + | 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 [separate section](apps.md#state-sync) for details. diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 32d57b01c..ff892d43d 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -26,15 +26,19 @@ The Tendermint blockchains consists of a short list of data types: - [`Validator`](#validator) - [`ValidatorSet`](#validatorset) - [`Address`](#address) +- [`ConsensusParams](#consensusparams) +- [`EvidenceParams`](#evidenceparams) +- [`ValidatorParams`](#validatorparams) +- [`VersionParams`](#versionparams) ## 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) | +| 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 | [EvidenceData](#evidence_data) | Evidence contains a list of infractions committed by validators. | Can be empty, but when populated the validations rules from [evidenceData](#evidence_data) 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). | @@ -104,37 +108,37 @@ The steps to validate a new block are: 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 verion 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 | int64 | 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 | +| Name | Type | Description | Validation | +|-------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Version | [Version](#version) | Version defines the application and protocol verion 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 | int64 | 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 -| 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` | +| 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 | +| 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. @@ -165,7 +169,7 @@ Commit is a simple wrapper for a list of signatures, with one for each validator | Name | Type | Description | Validation | |------------|----------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| -| Height | int64 | Height at which this commit was created. | Must be > 0 | +| Height | int64 | 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) | @@ -176,12 +180,12 @@ Commit is a simple wrapper for a list of signatures, with one for each validator 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 | -|------------------|-----------------------------|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| +| 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 | +| 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)). @@ -206,7 +210,7 @@ The vote includes information about the validator signing it. When stored in the | 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) | +| 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 | int64 | 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) | @@ -278,8 +282,8 @@ See the [signature spec](./encoding.md#key-types) for more. EvidenceData is a simple wrapper for a list of evidence: -| Name | Type | Description | Validation | -|----------|--------------------------------|------------------------------------------|-----------------------------------------------------------------| +| Name | Type | Description | Validation | +|----------|--------------------------------|----------------------------------------|-----------------------------------------------------------------| | Evidence | Array of [Evidence](#evidence) | List of verified [evidence](#evidence) | Validation adheres to individual types of [Evidence](#evidence) | ## Evidence @@ -363,8 +367,8 @@ The `SignedHeader` and `ValidatorSet` are linked by the hash of the validator se The SignedhHeader is the [header](#header) accompanied by the commit to prove it. -| Name | Type | Description | Validation | -|--------|-------------------|-------------------|---------------------------------------------------------------------------------| +| 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 | @@ -373,16 +377,16 @@ The SignedhHeader is the [header](#header) accompanied by the commit to prove it | 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) | +| 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 | +| 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 @@ -398,3 +402,40 @@ func SumTruncated(bz []byte) []byte { 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 | +| time_iota_ms | int64 | (**Deprecated**) Unused Param | 3 | + +### 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 | From 1b2b24055c5e44c41347cd524a713f0eddb7881c Mon Sep 17 00:00:00 2001 From: Josef Widder <44643235+josef-widder@users.noreply.github.com> Date: Fri, 15 Jan 2021 13:39:36 +0100 Subject: [PATCH 132/223] Update supervisor_001_draft.md (#243) --- rust-spec/lightclient/supervisor/supervisor_001_draft.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rust-spec/lightclient/supervisor/supervisor_001_draft.md b/rust-spec/lightclient/supervisor/supervisor_001_draft.md index cdd291436..df7a71b3e 100644 --- a/rust-spec/lightclient/supervisor/supervisor_001_draft.md +++ b/rust-spec/lightclient/supervisor/supervisor_001_draft.md @@ -584,6 +584,7 @@ func VerifyAndDetect (lightStore LightStore, targetHeight Height) } else { // there is an attack, we exit + submitEvidence(Evidences); return(lightStore, ErrorAttack); } } From 72d15a4b0773adb8d5160a34d9a8e2d981394484 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 19 Jan 2021 16:28:05 +0100 Subject: [PATCH 133/223] spec: remove reactor section (#242) Co-authored-by: Tess Rinearson --- rust-spec/fastsync/README.md | 50 - rust-spec/fastsync/Tinychain.tla | 113 -- rust-spec/fastsync/fastsync.md | 1216 ----------------- rust-spec/fastsync/fastsync.tla | 825 ----------- rust-spec/fastsync/scheduler.tla | 606 -------- .../fastsync/verification/001bmc-apalache.csv | 13 - .../fastsync/verification/002tlc-tlc.csv | 9 - .../verification/MC-CorrectBlocksInv.cfg | 10 - .../MC-CorrectNeverSuspectedInv.cfg | 10 - .../fastsync/verification/MC-Sync1AsInv.cfg | 10 - .../fastsync/verification/MC-Sync2AsInv.cfg | 10 - .../fastsync/verification/MC-Sync3AsInv.cfg | 10 - .../verification/MC-SyncFromCorrectInv.cfg | 10 - .../fastsync/verification/MC-Termination.cfg | 10 - .../verification/MC-TerminationByTO.cfg | 10 - .../verification/MC-TerminationCorr.cfg | 10 - rust-spec/fastsync/verification/MC_1_0_4.tla | 17 - rust-spec/fastsync/verification/MC_1_1_4.tla | 15 - rust-spec/fastsync/verification/MC_1_2_4.tla | 15 - rust-spec/fastsync/verification/MC_1_2_5.tla | 15 - rust-spec/fastsync/verification/MC_1_3_5.tla | 15 - rust-spec/fastsync/verification/MC_2_0_4.tla | 17 - rust-spec/fastsync/verification/Tinychain.tla | 110 -- .../verification/fastsync_apalache.tla | 822 ----------- spec/consensus/bft-time.md | 3 + spec/consensus/consensus.md | 3 + spec/consensus/creating-proposal.md | 3 + spec/consensus/light-client/README.md | 6 + .../consensus/proposer-selection.md | 120 +- spec/core/data_structures.md | 44 +- 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 | 48 + spec/p2p/messages/state-sync.md | 85 ++ .../block_sync/img/bc-reactor-routines.png | Bin 271695 -> 0 bytes spec/reactors/block_sync/img/bc-reactor.png | Bin 44211 -> 0 bytes spec/reactors/block_sync/impl.md | 43 - spec/reactors/block_sync/reactor.md | 308 ----- spec/reactors/consensus/consensus-reactor.md | 366 ----- spec/reactors/consensus/consensus.md | 187 --- spec/reactors/evidence/reactor.md | 10 - spec/reactors/mempool/concurrency.md | 8 - spec/reactors/mempool/config.md | 54 - spec/reactors/mempool/functionality.md | 43 - spec/reactors/mempool/messages.md | 52 - spec/reactors/mempool/reactor.md | 27 - spec/reactors/pex/pex.md | 164 --- spec/reactors/pex/reactor.md | 12 - spec/reactors/readme.md | 5 - spec/reactors/state_sync/reactor.md | 77 -- 53 files changed, 542 insertions(+), 5366 deletions(-) delete mode 100644 rust-spec/fastsync/README.md delete mode 100644 rust-spec/fastsync/Tinychain.tla delete mode 100644 rust-spec/fastsync/fastsync.md delete mode 100644 rust-spec/fastsync/fastsync.tla delete mode 100644 rust-spec/fastsync/scheduler.tla delete mode 100644 rust-spec/fastsync/verification/001bmc-apalache.csv delete mode 100644 rust-spec/fastsync/verification/002tlc-tlc.csv delete mode 100644 rust-spec/fastsync/verification/MC-CorrectBlocksInv.cfg delete mode 100644 rust-spec/fastsync/verification/MC-CorrectNeverSuspectedInv.cfg delete mode 100644 rust-spec/fastsync/verification/MC-Sync1AsInv.cfg delete mode 100644 rust-spec/fastsync/verification/MC-Sync2AsInv.cfg delete mode 100644 rust-spec/fastsync/verification/MC-Sync3AsInv.cfg delete mode 100644 rust-spec/fastsync/verification/MC-SyncFromCorrectInv.cfg delete mode 100644 rust-spec/fastsync/verification/MC-Termination.cfg delete mode 100644 rust-spec/fastsync/verification/MC-TerminationByTO.cfg delete mode 100644 rust-spec/fastsync/verification/MC-TerminationCorr.cfg delete mode 100644 rust-spec/fastsync/verification/MC_1_0_4.tla delete mode 100644 rust-spec/fastsync/verification/MC_1_1_4.tla delete mode 100644 rust-spec/fastsync/verification/MC_1_2_4.tla delete mode 100644 rust-spec/fastsync/verification/MC_1_2_5.tla delete mode 100644 rust-spec/fastsync/verification/MC_1_3_5.tla delete mode 100644 rust-spec/fastsync/verification/MC_2_0_4.tla delete mode 100644 rust-spec/fastsync/verification/Tinychain.tla delete mode 100644 rust-spec/fastsync/verification/fastsync_apalache.tla rename spec/{reactors => }/consensus/proposer-selection.md (77%) 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 delete mode 100644 spec/reactors/block_sync/img/bc-reactor-routines.png delete mode 100644 spec/reactors/block_sync/img/bc-reactor.png delete mode 100644 spec/reactors/block_sync/impl.md delete mode 100644 spec/reactors/block_sync/reactor.md delete mode 100644 spec/reactors/consensus/consensus-reactor.md delete mode 100644 spec/reactors/consensus/consensus.md delete mode 100644 spec/reactors/evidence/reactor.md delete mode 100644 spec/reactors/mempool/concurrency.md delete mode 100644 spec/reactors/mempool/config.md delete mode 100644 spec/reactors/mempool/functionality.md delete mode 100644 spec/reactors/mempool/messages.md delete mode 100644 spec/reactors/mempool/reactor.md delete mode 100644 spec/reactors/pex/pex.md delete mode 100644 spec/reactors/pex/reactor.md delete mode 100644 spec/reactors/readme.md delete mode 100644 spec/reactors/state_sync/reactor.md diff --git a/rust-spec/fastsync/README.md b/rust-spec/fastsync/README.md deleted file mode 100644 index 9e2f91796..000000000 --- a/rust-spec/fastsync/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Fast Sync Subprotocol Specification - -This directory contains English and TLA+ specifications for the FastSync -protocol as it is currently implemented in the Tendermint Core codebase. - -## English Specification - -The [English Specification](fastsync.md) provides a detailed description of the -fast sync problem and the properties a correct protocol must satisfy. It also -includes a detailed description of the protocol as currently implemented in Go, -and an anlaysis of the implementation with respect to the properties. - -It was found that the current implementation does not satisfy certain -properties, and is therefore not a correct solution to the fast sync problem. -The issue discovered holds for all previous implementations of the protocol. A -fix is described which is straight forward to implement. - -## TLA+ Specification - -Two TLA+ specifications are provided: a high level [specification -of the protocol](fastsync.tla) and a low level [specification of the scheduler -component of the implementation](scheduler.tla). Both specifications contain -properties that may be checked by the TLC model checker, though only for small -values of the relevant parameters. - -We will continue to refine these specifications in our research work, -to deduplicate -the redundancies between them, improve their utility to researchers and -engineers, and to improve their verifiability. For now, they provide a complete -description of the fast sync protocol in TLA+; especially the -[scheduler.tla](scheduler.tla), which maps very closely to the current -implementation of the [scheduler in Go](https://github.com/tendermint/tendermint/blob/master/blockchain/v2/scheduler.go). - -The [scheduler.tla](scheduler.tla) can be model checked in TLC with the following -parameters: - -- Constants: - - numRequests <- 2 - - PeerIDs <- 0..2 - - ultimateHeight <- 3 -- Invariants: - - TypeOK -- Properties: - - TerminationWhenNoAdvance - - TerminationGoodPeers - - TerminationAllCases -- Proofs that properties are not vacuously true: - - TerminationGoodPeersPre - - TerminationAllCases - - SchedulerIncreasePre diff --git a/rust-spec/fastsync/Tinychain.tla b/rust-spec/fastsync/Tinychain.tla deleted file mode 100644 index 26200c4f6..000000000 --- a/rust-spec/fastsync/Tinychain.tla +++ /dev/null @@ -1,113 +0,0 @@ --------------------------- MODULE Tinychain ---------------------------------- -(* A very abstract model of Tendermint blockchain. Its only purpose is to highlight - the relation between validator sets, next validator sets, and last commits. - *) - -EXTENDS Integers - -\* type annotation -a <: b == a - -\* the type of validator sets, e.g., STRING -VST == STRING - -\* LastCommit type. -\* It contains: -\* 1) the flag of whether the block id equals to the hash of the previous block, and -\* 2) the set of the validators who have committed the block. -\* In the implementation, blockId is the hash of the previous block, which cannot be forged. -\* We abstract block id into whether it equals to the hash of the previous block or not. -LCT == [blockIdEqRef |-> BOOLEAN, committers |-> VST] - -\* Block type. -\* A block contains its height, validator set, next validator set, and last commit. -\* Moreover, it contains the flag that tells us whether the block is equal to the one -\* on the reference chain (this is an abstraction of hash). -BT == [height |-> Int, hashEqRef |-> BOOLEAN, wellFormed |-> BOOLEAN, - VS |-> VST, NextVS |-> VST, lastCommit |-> LCT] - -CONSTANTS - (* - A set of abstract values, each value representing a set of validators. - For the purposes of this specification, they can be any values, - e.g., "s1", "s2", etc. - *) - VALIDATOR_SETS, - (* a nil validator set that is outside of VALIDATOR_SETS *) - NIL_VS, - (* The maximal height, up to which the blockchain may grow *) - MAX_HEIGHT - -Heights == 1..MAX_HEIGHT - -\* the set of all potential commits -Commits == [blockIdEqRef: BOOLEAN, committers: VALIDATOR_SETS] - -\* the set of all potential blocks, not necessarily coming from the blockchain -Blocks == - [height: Heights, hashEqRef: BOOLEAN, wellFormed: BOOLEAN, - VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: Commits] - -\* Does the chain contain a sound sequence of blocks that could be produced by -\* a 2/3 of faulty validators. This operator can be used to initialise the chain! -\* Since we are abstracting validator sets with VALIDATOR_SETS, which are -\* 2/3 quorums, we just compare committers to those sets. In a more detailed -\* specification, one would write the \subseteq operator instead of equality. -IsCorrectChain(chain) == - \* restrict the structure of the blocks, to decrease the TLC search space - LET OkCommits == [blockIdEqRef: {TRUE}, committers: VALIDATOR_SETS] - OkBlocks == [height: Heights, hashEqRef: {TRUE}, wellFormed: {TRUE}, - VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: OkCommits] - IN - /\ chain \in [1..MAX_HEIGHT -> OkBlocks] - /\ \A h \in 1..MAX_HEIGHT: - LET b == chain[h] IN - /\ b.height = h \* the height is correct - /\ h > 1 => - LET p == chain[h - 1] IN - /\ b.VS = p.NextVS \* the validators propagate from the previous block - /\ b.lastCommit.committers = p.VS \* and they are the committers - - -\* The basic properties of blocks on the blockchain: -\* They should pass the validity check and they may verify the next block. - -\* Does the block pass the consistency check against the next validators of the previous block -IsMatchingValidators(block, nextVS) == - \* simply check that the validator set is propagated correctly. - \* (the implementation tests hashes and the application state) - block.VS = nextVS - -\* Does the block verify the commit (of the next block) -PossibleCommit(block, commit) == - \* the commits are signed by the block validators - /\ commit.committers = block.VS - \* The block id in the commit matches the block hash (abstract comparison). - \* (The implementation has extensive tests for that.) - \* this is an abstraction of: commit.blockId = hash(block) - \* - \* These are possible scenarios on the concrete hashes: - \* - \* scenario 1: commit.blockId = 10 /\ hash(block) = 10 /\ hash(ref) = 10 - \* scenario 2: commit.blockId = 20 /\ hash(block) = 20 /\ block.VS /= ref.VS - \* scenario 3: commit.blockId = 50 /\ hash(block) = 100 - \* scenario 4: commit.blockId = 10 /\ hash(block) = 100 - \* scenario 5: commit.blockId = 100 /\ hash(block) = 10 - /\ commit.blockIdEqRef = block.hashEqRef - \* the following test would be cheating, as we do not have access to the - \* reference chain: - \* /\ commit.blockIdEqRef - -\* Basic invariants - -\* every block has the validator set that is chosen by its predecessor -ValidBlockInv(chain) == - \A h \in 2..MAX_HEIGHT: - IsMatchingValidators(chain[h], chain[h - 1].NextVS) - -\* last commit of every block is signed by the validators of the predecessor -VerifiedBlockInv(chain) == - \A h \in 2..MAX_HEIGHT: - PossibleCommit(chain[h - 1], chain[h].lastCommit) - -================================================================================== diff --git a/rust-spec/fastsync/fastsync.md b/rust-spec/fastsync/fastsync.md deleted file mode 100644 index aaf0e7334..000000000 --- a/rust-spec/fastsync/fastsync.md +++ /dev/null @@ -1,1216 +0,0 @@ -# Fastsync - -Fastsync is a protocol that is used by a node to catch-up to the -current state of a Tendermint blockchain. Its typical use case is a -node that was disconnected from the system for some time. The -recovering node locally has a copy of a prefix of the blockchain, -and the corresponding application state that is slightly outdated. It -then queries its peers for the blocks that were decided on by the -Tendermint blockchain during the period the full node was -disconnected. After receiving these blocks, it executes the -transactions in the blocks in order to catch-up to the current height -of the blockchain and the corresponding application state. - -In practice it is sufficient to catch-up only close to the current -height: The Tendermint consensus reactor implements its own catch-up -functionality and can synchronize a node that is close to the current height, -perhaps within 10 blocks away from the current height of the blockchain. -Fastsync should bring a node within this range. - -## Outline - -- [Part I](#part-i---tendermint-blockchain): Introduction of Tendermint -blockchain terms that are relevant for FastSync protocol. - -- [Part II](#part-ii---sequential-definition-of-fastsync-problem): Introduction -of the problem addressed by the Fastsync protocol. - - [Fastsync Informal Problem - statement](#Fastsync-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---fastsync-as-distributed-system): Distributed - aspects of the fast sync problem, system assumptions and temporal - logic specifications. - - - [Computational Model](#Computational-Model): - timing and correctness assumptions. - - - [Distributed Problem Statement](#Distributed-Problem-Statement): - temporal properties that formalize safety and liveness - properties of fast sync in distributed setting. - -- [Part IV](#part-iv---fastsync-protocol): Specification of Fastsync V2 - (the protocol underlying the current Golang implementation). - - - [Definitions](#Definitions): Describes inputs, outputs, - variables used by the protocol, auxiliary functions - - - [FastSync V2](#FastSync-V2): gives an outline of the solution, - and details of the functions used (with preconditions, - postconditions, error conditions). - - - [Algorithm Invariants](#Algorithm-Invariants): invariants over - the protocol variables that the implementation should maintain. - -- [Part V](#part-v---analysis-and-improvements): Analysis - of Fastsync V2 that highlights several issues that prevent achieving - some of the desired fault-tolerance properties. We also give some - suggestions on how to address the issues in the future. - - - [Analysis of Fastsync V2](#Analysis-of-Fastsync-V2): describes - undesirable scenarios of Fastsync V2, and why they violate - desirable temporal logic specification in an unreliable - distributed system. - - - [Suggestions](#Suggestions-for-an-Improved-Fastsync-Implementation) to address the issues discussed in the analysis. - -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 -- FS: Fastsync -- LIVE: liveness -- SAFE: safety -- INV: invariant -- A: assumption -- V2: refers to specifics of Fastsync V2 -- FS-VAR: refers to properties of Fastsync protocol variables -- NewFS: refers to improved future Fastsync implementations - -# Part I - Tendermint Blockchain - -We will briefly list some of the notions of Tendermint blockchains that are -required for this specification. More details can be found [here][block]. - -#### **[TMBC-HEADER]** - -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-SEQ]** - -The Tendermint blockchain is a list *chain* of headers. - -#### **[TMBC-SEQ-GROW]** - -During operation, new headers may be appended to the list one by one. - -> In the following, *ETIME* is a lower bound -> on the time interval between the times at which two -> successor blocks are added. - -#### **[TMBC-SEQ-APPEND-E]** - -If a header is appended at time *t* then no additional header will be -appended before time *t + ETIME*. - -#### **[TMBC-AUTH-BYZ]** - -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. - - - - - -> We observed that in the existing documentation the term -> *validator* refers to both a data structure and a full node that -> participates in the distributed computation. Therefore, we introduce -> the notions *validator pair* and *validator node*, respectively, to -> distinguish these notions in the cases where they are not clear from -> the context. - -#### **[TMBC-VALIDATOR-PAIR]** - -Given a full node, a -*validator pair* is a pair *(address, voting_power)*, where - -- *address* is the address (public key) of a full node, -- *voting_power* is an integer (representing the full node's - voting power in a given consensus instance). - -> In the Golang implementation the data type for *validator -> pair* is called `Validator`. - -#### **[TMBC-VALIDATOR-SET]** - -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-CORRECT]** - -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-TIME-PARAMS]** - -A blockchain has the following configuration parameters: - -- *unbondingPeriod*: a time duration. -- *trustingPeriod*: a time duration smaller than *unbondingPeriod*. - -#### **[TMBC-FM-2THIRDS]** - -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)*; -- For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n, - h.Time + trustingPeriod)*. - -#### **[TMBC-CORR-FULL]** - -Every correct full node locally stores a prefix of the -current list of headers from [**[TMBC-SEQ]**][TMBC-SEQ-link]. - -# Part II - Sequential Definition of Fastsync Problem - -## Fastsync Informal Problem statement - -A full node has as input a block of the blockchain at height *h* and -the corresponding application state (or the prefix of the current -blockchain until height *h*). It has access to a set *peerIDs* of full -nodes called *peers* that it knows of. The full node uses the peers -to read blocks of the Tendermint blockchain (in a safe way, that is, -it checks the soundness conditions), until it has read the most recent -block and then terminates. - -## Sequential Problem statement - -*Fastsync* gets as input a block of height *h* and the corresponding -application state *s* that corresponds to the block and state of that -height of the blockchain, and produces -as output (i) a list *L* of blocks starting at height *h* to some height -*terminationHeight*, and (ii) the application state when applying the -transactions of the list *L* to *s*. - -> In Tendermint, the commit for block of height *h* is contained in block *h + 1*, -> and thus the block of height *h + 1* is needed to verify the block of -> height *h*. Let us therefore clarify the following on the -> termination height: -> The returned value *terminationHeight* is the height of the block with the largest -> height that could be verified. In order to do so, *Fastsync* needs the -> block at height *terminationHeight + 1* of the blockchain. - -Fastsync has to satisfy the following properties: - -#### **[FS-SEQ-SAFE-START]** - -Let *bh* be the height of the blockchain at the time *Fastsync* -starts. By assumption we have *bh >= h*. -When *Fastsync* terminates, it outputs a list of all blocks from -height *h* to some height *terminationHeight >= bh - 1*. - -> The above property is independent of how many blocks are added to the -> blockchain while Fastsync is running. It links the target height to the -> initial state. If Fastsync has to catch-up many blocks, it would be -> better to link the target height to a time close to the -> termination. This is captured by the following specification: - -#### **[FS-SEQ-SAFE-SYNC]** - -Let *eh* be the height of the blockchain at the time *Fastsync* -terminates. There is a constant *D >= 1* such that when *Fastsync* -terminates, it outputs a list of all blocks from height *h* to some -height *terminationHeight >= eh - D*. - -#### **[FS-SEQ-SAFE-STATE]** - -Upon termination, the application state is the one that corresponds to -the blockchain at height *terminationHeight*. - -#### **[FS-SEQ-LIVE]** - -*Fastsync* eventually terminates. - -# Part III - FastSync as Distributed System - -## Computational Model - -#### **[FS-A-NODE]** - -We consider a node *FS* that performs *Fastsync*. - -#### **[FS-A-PEER-IDS]** - -*FS* has access to a set *peerIDs* of IDs (public keys) of peers - . During the execution of *Fastsync*, another protocol (outside - of this specification) may add new IDs to *peerIDs*. - -#### **[FS-A-PEER]** - -Peers can be faulty, and we do not make any assumptions about the number or -ratio of correct/faulty nodes. Faulty processes may be Byzantine -according to [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link]. - -#### **[FS-A-VAL]** - -The system satisfies [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] and -[**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link]. Thus, there is a -blockchain that satisfies the soundness requirements (that is, the -validation rules in [[block]]). - -#### **[FS-A-COMM]** - -Communication between the node *FS* and all correct peers is reliable and -bounded in time: there is a message end-to-end delay *Delta* such that -if a message is sent at time *t* by a correct process to a correct -process, then it will be 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. - -## Distributed Problem Statement - -### Two Kinds of Termination - -We do not assume that there is a correct full node in -*peerIDs*. Under this assumption no protocol can guarantee the combination -of the properties [FS-SEQ-LIVE] and -[FS-SEQ-SAFE-START] and [FS-SEQ-SAFE-SYNC] described in the sequential -specification above. Thus, in the (unreliable) distributed setting, we -consider two kinds of termination (successful and failure) and we will -specify below under what (favorable) conditions *Fastsync* ensures to -terminate successfully, and satisfy the requirements of the sequential -problem statement: - -#### **[FS-DIST-LIVE]** - -*Fastsync* eventually terminates: it either *terminates successfully* or -it *terminates with failure*. - -### Fairness - -As mentioned above, without assumptions on the correctness of some -peers, no protocol can achieve the required specifications. Therefore, -we consider the following (fairness) constraint in the -safety and liveness properties below: - -#### **[FS-SOME-CORR-PEER]** - -Initially, the set *peerIDs* contains at least one correct full node. - -> While in principle the above condition [FS-SOME-CORR-PEER] -> can be part of a sufficient -> condition to solve [FS-SEQ-LIVE] and -> [FS-SEQ-SAFE-START] and [FS-SEQ-SAFE-SYNC] in the distributed -> setting (their corresponding properties are given below), we will discuss in -> [Part V](#part-v---analysis-and-improvements) that the -> current implementation of Fastsync (V2) requires the much -> stronger requirement [**[FS-ALL-CORR-PEER]**](#FS-ALL-CORR-PEER) -> given in Part V. - -### Safety - -> As this specification does -> not assume that a correct peer is at the most recent height -> of the blockchain (it might lag behind), the property [FS-SEQ-SAFE-START] -> cannot be ensured in an unreliable distributed setting. We consider -> the following relaxation. (Which is typically sufficient for -> Tendermint, as the consensus reactor then synchronizes from that -> height.) - -#### **[FS-DIST-SAFE-START]** - -Let *maxh* be the maximum -height of a correct peer [**[TMBC-CORR-FULL]**][TMBC-CORR-FULL-link] -in *peerIDs* at the time *Fastsync* starts. If *FastSync* terminates -successfully, it is at some height *terminationHeight >= maxh - 1*. - -> To address [FS-SEQ-SAFE-SYNC] we consider the following property in -> the distributed setting. See the comments below on the relation to -> the sequential version. - -#### **[FS-DIST-SAFE-SYNC]** - -Under [FS-SOME-CORR-PEER], there exists a constant time interval *TD*, such -that if *term* is the time *Fastsync* terminates and -*maxh* is the maximum height of a correct peer -[**[TMBC-CORR-FULL]**][TMBC-CORR-FULL-link] in *peerIDs* at the time -*term - TD*, then if *FastSync* terminates successfully, it is at -some height *terminationHeight >= maxh - 1*. - -> *TD* might depend on timeouts etc. We suggest that an acceptable -> value for *TD* is in the range of approx. 10 sec., that is the -> interval between two calls `QueryStatus()`; see below. -> We use *term - TD* as reference time, as we have to account -> for communication delay between the peer and *FS*. After the peer sent -> the last message to *FS*, the peer and *FS* run concurrently and -> independently. There is no assumption on the rate at which a peer can -> add blocks (e.g., it might be in the process of catching up -> itself). Hence, without additional assumption we cannot link -> [FS-DIST-SAFE-SYNC] to -> [**[FS-SEQ-SAFE-SYNC]**](#FS-SEQ-SAFE-SYNC), in particular to the -> parameter *D*. We discuss a -> way to achieve this below: -> **Relation to [FS-SEQ-SAFE-SYNC]:** -> Under [FS-SOME-CORR-PEER], if *peerIDs* contains a full node that is -> "synchronized with the blockchain", and *blockchainheight* is the height -> of the blockchain at time *term*, then *terminationHeight* may even -> achieve -> *blockchainheight - TD / ETIME*; -> cf. [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link], that is, -> the parameter *D* from [FS-SEQ-SAFE-SYNC] is in the range of *TD / ETIME*. - -#### **[FS-DIST-SAFE-STATE]** - -It is the same as the sequential version -[**[FS-SEQ-SAFE-STATE]**](#FS-SEQ-SAFE-STATE). - -#### **[FS-DIST-NONABORT]** - -If there is one correct process in *peerIDs* [FS-SOME-CORR-PEER], -*Fastsync* never terminates with failure. (Together with [FS-DIST-LIVE] - that means it will terminate successfully.) - -# Part IV - Fastsync protocol - -Here we provide a specification of the FastSync V2 protocol as it is currently -implemented. The V2 design is the result of significant refactoring to improve -the testability and determinism in the implementation. The architecture is -detailed in -[ADR-43](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-043-blockchain-riri-org.md). - -In the original design, a go-routine (thread of execution) was spawned for each block requested, and -was responsible for both protocol logic and IO. In the V2 design, protocol logic -is decoupled from IO by using three total threads of execution: a scheduler, a -processer, and a demuxer. - -The scheduler contains the business logic for managing -peers and requesting blocks from them, while the processor handles the -computationally expensive block execution. Both the scheduler and processor -are structured as finite state machines that receive input events and emit -output events. The demuxer is responsible for all IO, including translating -between internal events and IO messages, and routing events between components. - -Protocols in Tendermint can be considered to consist of two -components: a "core" state machine and a "peer" state machine. The core state -machine refers to the internal state managed by the node, while the peer state -machine determines what messages to send to peers. In the FastSync design, the -core and peer state machines correspond to the processor and scheduler, -respectively. - -In the case of FastSync, the core state machine (the processor) is effectively -just the Tendermint block execution function, while virtually all protocol logic -is contained in the peer state machine (the scheduler). The processor is -only implemented as a separate component due to the computationally expensive nature -of block execution. We therefore focus our specification here on the peer state machine -(the scheduler component), capturing the core state machine (the processor component) -in the single `Execute` function, defined below. - -While the internal details of the `Execute` function are not relevant for the -FastSync protocol and are thus not part of this specification, they will be -defined in detail at a later date in a separate Block Execution specification. - -## Definitions - -> We now introduce variables and auxiliary functions used by the protocol. - -### Inputs - -- *startBlock*: the block Fastsync starts from -- *startState*: application state corresponding to *startBlock.Height* - -#### **[FS-A-V2-INIT]** - -- *startBlock* is from the blockchain -- *startState* is the application state of the blockchain at Height *startBlock.Height*. - -### Variables - -- *height*: kinitially *startBlock.Height + 1* - > height should be thought of the "height of the next block we need to download" -- *state*: initially *startState* -- *peerIDs*: peer addresses [FS-A-PEER-IDS](#fs-a-peer-ids) -- *peerHeights*: stores for each peer the height it reported. initially 0 -- *pendingBlocks*: stores for each height which peer was - queried. initially nil for each height -- *receivedBlocks*: stores for each height which peer returned - it. initially nil -- *blockstore*: stores for each height greater than - *startBlock.Height*, the block of that height. initially nil for - all heights -- *peerTimeStamp*: stores for each peer the last time a block was - received - -- *pendingTime*: stores for a given height the time a block was requested -- *peerRate*: stores for each peer the rate of received data in Bytes/second - -### Auxiliary Functions - -#### **[FS-FUNC-TARGET]** - -- *TargetHeight = max {peerHeigts(addr): addr in peerIDs} union {height}* - -#### **[FS-FUNC-MATCH]** - -```go -func VerifyCommit(b Block, c Commit) Boolean -``` - -- Comment - - Corresponds to `verifyCommit(chainID string, blockID - types.BlockID, height int64, commit *types.Commit) error` in the - current Golang implementation, which expects blockID and height - (from the first block) and the - corresponding commit from the following block. We use the - simplified form for ease in presentation. - -- Implementation remark - - - - - implements the check that *c* is a valid commit for block *b* -- Expected precondition - - *c* is a valid commit for block *b* -- Expected postcondition - - *true* if precondition holds - - *false* if precondition is violated -- Error condition - - none - ----- - -### Messages - -Peers participating in FastSync exchange the following set of messages. Messages are -encoded using the Amino serialization protocol. We define each message here -using Go syntax, annoted with the Amino type name. The prefix `bc` refers to -`blockchain`, which is the name of the FastSync reactor in the Go -implementation. - -#### bcBlockRequestMessage - -```go -// type: "tendermint/blockchain/BlockRequest" -type bcBlockRequestMessage struct { - Height int64 -} -``` - -Remark: - -- `msg.Height` > 0 - -#### bcNoBlockResponseMessage - -```go -// type: "tendermint/blockchain/NoBlockResponse" -type bcNoBlockResponseMessage struct { - Height int64 -} -``` - -Remark: - -- `msg.Height` > 0 -- This message type is included in the protocol for convenience and is not expected to be sent between two correct peers - -#### bcBlockResponseMessage - -```go -// type: "tendermint/blockchain/BlockResponse" -type bcBlockResponseMessage struct { - Block *types.Block -} -``` - -Remark: - -- `msg.Block` is a Tendermint block as defined in [[block]]. -- `msg.Block` != nil - -#### bcStatusRequestMessage - -```go -// type: "tendermint/blockchain/StatusRequest" -type bcStatusRequestMessage struct { - Height int64 -} -``` - -Remark: - -- `msg.Height` > 0 - -#### bcStatusResponseMessage - -```go -// type: "tendermint/blockchain/StatusResponse" -type bcStatusResponseMessage struct { - Height int64 -} -``` - -Remark: - -- `msg.Height` > 0 - -### Remote Functions - -Peers expose the following functions over -remote procedure calls. The "Expected precondition" are only expected for -correct peers (as no assumption is made on internals of faulty -processes [FS-A-PEER]). These functions are implemented using the above defined message types. - -> In this document we describe the communication with peers -via asynchronous RPCs. - -```go -func Status(addr Address) (int64, error) -``` - -- Implementation remark - - RPC to full node *addr* - - Request message: `bcStatusRequestMessage`. - - Response message: `bcStatusResponseMessage`. -- Expected precondition - - none -- Expected postcondition - - if *addr* is correct: Returns the current height `height` of the - peer. [FS-A-COMM] - - if *addr* is faulty: Returns an arbitrary height. [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] -- Error condition - - if *addr* is correct: none. By [FS-A-COMM] we assume communication is reliable and timely. - - if *addr* is faulty: arbitrary error (including timeout). [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] - ----- - - ```go -func Block(addr Address, height int64) (Block, error) -``` - -- Implementation remark - - RPC to full node *addr* - - Request message: `bcBlockRequestMessage`. - - Response message: `bcBlockResponseMessage` or `bcNoBlockResponseMessage`. -- Expected precondition - - 'height` is less than or equal to height of the peer -- Expected postcondition - - if *addr* is correct: Returns the block of height `height` - from the blockchain. [FS-A-COMM] - - if *addr* is faulty: Returns arbitrary or no block [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] -- Error condition - - if *addr* is correct: precondition violated (returns `bcNoBlockResponseMessage`). [FS-A-COMM] - - if *addr* is faulty: arbitrary error (including timeout). [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] - ----- - -## FastSync V2 - -### Outline - -The protocol is described in terms of functions that are triggered by -(external) events. The implementation uses a scheduler and a -de-multiplexer to deal with communicating with peers and to -trigger the execution of these functions: - -- `QueryStatus()`: regularly (currently every 10sec; necessarily - interval greater than *2 Delta*) queries all peers from *peerIDs* - for their current height [TMBC-CORR-FULL]. It does so - by calling `Status(n)` remotely on all peers *n*. - -- `CreateRequest`: regularly checks whether certain blocks have no - open request. If a block does not have an open request, it requests - one from a peer. It does so by calling `Block(n,h)` remotely on one - peer *n* for a missing height *h*. - -> We have left the strategy how peers are selected unspecified, and -> the currently existing different implementations of Fastsync differ -> in this aspect. In V2, a peer *p* is selected with the minimum number of -> pending requests that can serve the required height *h*, that is -> with *peerHeight(p) >= h*. - -The functions `Status` and `Block` are called by asynchronous -RPC. When they return, the following functions are called: - -- `OnStatusResponse(addr Address, height int64)`: The full node with - address *addr* returns its current height. The function updates the height - information about *addr*, and may also increase *TargetHeight*. - -- `OnBlockResponse(addr Address, b Block)`. The full node with - address *addr* returns a block. It is added to *blockstore*. Then - the auxiliary function `Execute` is called. - -- `Execute()`: Iterates over the *blockstore*. Checks soundness of - the blocks, and - executes the transactions of a sound block and updates *state*. - -> In addition to the functions above, the following two features are -> implemented in Fastsync V2 - -#### **[FS-V2-PEER-REMOVE]** - -Periodically, *peerTimeStamp* and *peerRate* and *pendingTime* are -analyzed. -If a peer *p* -has not provided a block recently (check of *peerTimeStamp[p]*) or it -has not provided sufficiently many data (check of *peerRate[p]*), then -*p* is removed from *peerIDs*. In addition, *pendingTime* is used to -estimate whether the peer that is responsible for the current height -has provided the corresponding block on time. - -#### **[FS-V2-TIMEOUT]** - -*Fastsync V2* starts a timeout whenever a block is -executed (that is, when the height is incremented). If the timeout expires -before the next block is executed, *Fastsync* terminates. -If this happens, then *Fastsync* terminates -with failure. - -### Details - - - -```go -func QueryStatus() -``` - -- Expected precondition - - peerIDs initialized and non-empty -- Expected postcondition - - call asynchronously `Status(n)` at each peer *n* in *peerIDs*. -- Error condition - - fails if precondition is violated - ----- - -```go -func OnStatusResponse(addr Address, ht int64) -``` - -- Comment - - *ht* is a height - - peers can provide the status without being called -- Expected precondition - - *peerHeights(addr) <= ht* -- Expected postcondition - - *peerHeights(addr) = ht* - - *TargetHeight* is updated -- Error condition - - if precondition is violated: *addr* not in *peerIDs* (that is, - *addr* is removed from *peerIDs*) -- Timeout condition - - if `OnStatusResponse(addr, ht)` was not invoked within *2 Delta* after - `Status(addr)` was called: *addr* not in *peerIDs* - ----- - -```go -func CreateRequest -``` - -- Expected precondition - - *height < TargetHeight* - - *peerIDs* nonempty -- Expected postcondition - - Function `Block` is called remotely at a peer *addr* in peerIDs - for a missing height *h* - *Remark:* different implementations may have different - strategies to balance the load over the peers - - *pendingblocks(h) = addr* - ----- - -```go -func OnBlockResponse(addr Address, b Block) -``` - -- Comment - - if after adding block *b*, blocks of heights *height* and - *height + 1* are in *blockstore*, then `Execute` is called -- Expected precondition - - *pendingblocks(b.Height) = addr* - - *b* satisfies basic soundness -- Expected postcondition - - if function `Execute` has been executed without error or was not - executed: - - *receivedBlocks(b.Height) = addr* - - *blockstore(b.Height) = b* - - *peerTimeStamp[addr]* is set to a time between invocation and - return of the function. - - *peerRate[addr]* is updated according to size of received - block and time it has passed between current time and last block received from this peer (addr) -- Error condition - - if precondition is violated: *addr* not in *peerIDs*; reset - *pendingblocks(b.Height)* to nil; -- Timeout condition - - if `OnBlockResponse(addr, b)` was not invoked within *2 Delta* after - `Block(addr,h)` was called for *b.Height = h*: *addr* not in *peerIDs* - ----- - -```go -func Execute() -``` - -- Comments - - none -- Expected precondition - - application state is the one of the blockchain at height - *height - 1* - - **[FS-V2-Verif]** for any two blocks *a* and *b* from - *receivedBlocks*: if - *a.Height + 1 = b.Height* then *VerifyCommit (a,b.Commit) = true* -- Expected postcondition - - Any two blocks *a* and *b* violating [FS-V2-Verif]: - *a* and *b* not in *blockstore*; nodes with Address - receivedBlocks(a.Height) and receivedBlocks(b.Height) not in peerIDs - - height is updated height of complete prefix that matches the blockchain - - state is the one of the blockchain at height *height - 1* - - if the new value of *height* is equal to *TargetHeight*, then - Fastsync - **terminates - successfully**. -- Error condition - - none - ----- - -## Algorithm Invariants - -> In contrast to the temporal properties above that define the problem -> statement, the following are invariants on the solution to the -> problem, that is on the algorithm. These invariants are useful for -> the verification, but can also guide the implementation. - -#### **[FS-VAR-STATE-INV]** - -It is always the case that *state* corresponds to the application state of the -blockchain of that height, that is, *state = chain[height - -1].AppState*; *chain* is defined in -[**[TMBC-SEQ]**][TMBC-SEQ-link]. - -#### **[FS-VAR-PEER-INV]** - -It is always the case that the set *peerIDs* only contains nodes that -have not yet misbehaved (by sending wrong data or timing out). - -#### **[FS-VAR-BLOCK-INV]** - -For *startBlock.Height <= i < height - 1*, let *b(i)* be the block with -height *i* in *blockstore*, it always holds that -*VerifyCommit(b(i), b(i+1).Commit) = true*. This means that *height* -can only be incremented if all blocks with lower height have been verified. - -# Part V - Analysis and Improvements - -## Analysis of Fastsync V2 - -#### **[FS-ISSUE-KILL]** - -If two blocks are not matching [FS-V2-Verif], `Execute` dismisses both -blocks and removes the peers that provided these blocks from -*peerIDs*. If block *a* was correct and provided by a correct peer *p*, -and block b was faulty and provided by a faulty peer, the protocol - -- removes the correct peer *p*, although it might be useful to - download blocks from it in the future -- removes the block *a*, so that a fresh copy of *a* needs to be downloaded - again from another peer - -By [FS-A-PEER] we do not put a restriction on the number - of faulty peers, so that faulty peers can make *FS* to remove all - correct peers from *peerIDs*. As a result, this version of - *Fastsync* violates [FS-DIST-SAFE-SYNC]. - -#### **[FS-ISSUE-NON-TERM]** - -Due to [**[FS-ISSUE-KILL]**](#fs-issue-kill), from some point on, only -faulty peers may be in *peerIDs*. They can thus control at which rate -*Fastsync* gets blocks. If the timeout duration from [FS-V2-TIMEOUT] -is greater than the time it takes to add a block to the blockchain -(LTIME in [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link]), the -protocol may never terminate and thus violate [FS-DIST-LIVE]. This -scenario is even possible if a correct peer is always in *peerIDs*, -but faulty peers are regularly asked for blocks. - -### Consequence - -The issues [FS-ISSUE-KILL] and [FS-ISSUE-NON-TERM] explain why -does not satisfy the property [FS-DIST-LIVE] relevant for termination. -As a result, V2 only solves the specifications in a restricted form, -namely, when all peers are correct: - -#### **[FS-ALL-CORR-PEER]** - -At all times, the set *peerIDs* contains only correct full nodes. - -With this restriction we can give the achieved properties: - -#### **[FS-VC-ALL-CORR-NONABORT]** - -Under [FS-ALL-CORR-PEER], *Fastsync* never terminates with failure. - -#### **[FS-VC-ALL-CORR-LIVE]** - -Under [FS-ALL-CORR-PEER], *Fastsync* eventually terminates successfully. - -> In a fault tolerance context this is problematic, -> as it means that faulty peers can prevent *FastSync* from termination. -> We observe that this also touches other properties, namely, -> [FS-DIST-SAFE-START] and [FS-DIST-SAFE-SYNC]: -> Termination at an acceptable height are all conditional under -> "successful termination". The properties above severely restrict -> under which circumstances FastSync (V2) terminates successfully. -> As a result, large parts of the current -> implementation of are not fault-tolerant. We will -> discuss this, and suggestions how to solve this after the -> description of the current protocol. - -## Suggestions for an Improved Fastsync Implementation - -### Solution for [FS-ISSUE-KILL] - -To avoid [FS-ISSUE-KILL], we observe that -[**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link] ensures that from the -point a block was created, we assume that more than two thirds of the -validator nodes are correct until the *trustingPeriod* expires. Under -this assumption, assume the trusting period of *startBlock* is not -expired by the time *FastSync* checks a block *b1* with height -*startBlock.Height + 1*. To do so, we first need to check whether the -Commit in the block *b2* with *startBlock.Height + 2* contains more -than 2/3 of the voting power in *startBlock.NextValidators*. If this -is the case we can check *VerifyCommit (b1,b2.Commit)*. If we perform -checks in this order we observe: - -- By assumption, *startBlock* is OK, -- If the first check (2/3 of voting power) fails, - the peer that provided block *b2* is faulty, -- If the first check passes and the second check - fails (*VerifyCommit*), then the peer that provided *b1* is - faulty. -- If both checks pass, we can trust *b1* - -Based on this reasoning, we can ensure to only remove faulty peers -from *peerIDs*. That is, if -we sequentially verify blocks starting with *startBlock*, we will -never remove a correct peer from *peerIDs* and we will be able to -ensure the following invariant: - -#### **[NewFS-VAR-PEER-INV]** - -If a peer never misbehaves, it is never removed from *peerIDs*. It -follows that under [FS-SOME-CORR-PEER], *peerIDs* is always non-empty. - -> To ensure this, we suggest to change the protocol as follows: - -#### Fastsync has the following configuration parameters - -- *trustingPeriod*: a time duration; cf. - [**[TMBC-TIME-PARAMS]**][TMBC-TIME-PARAMS-link]. - -> [NewFS-A-INIT] is the suggested replacement of [FS-A-V2-INIT]. This will -> allow us to use the established trust to understand precisely which -> peer reported an invalid block in order to ensure the -> invariant [NewFS-VAR-TRUST-INV] below: - -#### **[NewFS-A-INIT]** - -- *startBlock* is from the blockchain, and within *trustingPeriod* -(possible with some extra margin to ensure termination before -*trustingPeriod* expired) -- *startState* is the application state of the blockchain at Height - *startBlock.Height*. -- *startHeight = startBlock.Height* - -#### Additional Variables - -- *trustedBlockstore*: stores for each height greater than or equal to - *startBlock.Height*, the block of that height. Initially it - contains only *startBlock* - -#### **[NewFS-VAR-TRUST-INV]** - -Let *b(i)* be the block in *trustedBlockstore* -with b(i).Height = i. It holds that -for *startHeight < i < height - 1*, -*VerifyCommit (b(i),b(i+1).Commit) = true*. - -> We propose to update the function `Execute`. To do so, we first -> define the following helper functions: - -```go -func ValidCommit(VS ValidatorSet, C Commit) Boolean -``` - -- Comments - - checks validator set based on [**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link] -- Expected precondition - - The validators in *C* - - are a subset of VS - - have more than 2/3 of the voting power in VS -- Expected postcondition - - returns *true* if precondition holds, and *false* otherwise -- Error condition - - none - ----- - -```go -func SequentialVerify { - while (true) { - b1 = blockstore[height]; - b2 = blockstore[height+1]; - if b1 == nil or b2 == nil { - exit; - } - if ValidCommit(trustedBlockstore[height - 1].NextValidators, b2.commit) { - // we trust b2 - if VerifyCommit(b1, b2.commit) { - trustedBlockstore.Add(b1); - height = height + 1; - } - else { - // as we trust b2, b1 must be faulty - blockstore.RemoveFromPeer(receivedBlocks[height]); - // we remove all blocks received from the faulty peer - peerIDs.Remove(receivedBlocks(bnew.Height)); - exit; - - } - } else { - // b2 is faulty - blockstore.RemoveFromPeer(receivedBlocks[height + 1]); - // we remove all blocks received from the faulty peer - peerIDs.Remove(receivedBlocks(bnew.Height)); - exit; } - } -} -``` - -- Comments - - none -- Expected precondition - - [NewFS-VAR-TRUST-INV] -- Expected postcondition - - [NewFS-VAR-TRUST-INV] - - there is no block *bnew* with *bnew.Height = height + 1* in - *blockstore* -- Error condition - - none - ----- - -> Then `Execute` just consists in calling `SequentialVerify` and then -> updating the application state to the (new) height. - -```go -func Execute() -``` - -- Comments - - first `SequentialVerify` is executed -- Expected precondition - - application state is the one of the blockchain at height - *height - 1* - - [NewFS-NOT-EXP] *trustedBlockstore[height-1].Time > now - trustingPeriod* -- Expected postcondition - - there is no block *bnew* with *bnew.Height = height + 1* in - *blockstore* - - state is the one of the blockchain at height *height - 1* - - if height = TargetHeight: **terminate successfully** -- Error condition - - fails if [NewFS-NOT-EXP] is violated - ----- - -### Solution for [FS-ISSUE-NON-TERM] - -As discussed above, the advantageous termination requirement is the -combination of [FS-DIST-LIVE] and [FS-DIST-NONABORT], that is, *Fastsync* -should terminate successfully in case there is at least one correct -peer in *peerIDs*. For this we have to ensure that faulty processes -cannot slow us down and provide blocks at a lower rate than the -blockchain may grow. To ensure that we will have to add an assumption -on message delays. - -#### **[NewFS-A-DELTA]** - -*2 Delta < ETIME*; cf. [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link]. - -> This assumption implies that the timeouts for `OnBlockResponse` and -> `OnStatusResponse` are such that a faulty peer that tries to respond -> slower than *2 Delta* will be removed. In the following we will -> provide a rough estimate on termination time in a fault-prone -> scenario. -> In the following -> we assume that during a "long enough" finite good period no new -> faulty peers are added to *peerIDs*. Below we will sketch how "long -> enough" can be estimated based on the timing assumption in this -> specification. - -#### **[NewFS-A-STATUS-INTERVAL]** - -Let Sigma be the (upper bound on the) -time between two calls of `QueryStatus()`. - -#### **[NewFS-A-GOOD-PERIOD]** - -A time interval *[begin,end]* is *good period* if: - -- *fmax* is the number of faulty peers in *peerIDs* at time *begin* -- *end >= begin + 2 Delta (fmax + 3)* -- no faulty peer is added before time *end* - -> In the analysis below we assume that the termination condition of -> *Fastsync* is -> *height = TargetHeight* in the postcondition of -> `Execute`. Therefore, [NewFS-A-STATUS-INTERVAL] does not interfere -> with this analysis. If a correct peer reports a new height "shortly -> before termination" this leads to an additional round trip to -> request and add the new block. Then [NewFS-A-DELTA] ensures that -> *Fastsync* catches up. - -Arguments: - -1. If a faulty peer *p* reports a faulty block, `SequentialVerify` will - eventually remove *p* from *peerIDs* - -2. By `SequentialVerify`, if a faulty peer *p* reports multiple faulty - blocks, *p* will be removed upon trying to check the block with the - smallest height received from *p*. - -3. Assume whenever a block does not have an open request, `CreateRequest` is - called immediately, which calls `Block(n)` on a peer. Say this - happens at time *t*. There are two cases: - - - by t + 2 Delta a block is added to *blockStore* - - at t + 2 Delta `Block(n)` timed out and *n* is removed from - peer. - -4. Let *f(t)* be the number of faulty peers in *peerIDs* at time *t*; - *f(begin) = fmax*. - -5. Let t_i be the sequence of times `OnBlockResponse(addr,b)` is - invoked or times out with *b.Height = height + 1*. - -6. By 3., - - (a). *t_1 <= begin + 2 Delta* - - (b). *t_{i+1} <= t_i + 2 Delta* - -7. By an inductive argument we prove for *i > 0* that - - - (a). *height(t_{i+1}) > height(t_i)*, or - - (b). *f(t_{i+1}) < f(t_i))* and *height(t_{i+1}) = height(t_i)* - - Argument: if the peer is faulty and does not return a block, the - peer is removed, if it is faulty and returns a faulty block - `SequentialVerify` removes the peer (b). If the returned block is OK, - height is increased (a). - -8. By 2. and 7., faulty peers can delay incrementing the height at - most *fmax* times, where each time "costs" *2 Delta* seconds. We - have additional *2 Delta* initial offset (3a) plus *2 Delta* to get - all missing blocks after the last fault showed itself. (This - assumes that an arbitrary number of blocks can be obtained and - checked within one round-trip 2 Delta; which either needs - conservative estimation of Delta, or a more refined analysis). Thus - we reach the *targetHeight* and terminate by time *end*. - -# References - - - -[[block]] Specification of the block data structure. - - - -[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md - - - -[TMBC-HEADER-link]: #tmbc-header - -[TMBC-SEQ-link]: #tmbc-seq - -[TMBC-CORR-FULL-link]: #tmbc-corrfull - -[TMBC-CORRECT-link]: #tmbc-correct - -[TMBC-Sign-link]: #tmbc-sign - -[TMBC-FaultyFull-link]: #tmbc-faultyfull - -[TMBC-TIME-PARAMS-link]: #tmbc-time-params - -[TMBC-SEQ-APPEND-E-link]: #tmbc-seq-append-e - -[TMBC-FM-2THIRDS-link]: #tmbc-fm-2thirds - -[TMBC-Auth-Byz-link]: #tmbc-auth-byz - -[TMBC-INV-SIGN-link]: #tmbc-inv-sign - -[TMBC-SOUND-DISTR-PossCommit--link]: #tmbc-sound-distr-posscommit - -[TMBC-INV-VALID-link]: #tmbc-inv-valid - - - - - - - - - - - - - - - - - - - - - - - - - -[LCV-VC-LIVE-link]: https://github.com/informalsystems/VDD/tree/master/lightclient/verification.md#lcv-vc-live - -[lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md - -[failuredetector]: https://github.com/informalsystems/VDD/blob/master/liteclient/failuredetector.md - -[fullnode]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md - -[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/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.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 diff --git a/rust-spec/fastsync/fastsync.tla b/rust-spec/fastsync/fastsync.tla deleted file mode 100644 index e1b7b812b..000000000 --- a/rust-spec/fastsync/fastsync.tla +++ /dev/null @@ -1,825 +0,0 @@ ------------------------------ MODULE fastsync ----------------------------- -(* - In this document we give the high level specification of the fast sync - protocol as implemented here: - https://github.com/tendermint/tendermint/tree/master/blockchain/v2. - -We assume a system in which one node is trying to sync with the blockchain -(replicated state machine) by downloading blocks from the set of full nodes -(we call them peers) that are block providers, and executing transactions -(part of the block) against the application. - -Peers can be faulty, and we don't make any assumption about the rate of correct/faulty -nodes in the node peerset (i.e., they can all be faulty). Correct peers are part -of the replicated state machine, i.e., they manage blockchain and execute -transactions against the same deterministic application. We don't make any -assumptions about the behavior of faulty processes. Processes (client and peers) -communicate by message passing. - - In this specification, we model this system with two parties: - - the node (state machine) that is doing fastsync and - - the environment with which node interacts. - -The environment consists of the set of (correct and faulty) peers with -which node interacts as part of fast sync protocol, but also contains some -aspects (adding/removing peers, timeout mechanism) that are part of the node -local environment (could be seen as part of the runtime in which node -executes). - -As part of the fast sync protocol a node and the peers exchange the following messages: - -- StatusRequest -- StatusResponse -- BlockRequest -- BlockResponse -- NoBlockResponse. - -A node is periodically issuing StatusRequests to query peers for their current height (to decide what -blocks to ask from what peers). Based on StatusResponses (that are sent by peers), the node queries -blocks for some height(s) by sending peers BlockRequest messages. A peer provides a requested block by -BlockResponse message. If a peer does not want to provide a requested block, then it sends NoBlockResponse message. -In addition to those messages, a node in this spec receives additional input messages (events): - -- AddPeer -- RemovePeer -- SyncTimeout. - -These are the control messages that are provided to the node by its execution enviornment. AddPeer -is for the case when a connection is established with a peer; similarly RemovePeer is for the case -a connection with the peer is terminated. Finally SyncTimeout is used to model a timeout trigger. - -We assume that fast sync protocol starts when connections with some number of peers -are established. Therefore, peer set is initialised with non-empty set of peer ids. Note however -that node does not know initially the peer heights. -*) - -EXTENDS Integers, FiniteSets, Sequences - - -CONSTANTS MAX_HEIGHT, \* the maximal height of blockchain - VALIDATOR_SETS, \* abstract set of validators - NIL_VS, \* a nil validator set - CORRECT, \* set of correct peers - FAULTY, \* set of faulty peers - TARGET_PENDING, \* maximum number of pending requests + downloaded blocks that are not yet processed - PEER_MAX_REQUESTS \* maximum number of pending requests per peer - -ASSUME CORRECT \intersect FAULTY = {} -ASSUME TARGET_PENDING > 0 -ASSUME PEER_MAX_REQUESTS > 0 - -\* the blockchain, see Tinychain -VARIABLE chain - -\* introduce tiny chain as the source of blocks for the correct nodes -INSTANCE Tinychain - -\* a special value for an undefined height -NilHeight == 0 - -\* the height of the genesis block -TrustedHeight == 1 - -\* the set of all peer ids the node can receive a message from -AllPeerIds == CORRECT \union FAULTY - -\* Correct last commit have enough voting power, i.e., +2/3 of the voting power of -\* the corresponding validator set (enoughVotingPower = TRUE) that signs blockId. -\* BlockId defines correct previous block (in the implementation it is the hash of the block). -\* Instead of blockId, we encode blockIdEqRef, which is true, if the block id is equal -\* to the hash of the previous block, see Tinychain. -CorrectLastCommit(h) == chain[h].lastCommit - -NilCommit == [blockIdEqRef |-> FALSE, committers |-> NIL_VS] - -\* correct node always supplies the blocks from the blockchain -CorrectBlock(h) == chain[h] - -NilBlock == - [height |-> 0, hashEqRef |-> FALSE, wellFormed |-> FALSE, - lastCommit |-> NilCommit, VS |-> NIL_VS, NextVS |-> NIL_VS] - -\* a special value for an undefined peer -NilPeer == "Nil" \* STRING for apalache efficiency - -\* control the state of the syncing node -States == { "running", "finished"} - -NoMsg == [type |-> "None"] - -\* the variables of the node running fastsync -VARIABLES - state, \* running or finished - (* - blockPool [ - height, \* current height we are trying to sync. Last block executed is height - 1 - peerIds, \* set of peers node is connected to - peerHeights, \* map of peer ids to its (stated) height - blockStore, \* map of heights to (received) blocks - receivedBlocks, \* map of heights to peer that has sent us the block (stored in blockStore) - pendingBlocks, \* map of heights to peer to which block request has been sent - syncHeight, \* height at the point syncTimeout was triggered last time - syncedBlocks \* number of blocks synced since last syncTimeout. If it is 0 when the next timeout occurs, then protocol terminates. - ] - *) - blockPool - - -\* the variables of the peers providing blocks -VARIABLES - (* - peersState [ - peerHeights, \* track peer heights - statusRequested, \* boolean set to true when StatusRequest is received. Models periodic sending of StatusRequests. - blocksRequested \* set of BlockRequests received that are not answered yet - ] - *) - peersState - - \* the variables for the network and scheduler -VARIABLES - turn, \* who is taking the turn: "Peers" or "Node" - inMsg, \* a node receives message by this variable - outMsg \* a node sends a message by this variable - - -(* the variables of the node *) -nvars == <> - -(*************** Type definitions for Apalache (model checker) **********************) -AsIntSet(S) == S <: {Int} - -\* type of process ids -PIDT == STRING -AsPidSet(S) == S <: {PIDT} - -\* ControlMessage type -CMT == [type |-> STRING, peerId |-> PIDT] \* type of control messages - -\* InMsg type -IMT == [type |-> STRING, peerId |-> PIDT, height |-> Int, block |-> BT] -AsInMsg(m) == m <: IMT -AsInMsgSet(S) == S <: {IMT} - -\* OutMsg type -OMT == [type |-> STRING, peerId |-> PIDT, height |-> Int] -AsOutMsg(m) == m <: OMT -AsOutMsgSet(S) == S <: {OMT} - -\* block pool type -BPT == [height |-> Int, peerIds |-> {PIDT}, peerHeights |-> [PIDT -> Int], - blockStore |-> [Int -> BT], receivedBlocks |-> [Int -> PIDT], - pendingBlocks |-> [Int -> PIDT], syncedBlocks |-> Int, syncHeight |-> Int] - -AsBlockPool(bp) == bp <: BPT - -(******************** Sets of messages ********************************) - -\* Control messages -ControlMsgs == - AsInMsgSet([type: {"addPeer"}, peerId: AllPeerIds]) - \union - AsInMsgSet([type: {"removePeer"}, peerId: AllPeerIds]) - \union - AsInMsgSet([type: {"syncTimeout"}]) - -\* All messages (and events) received by a node -InMsgs == - AsInMsgSet({NoMsg}) - \union - AsInMsgSet([type: {"blockResponse"}, peerId: AllPeerIds, block: Blocks]) - \union - AsInMsgSet([type: {"noBlockResponse"}, peerId: AllPeerIds, height: Heights]) - \union - AsInMsgSet([type: {"statusResponse"}, peerId: AllPeerIds, height: Heights]) - \union - ControlMsgs - -\* Messages sent by a node and received by peers (environment in our case) -OutMsgs == - AsOutMsgSet({NoMsg}) - \union - AsOutMsgSet([type: {"statusRequest"}]) \* StatusRequest is broadcast to the set of connected peers. - \union - AsOutMsgSet([type: {"blockRequest"}, peerId: AllPeerIds, height: Heights]) - - -(********************************** NODE ***********************************) - -InitNode == - \E pIds \in SUBSET AllPeerIds: \* set of peers node established initial connections with - /\ pIds \subseteq CORRECT \* this line is not necessary - /\ pIds /= AsPidSet({}) \* apalache better checks non-emptiness than subtracts from SUBSET - /\ blockPool = AsBlockPool([ - height |-> TrustedHeight + 1, \* the genesis block is at height 1 - syncHeight |-> TrustedHeight + 1, \* and we are synchronized to it - peerIds |-> pIds, - peerHeights |-> [p \in AllPeerIds |-> NilHeight], \* no peer height is known - blockStore |-> - [h \in Heights |-> - IF h > TrustedHeight THEN NilBlock ELSE chain[1]], - receivedBlocks |-> [h \in Heights |-> NilPeer], - pendingBlocks |-> [h \in Heights |-> NilPeer], - syncedBlocks |-> -1 - ]) - /\ state = "running" - -\* Remove faulty peers. -\* Returns new block pool. -\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L222 -RemovePeers(rmPeers, bPool) == - LET keepPeers == bPool.peerIds \ rmPeers IN - LET pHeights == - [p \in AllPeerIds |-> IF p \in rmPeers THEN NilHeight ELSE bPool.peerHeights[p]] IN - - LET failedRequests == - {h \in Heights: /\ h >= bPool.height - /\ \/ bPool.pendingBlocks[h] \in rmPeers - \/ bPool.receivedBlocks[h] \in rmPeers} IN - LET pBlocks == - [h \in Heights |-> IF h \in failedRequests THEN NilPeer ELSE bPool.pendingBlocks[h]] IN - LET rBlocks == - [h \in Heights |-> IF h \in failedRequests THEN NilPeer ELSE bPool.receivedBlocks[h]] IN - LET bStore == - [h \in Heights |-> IF h \in failedRequests THEN NilBlock ELSE bPool.blockStore[h]] IN - - IF keepPeers /= bPool.peerIds - THEN [bPool EXCEPT - !.peerIds = keepPeers, - !.peerHeights = pHeights, - !.pendingBlocks = pBlocks, - !.receivedBlocks = rBlocks, - !.blockStore = bStore - ] - ELSE bPool - -\* Add a peer. -\* see https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L198 -AddPeer(peer, bPool) == - [bPool EXCEPT !.peerIds = bPool.peerIds \union {peer}] - - -(* -Handle StatusResponse message. -If valid status response, update peerHeights. -If invalid (height is smaller than the current), then remove peer. -Returns new block pool. -See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L667 -*) -HandleStatusResponse(msg, bPool) == - LET peerHeight == bPool.peerHeights[msg.peerId] IN - - IF /\ msg.peerId \in bPool.peerIds - /\ msg.height >= peerHeight - THEN \* a correct response - LET pHeights == [bPool.peerHeights EXCEPT ![msg.peerId] = msg.height] IN - [bPool EXCEPT !.peerHeights = pHeights] - ELSE RemovePeers({msg.peerId}, bPool) \* the peer has sent us message with smaller height or peer is not in our peer list - - -(* -Handle BlockResponse message. -If valid block response, update blockStore, pendingBlocks and receivedBlocks. -If invalid (unsolicited response or malformed block), then remove peer. -Returns new block pool. -See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L522 -*) -HandleBlockResponse(msg, bPool) == - LET h == msg.block.height IN - - IF /\ msg.peerId \in bPool.peerIds - /\ bPool.blockStore[h] = NilBlock - /\ bPool.pendingBlocks[h] = msg.peerId - /\ msg.block.wellFormed - THEN - [bPool EXCEPT - !.blockStore = [bPool.blockStore EXCEPT ![h] = msg.block], - !.receivedBlocks = [bPool.receivedBlocks EXCEPT![h] = msg.peerId], - !.pendingBlocks = [bPool.pendingBlocks EXCEPT![h] = NilPeer] - ] - ELSE RemovePeers({msg.peerId}, bPool) - - HandleNoBlockResponse(msg, bPool) == - RemovePeers({msg.peerId}, bPool) - - -\* Compute max peer height. -\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L440 -MaxPeerHeight(bPool) == - IF bPool.peerIds = AsPidSet({}) - THEN 0 \* no peers, just return 0 - ELSE LET Hts == {bPool.peerHeights[p] : p \in bPool.peerIds} IN - CHOOSE max \in Hts: \A h \in Hts: h <= max - -(* Returns next height for which request should be sent. - Returns NilHeight in case there is no height for which request can be sent. - See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L454 *) -FindNextRequestHeight(bPool) == - LET S == {i \in Heights: - /\ i >= bPool.height - /\ i <= MaxPeerHeight(bPool) - /\ bPool.blockStore[i] = NilBlock - /\ bPool.pendingBlocks[i] = NilPeer} IN - IF S = AsIntSet({}) - THEN NilHeight - ELSE - CHOOSE min \in S: \A h \in S: h >= min - -\* Returns number of pending requests for a given peer. -NumOfPendingRequests(bPool, peer) == - LET peerPendingRequests == - {h \in Heights: - /\ h >= bPool.height - /\ bPool.pendingBlocks[h] = peer - } - IN - Cardinality(peerPendingRequests) - -(* Returns peer that can serve block for a given height. - Returns NilPeer in case there are no such peer. - See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L477 *) -FindPeerToServe(bPool, h) == - LET peersThatCanServe == { p \in bPool.peerIds: - /\ bPool.peerHeights[p] >= h - /\ NumOfPendingRequests(bPool, p) < PEER_MAX_REQUESTS } IN - - LET pendingBlocks == - {i \in Heights: - /\ i >= bPool.height - /\ \/ bPool.pendingBlocks[i] /= NilPeer - \/ bPool.blockStore[i] /= NilBlock - } IN - - IF \/ peersThatCanServe = AsPidSet({}) - \/ Cardinality(pendingBlocks) >= TARGET_PENDING - THEN NilPeer - \* pick a peer that can serve request for height h that has minimum number of pending requests - ELSE CHOOSE p \in peersThatCanServe: \A q \in peersThatCanServe: - /\ NumOfPendingRequests(bPool, p) <= NumOfPendingRequests(bPool, q) - - -\* Make a request for a block (if possible) and return a request message and block poool. -CreateRequest(bPool) == - LET nextHeight == FindNextRequestHeight(bPool) IN - - IF nextHeight = NilHeight THEN [msg |-> AsOutMsg(NoMsg), pool |-> bPool] - ELSE - LET peer == FindPeerToServe(bPool, nextHeight) IN - IF peer = NilPeer THEN [msg |-> AsOutMsg(NoMsg), pool |-> bPool] - ELSE - LET m == [type |-> "blockRequest", peerId |-> peer, height |-> nextHeight] IN - LET newPool == [bPool EXCEPT - !.pendingBlocks = [bPool.pendingBlocks EXCEPT ![nextHeight] = peer] - ] IN - [msg |-> m, pool |-> newPool] - - -\* Returns node state, i.e., defines termination condition. -\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L432 -ComputeNextState(bPool) == - IF bPool.syncedBlocks = 0 \* corresponds to the syncTimeout in case no progress has been made for a period of time. - THEN "finished" - ELSE IF /\ bPool.height > 1 - /\ bPool.height >= MaxPeerHeight(bPool) \* see https://github.com/tendermint/tendermint/blob/61057a8b0af2beadee106e47c4616b279e83c920/blockchain/v2/scheduler.go#L566 - THEN "finished" - ELSE "running" - -(* Verify if commit is for the given block id and if commit has enough voting power. - See https://github.com/tendermint/tendermint/blob/61057a8b0af2beadee106e47c4616b279e83c920/blockchain/v2/processor_context.go#L12 *) -VerifyCommit(block, lastCommit) == - PossibleCommit(block, lastCommit) - -(* Tries to execute next block in the pool, i.e., defines block validation logic. - Returns new block pool (peers that has send invalid blocks are removed). - See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/processor.go#L135 *) -ExecuteBlocks(bPool) == - LET bStore == bPool.blockStore IN - LET block0 == bStore[bPool.height - 1] IN - \* blockPool is initialized with height = TrustedHeight + 1, - \* so bStore[bPool.height - 1] is well defined - LET block1 == bStore[bPool.height] IN - LET block2 == bStore[bPool.height + 1] IN - - IF block1 = NilBlock \/ block2 = NilBlock - THEN bPool \* we don't have two next consecutive blocks - - ELSE IF ~IsMatchingValidators(block1, block0.NextVS) - \* Check that block1.VS = block0.Next. - \* Otherwise, CorrectBlocksInv fails. - \* In the implementation NextVS is part of the application state, - \* so a mismatch can be found without access to block0.NextVS. - THEN \* the block does not have the expected validator set - RemovePeers({bPool.receivedBlocks[bPool.height]}, bPool) - ELSE IF ~VerifyCommit(block1, block2.lastCommit) - \* Verify commit of block2 based on block1. - \* Interestingly, we do not have to call IsMatchingValidators. - THEN \* remove the peers of block1 and block2, as they are considered faulty - RemovePeers({bPool.receivedBlocks[bPool.height], - bPool.receivedBlocks[bPool.height + 1]}, - bPool) - ELSE \* all good, execute block at position height - [bPool EXCEPT !.height = bPool.height + 1] - - -\* Defines logic for pruning peers. -\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L613 -TryPrunePeer(bPool, suspectedSet, isTimedOut) == - (* -----------------------------------------------------------------------------------------------------------------------*) - (* Corresponds to function prunablePeers in scheduler.go file. Note that this function only checks if block has been *) - (* received from a peer during peerTimeout period. *) - (* Note that in case no request has been scheduled to a correct peer, or a request has been scheduled *) - (* recently, so the peer hasn't responded yet, a peer will be removed as no block is received within peerTimeout. *) - (* In case of faulty peers, we don't have any guarantee that they will respond. *) - (* Therefore, we model this with nondeterministic behavior as it could lead to peer removal, for both correct and faulty. *) - (* See scheduler.go *) - (* https://github.com/tendermint/tendermint/blob/4298bbcc4e25be78e3c4f21979d6aa01aede6e87/blockchain/v2/scheduler.go#L335 *) - LET toRemovePeers == bPool.peerIds \intersect suspectedSet IN - - (* - Corresponds to logic for pruning a peer that is responsible for delivering block for the next height. - The pruning logic for the next height is based on the time when a BlockRequest is sent. Therefore, if a request is sent - to a correct peer for the next height (blockPool.height), it should never be removed by this check as we assume that - correct peers respond timely and reliably. However, if a request is sent to a faulty peer then we - might get response on time or not, which is modelled with nondeterministic isTimedOut flag. - See scheduler.go - https://github.com/tendermint/tendermint/blob/4298bbcc4e25be78e3c4f21979d6aa01aede6e87/blockchain/v2/scheduler.go#L617 - *) - LET nextHeightPeer == bPool.pendingBlocks[bPool.height] IN - LET prunablePeers == - IF /\ nextHeightPeer /= NilPeer - /\ nextHeightPeer \in FAULTY - /\ isTimedOut - THEN toRemovePeers \union {nextHeightPeer} - ELSE toRemovePeers - IN - RemovePeers(prunablePeers, bPool) - - -\* Handle SyncTimeout. It models if progress has been made (height has increased) since the last SyncTimeout event. -HandleSyncTimeout(bPool) == - [bPool EXCEPT - !.syncedBlocks = bPool.height - bPool.syncHeight, - !.syncHeight = bPool.height - ] - -HandleResponse(msg, bPool) == - IF msg.type = "blockResponse" THEN - HandleBlockResponse(msg, bPool) - ELSE IF msg.type = "noBlockResponse" THEN - HandleNoBlockResponse(msg, bPool) - ELSE IF msg.type = "statusResponse" THEN - HandleStatusResponse(msg, bPool) - ELSE IF msg.type = "addPeer" THEN - AddPeer(msg.peerId, bPool) - ELSE IF msg.type = "removePeer" THEN - RemovePeers({msg.peerId}, bPool) - ELSE IF msg.type = "syncTimeout" THEN - HandleSyncTimeout(bPool) - ELSE - bPool - - -(* - At every node step we executed the following steps (atomically): - 1) input message is consumed and the corresponding handler is called, - 2) pruning logic is called - 3) block execution is triggered (we try to execute block at next height) - 4) a request to a peer is made (if possible) and - 5) we decide if termination condition is satisifed so we stop. -*) -NodeStep == - \E suspectedSet \in SUBSET AllPeerIds: \* suspectedSet is a nondeterministic set of peers - \E isTimedOut \in BOOLEAN: - LET bPool == HandleResponse(inMsg, blockPool) IN - LET bp == TryPrunePeer(bPool, suspectedSet, isTimedOut) IN - LET nbPool == ExecuteBlocks(bp) IN - LET msgAndPool == CreateRequest(nbPool) IN - LET nstate == ComputeNextState(msgAndPool.pool) IN - - /\ state' = nstate - /\ blockPool' = msgAndPool.pool - /\ outMsg' = msgAndPool.msg - /\ inMsg' = AsInMsg(NoMsg) - - -\* If node is running, then in every step we try to create blockRequest. -\* In addition, input message (if exists) is consumed and processed. -NextNode == - \/ /\ state = "running" - /\ NodeStep - - \/ /\ state = "finished" - /\ UNCHANGED <> - - -(********************************** Peers ***********************************) - -InitPeers == - \E pHeights \in [AllPeerIds -> Heights]: - peersState = [ - peerHeights |-> pHeights, - statusRequested |-> FALSE, - blocksRequested |-> AsOutMsgSet({}) - ] - -HandleStatusRequest(msg, pState) == - [pState EXCEPT - !.statusRequested = TRUE - ] - -HandleBlockRequest(msg, pState) == - [pState EXCEPT - !.blocksRequested = pState.blocksRequested \union AsOutMsgSet({msg}) - ] - -HandleRequest(msg, pState) == - IF msg = AsOutMsg(NoMsg) - THEN pState - ELSE IF msg.type = "statusRequest" - THEN HandleStatusRequest(msg, pState) - ELSE HandleBlockRequest(msg, pState) - -CreateStatusResponse(peer, pState, anyHeight) == - LET m == - IF peer \in CORRECT - THEN AsInMsg([type |-> "statusResponse", peerId |-> peer, height |-> pState.peerHeights[peer]]) - ELSE AsInMsg([type |-> "statusResponse", peerId |-> peer, height |-> anyHeight]) IN - - [msg |-> m, peers |-> pState] - -CreateBlockResponse(msg, pState, arbitraryBlock) == - LET m == - IF msg.peerId \in CORRECT - THEN AsInMsg([type |-> "blockResponse", peerId |-> msg.peerId, block |-> CorrectBlock(msg.height)]) - ELSE AsInMsg([type |-> "blockResponse", peerId |-> msg.peerId, block |-> arbitraryBlock]) IN - LET npState == - [pState EXCEPT - !.blocksRequested = pState.blocksRequested \ {msg} - ] IN - [msg |-> m, peers |-> npState] - -GrowPeerHeight(pState) == - \E p \in CORRECT: - /\ pState.peerHeights[p] < MAX_HEIGHT - /\ peersState' = [pState EXCEPT !.peerHeights[p] = @ + 1] - /\ inMsg' = AsInMsg(NoMsg) - -SendStatusResponseMessage(pState) == - /\ \E arbitraryHeight \in Heights: - \E peer \in AllPeerIds: - LET msgAndPeers == CreateStatusResponse(peer, pState, arbitraryHeight) IN - /\ peersState' = msgAndPeers.peers - /\ inMsg' = msgAndPeers.msg - - -SendAddPeerMessage == - \E peer \in AllPeerIds: - inMsg' = AsInMsg([type |-> "addPeer", peerId |-> peer]) - -SendRemovePeerMessage == - \E peer \in AllPeerIds: - inMsg' = AsInMsg([type |-> "removePeer", peerId |-> peer]) - -SendSyncTimeoutMessage == - inMsg' = AsInMsg([type |-> "syncTimeout"]) - - -SendControlMessage == - \/ SendAddPeerMessage - \/ SendRemovePeerMessage - \/ SendSyncTimeoutMessage - -\* An extremely important property of block hashes (blockId): -\* If the block hash coincides with the hash of the reference block, -\* then the blocks should be equal. -UnforgeableBlockId(height, block) == - block.hashEqRef => block = chain[height] - -\* A faulty peer cannot forge enough of the validators signatures. -\* In other words: If a commit contains enough signatures from the validators (in reality 2/3, in the model all), -\* then the blockID points to the block on the chain, encoded as block.lastCommit.blockIdEqRef being true -\* A more precise rule should have checked that the commiters have over 2/3 of the VS's voting power. -NoFork(height, block) == - (height > 1 /\ block.lastCommit.committers = chain[height - 1].VS) - => block.lastCommit.blockIdEqRef - -\* Can be block produced by a faulty peer, assuming it cannot generate forks (basic assumption of the protocol) -IsBlockByFaulty(height, block) == - /\ block.height = height - /\ UnforgeableBlockId(height, block) - /\ NoFork(height, block) - -SendBlockResponseMessage(pState) == - \* a response to a requested block: either by a correct, or by a faulty peer - \/ /\ pState.blocksRequested /= AsOutMsgSet({}) - /\ \E msg \in pState.blocksRequested: - \E block \in Blocks: - /\ IsBlockByFaulty(msg.height, block) - /\ LET msgAndPeers == CreateBlockResponse(msg, pState, block) IN - /\ peersState' = msgAndPeers.peers - /\ inMsg' = msgAndPeers.msg - - \* a faulty peer can always send an unsolicited block - \/ \E peerId \in FAULTY: - \E block \in Blocks: - /\ IsBlockByFaulty(block.height, block) - /\ peersState' = pState - /\ inMsg' = AsInMsg([type |-> "blockResponse", - peerId |-> peerId, block |-> block]) - -SendNoBlockResponseMessage(pState) == - /\ peersState' = pState - /\ inMsg' \in AsInMsgSet([type: {"noBlockResponse"}, peerId: FAULTY, height: Heights]) - - -SendResponseMessage(pState) == - \/ SendBlockResponseMessage(pState) - \/ SendNoBlockResponseMessage(pState) - \/ SendStatusResponseMessage(pState) - - -NextEnvStep(pState) == - \/ SendResponseMessage(pState) - \/ GrowPeerHeight(pState) - \/ SendControlMessage /\ peersState' = pState - \* note that we propagate pState that was missing in the previous version - - -\* Peers consume a message and update it's local state. It then makes a single step, i.e., it sends at most single message. -\* Message sent could be either a response to a request or faulty message (sent by faulty processes). -NextPeers == - LET pState == HandleRequest(outMsg, peersState) IN - /\ outMsg' = AsOutMsg(NoMsg) - /\ NextEnvStep(pState) - - -\* the composition of the node, the peers, the network and scheduler -Init == - /\ IsCorrectChain(chain) \* initialize the blockchain - /\ InitNode - /\ InitPeers - /\ turn = "Peers" - /\ inMsg = AsInMsg(NoMsg) - /\ outMsg = AsOutMsg([type |-> "statusRequest"]) - -Next == - IF turn = "Peers" - THEN - /\ NextPeers - /\ turn' = "Node" - /\ UNCHANGED <> - ELSE - /\ NextNode - /\ turn' = "Peers" - /\ UNCHANGED <> - - -FlipTurn == - turn' = - IF turn = "Peers" THEN - "Node" - ELSE - "Peers" - -\* Compute max peer height. Used as a helper operator in properties. -MaxCorrectPeerHeight(bPool) == - LET correctPeers == {p \in bPool.peerIds: p \in CORRECT} IN - IF correctPeers = AsPidSet({}) - THEN 0 \* no peers, just return 0 - ELSE LET Hts == {bPool.peerHeights[p] : p \in correctPeers} IN - CHOOSE max \in Hts: \A h \in Hts: h <= max - -\* properties to check -TypeOK == - /\ state \in States - /\ inMsg \in InMsgs - /\ outMsg \in OutMsgs - /\ turn \in {"Peers", "Node"} - /\ peersState \in [ - peerHeights: [AllPeerIds -> Heights \union {NilHeight}], - statusRequested: BOOLEAN, - blocksRequested: - SUBSET - [type: {"blockRequest"}, peerId: AllPeerIds, height: Heights] - - ] - /\ blockPool \in [ - height: Heights, - peerIds: SUBSET AllPeerIds, - peerHeights: [AllPeerIds -> Heights \union {NilHeight}], - blockStore: [Heights -> Blocks \union {NilBlock}], - receivedBlocks: [Heights -> AllPeerIds \union {NilPeer}], - pendingBlocks: [Heights -> AllPeerIds \union {NilPeer}], - syncedBlocks: Heights \union {NilHeight, -1}, - syncHeight: Heights - ] - -(* Incorrect synchronization: The last block may be never received *) -Sync1 == - [](state = "finished" => - blockPool.height >= MaxCorrectPeerHeight(blockPool)) - -Sync1AsInv == - state = "finished" => blockPool.height >= MaxCorrectPeerHeight(blockPool) - -(* Incorrect synchronization, as there may be a timeout *) -Sync2 == - \A p \in CORRECT: - \/ p \notin blockPool.peerIds - \/ [] (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) - -Sync2AsInv == - \A p \in CORRECT: - \/ p \notin blockPool.peerIds - \/ (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) - -(* Correct synchronization *) -Sync3 == - \A p \in CORRECT: - \/ p \notin blockPool.peerIds - \/ blockPool.syncedBlocks <= 0 \* timeout - \/ [] (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) - -Sync3AsInv == - \A p \in CORRECT: - \/ p \notin blockPool.peerIds - \/ blockPool.syncedBlocks <= 0 \* timeout - \/ (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) - -(* Naive termination *) -\* This property is violated, as the faulty peers may produce infinitely many responses -Termination == - WF_turn(FlipTurn) => <>(state = "finished") - -(* Termination by timeout: the protocol terminates, if there is a timeout *) -\* the precondition: fair flip turn and eventual timeout when no new blocks were synchronized -TerminationByTOPre == - /\ WF_turn(FlipTurn) - /\ <>(inMsg.type = "syncTimeout" /\ blockPool.height <= blockPool.syncHeight) - -TerminationByTO == - TerminationByTOPre => <>(state = "finished") - -(* The termination property when we only have correct peers *) -\* as correct peers may spam the node with addPeer, removePeer, and statusResponse, -\* we have to enforce eventual response (there are no queues in our spec) -CorrBlockResponse == - \A h \in Heights: - [](outMsg.type = "blockRequest" /\ outMsg.height = h - => <>(inMsg.type = "blockResponse" /\ inMsg.block.height = h)) - -\* a precondition for termination in presence of only correct processes -TerminationCorrPre == - /\ FAULTY = AsPidSet({}) - /\ WF_turn(FlipTurn) - /\ CorrBlockResponse - -\* termination when there are only correct processes -TerminationCorr == - TerminationCorrPre => <>(state = "finished") - -\* All synchronized blocks (but the last one) are exactly like in the reference chain -CorrectBlocksInv == - \/ state /= "finished" - \/ \A h \in 1..(blockPool.height - 1): - blockPool.blockStore[h] = chain[h] - -\* A false expectation that the protocol only finishes with the blocks -\* from the processes that had not been suspected in being faulty -SyncFromCorrectInv == - \/ state /= "finished" - \/ \A h \in 1..blockPool.height: - blockPool.receivedBlocks[h] \in blockPool.peerIds \union {NilPeer} - -\* A false expectation that a correct process is never removed from the set of peer ids. -\* A correct process may reply too late and then gets evicted. -CorrectNeverSuspectedInv == - CORRECT \subseteq blockPool.peerIds - -BlockPoolInvariant == - \A h \in Heights: - \* waiting for a block to arrive - \/ /\ blockPool.receivedBlocks[h] = NilPeer - /\ blockPool.blockStore[h] = NilBlock - \* valid block is received and is present in the store - \/ /\ blockPool.receivedBlocks[h] /= NilPeer - /\ blockPool.blockStore[h] /= NilBlock - /\ blockPool.pendingBlocks[h] = NilPeer - -(* a few simple properties that trigger counterexamples *) - -\* Shows execution in which peer set is empty -PeerSetIsNeverEmpty == blockPool.peerIds /= AsPidSet({}) - -\* Shows execution in which state = "finished" and MaxPeerHeight is not equal to 1 -StateNotFinished == - state /= "finished" \/ MaxPeerHeight(blockPool) = 1 - - -============================================================================= - -\*============================================================================= -\* Modification History -\* Last modified Fri May 29 20:41:53 CEST 2020 by igor -\* Last modified Thu Apr 16 16:57:22 CEST 2020 by zarkomilosevic -\* Created Tue Feb 04 10:36:18 CET 2020 by zarkomilosevic diff --git a/rust-spec/fastsync/scheduler.tla b/rust-spec/fastsync/scheduler.tla deleted file mode 100644 index 1de22e997..000000000 --- a/rust-spec/fastsync/scheduler.tla +++ /dev/null @@ -1,606 +0,0 @@ -------------------------------- MODULE scheduler ------------------------------- -(* - A specification of the fast sync scheduler that is introduced in blockchain/v2: - - https://github.com/tendermint/tendermint/tree/brapse/blockchain-v2-riri-reactor-2 - - The model includes: - - a scheduler that maintains the peers and blocks that it receives from the peers, and - - one environment simulating a correct peer - - This specification focuses on the events that are received and produced by the scheduler. - Communication between the scheduler and the other fastsync components is not specified. -*) - -EXTENDS Integers, FiniteSets - -\* the protocol parameters -CONSTANTS - PeerIDs, \* potential peer ids, a set of integers, e.g. 0..2 - ultimateHeight, \* the maximum height of the blockchain, an integer, e.g. 3 - numRequests \* the maximum number of requests made when scheduling new blocks, e.g. 2 - -\* a few definitions -None == -1 \* an undefined value -Heights == 0..ultimateHeight \* potential heights -noErr == "errNone" -Errors == { - noErr, "errPeerNotFound", "errDelRemovedPeer", "errAddDuplicatePeer", - "errUpdateRemovedPeer", "errAfterPeerRemove", "errBadPeer", "errBadPeerState", - "errProcessedBlockEv", "finished", "timeout", "errAddRemovedPeer", "errPeerNoBlock", "errBadBlockState"} - -PeerStates == {"peerStateUnknown", "peerStateNew", "peerStateReady", "peerStateRemoved"} - -BlockStates == { - "blockStateUnknown", "blockStateNew", "blockStatePending", - "blockStateReceived", "blockStateProcessed"} - -\* basic stuff -Min(a, b) == IF a < b THEN a ELSE b -Max(a, b) == IF a > b THEN a ELSE b - -\* the state of the scheduler: -VARIABLE turn \* who makes a step: the scheduler or the environment - -\* the state of the reactor: -VARIABLES inEvent, \* an event from the environment to the scheduler - envRunning \* a Boolean, negation of stopProcessing in the implementation - -\* the state of the scheduler: -VARIABLES outEvent, \* an event from the scheduler to the environment - scRunning - -\* the block pool: -VARIABLE scheduler - (* - scheduler is a record that contains: - height: Int, - height of the next block to collect - peers: PeerIDs, - the set of peers that have connected in the past, may include removed peers - peerHeights: [PeerIDs -> Heights], - a map to collect the peer heights, >0 if peer in ready state, None (-1) otherwise - peerStates: [PeerIDs -> PeerStates], - a map to record the peer states - blockStates: [Heights -> BlockStates] - a set of heights for which blocks are to be scheduled, pending or received - pendingBlocks: [Heights -> PeerIDs], - a set of heights for which blocks are to be scheduled or pending - receivedBlocks: [Heights -> PeerIDs], - a set of heights for which blocks were received but not yet processed - blocks: Heights, - the full set of blocks requested or downloaded by the scheduler - *) - -vars == <> - -\* for now just keep the height in the block -Blocks == [ height: Heights ] - -noEvent == [type |-> "NoEvent"] - -InEvents == - {noEvent} \cup - [type: {"rTrySchedule", "tNoAdvanceExp"}] \cup - [type: {"bcStatusResponse"}, peerID: PeerIDs, height: Heights] \cup - [type: {"bcBlockResponse"}, peerID: PeerIDs, height: Heights, block: Blocks] \cup - [type: {"bcNoBlockResponse"}, peerID: PeerIDs, height: Heights] \cup - [type: {"pcBlockProcessed"}, peerID: PeerIDs, height: Heights] \cup - [type: {"pcBlockVerificationFailure"}, height: Heights, firstPeerID: PeerIDs, secondPeerID: PeerIDs] \cup - [type: {"bcAddNewPeer"}, peerID: PeerIDs] \cup - [type: {"bcRemovePeer"}, peerID: PeerIDs] - -\* Output events produced by the scheduler. -\* Note: in v2 the status request is done by the reactor/ environment -OutEvents == - {noEvent} \cup - [type: {"scPeerError"}, peerID: PeerIDs, error: Errors] \cup - [type: {"scSchedulerFail"}, error: Errors] \cup - [type: {"scBlockRequest"}, peerID: PeerIDs, height: Heights] \cup - [type: {"scBlockReceived"}, peerID: PeerIDs, block: Blocks] \cup - [type: {"scPeersPruned"}, pruned: SUBSET [peerID: PeerIDs]] \cup - [type: {"scFinishedEv"}, error: Errors] - -(* ----------------------------------------------------------------------------------------------*) -(* The behavior of the scheduler that keeps track of peers, block requests and responses, etc. *) -(* See scheduler.go *) -(* https://github.com/tendermint/tendermint/blob/v0.33.3/blockchain/v2/scheduler.go *) -(* ----------------------------------------------------------------------------------------------*) - -addPeer(sc, peerID) == - IF peerID \in sc.peers THEN - [err |-> "errAddDuplicatePeer", val |-> sc] - ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN - [err |-> "errAddRemovedPeer", val |-> sc] - ELSE - LET newPeers == sc.peers \cup { peerID } IN - LET newPeerHeights == [sc.peerHeights EXCEPT ![peerID] = None] IN - LET newPeerStates == [sc.peerStates EXCEPT ![peerID] = "peerStateNew"] IN - LET newSc == [sc EXCEPT - !.peers = newPeers, - !.peerHeights = newPeerHeights, - !.peerStates = newPeerStates] IN - [err |-> noErr, val |-> newSc] - -maxHeight(states, heights) == - LET activePeers == {p \in DOMAIN states: states[p] = "peerStateReady"} IN - IF activePeers = {} THEN - 0 \* no peers, just return 0 - ELSE - CHOOSE max \in { heights[p] : p \in activePeers }: - \A p \in activePeers: heights[p] <= max \* max is the maximum - -maxHeightScheduler(sc) == - maxHeight(sc.peerStates, sc.peerHeights) - -removePeer(sc, peerID) == - IF peerID \notin sc.peers THEN - [err |-> "errPeerNotFound", val |-> sc] - ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN - [err |-> "errDelRemovedPeer", val |-> sc] - ELSE - LET newSc == [sc EXCEPT - !.peerHeights[peerID] = None, - !.peerStates[peerID] = "peerStateRemoved", - \* remove all blocks from peerID and block requests to peerID, see scheduler.removePeer - !.blockStates = [h \in Heights |-> - IF sc.pendingBlocks[h] = peerID \/ sc.receivedBlocks[h] = peerID THEN "blockStateNew" - ELSE sc.blockStates[h]], - !.pendingBlocks = [h \in Heights |-> IF sc.pendingBlocks[h] = peerID THEN None ELSE sc.pendingBlocks[h]], - !.receivedBlocks = [h \in Heights |-> IF sc.receivedBlocks[h] = peerID THEN None ELSE sc.receivedBlocks[h]] - ] IN - [err |-> noErr, val |-> newSc] - -addNewBlocks(sc, newMph) == - \* add new blocks to be requested (e.g. when overall max peer height has changed) - IF Cardinality(sc.blocks) >= numRequests THEN - [newBlocks |-> sc.blocks, newBlockStates |-> sc.blockStates] - ELSE - LET requestSet == sc.height.. Min(numRequests+sc.height, newMph) IN - LET heightsToRequest == {h \in requestSet: sc.blockStates[h] = "blockStateUnknown"} IN - LET newBlockStates == [ - h \in Heights |-> IF h \in heightsToRequest THEN "blockStateNew" - ELSE sc.blockStates[h]] IN - [newBlocks |-> heightsToRequest, newBlockStates |-> newBlockStates] - -\* Update the peer height (the peer should have been previously added) -setPeerHeight(sc, peerID, height) == - IF peerID \notin sc.peers THEN - [err |-> "errPeerNotFound", val |-> sc] - ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN - [err |-> "errUpdateRemovedPeer", val |-> sc] - ELSE IF height < sc.peerHeights[peerID] THEN (* The peer is corrupt? Remove the peer. *) - removePeer(sc, peerID) - ELSE - LET newPeerHeights == [sc.peerHeights EXCEPT ![peerID] = height] IN \* set the peer's height - LET newPeerStates == [sc.peerStates EXCEPT ![peerID] = "peerStateReady"] IN \* set the peer's state - LET newMph == maxHeight(newPeerStates, newPeerHeights) IN - LET res == addNewBlocks(sc, newMph) IN - LET newSc == [sc EXCEPT - !.peerHeights = newPeerHeights, !.peerStates = newPeerStates, - !.blocks = res.newBlocks, !.blockStates = res.newBlockStates] IN - [err |-> noErr, val |-> newSc] - -nextHeightToSchedule(sc) == - LET toBeScheduled == {h \in DOMAIN sc.blockStates: sc.blockStates[h] = "blockStateNew"} \cup {ultimateHeight+1} IN - CHOOSE minH \in toBeScheduled: \A h \in toBeScheduled: h >= minH - -getStateAtHeight(sc, h) == - IF h < sc.height THEN - "blockStateProcessed" - ELSE IF h \in DOMAIN sc.blockStates THEN - sc.blockStates[h] - ELSE - "blockStateUnknown" - -markPending(sc, peerID, h) == - IF getStateAtHeight(sc, h) /= "blockStateNew" THEN - [err |-> "errBadBlockState", val |-> sc] - ELSE IF peerID \notin sc.peers \/ sc.peerStates[peerID] /= "peerStateReady" THEN - [err |-> "errBadPeerState", val |-> sc] - ELSE IF h > sc.peerHeights[peerID] THEN - [err |-> "errPeerTooShort", val |-> sc] - ELSE - LET newSc == [sc EXCEPT - !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStatePending"], - !.pendingBlocks = [sc.pendingBlocks EXCEPT ![h] = peerID]] IN - [err |-> noErr, val |-> newSc] - -markReceived(sc, peerID, h) == - IF peerID \notin sc.peers \/ sc.peerStates[peerID] /= "peerStateReady" THEN - [err |-> "errBadPeerState", val |-> sc] - ELSE IF getStateAtHeight(sc, h) /= "blockStatePending" \/ sc.pendingBlocks[h] /= peerID THEN - [err |-> "errBadPeer", val |-> sc] - ELSE - LET newSc == [sc EXCEPT - !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStateReceived"], - !.pendingBlocks = [sc.pendingBlocks EXCEPT ![h] = None], - !.receivedBlocks = [sc.receivedBlocks EXCEPT ![h] = peerID]] IN - [err |-> noErr, val |-> newSc] - -markProcessed(sc, h) == - IF getStateAtHeight(sc, h) /= "blockStateReceived" THEN - [err |-> "errProcessedBlockEv", val |-> sc] - ELSE - LET newSc == [sc EXCEPT - !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStateProcessed"], - !.receivedBlocks = [sc.receivedBlocks EXCEPT ![h] = None], - !.height = sc.height + 1] IN - [err |-> noErr, val |-> newSc] - -reachedMaxHeight(sc) == - IF sc.peers = {} THEN - FALSE - ELSE - LET maxH == maxHeightScheduler(sc) IN - maxH > 0 /\ (sc.height >= maxH) - -highPeers(sc, minH) == {p \in sc.peers: sc.peerHeights[p] >= minH} - -(* ----------------------------------------------------------------------------------------------*) -(* The behavior of the scheduler state machine *) -(* See scheduler.go *) -(* https://github.com/tendermint/tendermint/tree/brapse/blockchain-v2-riri-reactor-2/scheduler.go*) -(* ----------------------------------------------------------------------------------------------*) -blStateInit(h, start) == - IF h <= start THEN - "blockStateProcessed" - ELSE "blockStateUnknown" - -InitSc == - /\ scRunning = TRUE - /\ outEvent = noEvent - /\ \E startHeight \in Heights: - scheduler = [ - initHeight |-> startHeight, - height |-> startHeight + 1, - peers |-> {}, - peerHeights |-> [p \in PeerIDs |-> None], - peerStates |-> [p \in PeerIDs |-> "peerStateUnknown"], - blocks |-> {}, - blockStates |-> [h \in Heights |-> blStateInit(h, startHeight)], - pendingBlocks |-> [h \in Heights |-> None], - receivedBlocks |-> [h \in Heights |-> None] - ] - -handleAddNewPeer == - /\ inEvent.type = "bcAddNewPeer" - /\ LET res == addPeer(scheduler, inEvent.peerID) IN - IF res.err /= noErr THEN - /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err] - /\ UNCHANGED <> - ELSE - /\ scheduler' = res.val - /\ UNCHANGED outEvent - /\ UNCHANGED scRunning - -finishSc(event) == - event.type = "scFinishedEv" /\ event.error = "finished" - -handleRemovePeer == - /\ inEvent.type = "bcRemovePeer" - /\ LET res == removePeer(scheduler, inEvent.peerID) IN - IF res.err /= noErr THEN - /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err] - /\ UNCHANGED scheduler - ELSE - /\ scheduler' = res.val - /\ IF reachedMaxHeight(scheduler') THEN - outEvent' = [type |-> "scFinishedEv", error |-> "errAfterPeerRemove"] - ELSE - UNCHANGED outEvent - /\ IF finishSc(outEvent') THEN - scRunning' = FALSE - ELSE UNCHANGED scRunning - -handleStatusResponse == - /\ inEvent.type = "bcStatusResponse" - /\ LET res == setPeerHeight(scheduler, inEvent.peerID, inEvent.height) IN - IF res.err /= noErr THEN - /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> res.err] - /\ UNCHANGED scheduler - ELSE - /\ scheduler' = res.val - /\ UNCHANGED outEvent - /\ UNCHANGED scRunning - -handleTrySchedule == \* every 10 ms, but our spec is asynchronous - /\ inEvent.type = "rTrySchedule" - /\ LET minH == nextHeightToSchedule(scheduler) IN - IF minH = ultimateHeight+1 THEN - /\ outEvent' = noEvent - /\ UNCHANGED scheduler - ELSE IF minH = ultimateHeight+1 THEN - /\ outEvent' = noEvent - /\ UNCHANGED scheduler - ELSE - /\ LET hp == highPeers(scheduler, minH) IN - IF hp = {} THEN - /\ outEvent' = noEvent - /\ UNCHANGED scheduler - ELSE \E bestPeerID \in hp: - /\ LET res == markPending(scheduler, bestPeerID, minH) IN - /\ IF res.err /= noErr THEN - outEvent' = [type |-> "scSchedulerFail", error|-> res.err] - ELSE - outEvent' = [type |-> "scBlockRequest", peerID |-> bestPeerID, height |-> minH] - /\ scheduler' = res.val - /\ UNCHANGED scRunning - -handleBlockResponse == - /\ inEvent.type = "bcBlockResponse" - /\ LET res == markReceived(scheduler, inEvent.peerID, inEvent.height) IN - IF res.err /= noErr THEN - LET res1 == removePeer(scheduler, inEvent.peerID) IN - /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> res.err] - /\ scheduler' = res1.val - ELSE - /\ outEvent' = [type |-> "scBlockReceived", peerID |-> inEvent.peerID, block |-> inEvent.block] - /\ scheduler' = res.val - /\ UNCHANGED scRunning - -handleNoBlockResponse == - /\ inEvent.type = "bcNoBlockResponse" - /\ IF (scheduler.peers = {} \/ scheduler.peerStates[inEvent.peerID] = "peerStateRemoved") THEN - /\ outEvent' = noEvent - /\ UNCHANGED scheduler - ELSE - LET res == removePeer(scheduler, inEvent.peerID) IN - /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> "errPeerNoBlock"] - /\ scheduler' = res.val - /\ UNCHANGED scRunning - -handleBlockProcessed == - /\ inEvent.type = "pcBlockProcessed" - /\ IF inEvent.height /= scheduler.height THEN - /\ outEvent' = [type |-> "scSchedulerFail", error |-> "errProcessedBlockEv"] - /\ UNCHANGED scheduler - ELSE - LET res == markProcessed(scheduler, inEvent.height) IN - IF res.err /= noErr THEN - /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err] - /\ UNCHANGED scheduler - ELSE - /\ scheduler' = res.val - /\ IF reachedMaxHeight(scheduler') THEN - outEvent' = [type |-> "scFinishedEv", error |-> "finished"] - ELSE - outEvent' = noEvent - /\ IF finishSc(outEvent') THEN - scRunning' = FALSE - ELSE UNCHANGED scRunning - -handleBlockProcessError == - /\ inEvent.type = "pcBlockVerificationFailure" - /\ IF scheduler.peers = {} THEN - /\ outEvent' = noEvent - /\ UNCHANGED scheduler - ELSE - LET res1 == removePeer(scheduler, inEvent.firstPeerID) IN - LET res2 == removePeer(res1.val, inEvent.secondPeerID) IN - /\ IF reachedMaxHeight(res2.val) THEN - outEvent' = [type |-> "scFinishedEv", error |-> "finished"] - ELSE - outEvent' = noEvent - /\ scheduler' = res2.val - /\ IF finishSc(outEvent') THEN - scRunning' = FALSE - ELSE UNCHANGED scRunning - -handleNoAdvanceExp == - /\ inEvent.type = "tNoAdvanceExp" - /\ outEvent' = [type |-> "scFinishedEv", error |-> "timeout"] - /\ scRunning' = FALSE - /\ UNCHANGED <> - -NextSc == - IF ~scRunning THEN - UNCHANGED <> - ELSE - \/ handleStatusResponse - \/ handleAddNewPeer - \/ handleRemovePeer - \/ handleTrySchedule - \/ handleBlockResponse - \/ handleNoBlockResponse - \/ handleBlockProcessed - \/ handleBlockProcessError - \/ handleNoAdvanceExp - -(* ----------------------------------------------------------------------------------------------*) -(* The behavior of the environment. *) -(* ----------------------------------------------------------------------------------------------*) - -InitEnv == - /\ inEvent = noEvent - /\ envRunning = TRUE - -OnGlobalTimeoutTicker == - /\ inEvent' = [type |-> "tNoAdvanceExp"] - /\ envRunning' = FALSE - -OnTrySchedule == - /\ inEvent' = [type |-> "rTrySchedule"] - /\ UNCHANGED envRunning - -OnAddPeerEv == - /\ inEvent' \in [type: {"bcAddNewPeer"}, peerID: PeerIDs] - /\ UNCHANGED envRunning - -OnStatusResponseEv == - \* any status response can come from the blockchain, pick one non-deterministically - /\ inEvent' \in [type: {"bcStatusResponse"}, peerID: PeerIDs, height: Heights] - /\ UNCHANGED envRunning - -OnBlockResponseEv == - \* any block response can come from the blockchain, pick one non-deterministically - /\ inEvent' \in [type: {"bcBlockResponse"}, peerID: PeerIDs, height: Heights, block: Blocks] - /\ UNCHANGED envRunning - -OnNoBlockResponseEv == - \* any no block response can come from the blockchain, pick one non-deterministically - /\ inEvent' \in [type: {"bcNoBlockResponse"}, peerID: PeerIDs, height: Heights] - /\ UNCHANGED envRunning - -OnRemovePeerEv == - \* although bcRemovePeer admits an arbitrary set, we produce just a singleton - /\ inEvent' \in [type: {"bcRemovePeer"}, peerID: PeerIDs] - /\ UNCHANGED envRunning - -OnPcBlockProcessed == - /\ inEvent' \in [type: {"pcBlockProcessed"}, peerID: PeerIDs, height: Heights] - /\ UNCHANGED envRunning - -OnPcBlockVerificationFailure == - /\ inEvent' \in [type: {"pcBlockVerificationFailure"}, firstPeerID: PeerIDs, secondPeerID: PeerIDs, height: Heights] - /\ UNCHANGED envRunning - -\* messages from scheduler -OnScFinishedEv == - /\ outEvent.type = "scFinishedEv" - /\ envRunning' = FALSE \* stop the env - /\ UNCHANGED inEvent - -NextEnv == - IF ~envRunning THEN - UNCHANGED <> - ELSE - \/ OnScFinishedEv - \/ OnGlobalTimeoutTicker - \/ OnAddPeerEv - \/ OnTrySchedule - \/ OnStatusResponseEv - \/ OnBlockResponseEv - \/ OnNoBlockResponseEv - \/ OnRemovePeerEv - \/ OnPcBlockProcessed - \/ OnPcBlockVerificationFailure - -(* ----------------------------------------------------------------------------------------------*) -(* The system is the composition of the environment and the schedule *) -(* ----------------------------------------------------------------------------------------------*) -Init == turn = "environment" /\ InitEnv /\ InitSc - -FlipTurn == - turn' = ( - IF turn = "scheduler" THEN - "environment" - ELSE - "scheduler" - ) - -\* scheduler and environment alternate their steps (synchronous composition introduces more states) -Next == -/\ FlipTurn -/\ IF turn = "scheduler" THEN - /\ NextSc - /\ inEvent' = noEvent - /\ UNCHANGED envRunning - ELSE - /\ NextEnv - /\ outEvent' = noEvent - /\ UNCHANGED <> - -Spec == Init /\ [][Next]_vars /\ WF_turn(FlipTurn) - -(* ----------------------------------------------------------------------------------------------*) -(* Invariants *) -(* ----------------------------------------------------------------------------------------------*) -TypeOK == - /\ turn \in {"scheduler", "environment"} - /\ inEvent \in InEvents - /\ envRunning \in BOOLEAN - /\ outEvent \in OutEvents - /\ scheduler \in [ - initHeight: Heights, - height: Heights \cup {ultimateHeight + 1}, - peers: SUBSET PeerIDs, - peerHeights: [PeerIDs -> Heights \cup {None}], - peerStates: [PeerIDs -> PeerStates], - blocks: SUBSET Heights, - blockStates: [Heights -> BlockStates], - pendingBlocks: [Heights -> PeerIDs \cup {None}], - receivedBlocks: [Heights -> PeerIDs \cup {None}] - ] - -(* ----------------------------------------------------------------------------------------------*) -(* Helpers for Properties *) -(* ----------------------------------------------------------------------------------------------*) -NoFailuresAndTimeouts == - /\ inEvent.type /= "bcRemovePeer" - /\ inEvent.type /= "bcNoBlockResponse" - /\ inEvent.type /= "pcBlockVerificationFailure" - -\* simulate good peer behavior using this formula. Useful to show termination in the presence of good peers. -GoodResponse == - \/ inEvent.type - \in {"bcAddNewPeer", "bcStatusResponse", "bcBlockResponse", "pcBlockProcessed", "rTrySchedule"} - \/ ~envRunning - -\* all blocks from initHeight up to max peer height have been processed -AllRequiredBlocksProcessed == - LET maxH == Max(scheduler.height, maxHeightScheduler(scheduler)) IN - LET processedBlocks == {h \in scheduler.initHeight.. maxH-1: scheduler.blockStates[h] = "blockStateProcessed"} IN - scheduler.height >= maxH /\ Cardinality(processedBlocks) = scheduler.height - scheduler.initHeight - -IncreaseHeight == - (scheduler'.height > scheduler.height) \/ (scheduler.height >= ultimateHeight) - -(* ----------------------------------------------------------------------------------------------*) -(* Expected properties *) -(* ----------------------------------------------------------------------------------------------*) -(* *) -(* 1. TerminationWhenNoAdvance - termination if there are no correct peers. *) -(* The model assumes the "noAdvance" timer expires and the "tNoAdvanceExp" event is received *) -(* *) -TerminationWhenNoAdvance == - (inEvent.type = "tNoAdvanceExp") - => <>(outEvent.type = "scFinishedEv" /\ outEvent.error = "timeout") - -(* ----------------------------------------------------------------------------------------------*) -(* *) -(* 2. TerminationGoodPeers - *) -(* termination when IncreaseHeight holds true, fastsync is progressing, all blocks processed *) -(* *) -TerminationGoodPeers == - (/\ scheduler.height < ultimateHeight - /\ <>[]GoodResponse - /\[]<>(<>_<>) - ) - => <>(outEvent.type = "scFinishedEv" /\ AllRequiredBlocksProcessed) - -(* This property is violated. It shows that the precondition of TerminationGoodPeers is not *) -(* always FALSE *) -TerminationGoodPeersPre == - (/\ scheduler.height < ultimateHeight - /\ <>[]GoodResponse - /\[]<>(<>_<>) - ) - => FALSE - -(* ----------------------------------------------------------------------------------------------*) -(* 3. TerminationAllCases - *) -(* any peer behavior, either terminates with all blocks processed or times out *) -TerminationAllCases == - (/\ scheduler.height < ultimateHeight - /\(([]<> (<>_<>)) \/ <>(inEvent.type = "tNoAdvanceExp")) - ) - => <>(outEvent.type = "scFinishedEv" /\ (AllRequiredBlocksProcessed \/ outEvent.error = "timeout")) - -(* This property is violated. It shows that the precondition of TerminationAllCases is not *) -(* always FALSE *) -TerminationAllCasesPre == - (/\ scheduler.height < ultimateHeight - /\(([]<> (<>_<>)) \/ <>(inEvent.type = "tNoAdvanceExp")) - ) - => FALSE - -(* This property is violated. TLC output shows an example of increasing heights in the scheduler *) -SchedulerIncreasePre == -[]<>(<>_<>) - => FALSE - -============================================================================= -\* Modification History -\* Last modified Thu Apr 16 13:21:33 CEST 2020 by ancaz -\* Created Sat Feb 08 13:12:30 CET 2020 by ancaz diff --git a/rust-spec/fastsync/verification/001bmc-apalache.csv b/rust-spec/fastsync/verification/001bmc-apalache.csv deleted file mode 100644 index e83c4c96f..000000000 --- a/rust-spec/fastsync/verification/001bmc-apalache.csv +++ /dev/null @@ -1,13 +0,0 @@ -no,filename,tool,timeout,init,inv,next,args -1,MC_1_1_4.tla,apalache,1h,,Sync1AsInv,,--length=20 -2,MC_1_1_4.tla,apalache,1h,,Sync2AsInv,,--length=20 -3,MC_1_1_4.tla,apalache,4h,,Sync3AsInv,,--length=20 -4,MC_1_1_4.tla,apalache,8h,,CorrectBlocksInv,,--length=16 -5,MC_1_1_4.tla,apalache,1h,,SyncFromCorrectInv,,--length=20 -6,MC_1_1_4.tla,apalache,1h,,CorrectNeverSuspectedInv,,--length=20 -7,MC_1_2_4.tla,apalache,1h,,Sync1AsInv,,--length=20 -8,MC_1_2_4.tla,apalache,1h,,Sync2AsInv,,--length=20 -9,MC_1_2_4.tla,apalache,4h,,Sync3AsInv,,--length=20 -10,MC_1_2_4.tla,apalache,8h,,CorrectBlocksInv,,--length=16 -11,MC_1_2_4.tla,apalache,1h,,SyncFromCorrectInv,,--length=20 -12,MC_1_2_4.tla,apalache,1h,,CorrectNeverSuspectedInv,,--length=20 diff --git a/rust-spec/fastsync/verification/002tlc-tlc.csv b/rust-spec/fastsync/verification/002tlc-tlc.csv deleted file mode 100644 index 2e5429d48..000000000 --- a/rust-spec/fastsync/verification/002tlc-tlc.csv +++ /dev/null @@ -1,9 +0,0 @@ -no,filename,tool,timeout,init,inv,next,args -1,MC_1_1_4.tla,tlc,1h,,,,-workers 4 -config MC-Sync1AsInv.cfg -2,MC_1_1_4.tla,tlc,1h,,,,-workers 4 -config MC-Sync2AsInv.cfg -3,MC_1_1_4.tla,tlc,24h,,,,-workers 4 -config MC-Sync3AsInv.cfg -4,MC_1_1_4.tla,tlc,2h,,Termination,,-workers 4 -config MC-Termination.cfg -5,MC_1_1_4.tla,tlc,24h,,TerminationByTO,,-workers 4 -config MC-TerminationByTO.cfg -6,MC_1_1_4.tla,tlc,24h,,,,-workers 4 -config MC-CorrectBlocksInv.cfg -7,MC_1_1_4.tla,tlc,1h,,,,-workers 4 -config MC-SyncFromCorrectInv.cfg -8,MC_1_1_4.tla,tlc,1h,,,,-workers 4 -config MC-CorrectNeverSuspectedInv.cfg diff --git a/rust-spec/fastsync/verification/MC-CorrectBlocksInv.cfg b/rust-spec/fastsync/verification/MC-CorrectBlocksInv.cfg deleted file mode 100644 index 3e80ef165..000000000 --- a/rust-spec/fastsync/verification/MC-CorrectBlocksInv.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* INVARIANT definition -INVARIANT -CorrectBlocksInv -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-CorrectNeverSuspectedInv.cfg b/rust-spec/fastsync/verification/MC-CorrectNeverSuspectedInv.cfg deleted file mode 100644 index 8a4606a86..000000000 --- a/rust-spec/fastsync/verification/MC-CorrectNeverSuspectedInv.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* INVARIANT definition -INVARIANT -CorrectNeverSuspectedInv -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-Sync1AsInv.cfg b/rust-spec/fastsync/verification/MC-Sync1AsInv.cfg deleted file mode 100644 index b557fb8b1..000000000 --- a/rust-spec/fastsync/verification/MC-Sync1AsInv.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* INVARIANT definition -INVARIANT -Sync1AsInv -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-Sync2AsInv.cfg b/rust-spec/fastsync/verification/MC-Sync2AsInv.cfg deleted file mode 100644 index 3308dc805..000000000 --- a/rust-spec/fastsync/verification/MC-Sync2AsInv.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* INVARIANT definition -INVARIANT -Sync2AsInv -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-Sync3AsInv.cfg b/rust-spec/fastsync/verification/MC-Sync3AsInv.cfg deleted file mode 100644 index e2d3db6e4..000000000 --- a/rust-spec/fastsync/verification/MC-Sync3AsInv.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* INVARIANT definition -INVARIANT -Sync3AsInv -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-SyncFromCorrectInv.cfg b/rust-spec/fastsync/verification/MC-SyncFromCorrectInv.cfg deleted file mode 100644 index b17317633..000000000 --- a/rust-spec/fastsync/verification/MC-SyncFromCorrectInv.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* INVARIANT definition -INVARIANT -SyncFromCorrectInv -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-Termination.cfg b/rust-spec/fastsync/verification/MC-Termination.cfg deleted file mode 100644 index 00e8236e7..000000000 --- a/rust-spec/fastsync/verification/MC-Termination.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* PROPERTY definition -PROPERTY -Termination -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-TerminationByTO.cfg b/rust-spec/fastsync/verification/MC-TerminationByTO.cfg deleted file mode 100644 index 3a871ccf9..000000000 --- a/rust-spec/fastsync/verification/MC-TerminationByTO.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* PROPERTY definition -PROPERTY -TerminationByTO -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC-TerminationCorr.cfg b/rust-spec/fastsync/verification/MC-TerminationCorr.cfg deleted file mode 100644 index bea493637..000000000 --- a/rust-spec/fastsync/verification/MC-TerminationCorr.cfg +++ /dev/null @@ -1,10 +0,0 @@ -\* INIT definition -INIT -Init -\* NEXT definition -NEXT -Next -\* PROPERTY definition -PROPERTY -TerminationCorr -\* Generated on Wed May 27 18:45:18 CEST 2020 diff --git a/rust-spec/fastsync/verification/MC_1_0_4.tla b/rust-spec/fastsync/verification/MC_1_0_4.tla deleted file mode 100644 index b4ab71228..000000000 --- a/rust-spec/fastsync/verification/MC_1_0_4.tla +++ /dev/null @@ -1,17 +0,0 @@ ---------------------------- MODULE MC_1_0_4 ------------------------------------ - -\*a <: b == a \* type annotation - -VALIDATOR_SETS == {"vs1", "vs2"} -NIL_VS == "NilVS" -CORRECT == {"c1"} -FAULTY == {"f2"} \ {"f2"} -MAX_HEIGHT == 4 -PEER_MAX_REQUESTS == 2 -TARGET_PENDING == 3 - -VARIABLES - state, blockPool, peersState, chain, turn, inMsg, outMsg - -INSTANCE fastsync_apalache -================================================================================ diff --git a/rust-spec/fastsync/verification/MC_1_1_4.tla b/rust-spec/fastsync/verification/MC_1_1_4.tla deleted file mode 100644 index 9797fabfc..000000000 --- a/rust-spec/fastsync/verification/MC_1_1_4.tla +++ /dev/null @@ -1,15 +0,0 @@ ---------------------------- MODULE MC_1_1_4 ------------------------------------ - -VALIDATOR_SETS == {"vs1", "vs2"} -NIL_VS == "NilVS" -CORRECT == {"c1"} -FAULTY == {"f2"} -MAX_HEIGHT == 4 -PEER_MAX_REQUESTS == 2 -TARGET_PENDING == 3 - -VARIABLES - state, blockPool, peersState, chain, turn, inMsg, outMsg - -INSTANCE fastsync_apalache -================================================================================ diff --git a/rust-spec/fastsync/verification/MC_1_2_4.tla b/rust-spec/fastsync/verification/MC_1_2_4.tla deleted file mode 100644 index 8e9dc2cc9..000000000 --- a/rust-spec/fastsync/verification/MC_1_2_4.tla +++ /dev/null @@ -1,15 +0,0 @@ --------------------------- MODULE MC_1_2_4 ------------------------------------ - -VALIDATOR_SETS == {"vs1", "vs2"} -NIL_VS == "NilVS" -CORRECT == {"c1"} -FAULTY == {"f2", "f3"} -MAX_HEIGHT == 4 -PEER_MAX_REQUESTS == 2 -TARGET_PENDING == 3 - -VARIABLES - state, blockPool, peersState, chain, turn, inMsg, outMsg - -INSTANCE fastsync_apalache -=============================================================================== diff --git a/rust-spec/fastsync/verification/MC_1_2_5.tla b/rust-spec/fastsync/verification/MC_1_2_5.tla deleted file mode 100644 index 1bd335082..000000000 --- a/rust-spec/fastsync/verification/MC_1_2_5.tla +++ /dev/null @@ -1,15 +0,0 @@ --------------------------- MODULE MC_1_2_5 ------------------------------------ - -VALIDATOR_SETS == {"vs1", "vs2"} -NIL_VS == "NilVS" -CORRECT == {"c1"} -FAULTY == {"f2", "f3"} -MAX_HEIGHT == 5 -PEER_MAX_REQUESTS == 2 -TARGET_PENDING == 3 - -VARIABLES - state, blockPool, peersState, chain, turn, inMsg, outMsg - -INSTANCE fastsync_apalache -=============================================================================== diff --git a/rust-spec/fastsync/verification/MC_1_3_5.tla b/rust-spec/fastsync/verification/MC_1_3_5.tla deleted file mode 100644 index 81dadb470..000000000 --- a/rust-spec/fastsync/verification/MC_1_3_5.tla +++ /dev/null @@ -1,15 +0,0 @@ -------------------------------- MODULE MC_1_3_5 ------------------------------------ - -MAX_HEIGHT == 5 -VALIDATOR_SETS == {"vs1", "vs2"} -NIL_VS == "NilVS" -CORRECT == {"c1"} -FAULTY == {"f2", "f3", "f4"} -PEER_MAX_REQUESTS == 2 -TARGET_PENDING == 3 - -VARIABLES - state, blockPool, peersState, chain, turn, inMsg, outMsg - -INSTANCE fastsync_apalache -======================================================================================== diff --git a/rust-spec/fastsync/verification/MC_2_0_4.tla b/rust-spec/fastsync/verification/MC_2_0_4.tla deleted file mode 100644 index b39dcd608..000000000 --- a/rust-spec/fastsync/verification/MC_2_0_4.tla +++ /dev/null @@ -1,17 +0,0 @@ ---------------------------- MODULE MC_2_0_4 ------------------------------------ - -a <: b == a \* type annotation - -VALIDATOR_SETS == {"vs1", "vs2"} -NIL_VS == "NilVS" -CORRECT == {"c1", "c2"} -FAULTY == {} <: {STRING} -MAX_HEIGHT == 4 -PEER_MAX_REQUESTS == 2 -TARGET_PENDING == 3 - -VARIABLES - state, blockPool, peersState, chain, turn, inMsg, outMsg - -INSTANCE fastsync_apalache -================================================================================ diff --git a/rust-spec/fastsync/verification/Tinychain.tla b/rust-spec/fastsync/verification/Tinychain.tla deleted file mode 100644 index b8a212996..000000000 --- a/rust-spec/fastsync/verification/Tinychain.tla +++ /dev/null @@ -1,110 +0,0 @@ --------------------------- MODULE Tinychain ---------------------------------- -(* A very abstract model of Tendermint blockchain. Its only purpose is to highlight - the relation between validator sets, next validator sets, and last commits. - *) - -EXTENDS Integers - -\* type annotation -a <: b == a - -\* the type of validator sets, e.g., STRING -VST == STRING - -\* LastCommit type. -\* It contains: -\* 1) the flag of whether the block id equals to the hash of the previous block, and -\* 2) the set of the validators who have committed the block. -\* In the implementation, blockId is the hash of the previous block, which cannot be forged. -\* We abstract block id into whether it equals to the hash of the previous block or not. -LCT == [blockIdEqRef |-> BOOLEAN, committers |-> VST] - -\* Block type. -\* A block contains its height, validator set, next validator set, and last commit. -\* Moreover, it contains the flag that tells us whether the block equals to the one -\* on the reference chain (this is an abstraction of hash). -BT == [height |-> Int, hashEqRef |-> BOOLEAN, wellFormed |-> BOOLEAN, - VS |-> VST, NextVS |-> VST, lastCommit |-> LCT] - -CONSTANTS - (* - A set of abstract values, each value representing a set of validators. - For the purposes of this specification, they can be any values, - e.g., "s1", "s2", etc. - *) - VALIDATOR_SETS, - (* a nil validator set that is outside of VALIDATOR_SETS *) - NIL_VS, - (* The maximal height, up to which the blockchain may grow *) - MAX_HEIGHT - -Heights == 1..MAX_HEIGHT - -\* the set of all possible commits -Commits == [blockIdEqRef: BOOLEAN, committers: VALIDATOR_SETS] - -\* the set of all possible blocks, not necessarily valid ones -Blocks == - [height: Heights, hashEqRef: BOOLEAN, wellFormed: BOOLEAN, - VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: Commits] - -\* Does the chain contain a sound sequence of blocks that could be produced by -\* a 2/3 of faulty validators. This operator can be used to initialise the chain! -IsCorrectChain(chain) == - \* restrict the structure of the blocks, to decrease the TLC search space - LET OkCommits == [blockIdEqRef: {TRUE}, committers: VALIDATOR_SETS] - OkBlocks == [height: Heights, hashEqRef: {TRUE}, wellFormed: {TRUE}, - VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: OkCommits] - IN - /\ chain \in [1..MAX_HEIGHT -> OkBlocks] - /\ \A h \in 1..MAX_HEIGHT: - LET b == chain[h] IN - /\ b.height = h \* the height is correct - /\ h > 1 => - LET p == chain[h - 1] IN - /\ b.VS = p.NextVS \* the validators propagate from the previous block - /\ b.lastCommit.committers = p.VS \* and they are the committers - - -\* The basic properties of blocks on the blockchain: -\* They should pass the validity check and they may verify the next block. - -\* Does the block pass the consistency check against the next validators of the previous block -IsMatchingValidators(block, nextVS) == - \* simply check that the validator set is propagated correctly. - \* (the implementation tests hashes and the application state) - block.VS = nextVS - -\* Does the block verify the commit (of the next block) -PossibleCommit(block, commit) == - \* the commits are signed by the block validators - /\ commit.committers = block.VS - \* The block id in the commit matches the block hash (abstract comparison). - \* (The implementation has extensive tests for that.) - \* this is an abstraction of: commit.blockId = hash(block) - \* - \* These are possible scenarios on the concrete hashes: - \* - \* scenario 1: commit.blockId = 10 /\ hash(block) = 10 /\ hash(ref) = 10 - \* scenario 2: commit.blockId = 20 /\ hash(block) = 20 /\ block.VS /= ref.VS - \* scenario 3: commit.blockId = 50 /\ hash(block) = 100 - \* scenario 4: commit.blockId = 10 /\ hash(block) = 100 - \* scenario 5: commit.blockId = 100 /\ hash(block) = 10 - /\ commit.blockIdEqRef = block.hashEqRef - \* the following test would be cheating, as we do not have access to the - \* reference chain: - \* /\ commit.blockIdEqRef - -\* Basic invariants - -\* every block has the validator set that is chosen by its predecessor -ValidBlockInv(chain) == - \A h \in 2..MAX_HEIGHT: - IsMatchingValidators(chain[h], chain[h - 1].NextVS) - -\* last commit of every block is signed by the validators of the predecessor -VerifiedBlockInv(chain) == - \A h \in 2..MAX_HEIGHT: - PossibleCommit(chain[h - 1], chain[h].lastCommit) - -================================================================================== diff --git a/rust-spec/fastsync/verification/fastsync_apalache.tla b/rust-spec/fastsync/verification/fastsync_apalache.tla deleted file mode 100644 index 51dd54e57..000000000 --- a/rust-spec/fastsync/verification/fastsync_apalache.tla +++ /dev/null @@ -1,822 +0,0 @@ ------------------------------ MODULE fastsync_apalache ----------------------------- -(* - In this document we give the high level specification of the fast sync - protocol as implemented here: - https://github.com/tendermint/tendermint/tree/master/blockchain/v2. - -We assume a system in which one node is trying to sync with the blockchain -(replicated state machine) by downloading blocks from the set of full nodes -(we call them peers) that are block providers, and executing transactions -(part of the block) against the application. - -Peers can be faulty, and we don't make any assumption about the rate of correct/faulty -nodes in the node peerset (i.e., they can all be faulty). Correct peers are part -of the replicated state machine, i.e., they manage blockchain and execute -transactions against the same deterministic application. We don't make any -assumptions about the behavior of faulty processes. Processes (client and peers) -communicate by message passing. - - In this specification, we model this system with two parties: - - the node (state machine) that is doing fastsync and - - the environment with which node interacts. - -The environment consists of the set of (correct and faulty) peers with -which node interacts as part of fast sync protocol, but also contains some -aspects (adding/removing peers, timeout mechanism) that are part of the node -local environment (could be seen as part of the runtime in which node -executes). - -As part of the fast sync protocol a node and the peers exchange the following messages: - -- StatusRequest -- StatusResponse -- BlockRequest -- BlockResponse -- NoBlockResponse. - -A node is periodically issuing StatusRequests to query peers for their current height (to decide what -blocks to ask from what peers). Based on StatusResponses (that are sent by peers), the node queries -blocks for some height(s) by sending peers BlockRequest messages. A peer provides a requested block by -BlockResponse message. If a peer does not want to provide a requested block, then it sends NoBlockResponse message. -In addition to those messages, a node in this spec receives additional input messages (events): - -- AddPeer -- RemovePeer -- SyncTimeout. - -These are the control messages that are provided to the node by its execution enviornment. AddPeer -is for the case when a connection is established with a peer; similarly RemovePeer is for the case -a connection with the peer is terminated. Finally SyncTimeout is used to model a timeout trigger. - -We assume that fast sync protocol starts when connections with some number of peers -are established. Therefore, peer set is initialised with non-empty set of peer ids. Note however -that node does not know initially the peer heights. -*) - -EXTENDS Integers, FiniteSets, Sequences - - -CONSTANTS MAX_HEIGHT, \* the maximal height of blockchain - VALIDATOR_SETS, \* abstract set of validators - NIL_VS, \* a nil validator set - CORRECT, \* set of correct peers - FAULTY, \* set of faulty peers - TARGET_PENDING, \* maximum number of pending requests + downloaded blocks that are not yet processed - PEER_MAX_REQUESTS \* maximum number of pending requests per peer - -ASSUME CORRECT \intersect FAULTY = {} -ASSUME TARGET_PENDING > 0 -ASSUME PEER_MAX_REQUESTS > 0 - -\* the blockchain, see Tinychain -VARIABLE chain - -\* introduce tiny chain as the source of blocks for the correct nodes -INSTANCE Tinychain - -\* a special value for an undefined height -NilHeight == 0 - -\* the height of the genesis block -TrustedHeight == 1 - -\* the set of all peer ids the node can receive a message from -AllPeerIds == CORRECT \union FAULTY - -\* Correct last commit have enough voting power, i.e., +2/3 of the voting power of -\* the corresponding validator set (enoughVotingPower = TRUE) that signs blockId. -\* BlockId defines correct previous block (in the implementation it is the hash of the block). -\* Instead of blockId, we encode blockIdEqRef, which is true, if the block id is equal -\* to the hash of the previous block, see Tinychain. -CorrectLastCommit(h) == chain[h].lastCommit - -NilCommit == [blockIdEqRef |-> FALSE, committers |-> NIL_VS] - -\* correct node always supplies the blocks from the blockchain -CorrectBlock(h) == chain[h] - -NilBlock == - [height |-> 0, hashEqRef |-> FALSE, wellFormed |-> FALSE, - lastCommit |-> NilCommit, VS |-> NIL_VS, NextVS |-> NIL_VS] - -\* a special value for an undefined peer -NilPeer == "Nil" \* STRING for apalache efficiency - -\* control the state of the syncing node -States == { "running", "finished"} - -NoMsg == [type |-> "None"] - -\* the variables of the node running fastsync -VARIABLES - state, \* running or finished - (* - blockPool [ - height, \* current height we are trying to sync. Last block executed is height - 1 - peerIds, \* set of peers node is connected to - peerHeights, \* map of peer ids to its (stated) height - blockStore, \* map of heights to (received) blocks - receivedBlocks, \* map of heights to peer that has sent us the block (stored in blockStore) - pendingBlocks, \* map of heights to peer to which block request has been sent - syncHeight, \* height at the point syncTimeout was triggered last time - syncedBlocks \* number of blocks synced since last syncTimeout. If it is 0 when the next timeout occurs, then protocol terminates. - ] - *) - blockPool - - -\* the variables of the peers providing blocks -VARIABLES - (* - peersState [ - peerHeights, \* track peer heights - statusRequested, \* boolean set to true when StatusRequest is received. Models periodic sending of StatusRequests. - blocksRequested \* set of BlockRequests received that are not answered yet - ] - *) - peersState - - \* the variables for the network and scheduler -VARIABLES - turn, \* who is taking the turn: "Peers" or "Node" - inMsg, \* a node receives message by this variable - outMsg \* a node sends a message by this variable - - -(* the variables of the node *) -nvars == <> - -(*************** Type definitions for Apalache (model checker) **********************) -AsIntSet(S) == S <: {Int} - -\* type of process ids -PIDT == STRING -AsPidSet(S) == S <: {PIDT} - -\* ControlMessage type -CMT == [type |-> STRING, peerId |-> PIDT] \* type of control messages - -\* InMsg type -IMT == [type |-> STRING, peerId |-> PIDT, height |-> Int, block |-> BT] -AsInMsg(m) == m <: IMT -AsInMsgSet(S) == S <: {IMT} - -\* OutMsg type -OMT == [type |-> STRING, peerId |-> PIDT, height |-> Int] -AsOutMsg(m) == m <: OMT -AsOutMsgSet(S) == S <: {OMT} - -\* block pool type -BPT == [height |-> Int, peerIds |-> {PIDT}, peerHeights |-> [PIDT -> Int], - blockStore |-> [Int -> BT], receivedBlocks |-> [Int -> PIDT], - pendingBlocks |-> [Int -> PIDT], syncedBlocks |-> Int, syncHeight |-> Int] - -AsBlockPool(bp) == bp <: BPT - -(******************** Sets of messages ********************************) - -\* Control messages -ControlMsgs == - AsInMsgSet([type: {"addPeer"}, peerId: AllPeerIds]) - \union - AsInMsgSet([type: {"removePeer"}, peerId: AllPeerIds]) - \union - AsInMsgSet([type: {"syncTimeout"}]) - -\* All messages (and events) received by a node -InMsgs == - AsInMsgSet({NoMsg}) - \union - AsInMsgSet([type: {"blockResponse"}, peerId: AllPeerIds, block: Blocks]) - \union - AsInMsgSet([type: {"noBlockResponse"}, peerId: AllPeerIds, height: Heights]) - \union - AsInMsgSet([type: {"statusResponse"}, peerId: AllPeerIds, height: Heights]) - \union - ControlMsgs - -\* Messages sent by a node and received by peers (environment in our case) -OutMsgs == - AsOutMsgSet({NoMsg}) - \union - AsOutMsgSet([type: {"statusRequest"}]) \* StatusRequest is broadcast to the set of connected peers. - \union - AsOutMsgSet([type: {"blockRequest"}, peerId: AllPeerIds, height: Heights]) - - -(********************************** NODE ***********************************) - -InitNode == - \E pIds \in SUBSET AllPeerIds: \* set of peers node established initial connections with - /\ pIds \subseteq CORRECT - /\ pIds /= AsPidSet({}) \* apalache better checks non-emptiness than subtracts from SUBSET - /\ blockPool = AsBlockPool([ - height |-> TrustedHeight + 1, \* the genesis block is at height 1 - syncHeight |-> TrustedHeight + 1, \* and we are synchronized to it - peerIds |-> pIds, - peerHeights |-> [p \in AllPeerIds |-> NilHeight], \* no peer height is known - blockStore |-> - [h \in Heights |-> - IF h > TrustedHeight THEN NilBlock ELSE chain[1]], - receivedBlocks |-> [h \in Heights |-> NilPeer], - pendingBlocks |-> [h \in Heights |-> NilPeer], - syncedBlocks |-> -1 - ]) - /\ state = "running" - -\* Remove faulty peers. -\* Returns new block pool. -\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L222 -RemovePeers(rmPeers, bPool) == - LET keepPeers == bPool.peerIds \ rmPeers IN - LET pHeights == - [p \in AllPeerIds |-> IF p \in rmPeers THEN NilHeight ELSE bPool.peerHeights[p]] IN - - LET failedRequests == - {h \in Heights: /\ h >= bPool.height - /\ \/ bPool.pendingBlocks[h] \in rmPeers - \/ bPool.receivedBlocks[h] \in rmPeers} IN - LET pBlocks == - [h \in Heights |-> IF h \in failedRequests THEN NilPeer ELSE bPool.pendingBlocks[h]] IN - LET rBlocks == - [h \in Heights |-> IF h \in failedRequests THEN NilPeer ELSE bPool.receivedBlocks[h]] IN - LET bStore == - [h \in Heights |-> IF h \in failedRequests THEN NilBlock ELSE bPool.blockStore[h]] IN - - IF keepPeers /= bPool.peerIds - THEN [bPool EXCEPT - !.peerIds = keepPeers, - !.peerHeights = pHeights, - !.pendingBlocks = pBlocks, - !.receivedBlocks = rBlocks, - !.blockStore = bStore - ] - ELSE bPool - -\* Add a peer. -\* see https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L198 -AddPeer(peer, bPool) == - [bPool EXCEPT !.peerIds = bPool.peerIds \union {peer}] - - -(* -Handle StatusResponse message. -If valid status response, update peerHeights. -If invalid (height is smaller than the current), then remove peer. -Returns new block pool. -See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L667 -*) -HandleStatusResponse(msg, bPool) == - LET peerHeight == bPool.peerHeights[msg.peerId] IN - - IF /\ msg.peerId \in bPool.peerIds - /\ msg.height >= peerHeight - THEN \* a correct response - LET pHeights == [bPool.peerHeights EXCEPT ![msg.peerId] = msg.height] IN - [bPool EXCEPT !.peerHeights = pHeights] - ELSE RemovePeers({msg.peerId}, bPool) \* the peer has sent us message with smaller height or peer is not in our peer list - - -(* -Handle BlockResponse message. -If valid block response, update blockStore, pendingBlocks and receivedBlocks. -If invalid (unsolicited response or malformed block), then remove peer. -Returns new block pool. -See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L522 -*) -HandleBlockResponse(msg, bPool) == - LET h == msg.block.height IN - - IF /\ msg.peerId \in bPool.peerIds - /\ bPool.blockStore[h] = NilBlock - /\ bPool.pendingBlocks[h] = msg.peerId - /\ msg.block.wellFormed - THEN - [bPool EXCEPT - !.blockStore = [bPool.blockStore EXCEPT ![h] = msg.block], - !.receivedBlocks = [bPool.receivedBlocks EXCEPT![h] = msg.peerId], - !.pendingBlocks = [bPool.pendingBlocks EXCEPT![h] = NilPeer] - ] - ELSE RemovePeers({msg.peerId}, bPool) - - HandleNoBlockResponse(msg, bPool) == - RemovePeers({msg.peerId}, bPool) - - -\* Compute max peer height. -\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L440 -MaxPeerHeight(bPool) == - IF bPool.peerIds = AsPidSet({}) - THEN 0 \* no peers, just return 0 - ELSE LET Hts == {bPool.peerHeights[p] : p \in bPool.peerIds} IN - CHOOSE max \in Hts: \A h \in Hts: h <= max - -(* Returns next height for which request should be sent. - Returns NilHeight in case there is no height for which request can be sent. - See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L454 *) -FindNextRequestHeight(bPool) == - LET S == {i \in Heights: - /\ i >= bPool.height - /\ i <= MaxPeerHeight(bPool) - /\ bPool.blockStore[i] = NilBlock - /\ bPool.pendingBlocks[i] = NilPeer} IN - IF S = AsIntSet({}) - THEN NilHeight - ELSE - CHOOSE min \in S: \A h \in S: h >= min - -\* Returns number of pending requests for a given peer. -NumOfPendingRequests(bPool, peer) == - LET peerPendingRequests == - {h \in Heights: - /\ h >= bPool.height - /\ bPool.pendingBlocks[h] = peer - } - IN - Cardinality(peerPendingRequests) - -(* Returns peer that can serve block for a given height. - Returns NilPeer in case there are no such peer. - See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L477 *) -FindPeerToServe(bPool, h) == - LET peersThatCanServe == { p \in bPool.peerIds: - /\ bPool.peerHeights[p] >= h - /\ NumOfPendingRequests(bPool, p) < PEER_MAX_REQUESTS } IN - - LET pendingBlocks == - {i \in Heights: - /\ i >= bPool.height - /\ \/ bPool.pendingBlocks[i] /= NilPeer - \/ bPool.blockStore[i] /= NilBlock - } IN - - IF \/ peersThatCanServe = AsPidSet({}) - \/ Cardinality(pendingBlocks) >= TARGET_PENDING - THEN NilPeer - \* pick a peer that can serve request for height h that has minimum number of pending requests - ELSE CHOOSE p \in peersThatCanServe: \A q \in peersThatCanServe: - /\ NumOfPendingRequests(bPool, p) <= NumOfPendingRequests(bPool, q) - - -\* Make a request for a block (if possible) and return a request message and block poool. -CreateRequest(bPool) == - LET nextHeight == FindNextRequestHeight(bPool) IN - - IF nextHeight = NilHeight THEN [msg |-> AsOutMsg(NoMsg), pool |-> bPool] - ELSE - LET peer == FindPeerToServe(bPool, nextHeight) IN - IF peer = NilPeer THEN [msg |-> AsOutMsg(NoMsg), pool |-> bPool] - ELSE - LET m == [type |-> "blockRequest", peerId |-> peer, height |-> nextHeight] IN - LET newPool == [bPool EXCEPT - !.pendingBlocks = [bPool.pendingBlocks EXCEPT ![nextHeight] = peer] - ] IN - [msg |-> m, pool |-> newPool] - - -\* Returns node state, i.e., defines termination condition. -\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L432 -ComputeNextState(bPool) == - IF bPool.syncedBlocks = 0 \* corresponds to the syncTimeout in case no progress has been made for a period of time. - THEN "finished" - ELSE IF /\ bPool.height > 1 - /\ bPool.height >= MaxPeerHeight(bPool) \* see https://github.com/tendermint/tendermint/blob/61057a8b0af2beadee106e47c4616b279e83c920/blockchain/v2/scheduler.go#L566 - THEN "finished" - ELSE "running" - -(* Verify if commit is for the given block id and if commit has enough voting power. - See https://github.com/tendermint/tendermint/blob/61057a8b0af2beadee106e47c4616b279e83c920/blockchain/v2/processor_context.go#L12 *) -VerifyCommit(block, lastCommit) == - PossibleCommit(block, lastCommit) - -(* Tries to execute next block in the pool, i.e., defines block validation logic. - Returns new block pool (peers that has send invalid blocks are removed). - See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/processor.go#L135 *) -ExecuteBlocks(bPool) == - LET bStore == bPool.blockStore IN - LET block0 == bStore[bPool.height - 1] IN - \* blockPool is initialized with height = TrustedHeight + 1, - \* so bStore[bPool.height - 1] is well defined - LET block1 == bStore[bPool.height] IN - LET block2 == bStore[bPool.height + 1] IN - - IF block1 = NilBlock \/ block2 = NilBlock - THEN bPool \* we don't have two next consecutive blocks - - ELSE IF ~IsMatchingValidators(block1, block0.NextVS) - \* Check that block1.VS = block0.Next. - \* Otherwise, CorrectBlocksInv fails. - \* In the implementation NextVS is part of the application state, - \* so a mismatch can be found without access to block0.NextVS. - THEN \* the block does not have the expected validator set - RemovePeers({bPool.receivedBlocks[bPool.height]}, bPool) - ELSE IF ~VerifyCommit(block1, block2.lastCommit) - \* Verify commit of block2 based on block1. - \* Interestingly, we do not have to call IsMatchingValidators. - THEN \* remove the peers of block1 and block2, as they are considered faulty - RemovePeers({bPool.receivedBlocks[bPool.height], - bPool.receivedBlocks[bPool.height + 1]}, - bPool) - ELSE \* all good, execute block at position height - [bPool EXCEPT !.height = bPool.height + 1] - - -\* Defines logic for pruning peers. -\* See https://github.com/tendermint/tendermint/blob/dac030d6daf4d3e066d84275911128856838af4e/blockchain/v2/scheduler.go#L613 -TryPrunePeer(bPool, suspectedSet, isTimedOut) == - (* -----------------------------------------------------------------------------------------------------------------------*) - (* Corresponds to function prunablePeers in scheduler.go file. Note that this function only checks if block has been *) - (* received from a peer during peerTimeout period. *) - (* Note that in case no request has been scheduled to a correct peer, or a request has been scheduled *) - (* recently, so the peer hasn't responded yet, a peer will be removed as no block is received within peerTimeout. *) - (* In case of faulty peers, we don't have any guarantee that they will respond. *) - (* Therefore, we model this with nondeterministic behavior as it could lead to peer removal, for both correct and faulty. *) - (* See scheduler.go *) - (* https://github.com/tendermint/tendermint/blob/4298bbcc4e25be78e3c4f21979d6aa01aede6e87/blockchain/v2/scheduler.go#L335 *) - LET toRemovePeers == bPool.peerIds \intersect suspectedSet IN - - (* - Corresponds to logic for pruning a peer that is responsible for delivering block for the next height. - The pruning logic for the next height is based on the time when a BlockRequest is sent. Therefore, if a request is sent - to a correct peer for the next height (blockPool.height), it should never be removed by this check as we assume that - correct peers respond timely and reliably. However, if a request is sent to a faulty peer then we - might get response on time or not, which is modelled with nondeterministic isTimedOut flag. - See scheduler.go - https://github.com/tendermint/tendermint/blob/4298bbcc4e25be78e3c4f21979d6aa01aede6e87/blockchain/v2/scheduler.go#L617 - *) - LET nextHeightPeer == bPool.pendingBlocks[bPool.height] IN - LET prunablePeers == - IF /\ nextHeightPeer /= NilPeer - /\ nextHeightPeer \in FAULTY - /\ isTimedOut - THEN toRemovePeers \union {nextHeightPeer} - ELSE toRemovePeers - IN - RemovePeers(prunablePeers, bPool) - - -\* Handle SyncTimeout. It models if progress has been made (height has increased) since the last SyncTimeout event. -HandleSyncTimeout(bPool) == - [bPool EXCEPT - !.syncedBlocks = bPool.height - bPool.syncHeight, - !.syncHeight = bPool.height - ] - -HandleResponse(msg, bPool) == - IF msg.type = "blockResponse" THEN - HandleBlockResponse(msg, bPool) - ELSE IF msg.type = "noBlockResponse" THEN - HandleNoBlockResponse(msg, bPool) - ELSE IF msg.type = "statusResponse" THEN - HandleStatusResponse(msg, bPool) - ELSE IF msg.type = "addPeer" THEN - AddPeer(msg.peerId, bPool) - ELSE IF msg.type = "removePeer" THEN - RemovePeers({msg.peerId}, bPool) - ELSE IF msg.type = "syncTimeout" THEN - HandleSyncTimeout(bPool) - ELSE - bPool - - -(* - At every node step we executed the following steps (atomically): - 1) input message is consumed and the corresponding handler is called, - 2) pruning logic is called - 3) block execution is triggered (we try to execute block at next height) - 4) a request to a peer is made (if possible) and - 5) we decide if termination condition is satisifed so we stop. -*) -NodeStep == - \E suspectedSet \in SUBSET AllPeerIds: \* suspectedSet is a nondeterministic set of peers - \E isTimedOut \in BOOLEAN: - LET bPool == HandleResponse(inMsg, blockPool) IN - LET bp == TryPrunePeer(bPool, suspectedSet, isTimedOut) IN - LET nbPool == ExecuteBlocks(bp) IN - LET msgAndPool == CreateRequest(nbPool) IN - LET nstate == ComputeNextState(msgAndPool.pool) IN - - /\ state' = nstate - /\ blockPool' = msgAndPool.pool - /\ outMsg' = msgAndPool.msg - /\ inMsg' = AsInMsg(NoMsg) - - -\* If node is running, then in every step we try to create blockRequest. -\* In addition, input message (if exists) is consumed and processed. -NextNode == - \/ /\ state = "running" - /\ NodeStep - - \/ /\ state = "finished" - /\ UNCHANGED <> - - -(********************************** Peers ***********************************) - -InitPeers == - \E pHeights \in [AllPeerIds -> Heights]: - peersState = [ - peerHeights |-> pHeights, - statusRequested |-> FALSE, - blocksRequested |-> AsOutMsgSet({}) - ] - -HandleStatusRequest(msg, pState) == - [pState EXCEPT - !.statusRequested = TRUE - ] - -HandleBlockRequest(msg, pState) == - [pState EXCEPT - !.blocksRequested = pState.blocksRequested \union AsOutMsgSet({msg}) - ] - -HandleRequest(msg, pState) == - IF msg = AsOutMsg(NoMsg) - THEN pState - ELSE IF msg.type = "statusRequest" - THEN HandleStatusRequest(msg, pState) - ELSE HandleBlockRequest(msg, pState) - -CreateStatusResponse(peer, pState, anyHeight) == - LET m == - IF peer \in CORRECT - THEN AsInMsg([type |-> "statusResponse", peerId |-> peer, height |-> pState.peerHeights[peer]]) - ELSE AsInMsg([type |-> "statusResponse", peerId |-> peer, height |-> anyHeight]) IN - - [msg |-> m, peers |-> pState] - -CreateBlockResponse(msg, pState, arbitraryBlock) == - LET m == - IF msg.peerId \in CORRECT - THEN AsInMsg([type |-> "blockResponse", peerId |-> msg.peerId, block |-> CorrectBlock(msg.height)]) - ELSE AsInMsg([type |-> "blockResponse", peerId |-> msg.peerId, block |-> arbitraryBlock]) IN - LET npState == - [pState EXCEPT - !.blocksRequested = pState.blocksRequested \ {msg} - ] IN - [msg |-> m, peers |-> npState] - -GrowPeerHeight(pState) == - \E p \in CORRECT: - /\ pState.peerHeights[p] < MAX_HEIGHT - /\ peersState' = [pState EXCEPT !.peerHeights[p] = @ + 1] - /\ inMsg' = AsInMsg(NoMsg) - -SendStatusResponseMessage(pState) == - /\ \E arbitraryHeight \in Heights: - \E peer \in AllPeerIds: - LET msgAndPeers == CreateStatusResponse(peer, pState, arbitraryHeight) IN - /\ peersState' = msgAndPeers.peers - /\ inMsg' = msgAndPeers.msg - - -SendAddPeerMessage == - \E peer \in AllPeerIds: - inMsg' = AsInMsg([type |-> "addPeer", peerId |-> peer]) - -SendRemovePeerMessage == - \E peer \in AllPeerIds: - inMsg' = AsInMsg([type |-> "removePeer", peerId |-> peer]) - -SendSyncTimeoutMessage == - inMsg' = AsInMsg([type |-> "syncTimeout"]) - - -SendControlMessage == - \/ SendAddPeerMessage - \/ SendRemovePeerMessage - \/ SendSyncTimeoutMessage - -\* An extremely important property of block hashes (blockId): -\* If a commit is correct, then the previous block in the block store must be also correct. -UnforgeableBlockId(height, block) == - block.hashEqRef => block = chain[height] - -\* A faulty peer cannot forge enough of the validators signatures. -\* A more precise rule should have checked that the commiters have over 2/3 of the VS's voting power. -NoFork(height, block) == - (height > 1 /\ block.lastCommit.committers = chain[height - 1].VS) - => block.lastCommit.blockIdEqRef - -\* Can be block produced by a faulty peer -IsBlockByFaulty(height, block) == - /\ block.height = height - /\ UnforgeableBlockId(height, block) - /\ NoFork(height, block) - -SendBlockResponseMessage(pState) == - \* a response to a requested block: either by a correct, or by a faulty peer - \/ /\ pState.blocksRequested /= AsOutMsgSet({}) - /\ \E msg \in pState.blocksRequested: - \E block \in Blocks: - /\ IsBlockByFaulty(msg.height, block) - /\ LET msgAndPeers == CreateBlockResponse(msg, pState, block) IN - /\ peersState' = msgAndPeers.peers - /\ inMsg' = msgAndPeers.msg - - \* a faulty peer can always send an unsolicited block - \/ \E peerId \in FAULTY: - \E block \in Blocks: - /\ IsBlockByFaulty(block.height, block) - /\ peersState' = pState - /\ inMsg' = AsInMsg([type |-> "blockResponse", - peerId |-> peerId, block |-> block]) - -SendNoBlockResponseMessage(pState) == - /\ peersState' = pState - /\ inMsg' \in AsInMsgSet([type: {"noBlockResponse"}, peerId: FAULTY, height: Heights]) - - -SendResponseMessage(pState) == - \/ SendBlockResponseMessage(pState) - \/ SendNoBlockResponseMessage(pState) - \/ SendStatusResponseMessage(pState) - - -NextEnvStep(pState) == - \/ SendResponseMessage(pState) - \/ GrowPeerHeight(pState) - \/ SendControlMessage /\ peersState' = pState - \* note that we propagate pState that was missing in the previous version - - -\* Peers consume a message and update it's local state. It then makes a single step, i.e., it sends at most single message. -\* Message sent could be either a response to a request or faulty message (sent by faulty processes). -NextPeers == - LET pState == HandleRequest(outMsg, peersState) IN - /\ outMsg' = AsOutMsg(NoMsg) - /\ NextEnvStep(pState) - - -\* the composition of the node, the peers, the network and scheduler -Init == - /\ IsCorrectChain(chain) \* initialize the blockchain - /\ InitNode - /\ InitPeers - /\ turn = "Peers" - /\ inMsg = AsInMsg(NoMsg) - /\ outMsg = AsOutMsg([type |-> "statusRequest"]) - -Next == - IF turn = "Peers" - THEN - /\ NextPeers - /\ turn' = "Node" - /\ UNCHANGED <> - ELSE - /\ NextNode - /\ turn' = "Peers" - /\ UNCHANGED <> - - -FlipTurn == - turn' = - IF turn = "Peers" THEN - "Node" - ELSE - "Peers" - -\* Compute max peer height. Used as a helper operator in properties. -MaxCorrectPeerHeight(bPool) == - LET correctPeers == {p \in bPool.peerIds: p \in CORRECT} IN - IF correctPeers = AsPidSet({}) - THEN 0 \* no peers, just return 0 - ELSE LET Hts == {bPool.peerHeights[p] : p \in correctPeers} IN - CHOOSE max \in Hts: \A h \in Hts: h <= max - -\* properties to check -TypeOK == - /\ state \in States - /\ inMsg \in InMsgs - /\ outMsg \in OutMsgs - /\ turn \in {"Peers", "Node"} - /\ peersState \in [ - peerHeights: [AllPeerIds -> Heights \union {NilHeight}], - statusRequested: BOOLEAN, - blocksRequested: - SUBSET - [type: {"blockRequest"}, peerId: AllPeerIds, height: Heights] - - ] - /\ blockPool \in [ - height: Heights, - peerIds: SUBSET AllPeerIds, - peerHeights: [AllPeerIds -> Heights \union {NilHeight}], - blockStore: [Heights -> Blocks \union {NilBlock}], - receivedBlocks: [Heights -> AllPeerIds \union {NilPeer}], - pendingBlocks: [Heights -> AllPeerIds \union {NilPeer}], - syncedBlocks: Heights \union {NilHeight, -1}, - syncHeight: Heights - ] - -(* Incorrect synchronization: The last block may be never received *) -Sync1 == - [](state = "finished" => - blockPool.height >= MaxCorrectPeerHeight(blockPool)) - -Sync1AsInv == - state = "finished" => blockPool.height >= MaxCorrectPeerHeight(blockPool) - -(* Incorrect synchronization, as there may be a timeout *) -Sync2 == - \A p \in CORRECT: - \/ p \notin blockPool.peerIds - \/ [] (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) - -Sync2AsInv == - \A p \in CORRECT: - \/ p \notin blockPool.peerIds - \/ (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) - -(* Correct synchronization *) -Sync3 == - \A p \in CORRECT: - \/ p \notin blockPool.peerIds - \/ blockPool.syncedBlocks <= 0 \* timeout - \/ [] (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) - -Sync3AsInv == - \A p \in CORRECT: - \/ p \notin blockPool.peerIds - \/ blockPool.syncedBlocks <= 0 \* timeout - \/ (state = "finished" => blockPool.height >= blockPool.peerHeights[p] - 1) - -(* Naive termination *) -\* This property is violated, as the faulty peers may produce infinitely many responses -Termination == - WF_turn(FlipTurn) => <>(state = "finished") - -(* Termination by timeout: the protocol terminates, if there is a timeout *) -\* the precondition: fair flip turn and eventual timeout when no new blocks were synchronized -TerminationByTOPre == - /\ WF_turn(FlipTurn) - /\ <>(inMsg.type = "syncTimeout" /\ blockPool.height <= blockPool.syncHeight) - -TerminationByTO == - TerminationByTOPre => <>(state = "finished") - -(* The termination property when we only have correct peers *) -\* as correct peers may spam the node with addPeer, removePeer, and statusResponse, -\* we have to enforce eventual response (there are no queues in our spec) -CorrBlockResponse == - \A h \in Heights: - [](outMsg.type = "blockRequest" /\ outMsg.height = h - => <>(inMsg.type = "blockResponse" /\ inMsg.block.height = h)) - -\* a precondition for termination in presence of only correct processes -TerminationCorrPre == - /\ FAULTY = AsPidSet({}) - /\ WF_turn(FlipTurn) - /\ CorrBlockResponse - -\* termination when there are only correct processes -TerminationCorr == - TerminationCorrPre => <>(state = "finished") - -\* All synchronized blocks (but the last one) are exactly like in the reference chain -CorrectBlocksInv == - \/ state /= "finished" - \/ \A h \in 1..(blockPool.height - 1): - blockPool.blockStore[h] = chain[h] - -\* A false expectation that the protocol only finishes with the blocks -\* from the processes that had not been suspected in being faulty -SyncFromCorrectInv == - \/ state /= "finished" - \/ \A h \in 1..blockPool.height: - blockPool.receivedBlocks[h] \in blockPool.peerIds \union {NilPeer} - -\* A false expectation that a correct process is never removed from the set of peer ids. -\* A correct process may reply too late and then gets evicted. -CorrectNeverSuspectedInv == - CORRECT \subseteq blockPool.peerIds - -BlockPoolInvariant == - \A h \in Heights: - \* waiting for a block to arrive - \/ /\ blockPool.receivedBlocks[h] = NilPeer - /\ blockPool.blockStore[h] = NilBlock - \* valid block is received and is present in the store - \/ /\ blockPool.receivedBlocks[h] /= NilPeer - /\ blockPool.blockStore[h] /= NilBlock - /\ blockPool.pendingBlocks[h] = NilPeer - -(* a few simple properties that trigger counterexamples *) - -\* Shows execution in which peer set is empty -PeerSetIsNeverEmpty == blockPool.peerIds /= AsPidSet({}) - -\* Shows execution in which state = "finished" and MaxPeerHeight is not equal to 1 -StateNotFinished == - state /= "finished" \/ MaxPeerHeight(blockPool) = 1 - - -============================================================================= - -\*============================================================================= -\* Modification History -\* Last modified Fri May 29 20:41:53 CEST 2020 by igor -\* Last modified Thu Apr 16 16:57:22 CEST 2020 by zarkomilosevic -\* Created Tue Feb 04 10:36:18 CET 2020 by zarkomilosevic diff --git a/spec/consensus/bft-time.md b/spec/consensus/bft-time.md index b2571f9cb..3d4421d54 100644 --- a/spec/consensus/bft-time.md +++ b/spec/consensus/bft-time.md @@ -1,3 +1,6 @@ +--- +order: 2 +--- # BFT Time Tendermint provides a deterministic, Byzantine fault-tolerant, source of time. diff --git a/spec/consensus/consensus.md b/spec/consensus/consensus.md index 755587cb8..53793068a 100644 --- a/spec/consensus/consensus.md +++ b/spec/consensus/consensus.md @@ -1,3 +1,6 @@ +--- +order: 1 +--- # Byzantine Consensus Algorithm ## Terms diff --git a/spec/consensus/creating-proposal.md b/spec/consensus/creating-proposal.md index 52cdb1ce6..cb43c8ebb 100644 --- a/spec/consensus/creating-proposal.md +++ b/spec/consensus/creating-proposal.md @@ -1,3 +1,6 @@ +--- +order: 2 +--- # Creating a proposal A block consists of a header, transactions, votes (the commit), diff --git a/spec/consensus/light-client/README.md b/spec/consensus/light-client/README.md index b25a6463d..44b9e0c76 100644 --- a/spec/consensus/light-client/README.md +++ b/spec/consensus/light-client/README.md @@ -1,3 +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/reactors/consensus/proposer-selection.md b/spec/consensus/proposer-selection.md similarity index 77% rename from spec/reactors/consensus/proposer-selection.md rename to spec/consensus/proposer-selection.md index 6b2f3d76d..3cea3d5cd 100644 --- a/spec/reactors/consensus/proposer-selection.md +++ b/spec/consensus/proposer-selection.md @@ -1,4 +1,8 @@ -# Proposer selection procedure in Tendermint +--- +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. @@ -68,24 +72,24 @@ Simple view at the Selection Algorithm: Consider the validator set: -Validator | p1| p2 -----------|---|--- -VP | 1 | 3 +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 +| 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: @@ -100,17 +104,17 @@ Between proposer selection runs the validator set may change. Some changes have Consider again the earlier example and assume that the voting power of p1 is changed to 4: -Validator | p1| p2 -----------|---| --- -VP | 4 | 3 +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 +| 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. @@ -123,22 +127,22 @@ As before: Consider a new example with set: -Validator | p1 | p2 | p3 | ---------- |--- |--- |--- | -VP | 1 | 2 | 3 | +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 +| 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: @@ -180,7 +184,7 @@ Curent implementation uses the penalty factor of 1.125 because it provides a sma 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: @@ -193,13 +197,13 @@ Note that since current computation uses integer division there is penalty loss 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 +| 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 @@ -207,25 +211,25 @@ With the introduction of centering, some interesting cases occur. Low power vali 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__ +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__ + | 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) @@ -233,11 +237,11 @@ Then execute the following steps: 3. Remove p1 and run selection once: - Validator | p3 | p2 | Comment - ----------|----- |---- |-------- - VP | 10 | 10 | - A |-60k |-15k | - A |-22.5k|22.5k| __run selection__ + 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. @@ -300,9 +304,9 @@ __[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 +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`]* diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index ff892d43d..d2919bb8c 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -17,7 +17,7 @@ The Tendermint blockchains consists of a short list of data types: - [`Vote`](#vote) - [`CanonicalVote`](#canonicalvote) - [`SignedMsgType`](#signedmsgtype) -- [`EvidenceData`](#evidence_data) +- [`EvidenceList`](#evidence_list) - [`Evidence`](#evidence) - [`DuplicateVoteEvidence`](#duplicatevoteevidence) - [`LightClientAttackEvidence`](#lightclientattackevidence) @@ -40,7 +40,7 @@ and a list of evidence of malfeasance (ie. signing conflicting votes). |--------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| | 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 | [EvidenceData](#evidence_data) | Evidence contains a list of infractions committed by validators. | Can be empty, but when populated the validations rules from [evidenceData](#evidence_data) apply | +| 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 @@ -150,6 +150,16 @@ See [MerkleRoot](./encoding.md#MerkleRoot) for details. | 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.WellKnownTypes.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp) @@ -255,6 +265,23 @@ func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { } ``` +### 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 | int64 | 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. @@ -278,9 +305,9 @@ Signatures in Tendermint are raw bytes representing the underlying signature. See the [signature spec](./encoding.md#key-types) for more. -## EvidenceData +## EvidenceList -EvidenceData is a simple wrapper for a list of evidence: +EvidenceList is a simple wrapper for a list of evidence: | Name | Type | Description | Validation | |----------|--------------------------------|----------------------------------------|-----------------------------------------------------------------| @@ -439,3 +466,12 @@ func SumTruncated(bz []byte) []byte { | 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/p2p/messages/README.md b/spec/p2p/messages/README.md new file mode 100644 index 000000000..1b5f5c60d --- /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 000000000..5f341be6a --- /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 | [BlockRequest](#noblockresponse) | Response saying it doe snot have the requested block | 2 | +| block_response | [BlockRequest](#blockresponse) | Response with requested block | 3 | +| status_request | [BlockRequest](#statusrequest) | Request the highest and lowest block numbers from a peer | 4 | +| status_response | [BlockRequest](#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 000000000..cd5ee7711 --- /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 | + +### HasVote + +HasVote 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 | +| has_vote | [HasVote](#hasvote) | | 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 000000000..34fc40a91 --- /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 000000000..8f3925cad --- /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 000000000..dc11b88c9 --- /dev/null +++ b/spec/p2p/messages/pex.md @@ -0,0 +1,48 @@ +--- +order: 6 +--- + +# Peer Exchange + +## Channels + +Pex has one channel. The channel identifier is listed below. + +| Name | Number | +|------------|--------| +| PexChannel | 0 | + +## Message Types + +### PexRequest + +PexRequest is an empty message requesting a list of peers. + +> EmptyRequest + +### PexAddrs + +PexAddrs is an list of net addresses provided to a peer to dial. + +| Name | Type | Description | Field Number | +|-------|------------------------------------|------------------------------------------|--------------| +| addrs | repeated [NetAddress](#netaddress) | List of peer addresses available to dial | 1 | + +### NetAddress + +NetAddress 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 | + +### 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_addrs | [PexAddrs] | List of addresses to dial | 2 | diff --git a/spec/p2p/messages/state-sync.md b/spec/p2p/messages/state-sync.md new file mode 100644 index 000000000..37c78e86c --- /dev/null +++ b/spec/p2p/messages/state-sync.md @@ -0,0 +1,85 @@ +--- +order: 5 +--- + +# State Sync + +## Channels + +State sync has two distinct channels. The channel identifiers are listed below. + +| Name | Number | +|-----------------|--------| +| SnapshotChannel | 96 | +| ChunkChannel | 97 | + +## 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. + +If no state sync is in progress (i.e. during normal operation), any unsolicited response messages +are discarded. + +### Message + +Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buffers/docs/proto#oneof). The `oneof` consists of four 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 | diff --git a/spec/reactors/block_sync/img/bc-reactor-routines.png b/spec/reactors/block_sync/img/bc-reactor-routines.png deleted file mode 100644 index 3f574a79b1ad304e7c03cfdb717c4f9aa600359a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271695 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 z 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 ~ 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 rPS?R%#7i-)emg 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

w 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

yWw3MnVBCW%u7u^gmB*PD0N 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

d=tvlV&!=8%Kuk} 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˱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�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 I?OV_O1Bz9-`2YX_ diff --git a/spec/reactors/block_sync/impl.md b/spec/reactors/block_sync/impl.md deleted file mode 100644 index 4f39658e7..000000000 --- a/spec/reactors/block_sync/impl.md +++ /dev/null @@ -1,43 +0,0 @@ -# Blockchain Reactor v0 Module - -## Blockchain Reactor - -- coordinates the pool for syncing -- coordinates the store for persistence -- coordinates the playing of blocks towards the app using a sm.BlockExecutor -- handles switching between fastsync and consensus -- it is a p2p.BaseReactor -- starts the pool.Start() and its poolRoutine() -- registers all the concrete types and interfaces for serialisation - -### poolRoutine - -- listens to these channels: - - pool requests blocks from a specific peer by posting to requestsCh, block reactor then sends - a &bcBlockRequestMessage for a specific height - - pool signals timeout of a specific peer by posting to timeoutsCh - - switchToConsensusTicker to periodically try and switch to consensus - - trySyncTicker to periodically check if we have fallen behind and then catch-up sync - - if there aren't any new blocks available on the pool it skips syncing -- tries to sync the app by taking downloaded blocks from the pool, gives them to the app and stores - them on disk -- implements Receive which is called by the switch/peer - - calls AddBlock on the pool when it receives a new block from a peer - -## Block Pool - -- responsible for downloading blocks from peers -- makeRequestersRoutine() - - removes timeout peers - - starts new requesters by calling makeNextRequester() -- requestRoutine(): - - picks a peer and sends the request, then blocks until: - - pool is stopped by listening to pool.Quit - - requester is stopped by listening to Quit - - request is redone - - we receive a block - - gotBlockCh is strange - -## Go Routines in Blockchain Reactor - -![Go Routines Diagram](img/bc-reactor-routines.png) diff --git a/spec/reactors/block_sync/reactor.md b/spec/reactors/block_sync/reactor.md deleted file mode 100644 index 9c34ab49c..000000000 --- a/spec/reactors/block_sync/reactor.md +++ /dev/null @@ -1,308 +0,0 @@ -# Blockchain Reactor - -The Blockchain Reactor's high level responsibility is to enable peers who are -far behind the current state of the consensus to quickly catch up by downloading -many blocks in parallel, verifying their commits, and executing them against the -ABCI application. - -Tendermint full nodes run the Blockchain Reactor as a service to provide blocks -to new nodes. New nodes run the Blockchain Reactor in "fast_sync" mode, -where they actively make requests for more blocks until they sync up. -Once caught up, "fast_sync" mode is disabled and the node switches to -using (and turns on) the Consensus Reactor. - -## Message Types - -```go -const ( - msgTypeBlockRequest = byte(0x10) - msgTypeBlockResponse = byte(0x11) - msgTypeNoBlockResponse = byte(0x12) - msgTypeStatusResponse = byte(0x20) - msgTypeStatusRequest = byte(0x21) -) -``` - -```go -type bcBlockRequestMessage struct { - Height int64 -} - -type bcNoBlockResponseMessage struct { - Height int64 -} - -type bcBlockResponseMessage struct { - Block Block -} - -type bcStatusRequestMessage struct { - Height int64 - -type bcStatusResponseMessage struct { - Height int64 -} -``` - -## Architecture and algorithm - -The Blockchain reactor is organised as a set of concurrent tasks: - -- Receive routine of Blockchain Reactor -- Task for creating Requesters -- Set of Requesters tasks and - Controller task. - -![Blockchain Reactor Architecture Diagram](img/bc-reactor.png) - -### Data structures - -These are the core data structures necessarily to provide the Blockchain Reactor logic. - -Requester data structure is used to track assignment of request for `block` at position `height` to a peer with id equals to `peerID`. - -```go -type Requester { - mtx Mutex - block Block - height int64 - 
 peerID p2p.ID - redoChannel chan p2p.ID //redo may send multi-time; peerId is used to identify repeat -} -``` - -Pool is a core data structure that stores last executed block (`height`), assignment of requests to peers (`requesters`), current height for each peer and number of pending requests for each peer (`peers`), maximum peer height, etc. - -```go -type Pool { - mtx Mutex - requesters map[int64]*Requester - height int64 - peers map[p2p.ID]*Peer - maxPeerHeight int64 - numPending int32 - store BlockStore - requestsChannel chan<- BlockRequest - errorsChannel chan<- peerError -} -``` - -Peer data structure stores for each peer current `height` and number of pending requests sent to the peer (`numPending`), etc. - -```go -type Peer struct { - id p2p.ID - height int64 - numPending int32 - timeout *time.Timer - didTimeout bool -} -``` - -BlockRequest is internal data structure used to denote current mapping of request for a block at some `height` to a peer (`PeerID`). - -```go -type BlockRequest { - Height int64 - PeerID p2p.ID -} -``` - -### Receive routine of Blockchain Reactor - -It is executed upon message reception on the BlockchainChannel inside p2p receive routine. There is a separate p2p receive routine (and therefore receive routine of the Blockchain Reactor) executed for each peer. Note that try to send will not block (returns immediately) if outgoing buffer is full. - -```go -handleMsg(pool, m): - upon receiving bcBlockRequestMessage m from peer p: - block = load block for height m.Height from pool.store - if block != nil then - try to send BlockResponseMessage(block) to p - else - try to send bcNoBlockResponseMessage(m.Height) to p - - upon receiving bcBlockResponseMessage m from peer p: - pool.mtx.Lock() - requester = pool.requesters[m.Height] - if requester == nil then - error("peer sent us a block we didn't expect") - continue - - if requester.block == nil and requester.peerID == p then - requester.block = m - pool.numPending -= 1 // atomic decrement - peer = pool.peers[p] - if peer != nil then - peer.numPending-- - if peer.numPending == 0 then - peer.timeout.Stop() - // NOTE: we don't send Quit signal to the corresponding requester task! - else - trigger peer timeout to expire after peerTimeout - pool.mtx.Unlock() - - - upon receiving bcStatusRequestMessage m from peer p: - try to send bcStatusResponseMessage(pool.store.Height) - - upon receiving bcStatusResponseMessage m from peer p: - pool.mtx.Lock() - peer = pool.peers[p] - if peer != nil then - peer.height = m.height - else - peer = create new Peer data structure with id = p and height = m.Height - pool.peers[p] = peer - - if m.Height > pool.maxPeerHeight then - pool.maxPeerHeight = m.Height - pool.mtx.Unlock() - -onTimeout(p): - send error message to pool error channel - peer = pool.peers[p] - peer.didTimeout = true -``` - -### Requester tasks - -Requester task is responsible for fetching a single block at position `height`. - -```go -fetchBlock(height, pool): - while true do { - peerID = nil - block = nil - peer = pickAvailablePeer(height) - peerID = peer.id - - enqueue BlockRequest(height, peerID) to pool.requestsChannel - redo = false - while !redo do - select { - upon receiving Quit message do - return - upon receiving redo message with id on redoChannel do - if peerID == id { - mtx.Lock() - pool.numPending++ - redo = true - mtx.UnLock() - } - } - } - -pickAvailablePeer(height): - selectedPeer = nil - while selectedPeer = nil do - pool.mtx.Lock() - for each peer in pool.peers do - if !peer.didTimeout and peer.numPending < maxPendingRequestsPerPeer and peer.height >= height then - peer.numPending++ - selectedPeer = peer - break - pool.mtx.Unlock() - - if selectedPeer = nil then - sleep requestIntervalMS - - return selectedPeer -``` - -sleep for requestIntervalMS - -### Task for creating Requesters - -This task is responsible for continuously creating and starting Requester tasks. - -```go -createRequesters(pool): - while true do - if !pool.isRunning then break - if pool.numPending < maxPendingRequests or size(pool.requesters) < maxTotalRequesters then - pool.mtx.Lock() - nextHeight = pool.height + size(pool.requesters) - requester = create new requester for height nextHeight - pool.requesters[nextHeight] = requester - pool.numPending += 1 // atomic increment - start requester task - pool.mtx.Unlock() - else - sleep requestIntervalMS - pool.mtx.Lock() - for each peer in pool.peers do - if !peer.didTimeout && peer.numPending > 0 && peer.curRate < minRecvRate then - send error on pool error channel - peer.didTimeout = true - if peer.didTimeout then - for each requester in pool.requesters do - if requester.getPeerID() == peer then - enqueue msg on requestor's redoChannel - delete(pool.peers, peerID) - pool.mtx.Unlock() -``` - -### Main blockchain reactor controller task - -```go -main(pool): - create trySyncTicker with interval trySyncIntervalMS - create statusUpdateTicker with interval statusUpdateIntervalSeconds - create switchToConsensusTicker with interval switchToConsensusIntervalSeconds - - while true do - select { - upon receiving BlockRequest(Height, Peer) on pool.requestsChannel: - try to send bcBlockRequestMessage(Height) to Peer - - upon receiving error(peer) on errorsChannel: - stop peer for error - - upon receiving message on statusUpdateTickerChannel: - broadcast bcStatusRequestMessage(bcR.store.Height) // message sent in a separate routine - - upon receiving message on switchToConsensusTickerChannel: - pool.mtx.Lock() - receivedBlockOrTimedOut = pool.height > 0 || (time.Now() - pool.startTime) > 5 Seconds - ourChainIsLongestAmongPeers = pool.maxPeerHeight == 0 || pool.height >= pool.maxPeerHeight - haveSomePeers = size of pool.peers > 0 - pool.mtx.Unlock() - if haveSomePeers && receivedBlockOrTimedOut && ourChainIsLongestAmongPeers then - switch to consensus mode - - upon receiving message on trySyncTickerChannel: - for i = 0; i < 10; i++ do - pool.mtx.Lock() - firstBlock = pool.requesters[pool.height].block - secondBlock = pool.requesters[pool.height].block - if firstBlock == nil or secondBlock == nil then continue - pool.mtx.Unlock() - verify firstBlock using LastCommit from secondBlock - if verification failed - pool.mtx.Lock() - peerID = pool.requesters[pool.height].peerID - redoRequestsForPeer(peerId) - delete(pool.peers, peerID) - stop peer peerID for error - pool.mtx.Unlock() - else - delete(pool.requesters, pool.height) - save firstBlock to store - pool.height++ - execute firstBlock - } - -redoRequestsForPeer(pool, peerId): - for each requester in pool.requesters do - if requester.getPeerID() == peerID - enqueue msg on redoChannel for requester -``` - -## Channels - -Defines `maxMsgSize` for the maximum size of incoming messages, -`SendQueueCapacity` and `RecvBufferCapacity` for maximum sending and -receiving buffers respectively. These are supposed to prevent amplification -attacks by setting up the upper limit on how much data we can receive & send to -a peer. - -Sending incorrectly encoded data will result in stopping the peer. diff --git a/spec/reactors/consensus/consensus-reactor.md b/spec/reactors/consensus/consensus-reactor.md deleted file mode 100644 index c228d2dd3..000000000 --- a/spec/reactors/consensus/consensus-reactor.md +++ /dev/null @@ -1,366 +0,0 @@ -# Consensus Reactor - -Consensus Reactor defines a reactor for the consensus service. It contains the ConsensusState service that -manages the state of the Tendermint consensus internal state machine. -When Consensus Reactor is started, it starts Broadcast Routine which starts ConsensusState service. -Furthermore, for each peer that is added to the Consensus Reactor, it creates (and manages) the known peer state -(that is used extensively in gossip routines) and starts the following three routines for the peer p: -Gossip Data Routine, Gossip Votes Routine and QueryMaj23Routine. Finally, Consensus Reactor is responsible -for decoding messages received from a peer and for adequate processing of the message depending on its type and content. -The processing normally consists of updating the known peer state and for some messages -(`ProposalMessage`, `BlockPartMessage` and `VoteMessage`) also forwarding message to ConsensusState module -for further processing. In the following text we specify the core functionality of those separate unit of executions -that are part of the Consensus Reactor. - -## ConsensusState service - -Consensus State handles execution of the Tendermint BFT consensus algorithm. It processes votes and proposals, -and upon reaching agreement, commits blocks to the chain and executes them against the application. -The internal state machine receives input from peers, the internal validator and from a timer. - -Inside Consensus State we have the following units of execution: Timeout Ticker and Receive Routine. -Timeout Ticker is a timer that schedules timeouts conditional on the height/round/step that are processed -by the Receive Routine. - -### Receive Routine of the ConsensusState service - -Receive Routine of the ConsensusState handles messages which may cause internal consensus state transitions. -It is the only routine that updates RoundState that contains internal consensus state. -Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majorities. -It receives messages from peers, internal validators and from Timeout Ticker -and invokes the corresponding handlers, potentially updating the RoundState. -The details of the protocol (together with formal proofs of correctness) implemented by the Receive Routine are -discussed in separate document. For understanding of this document -it is sufficient to understand that the Receive Routine manages and updates RoundState data structure that is -then extensively used by the gossip routines to determine what information should be sent to peer processes. - -## Round State - -RoundState defines the internal consensus state. It contains height, round, round step, a current validator set, -a proposal and proposal block for the current round, locked round and block (if some block is being locked), set of -received votes and last commit and last validators set. - -```go -type RoundState struct { - Height int64 - Round int - Step RoundStepType - Validators ValidatorSet - Proposal Proposal - ProposalBlock Block - ProposalBlockParts PartSet - LockedRound int - LockedBlock Block - LockedBlockParts PartSet - Votes HeightVoteSet - LastCommit VoteSet - LastValidators ValidatorSet -} -``` - -Internally, consensus will run as a state machine with the following states: - -- RoundStepNewHeight -- RoundStepNewRound -- RoundStepPropose -- RoundStepProposeWait -- RoundStepPrevote -- RoundStepPrevoteWait -- RoundStepPrecommit -- RoundStepPrecommitWait -- RoundStepCommit - -## Peer Round State - -Peer round state contains the known state of a peer. It is being updated by the Receive routine of -Consensus Reactor and by the gossip routines upon sending a message to the peer. - -```golang -type PeerRoundState struct { - Height int64 // Height peer is at - Round int // Round peer is at, -1 if unknown. - Step RoundStepType // Step peer is at - Proposal bool // True if peer has proposal for this round - ProposalBlockPartsHeader PartSetHeader - ProposalBlockParts BitArray - ProposalPOLRound int // Proposal's POL round. -1 if none. - ProposalPOL BitArray // nil until ProposalPOLMessage received. - Prevotes BitArray // All votes peer has for this round - Precommits BitArray // All precommits peer has for this round - LastCommitRound int // Round of commit for last height. -1 if none. - LastCommit BitArray // All commit precommits of commit for last height. - CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. - CatchupCommit BitArray // All commit precommits peer has for this height & CatchupCommitRound -} -``` - -## Receive method of Consensus reactor - -The entry point of the Consensus reactor is a receive method. When a message is -received from a peer p, normally the peer round state is updated -correspondingly, and some messages are passed for further processing, for -example to ConsensusState service. We now specify the processing of messages in -the receive method of Consensus reactor for each message type. In the following -message handler, `rs` and `prs` denote `RoundState` and `PeerRoundState`, -respectively. - -### NewRoundStepMessage handler - -```go -handleMessage(msg): - if msg is from smaller height/round/step then return - // Just remember these values. - prsHeight = prs.Height - prsRound = prs.Round - prsCatchupCommitRound = prs.CatchupCommitRound - prsCatchupCommit = prs.CatchupCommit - - Update prs with values from msg - if prs.Height or prs.Round has been updated then - reset Proposal related fields of the peer state - if prs.Round has been updated and msg.Round == prsCatchupCommitRound then - prs.Precommits = psCatchupCommit - if prs.Height has been updated then - if prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound then - prs.LastCommitRound = msg.LastCommitRound - prs.LastCommit = prs.Precommits - } else { - prs.LastCommitRound = msg.LastCommitRound - prs.LastCommit = nil - } - Reset prs.CatchupCommitRound and prs.CatchupCommit -``` - -### NewValidBlockMessage handler - -```go -handleMessage(msg): - if prs.Height != msg.Height then return - - if prs.Round != msg.Round && !msg.IsCommit then return - - prs.ProposalBlockPartsHeader = msg.BlockPartsHeader - prs.ProposalBlockParts = msg.BlockParts -``` - -The number of block parts is limited to 1601 (`types.MaxBlockPartsCount`) to -protect the node against DOS attacks. - -### HasVoteMessage handler - -```go -handleMessage(msg): - if prs.Height == msg.Height then - prs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index) -``` - -### VoteSetMaj23Message handler - -```go -handleMessage(msg): - if prs.Height == msg.Height then - Record in rs that a peer claim to have ⅔ majority for msg.BlockID - Send VoteSetBitsMessage showing votes node has for that BlockId -``` - -### ProposalMessage handler - -```go -handleMessage(msg): - if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return - prs.Proposal = true - if prs.ProposalBlockParts == empty set then // otherwise it is set in NewValidBlockMessage handler - prs.ProposalBlockPartsHeader = msg.BlockPartsHeader - prs.ProposalPOLRound = msg.POLRound - prs.ProposalPOL = nil - Send msg through internal peerMsgQueue to ConsensusState service -``` - -### ProposalPOLMessage handler - -```go -handleMessage(msg): - if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then return - prs.ProposalPOL = msg.ProposalPOL -``` - -The number of votes is limited to 10000 (`types.MaxVotesCount`) to protect the -node against DOS attacks. - -### BlockPartMessage handler - -```go -handleMessage(msg): - if prs.Height != msg.Height || prs.Round != msg.Round then return - Record in prs that peer has block part msg.Part.Index - Send msg trough internal peerMsgQueue to ConsensusState service -``` - -### VoteMessage handler - -```go -handleMessage(msg): - Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and round - Send msg trough internal peerMsgQueue to ConsensusState service -``` - -### VoteSetBitsMessage handler - -```go -handleMessage(msg): - Update prs for the bit-array of votes peer claims to have for the msg.BlockID -``` - -The number of votes is limited to 10000 (`types.MaxVotesCount`) to protect the -node against DOS attacks. - -## Gossip Data Routine - -It is used to send the following messages to the peer: `BlockPartMessage`, `ProposalMessage` and -`ProposalPOLMessage` on the DataChannel. The gossip data routine is based on the local RoundState (`rs`) -and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below: - -```go -1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts then - Part = pick a random proposal block part the peer does not have - Send BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannel - if send returns true, record that the peer knows the corresponding block Part - Continue - -1b) if (0 < prs.Height) and (prs.Height < rs.Height) then - help peer catch up using gossipDataForCatchup function - Continue - -1c) if (rs.Height != prs.Height) or (rs.Round != prs.Round) then - Sleep PeerGossipSleepDuration - Continue - -// at this point rs.Height == prs.Height and rs.Round == prs.Round -1d) if (rs.Proposal != nil and !prs.Proposal) then - Send ProposalMessage(rs.Proposal) to the peer - if send returns true, record that the peer knows Proposal - if 0 <= rs.Proposal.POLRound then - polRound = rs.Proposal.POLRound - prevotesBitArray = rs.Votes.Prevotes(polRound).BitArray() - Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray) - Continue - -2) Sleep PeerGossipSleepDuration -``` - -### Gossip Data For Catchup - -This function is responsible for helping peer catch up if it is at the smaller height (prs.Height < rs.Height). -The function executes the following logic: - -```go - if peer does not have all block parts for prs.ProposalBlockPart then - blockMeta = Load Block Metadata for height prs.Height from blockStore - if (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) then - Sleep PeerGossipSleepDuration - return - Part = pick a random proposal block part the peer does not have - Send BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannel - if send returns true, record that the peer knows the corresponding block Part - return - else Sleep PeerGossipSleepDuration -``` - -## Gossip Votes Routine - -It is used to send the following message: `VoteMessage` on the VoteChannel. -The gossip votes routine is based on the local RoundState (`rs`) -and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below: - -```go -1a) if rs.Height == prs.Height then - if prs.Step == RoundStepNewHeight then - vote = random vote from rs.LastCommit the peer does not have - Send VoteMessage(vote) to the peer - if send returns true, continue - - if prs.Step <= RoundStepPrevote and prs.Round != -1 and prs.Round <= rs.Round then - Prevotes = rs.Votes.Prevotes(prs.Round) - vote = random vote from Prevotes the peer does not have - Send VoteMessage(vote) to the peer - if send returns true, continue - - if prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round then - Precommits = rs.Votes.Precommits(prs.Round) - vote = random vote from Precommits the peer does not have - Send VoteMessage(vote) to the peer - if send returns true, continue - - if prs.ProposalPOLRound != -1 then - PolPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound) - vote = random vote from PolPrevotes the peer does not have - Send VoteMessage(vote) to the peer - if send returns true, continue - -1b) if prs.Height != 0 and rs.Height == prs.Height+1 then - vote = random vote from rs.LastCommit peer does not have - Send VoteMessage(vote) to the peer - if send returns true, continue - -1c) if prs.Height != 0 and rs.Height >= prs.Height+2 then - Commit = get commit from BlockStore for prs.Height - vote = random vote from Commit the peer does not have - Send VoteMessage(vote) to the peer - if send returns true, continue - -2) Sleep PeerGossipSleepDuration -``` - -## QueryMaj23Routine - -It is used to send the following message: `VoteSetMaj23Message`. `VoteSetMaj23Message` is sent to indicate that a given -BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs`) and the known PeerRoundState -(`prs`). The routine repeats forever the logic shown below. - -```go -1a) if rs.Height == prs.Height then - Prevotes = rs.Votes.Prevotes(prs.Round) - if there is a ⅔ majority for some blockId in Prevotes then - m = VoteSetMaj23Message(prs.Height, prs.Round, Prevote, blockId) - Send m to peer - Sleep PeerQueryMaj23SleepDuration - -1b) if rs.Height == prs.Height then - Precommits = rs.Votes.Precommits(prs.Round) - if there is a ⅔ majority for some blockId in Precommits then - m = VoteSetMaj23Message(prs.Height,prs.Round,Precommit,blockId) - Send m to peer - Sleep PeerQueryMaj23SleepDuration - -1c) if rs.Height == prs.Height and prs.ProposalPOLRound >= 0 then - Prevotes = rs.Votes.Prevotes(prs.ProposalPOLRound) - if there is a ⅔ majority for some blockId in Prevotes then - m = VoteSetMaj23Message(prs.Height,prs.ProposalPOLRound,Prevotes,blockId) - Send m to peer - Sleep PeerQueryMaj23SleepDuration - -1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height and - prs.Height <= blockStore.Height() then - Commit = LoadCommit(prs.Height) - m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.BlockID) - Send m to peer - Sleep PeerQueryMaj23SleepDuration - -2) Sleep PeerQueryMaj23SleepDuration -``` - -## Broadcast routine - -The Broadcast routine subscribes to an internal event bus to receive new round steps and votes messages, and broadcasts messages to peers upon receiving those -events. -It broadcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state event. Note that -broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel. -Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers on the StateChannel. - -## Channels - -Defines 4 channels: state, data, vote and vote_set_bits. Each channel -has `SendQueueCapacity` and `RecvBufferCapacity` and -`RecvMessageCapacity` set to `maxMsgSize`. - -Sending incorrectly encoded data will result in stopping the peer. diff --git a/spec/reactors/consensus/consensus.md b/spec/reactors/consensus/consensus.md deleted file mode 100644 index 459e4afbd..000000000 --- a/spec/reactors/consensus/consensus.md +++ /dev/null @@ -1,187 +0,0 @@ -# Tendermint Consensus Reactor - -Tendermint Consensus is a distributed protocol executed by validator processes to agree on -the next block to be added to the Tendermint blockchain. The protocol proceeds in rounds, where -each round is a try to reach agreement on the next block. A round starts by having a dedicated -process (called proposer) suggesting to other processes what should be the next block with -the `ProposalMessage`. -The processes respond by voting for a block with `VoteMessage` (there are two kinds of vote -messages, prevote and precommit votes). Note that a proposal message is just a suggestion what the -next block should be; a validator might vote with a `VoteMessage` for a different block. If in some -round, enough number of processes vote for the same block, then this block is committed and later -added to the blockchain. `ProposalMessage` and `VoteMessage` are signed by the private key of the -validator. The internals of the protocol and how it ensures safety and liveness properties are -explained in a forthcoming document. - -For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the -block as the block size is big, i.e., they don't embed the block inside `Proposal` and -`VoteMessage`. Instead, they reach agreement on the `BlockID` (see `BlockID` definition in -[Blockchain](https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md#blockid) section) -that uniquely identifies each block. The block itself is -disseminated to validator processes using peer-to-peer gossiping protocol. It starts by having a -proposer first splitting a block into a number of block parts, that are then gossiped between -processes using `BlockPartMessage`. - -Validators in Tendermint communicate by peer-to-peer gossiping protocol. Each validator is connected -only to a subset of processes called peers. By the gossiping protocol, a validator send to its peers -all needed information (`ProposalMessage`, `VoteMessage` and `BlockPartMessage`) so they can -reach agreement on some block, and also obtain the content of the chosen block (block parts). As -part of the gossiping protocol, processes also send auxiliary messages that inform peers about the -executed steps of the core consensus algorithm (`NewRoundStepMessage` and `NewValidBlockMessage`), and -also messages that inform peers what votes the process has seen (`HasVoteMessage`, -`VoteSetMaj23Message` and `VoteSetBitsMessage`). These messages are then used in the gossiping -protocol to determine what messages a process should send to its peers. - -We now describe the content of each message exchanged during Tendermint consensus protocol. - -## ProposalMessage - -ProposalMessage is sent when a new block is proposed. It is a suggestion of what the -next block in the blockchain should be. - -```go -type ProposalMessage struct { - Proposal Proposal -} -``` - -### 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. - -```go -type Proposal struct { - Height int64 - Round int - POLRound int - BlockID BlockID - Timestamp Time - Signature Signature -} -``` - -## VoteMessage - -VoteMessage 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. - -```go -type VoteMessage struct { - Vote Vote -} -``` - -## BlockPartMessage - -BlockPartMessage is sent when gossiping a piece of the proposed block. It contains height, round -and the block part. - -```go -type BlockPartMessage struct { - Height int64 - Round int - Part Part -} -``` - -## NewRoundStepMessage - -NewRoundStepMessage 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. - -```go -type NewRoundStepMessage struct { - Height int64 - Round int - Step RoundStepType - SecondsSinceStartTime int - LastCommitRound int -} -``` - -## NewValidBlockMessage - -NewValidBlockMessage 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. - -```go -type NewValidBlockMessage struct { - Height int64 - Round int - BlockPartsHeader PartSetHeader - BlockParts BitArray - IsCommit bool -} -``` - -## ProposalPOLMessage - -ProposalPOLMessage 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. - -```go -type ProposalPOLMessage struct { - Height int64 - ProposalPOLRound int - ProposalPOL BitArray -} -``` - -## HasVoteMessage - -HasVoteMessage 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. - -```go -type HasVoteMessage struct { - Height int64 - Round int - Type byte - Index int -} -``` - -## VoteSetMaj23Message - -VoteSetMaj23Message is sent to indicate that a process has seen +2/3 votes for some BlockID. -It contains height, round, vote type and the BlockID. - -```go -type VoteSetMaj23Message struct { - Height int64 - Round int - Type byte - BlockID BlockID -} -``` - -## VoteSetBitsMessage - -VoteSetBitsMessage 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. - -```go -type VoteSetBitsMessage struct { - Height int64 - Round int - Type byte - BlockID BlockID - Votes BitArray -} -``` diff --git a/spec/reactors/evidence/reactor.md b/spec/reactors/evidence/reactor.md deleted file mode 100644 index efa63aa4c..000000000 --- a/spec/reactors/evidence/reactor.md +++ /dev/null @@ -1,10 +0,0 @@ -# Evidence Reactor - -## Channels - -[#1503](https://github.com/tendermint/tendermint/issues/1503) - -Sending invalid evidence will result in stopping the peer. - -Sending incorrectly encoded data or data exceeding `maxMsgSize` will result -in stopping the peer. diff --git a/spec/reactors/mempool/concurrency.md b/spec/reactors/mempool/concurrency.md deleted file mode 100644 index a6870db9b..000000000 --- a/spec/reactors/mempool/concurrency.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mempool Concurrency - -Look at the concurrency model this uses... - -- Receiving CheckTx -- Broadcasting new tx -- Interfaces with consensus engine, reap/update while checking -- Calling the ABCI app (ordering. callbacks. how proxy works alongside the blockchain proxy which actually writes blocks) diff --git a/spec/reactors/mempool/config.md b/spec/reactors/mempool/config.md deleted file mode 100644 index 0f5366fe2..000000000 --- a/spec/reactors/mempool/config.md +++ /dev/null @@ -1,54 +0,0 @@ -# Mempool Configuration - -Here we describe configuration options around mempool. -For the purposes of this document, they are described -as command-line flags, but they can also be passed in as -environmental variables or in the config.toml file. The -following are all equivalent: - -Flag: `--mempool.recheck=false` - -Environment: `TM_MEMPOOL_RECHECK=false` - -Config: - -```toml -[mempool] -recheck = false -``` - -## Recheck - -`--mempool.recheck=false` (default: true) - -Recheck determines if the mempool rechecks all pending -transactions after a block was committed. Once a block -is committed, the mempool removes all valid transactions -that were successfully included in the block. - -If `recheck` is true, then it will rerun CheckTx on -all remaining transactions with the new block state. - -## Broadcast - -`--mempool.broadcast=false` (default: true) - -Determines whether this node gossips any valid transactions -that arrive in mempool. Default is to gossip anything that -passes checktx. If this is disabled, transactions are not -gossiped, but instead stored locally and added to the next -block this node is the proposer. - -## WalDir - -`--mempool.wal_dir=/tmp/gaia/mempool.wal` (default: $TM_HOME/data/mempool.wal) - -This defines the directory where mempool writes the write-ahead -logs. These files can be used to reload unbroadcasted -transactions if the node crashes. - -If the directory passed in is an absolute path, the wal file is -created there. If the directory is a relative path, the path is -appended to home directory of the tendermint process to -generate an absolute path to the wal directory -(default `$HOME/.tendermint` or set via `TM_HOME` or `--home`) diff --git a/spec/reactors/mempool/functionality.md b/spec/reactors/mempool/functionality.md deleted file mode 100644 index 416ffad97..000000000 --- a/spec/reactors/mempool/functionality.md +++ /dev/null @@ -1,43 +0,0 @@ -# Mempool Functionality - -The mempool maintains a list of potentially valid transactions, -both to broadcast to other nodes, as well as to provide to the -consensus reactor when it is selected as the block proposer. - -There are two sides to the mempool state: - -- External: get, check, and broadcast new transactions -- Internal: return valid transaction, update list after block commit - -## External functionality - -External functionality is exposed via network interfaces -to potentially untrusted actors. - -- CheckTx - triggered via RPC or P2P -- Broadcast - gossip messages after a successful check - -## Internal functionality - -Internal functionality is exposed via method calls to other -code compiled into the tendermint binary. - -- ReapMaxBytesMaxGas - get txs to propose in the next block. Guarantees that the - size of the txs is less than MaxBytes, and gas is less than MaxGas -- Update - remove tx that were included in last block -- ABCI.CheckTx - call ABCI app to validate the tx - -What does it provide the consensus reactor? -What guarantees does it need from the ABCI app? -(talk about interleaving processes in concurrency) - -## Optimizations - -The implementation within this library also implements a tx cache. -This is so that signatures don't have to be reverified if the tx has -already been seen before. -However, we only store valid txs in the cache, not invalid ones. -This is because invalid txs could become good later. -Txs that are included in a block aren't removed from the cache, -as they still may be getting received over the p2p network. -These txs are stored in the cache by their hash, to mitigate memory concerns. diff --git a/spec/reactors/mempool/messages.md b/spec/reactors/mempool/messages.md deleted file mode 100644 index a71caf590..000000000 --- a/spec/reactors/mempool/messages.md +++ /dev/null @@ -1,52 +0,0 @@ -# Mempool Messages - -## P2P Messages - -There is currently only one message that Mempool broadcasts and receives over -the p2p gossip network (via the reactor): `TxsMessage` - -```go -// TxsMessage is a MempoolMessage containing a list of transactions. -type TxsMessage struct { - Txs []types.Tx -} -``` - -## RPC Messages - -Mempool exposes `CheckTx([]byte)` over the RPC interface. - -It can be posted via `broadcast_commit`, `broadcast_sync` or -`broadcast_async`. They all parse a message with one argument, -`"tx": "HEX_ENCODED_BINARY"` and differ in only how long they -wait before returning (sync makes sure CheckTx passes, commit -makes sure it was included in a signed block). - -Request (`POST http://gaia.zone:26657/`): - -```json -{ - "id": "", - "jsonrpc": "2.0", - "method": "broadcast_sync", - "params": { - "tx": "F012A4BC68..." - } -} -``` - -Response: - -```json -{ - "error": "", - "result": { - "hash": "E39AAB7A537ABAA237831742DCE1117F187C3C52", - "log": "", - "data": "", - "code": 0 - }, - "id": "", - "jsonrpc": "2.0" -} -``` diff --git a/spec/reactors/mempool/reactor.md b/spec/reactors/mempool/reactor.md deleted file mode 100644 index 03fa1b7bd..000000000 --- a/spec/reactors/mempool/reactor.md +++ /dev/null @@ -1,27 +0,0 @@ -# Mempool Reactor - -## Channels - -See [this issue](https://github.com/tendermint/tendermint/issues/1503) - -Mempool maintains a cache of the last 10000 transactions to prevent -replaying old transactions (plus transactions coming from other -validators, who are continually exchanging transactions). Read [Replay -Protection](https://github.com/tendermint/tendermint/blob/8cdaa7f515a9d366bbc9f0aff2a263a1a6392ead/docs/app-dev/app-development.md#replay-protection) -for details. - -Sending incorrectly encoded data or data exceeding `maxMsgSize` will result -in stopping the peer. - -`maxMsgSize` equals `MaxBatchBytes` (10MB) + 4 (proto overhead). -`MaxBatchBytes` is a mempool config parameter -> defined locally. The reactor -sends transactions to the connected peers in batches. The maximum size of one -batch is `MaxBatchBytes`. - -The mempool will not send a tx back to any peer which it received it from. - -The reactor assigns an `uint16` number for each peer and maintains a map from -p2p.ID to `uint16`. Each mempool transaction carries a list of all the senders -(`[]uint16`). The list is updated every time mempool receives a transaction it -is already seen. `uint16` assumes that a node will never have over 65535 active -peers (0 is reserved for unknown source - e.g. RPC). diff --git a/spec/reactors/pex/pex.md b/spec/reactors/pex/pex.md deleted file mode 100644 index ccd4c836c..000000000 --- a/spec/reactors/pex/pex.md +++ /dev/null @@ -1,164 +0,0 @@ -# Peer Strategy and Exchange - -Here we outline the design of the AddressBook -and how it used by the Peer Exchange Reactor (PEX) to ensure we are connected -to good peers and to gossip peers to others. - -## Peer Types - -Certain peers are special in that they are specified by the user as `persistent`, -which means we auto-redial them if the connection fails, or if we fail to dial -them. -Some peers can be marked as `private`, which means -we will not put them in the address book or gossip them to others. - -All peers except private peers and peers coming from them are tracked using the -address book. - -The rest of our peers are only distinguished by being either -inbound (they dialed our public address) or outbound (we dialed them). - -## Discovery - -Peer discovery begins with a list of seeds. - -When we don't have enough peers, we - -1. ask existing peers -2. dial seeds if we're not dialing anyone currently - -On startup, we will also immediately dial the given list of `persistent_peers`, -and will attempt to maintain persistent connections with them. If the -connections die, or we fail to dial, we will redial every 5s for a few minutes, -then switch to an exponential backoff schedule, and after about a day of -trying, stop dialing the peer. This behavior is when `persistent_peers_max_dial_period` is configured to zero. - -But If `persistent_peers_max_dial_period` is set greater than zero, terms between each dial to each persistent peer -will not exceed `persistent_peers_max_dial_period` during exponential backoff. -Therefore, `dial_period` = min(`persistent_peers_max_dial_period`, `exponential_backoff_dial_period`) -and we keep trying again regardless of `maxAttemptsToDial` - -As long as we have less than `MaxNumOutboundPeers`, we periodically request -additional peers from each of our own and try seeds. - -## Listening - -Peers listen on a configurable ListenAddr that they self-report in their -NodeInfo during handshakes with other peers. Peers accept up to -`MaxNumInboundPeers` incoming peers. - -## Address Book - -Peers are tracked via their ID (their PubKey.Address()). -Peers are added to the address book from the PEX when they first connect to us or -when we hear about them from other peers. - -The address book is arranged in sets of buckets, and distinguishes between -vetted (old) and unvetted (new) peers. It keeps different sets of buckets for -vetted and unvetted peers. Buckets provide randomization over peer selection. -Peers are put in buckets according to their IP groups. - -IP group can be a masked IP (e.g. `1.2.0.0` or `2602:100::`) or `local` for -local addresses or `unroutable` for unroutable addresses. The mask which -corresponds to the `/16` subnet is used for IPv4, `/32` subnet - for IPv6. -Each group has a limited number of buckets to prevent DoS attacks coming from -that group (e.g. an attacker buying a `/16` block of IPs and launching a DoS -attack). - -[highwayhash](https://arxiv.org/abs/1612.06257) is used as a hashing function -when calculating a bucket. - -When placing a peer into a new bucket: - -```md -hash(key + sourcegroup + int64(hash(key + group + sourcegroup)) % bucket_per_group) % num_new_buckets -``` - -When placing a peer into an old bucket: - -```md -hash(key + group + int64(hash(key + addr)) % buckets_per_group) % num_old_buckets -``` - -where `key` - random 24 HEX string, `group` - IP group of the peer (e.g. `1.2.0.0`), -`sourcegroup` - IP group of the sender (peer who sent us this address) (e.g. `174.11.0.0`), -`addr` - string representation of the peer's address (e.g. `174.11.10.2:26656`). - -A vetted peer can only be in one bucket. An unvetted peer can be in multiple buckets, and -each instance of the peer can have a different IP:PORT. - -If we're trying to add a new peer but there's no space in its bucket, we'll -remove the worst peer from that bucket to make room. - -## Vetting - -When a peer is first added, it is unvetted. -Marking a peer as vetted is outside the scope of the `p2p` package. -For Tendermint, a Peer becomes vetted once it has contributed sufficiently -at the consensus layer; ie. once it has sent us valid and not-yet-known -votes and/or block parts for `NumBlocksForVetted` blocks. -Other users of the p2p package can determine their own conditions for when a peer is marked vetted. - -If a peer becomes vetted but there are already too many vetted peers, -a randomly selected one of the vetted peers becomes unvetted. - -If a peer becomes unvetted (either a new peer, or one that was previously vetted), -a randomly selected one of the unvetted peers is removed from the address book. - -More fine-grained tracking of peer behaviour can be done using -a trust metric (see below), but it's best to start with something simple. - -## Select Peers to Dial - -When we need more peers, we pick addresses randomly from the addrbook with some -configurable bias for unvetted peers. The bias should be lower when we have -fewer peers and can increase as we obtain more, ensuring that our first peers -are more trustworthy, but always giving us the chance to discover new good -peers. - -We track the last time we dialed a peer and the number of unsuccessful attempts -we've made. If too many attempts are made, we mark the peer as bad. - -Connection attempts are made with exponential backoff (plus jitter). Because -the selection process happens every `ensurePeersPeriod`, we might not end up -dialing a peer for much longer than the backoff duration. - -If we fail to connect to the peer after 16 tries (with exponential backoff), we -remove from address book completely. But for persistent peers, we indefinitely try to -dial all persistent peers unless `persistent_peers_max_dial_period` is configured to zero - -## Select Peers to Exchange - -When we’re asked for peers, we select them as follows: - -- select at most `maxGetSelection` peers -- try to select at least `minGetSelection` peers - if we have less than that, select them all. -- select a random, unbiased `getSelectionPercent` of the peers - -Send the selected peers. Note we select peers for sending without bias for vetted/unvetted. - -## Preventing Spam - -There are various cases where we decide a peer has misbehaved and we disconnect from them. -When this happens, the peer is removed from the address book and black listed for -some amount of time. We call this "Disconnect and Mark". -Note that the bad behaviour may be detected outside the PEX reactor itself -(for instance, in the mconnection, or another reactor), but it must be communicated to the PEX reactor -so it can remove and mark the peer. - -In the PEX, if a peer sends us an unsolicited list of peers, -or if the peer sends a request too soon after another one, -we Disconnect and MarkBad. - -## Trust Metric - -The quality of peers can be tracked in more fine-grained detail using a -Proportional-Integral-Derivative (PID) controller that incorporates -current, past, and rate-of-change data to inform peer quality. - -While a PID trust metric has been implemented, it remains for future work -to use it in the PEX. - -See the [trustmetric](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-006-trust-metric.md) -and [trustmetric useage](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-007-trust-metric-usage.md) -architecture docs for more details. diff --git a/spec/reactors/pex/reactor.md b/spec/reactors/pex/reactor.md deleted file mode 100644 index 468f182cc..000000000 --- a/spec/reactors/pex/reactor.md +++ /dev/null @@ -1,12 +0,0 @@ -# PEX Reactor - -## Channels - -Defines only `SendQueueCapacity`. [#1503](https://github.com/tendermint/tendermint/issues/1503) - -Implements rate-limiting by enforcing minimal time between two consecutive -`pexRequestMessage` requests. If the peer sends us addresses we did not ask, -it is stopped. - -Sending incorrectly encoded data or data exceeding `maxMsgSize` will result -in stopping the peer. diff --git a/spec/reactors/readme.md b/spec/reactors/readme.md deleted file mode 100644 index 82a19485b..000000000 --- a/spec/reactors/readme.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -cards: true ---- - -# Reactors diff --git a/spec/reactors/state_sync/reactor.md b/spec/reactors/state_sync/reactor.md deleted file mode 100644 index d3c043087..000000000 --- a/spec/reactors/state_sync/reactor.md +++ /dev/null @@ -1,77 +0,0 @@ -# State Sync Reactor - -State sync allows new nodes to rapidly bootstrap and join the network by discovering, fetching, -and restoring state machine snapshots. For more information, see the [state sync ABCI section](../../abci/apps.md#state-sync). - -The state sync reactor has two main responsibilites: - -* Serving state machine snapshots taken by the local ABCI application to new nodes joining the - network. - -* Discovering existing snapshots and fetching snapshot chunks for an empty local application - being bootstrapped. - -The state sync process for bootstrapping a new node is described in detail in the section linked -above. While technically part of the reactor (see `statesync/syncer.go` and related components), -this document will only cover the P2P reactor component. - -For details on the ABCI methods and data types, see the [ABCI documentation](../../abci/abci.md). - -## State Sync P2P Protocol - -When a new node begin state syncing, it will ask all peers it encounters if it has any -available snapshots: - -```go -type snapshotsRequestMessage struct{} -``` - -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: - -```go -type snapshotsResponseMessage struct { - Height uint64 - Format uint32 - Chunks uint32 - Hash []byte - Metadata []byte -} -``` - -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: - -```go -type chunkRequestMessage struct { - Height uint64 - Format uint32 - Index uint32 -} -``` - -The receiver will load the requested chunk from its local application via `LoadSnapshotChunk`, -and respond with it (limited to 16 MB): - -```go -type chunkResponseMessage struct { - Height uint64 - Format uint32 - Index uint32 - Chunk []byte - Missing bool -} -``` - -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. - -If no state sync is in progress (i.e. during normal operation), any unsolicited response messages -are discarded. From 2f590a6392b8441b0347edd658c244cc4794ae7f Mon Sep 17 00:00:00 2001 From: Igor Konnov Date: Thu, 21 Jan 2021 11:50:06 +0100 Subject: [PATCH 134/223] non-critical bugfix in the TLA+ spec (found by new version of apalache) (#244) --- rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla b/rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla index bb214bf9b..9d3a543d4 100644 --- a/rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla +++ b/rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla @@ -332,7 +332,7 @@ OnQuorumOfNilPrevotes(p) == /\ Cardinality(PV) >= THRESHOLD2 \* line 36 /\ evidence' = PV \union evidence /\ BroadcastPrecommit(p, round[p], Id(NilValue)) - /\ step' = [step EXCEPT ![p] = "PREVOTE"] + /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] /\ UNCHANGED <> /\ action' = "OnQuorumOfNilPrevotes" From 038f3e025a19fed9dc96e718b9834ab1b545f136 Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 27 Jan 2021 11:29:54 +0000 Subject: [PATCH 135/223] params: remove block timeiota (#248) --- spec/abci/abci.md | 15 ++------------- spec/consensus/bft-time.md | 7 +++---- spec/core/data_structures.md | 1 - 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index cef54344c..b55a7fc5b 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -674,21 +674,10 @@ The data types not listed below are the same as the [core data structures](../co | Name | Type | Description | Field Number | |-----------|---------------------------------------------------------------|------------------------------------------------------------------------------|--------------| - | block | [BlockParams](#blockparams) | Parameters limiting the size of a block and time between consecutive blocks. | 1 | + | 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 | [BlockParams](../core/data_structures.md#versionparams) | The ABCI application version. | 4 | - -### BlockParams - -- **Fields**: - - | 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 | - -> Note: time_iota_ms is removed from this data structure. + | version | [VersionsParams](../core/data_structures.md#versionparams) | The ABCI application version. | 4 | ### ProofOps diff --git a/spec/consensus/bft-time.md b/spec/consensus/bft-time.md index 3d4421d54..cec3b91ab 100644 --- a/spec/consensus/bft-time.md +++ b/spec/consensus/bft-time.md @@ -45,12 +45,11 @@ 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 + config.BlockTimeIota, time.Now())`, where `time.Now()` - denotes local Unix time in milliseconds, and `config.BlockTimeIota` is a configuration parameter that corresponds - to the minimum timestamp increment of the next block. + `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 + config.BlockTimeIota, time.Now())`, + `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/core/data_structures.md b/spec/core/data_structures.md index d2919bb8c..4906db423 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -445,7 +445,6 @@ func SumTruncated(bz []byte) []byte { |--------------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| | 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 | -| time_iota_ms | int64 | (**Deprecated**) Unused Param | 3 | ### EvidenceParams From abaffef9120f01bfbbfdfdc7ba01dbf64ae919d5 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 28 Jan 2021 12:10:22 +0000 Subject: [PATCH 136/223] proto: add files (#246) Co-authored-by: Erik Grinaker --- .github/workflows/linter.yml | 2 + proto/README.md | 23 ++ proto/abci/types.proto | 392 +++++++++++++++++++++++++++++++++++ proto/blockchain/types.proto | 41 ++++ proto/consensus/types.proto | 92 ++++++++ proto/mempool/types.proto | 16 ++ proto/p2p/conn.proto | 31 +++ proto/p2p/pex.proto | 20 ++ proto/p2p/types.proto | 35 ++++ proto/statesync/types.proto | 37 ++++ proto/types/block.proto | 15 ++ proto/types/evidence.proto | 38 ++++ proto/types/params.proto | 77 +++++++ proto/types/types.proto | 157 ++++++++++++++ proto/types/validator.proto | 26 +++ spec/p2p/messages/pex.md | 18 +- 16 files changed, 1011 insertions(+), 9 deletions(-) create mode 100644 proto/README.md create mode 100644 proto/abci/types.proto create mode 100644 proto/blockchain/types.proto create mode 100644 proto/consensus/types.proto create mode 100644 proto/mempool/types.proto create mode 100644 proto/p2p/conn.proto create mode 100644 proto/p2p/pex.proto create mode 100644 proto/p2p/types.proto create mode 100644 proto/statesync/types.proto create mode 100644 proto/types/block.proto create mode 100644 proto/types/evidence.proto create mode 100644 proto/types/params.proto create mode 100644 proto/types/types.proto create mode 100644 proto/types/validator.proto diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 0f12df470..c04923c35 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -26,3 +26,5 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_MD: true MARKDOWN_CONFIG_FILE: .markdownlint.yml + VALIDATE_PROTOBUF: false + VALIDATE_JSCPD: false diff --git a/proto/README.md b/proto/README.md new file mode 100644 index 000000000..ebecd82d1 --- /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/proto/abci/types.proto b/proto/abci/types.proto new file mode 100644 index 000000000..91ac34c04 --- /dev/null +++ b/proto/abci/types.proto @@ -0,0 +1,392 @@ +syntax = "proto3"; +package tendermint.abci; + +option go_package = "github.com/tendermint/tendermint/abci/types"; + +// For more information on gogo.proto, see: +// https://github.com/gogo/protobuf/blob/master/extensions.md +import "tendermint/crypto/proof.proto"; +import "tendermint/types/types.proto"; +import "tendermint/crypto/keys.proto"; +import "tendermint/types/params.proto"; +import "google/protobuf/timestamp.proto"; +import "gogoproto/gogo.proto"; + +// This file is copied from http://github.com/tendermint/abci +// NOTE: When using custom types, mind the warnings. +// https://github.com/gogo/protobuf/blob/master/custom_types.md#warnings-and-issues + +//---------------------------------------- +// Request types + +message Request { + oneof value { + RequestEcho echo = 1; + RequestFlush flush = 2; + RequestInfo info = 3; + RequestInitChain init_chain = 4; + RequestQuery query = 5; + RequestBeginBlock begin_block = 6; + RequestCheckTx check_tx = 7; + RequestDeliverTx deliver_tx = 8; + RequestEndBlock end_block = 9; + RequestCommit commit = 10; + RequestListSnapshots list_snapshots = 11; + RequestOfferSnapshot offer_snapshot = 12; + RequestLoadSnapshotChunk load_snapshot_chunk = 13; + RequestApplySnapshotChunk apply_snapshot_chunk = 14; + } +} + +message RequestEcho { + string message = 1; +} + +message RequestFlush {} + +message RequestInfo { + string version = 1; + uint64 block_version = 2; + uint64 p2p_version = 3; + string abci_version = 4; +} + +message RequestInitChain { + google.protobuf.Timestamp time = 1 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + string chain_id = 2; + ConsensusParams consensus_params = 3; + repeated ValidatorUpdate validators = 4 [(gogoproto.nullable) = false]; + bytes app_state_bytes = 5; + int64 initial_height = 6; +} + +message RequestQuery { + bytes data = 1; + string path = 2; + int64 height = 3; + bool prove = 4; +} + +message RequestBeginBlock { + bytes hash = 1; + tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; + LastCommitInfo last_commit_info = 3 [(gogoproto.nullable) = false]; + repeated Evidence byzantine_validators = 4 [(gogoproto.nullable) = false]; +} + +enum CheckTxType { + NEW = 0 [(gogoproto.enumvalue_customname) = "New"]; + RECHECK = 1 [(gogoproto.enumvalue_customname) = "Recheck"]; +} + +message RequestCheckTx { + bytes tx = 1; + CheckTxType type = 2; +} + +message RequestDeliverTx { + bytes tx = 1; +} + +message RequestEndBlock { + int64 height = 1; +} + +message RequestCommit {} + +// lists available snapshots +message RequestListSnapshots { +} + +// offers a snapshot to the application +message RequestOfferSnapshot { + Snapshot snapshot = 1; // snapshot offered by peers + bytes app_hash = 2; // light client-verified app hash for snapshot height +} + +// loads a snapshot chunk +message RequestLoadSnapshotChunk { + uint64 height = 1; + uint32 format = 2; + uint32 chunk = 3; +} + +// Applies a snapshot chunk +message RequestApplySnapshotChunk { + uint32 index = 1; + bytes chunk = 2; + string sender = 3; +} + +//---------------------------------------- +// Response types + +message Response { + oneof value { + ResponseException exception = 1; + ResponseEcho echo = 2; + ResponseFlush flush = 3; + ResponseInfo info = 4; + ResponseInitChain init_chain = 5; + ResponseQuery query = 6; + ResponseBeginBlock begin_block = 7; + ResponseCheckTx check_tx = 8; + ResponseDeliverTx deliver_tx = 9; + ResponseEndBlock end_block = 10; + ResponseCommit commit = 11; + ResponseListSnapshots list_snapshots = 12; + ResponseOfferSnapshot offer_snapshot = 13; + ResponseLoadSnapshotChunk load_snapshot_chunk = 14; + ResponseApplySnapshotChunk apply_snapshot_chunk = 15; + } +} + +// nondeterministic +message ResponseException { + string error = 1; +} + +message ResponseEcho { + string message = 1; +} + +message ResponseFlush {} + +message ResponseInfo { + string data = 1; + + // this is the software version of the application. TODO: remove? + string version = 2; + uint64 app_version = 3; + + int64 last_block_height = 4; + bytes last_block_app_hash = 5; +} + +message ResponseInitChain { + ConsensusParams consensus_params = 1; + repeated ValidatorUpdate validators = 2 [(gogoproto.nullable) = false]; + bytes app_hash = 3; +} + +message ResponseQuery { + uint32 code = 1; + // bytes data = 2; // use "value" instead. + string log = 3; // nondeterministic + string info = 4; // nondeterministic + int64 index = 5; + bytes key = 6; + bytes value = 7; + tendermint.crypto.ProofOps proof_ops = 8; + int64 height = 9; + string codespace = 10; +} + +message ResponseBeginBlock { + repeated Event events = 1 + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; +} + +message ResponseCheckTx { + uint32 code = 1; + bytes data = 2; + string log = 3; // nondeterministic + string info = 4; // nondeterministic + int64 gas_wanted = 5 [json_name = "gas_wanted"]; + int64 gas_used = 6 [json_name = "gas_used"]; + repeated Event events = 7 + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; + string codespace = 8; +} + +message ResponseDeliverTx { + uint32 code = 1; + bytes data = 2; + string log = 3; // nondeterministic + string info = 4; // nondeterministic + int64 gas_wanted = 5 [json_name = "gas_wanted"]; + int64 gas_used = 6 [json_name = "gas_used"]; + repeated Event events = 7 + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; + string codespace = 8; +} + +message ResponseEndBlock { + repeated ValidatorUpdate validator_updates = 1 + [(gogoproto.nullable) = false]; + ConsensusParams consensus_param_updates = 2; + repeated Event events = 3 + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; +} + +message ResponseCommit { + // reserve 1 + bytes data = 2; + int64 retain_height = 3; +} + +message ResponseListSnapshots { + repeated Snapshot snapshots = 1; +} + +message ResponseOfferSnapshot { + Result result = 1; + + enum Result { + UNKNOWN = 0; // Unknown result, abort all snapshot restoration + ACCEPT = 1; // Snapshot accepted, apply chunks + ABORT = 2; // Abort all snapshot restoration + REJECT = 3; // Reject this specific snapshot, try others + REJECT_FORMAT = 4; // Reject all snapshots of this format, try others + REJECT_SENDER = 5; // Reject all snapshots from the sender(s), try others + } +} + +message ResponseLoadSnapshotChunk { + bytes chunk = 1; +} + +message ResponseApplySnapshotChunk { + Result result = 1; + repeated uint32 refetch_chunks = 2; // Chunks to refetch and reapply + repeated string reject_senders = 3; // Chunk senders to reject and ban + + enum Result { + UNKNOWN = 0; // Unknown result, abort all snapshot restoration + ACCEPT = 1; // Chunk successfully accepted + ABORT = 2; // Abort all snapshot restoration + RETRY = 3; // Retry chunk (combine with refetch and reject) + RETRY_SNAPSHOT = 4; // Retry snapshot (combine with refetch and reject) + REJECT_SNAPSHOT = 5; // Reject this snapshot, try others + } +} + +//---------------------------------------- +// Misc. + +// ConsensusParams contains all consensus-relevant parameters +// that can be adjusted by the abci app +message ConsensusParams { + BlockParams block = 1; + tendermint.types.EvidenceParams evidence = 2; + tendermint.types.ValidatorParams validator = 3; + tendermint.types.VersionParams version = 4; +} + +// BlockParams contains limits on the block size. +message BlockParams { + // Note: must be greater than 0 + int64 max_bytes = 1; + // Note: must be greater or equal to -1 + int64 max_gas = 2; +} + +message LastCommitInfo { + int32 round = 1; + repeated VoteInfo votes = 2 [(gogoproto.nullable) = false]; +} + +// Event allows application developers to attach additional information to +// ResponseBeginBlock, ResponseEndBlock, ResponseCheckTx and ResponseDeliverTx. +// Later, transactions may be queried using these events. +message Event { + string type = 1; + repeated EventAttribute attributes = 2 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "attributes,omitempty" + ]; +} + +// EventAttribute is a single key-value pair, associated with an event. +message EventAttribute { + bytes key = 1; + bytes value = 2; + bool index = 3; // nondeterministic +} + +// TxResult contains results of executing the transaction. +// +// One usage is indexing transaction results. +message TxResult { + int64 height = 1; + uint32 index = 2; + bytes tx = 3; + ResponseDeliverTx result = 4 [(gogoproto.nullable) = false]; +} + +//---------------------------------------- +// Blockchain Types + +// Validator +message Validator { + bytes address = 1; // The first 20 bytes of SHA256(public key) + // PubKey pub_key = 2 [(gogoproto.nullable)=false]; + int64 power = 3; // The voting power +} + +// ValidatorUpdate +message ValidatorUpdate { + tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false]; + int64 power = 2; +} + +// VoteInfo +message VoteInfo { + Validator validator = 1 [(gogoproto.nullable) = false]; + bool signed_last_block = 2; +} + +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; +} + +//---------------------------------------- +// State Sync Types + +message Snapshot { + uint64 height = 1; // The height at which the snapshot was taken + uint32 format = 2; // The application-specific snapshot format + uint32 chunks = 3; // Number of chunks in the snapshot + bytes hash = 4; // Arbitrary snapshot hash, equal only if identical + bytes metadata = 5; // Arbitrary application metadata +} + +//---------------------------------------- +// Service Definition + +service ABCIApplication { + rpc Echo(RequestEcho) returns (ResponseEcho); + rpc Flush(RequestFlush) returns (ResponseFlush); + rpc Info(RequestInfo) returns (ResponseInfo); + rpc DeliverTx(RequestDeliverTx) returns (ResponseDeliverTx); + rpc CheckTx(RequestCheckTx) returns (ResponseCheckTx); + rpc Query(RequestQuery) returns (ResponseQuery); + rpc Commit(RequestCommit) returns (ResponseCommit); + rpc InitChain(RequestInitChain) returns (ResponseInitChain); + rpc BeginBlock(RequestBeginBlock) returns (ResponseBeginBlock); + rpc EndBlock(RequestEndBlock) returns (ResponseEndBlock); + rpc ListSnapshots(RequestListSnapshots) returns (ResponseListSnapshots); + rpc OfferSnapshot(RequestOfferSnapshot) returns (ResponseOfferSnapshot); + rpc LoadSnapshotChunk(RequestLoadSnapshotChunk) returns (ResponseLoadSnapshotChunk); + rpc ApplySnapshotChunk(RequestApplySnapshotChunk) returns (ResponseApplySnapshotChunk); +} diff --git a/proto/blockchain/types.proto b/proto/blockchain/types.proto new file mode 100644 index 000000000..f5c143cf5 --- /dev/null +++ b/proto/blockchain/types.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; +package tendermint.blockchain; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/blockchain"; + +import "tendermint/types/block.proto"; + +// BlockRequest requests a block for a specific height +message BlockRequest { + int64 height = 1; +} + +// NoBlockResponse informs the node that the peer does not have block at the requested height +message NoBlockResponse { + int64 height = 1; +} + +// BlockResponse returns block to the requested +message BlockResponse { + tendermint.types.Block block = 1; +} + +// StatusRequest requests the status of a peer. +message StatusRequest { +} + +// StatusResponse is a peer response to inform their status. +message StatusResponse { + int64 height = 1; + int64 base = 2; +} + +message Message { + oneof sum { + BlockRequest block_request = 1; + NoBlockResponse no_block_response = 2; + BlockResponse block_response = 3; + StatusRequest status_request = 4; + StatusResponse status_response = 5; + } +} diff --git a/proto/consensus/types.proto b/proto/consensus/types.proto new file mode 100644 index 000000000..6e1f41371 --- /dev/null +++ b/proto/consensus/types.proto @@ -0,0 +1,92 @@ +syntax = "proto3"; +package tendermint.consensus; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/consensus"; + +import "gogoproto/gogo.proto"; +import "tendermint/types/types.proto"; +import "tendermint/libs/bits/types.proto"; + +// NewRoundStep is sent for every step taken in the ConsensusState. +// For every height/round/step transition +message NewRoundStep { + int64 height = 1; + int32 round = 2; + uint32 step = 3; + int64 seconds_since_start_time = 4; + int32 last_commit_round = 5; +} + +// 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. +// In case the block is also committed, then IsCommit flag is set to true. +message NewValidBlock { + int64 height = 1; + int32 round = 2; + tendermint.types.PartSetHeader block_part_set_header = 3 [(gogoproto.nullable) = false]; + tendermint.libs.bits.BitArray block_parts = 4; + bool is_commit = 5; +} + +// Proposal is sent when a new block is proposed. +message Proposal { + tendermint.types.Proposal proposal = 1 [(gogoproto.nullable) = false]; +} + +// ProposalPOL is sent when a previous proposal is re-proposed. +message ProposalPOL { + int64 height = 1; + int32 proposal_pol_round = 2; + tendermint.libs.bits.BitArray proposal_pol = 3 [(gogoproto.nullable) = false]; +} + +// BlockPart is sent when gossipping a piece of the proposed block. +message BlockPart { + int64 height = 1; + int32 round = 2; + tendermint.types.Part part = 3 [(gogoproto.nullable) = false]; +} + +// Vote is sent when voting for a proposal (or lack thereof). +message Vote { + tendermint.types.Vote vote = 1; +} + +// HasVote is sent to indicate that a particular vote has been received. +message HasVote { + int64 height = 1; + int32 round = 2; + tendermint.types.SignedMsgType type = 3; + int32 index = 4; +} + +// VoteSetMaj23 is sent to indicate that a given BlockID has seen +2/3 votes. +message VoteSetMaj23 { + int64 height = 1; + int32 round = 2; + tendermint.types.SignedMsgType type = 3; + tendermint.types.BlockID block_id = 4 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; +} + +// VoteSetBits is sent to communicate the bit-array of votes seen for the BlockID. +message VoteSetBits { + int64 height = 1; + int32 round = 2; + tendermint.types.SignedMsgType type = 3; + tendermint.types.BlockID block_id = 4 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; + tendermint.libs.bits.BitArray votes = 5 [(gogoproto.nullable) = false]; +} + +message Message { + oneof sum { + NewRoundStep new_round_step = 1; + NewValidBlock new_valid_block = 2; + Proposal proposal = 3; + ProposalPOL proposal_pol = 4; + BlockPart block_part = 5; + Vote vote = 6; + HasVote has_vote = 7; + VoteSetMaj23 vote_set_maj23 = 8; + VoteSetBits vote_set_bits = 9; + } +} diff --git a/proto/mempool/types.proto b/proto/mempool/types.proto new file mode 100644 index 000000000..eaa7b4d67 --- /dev/null +++ b/proto/mempool/types.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package tendermint.mempool; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/mempool"; + +// Txs list of transactions. +// Note because they are sent over the wire does not mean they are valid transactions +message Txs { + repeated bytes txs = 1; +} + +message Message { + oneof sum { + Txs txs = 1; + } +} diff --git a/proto/p2p/conn.proto b/proto/p2p/conn.proto new file mode 100644 index 000000000..718df97d9 --- /dev/null +++ b/proto/p2p/conn.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; +package tendermint.p2p; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; + +import "gogoproto/gogo.proto"; +import "tendermint/crypto/keys.proto"; + +message PacketPing {} + +message PacketPong {} + +message PacketMsg { + int32 channel_id = 1 [(gogoproto.customname) = "ChannelID"]; + bool eof = 2 [(gogoproto.customname) = "EOF"]; + bytes data = 3; +} + +message Packet { + oneof sum { + PacketPing packet_ping = 1; + PacketPong packet_pong = 2; + PacketMsg packet_msg = 3; + } +} + +// AuthSigMessage is used while upgrading an insecure connection. +message AuthSigMessage { + tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false]; + bytes sig = 2; +} diff --git a/proto/p2p/pex.proto b/proto/p2p/pex.proto new file mode 100644 index 000000000..04ebccf31 --- /dev/null +++ b/proto/p2p/pex.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package tendermint.p2p; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; + +import "tendermint/p2p/types.proto"; +import "gogoproto/gogo.proto"; + +message PexRequest {} + +message PexResponse { + repeated NetAddress addresses = 1 [(gogoproto.nullable) = false]; +} + +message PexMessage { + oneof sum { + PexRequest pex_request = 1; + PexResponse pex_response = 2; + } +} diff --git a/proto/p2p/types.proto b/proto/p2p/types.proto new file mode 100644 index 000000000..739d99b13 --- /dev/null +++ b/proto/p2p/types.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; +package tendermint.p2p; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; + +import "gogoproto/gogo.proto"; + +message NetAddress { + string id = 1 [(gogoproto.customname) = "ID"]; + string ip = 2 [(gogoproto.customname) = "IP"]; + uint32 port = 3; +} + +message ProtocolVersion { + uint64 p2p = 1 [(gogoproto.customname) = "P2P"]; + uint64 block = 2; + uint64 app = 3; +} + +// NodeInfo is the information exchanged between two peers when conducting the handshake. +message NodeInfo { + ProtocolVersion protocol_version = 1 [(gogoproto.nullable) = false]; + string node_id = 2 [(gogoproto.customname) = "NodeID"]; + string listen_addr = 3; + string network = 4; + string version = 5; + bytes channels = 6; + string moniker = 7; + NodeInfoOther other = 8 [(gogoproto.nullable) = false]; +} + +message NodeInfoOther { + string tx_index = 1; + string rpc_address = 2 [(gogoproto.customname) = "RPCAddress"]; +} diff --git a/proto/statesync/types.proto b/proto/statesync/types.proto new file mode 100644 index 000000000..8d4a714c1 --- /dev/null +++ b/proto/statesync/types.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; +package tendermint.statesync; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/statesync"; + +message Message { + oneof sum { + SnapshotsRequest snapshots_request = 1; + SnapshotsResponse snapshots_response = 2; + ChunkRequest chunk_request = 3; + ChunkResponse chunk_response = 4; + } +} + +message SnapshotsRequest {} + +message SnapshotsResponse { + uint64 height = 1; + uint32 format = 2; + uint32 chunks = 3; + bytes hash = 4; + bytes metadata = 5; +} + +message ChunkRequest { + uint64 height = 1; + uint32 format = 2; + uint32 index = 3; +} + +message ChunkResponse { + uint64 height = 1; + uint32 format = 2; + uint32 index = 3; + bytes chunk = 4; + bool missing = 5; +} diff --git a/proto/types/block.proto b/proto/types/block.proto new file mode 100644 index 000000000..84e9bb15d --- /dev/null +++ b/proto/types/block.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "tendermint/types/types.proto"; +import "tendermint/types/evidence.proto"; + +message Block { + Header header = 1 [(gogoproto.nullable) = false]; + Data data = 2 [(gogoproto.nullable) = false]; + tendermint.types.EvidenceList evidence = 3 [(gogoproto.nullable) = false]; + Commit last_commit = 4; +} diff --git a/proto/types/evidence.proto b/proto/types/evidence.proto new file mode 100644 index 000000000..451b8dca3 --- /dev/null +++ b/proto/types/evidence.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "tendermint/types/types.proto"; +import "tendermint/types/validator.proto"; + +message Evidence { + oneof sum { + DuplicateVoteEvidence duplicate_vote_evidence = 1; + LightClientAttackEvidence light_client_attack_evidence = 2; + } +} + +// DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. +message DuplicateVoteEvidence { + tendermint.types.Vote vote_a = 1; + tendermint.types.Vote vote_b = 2; + int64 total_voting_power = 3; + int64 validator_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +// LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. +message LightClientAttackEvidence { + tendermint.types.LightBlock conflicting_block = 1; + int64 common_height = 2; + repeated tendermint.types.Validator byzantine_validators = 3; + int64 total_voting_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +message EvidenceList { + repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/types/params.proto b/proto/types/params.proto new file mode 100644 index 000000000..bfe933676 --- /dev/null +++ b/proto/types/params.proto @@ -0,0 +1,77 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/duration.proto"; + +option (gogoproto.equal_all) = true; + +// ConsensusParams contains consensus critical parameters that determine the +// validity of blocks. +message ConsensusParams { + BlockParams block = 1 [(gogoproto.nullable) = false]; + EvidenceParams evidence = 2 [(gogoproto.nullable) = false]; + ValidatorParams validator = 3 [(gogoproto.nullable) = false]; + VersionParams version = 4 [(gogoproto.nullable) = false]; +} + +// BlockParams contains limits on the block size. +message BlockParams { + // Max block size, in bytes. + // Note: must be greater than 0 + int64 max_bytes = 1; + // Max gas per block. + // Note: must be greater or equal to -1 + int64 max_gas = 2; + // This parameter is unused. + int64 time_iota_ms = 3; +} + +// EvidenceParams determine how we handle evidence of malfeasance. +message EvidenceParams { + // Max age of evidence, in blocks. + // + // The basic formula for calculating this is: MaxAgeDuration / {average block + // time}. + int64 max_age_num_blocks = 1; + + // 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). + google.protobuf.Duration max_age_duration = 2 + [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; + + // This sets the maximum size of total evidence in bytes that can be committed in a single block. + // and should fall comfortably under the max block bytes. + // Default is 1048576 or 1MB + int64 max_bytes = 3; +} + +// ValidatorParams restrict the public key types validators can use. +// NOTE: uses ABCI pubkey naming, not Amino names. +message ValidatorParams { + option (gogoproto.populate) = true; + option (gogoproto.equal) = true; + + repeated string pub_key_types = 1; +} + +// VersionParams contains the ABCI application version. +message VersionParams { + option (gogoproto.populate) = true; + option (gogoproto.equal) = true; + + uint64 app_version = 1; +} + +// HashedParams is a subset of ConsensusParams. +// +// It is hashed into the Header.ConsensusHash. +message HashedParams { + int64 block_max_bytes = 1; + int64 block_max_gas = 2; +} diff --git a/proto/types/types.proto b/proto/types/types.proto new file mode 100644 index 000000000..8d4f00972 --- /dev/null +++ b/proto/types/types.proto @@ -0,0 +1,157 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "tendermint/crypto/proof.proto"; +import "tendermint/version/types.proto"; +import "tendermint/types/validator.proto"; + +// BlockIdFlag indicates which BlcokID the signature is for +enum BlockIDFlag { + option (gogoproto.goproto_enum_stringer) = true; + option (gogoproto.goproto_enum_prefix) = false; + + BLOCK_ID_FLAG_UNKNOWN = 0 [(gogoproto.enumvalue_customname) = "BlockIDFlagUnknown"]; + BLOCK_ID_FLAG_ABSENT = 1 [(gogoproto.enumvalue_customname) = "BlockIDFlagAbsent"]; + BLOCK_ID_FLAG_COMMIT = 2 [(gogoproto.enumvalue_customname) = "BlockIDFlagCommit"]; + BLOCK_ID_FLAG_NIL = 3 [(gogoproto.enumvalue_customname) = "BlockIDFlagNil"]; +} + +// SignedMsgType is a type of signed message in the consensus. +enum SignedMsgType { + option (gogoproto.goproto_enum_stringer) = true; + option (gogoproto.goproto_enum_prefix) = false; + + SIGNED_MSG_TYPE_UNKNOWN = 0 [(gogoproto.enumvalue_customname) = "UnknownType"]; + // Votes + SIGNED_MSG_TYPE_PREVOTE = 1 [(gogoproto.enumvalue_customname) = "PrevoteType"]; + SIGNED_MSG_TYPE_PRECOMMIT = 2 [(gogoproto.enumvalue_customname) = "PrecommitType"]; + + // Proposals + SIGNED_MSG_TYPE_PROPOSAL = 32 [(gogoproto.enumvalue_customname) = "ProposalType"]; +} + +// PartsetHeader +message PartSetHeader { + uint32 total = 1; + bytes hash = 2; +} + +message Part { + uint32 index = 1; + bytes bytes = 2; + tendermint.crypto.Proof proof = 3 [(gogoproto.nullable) = false]; +} + +// BlockID +message BlockID { + bytes hash = 1; + PartSetHeader part_set_header = 2 [(gogoproto.nullable) = false]; +} + +// -------------------------------- + +// Header defines the structure of a Tendermint block header. +message Header { + // basic block info + tendermint.version.Consensus version = 1 [(gogoproto.nullable) = false]; + string chain_id = 2 [(gogoproto.customname) = "ChainID"]; + int64 height = 3; + google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + + // prev block info + BlockID last_block_id = 5 [(gogoproto.nullable) = false]; + + // hashes of block data + bytes last_commit_hash = 6; // commit from validators from the last block + bytes data_hash = 7; // transactions + + // hashes from the app output from the prev block + bytes validators_hash = 8; // validators for the current block + bytes next_validators_hash = 9; // validators for the next block + bytes consensus_hash = 10; // consensus params for current block + bytes app_hash = 11; // state after txs from the previous block + bytes last_results_hash = 12; // root hash of all results from the txs from the previous block + + // consensus info + bytes evidence_hash = 13; // evidence included in the block + bytes proposer_address = 14; // original proposer of the block +} + +// Data contains the set of transactions included in the block +message Data { + // Txs that will be applied by state @ block.Height+1. + // NOTE: not all txs here are valid. We're just agreeing on the order first. + // This means that block.AppHash does not include these txs. + repeated bytes txs = 1; +} + +// Vote represents a prevote, precommit, or commit vote from validators for +// consensus. +message Vote { + SignedMsgType type = 1; + int64 height = 2; + int32 round = 3; + BlockID block_id = 4 + [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; // zero if vote is nil. + google.protobuf.Timestamp timestamp = 5 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes validator_address = 6; + int32 validator_index = 7; + bytes signature = 8; +} + +// Commit contains the evidence that a block was committed by a set of validators. +message Commit { + int64 height = 1; + int32 round = 2; + BlockID block_id = 3 [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; + repeated CommitSig signatures = 4 [(gogoproto.nullable) = false]; +} + +// CommitSig is a part of the Vote included in a Commit. +message CommitSig { + BlockIDFlag block_id_flag = 1; + bytes validator_address = 2; + google.protobuf.Timestamp timestamp = 3 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes signature = 4; +} + +message Proposal { + SignedMsgType type = 1; + int64 height = 2; + int32 round = 3; + int32 pol_round = 4; + BlockID block_id = 5 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; + google.protobuf.Timestamp timestamp = 6 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes signature = 7; +} + +message SignedHeader { + Header header = 1; + Commit commit = 2; +} + +message LightBlock { + SignedHeader signed_header = 1; + tendermint.types.ValidatorSet validator_set = 2; +} + +message BlockMeta { + BlockID block_id = 1 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; + int64 block_size = 2; + Header header = 3 [(gogoproto.nullable) = false]; + int64 num_txs = 4; +} + +// TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree. +message TxProof { + bytes root_hash = 1; + bytes data = 2; + tendermint.crypto.Proof proof = 3; +} diff --git a/proto/types/validator.proto b/proto/types/validator.proto new file mode 100644 index 000000000..c33ae4d24 --- /dev/null +++ b/proto/types/validator.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "tendermint/crypto/keys.proto"; + +message ValidatorSet { + repeated Validator validators = 1; + Validator proposer = 2; + int64 total_voting_power = 3; +} + +message Validator { + bytes address = 1; + tendermint.crypto.PublicKey pub_key = 2 [(gogoproto.nullable) = false]; + int64 voting_power = 3; + int64 proposer_priority = 4; +} + +// Used to compute the validator hash within the ValidatorSetHash +message SimpleValidator { + tendermint.crypto.PublicKey pub_key = 1; + int64 voting_power = 2; +} diff --git a/spec/p2p/messages/pex.md b/spec/p2p/messages/pex.md index dc11b88c9..322d03d63 100644 --- a/spec/p2p/messages/pex.md +++ b/spec/p2p/messages/pex.md @@ -20,17 +20,17 @@ PexRequest is an empty message requesting a list of peers. > EmptyRequest -### PexAddrs +### PexResponse -PexAddrs is an list of net addresses provided to a peer to dial. +PexResponse is an list of net addresses provided to a peer to dial. | Name | Type | Description | Field Number | |-------|------------------------------------|------------------------------------------|--------------| -| addrs | repeated [NetAddress](#netaddress) | List of peer addresses available to dial | 1 | +| addresses | repeated [PexAddress](#Pexaddress) | List of peer addresses available to dial | 1 | -### NetAddress +### PexAddress -NetAddress provides needed information for a node to dial a peer. +PexAddress provides needed information for a node to dial a peer. | Name | Type | Description | Field Number | |------|--------|------------------|--------------| @@ -42,7 +42,7 @@ NetAddress provides needed information for a node to dial a peer. 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_addrs | [PexAddrs] | List of addresses to dial | 2 | +| Name | Type | Description | Field Number | +|--------------|---------------------------|------------------------------------------------------|--------------| +| pex_request | [PexRequest](#pexrequest) | Empty request asking for a list of addresses to dial | 1 | +| pex_response | [PexResponse] | List of addresses to dial | 2 | From b315f049805e8249dbee699c6be0526cb743305e Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 16 Feb 2021 13:06:00 +0000 Subject: [PATCH 137/223] proto: modify height int64 to uint64 (#253) --- proto/abci/types.proto | 22 +++++++++++----------- proto/blockchain/types.proto | 8 ++++---- proto/consensus/types.proto | 14 +++++++------- proto/p2p/conn.proto | 1 - proto/p2p/pex.proto | 9 +++++++-- proto/p2p/types.proto | 21 ++++++++++++++------- proto/types/evidence.proto | 2 +- proto/types/params.proto | 7 ++----- proto/types/types.proto | 8 ++++---- spec/core/data_structures.md | 10 +++++----- 10 files changed, 55 insertions(+), 47 deletions(-) diff --git a/proto/abci/types.proto b/proto/abci/types.proto index 91ac34c04..71b386e2f 100644 --- a/proto/abci/types.proto +++ b/proto/abci/types.proto @@ -48,7 +48,7 @@ message RequestInfo { string version = 1; uint64 block_version = 2; uint64 p2p_version = 3; - string abci_version = 4; + string abci_version = 4; } message RequestInitChain { @@ -58,13 +58,13 @@ message RequestInitChain { ConsensusParams consensus_params = 3; repeated ValidatorUpdate validators = 4 [(gogoproto.nullable) = false]; bytes app_state_bytes = 5; - int64 initial_height = 6; + uint64 initial_height = 6; } message RequestQuery { bytes data = 1; string path = 2; - int64 height = 3; + uint64 height = 3; bool prove = 4; } @@ -90,7 +90,7 @@ message RequestDeliverTx { } message RequestEndBlock { - int64 height = 1; + uint64 height = 1; } message RequestCommit {} @@ -160,8 +160,8 @@ message ResponseInfo { string version = 2; uint64 app_version = 3; - int64 last_block_height = 4; - bytes last_block_app_hash = 5; + uint64 last_block_height = 4; + bytes last_block_app_hash = 5; } message ResponseInitChain { @@ -179,7 +179,7 @@ message ResponseQuery { bytes key = 6; bytes value = 7; tendermint.crypto.ProofOps proof_ops = 8; - int64 height = 9; + uint64 height = 9; string codespace = 10; } @@ -222,8 +222,8 @@ message ResponseEndBlock { message ResponseCommit { // reserve 1 - bytes data = 2; - int64 retain_height = 3; + bytes data = 2; + uint64 retain_height = 3; } message ResponseListSnapshots { @@ -309,7 +309,7 @@ message EventAttribute { // // One usage is indexing transaction results. message TxResult { - int64 height = 1; + uint64 height = 1; uint32 index = 2; bytes tx = 3; ResponseDeliverTx result = 4 [(gogoproto.nullable) = false]; @@ -348,7 +348,7 @@ message Evidence { // The offending validator Validator validator = 2 [(gogoproto.nullable) = false]; // The height when the offense occurred - int64 height = 3; + uint64 height = 3; // The corresponding time where the offense occurred google.protobuf.Timestamp time = 4 [ (gogoproto.nullable) = false, diff --git a/proto/blockchain/types.proto b/proto/blockchain/types.proto index f5c143cf5..5da642887 100644 --- a/proto/blockchain/types.proto +++ b/proto/blockchain/types.proto @@ -7,12 +7,12 @@ import "tendermint/types/block.proto"; // BlockRequest requests a block for a specific height message BlockRequest { - int64 height = 1; + uint64 height = 1; } // NoBlockResponse informs the node that the peer does not have block at the requested height message NoBlockResponse { - int64 height = 1; + uint64 height = 1; } // BlockResponse returns block to the requested @@ -26,8 +26,8 @@ message StatusRequest { // StatusResponse is a peer response to inform their status. message StatusResponse { - int64 height = 1; - int64 base = 2; + uint64 height = 1; + uint64 base = 2; } message Message { diff --git a/proto/consensus/types.proto b/proto/consensus/types.proto index 6e1f41371..f7f94d91d 100644 --- a/proto/consensus/types.proto +++ b/proto/consensus/types.proto @@ -10,7 +10,7 @@ import "tendermint/libs/bits/types.proto"; // NewRoundStep is sent for every step taken in the ConsensusState. // For every height/round/step transition message NewRoundStep { - int64 height = 1; + uint64 height = 1; int32 round = 2; uint32 step = 3; int64 seconds_since_start_time = 4; @@ -21,7 +21,7 @@ message NewRoundStep { //i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r. // In case the block is also committed, then IsCommit flag is set to true. message NewValidBlock { - int64 height = 1; + uint64 height = 1; int32 round = 2; tendermint.types.PartSetHeader block_part_set_header = 3 [(gogoproto.nullable) = false]; tendermint.libs.bits.BitArray block_parts = 4; @@ -35,14 +35,14 @@ message Proposal { // ProposalPOL is sent when a previous proposal is re-proposed. message ProposalPOL { - int64 height = 1; + uint64 height = 1; int32 proposal_pol_round = 2; tendermint.libs.bits.BitArray proposal_pol = 3 [(gogoproto.nullable) = false]; } // BlockPart is sent when gossipping a piece of the proposed block. message BlockPart { - int64 height = 1; + uint64 height = 1; int32 round = 2; tendermint.types.Part part = 3 [(gogoproto.nullable) = false]; } @@ -54,7 +54,7 @@ message Vote { // HasVote is sent to indicate that a particular vote has been received. message HasVote { - int64 height = 1; + uint64 height = 1; int32 round = 2; tendermint.types.SignedMsgType type = 3; int32 index = 4; @@ -62,7 +62,7 @@ message HasVote { // VoteSetMaj23 is sent to indicate that a given BlockID has seen +2/3 votes. message VoteSetMaj23 { - int64 height = 1; + uint64 height = 1; int32 round = 2; tendermint.types.SignedMsgType type = 3; tendermint.types.BlockID block_id = 4 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; @@ -70,7 +70,7 @@ message VoteSetMaj23 { // VoteSetBits is sent to communicate the bit-array of votes seen for the BlockID. message VoteSetBits { - int64 height = 1; + uint64 height = 1; int32 round = 2; tendermint.types.SignedMsgType type = 3; tendermint.types.BlockID block_id = 4 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; diff --git a/proto/p2p/conn.proto b/proto/p2p/conn.proto index 718df97d9..b12de6c82 100644 --- a/proto/p2p/conn.proto +++ b/proto/p2p/conn.proto @@ -24,7 +24,6 @@ message Packet { } } -// AuthSigMessage is used while upgrading an insecure connection. message AuthSigMessage { tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false]; bytes sig = 2; diff --git a/proto/p2p/pex.proto b/proto/p2p/pex.proto index 04ebccf31..48e1cfce3 100644 --- a/proto/p2p/pex.proto +++ b/proto/p2p/pex.proto @@ -3,13 +3,18 @@ package tendermint.p2p; option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; -import "tendermint/p2p/types.proto"; import "gogoproto/gogo.proto"; +message PexAddress { + string id = 1 [(gogoproto.customname) = "ID"]; + string ip = 2 [(gogoproto.customname) = "IP"]; + uint32 port = 3; +} + message PexRequest {} message PexResponse { - repeated NetAddress addresses = 1 [(gogoproto.nullable) = false]; + repeated PexAddress addresses = 1 [(gogoproto.nullable) = false]; } message PexMessage { diff --git a/proto/p2p/types.proto b/proto/p2p/types.proto index 739d99b13..216a6d8d0 100644 --- a/proto/p2p/types.proto +++ b/proto/p2p/types.proto @@ -4,12 +4,7 @@ package tendermint.p2p; option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; import "gogoproto/gogo.proto"; - -message NetAddress { - string id = 1 [(gogoproto.customname) = "ID"]; - string ip = 2 [(gogoproto.customname) = "IP"]; - uint32 port = 3; -} +import "google/protobuf/timestamp.proto"; message ProtocolVersion { uint64 p2p = 1 [(gogoproto.customname) = "P2P"]; @@ -17,7 +12,6 @@ message ProtocolVersion { uint64 app = 3; } -// NodeInfo is the information exchanged between two peers when conducting the handshake. message NodeInfo { ProtocolVersion protocol_version = 1 [(gogoproto.nullable) = false]; string node_id = 2 [(gogoproto.customname) = "NodeID"]; @@ -33,3 +27,16 @@ message NodeInfoOther { string tx_index = 1; string rpc_address = 2 [(gogoproto.customname) = "RPCAddress"]; } + +message PeerInfo { + string id = 1 [(gogoproto.customname) = "ID"]; + repeated PeerAddressInfo address_info = 2; + google.protobuf.Timestamp last_connected = 3 [(gogoproto.stdtime) = true]; +} + +message PeerAddressInfo { + string address = 1; + google.protobuf.Timestamp last_dial_success = 2 [(gogoproto.stdtime) = true]; + google.protobuf.Timestamp last_dial_failure = 3 [(gogoproto.stdtime) = true]; + uint32 dial_failures = 4; +} diff --git a/proto/types/evidence.proto b/proto/types/evidence.proto index 451b8dca3..800591e32 100644 --- a/proto/types/evidence.proto +++ b/proto/types/evidence.proto @@ -27,7 +27,7 @@ message DuplicateVoteEvidence { // LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. message LightClientAttackEvidence { tendermint.types.LightBlock conflicting_block = 1; - int64 common_height = 2; + uint64 common_height = 2; repeated tendermint.types.Validator byzantine_validators = 3; int64 total_voting_power = 4; google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; diff --git a/proto/types/params.proto b/proto/types/params.proto index bfe933676..8f2cb1497 100644 --- a/proto/types/params.proto +++ b/proto/types/params.proto @@ -20,13 +20,10 @@ message ConsensusParams { // BlockParams contains limits on the block size. message BlockParams { // Max block size, in bytes. - // Note: must be greater than 0 int64 max_bytes = 1; // Max gas per block. // Note: must be greater or equal to -1 int64 max_gas = 2; - // This parameter is unused. - int64 time_iota_ms = 3; } // EvidenceParams determine how we handle evidence of malfeasance. @@ -72,6 +69,6 @@ message VersionParams { // // It is hashed into the Header.ConsensusHash. message HashedParams { - int64 block_max_bytes = 1; - int64 block_max_gas = 2; + uint64 block_max_bytes = 1; + uint64 block_max_gas = 2; } diff --git a/proto/types/types.proto b/proto/types/types.proto index 8d4f00972..9322bf9d6 100644 --- a/proto/types/types.proto +++ b/proto/types/types.proto @@ -59,7 +59,7 @@ message Header { // basic block info tendermint.version.Consensus version = 1 [(gogoproto.nullable) = false]; string chain_id = 2 [(gogoproto.customname) = "ChainID"]; - int64 height = 3; + uint64 height = 3; google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; // prev block info @@ -93,7 +93,7 @@ message Data { // consensus. message Vote { SignedMsgType type = 1; - int64 height = 2; + uint64 height = 2; int32 round = 3; BlockID block_id = 4 [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; // zero if vote is nil. @@ -106,7 +106,7 @@ message Vote { // Commit contains the evidence that a block was committed by a set of validators. message Commit { - int64 height = 1; + uint64 height = 1; int32 round = 2; BlockID block_id = 3 [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; repeated CommitSig signatures = 4 [(gogoproto.nullable) = false]; @@ -123,7 +123,7 @@ message CommitSig { message Proposal { SignedMsgType type = 1; - int64 height = 2; + uint64 height = 2; int32 round = 3; int32 pol_round = 4; BlockID block_id = 5 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index 4906db423..e63b9529f 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -112,7 +112,7 @@ the data in the current block, the previous block, and the results returned by t |-------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Version | [Version](#version) | Version defines the application and protocol verion 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 | int64 | Height is the height for this header. | Must be > 0, >= initialHeight, and == previous Height+1 | +| 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 | @@ -179,7 +179,7 @@ Commit is a simple wrapper for a list of signatures, with one for each validator | Name | Type | Description | Validation | |------------|----------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| -| Height | int64 | Height at which this commit was created. | Must be > 0 | +| 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) | @@ -221,7 +221,7 @@ The vote includes information about the validator signing it. When stored in the | 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 | int64 | Height for which this vote was created for | Must be > 0 | +| 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) | @@ -237,7 +237,7 @@ the fields. ```proto message CanonicalVote { SignedMsgType type = 1; - sfixed64 height = 2; + fixed64 height = 2; sfixed64 round = 3; CanonicalBlockID block_id = 4; google.protobuf.Timestamp timestamp = 5; @@ -275,7 +275,7 @@ 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 | int64 | Height for which this vote was created for | Must be > 0 | +| 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) | From 227e5269ca4e64a6961828ddfda33d2119f79552 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 26 Feb 2021 05:30:09 -0500 Subject: [PATCH 138/223] abci: note on concurrency (#258) Co-authored-by: Marko --- spec/abci/apps.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/abci/apps.md b/spec/abci/apps.md index 68aa542a1..75944fab1 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -22,6 +22,26 @@ 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 @@ -37,7 +57,7 @@ pick up from when it restarts. See information on the Handshake, below. 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 three +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. From b270ab8d15410ab4e70e3835ef2bbd3bbf9c48d7 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 1 Mar 2021 08:54:08 +0000 Subject: [PATCH 139/223] spec: merge rust-spec (#252) --- rust-spec/lightclient/README.md | 202 ---------------- .../lightclient/verification/verification.md | 4 - spec/light-client/README.md | 227 ++++++++++++++---- .../accountability}/001indinv-apalache.csv | 0 .../light-client/accountability}/MC_n4_f1.tla | 0 .../light-client/accountability}/MC_n4_f2.tla | 0 .../accountability}/MC_n4_f2_amnesia.tla | 0 .../light-client/accountability}/MC_n4_f3.tla | 0 .../light-client/accountability}/MC_n5_f1.tla | 0 .../light-client/accountability}/MC_n5_f2.tla | 0 .../light-client/accountability}/MC_n6_f1.tla | 0 .../README.md} | 10 +- .../light-client/accountability/Synopsis.md | 37 ++- .../TendermintAccDebug_004_draft.tla | 0 .../TendermintAccInv_004_draft.tla | 0 .../TendermintAccTrace_004_draft.tla | 0 .../TendermintAcc_004_draft.tla | 0 .../results/001indinv-apalache-mem-log.svg | 0 .../results/001indinv-apalache-mem.svg | 0 .../results/001indinv-apalache-ncells.svg | 0 .../results/001indinv-apalache-nclauses.svg | 0 .../results/001indinv-apalache-report.md | 1 - .../results/001indinv-apalache-time-log.svg | 0 .../results/001indinv-apalache-time.svg | 0 .../results/001indinv-apalache-unstable.csv | 0 .../light-client/accountability}/run.sh | 0 .../attacks/Blockchain_003_draft.tla | 0 .../attacks/Isolation_001_draft.tla | 0 .../attacks/LCVerificationApi_003_draft.tla | 0 .../light-client}/attacks/MC_5_3.tla | 0 .../attacks/isolate-attackers_001_draft.md | 78 +++--- .../attacks/isolate-attackers_002_reviewed.md | 74 +++--- .../attacks/notes-on-evidence-handling.md | 0 spec/light-client/detection.md | 3 - .../detection/004bmc-apalache-ok.csv | 0 .../detection/005bmc-apalache-error.csv | 0 .../detection/Blockchain_003_draft.tla | 0 .../detection/LCD_MC3_3_faulty.tla | 0 .../detection/LCD_MC3_4_faulty.tla | 0 .../detection/LCD_MC4_4_faulty.tla | 0 .../detection/LCD_MC5_5_faulty.tla | 0 .../detection/LCDetector_003_draft.tla | 0 .../detection/LCVerificationApi_003_draft.tla | 0 .../light-client}/detection/README.md | 6 + .../detection/detection_001_reviewed.md | 0 .../detection/detection_003_reviewed.md | 37 ++- .../light-client}/detection/discussions.md | 0 .../detection/draft-functions.md | 0 .../detection/req-ibc-detection.md | 0 .../light-client}/experiments.png | Bin .../supervisor/supervisor_001_draft.md | 36 +-- .../supervisor/supervisor_001_draft.tla | 0 .../verification/001bmc-apalache.csv | 0 .../verification/002bmc-apalache-ok.csv | 0 .../verification/003bmc-apalache-error.csv | 0 .../verification/004bmc-apalache-ok.csv | 0 .../verification/005bmc-apalache-error.csv | 0 .../verification/Blockchain_002_draft.tla | 0 .../verification/Blockchain_003_draft.tla | 0 .../verification/Blockchain_A_1.tla | 0 .../LCVerificationApi_003_draft.tla | 0 .../verification/Lightclient_002_draft.tla | 0 .../verification/Lightclient_003_draft.tla | 0 .../verification/Lightclient_A_1.tla | 0 .../verification/MC4_3_correct.tla | 0 .../verification/MC4_3_faulty.tla | 0 .../verification/MC4_4_correct.tla | 0 .../verification/MC4_4_correct_drifted.tla | 0 .../verification/MC4_4_faulty.tla | 0 .../verification/MC4_4_faulty_drifted.tla | 0 .../verification/MC4_5_correct.tla | 0 .../verification/MC4_5_faulty.tla | 0 .../verification/MC4_6_faulty.tla | 0 .../verification/MC4_7_faulty.tla | 0 .../verification/MC5_5_correct.tla | 0 .../MC5_5_correct_peer_two_thirds_faulty.tla | 0 .../verification/MC5_5_faulty.tla | 0 .../MC5_5_faulty_peer_two_thirds_faulty.tla | 0 .../verification/MC5_7_faulty.tla | 0 .../verification/MC7_5_faulty.tla | 0 .../verification/MC7_7_faulty.tla | 0 .../README.md} | 6 + .../verification_001_published.md | 5 +- .../verification/verification_002_draft.md | 30 +-- 84 files changed, 344 insertions(+), 412 deletions(-) delete mode 100644 rust-spec/lightclient/README.md delete mode 100644 rust-spec/lightclient/verification/verification.md rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/001indinv-apalache.csv (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/MC_n4_f1.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/MC_n4_f2.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/MC_n4_f2_amnesia.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/MC_n4_f3.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/MC_n5_f1.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/MC_n5_f2.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/MC_n6_f1.tla (100%) rename spec/light-client/{accountability.md => accountability/README.md} (99%) rename rust-spec/tendermint-accountability/README.md => spec/light-client/accountability/Synopsis.md (73%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/TendermintAccDebug_004_draft.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/TendermintAccInv_004_draft.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/TendermintAccTrace_004_draft.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/TendermintAcc_004_draft.tla (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/results/001indinv-apalache-mem-log.svg (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/results/001indinv-apalache-mem.svg (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/results/001indinv-apalache-ncells.svg (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/results/001indinv-apalache-nclauses.svg (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/results/001indinv-apalache-report.md (99%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/results/001indinv-apalache-time-log.svg (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/results/001indinv-apalache-time.svg (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/results/001indinv-apalache-unstable.csv (100%) rename {rust-spec/tendermint-accountability => spec/light-client/accountability}/run.sh (100%) rename {rust-spec/lightclient => spec/light-client}/attacks/Blockchain_003_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/attacks/Isolation_001_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/attacks/LCVerificationApi_003_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/attacks/MC_5_3.tla (100%) rename {rust-spec/lightclient => spec/light-client}/attacks/isolate-attackers_001_draft.md (93%) rename {rust-spec/lightclient => spec/light-client}/attacks/isolate-attackers_002_reviewed.md (94%) rename {rust-spec/lightclient => spec/light-client}/attacks/notes-on-evidence-handling.md (100%) delete mode 100644 spec/light-client/detection.md rename {rust-spec/lightclient => spec/light-client}/detection/004bmc-apalache-ok.csv (100%) rename {rust-spec/lightclient => spec/light-client}/detection/005bmc-apalache-error.csv (100%) rename {rust-spec/lightclient => spec/light-client}/detection/Blockchain_003_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/detection/LCD_MC3_3_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/detection/LCD_MC3_4_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/detection/LCD_MC4_4_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/detection/LCD_MC5_5_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/detection/LCDetector_003_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/detection/LCVerificationApi_003_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/detection/README.md (97%) rename {rust-spec/lightclient => spec/light-client}/detection/detection_001_reviewed.md (100%) rename {rust-spec/lightclient => spec/light-client}/detection/detection_003_reviewed.md (97%) rename {rust-spec/lightclient => spec/light-client}/detection/discussions.md (100%) rename {rust-spec/lightclient => spec/light-client}/detection/draft-functions.md (100%) rename {rust-spec/lightclient => spec/light-client}/detection/req-ibc-detection.md (100%) rename {rust-spec/lightclient => spec/light-client}/experiments.png (100%) rename {rust-spec/lightclient => spec/light-client}/supervisor/supervisor_001_draft.md (98%) rename {rust-spec/lightclient => spec/light-client}/supervisor/supervisor_001_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/001bmc-apalache.csv (100%) rename {rust-spec/lightclient => spec/light-client}/verification/002bmc-apalache-ok.csv (100%) rename {rust-spec/lightclient => spec/light-client}/verification/003bmc-apalache-error.csv (100%) rename {rust-spec/lightclient => spec/light-client}/verification/004bmc-apalache-ok.csv (100%) rename {rust-spec/lightclient => spec/light-client}/verification/005bmc-apalache-error.csv (100%) rename {rust-spec/lightclient => spec/light-client}/verification/Blockchain_002_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/Blockchain_003_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/Blockchain_A_1.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/LCVerificationApi_003_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/Lightclient_002_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/Lightclient_003_draft.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/Lightclient_A_1.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_3_correct.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_3_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_4_correct.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_4_correct_drifted.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_4_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_4_faulty_drifted.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_5_correct.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_5_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_6_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC4_7_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC5_5_correct.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC5_5_correct_peer_two_thirds_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC5_5_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC5_5_faulty_peer_two_thirds_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC5_7_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC7_5_faulty.tla (100%) rename {rust-spec/lightclient => spec/light-client}/verification/MC7_7_faulty.tla (100%) rename spec/light-client/{verification.md => verification/README.md} (99%) rename {rust-spec/lightclient => spec/light-client}/verification/verification_001_published.md (99%) rename {rust-spec/lightclient => spec/light-client}/verification/verification_002_draft.md (99%) diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md deleted file mode 100644 index cdecb8fa4..000000000 --- a/rust-spec/lightclient/README.md +++ /dev/null @@ -1,202 +0,0 @@ -# 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+][tendermint-accountability] 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]: -https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md diff --git a/rust-spec/lightclient/verification/verification.md b/rust-spec/lightclient/verification/verification.md deleted file mode 100644 index 09226ab64..000000000 --- a/rust-spec/lightclient/verification/verification.md +++ /dev/null @@ -1,4 +0,0 @@ -We changed the naming convention and versioning of specifications. -See [verification_001_published.md](./verification_001_published.md) -for the file that used to be called `verification.md`. There are also newer -versions of this specification in this directory. \ No newline at end of file diff --git a/spec/light-client/README.md b/spec/light-client/README.md index 573eddbaf..42f20d46c 100644 --- a/spec/light-client/README.md +++ b/spec/light-client/README.md @@ -5,66 +5,201 @@ parent: order: 5 --- -NOTE: This specification is under heavy development and is not yet complete nor -accurate. +# Light Client Specification -## Contents +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). -- [Motivation](#motivation) -- [Structure](#structure) -- [Core Verification](./verification.md) -- [Fork Detection](./detection.md) -- [Fork Accountability](./accountability.md) +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). -## Motivation +The light client is decomposed into two main components: -The Tendermint Light Client is motivated by the need for a light weight protocol -to sync with a Tendermint blockchain, with the least processing necessary to -securely verify a recent state. The protocol consists of managing trusted validator -sets and trusted block headers, and is based primarily on checking hashes -and verifying Tendermint commit signatures. +- [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) -Motivating use cases include: +In case a lightclient attack is detected, the lightclient submits evidence to a full node which is responsible for "accountability", that is, punishing attackers: -- Light Node: a daemon that syncs a blockchain to the latest committed header by making RPC requests to full nodes. -- State Sync: a reactor that syncs a blockchain to a recent committed state by making P2P requests to full nodes. -- IBC Client: an ABCI application library that syncs a blockchain to a recent committed header by receiving proof-carrying -transactions from "IBC relayers", who make RPC requests to full nodes on behalf of the IBC clients. +- [Accountability](#Accountability) - given evidence for an attack, compute a set of validators that are responsible for it. -## Structure +## Commit Verification -### Components +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. -The Tendermint Light Client consists of three primary components: +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. -- [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes -- [Fork Detection](./detection.md): talking to multiple peers to detect Byzantine behaviour -- [Fork Accountability](./accountability.md): analyzing Byzantine behaviour to hold validators accountable. +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. -While every light client must perform core verification and fork detection -to achieve their prescribed security level, fork accountability is expected to -be done by full nodes and validators, and is thus more accurately a component of -the full node consensus protocol, though it is included here since it is -primarily concerned with providing security to light clients. +A more detailed TLA+ specification of +[Light client verification ver. 003](verification/Lightclient_003_draft.tla) +is currently under peer review. -A schematic of the core verification and fork detection components in -a Light Node are depicted below. The schematic is quite similar for other use cases. -Note that fork accountability is not depicted, as it is the responsibility of the -full nodes. +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: -![Light Client Diagram](./assets/light-node-image.png). +```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 +``` -### Synchrony +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: -Light clients are fundamentally synchronous protocols, -where security is restricted by the interval during which a validator can be punished -for Byzantine behaviour. We assume here that such intervals have fixed and known minimal duration -referred to commonly as a blockchain's Unbonding Period. +```sh +$DIR/apalache-tests/scripts/mk-run.py --memlimit 28 002bmc-apalache-ok.csv $DIR/apalache . out +./out/run-all.sh +``` -A secure light client must guarantee that all three components - -core verification, fork detection, and fork accountability - -each with their own synchrony assumptions and fault model, can execute -sequentially and to completion within the given Unbonding Period. +After the experiments have finished, you can collect the logs by executing the following command: -TODO: define all the synchrony parameters used in the protocol and their -relation to the Unbonding Period. +```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/rust-spec/tendermint-accountability/001indinv-apalache.csv b/spec/light-client/accountability/001indinv-apalache.csv similarity index 100% rename from rust-spec/tendermint-accountability/001indinv-apalache.csv rename to spec/light-client/accountability/001indinv-apalache.csv diff --git a/rust-spec/tendermint-accountability/MC_n4_f1.tla b/spec/light-client/accountability/MC_n4_f1.tla similarity index 100% rename from rust-spec/tendermint-accountability/MC_n4_f1.tla rename to spec/light-client/accountability/MC_n4_f1.tla diff --git a/rust-spec/tendermint-accountability/MC_n4_f2.tla b/spec/light-client/accountability/MC_n4_f2.tla similarity index 100% rename from rust-spec/tendermint-accountability/MC_n4_f2.tla rename to spec/light-client/accountability/MC_n4_f2.tla diff --git a/rust-spec/tendermint-accountability/MC_n4_f2_amnesia.tla b/spec/light-client/accountability/MC_n4_f2_amnesia.tla similarity index 100% rename from rust-spec/tendermint-accountability/MC_n4_f2_amnesia.tla rename to spec/light-client/accountability/MC_n4_f2_amnesia.tla diff --git a/rust-spec/tendermint-accountability/MC_n4_f3.tla b/spec/light-client/accountability/MC_n4_f3.tla similarity index 100% rename from rust-spec/tendermint-accountability/MC_n4_f3.tla rename to spec/light-client/accountability/MC_n4_f3.tla diff --git a/rust-spec/tendermint-accountability/MC_n5_f1.tla b/spec/light-client/accountability/MC_n5_f1.tla similarity index 100% rename from rust-spec/tendermint-accountability/MC_n5_f1.tla rename to spec/light-client/accountability/MC_n5_f1.tla diff --git a/rust-spec/tendermint-accountability/MC_n5_f2.tla b/spec/light-client/accountability/MC_n5_f2.tla similarity index 100% rename from rust-spec/tendermint-accountability/MC_n5_f2.tla rename to spec/light-client/accountability/MC_n5_f2.tla diff --git a/rust-spec/tendermint-accountability/MC_n6_f1.tla b/spec/light-client/accountability/MC_n6_f1.tla similarity index 100% rename from rust-spec/tendermint-accountability/MC_n6_f1.tla rename to spec/light-client/accountability/MC_n6_f1.tla diff --git a/spec/light-client/accountability.md b/spec/light-client/accountability/README.md similarity index 99% rename from spec/light-client/accountability.md rename to spec/light-client/accountability/README.md index c46495553..bb872649b 100644 --- a/spec/light-client/accountability.md +++ b/spec/light-client/accountability/README.md @@ -1,3 +1,10 @@ +--- +order: 1 +parent: + title: Accountability + order: 4 +--- + # Fork accountability ## Problem Statement @@ -8,8 +15,7 @@ Tendermint consensus guarantees the following specifications for all heights: * 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 +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. diff --git a/rust-spec/tendermint-accountability/README.md b/spec/light-client/accountability/Synopsis.md similarity index 73% rename from rust-spec/tendermint-accountability/README.md rename to spec/light-client/accountability/Synopsis.md index ac321d457..76da3868c 100644 --- a/rust-spec/tendermint-accountability/README.md +++ b/spec/light-client/accountability/Synopsis.md @@ -1,53 +1,54 @@ + # 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 +- the procotol runs for one height, that is, one-shot consensus - - this specification focuses on safety, so timeouts are modelled with +- this specification focuses on safety, so timeouts are modelled with with non-determinism - - the proposer function is non-determinstic, no fairness is assumed +- the proposer function is non-determinstic, no fairness is assumed - - the messages by the faulty processes are injected right in the initial states +- the messages by the faulty processes are injected right in the initial states - - every process has the voting power of 1 +- every process has the voting power of 1 - - hashes are modelled as identity +- 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 + 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 +- Equivocation: a Byzantine process sends two different values in the same round. - - Amnesia: a Byzantine process locks a value, although it has locked +- 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 + +- [TendermintAcc_004_draft](TendermintAcc_004_draft.tla) is the protocol specification, - - [TendermintAccInv_004_draft](TendermintAccInv_004_draft.tla) contains an +- [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 +- `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 +- [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 +- [TendermintAccDebug_004_draft](TendermintAccDebug_004_draft.tla) contains the useful definitions for debugging the protocol specification with TLC and Apalache. @@ -90,7 +91,7 @@ THEOREM AgreementOrFork == ~FaultyQuorum /\ TypedInv => Accountability ``` -# Model checking results +# Model checking results Check the report on [model checking with Apalache](./results/001indinv-apalache-report.md). @@ -102,5 +103,3 @@ To run the model checking experiments, use the script: This script assumes that the apalache build is available in `~/devl/apalache-unstable`. - - diff --git a/rust-spec/tendermint-accountability/TendermintAccDebug_004_draft.tla b/spec/light-client/accountability/TendermintAccDebug_004_draft.tla similarity index 100% rename from rust-spec/tendermint-accountability/TendermintAccDebug_004_draft.tla rename to spec/light-client/accountability/TendermintAccDebug_004_draft.tla diff --git a/rust-spec/tendermint-accountability/TendermintAccInv_004_draft.tla b/spec/light-client/accountability/TendermintAccInv_004_draft.tla similarity index 100% rename from rust-spec/tendermint-accountability/TendermintAccInv_004_draft.tla rename to spec/light-client/accountability/TendermintAccInv_004_draft.tla diff --git a/rust-spec/tendermint-accountability/TendermintAccTrace_004_draft.tla b/spec/light-client/accountability/TendermintAccTrace_004_draft.tla similarity index 100% rename from rust-spec/tendermint-accountability/TendermintAccTrace_004_draft.tla rename to spec/light-client/accountability/TendermintAccTrace_004_draft.tla diff --git a/rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla b/spec/light-client/accountability/TendermintAcc_004_draft.tla similarity index 100% rename from rust-spec/tendermint-accountability/TendermintAcc_004_draft.tla rename to spec/light-client/accountability/TendermintAcc_004_draft.tla diff --git a/rust-spec/tendermint-accountability/results/001indinv-apalache-mem-log.svg b/spec/light-client/accountability/results/001indinv-apalache-mem-log.svg similarity index 100% rename from rust-spec/tendermint-accountability/results/001indinv-apalache-mem-log.svg rename to spec/light-client/accountability/results/001indinv-apalache-mem-log.svg diff --git a/rust-spec/tendermint-accountability/results/001indinv-apalache-mem.svg b/spec/light-client/accountability/results/001indinv-apalache-mem.svg similarity index 100% rename from rust-spec/tendermint-accountability/results/001indinv-apalache-mem.svg rename to spec/light-client/accountability/results/001indinv-apalache-mem.svg diff --git a/rust-spec/tendermint-accountability/results/001indinv-apalache-ncells.svg b/spec/light-client/accountability/results/001indinv-apalache-ncells.svg similarity index 100% rename from rust-spec/tendermint-accountability/results/001indinv-apalache-ncells.svg rename to spec/light-client/accountability/results/001indinv-apalache-ncells.svg diff --git a/rust-spec/tendermint-accountability/results/001indinv-apalache-nclauses.svg b/spec/light-client/accountability/results/001indinv-apalache-nclauses.svg similarity index 100% rename from rust-spec/tendermint-accountability/results/001indinv-apalache-nclauses.svg rename to spec/light-client/accountability/results/001indinv-apalache-nclauses.svg diff --git a/rust-spec/tendermint-accountability/results/001indinv-apalache-report.md b/spec/light-client/accountability/results/001indinv-apalache-report.md similarity index 99% rename from rust-spec/tendermint-accountability/results/001indinv-apalache-report.md rename to spec/light-client/accountability/results/001indinv-apalache-report.md index d63a93598..0c14742c5 100644 --- a/rust-spec/tendermint-accountability/results/001indinv-apalache-report.md +++ b/spec/light-client/accountability/results/001indinv-apalache-report.md @@ -1,6 +1,5 @@ # Results of 001indinv-apalache - ## 1. Awesome plots ### 1.1. Time (logarithmic scale) diff --git a/rust-spec/tendermint-accountability/results/001indinv-apalache-time-log.svg b/spec/light-client/accountability/results/001indinv-apalache-time-log.svg similarity index 100% rename from rust-spec/tendermint-accountability/results/001indinv-apalache-time-log.svg rename to spec/light-client/accountability/results/001indinv-apalache-time-log.svg diff --git a/rust-spec/tendermint-accountability/results/001indinv-apalache-time.svg b/spec/light-client/accountability/results/001indinv-apalache-time.svg similarity index 100% rename from rust-spec/tendermint-accountability/results/001indinv-apalache-time.svg rename to spec/light-client/accountability/results/001indinv-apalache-time.svg diff --git a/rust-spec/tendermint-accountability/results/001indinv-apalache-unstable.csv b/spec/light-client/accountability/results/001indinv-apalache-unstable.csv similarity index 100% rename from rust-spec/tendermint-accountability/results/001indinv-apalache-unstable.csv rename to spec/light-client/accountability/results/001indinv-apalache-unstable.csv diff --git a/rust-spec/tendermint-accountability/run.sh b/spec/light-client/accountability/run.sh similarity index 100% rename from rust-spec/tendermint-accountability/run.sh rename to spec/light-client/accountability/run.sh diff --git a/rust-spec/lightclient/attacks/Blockchain_003_draft.tla b/spec/light-client/attacks/Blockchain_003_draft.tla similarity index 100% rename from rust-spec/lightclient/attacks/Blockchain_003_draft.tla rename to spec/light-client/attacks/Blockchain_003_draft.tla diff --git a/rust-spec/lightclient/attacks/Isolation_001_draft.tla b/spec/light-client/attacks/Isolation_001_draft.tla similarity index 100% rename from rust-spec/lightclient/attacks/Isolation_001_draft.tla rename to spec/light-client/attacks/Isolation_001_draft.tla diff --git a/rust-spec/lightclient/attacks/LCVerificationApi_003_draft.tla b/spec/light-client/attacks/LCVerificationApi_003_draft.tla similarity index 100% rename from rust-spec/lightclient/attacks/LCVerificationApi_003_draft.tla rename to spec/light-client/attacks/LCVerificationApi_003_draft.tla diff --git a/rust-spec/lightclient/attacks/MC_5_3.tla b/spec/light-client/attacks/MC_5_3.tla similarity index 100% rename from rust-spec/lightclient/attacks/MC_5_3.tla rename to spec/light-client/attacks/MC_5_3.tla diff --git a/rust-spec/lightclient/attacks/isolate-attackers_001_draft.md b/spec/light-client/attacks/isolate-attackers_001_draft.md similarity index 93% rename from rust-spec/lightclient/attacks/isolate-attackers_001_draft.md rename to spec/light-client/attacks/isolate-attackers_001_draft.md index 54f02cccd..e4f585f4a 100644 --- a/rust-spec/lightclient/attacks/isolate-attackers_001_draft.md +++ b/spec/light-client/attacks/isolate-attackers_001_draft.md @@ -1,25 +1,25 @@ -*** This is the beginning of an unfinished draft. Don't continue reading! *** # 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 +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. - - +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. @@ -30,7 +30,8 @@ For definitions of data structures used here, in particular LightBlocks [[LCV-DA # Part II - Definition of the Problem -The specification of the [detection mechanism][detection] describes +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 @@ -48,11 +49,12 @@ and a prefix of the blockchain `bc` at least up to height `ev.ConflictingBlock.H 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: + +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, + - `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 @@ -60,7 +62,6 @@ When an output is generated it satisfies the following properties: - 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. @@ -72,9 +73,10 @@ Here we discuss how to solve the problem of isolating misbehaving processes. We > 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 @@ -87,18 +89,19 @@ func isolateMisbehavingProcesses(ev LightClientAttackEvidence, bc Blockchain) [] 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 + // 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. @@ -109,27 +112,30 @@ func isolateMisbehavingProcesses(ev LightClientAttackEvidence, bc Blockchain) [] - TODO: input light blocks pass basic validation - Expected postcondition - [[FN-INV-Output.1]](#FN-INV-Output1) holds -- Error condition +- 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` + - `ref.Height == ev.Height` - Expected postcondition - returns evaluation of the following disjunction **[[LCAI-NONVALID-OUTPUT.1]]** == @@ -139,53 +145,52 @@ func violatesTMValidity(ref Header, ev Header) boolean `ref.AppHash != ev.AppHash` or `ref.LastResultsHash != ev.LastResultsHash` - ```go -func IsolateAmnesiaAttacker(ev LightClientAttackEvidence, bc Blockchain) []ValidatorAddress +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 +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 +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 +- 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. +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. @@ -194,16 +199,15 @@ First observe that the first checking in `isolateMisbehavingProcesses` is `viola [[detection]] The specification of the light client attack detection mechanism. -[supervisor]: +[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]: +[detection]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md - -[LC-DATA-EVIDENCE-link]: +[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]: diff --git a/rust-spec/lightclient/attacks/isolate-attackers_002_reviewed.md b/spec/light-client/attacks/isolate-attackers_002_reviewed.md similarity index 94% rename from rust-spec/lightclient/attacks/isolate-attackers_002_reviewed.md rename to spec/light-client/attacks/isolate-attackers_002_reviewed.md index c4e270efa..febcc10a8 100644 --- a/rust-spec/lightclient/attacks/isolate-attackers_002_reviewed.md +++ b/spec/light-client/attacks/isolate-attackers_002_reviewed.md @@ -2,22 +2,21 @@ 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 +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. - - +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. @@ -26,8 +25,8 @@ After providing the [problem statement](#Part-I---Basics-and-Definition-of-the-P 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 -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 @@ -45,11 +44,12 @@ and a prefix of the blockchain `bc` at least up to height `ev.ConflictingBlock.H 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: + +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, + - `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 @@ -57,7 +57,6 @@ When an output is generated it satisfies the following properties: - 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. @@ -69,9 +68,10 @@ Here we discuss how to solve the problem of isolating misbehaving processes. We 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 @@ -84,18 +84,19 @@ func isolateMisbehavingProcesses(ev LightClientAttackEvidence, bc Blockchain) [] 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 + // 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. @@ -106,27 +107,30 @@ func isolateMisbehavingProcesses(ev LightClientAttackEvidence, bc Blockchain) [] - `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 +- 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` + - `ref.Height == ev.Height` - Expected postcondition - returns evaluation of the following disjunction **[LCAI-NONVALID-OUTPUT.1]** == @@ -136,18 +140,19 @@ func violatesTMValidity(ref Header, ev Header) boolean `ref.AppHash != ev.AppHash` or `ref.LastResultsHash != ev.LastResultsHash` - ```go -func IsolateAmnesiaAttacker(ev LightClientAttackEvidence, bc Blockchain) []ValidatorAddress +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 +func RoundOf(commit Commit) []ValidatorAddress ``` + - Expected precondition - `commit` is well-formed. In particular all votes are from the same round `r`. - Expected postcondition @@ -156,35 +161,33 @@ func RoundOf(commit Commit) []ValidatorAddress - reports error if precondition is violated ```go -func Signers(commit Commit) []ValidatorAddress +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 +- 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. +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. @@ -198,16 +201,15 @@ For this fixed group membership, we have analyzed the attacks using the TLA+ spe [tendermint-accountability]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md -[supervisor]: +[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]: +[detection]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md - -[LC-DATA-EVIDENCE-link]: +[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]: diff --git a/rust-spec/lightclient/attacks/notes-on-evidence-handling.md b/spec/light-client/attacks/notes-on-evidence-handling.md similarity index 100% rename from rust-spec/lightclient/attacks/notes-on-evidence-handling.md rename to spec/light-client/attacks/notes-on-evidence-handling.md diff --git a/spec/light-client/detection.md b/spec/light-client/detection.md deleted file mode 100644 index d0a0ad1da..000000000 --- a/spec/light-client/detection.md +++ /dev/null @@ -1,3 +0,0 @@ -# Detection - -TODO diff --git a/rust-spec/lightclient/detection/004bmc-apalache-ok.csv b/spec/light-client/detection/004bmc-apalache-ok.csv similarity index 100% rename from rust-spec/lightclient/detection/004bmc-apalache-ok.csv rename to spec/light-client/detection/004bmc-apalache-ok.csv diff --git a/rust-spec/lightclient/detection/005bmc-apalache-error.csv b/spec/light-client/detection/005bmc-apalache-error.csv similarity index 100% rename from rust-spec/lightclient/detection/005bmc-apalache-error.csv rename to spec/light-client/detection/005bmc-apalache-error.csv diff --git a/rust-spec/lightclient/detection/Blockchain_003_draft.tla b/spec/light-client/detection/Blockchain_003_draft.tla similarity index 100% rename from rust-spec/lightclient/detection/Blockchain_003_draft.tla rename to spec/light-client/detection/Blockchain_003_draft.tla diff --git a/rust-spec/lightclient/detection/LCD_MC3_3_faulty.tla b/spec/light-client/detection/LCD_MC3_3_faulty.tla similarity index 100% rename from rust-spec/lightclient/detection/LCD_MC3_3_faulty.tla rename to spec/light-client/detection/LCD_MC3_3_faulty.tla diff --git a/rust-spec/lightclient/detection/LCD_MC3_4_faulty.tla b/spec/light-client/detection/LCD_MC3_4_faulty.tla similarity index 100% rename from rust-spec/lightclient/detection/LCD_MC3_4_faulty.tla rename to spec/light-client/detection/LCD_MC3_4_faulty.tla diff --git a/rust-spec/lightclient/detection/LCD_MC4_4_faulty.tla b/spec/light-client/detection/LCD_MC4_4_faulty.tla similarity index 100% rename from rust-spec/lightclient/detection/LCD_MC4_4_faulty.tla rename to spec/light-client/detection/LCD_MC4_4_faulty.tla diff --git a/rust-spec/lightclient/detection/LCD_MC5_5_faulty.tla b/spec/light-client/detection/LCD_MC5_5_faulty.tla similarity index 100% rename from rust-spec/lightclient/detection/LCD_MC5_5_faulty.tla rename to spec/light-client/detection/LCD_MC5_5_faulty.tla diff --git a/rust-spec/lightclient/detection/LCDetector_003_draft.tla b/spec/light-client/detection/LCDetector_003_draft.tla similarity index 100% rename from rust-spec/lightclient/detection/LCDetector_003_draft.tla rename to spec/light-client/detection/LCDetector_003_draft.tla diff --git a/rust-spec/lightclient/detection/LCVerificationApi_003_draft.tla b/spec/light-client/detection/LCVerificationApi_003_draft.tla similarity index 100% rename from rust-spec/lightclient/detection/LCVerificationApi_003_draft.tla rename to spec/light-client/detection/LCVerificationApi_003_draft.tla diff --git a/rust-spec/lightclient/detection/README.md b/spec/light-client/detection/README.md similarity index 97% rename from rust-spec/lightclient/detection/README.md rename to spec/light-client/detection/README.md index 8f92bdaa2..50d8ab6fe 100644 --- a/rust-spec/lightclient/detection/README.md +++ b/spec/light-client/detection/README.md @@ -1,3 +1,9 @@ +--- +order: 1 +parent: + title: Fork Detection + order: 2 +--- # Tendermint fork detection and IBC fork detection diff --git a/rust-spec/lightclient/detection/detection_001_reviewed.md b/spec/light-client/detection/detection_001_reviewed.md similarity index 100% rename from rust-spec/lightclient/detection/detection_001_reviewed.md rename to spec/light-client/detection/detection_001_reviewed.md diff --git a/rust-spec/lightclient/detection/detection_003_reviewed.md b/spec/light-client/detection/detection_003_reviewed.md similarity index 97% rename from rust-spec/lightclient/detection/detection_003_reviewed.md rename to spec/light-client/detection/detection_003_reviewed.md index 89e920f19..812f80d1c 100644 --- a/rust-spec/lightclient/detection/detection_003_reviewed.md +++ b/spec/light-client/detection/detection_003_reviewed.md @@ -58,37 +58,33 @@ a new light block, the light client first does 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 + - [Node-based characterization of attacks](#Node-based-characterization-of-attacks). The definition of attacks used in the problem statement of - this specification. + this specification. - - [Block-based characterization of attacks](#Block-based-characterization-of-attacks). Alternative definitions + - [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) + - [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) - - + - [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 @@ -264,7 +260,6 @@ of v(i) and v(i+1) such that > - 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 @@ -420,7 +415,6 @@ 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 @@ -576,6 +570,7 @@ then the secondary is replaced before the detector terminates. ### From the [supervisor][supervisor] [[LC-FUNC-REPLACE-SECONDARY.1]][repl] + ```go Replace_Secondary(addr Address, root-of-trust LightBlock) ``` @@ -583,6 +578,7 @@ 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) @@ -628,7 +624,7 @@ func AttackDetector(root LightBlock, primary_trace []LightBlock) 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 @@ -732,9 +728,9 @@ func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) ## Correctness arguments -#### On the existence of evidence +#### On the existence of evidence -**Proposition.** In the case of attack, +**Proposition.** In the case of attack, evidence [[TMBC-LC-ATTACK-EVIDENCE.1]](#TMBC-LC-ATTACK-EVIDENCE1) exists. *Proof.* First observe that @@ -750,7 +746,6 @@ Now by contradiction assume there is no evidence. Thus 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, diff --git a/rust-spec/lightclient/detection/discussions.md b/spec/light-client/detection/discussions.md similarity index 100% rename from rust-spec/lightclient/detection/discussions.md rename to spec/light-client/detection/discussions.md diff --git a/rust-spec/lightclient/detection/draft-functions.md b/spec/light-client/detection/draft-functions.md similarity index 100% rename from rust-spec/lightclient/detection/draft-functions.md rename to spec/light-client/detection/draft-functions.md diff --git a/rust-spec/lightclient/detection/req-ibc-detection.md b/spec/light-client/detection/req-ibc-detection.md similarity index 100% rename from rust-spec/lightclient/detection/req-ibc-detection.md rename to spec/light-client/detection/req-ibc-detection.md diff --git a/rust-spec/lightclient/experiments.png b/spec/light-client/experiments.png similarity index 100% rename from rust-spec/lightclient/experiments.png rename to spec/light-client/experiments.png diff --git a/rust-spec/lightclient/supervisor/supervisor_001_draft.md b/spec/light-client/supervisor/supervisor_001_draft.md similarity index 98% rename from rust-spec/lightclient/supervisor/supervisor_001_draft.md rename to spec/light-client/supervisor/supervisor_001_draft.md index df7a71b3e..3c3d45d87 100644 --- a/rust-spec/lightclient/supervisor/supervisor_001_draft.md +++ b/spec/light-client/supervisor/supervisor_001_draft.md @@ -6,7 +6,6 @@ This specification in done in parallel with updates on the verification specification. So some hyperlinks have to be placed to the correct files eventually. - # Light Client Sequential Supervisor The light client implements a read operation of a @@ -92,7 +91,6 @@ 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 @@ -140,13 +138,11 @@ that contains light blocks (that contain a header). **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, @@ -171,8 +167,6 @@ 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. @@ -203,7 +197,6 @@ block but were not cross-checked are stored as "verified" > performance is worth it, we keep for future versions/discussion of > lightclient protocols. - ## Definitions ### Peers @@ -268,8 +261,6 @@ Replace_Secondary(addr Address, root-of-trust LightBlock) - Error condition - if precondition is violated - - ### Data Types The core data structure of the protocol is the LightBlock. @@ -301,7 +292,6 @@ 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 @@ -369,11 +359,10 @@ func makeblock (genesisDoc GenesisDoc) (lightBlock LightBlock)) ### 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(). +and all secondaries agree on lightStore.Latest(). ### Assumptions @@ -436,7 +425,7 @@ func Sequential-Supervisor (initdata LCInitData) (Error) { if result == OK { output(LightStore.Get(targetHeight)); - // we only output a trusted lightblock + // we only output a trusted lightblock } else { return result @@ -449,13 +438,13 @@ func Sequential-Supervisor (initdata LCInitData) (Error) { - 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: + - 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. + 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 @@ -519,8 +508,8 @@ func InitLightClient (initData LCInitData) (LightStore, Error) { } // cross-check - auxLS := new LightStore - auxLS.Add(current) + auxLS := new LightStore + auxLS.Add(current) Evidences := AttackDetector(genesisBlock, auxLS) if Evidences.Empty { newBlock := current @@ -567,12 +556,12 @@ func VerifyAndDetect (lightStore LightStore, targetHeight Height) if b1.State == StateTrusted { // block already there and trusted return (lightStore, ResultSuccess) - } - else { + } + 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 { @@ -584,7 +573,7 @@ func VerifyAndDetect (lightStore LightStore, targetHeight Height) } else { // there is an attack, we exit - submitEvidence(Evidences); + submitEvidence(Evidences); return(lightStore, ErrorAttack); } } @@ -646,4 +635,3 @@ func VerifyAndDetect (lightStore LightStore, targetHeight Height) - [LC-DATA-PEERLIST-INV.1] is violated ---- - diff --git a/rust-spec/lightclient/supervisor/supervisor_001_draft.tla b/spec/light-client/supervisor/supervisor_001_draft.tla similarity index 100% rename from rust-spec/lightclient/supervisor/supervisor_001_draft.tla rename to spec/light-client/supervisor/supervisor_001_draft.tla diff --git a/rust-spec/lightclient/verification/001bmc-apalache.csv b/spec/light-client/verification/001bmc-apalache.csv similarity index 100% rename from rust-spec/lightclient/verification/001bmc-apalache.csv rename to spec/light-client/verification/001bmc-apalache.csv diff --git a/rust-spec/lightclient/verification/002bmc-apalache-ok.csv b/spec/light-client/verification/002bmc-apalache-ok.csv similarity index 100% rename from rust-spec/lightclient/verification/002bmc-apalache-ok.csv rename to spec/light-client/verification/002bmc-apalache-ok.csv diff --git a/rust-spec/lightclient/verification/003bmc-apalache-error.csv b/spec/light-client/verification/003bmc-apalache-error.csv similarity index 100% rename from rust-spec/lightclient/verification/003bmc-apalache-error.csv rename to spec/light-client/verification/003bmc-apalache-error.csv diff --git a/rust-spec/lightclient/verification/004bmc-apalache-ok.csv b/spec/light-client/verification/004bmc-apalache-ok.csv similarity index 100% rename from rust-spec/lightclient/verification/004bmc-apalache-ok.csv rename to spec/light-client/verification/004bmc-apalache-ok.csv diff --git a/rust-spec/lightclient/verification/005bmc-apalache-error.csv b/spec/light-client/verification/005bmc-apalache-error.csv similarity index 100% rename from rust-spec/lightclient/verification/005bmc-apalache-error.csv rename to spec/light-client/verification/005bmc-apalache-error.csv diff --git a/rust-spec/lightclient/verification/Blockchain_002_draft.tla b/spec/light-client/verification/Blockchain_002_draft.tla similarity index 100% rename from rust-spec/lightclient/verification/Blockchain_002_draft.tla rename to spec/light-client/verification/Blockchain_002_draft.tla diff --git a/rust-spec/lightclient/verification/Blockchain_003_draft.tla b/spec/light-client/verification/Blockchain_003_draft.tla similarity index 100% rename from rust-spec/lightclient/verification/Blockchain_003_draft.tla rename to spec/light-client/verification/Blockchain_003_draft.tla diff --git a/rust-spec/lightclient/verification/Blockchain_A_1.tla b/spec/light-client/verification/Blockchain_A_1.tla similarity index 100% rename from rust-spec/lightclient/verification/Blockchain_A_1.tla rename to spec/light-client/verification/Blockchain_A_1.tla diff --git a/rust-spec/lightclient/verification/LCVerificationApi_003_draft.tla b/spec/light-client/verification/LCVerificationApi_003_draft.tla similarity index 100% rename from rust-spec/lightclient/verification/LCVerificationApi_003_draft.tla rename to spec/light-client/verification/LCVerificationApi_003_draft.tla diff --git a/rust-spec/lightclient/verification/Lightclient_002_draft.tla b/spec/light-client/verification/Lightclient_002_draft.tla similarity index 100% rename from rust-spec/lightclient/verification/Lightclient_002_draft.tla rename to spec/light-client/verification/Lightclient_002_draft.tla diff --git a/rust-spec/lightclient/verification/Lightclient_003_draft.tla b/spec/light-client/verification/Lightclient_003_draft.tla similarity index 100% rename from rust-spec/lightclient/verification/Lightclient_003_draft.tla rename to spec/light-client/verification/Lightclient_003_draft.tla diff --git a/rust-spec/lightclient/verification/Lightclient_A_1.tla b/spec/light-client/verification/Lightclient_A_1.tla similarity index 100% rename from rust-spec/lightclient/verification/Lightclient_A_1.tla rename to spec/light-client/verification/Lightclient_A_1.tla diff --git a/rust-spec/lightclient/verification/MC4_3_correct.tla b/spec/light-client/verification/MC4_3_correct.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_3_correct.tla rename to spec/light-client/verification/MC4_3_correct.tla diff --git a/rust-spec/lightclient/verification/MC4_3_faulty.tla b/spec/light-client/verification/MC4_3_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_3_faulty.tla rename to spec/light-client/verification/MC4_3_faulty.tla diff --git a/rust-spec/lightclient/verification/MC4_4_correct.tla b/spec/light-client/verification/MC4_4_correct.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_4_correct.tla rename to spec/light-client/verification/MC4_4_correct.tla diff --git a/rust-spec/lightclient/verification/MC4_4_correct_drifted.tla b/spec/light-client/verification/MC4_4_correct_drifted.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_4_correct_drifted.tla rename to spec/light-client/verification/MC4_4_correct_drifted.tla diff --git a/rust-spec/lightclient/verification/MC4_4_faulty.tla b/spec/light-client/verification/MC4_4_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_4_faulty.tla rename to spec/light-client/verification/MC4_4_faulty.tla diff --git a/rust-spec/lightclient/verification/MC4_4_faulty_drifted.tla b/spec/light-client/verification/MC4_4_faulty_drifted.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_4_faulty_drifted.tla rename to spec/light-client/verification/MC4_4_faulty_drifted.tla diff --git a/rust-spec/lightclient/verification/MC4_5_correct.tla b/spec/light-client/verification/MC4_5_correct.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_5_correct.tla rename to spec/light-client/verification/MC4_5_correct.tla diff --git a/rust-spec/lightclient/verification/MC4_5_faulty.tla b/spec/light-client/verification/MC4_5_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_5_faulty.tla rename to spec/light-client/verification/MC4_5_faulty.tla diff --git a/rust-spec/lightclient/verification/MC4_6_faulty.tla b/spec/light-client/verification/MC4_6_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_6_faulty.tla rename to spec/light-client/verification/MC4_6_faulty.tla diff --git a/rust-spec/lightclient/verification/MC4_7_faulty.tla b/spec/light-client/verification/MC4_7_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC4_7_faulty.tla rename to spec/light-client/verification/MC4_7_faulty.tla diff --git a/rust-spec/lightclient/verification/MC5_5_correct.tla b/spec/light-client/verification/MC5_5_correct.tla similarity index 100% rename from rust-spec/lightclient/verification/MC5_5_correct.tla rename to spec/light-client/verification/MC5_5_correct.tla diff --git a/rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla b/spec/light-client/verification/MC5_5_correct_peer_two_thirds_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC5_5_correct_peer_two_thirds_faulty.tla rename to spec/light-client/verification/MC5_5_correct_peer_two_thirds_faulty.tla diff --git a/rust-spec/lightclient/verification/MC5_5_faulty.tla b/spec/light-client/verification/MC5_5_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC5_5_faulty.tla rename to spec/light-client/verification/MC5_5_faulty.tla diff --git a/rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla b/spec/light-client/verification/MC5_5_faulty_peer_two_thirds_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC5_5_faulty_peer_two_thirds_faulty.tla rename to spec/light-client/verification/MC5_5_faulty_peer_two_thirds_faulty.tla diff --git a/rust-spec/lightclient/verification/MC5_7_faulty.tla b/spec/light-client/verification/MC5_7_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC5_7_faulty.tla rename to spec/light-client/verification/MC5_7_faulty.tla diff --git a/rust-spec/lightclient/verification/MC7_5_faulty.tla b/spec/light-client/verification/MC7_5_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC7_5_faulty.tla rename to spec/light-client/verification/MC7_5_faulty.tla diff --git a/rust-spec/lightclient/verification/MC7_7_faulty.tla b/spec/light-client/verification/MC7_7_faulty.tla similarity index 100% rename from rust-spec/lightclient/verification/MC7_7_faulty.tla rename to spec/light-client/verification/MC7_7_faulty.tla diff --git a/spec/light-client/verification.md b/spec/light-client/verification/README.md similarity index 99% rename from spec/light-client/verification.md rename to spec/light-client/verification/README.md index 9166923e8..8777374ac 100644 --- a/spec/light-client/verification.md +++ b/spec/light-client/verification/README.md @@ -1,3 +1,9 @@ +--- +order: 1 +parent: + title: Verification + order: 2 +--- # Core Verification ## Problem statement diff --git a/rust-spec/lightclient/verification/verification_001_published.md b/spec/light-client/verification/verification_001_published.md similarity index 99% rename from rust-spec/lightclient/verification/verification_001_published.md rename to spec/light-client/verification/verification_001_published.md index 05baedb3f..0a63803d7 100644 --- a/rust-spec/lightclient/verification/verification_001_published.md +++ b/spec/light-client/verification/verification_001_published.md @@ -502,7 +502,6 @@ const ( ) ``` - > Only the detector module sets a lightBlock state to `StateTrusted` > and only if it was `StateVerified` before. @@ -533,12 +532,12 @@ func (ls LightStore) LatestVerified() LightBlock ```go func (ls LightStore) Update(lightBlock LightBlock, verfiedState VerifiedState - verifiedBy Height) + verifiedBy Height) ``` - Expected postcondition - The state of the LightBlock is set to *verifiedState*. - - verifiedBy of the Lightblock is set to *Height* + - verifiedBy of the Lightblock is set to *Height* > The following function is used only in the detector specification > listed here for completeness. diff --git a/rust-spec/lightclient/verification/verification_002_draft.md b/spec/light-client/verification/verification_002_draft.md similarity index 99% rename from rust-spec/lightclient/verification/verification_002_draft.md rename to spec/light-client/verification/verification_002_draft.md index 08bf0a807..e502880bb 100644 --- a/rust-spec/lightclient/verification/verification_002_draft.md +++ b/spec/light-client/verification/verification_002_draft.md @@ -523,7 +523,9 @@ then 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 @@ -555,15 +557,16 @@ func (ls LightStore) Latest() LightBlock - 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 @@ -573,8 +576,6 @@ func (ls LightStore) store_chain(newLS LightStore) - Expected postcondition - adds `newLS` to the lightStore. - - #### **[LCV-FUNC-LATEST-VERIF.2]** ```go @@ -605,18 +606,16 @@ VerifiedState, root-height Height) - 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 + - returns a lightstore that contains lightblocks that constitute a [verification trace](TODOlinkToDetectorSpecOnceThere) from `root` to `lightBlock` (including `lightBlock`) - - ### Inputs @@ -690,7 +689,7 @@ func Commit(height int64) (SignedHeader, error) - if *n* is correct: precondition violated or timeout - if *n* is faulty: arbitrary error ----- +----; ```go func Validators(height int64) (ValidatorSet, error) @@ -722,7 +721,7 @@ func Validators(height int64) (ValidatorSet, error) - if *n* is correct: precondition violated or timeout - if *n* is faulty: arbitrary error ----- +----; ### Communicating Function @@ -753,7 +752,7 @@ func FetchLightBlock(peer PeerID, height Height) LightBlock - if *lb.provider != peer* - times out after 2 Delta (by assumption *n* is faulty) ----- +----; ## Core Verification @@ -875,7 +874,7 @@ func ValidAndVerified(trusted LightBlock, untrusted LightBlock) Result - Error condition: - if precondition violated ----- +----; #### **[LCV-FUNC-SCHEDULE.1]** @@ -958,16 +957,19 @@ func (ls LightStore) LatestPrevious(height Height) (LightBlock, bool) 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]** From b4a31746dd9e517b0fbedd98722f4a318fa4e713 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 17 Mar 2021 03:43:54 -0400 Subject: [PATCH 140/223] Fix list of RFCs (#266) --- rfc/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rfc/README.md b/rfc/README.md index 9405b3fb0..b07a2c37f 100644 --- a/rfc/README.md +++ b/rfc/README.md @@ -22,6 +22,6 @@ Some RFC's will be presented at a Tendermint Dev Session. If you are an outside ## Table of Contents -[001-block-retention](./001-block-retention.md) -[002-nonzero-genesis](./002-nonzero-genesis.md) -[003-ed25519-verification](./003-ed25519-verification.md) +- [001-block-retention](./001-block-retention.md) +- [002-nonzero-genesis](./002-nonzero-genesis.md) +- [003-ed25519-verification](./003-ed25519-verification.md) From 9f6a4bcf23b0efa7ef6ec7c89a202c9ea85ec02a Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 17 Mar 2021 13:51:10 +0000 Subject: [PATCH 141/223] readme: cleanup (#262) * modify readme * add rfc and proto * add rust=spec back to avoid breakage * lint readme --- README.md | 48 +++++++++++++++----- rust-spec/fastsync/README.md | 3 ++ rust-spec/lightclient/README.md | 3 ++ rust-spec/lightclient/verification/README.md | 3 ++ 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 rust-spec/fastsync/README.md create mode 100644 rust-spec/lightclient/README.md create mode 100644 rust-spec/lightclient/verification/README.md diff --git a/README.md b/README.md index 678574873..dcadaf9ce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Tendermint Spec -This repository contains specifications for the Tendermint protocol. For the pdf, see the [latest release](https://github.com/tendermint/spec/releases). +This repository contains specifications for the Tendermint protocol. There are currently two implementations of the Tendermint protocol, maintained by two separate-but-collaborative entities: @@ -9,16 +9,40 @@ maintained by Interchain GmbH, and one in [Rust](https://github.com/informalsystems/tendermint-rs), maintained by Informal Systems. -There have been inadvertent divergences in the specs followed -by the Go implementation and the Rust implementation respectively. -However, we are working toward reconverging these specs into a single unified spec. -Consequently, this repository is in a bit of a state of flux. +### Data Structures -At the moment, the spec followed by the Go implementation -(tendermint/tendermint) is in the [spec](spec) directory, -while the spec followed by the Rust implementation -(informalsystems/tendermint-rs) is in the [rust-spec](rust-spec) -directory. TLA+ specifications are also in the rust-spec directory. +- [Encoding and Digests](./spec/core/encoding.md) +- [Blockchain](./spec/core/data_structures.md) +- [State](./spec/core/state.md) -Over time, these specs will converge in the spec directory. -Once they have fully converged, we will version the spec moving forward. +### Consensus Protocol + +- [Consensus Algorithm](./spec/consensus/consensus.md) +- [Creating a proposal](./spec/consensus/creating-proposal.md) +- [Time](./spec/consensus/bft-time.md) +- [Light-Client](./spec/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 + +#### P2P Messages + +- [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 + +### ABCI + +- [ABCI](./spec/abci/README.md): Details about interactions between the + application and consensus engine over ABCI + +### RFC + +- [RFC](./rfc/README.md): RFCs describe proposals to change the spec. + +### ProtoBuf + +- [Proto](./proto/README.md): The data structures of the Tendermint protocol are located in the `proto` directory. These specify P2P messages that each implementation should follow to be compatible. diff --git a/rust-spec/fastsync/README.md b/rust-spec/fastsync/README.md new file mode 100644 index 000000000..0714d1d32 --- /dev/null +++ b/rust-spec/fastsync/README.md @@ -0,0 +1,3 @@ +# Fast Sync + +Deprecated see [tendermint/tendermint/docs/tendermint-core/fastsync.md]([../../spec/light-client](https://github.com/tendermint/tendermint/blob/master/docs/tendermint-core/fast-sync.md)) diff --git a/rust-spec/lightclient/README.md b/rust-spec/lightclient/README.md new file mode 100644 index 000000000..b9fcce4d8 --- /dev/null +++ b/rust-spec/lightclient/README.md @@ -0,0 +1,3 @@ +# Light Clients + +Deprecated see [spec/light-client](../../spec/light-client) diff --git a/rust-spec/lightclient/verification/README.md b/rust-spec/lightclient/verification/README.md new file mode 100644 index 000000000..5f65f07ad --- /dev/null +++ b/rust-spec/lightclient/verification/README.md @@ -0,0 +1,3 @@ +# Verification + +Deprecated see [spec/light-client/verification](../../spec/light-client/verification/README.md) From b2465e0c3ae0864eacad4ef12c22ff92ac49d34f Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 23 Mar 2021 10:13:02 +0000 Subject: [PATCH 142/223] 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 --- spec/core/genesis.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spec/core/genesis.md diff --git a/spec/core/genesis.md b/spec/core/genesis.md new file mode 100644 index 000000000..1dc019e77 --- /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. From 5c32ebcda848612801a3f579179448ecaef1ddea Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 23 Mar 2021 11:13:42 +0000 Subject: [PATCH 143/223] p2p: links (#268) * fix links * fix more links --- spec/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/README.md b/spec/README.md index cf7d1c45d..31e740c44 100644 --- a/spec/README.md +++ b/spec/README.md @@ -36,11 +36,11 @@ please submit them to our [bug bounty](https://tendermint.com/security)! ### P2P and Network Protocols - [The Base P2P Layer](./p2p/node.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections -- [Peer Exchange (PEX)](./reactors/pex/reactor.md): gossip known peer addresses so peers can find each other -- [Block Sync](./reactors/block_sync/reactor.md): gossip blocks so peers can catch up quickly -- [Consensus](./reactors/consensus/consensus.md): gossip votes and block parts so new blocks can be committed -- [Mempool](./reactors/mempool/reactor.md): gossip transactions so they get included in blocks -- [Evidence](./reactors/evidence/reactor.md): sending invalid evidence will stop the peer +- [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 ### Software From 640b71038ba2c5b1a459e62aea0f26ec03d3346e Mon Sep 17 00:00:00 2001 From: istoilkovska Date: Tue, 23 Mar 2021 16:40:21 +0100 Subject: [PATCH 144/223] 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 --- .../proposer-based-timestamp/README.md | 19 + .../pbts-algorithm_001_draft.md | 152 +++++ .../pbts-sysmodel_001_draft.md | 179 ++++++ .../pbts_001_draft.md | 268 ++++++++ .../tla/TendermintPBT_001_draft.tla | 597 ++++++++++++++++++ 5 files changed, 1215 insertions(+) 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 diff --git a/spec/consensus/proposer-based-timestamp/README.md b/spec/consensus/proposer-based-timestamp/README.md new file mode 100644 index 000000000..ae7ec2337 --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/README.md @@ -0,0 +1,19 @@ +# 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 000000000..8162e461e --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md @@ -0,0 +1,152 @@ +# 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 000000000..b5521b696 --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md @@ -0,0 +1,179 @@ +# 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 000000000..ccaf55787 --- /dev/null +++ b/spec/consensus/proposer-based-timestamp/pbts_001_draft.md @@ -0,0 +1,268 @@ +# 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. +2. **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.) +3. **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. +2. **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 000000000..2bcdecb27 --- /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 + +============================================================================= From ea8238f09070707019d281a619b8a3318a28050b Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Tue, 6 Apr 2021 02:59:57 -0400 Subject: [PATCH 145/223] abci: reorder sidebar (#282) --- spec/abci/abci.md | 5 +++++ spec/abci/apps.md | 5 +++++ spec/abci/client-server.md | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/spec/abci/abci.md b/spec/abci/abci.md index b55a7fc5b..609c40293 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -1,3 +1,8 @@ +--- +order: 1 +title: Method and Types +--- + # Methods and Types ## Overview diff --git a/spec/abci/apps.md b/spec/abci/apps.md index 75944fab1..59b3dbc1c 100644 --- a/spec/abci/apps.md +++ b/spec/abci/apps.md @@ -1,3 +1,8 @@ +--- +order: 2 +title: Applications +--- + # Applications Please ensure you've first read the spec for [ABCI Methods and Types](abci.md) diff --git a/spec/abci/client-server.md b/spec/abci/client-server.md index 865957267..78c226743 100644 --- a/spec/abci/client-server.md +++ b/spec/abci/client-server.md @@ -1,3 +1,8 @@ +--- +order: 3 +title: Client and Server +--- + # Client and Server This section is for those looking to implement their own ABCI Server, perhaps in From 0dc5d4df07a5adf071770c8e3c09d3f7b86509ed Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 9 Apr 2021 05:33:45 -0500 Subject: [PATCH 146/223] 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/004-abci++.md | 251 ++++++++++++++++++++++++++++++++++++++++++ rfc/images/abci++.png | Bin 0 -> 2792638 bytes rfc/images/abci.png | Bin 0 -> 2644284 bytes 3 files changed, 251 insertions(+) create mode 100644 rfc/004-abci++.md create mode 100644 rfc/images/abci++.png create mode 100644 rfc/images/abci.png diff --git a/rfc/004-abci++.md b/rfc/004-abci++.md new file mode 100644 index 000000000..6ed2bffdf --- /dev/null +++ b/rfc/004-abci++.md @@ -0,0 +1,251 @@ +# RFC 004: ABCI++ + +## Changelog + +- January 11, 2020: initialized + +## Author(s) + +- Dev (@valardragon) +- Sunny (@sunnya97) + +## Context + +ABCI is the interface between the consensus engine and the application. +It defines when the application can talk to consensus during the execution of a blockchain. +At the moment, the application can only act at one phase in consensus, immediately after a block has been finalized. + +This restriction on the application prohibits numerous features for the application, including many scalability improvements that are now better understood than when ABCI was first written. +For example, many of the scalability proposals can be boiled down to "Make the miner / block proposers / validators do work, so the network does not have to". +This includes optimizations such as tx-level signature aggregation, state transition proofs, etc. +Furthermore, many new security properties cannot be achieved in the current paradigm, as the application cannot enforce validators do more than just finalize txs. +This includes features such as threshold cryptography, and guaranteed IBC connection attempts. +We propose introducing three new phases to ABCI to enable these new features, and renaming the existing methods for block execution. + +#### Prepare Proposal phase + +This phase aims to allow the block proposer to perform more computation, to reduce load on all other full nodes, and light clients in the network. +It is intended to enable features such as batch optimizations on the transaction data (e.g. signature aggregation, zk rollup style validity proofs, etc.), enabling stateless blockchains with validator provided authentication paths, etc. + +This new phase will only be executed by the block proposer. The application will take in the block header and raw transaction data output by the consensus engine's mempool. It will then return block data that is prepared for gossip on the network, and additional fields to include into the block header. + +#### Process Proposal Phase + +This phase aims to allow applications to determine validity of a new block proposal, and execute computation on the block data, prior to the blocks finalization. +It is intended to enable applications to reject block proposals with invalid data, and to enable alternate pipelined execution models. (Such as Ethereum-style immediate execution) + +This phase will be executed by all full nodes upon receiving a block, though on the application side it can do more work in the even that the current node is a validator. + +#### Vote Extension Phase + +This phase aims to allow applications to require their validators do more than just validate blocks. +Example usecases of this include validator determined price oracles, validator guaranteed IBC connection attempts, and validator based threshold crypto. + +This adds an app-determined data field that every validator must include with their vote, and these will thus appear in the header. +#### Rename {BeginBlock, [DeliverTx], EndBlock} to FinalizeBlock + +The prior phases gives the application more flexibility in their execution model for a block, and they obsolete the current methods for how the consensus engine relates the block data to the state machine. Thus we refactor the existing methods to better reflect what is happening in the new ABCI model. + +This rename doesn't on its own enable anything new, but instead improves naming to clarify the expectations from the application in this new communication model. The existing ABCI methods `BeginBlock, [DeliverTx], EndBlock` are renamed to a single method called `FinalizeBlock`. + +#### Summary + +We include a more detailed list of features / scaling improvements that are blocked, and which new phases resolve them at the end of this document. + + +On the top is the existing definition of ABCI, and on the bottom is the proposed ABCI++. + +## Proposal + +Below we suggest an API to add these three new phases. +In this document, sometimes the final round of voting is referred to as precommit for clarity in how it acts in the Tendermint case. + +### Prepare Proposal + +*Note, APIs in this section will change after Vote Extensions, we list the adjusted APIs further in the proposal.* + +The Prepare Proposal phase allows the block proposer to perform application-dependent work in a block, to lower the amount of work the rest of the network must do. This enables batch optimizations to a block, which has been empirically demonstrated to be a key component for scaling. This phase introduces the following ABCI method + +```rust +fn PrepareProposal(Block) -> BlockData +``` + +where `BlockData` is a type alias for however data is internally stored within the consensus engine. In Tendermint Core today, this is `[]Tx`. + +The application may read the entire block proposal, and mutate the block data fields. Mutated transactions will still get removed from the mempool later on, as the mempool rechecks all transactions after a block is executed. + +The `PrepareProposal` API will be modified in the vote extensions section, for allowing the application to modify the header. + +### Process Proposal + +The Process Proposal phase sends the block data to the state machine, prior to running the last round of votes on the state machine. This enables features such as allowing validators to reject a block according to whether state machine deems it valid, and changing block execution pipeline. + +We introduce three new methods, + +```rust +fn VerifyHeader(header: Header, isValidator: bool) -> ResponseVerifyHeader {...} +fn ProcessProposal(block: Block) -> ResponseProcessProposal {...} +fn RevertProposal(height: usize, round: usize) {...} +``` + +where + +```rust +struct ResponseVerifyHeader { + accept_header: bool, + evidence: Vec +} +struct ResponseProcessProposal { + accept_block: bool, + evidence: Vec +} +``` + +Upon receiving a block header, every validator runs `VerifyHeader(header, isValidator)`. The reason for why `VerifyHeader` is split from `ProcessProposal` is due to the later sections for Preprocess Proposal and Vote Extensions, where there may be application dependent data in the header that must be verified before accepting the header. +If the returned `ResponseVerifyHeader.accept_header` is false, then the validator must precommit nil on this block, and reject all other precommits on this block. `ResponseVerifyHeader.evidence` is appended to the validators local `EvidencePool`. + +Upon receiving an entire block proposal (in the current implementation, all "block parts"), every validator runs `ProcessProposal(block)`. If the returned `ResponseProcessProposal.accept_block` is false, then the validator must precommit nil on this block, and reject all other precommits on this block. `ResponseProcessProposal.evidence` is appended to the validators local `EvidencePool`. + +Once a validator knows that consensus has failed to be achieved for a given block, it must run `RevertProposal(block.height, block.round)`, in order to signal to the application to revert any potentially mutative state changes it may have made. In Tendermint, this occurs when incrementing rounds. + +**RFC**: How do we handle the scenario where honest node A finalized on round x, and honest node B finalized on round x + 1? (e.g. when 2f precommits are publicly known, and a validator precommits themself but doesn't broadcast, but they increment rounds) Is this a real concern? The state root derived could change if everyone finalizes on round x+1, not round x, as the state machine can depend non-uniformly on timestamp. + +The application is expected to cache the block data for later execution. + +The `isValidator` flag is set according to whether the current node is a validator or a full node. This is intended to allow for beginning validator-dependent computation that will be included later in vote extensions. (An example of this is threshold decryptions of ciphertexts.) + +### DeliverTx rename to FinalizeBlock + +After implementing `ProcessProposal`, txs no longer need to be delivered during the block execution phase. Instead, they are already in the state machine. Thus `BeginBlock, DeliverTx, EndBlock` can all be replaced with a single ABCI method for `ExecuteBlock`. Internally the application may still structure its method for executing the block as `BeginBlock, DeliverTx, EndBlock`. However, it is overly restrictive to enforce that the block be executed after it is finalized. There are multiple other, very reasonable pipelined execution models one can go for. So instead we suggest calling this succession of methods `FinalizeBlock`. We propose the following API + +Replace the `BeginBlock, DeliverTx, EndBlock` ABCI methods with the following method + +```rust +fn FinalizeBlock() -> ResponseFinalizeBlock +``` + +where `ResponseFinalizeBlock` has the following API, in terms of what already exists + +```rust +struct ResponseFinalizeBlock { + updates: ResponseEndBlock, + tx_results: Vec +} +``` + +`ResponseEndBlock` should then be renamed to `ConsensusUpdates` and `ResponseDeliverTx` should be renamed to `ResponseTx`. + +### Vote Extensions + +The Vote Extensions phase allow applications to force their validators to do more than just validate within consensus. This is done by allowing the application to add more data to their votes, in the final round of voting. (Namely the precommit) +This additional application data will then appear in the block header. + +First we discuss the API changes to the vote struct directly + +```rust +fn ExtendVote(height: u64, round: u64) -> (UnsignedAppVoteData, SelfAuthenticatingAppData) +fn VerifyVoteExtension(signed_app_vote_data: Vec, self_authenticating_app_vote_data: Vec) -> bool +``` + +There are two types of data that the application can enforce validators to include with their vote. +There is data that the app needs the validator to sign over in their vote, and there can be self-authenticating vote data. Self-authenticating here means that the application upon seeing these bytes, knows its valid, came from the validator and is non-malleable. We give an example of each type of vote data here, to make their roles clearer. + +- Unsigned app vote data: A use case of this is if you wanted validator backed oracles, where each validator independently signs some oracle data in their vote, and the median of these values is used on chain. Thus we leverage consensus' signing process for convenience, and use that same key to sign the oracle data. +- Self-authenticating vote data: A use case of this is in threshold random beacons. Every validator produces a threshold beacon share. This threshold beacon share can be verified by any node in the network, given the share and the validators public key (which is not the same as its consensus public key). However, this decryption share will not make it into the subsequent block's header. They will be aggregated by the subsequent block proposer to get a single random beacon value that will appear in the subsequent block's header. Everyone can then verify that this aggregated value came from the requisite threshold of the validator set, without increasing the bandwidth for full nodes or light clients. To achieve this goal, the self-authenticating vote data cannot be signed over by the consensus key along with the rest of the vote, as that would require all full nodes & light clients to know this data in order to verify the vote. + +The `CanonicalVote` struct will acommodate the `UnsignedAppVoteData` field by adding another string to its encoding, after the `chain-id`. This should not interfere with existing hardware signing integrations, as it does not affect the constant offset for the `height` and `round`, and the vote size does not have an explicit upper bound. (So adding this unsigned app vote data field is equivalent from the HSM's perspective as having a superlong chain-ID) + +**RFC**: Please comment if you think it will be fine to have elongate the message the HSM signs, or if we need to explore pre-hashing the app vote data. + +The flow of these methods is that when a validator has to precommit, Tendermint will first produce a precommit canonical vote without the application vote data. It will then pass it to the application, which will return unsigned application vote data, and self authenticating application vote data. It will bundle the `unsigned_application_vote_data` into the canonical vote, and pass it to the HSM to sign. Finally it will package the self-authenticating app vote data, and the `signed_vote_data` together, into one final Vote struct to be passed around the network. + +#### Changes to Prepare Proposal Phase + +There are many use cases where the additional data from vote extensions can be batch optimized. +This is mainly of interest when the votes include self-authenticating app vote data that be batched together, or the unsigned app vote data is the same across all votes. +To allow for this, we change the PrepareProposal API to the following + +```rust +fn PrepareProposal(Block, UnbatchedHeader) -> (BlockData, Header) +``` + +where `UnbatchedHeader` essentially contains a "RawCommit", the `Header` contains a batch-optimized `commit` and an additional "Application Data" field in its root. This will involve a number of changes to core data structures, which will be gone over in the ADR. +The `Unbatched` header and `rawcommit` will never be broadcasted, they will be completely internal to consensus. + +#### Inter-process communication (IPC) effects + +For brevity in exposition above, we did not discuss the trade-offs that may occur in interprocess communication delays that these changs will introduce. +These new ABCI methods add more locations where the application must communicate with the consensus engine. +In most configurations, we expect that the consensus engine and the application will be either statically or dynamically linked, so all communication is a matter of at most adjusting the memory model the data is layed out within. +This memory model conversion is typically considered negligible, as delay here is measured on the order of microseconds at most, whereas we face milisecond delays due to cryptography and network overheads. +Thus we ignore the overhead in the case of linked libraries. + +In the case where the consensus engine and the application are ran in separate processes, and thus communicate with a form of Inter-process communication (IPC), the delays can easily become on the order of miliseconds based upon the data sent. Thus its important to consider whats happening here. +We go through this phase by phase. + +##### Prepare proposal IPC overhead + +This requires a round of IPC communication, where both directions are quite large. Namely the proposer communicating an entire block to the application. +However, this can be mitigated by splitting up `PrepareProposal` into two distinct, async methods, one for the block IPC communication, and one for the Header IPC communication. + +Then for chains where the block data does not depend on the header data, the block data IPC communication can proceed in parallel to the prior block's voting phase. (As a node can know whether or not its the leader in the next round) + +Furthermore, this IPC communication is expected to be quite low relative to the amount of p2p gossip time it takes to send the block data around the network, so this is perhaps a premature concern until more sophisticated block gossip protocols are implemented. + +##### Process Proposal IPC overhead + +This phase changes the amount of time available for the consensus engine to deliver a block's data to the state machine. +Before, the block data for block N would be delivered to the state machine upon receiving a commit for block N and then be executed. +The state machine would respond after executing the txs and before prevoting. +The time for block delivery from the consensus engine to the state machine after this change is the time of receiving block proposal N to the to time precommit on proposal N. +It is expected that this difference is unimportant in practice, as this time is in parallel to one round of p2p communication for prevoting, which is expected to be significantly less than the time for the consensus engine to deliver a block to the state machine. + +##### Vote Extension IPC overhead + +This has a small amount of data, but does incur an IPC round trip delay. This IPC round trip delay is pretty negligible as compared the variance in vote gossip time. (the IPC delay is typically on the order of 10 microseconds) + +## Status + +Proposed + +## Consequences + +### Positive + +- Enables a large number of new features for applications +- Supports both immediate and delayed execution models +- Allows application specific data from each validator +- Allows for batch optimizations across txs, and votes + +### Negative + +- This is a breaking change to all existing ABCI clients, however the application should be able to have a thin wrapper to replicate existing ABCI behavior. + - PrepareProposal - can be a no-op + - Process Proposal - has to cache the block, but can otherwise be a no-op + - Vote Extensions - can be a no-op + - Finalize Block - Can black-box call BeginBlock, DeliverTx, EndBlock given the cached block data + +- Vote Extensions adds more complexity to core Tendermint Data Structures +- Allowing alternate alternate execution models will lead to a proliferation of new ways for applications to violate expected guarantees. + +### Neutral + +- IPC overhead considerations change, but mostly for the better + +## References + +Reference for IPC delay constants: + +### Short list of blocked features / scaling improvements with required ABCI++ Phases + +| Feature | PrepareProposal | ProcessProposal | Vote Extensions | +| :--- | :---: | :---: | :---: | +| Tx based signature aggregation | X | | | +| SNARK proof of valid state transition | X | | | +| Validator provided authentication paths in stateless blockchains | X | | | +| Immediate Execution | | X | | +| Simple soft forks | | X | | +| Validator guaranteed IBC connection attempts | | | X | +| Validator based price oracles | | | X | +| Immediate Execution with increased time for block execution | X | X | X | +| Threshold Encrypted txs | X | X | X | diff --git a/rfc/images/abci++.png b/rfc/images/abci++.png new file mode 100644 index 0000000000000000000000000000000000000000..d5146f99573950d5b5a9f7fce594a2cb3c719c5e GIT binary patch literal 2792638 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
%h}`j;0`boDT56}}aPE#IrkxkW zDOzI9{9bZXkYRoy9v0k_$hZ0f`SA$N;kQCjnx4B#vu_oFpO^S z(l1TPSB62}FYJyPXqpNBl&A94Vg$-Fq)j8T0U|3uda$}L9aKiAwtPTHPBCB{{WL&@ zDYb9fD$+Y-UgN06@~S|BoSy!S!jR^=NQHuFPaZ6IPRxbVfPDkSNA=vbGoeQH4<(!g zp8Tt`@38GYdCi@Q<|re@Dv=FUGxo?}X1d4|s2O9&WVUb!`9`v1FA_R&=8JV*Xg_-6 zZz3N8ZiZY7XA1{}!^56-88cGiH`*GnluSj=yfB6F|$O)U-}PHy2Z8`M4-#tzhdv zQbO8Ph{}WhyCk)o$8s)K;6Q)QV03r%y5?e?2A`sO{>Sfrcrzu8UJ`9ZeemoX$U(wK zDNCk{-YC!Yv-ko)kkIHKzrL#k66Ii`9fDQmW6A_RP;P}pW)&UHyzHX!VCqE=YE=88 zBUbkq_3QBu_lz`}!98U{n?}0U`p4f$Z-kN@)=dy#^hA0T9l5_V!v(qzsu|1BNcPD#OMWhFF_|ot$wz#WQw~Jps%yJJx|#xIxYRYK$r%DTkS1QM;KYC*F%f74LS^m}5H{QhCL4u`4sr>3#2u77kcC>N}8@rquJrFo|5%Is+f; zbOwLH1`^~BhU!!O9y{@g9Lj2T8F_b$Z~9868HZiJ;Z^-x5KJ0j&@ucMRmm~JeCBo^ zSww?HcRicVBt0r-ZAQY=vX3S%oT`*(>j9T5k0f_eRB7bdSGKSlU;m76%0^L%9mzbs{d330gJc%o0PRkDrm6%xHu9Y~dBt4Qo}U~eZ*uDN zS!j;rrc)A>wta`XC)rf>55DB?DU&%9IR8>ohs!|qD>)3!Q8uj-z9#hoh4UO{O zzb*dms7r^bDPXu@^sl;>8Yj_hKQOvmf8s3yk(|Na`BW^TvDLZnO8RJ0yss2Xzzk<9 z78z6PPQRn;ndW3i2exz9xBljO{rxSfdTzZ~V~Yh+%Hl+=2~|@kAnC|oybXKM;{Nt3 zeQ{KUBB-hBPvEeTVTS4>RxW7JYg+N84|ssR7f^?L1d#Ar$*SqP)=R?Ob#bx~BC_6* zv$}+*S!6e8-yV=RPu6327Zm7$u!$SKXnfEWc|nwUXD8gNr92l){~KF037_W+JyAaT zgGFU`4e-|y%4+z*eC{k~Zu%^fvNfXVPyxVYC4e&IWKgp#H&8suWYTOPlx4^=vMada zxjP|3wMI>N?Y_}rqGQLwaLJhS?jeKE>-&?!tDB=a{@*@QJ{)$Ou!!l9dIq6{b(9-- zhEZ;`fXFD3_@RoDlfF^*n?a`lxQm7>^7%a3#)_nhyK8$~e&<~=##EMI$=`-gEhI9h4E zAogH%Bw%^}h2WW4q^6>mdlNMgEnO=^enaS@v4wlOGm?eULFG(j?@Fj;8tPcLjJN#o z@wUM-s!3HdBR=)im0J(C$^OG$PHrB`@5=SdB*Dt>)!XpE+aJ2~K4^s0O&KwWd5LSJ zS|<9}+X&C?LFInuisR6qMME#F<%J(1Qpch~-#)Lk#34St;pE5}yR(U<{G1R5)f?b0 ztja=sW2xUb^V~1R#C=Nxyr|0AsA6)9Sr{O<_P|@pGz+}G^jd@HSyAO~QosPQMoGobV+Fnl z6fjzOH24+?4f4KkxL^xddXLcYv9MoNwaaXl*ifl!J^~r zf=|ak1Rm(=;%^?w>*J$?$(K#-j-e@scG4#EBaF`6ujtR;!=LNbL|*oZY{z!8tH%_V zvGOp_yHGbSIurQ!EK-dwLdko=IF0BK?PU23@R-GyZ9cJTH;qFk=_L@VC-_MoLBZTe zusRp7M=qSH_IE3i`=renQ*!Ww}VlkLzN}%Uz|kT5V@1k%4>}y&Uf%yxBWan-Vmb1*c{`oUGm5)gi(1;GhT`q2e{o zT+t}{QO^UtE;l-G%Jqw51l{3e! zZQFgMhz90)T9!Pxf`inNjQ(omiYXL*Z$#fe(bObH42a@g9WXlI{57)&m7|lJT89Xi zi4*253DHnEPIdhQ%7&L;6xZ~k}y1=el z0k{yA3C~dvkIpt%m!jNzf;}cdecHAILo@J58w(D%0VG>>a-}91M~`huO%`nGL*lx=_AC>^6{&Q64PoD zXf6B1?^3zu%I1`F%%EWV=&`p>vS`hSWkHI|4ficY3Qc>be{w!_4!O}AHNi54hrR@f6UhMH(+Z- z5*kq(=3!VMWpa8N(u+8_PIN*ZZlIY!6t@O~-nNI1E0j}dp~|30^aTtvaJWZoq-crS z0p5CJU1t+%>86cmst>NWdvJ!24)6!kBF(a_OAVb^8X4Y2AqaJ-be}kC-g=c04c;;( z9Z!25pVxdxJTptbX{I-KVRS0X#j&<(f4nfV(OoKOvKTkE9?QmWchPW8-g=BGs{79cPNx+;E%^F(w|F-NV35=QTcR8d3)3-#jJ?~>}=N&7?L?D&87WJSnKEJ?{uj;#cEm|4m zSg1I`jGHT8lM_d^!}|AqG?e|A%FoPsXaTk73c$uJs@tvGelH30u7gDz2i#c`(ETn> z-onjh^Jo0vE$5Y>wNTDC{kb{Sl_E?rmZghgWcQ#yar&=@oUcsKU>L?%;DYVOUqV`1eg-FKWb)9G+oS?Snx>2h%1Xgua&`#a>Ai@z8fKn*oJ-= zb;tIXNu5^jYuks4^C6>uePRcdf+^Y&a~Ig8JJDt$x$iVvz8VPji7J`s1Pmvh_bh6c zCN}993xJZj!Icz{^<>RDv1dX>u}d9`aMgr~WQ_+#HLr2F>gLLvU*5BIuh1GF-E0m# z`g^auhwLQz88{k`E!3%1tnhAv?5-eo>3e)n@rKPjzc(hh&Ll2v2yi~ZRgt5J^1H;Hr=K3;X`!#cXr#Iz_;B>Uilywy^=etNs$Aiv# zPEp3B(Z_IhZ(A(ZgMJ(={EA5AvI4+o#0|&Sn;Yz_B`1<$&R^P+ zTYpi#tC+ORNC*~v(fA?JDL$U-KK+oeRL>W?sxq1fU_Y4;#0d*5Cj_7cfCU~K>TZMB zn>2c z&suVQHRc(4S8n8RWYvJIZKh&D-YuV(d)-#3e_7Uh4x*o(C9RZ1;qaG1z`x=GOteq- zvpd@?#Qotdh$izgo8jM36UN1UqY{eB5a0+v3QvhiD3Ka*QiRxsLy;l_oK)lOKWk^D zQ7GtjtEAf~1;&HuKyIODIWn8Z-{y{kEuFHY$&o&VvfBgo@1v~>5Y0X(51&3e8P#p8Lq4eOdMSpD zR4!fQh6XaB)mqakqCuEU;0SZGNW7_~PAk`dOQl-+*kwm>(1xIOvegvYicGu6E(Ksx zT1r_A7^RFe-}YGJ2sQCFc6H8=R%84JT*z4M&4^wLhDE@N%gyBG_>a|2GMW)X8AnbI zBwb$sce3!d*-2!RJ^ZoTnZV^@2et@8Hh2~Ey9?iZDTZLcXPoEVS_xkJP+H_NQ@;y! zr_9GKSiGxrzHRgGvUf=~&V#pM@u>PIvfM*oqdHyX6gPA@cFQ$G4qdD>+6NAC^ncN= ztdzrluJFstFbrCD;vn2W1Xg$Mkz4Ixv)zK$8ojz~6r3Z-*)nxno>*4(#i_E^F{9E= zU7YBT&JMvk8~ByNr74m zH=R<}Q;rx@OZv{X+GqRbp{oh79UcLhTFGkTafJ7a+DY@i#|tm97!yB!WN_u?2cI_H z(hCfMU`in`)?%Iw*fAcDb-MjH3C#GaPcEx)k&W4_Ut922IuH(x!U>q{iiR(ff`z;p zkujJ0aLjNfjzks`yT{R?rK@Kjy)PiYH503EaTWk1v9rXpMW; zeIhb`>tp@et4H^|uXePZ;z5)YFx_fH7o$`$?Rx{Nf~Zi|kLIM1%JUCnH1h$s8=5Pr zyOY_@v6altnfur-M9&XYOHlkg0N3+Dz9bO+1k!?+yn}AlJBXZ96HVhKB~Wu-c+(*| z^QJMV#sGqX*m=?-OrsunOV_8rnGrOU78&w1i9hNEC;y|^1d&=j=i!^=N0JoW0`+F7 z;0UUy$JP5&E*{Tvd^Ak(Z$6o`9$3WWnwhhuwsdU&ww~!i-hYVeGy}u#_)}ZvV7^!c zNkKAd=RIyFIBu^wU;hdbYcd?f74S;lJ*?!){=UM@BOt zPqBk_L6@suSUtdFz16NKF__dk>mX+swI&&#uHU3Vrx$HaSBzi#3ks6vx^WZGDzDA2 zw!SKW+R7)t2gFZaFc^qyQT3TqRndyz3$DGJ<-wVnZ7|X7S#wFZENJ1E5I1J8bUGDz zeBnj$6hmFwPYtBzUj7zJkt2ahfgpc%k>WosudsK87sr>XT!iHwy7oFMcp@f!XqOo? zbC;htbT_f#&m;X$RO%?wKhf4X$Hn{BYx4QW-;l>LYvYWnkDi(P=cG|FTn&S8~Wz^lkUvROyzD`=1^E~vI@ko^Hg6L`jv?isDQ?%OD*GZzuz&2VavHn~!Qm3|MwIH8{A;dDm*o9a%70tvBlw+8|#6MhcKY;(TmI+D$Osa;|xiv#>Z~ z^Y(@Qpz=Jo_nXx#S*(>^d8}0#y*v8ud#fa7{3HJA zY5}Oclip~RZ<+%r+da{4p}BD92`2y02FMa>3&x1Hh(j*iqQ%jRGEb4#*z@X)e$xk4STK4 zi15NhHXuZGqD-(>*C0VoC@*g*egNZ|FbaP<}&iu z?28>3k{g&%Dz`5$H6$5?_W0)NL|tvD)ftOyx3p4YQ0t^}^Jo!TC8=Y&$d`sIw^7L- z^*K>!luSozIu3=0cI4}T-ai3-$q^zGfmWSG_S|zgrNMMUbi~@9ccCagK$*vn=FH7z z_TrB$X?fC8L=0A|^_thcS1gs1izG8WB@| zYMfpike3#qU_l=IbFecc8swbcdDfoG7(1l~=Ne%x)rv{`GWS2Z(r|-Q({!XHl*JQIc)977PZ&vgCiAzz z{`04#@WD%iRzqp2xy;!$h^kig3~r*x@1qiQ&Z^P`bH&`t+`@}uDuACH-ah&AkqN2u zNZ6o<2kCjW0PGB7xQMB1ARJ~pX%OiY;CX(Y0nbRlhUEMrQ%ru{69p=^Gwt8_M!9V6 zrXYumpQyB6-Xlm+wyR7baf=$iwb><~hyjsAF2zmze!Fn1FBu@;D2FK{9QHqd;N`z0 zsGdW+saf^n`Hs4Hn{-8CzOZ492^WIS+F|+h|N2I04Rqe>VXm~DO z?~T7?;|4IKGPtE@Hl4hQ3PeEm193TUoBFh)F08sNw+NYd{zkt)_vHs_iV;Z61ver0 zyitPAIzP^UY0xidzE>;PJ6rG|^-gT&d5{{cW*y)*i;j{Cd0+k!3|dSR3_PuZVOj^b z?+kDdDmM~$LAHF=E&Rl=?w5RzRbKBMhqx~u@B)>$N>+7kr>+nnlcoPDwfSG|3a0<2 z^Xa^K0K@D;UcLm}r{~@fxQ_h27@`*^T%!Xh5BgXzV1U~F%;3jTCe5{Qgr^EXY|kV7s0SzI=nkJc>1D^-mv4*_C|){9cfQLju|+kaC2a^iypoDaq;k z9_zp@&)i^|=S5*NdD|pXz+gIP;DG?aXPq1w=A!nxnMqjCEMzyuv7(no9u2w9l{|3m zy87@?TpP@z-c$6?8f8iChgc8TCpT?Qc_RE2hpd^G^v=x(t058aVkez*DhF;v#oi^a zBKU!gbGkcQ%>%h)X}E&qm_A+TN?0E&b>lQ_s_(-8V&o9}c4zId+N>+G!Fdt=II_1c zUDqEa(j3(XlTdHRn{OHSt7o{$(2y5>h(!@1fzeyv)x{JC5IE zMXNS6x3X1zb#3X{2RrOkx5-;hY;C5f6hl|fp6#4$;t(J@7*n3e%aK*tkcsXJ%%P8- zXDf4?7FK2tOG-i;rAXE~asxaD&ft_NJ;q}(xS9I5V@BEQOMZwGoUO3-aN{}8RkU<> z;EE91QM+TaFrDvXU@eSNG@#(nF(~TTKNlb^DkV#)VzafN}azDct9Yh-)m zf8#F(s&WC?FOl$EDRCvlsO_w;+3?)X#7D-CmROwsgvy2ex7&}OB54cJ?_Mf( zgFBznuP<4OSe-{QO^J+}nILFa>kR$V0oxhssln{C5i)VIHqJ&Iw|6VCsgf$vLU938 z_~nL>oikzHMUH5Cv7zw=r|Q4&(Xlp|nPlHaZ!mL>TzqU*5&|pe_EO z4cp};8wlRG?(SoTgD3p7T^!FV?SX9Cc8D4i-yOfo6IHSZ!aRW_&`&$grnhB3C2JN! zn5=0DUrg2H;k+F9H;W{iMhj?|G!C&-a zzc$f%T~Kn$*K0{CV`uqS5ZHzmU}O0D3_D|uv3EzaVgjgaqJQvSF1@M|?Bmc-ao2C- zd!2U2#koOABor8U`?%2xH<-ux?t(3EM9}sx+P@<00=P0n6TMfD;I?CxW3|xrjvL_8 z`K^Z7Y-cK^R1)yk(#2Uk*jV)kKOFpYv9s7Zzv;Bm;gH3o$(JwCQu+Gy$IGF&NYG{? z3YtbSgdpincz0fff5g4z%wcBRzXkrSzm{sXGk6T{4gA;kyAvhj_8H367JTSd;^Fc% z+xsvZCdmV*Qgo$)onB%kTt@s|7^cw`x5(L&>b3M~!gO`T(Y2Is5E8^>b`vBnjcEK) z%K~ZXIaE{F$h?7!iR1x=X(MrquOUWZ@7;`BjIBXWZq;8=OI2U^~srG zLjf@qv08xf2KXRv?7g=EjqxpkZBj}8|0CJdK4LJ?tb3DUPOIe#8Tq12-K9sBTp(G? z#`cGJV8w|WG-eOHT))j`srMI+TJuH==fllL?Db2-|W9gajd(kFvPs>wy*V*!;Z>3USDz=Fk zc>6(B2DJR3v(wS}el*Dme*c)9zy0ETVb49(5rbm0uCl9>m?`Ng$%%&R*?d&|Q^{#8 zF*jTRRgGG&_?%$Q`Jc~qrjl=YM!pOF=W;Bzw!-XO*+?$|;O*C2B0NIOdHHvvvhJ(o zrQT>!I`^!V0W7?pn|7mOPnp9wT}d#!QsY64s={xW#MlFaEN+J_)|;k?)tQs1R&128 zR&27!A(C}mE6O_BMZ_l-ydXLfcliVEIqXb6mtP&-fD^Yq>ZbL1kno7*MkE3V_0Y+r z@}i0>KhhmF$+cOhZi(j3AjMBmAQL+T*4Oo250I#mmK@FUsOnW4IeA7j`VoXsE|7sC zOL;zw$Wxs_nhcXn{BjT{+Nd?RvL!=p9RnvyI=0!D{XaQ)(+!cpP|nfC;_us;`-sn$ zVr)bw8w|OvNG<7De#QWv%w1U`C;CBuClilOI{XO2oaUQ~DEJ+c)r}7y8GpZ$i9rXa zkWB=alhe4xQ$ws3*G#n7BKZQgJ2=J6QY`W87vRUG7mP?GJnq@1lKPJ4QV`|DOHEpvaY)q#1N%rbKROnc@v*H8 z7BVNfTA#BkMV0SxIH@t3zh9O?nrfZc;7Fv8ANNSwPd&E0AkNmRit2@V!!3(W z`?tKH9aM8MWy88UPKm-o+(KniE9m>vH^zF@qhE^!wl&|+|M9G{JDH9SZ$i^FcFFKg z{$c+_Z1|>*OA(nMBs#;H=<|XpSV0T)K_w!HP48j{S@2Uv)IP?$WfgPnLdRV82i1Uh z!C0o;WA1G<1Hi(^Agc#{I<7rFw7YpU;06%;#ixL_YD2#|qKD-nL| zv@~ND{CvX7Y6A6vn%N7aw!G`HU2kgL6OMFyH{>G5 zWa8b=h)sVyke#KJESpJjOOXUCToQ(t`W9u8VH!Cipw)mtyN4U5$x=Fg;HtjO)aQ@_ zhVy?0^51#IX8@f1L7QRXP&?Wew;%CmHEqtGRLC$>4YSwTSifH~6tHDgcm_&fv>d$o zLIrijf!l&3YEDxn;cnoqR@md~;95ziRXLF{h_u#8^{7@qDPv=U92o=OqFveLb@ara zLTxT^*Ycrv?^GQhPAnif($(kHH8JDs&XyH0du(T&nd=NQAIZGgL|p6_47qqa)-Dkm zvBm*8KR5`^sqf!jX|7Snm#*Vm0nEG~S`?zeKbo-2UZyLt%OAWIGtG1A$*$+}6YUKb zzA))`wBZEv7YDy`AYYF$`Ea$k9#d|v?|P4hNKLu#>f1@8ms)lZ4Tg^WV#1t~tJ}{E zd98)F7*G&x`D7-qk5Z4cJGfaHsc^4hTMCG)wOsbooJ6Jh4s(TDaL$KO%F9wnBm(3J zo{p}Q4sABz1k7*V7{PV#1?WL(D+@FE2{|H}FXLSR3?u2aAG0;4+Em6LMa6wiRTHg_ z_RcJiwyKwINF_|<{eBjyPDHWH%K0oITer0Rtc4%W(IW-JmakE|i# zNa@n?vZYw1Q8Y<^ww|J5lsSt~$G{83rGUZZlw2+g+|qvITcK*Eo`9tPHV|&L2mb*E zZ~}7SK-3*r?^Rm=SavMgcsqOuK5g=H;fU&>*&UrBOS*S+c*qaVhF4GVrUJL8?cAl; z;I-0`>cmEgoVx-@CcL9S1xv&ch8IyuLyp8JOH$`ozTM)vxI0r&TI}gRo!%`>u4Z?_ zu;~V$-O(Ptnj^6N_EhbFV_{3BpJ$YyulMO)YSkFfEV1hM<1)5ioP9?-PZR?+^C4-v zTaU+h*5N_>Tg~O^9GT{^SGw4oFMZN477vSSFXRg!SQ+fIupdQ5dM7$`?>Pvbf1Jto zKMxk%(T?2GwDVl9${W(a0)JM}VKj1w1&IxnAH_G!>s{D)r_P=i3_qxA+Qu|#M_Mc1 zPn;@ZY0^64W8?auP{lL?Z$G;xKGMs%^AvrViD(3&eaKW*6Pg|Fvx`(}%0?$8h8acQ zA)OxMc@zLt;uf@;n6P|02PQxLe1JYNy2;|mFrfxS)+b`aAk>?BYaPVAc$6yM{Y=~2?*w+b!ei7_QOljQqd2>KuLh8gy zbYy*plLJl9R!RxI27hO{QtBvdCG9r|@Q50)B!$nc@CbRlU@FYb#z?4l>gk`bR z(nJt;SJ^U7!O!d2{w2G1Q+z{T$v43FReLPt{8`+=pzQ$6Q?|q5I;bkES71_Fe?OPz z4Z5d3N4C@=hJBng@j?V(V`mh|ffmuHoREu)ncZ8 zU#r1IOVZx@K(i=AZ;f2{Q(hdsoKydMIG}hfHiD*T=tfIvgX-O{-~T8?BNu!n%O@;W zTmXi=A%=$S08_(oyJms=G14H_F6X5n5$DwgR_E0=^q!3QVDhW*f$F28H}AL~zdSQ| zhEUqAU z++-*1GykFJ_wbG7FIL0H+MW=2tzf1k4tEyC?rmy{vJsaOsL$PgyB12s>TkTf3qZr9c0m}ppMGE7c{ew~S~IHrcY=uaDE{kT-P z_iih8wT{{^T1oj_Y9YDFjjl;cD>ob?*dz|Eq+wwTA&{nSW6RvB;5_)%+b?YFm&UZj zW>qIAQIbsCb9e~cID|!;iVR!lw&;_^`yIL!5q2FGk??GM`^DX{U%6)=>-V-e0gOfU zUFeWbEtrJtBxM4l5*n+x$L`!DCn%<8(=X=m$0+?>@Fr8@eoUga?V zyz`g4i0#xsvqrCiR(%T<~2MZj6a0Wmy8_=8E!N>RH&~G z`K3`FQ4I&>rAA^(6}5+>6_P2e_c~9S#R;l2q=9!bN1OiEbaJ!rznzrhI+B)i=yH9$ zBK$cS<$$9s78Z3rC{~ByUp2@AfM5vj_ao8K4b77pK3xu)8M}QoUcXc*z_6nF-v3T_ zs9HrH@0^!cs)_BVn0Gq5_1=4yd-axm=^0$9s8)mpxqJ3hJs-j{{tdF^>Jubzc>9|0s>806OaJ#19Bcu5H3xHpqHj)Yv0(!h_e`JC&J`a&`f-L4;dO*^ZNPaR z<-W~9JSPEr(|?242lpF^_8pyU!p%QU>}3txuz-a@3z5>C$LhVLx&W&O;$81Ye7ZqZ z3iR$RgVf^4+e(Sx52`a$JUiYUM>UmZZoe(29k;qF23r{PSa1?V#i5Eg|t3tEHi})hA3#^2+VPJ12g+$ z3Dj1EaBuBx>&(j%27oubM@t#LtLoALUq1)n87$mv6dF~cI@RYo5rG#^4mDXdOw?Yl zUe6tzcKnIJL?+hwWK^a8@wdX<4-&Ue2~Cc3ga|zJ_LLMgbt^~I*3Kf!!JH7VzfyqX zsHs!Xyba=$u#0i-X~#3WwTv8E>wV)1qOFb(9UT^}bl8cfa4Bo&?We_W#{d%yEXu?& z!(&M7JoHKeNuPx=)E*VQ^0aJs)^s8{XV&@#A4I+^Q1^@s*BT#Fz?@=@^h3F=32XxQ zK(OwpTFtJT{zOKj`H$H4?q`CADj_$;&r27Uo7g;Q0J>tr>-4Eq=JZg>;<)Zha< zg5D0L2zy03#84@xHO0Y55plXE(s#Z{D`ZdNe0t;W%Q2bB&;HM2+u-BrjT~62+*(t0} zP5qgg%ihM`Z#9qNWqqT>{-$t4sgcrOSzbd h5@@W{iFO$Y!UH2l}Dv&N#OKfey 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%0AX;UMBDuJu1uY09ZlY_@U>X}UV#I1Gi%cDaI zR7T5^=mzBZWOf9{Yi!$Ub)lc0_x*#-&y|~#NC@i=JPe=~pffIbIf~5#Or+L^Hj%H1 zJRmtwSOz)OqA?M|FY-r!j~J5t(~0QiGfTg#BIz_^cNnHMPZ-+4;uJtN0fm*Ow9+CVA2$dv`W_d4E!gBMw|DU-jzx;SqU^1-`kAgZx8XyY$YBQTx!pW< z2BljdH}G>LW2M8DqLxqT%9JbSwxshn79#D571v`9J6Gcy1ggNtqo^E&`)rPCEQgvp zWQxTR(UlVzj)<@pulPSwLaCDfyO#vHG&91t*lyIp2`_xz)Bte%v4R zaiw)xp&j9_z&H1C9Ckv-Ov^VKvDlb2iZ{E4Myt#~ z6-FN+T^_Nkl?k#loY`XN!OpB=|4u*TPA|Y>0E-S|?RY{9etS}3kH(J?f?kpCl;-;0 zkL8Ry{6!-$@+va+O^2QkVKn~Ca%$zG$?+lRxj*OWVwtbTZ3rhKr5SqIbLYZ zAI{+Q*^MlMI=nB=nIA7B(O1X^r}@60V{T`aZbn%H}Ds`T}VJeW8Q+pJ8g zKemk43U7E(^qbqB?zihlhC8NH#%k6a_B{WIy!lY)v_QMAE)b?}2eI6O@gGcPsumB& z^jH$sz!CCBOc)bXy@wd8<0(DynFCLNut5G)VZO)K->rJI%!wr`XG+eV9b$Vs!D+D1 z$CA!SpFqIfTLnC#0|K0JhK6k;S4YU|g@-;n)Bcw{^SUNJIjgDErr`2( zA4L9pE%{FJe$?^Cs&nwD7C%JxjVVtJSGo}|ojoh{o@_LkMx`X!68uE4u1 zm6tP311X;N#Kw|vZGKCVt7_7V!QSQ1ch~B<{{`lOl}lT>5R4nfegZzJcRmf(j9W=s zi^fR!9Sh7rbs4%X)dG5_gVXwKQIo6U&HCuzw*(W>I$fcJJB^qBzDSQY#$N)CF0-|w z+#*>*rw#+X9e!Rr_gl(HzdjQMe+YOyyLjzSCNH@YXDg-tgq?ZySE{_&ProH7S&pr@ zG9eJw3Oz?_2z6RD+j7S{evOAMQ6j6k6Ep~~L8BsgW*qJ_)isBkcZ8vM;sSlE`93hP z@A3F_*Q4uP0Q$s+$bz&-3>dJp&qIFg`;7duL+-Wd#>5zQP6i@89nV2kas=X$Q2t6= zZ>HVym?(p5TCtR_*40xUOlxX~IE9^~@3a0!Gq^T%!%$cli;8`wzI+ZO$9AGFP6F6L z>6CGP`GJV@5nx>uErEZ;etiO-ZPgkPIpr+sblygU61TM?@=Z`z9U5ez@{D*kEWSZV zA6tDc6H?rRxj?tu(CjiT=yaBMiN-eno|B6fhM`u{67hRyIPr8*V>~HhF92yzg_;M1 z*L6{SCTTeWWl5KHg3-W$+~CaiC9|yxHMa!*drpmH_RD};yFRvgCo6dbM^9E2N{Xyv zoDf73meH)lF6=;r=qxPHh?JwRQU4u!(4^Te^bfdW4(Ba#pPY8XKoReoyx}UBi`VmI zsrgjU@f`T0`1GLO_jDt(Wqx!~E+BsQ%g4u*r$e_L>5-rfzdj(zL_=-E>ax4v2=sB3 zs3WYExmA6pK9*pL|30?Ol|x3TV9o1CZM*geSaW?cXI~DYxx{CM6r3^h`@!-zP2R2X zPp}^S%QV-jN=tDPf>}Xp<2Crh_Y{2fc6>ToNLCgYjh{g1ppRApcw7QMgUI$mmg+5* z*|b$*Ue!CkSlX4$j~n*=S`)`nFcY|r_j!no>R*z8UucXPva|o0OxH}64VzT4pz#>5 z&)SIv@di?+1YR@-kfL0T;4)39UE=aefBv_{A`|FkbOI93)%Mn3|w#pYh?mx z;<(m^yaeI1AIdJd9{!i2>-`xG?;-GsFVg;~-FLtLI-ye(f;;6k_uadhO39D^^JEZx zG@fYLdwTSI`w($ObN1KTwXt!*xvSQSL`3(_S-;QtlaDv8ur(zMeg90}Uk!G^l9bX7 zsjLdmEMxAIT^^Jq04Ym$+%J*<-Q%`*LI$XOZCC*K=F$l`MysB@ixi$4BI zr<;5igsaHRIqFNHzrZ?-Jaai}QSa+aW}INR0=+exj9-5-#I6seukd~x&R#f_&g=6h z`n1$xy8uUps};z)N*>LEIYdd`%pMgxG&RYFo==3{+zygTw}cX(9rS$q$^r~NDl z$@lyki_BU`Jj_s6nBA)G?($U@R~w-s|Bt;>&M??l?F0|Wy}p8Vd1c2UO=dK zK3$dR>YQnp<>W8G?5YQR(8INOlRbQQ^WXjuV_$7aXT%0*?F3TV0>aYNUk1F~14sMY0gY%%J)q9F}VFUy0@z=Zh;=v!1 zrJxacvkC;1UMmO9`psQCs#gUzE$jXG7kV1dH6=xxt+vN$t*Ck9D&8^sd2VVz=bsE3 zB*9&+;k47=>$aRghyBN zXMCVZd^C1%TYOlx;{qt)Fu^K4T6Xm?Cxx~Mjf8#aMVfVua}Y;xtVZClN(49~cBCF^ zy&!UFGxvgO_3tJ4Ag;dU0nL#c4ygCR0{(7MC>r;rpHoMj7b1un~+!Vr~eYXaqTPv#$e2pw{zT>K(C7ahv`$9)KO*S#5@9qXR7t8t&+N)f!Si z>Va810;0kWnO-L-K+?>yp2nU7&V%*#gFP<54*8?Mfe6|xY$qS2)qcSUWQe0+kB2lS zhAw$R(O?|F2%aU^cXkpQql3Us<87ug$ebW+PdM?D>9pkoPLTVyy9(z~vu z_rcJrvr+W^#Y)TE&slo=B+eclt8_w?5)1T{V%e5$d&W6={@S*(Q2Cl$Q8}Ozf(dDr{CF|kv&6uePH)+PI+-0FR zOC{g%0J+cfg`1mjHtq(vD}1>>wJtIc67SyHBbUQYc>26gEH9s`)$Z=aAVf*NX>~=S zmvxH7Z(`zb=9nTPd(MLQ9}&OXEmmUdfZYk_9{2mG|tFOOM;zcsx7p2>v}^g zroIniDKD?|H(R4)y@PUke)7((=mkd(9Tfo)F+Kvy@OCMvb&G1B{xxpDf7*y3;@e+Q zw|i?0Bhr$Q^Jal8mWHThS!0FkF5D*2BzYjX$f4byGRC$kwsCc}QOq3eCYON^NBW7@EY){6EV;0rVpyF;fxD zh|L3z0MPB;@59eF0w!tm^w#jm${H%G&{yfE zGp%#n4eODe z6zq83Fz#R*=zO7(>rNoVG#}_DrUQn|tE&GB3l7DBrV-Xdir?jp80wyN#arj?UViyn zw{W}3XE;M(S>(;K!=SNG_jSVWe_;x)ldm9!p>Fg+BPy@gv%u}$O_n@4eD}$EfawJfO?q(FeCnWqB++e?By@Y!D zHDYP%05Yimr7TMvqy*waNZgJ);5#Y|>Vg(TU@^HlEnA%DPASv&6Pv zJO1snz~}(q_8{5?lTEl!3!lY^5u4Of>v$=Qu1c2ZG^e5as)T7gDTZFBuD!9nx+wBn z8C9`@S$37oGOZ^2?Xc5qHN-ufd!(&4N zQzmEodt%aAS2)?2WOwCIGbW37&K{2tDUjmk6hc*pB+}WJfl7>Q4*Vr>2@71atlqHc z^ky}}+Z=ZLVs0YMF>Fd;&qEa2M^)L(Rj#*N1Jm7?r7@>nnOL+Mp$G+dN^vU*A>8cO zTEV*u4aQXZDz(EU_ALrnc;zw@!W#ND^eg$?vDp(Kb@=8B`;A`Y>B76y$#(k7S#vA(Sc9W3BTEqC_;7+e2q)7tbNUVDJHz>xv`CT>{3#PHGtFZe z_&*|e>^*+R+r;v^T+v(K9%E`w}k1!QZ;o2`iC1ydal%%n7Cb7#_ouIXSUtdp(XfIoBcVhYU>I8j1mr$GbUrY zs>{HTi?yW^`+`^v?!h4^9DoBxL=OQhWuJ@zk*dT_e4RX1b6F;!iRF4-2j?2xcE&m3 z-U#b*C|FiEVJ^wQ<(~ynPz_l|nH}V`h|Z|4wg8EQG-$Bv?npaQBRgj*-f&Qa?89Fm zy-Z}ntjKECk%xj`9eE>{1xwb@)g35a@$4q3IiWG5=9U=80`eJRl}Us^nsDfr8#9&_ zMfvppWv21#DkbZy%WykVAzNM_e*iKe$gg{DSxP{7#-=7|_QW-$S~i4sw1v!_3;c;! zL&L8|iP`JMW*Idb?N!THbZE{U&g^*>xFyoN%==`}S?VK1fgQ-B@#@VZNi5-!%BPwFweU5>g!D}l`JIUc4rHj;`me98S5?Btm zv7kXNXnykn)>9rmh_LFt8y+lE_GEZ)tG}1P4F}nJ^fz9(A@aXeEy-T;OzsxY40X^9 zfAmOqB?#umtDNMs4J>T7Vx|@W5t=r#VmPO^FGcr=M$aby94;EzR?t(v6Dd>@!4gQ^ zqPsMQ0=;K2(=4f912E2t(B`2fQR?4WVu7J(rd^Xn%SpLLlzOpt^&2h6aoYbKXhWyN|ty|c$+DCts8E3MI(Td z^511|iO-x+!gzq0Ie?~Kx_iQSK{7s80w0hl$I>j5Qc1HaZ7rK{*$J6Dv}Mc$gr8mI zHUiXKjwQfM(Irs(NMW?>S9HVsLTIlDk*eS_U(FlznrB_2i4{*w*`$V4?%D6zSEgug zgh2gi_)4WFA4#$-25JPJO!;2&&2?t66vM{MjF8aj5u9nl=4Bnpu+GG|?!(VpL9%8s z%L~_nQE^pMA6CDnH7n(fRm&Hm*_pxHQm`?FZq2OllLX&)lF7;w@5Eshl0|Z1xTTi6yc$AyBQWBKg! zdJyW+dsk?4I{jk*Kn1w!XU7{7pLbffL!hFN}r?#z>TBnwVpOoQFu9$T3tnW6nV(hosHyOwv8#UaS3DU;_M?H z$at>G2q*uU6BqmXgMVyOVnkE$w;WzHDj9zhs9suSPu3n`Wc(>aE`>W;yxmHMQ&tnN zmz4F5KEr+#{o4Qx!C`bWZ4`nX?G^(t6%H?6Jql)lvwt{}QE|cX=bavpY!pOlCz&(R zWlL%5SV~*W5SX*&bPjCjn<(o^&&`-YT&v~=^X(czQF|yqCDoLVbW2jttnF$72m8oD z$;{}hHedqyDPsGSyyjpufm)no_@q$8`RE$;%{qVPvN6$0<2E9Gq=TBn06Cb)da9+* zfBf+@tg>ROJe_aqw*&zolOuk(6|OsC=#8i=@~5*@D@oc@7AZQI?;QhjlH*(Y+-#px)Ce}c*4i9K#|ArBCFPz?d z`9dAksky^X>5oz&%mMl3aF%;j<)Qk`?g!)05P&Q?AuyhfzvnBT1C*~b`$PU#Hfx@= z)RElCJb7I4!24)K#Bp6)w8Qpkkil(mpqh$tG#ew6o++XAP>@9r3Uw70GhxUgNKD3z z#pC729{mr~dW=gtGig1_RQaW1J=a2DdX!okj3`t@pSv#M!O=rvqw9D3cI)G^M)M~) z#WJOBD%HC#{+8kAg?o(+NNax87|1 z84-e>xUeF%K%L2+f|mB2>4%O!fh~p;szcD6UsJ)XeU_Pnvo@*@+(L|Zk=zWD<#QxX z$U)dE=nQ41X-H5ul;ur(>`N!~t!&3+X@@c>VQm_lkbCxe+K^<<4ZaXQPE>3o3-r=e z33Ga5@%u~Eq@Kji%6&}ZD?N=-7{yhI( zVxH+MldJdkZ`@AH_bkUh9q!RPvuU&ngR)lu?i7B)Zib(nRJw1#Pf)=DkK<>)=MpBK zyM7}@PB%nBT0dFp;di;Vowa|mloO>bGwUG}W!SxqWz|bwm$ix6 z>!MsSyRa46ESV=bh9wPk2DlHn8D&q)`S$Q@?cZ6?5BrN(s+dS4>ju-mppS}{^q`5L zAI8O2%UD#SKQ z^@>JABi^ax(5>Y*N537g--tvt(*aQ$5=fA{fsCr}`o#1NVh zl#GbLU99kNn=iX6CfJ94x5%AnBJfwD{}n_#gUkUoLc1CL_{w$1M4&vlbZiN{(KgZBij@>%}!P2N*1@{ATnx)3~sg)KMrkE(Re;Oo)sU6hc5&=O@Sp_EHv zI_7ktUc}CVCkU!#ndIlZNVY?Q3&TJ%u*I@DMuzl+TQSeSleVFfYkg7dR_pZ%9x8{; zMo9n9a*wkc5jgE3QOeCMn)bOpPQ-FMa3t^@y-%S{{>ym%4)2!x_3)0 z#vb2Dbj^xcAH{GKNtc?o*=^Nk|99^5MtHy(bU>Za#olNy5$tv=TEWB;b8!S4?01iK zhA@W_#H0P*V_AX%mTh*KiB`H~iNX4)WWhkS)FtioF#I}F3H+&N2Sfb8 zB$OqKXnb?W`;tDsCFhv6?MSy%!2H>p8)iENnI> zeZ0wF9Q|4Wsx%iT+2+>SP>>uv>djj6D9?U#(kkm6Bm=dg*5j5MmKmYo z!6SaruRa8;gkLlT!3$r;8X3*J?H~vjAXaGU-;duFpq7tX6{ltQxC5;?m{tb0zZx)- zR-DUR^DsVwZ=s@CYaEV%uoEt zXB7VMv6u zhxc|)w6)s`lGM6{@YkA32L0xGWeL9Xxax>QM;(!>rJO&9I8xt|FOT~>0IUd4NCd0Q zT>Ig*l?bNcHcfbHW#LQfL}}8h>#-rkxth5p{e*p~j}W3~UOkeyBjEi_>)uFfj+;OG&d9T6Crrv$!E#JOWl6J-*>L#uz$(Y&)qdSV zuxeS4H6ZQ-2t1zVG7q1#gz3x|_D5t=K%oqNr>9MM_KAu=ygu$}^Z${{H))m*A@n}e z1TXUG&XGksmEh9Od@DMqj=tvUQvM6*#C%fpDCw~*x1sEgq4f-8q(+D(Q^AZF$YFjInnqCZmLEQxfSswg=l%-&a$h7l6UFL z+Y}_pE-kFt4kM+W6*(oP^TsU72YwQ+tpS?GBI+x7_1>*BPDm~`tW>h>!+uI?kZ=~6`lJk*0h;*OcXoGLmB6A4k#U7LjDNKT=RI$&V zI0~NzpHpM)#zaN-SwuR%^LplQB$3v)2qVP~L9%IQxp4|zwmu50DY&PU!u{Ew9j}9l z<*2-=%)4VmARybg&0g_`)+dl5neHr#FSS@VHPU(?yBn3x^V=3$wMUt=&A57Q=X^cW z8OGJ+tt_0q{i88LGHom5-ai{aaaStOL$yptOYK0gehbU~;8+JFbB`O09H!viUZD&4 zEMt?cqb?^8cqD7~%Axq%C9ju)jV^dkB8@g2hD#9D-MH9%)(3NupzhWZl}w?RpoOKM zSlnd%s2dLA-)_AlyWv@b2yfE+Woei`R%T@1_1q^He@%*4>F?;Zq>tpAo7ZcseMt+N zU@VPYzFah>2M<2>6s_9m2t=J@&_grO_uC)0}{*pbbjVY0gs$j5ImKY}H_} zi@k$hANO>0kOhMlz8J@}Eh|577`jU9JxhA8shd!DK{Fb9=f!jpuUbX@k&w0Tq*NlU z{gTuL0gO^CEH)IkQ)P`hZJ~go$gB<&Ry~VB8}gCgn{)R9@MTfipcxG*8XuAD zChXE<$ufT%hF1-YhJrd{E2x*^ux+8g#O!v77t+}#62>gFx+$fcEbQIX?~ANh0m&Do zX%B)|s8e#|hv~m+88yoaGtq{NSMBLmsTU*Sa$wjVs*vM1Bf?-nded0VXUn71PHMA3kh*puchUa^>jrVXaZy~LMW%7-+Q3+1LvjM@&+_(Q(* zOU;$r)>A1PIqI=$S*roS0D-gw2$Drh(NPPpN5NY@?A*dj(%{sN+NV+d0=p~8e2Oqx zwH2>e?X=ip@%1%({LVKfCGTs97#B14Gz8&Xoehr0=VNZ5>`F3(>zRdJR(xik;@(F@ znxZ8XlS46-qnKMa2?65`mr$le&P}3~?H7MQ4|fsEM0S~BBNjT%rF#>lPQTf$8fC49 zqBYuoFB$pGbWNh1)kXV?VLirsiNRiBDu!cHkU=7=-xy>HS=T2c z3#tG2F5_HbZmw^Q;E*8;nQKu=#c^;K-PM>Aj7)Kq)Wnp`~{PVa)uIKAW ztFCB4nMN`?_oWJdWi0~*s}^BNNYSaW5Wa;4wp3$NB5Z-PbjHUV17O^i9kNFIWpk>a zSV@(VxA+=sR2Qp4gT5(-4enR2fXXP8b&w2HrMzn=_Ned=1Ux__4&xtOgi>*$L3}7+ zinlp6LeYd4QvXp}NEsA48BriuUk%Q&j9viwwmwUTGEEfUS4lG`5*a;1jVD!qcWVf? zkUYkTiD#}rgQGC!2dNyP^&CwvnX+y(=*D;PCyr)c$#QBd{b}_6=k6(4flo%OdD_-V z2Zl0!l=d3gaC)*rUT_Vp7`Q6hq-v}7Ix+9@Sa0)!(w`k|#vuKK zQF)6Ax=%j4)vl^!{&8!u|Jt#;%cPaN)7NNwjBR(sL(nhdrg{-FiN~+)l>;WH z%usiEo;Nbz;?$?-Hgr7FKSPBQ@(UAg-lTO9UARmdDdj}}l#j)&L{K23_*A%WrqnN~ z-qjj$mk&zmlT(t%XsWO4nNe-o_ln|1|1(NI?p3dYG{BjX453$zy;^Jkw%OIe_Tb`9 z_%S~7ySl^YZDHts?woE8L0|v&{6$ixSTG$nS-rjQY<_i!XYkg=Bb(dG-NvBAGMSBZ zR&t9rjvJFLkEd-gGGDNsx!c2Lz&*$n7Z5@Znd`FdxG{3?lw@6)Rin{7U!ZPy^k#GaY(*S8IbKJT0N8`;Z{5%+_$xBS_PJ zP1V8@+9{iOw#aRWV=NvNp3L`#noTokK`Dc+?HL5(4?B*0d490v2d)C^f;VW}F?t3U z&0$P(-q-iY^ zdhK<}v>(CZjmsmJ^bw27cZqMv~t3VYRsz4|6qPEgj} z3rk350XRG&Rj*nFHOnZUMwj2R15m*V!i)ld7>47zA<4gk7TY1JYSkq;TsIAy|ph`h_tLnt}WuoyrR&B;tzbCBU z_S11`&1VWnO4qm$KJNahH4bbic23m^x#V2-EXx!59tQ}%C@HeveRY9!_Tbf~uOM|= z)zJQ{9?z7IXs?t~w0C(ozGH)%-pcfy=Z9+B@R|_`p;bgzsYV3Q#cp1j>Ced5`$Oec z-=QX%1)TDdmPk0IkSVs^a9N%{(KFH{!8Qqwrpc&_pOVsI%$!dL&(~R7y)f%tug*}Gi7noZ{5$qB@P_^!mkt>wa zY*WAQ%;BYdyd z5)N!sWyPNH<~VBtpFrhJMtl_eajeJ)Jsq5~QUuGGDu)uOThY^natEG#O_@7fJS+@! zoZ-(gXICFIq_idU@Ai>a)JP8Sy{waRKqX znAkKuE?HUI5d(U59V2ufW2(^Pta0kye`N|B&pGEana|P?*>wDW zt(PLd_7agOh7gD>F6>s})%7(^0kC}f_AkC_7p#G^M4Fsj}m*5o*ruhwadq;@)Hx?zJ+GzlD zMmIwQ(=rVW1m1Gi5lt&{GxJ;Gv%g)5o~-4CWUIMMs(tI~v?|(hov04@Kv0&tGXjIY z=!R{Sc}J#7PUnH6OnU_NyxF5V-akdX$hn>k3j$f!*6&>)sjok>B*UKD5TaMgt~asr zvyPGP+)^!>r?R8yjvV!4{0(ZEw_C~&^m?)HSp9Bk@jUf@tJxM>Z7p_Ld<($39Jj}l$}f8R~~zWgNKh`fvib4$kIH>H0#ul5RZXjx0Z*d10# zQXJ4Gsqo;a*pKf-0s+eJjrhYkudI?gMkA|W?{abbi(H1MD;puzw``N;30Ci{t(KeI zOEE#jh@VBez1aSCyM>HC6VIKC&%=tVlwwy3{!wd=L@SCMMJwNZmQyYwGO<=XtE z_Eo=MoANd9?~m;7hd&uYkkLz{U$xLdU2aEra9wNvWzB;cgq*Oo_e>w26mY+EOnl>9 zYlij|AG!9roc08P6DO&=4)O-3AH_7gz5cZ+`kS=I?ydarA-Vx+lBFMj+Y`6-dkDAB z!J9F@aX(ER$Mv1`M_f#RF_VwkwkD4(A9>Ob99=H$kA4i(!dh3Jz)7<@6XFzwZ-bfd zKWMY7l9>&>eU|E(z4x@1)cID;b?xbFx{-crPg87Ado~+sJN}tqiWk;MN1N_HVYD?! z7c^KNkSVveAMuJ4I9LCudfAemYV%?5o+JfoW&^Sh_r4^{jxhN@2@O&c;73hP{%yXr zi6})K0~jp*u=E zePPwd3n82Lv5uAhYla99+$q1w)|g|nsG z)D!pT{QjO$G~-kD>R5(V^cUFCr5!lZE-Z&xz#ZnjQi`r5FzmB~>3Yl3Ax8$pXH{uM zk>b0V8~>^jj1xXo)q9wzNt$3CNb0kaVM@G6hQx^d#gE1bZLKh+U^vHP_&a6qcZ}(^ zO7C&M(;wk`2v1nGT|hCjlcm^VUs(z&P+$bjfx477o%V1k=OUq91#^`tor?N0<~d}C zcH5~3>uG0VTlICMr4|YV>qN&>Zj481|KSpwhMCamM4bOV)|p2`Vm1GSNBLJrYI&jk z9$9h;vUWxYH^fNPl>{PL5OLSZH9_pS1RhY@aj=BiHiwr8;Q_rH(Km6eWtdvIyKbo-Lg3&-lcUl7Gl&gI7&!@u z>$1v)TjHttrbg4GDDUVZ`G;Xjq4Kd%X*=5!7TR<_(qg_sQ!JDhQK846Q;u&DP5OKXlklI!#Sxnc?l@QO0w!s^A9I zC)+E`BOWMeD7_=sw0C3}vwY4Ra7)>(QFI8jb&3&LcPvOR)0TzPEQ{g3sSve5UwJ)C zMD~kTsXVK;F5MrT=y`XkYOC6Rz1$W|wny)Uz}O~%mjs5#9D!fEO(kFRzs9S>G!iP! z0C6^(hkZ#X)#s5K|GfMPsC%JomP}`^mbtPCA0RO|&&()FWbn`RSdcv2jV>nb8`824 zZrBQxs7{58#G!gztT_^o+pKL#2SyAmP`X^y9RX(>0x~P$k>)}0t^pC}9&lx!eR6Yv zL<<f4Ll9c6l_fCzx<@~G+nDkg1a4sB&u4YVMCtt&3y5ic8DNb-kXKa zXMukdcD)@f4j{Y(M>!okb8lzT=)1q0&9D|o`=HKS-?`?d!{j@+sTFL^7rJBA{X6QW zl&ak9c_u|SFZ`3k1*XF|+&OC|5c)M|R=?nQ%Q%z4>*Vc&{l871^%Laa`SvENZLT~U z(HTy016r4+kf<4SVA%JguW{siMPhdT9rrtSZT|mgn>*hLe$IQBZ?xWOwE4d3wmF{* z;$7)UWY=-Q4fifgT7#R??^7_;B653+MC+yDmrLDeshpJIICob#wkq|K*Xw~yExY2bfY02}uT zKIY!Y>kl^!-Z{l3`X+{V`Ynn7eFipj)#V1#@j}1Fz94MRU+*GuwPFI;@)Fz$qL<_M zEj{VtNa*1oFPnG3xtG-fPoV1cr}0CgmK%XveQjOc;pAN3@`lUp`26m8c;x%%U2T7E zvvKBS35j9bl4;#G!C@NTqY`oB9WH8U6MC&XbJ) z@1#Se?I0t1(x0t6nS5JXBU;D%=ZCXfI@TriDYt%8?O_EV==ousXw|-iuEqFbKWRJm zAo-#p_YI)k3^E0$#yr?8pZ~$u;9DHADFtbz4&~Mk#-9$&zh+R=*$L@A@`t6`#+k~e z*@n_69_ouke!r2a_3D1iY0G;bmGW||V6~}{qg{`!X~bo#-%FBry^*Q$>Nc|(mQ~Cg zZ^Whr4KABBpu@$o={Ga+O)qsdu%!(Hsp%X_YLax z6dycT^hTUk-Bkc@f=zfwGp~KqvLwS=4mib&T(>HGwtL$62Y;yC$;?yV9dCO*1W8K$ z8iw#_u~{0}S3Xw}%RMM5Vq8->+)Y*Rt-4!1yl+J5I6r7`EoDab{)|qVb=dme{h%Eq z;7*$RGuZX5zVuj#!R*QVY7zN|wiDca6F50jO0_D4;9`R}{oorBpn@eqwP;E+fFz7B^Oh5fXnF;;|#Z4lay`RGgwmXn6;qfK2I+%KfQRI)<{1Goj zl@Gs~>3j?Ap%8DtNF@OF!<1YZ!{h$!Kx)X?yh(HE?Ph4%0hl|_t)_`G&l0D)uZNPG zEH`715D{4FM6Iz7S2y`74@V0EQShowK4zii*5j~tDn_rLAlx26J6l1j8A-mY5&^RU zh&gz*O9UoE6wMS^U7YgL|Kfq6=H|+!_XA@m_SeabKjRDq>V`{|+AITw>8Ow|@t+pp zBJZl{Cw*U<`GH7z8stRLmXC+TEzc>#fQW(=B=g<#esm(qlz%XQOR%I~S=7?dNL6`h zATu;yXaG3xl|ldDYca=>asG!LctcjjJ849L->a_1BeS{8+N^- zGtWxfnv_eeLne&^R|2#sEENfVjm4C0QA8kEO>xXl1L6YN)X{iM|Le@E=*U4LM6Y ziwli#NniQgEBmSWS8j!1+Q^^;thx(5o>wK3AVr7Q2GT*y0Ai6Xs>d z&SVw}gHztfRV}sp&5u8I^;=h8+km+lXULtEKLzlAb}R46&`UYP5x4e(yo&#1UH%WcIus9BIDFv?fRCG zXZdkVk$X~D*wB`oHPp&SZsr2bAB%|~Bq09YdHgD)-|pSoqum}2(=#=;fbHlvDwc!t z_-{R`JqZo%EghSq2V&WswVXQ#Dm~H`>$oIk2Ub57#hbu}+US*tqBDC=nX_M#OZNn^ zQ787Ss@IVadRp!9^L&peCK}knIFP{eAp4^~3k)A8dU2Mfx(V`N`24Zk_R8v|qETkwk2) zH03RxjH5fX`1ohgAEvl5J$$@AT6}B-A8ktaMMFF~FLB+xR(e345h?_~uXSGj`3X3@ zy}0EwRBvVbEBkRb0j@~%f_-`LZw0n(70s zKda{ATEY7mx;d!GHR*jkMCP#Ahf_X;Tx%7NBpJlns!YylIsXWAxn?s?G3?mlcIQcC z4;G>7fI9s5Sql|B3bO2wE;_o*d-UB5q|mNT-tJMmlj_kpT>_oga}TWt_8+W#e4A3W z{Pu9Q#FNRSNpI7cd(FG(n*`NnXNYpE<97-gzG=(6M=3W=v23|$NyHQXJs*{w3jP}L#zOe54RLRZBaMABARZ!L_4G&pn zv_|OTkH1{YwM_2PtdoWEwOH$!P^q+xpT6HFP4-%4)yR53pu6-h)skHd27KCIUkyWk zA>4I!(kh~<^&8Y2wQ_|F6eAp|lV|o=Qhx33S2N-O5Zwt2E|;sacSj|!c{9bd9|lEC z;2Nh_8_*KqEl*Bjqp1O|VH3D{p)(m>i4&n~xCwZ3+DtADQG^J`&>kKg)oe|0RsOY% zvn@)ch!4{${V~I-aVU+QNTJb~xA;+FZLOT`LYEq4yer z_AJ(2iW*+;7B*m@3CQ)7OIZ0PrzxGdeQ|PK)rWn5U`Mq!J{+DL-?kfUR<%aE)-*0* zWjgj2xmrsI0Rnqc)8^JFGn?D^#4u`N7ni^^G3=?SbI*hyV0_M$1Yc8Jip~?xSX+x+ zL+@1_DC@k56T;_cl-Qs&$}yyUaz@}=~mB!;TNB)(rgHZ z`+h5lVPCZq2j(HP%TnQzwgkO@=^xPv(UF>-WBh20n0#5ard(Gqepwg)L#p=&!rq&3i4-gv;t?IVc8WDIYbf*u&*G$-JcJ9ahJ7cAmX&2!5y8R zXuJ(2ekfJm7@fXOh|^ZMS>L&K*?E(0p}x_rmGIU^Tk9fuNQz z|0^&u`^STzq-#)ox7DPT0Y-lcvNXSJ3n0{*oT?$d*Of41P}#z3=uovZLTQ1{X3NEu zy=NLl>!c>ztqZ!c*Z7^-uPz`AOD;?PAipWNFHiFGMfgVtFfzrea_mt3ts9Is9XXeP zwW7x1)!;<<)mcVFvaeyLycJD9k_B7+mbb!$g`u($9+I81;^f2d`ZSO{YrW6Z-AW=} zt@M4wr*IA7Nm=qZ(cf^z;CH^S)h!m~D9(PDuuU%mfC$zAAy zMG$6BN#c02J9N%kpxpCKI6e&v;NGt@w)VV@D|>;b3uE!c?p3KeOPH)!=|d@V7&_ki z7k=VsGb2=&NoUM??Yccz@q6Jl;s*VZoiUAPd+5V2eqp*QlYW`Ae+`V=R(0oecIA5) zME1$)Ybl4I!0~T$CthWo(#=lFB%Xqo^>)c6hC~A7gL*t%%<&pjR^jPEAH-{4V__5J znS|g@f0{|b;TEa0o-b8qYk?=&yCj>g;Q6lm6uzEaR^qsv;doW|b4k|lz>7Oy^=$g? zJ#?znH%d^>A~3BrnRX+#EBwaoU0uv8_|!mpc%PE-jp=EtBvI#c3qh$J4N~iF(O>M) zoo&swg3R?$1^#h)KbpjrzwAAKUnwqYH=}0PZaZ z(C6m;wJ}O{nH~CUzjC_^9GNazQXz$5=iwNbLa;f~ZCAZa+6LhK4Mvx3%zRKIZtq>> zkU8T{v_4u$j+a_G$20iN+qoB%6CtmPpxm5g2Fx2un?LELkREV$qIIRlF z|6C4R5%Y|KN`L=}I{$V_SUk|<8CuVW8l^U`rsRO_7{w%Plt`#*?ziQkS^MXlOy=2} zud)k0F#L3+#YCQ;SdYB{lL!ZXRu-=j+NEe)uVqj4>Yn7XhUm3e!?@*$0fsx{i3^&(F+VLZ3`!D-VmPt4HxK80)tuL{lAv@OMXdM~gp zJ^|`c{!JjfdcmzCbQs!dG#!2XrJd1lbI1yLq2qcEL`m;@r|q=vTRRAMsV<))LQV`1 z?(29Y5o_CGf+0=O#K)v5j;}}W^rYb#j_@--MgjO(Uz!!H90a$VL_rA<_K?vZNsh-v zRKE{rI~}>)P1WABZMYwF6fVTndN}Eb{a91=+};y}if&O!&H4cG2-^8zohJ=+kyc3r zbH`|qi|d0G`o1Dw$fNH|ld1u7)yZ2+G9_P^=Ou$z1Mb`(6Mn&dEy&k8ldFFN+sL@m zk(4`_B>`IE55Ik#K9~&%U&!)yO*vQN8W9h|@*GVT9Q7kHK>D`QOFSj4X1Ek>5j*vD zeD?I^;QFgE=(~~ihor_d(!hSfphcbPAd~m>bPEx^%aj;ApE5`*5q^jiT<%?Kud+DC zW#baNcx7y#fRc@O5pE*w6;DbTOd^oxWNNwfMpfiIIH#(%`HzYdgt=kE48+rB1>=7W zOr~!tSd6n$Y5c=%2wCqDmeqniRs3OG#dS3#l122#J-(JubpG)JNxe;mcAWs+%U*;G zpfpfdg1lyBRCk#mw0T-4-nv=*MkqfH;=TJGHEk z{l~bz*d%sA%=OxTE(4?4@1lFX%C1@v3&c@@>5`84FG2}_QRUOC7PC~#N}&a+1z@+P z35TWX(-EfpthNja*ej*P?{q#aDYx{~EU1K3xXOyZRJJ|nmv7EqZjw<8*syPR#;HO( z;do{?1Qvb!ZyDZtEYheZl)T|Yn%sbc3-4(6zU!bohljuaY_`2mWEtP6n->Tk{wTw^ z8XEgThgFz#YT*o{vRIRcWut^qEQdnhmqyYd)uf7mv7P#Zy%%|bjK~`rSOR4g86G|n zhv9@( zBh~hYvV)|$CTNb{zx&;_^^5|yL^qN*>a4l1n#b*XOeCX*w7f4pS&bF#C!VB{aw8fi zB2ufH(?tF-((%a%Mj{qnH&uYiDx86|SGR1;qUMjK9YJ(P(ka1ugS$K3#7~3~0bKy8 z#1W??s?*ZZTn|eJv~1;>h~`#!ln9WTWv6!o+PsEio>HWfS<0u zy-R%lU3W+&Bt5*r;rTEwmyiR)`cW-jz5_7KM>IH_n!I|Y7HZpD@mj~xx|MOKy)n z=Rwft4;M`A27Wf|9TXg=pGjK;oV)4fgn^!vn~AW0IB_F>+B&WO%^QG=)%Dd<2Q zDnOP>GMPqhCmDg6OW3)2NhvP`w@#o{7o&_uDi@@_U4@?1>?c+P2UWk4-+6ZySWG2t zhy0qm!zvZ}v2uY_#{0Y^?UOw6mKaO(l?oz%O-On;?aLi_9**BRoG|m&K6)M;Idiuf z70;%9rMc21)|NN=2z~S2>d%&M4tRrDUYVh_{KJVy3i}=bBbsL~x96tZ zYLk0R0)`n2e$iIEfwl7bH;3MhCc$7f2%V)X2m+b%@M2TF4IAk+q-4H@OO(FCr_AZ6u^IYJ?sy$3j>$;ejrzwOQZ=R(08Fvn8! z^CqxMY+n{Ij&W=I>Qn2yh;#=JMLXA)^xZlTLu8zXYltNTYXKC_JF3HEnu2LE+earf zPfSDB!qX}~fFk_??}1LWbcMiX?B{dWN-hjIZNcJ2{fU8!hCPl!v~zFUmr|C5U+Hdn zi*MDY9JlW8Rc6f|J5X;{)wD8E{mWXx&~^#DCf+!?OEN&6Og*gxLeHDq(Iwi-P?rse zS%DOK4c+p~>ro4m`5L47|47mQcfNzu30Gzk?X*YbN*h8D4XGo zNup3&DoBso%cEyWr#gqTvRVf`3!FO6i+vcq+5vrA;Mg|8%KJEal>;GxoT15{ zjf$(V)Z=CFEM!{WvqJLhdc`cIoYm?W(I;$-`=#5*n6cdM`IUe5?v+sFWuk3V(-Us7 zHXf+^YPJ16iDW8#*J}KD?i9CKOC0!D8@!eW9*?VmeFY!-Nw$7ZxKqtu*CWZ9kKMOS zIEQg9BO#V8gHj0iUR@h+r1$fbqe+4ctGCYZsy%7WnN2Wd#+YtTeg@J=1GTcWh16N&nq%0&gA%?=xI-ClV&p06KPm z_w|vbdrPR#XECE;41YmMZXrdB6IWqm#=)rKAE4kCWU26nBUd1hVzAo_Lshg`-q5RRo z#>CAeY;8A1V3&Y&i&DxF#o1Smp%78_A{^$_Qj)6^{zWRc@}7l!C@|ME8j(wuIaKjN z_aBb=G?J&1c#Zj&)(P%+EWJ|RG8z{bJ4Xg|eW?x1=OksW0?$f1_0P{pf$J$opbNW9 zD~k8+K>=FvfiD~AtIagY?4pNEoEmERgO}j)(TL%BQS|Z0gs~UvVk@_~j}NDYv<0{4 z0rp#SBZF0!0Q;bv+&Y@_5Oq_@_zoenEC0+Y`Eyur7`dbUf&Q{3j60Kr#-O8nR6A-y zKiowQqERn(BxE~jRz^eG2v>g|JbYv>MiSRj=B)3F+utS-D#xxdG0067^4j{Tg?_Tw z*ep!C$ask!QJmO0=I>8`H;~=K?kh|3)+}c)kp#|F3<|3h5M%(kDMk@R`k5*y#SkHL zvm?Yz2nTne}pKV29dxw>)S{?l>=7r-{D@f@SN8KQuw8kM&_>-#N7;umclI* z;!@S>d+zY`VO%dfmDPiv3H+!Fe?@k}L^k(pQ={{3Y;Kb1e6@pna>67$RwKu4{ZMbv zC?ch!GY|WNs7t0J(7(cvOf8I!uDK4MAPyc6E-*gk!VJCf|8h87P4u<^kB5wvH+^G>YQ~A)m zXr;s7T7MPyE1!?ssUF9bzmTdx?35G;P7FhAWaA0-fv$Wa?jzahmj}F$&m!x2(iSZCG58{QQ9ZpN4N%wPfzj-5YQXgt{ios3 zSA(nYtHLke+)@JtG&gB8&iAT>4?-O3SC_5M>pWhxgQLrVPB@XvD(zQvC@cq$+npI- z0)s`y&f&^BDa|g}R>opuvh+)L&pS{<%OkOq-2L^Vn(s;XBNhRSab?@{7Lg`s!u#Y; zJ51a%x%W6;i6OW0n(-Z4RK7MOV)`72{TIgs`@~F2%y<_2uC9pxryrI`jB)#u6c2g` z1?8`KgKC$=QJc|?uBW>~88aDOhPyEgx|U%@J8znwToXo*+EZH-nbc6rduy3}|74FB z*UVWvyJm)4hA?U?M49@?0(jaYcK)THex@jUL(ri^+Oif`gwH*y4i!C<&AEI%=Qcf; z;XyUn%w)u~#aHBmx4dke)ouggaaoa(>b=&pSb#47?@It9?aHAD&!=`s#bVKs&uVsB z32*KhTCNY+QGX|zTrmO3i>>xJQu0^%Eksh! z!HjtizfeyQpYEFGz&7ax*b|-ouT&^Ov*?t5&wqsE&?% z`J$UkuJQoKTZR(=DT=`A@pEB4-Yf^J?pcvD1dZ&9Do;;56~Ju$z`c&v^R| zUGMm9lF{b2m=|4d3TkZtY9WBE^w{6Oxk=^bKjD^ejgLltjUSMjj~TG!W!Oc^c^L_+ z)Se&pSkjrhwQl9AfMHwrA3_pSG|H}RmCPbK1a zidr`095gc#S1^On;bqr;pZtS#2GQpbzWi22qFzH!0_i;chR4X;V4x8=>{_*D|Ga2V z=UTyX;oB7rU}a8RL_NGuJ(Tk6^WnWEor?p>pPxN#_IVa0Z(~vdLrpqI31$T_Jk~}SJ6V^dJY~Vg z^^tJ;g)wq!OyGP}Qqrw1eM6Xd)!`b?vNU%J??a1W>zxYB zpq|`pXG!fw#x#bM?ZMD91B4Q#N9?;9^o}5V38oW2n|tWh?Ve;BkuQ(B(ppe97AA-3yTk9FYoBG{1gnY29LZiFU$zs>kz|bEGAvX$uOu30LY~9=1O4l$X zkR~bW=`K`Au2G`*q0B6|tt1$?57oK!A&zcVR@*0oQ7D4QEO=+^rq76t4!ehi!uXp_ zk6Ir^f}^Xa%lTe#wOO#C>!dlif(1X#oMt6cuHENg7fTC4F)d>@SO|L_mLvb4IEl#> zRX)01`V=qfi11oN_bA1_UhHZq+<&P6oa3(*_ts?-02cry|PZC6@HGyQ6P^CU;vT&wYUdUgv9D_)sL0oBx#|Cuzi4rq zOJ&BU3WO8C{kUel!6{VTE3<^nC$2=G6syq?A|{qR!l8QXu3+~)XGhcM6QP`pzFUwz zPu1~D(v=2X0{by?ECE{knC}9N-3pDHZ(RT~rcSy%k%bB2`Q!u)RZT0eo9X(a@M?kN zso(0z$t&Z&`VkGr#CY~lQgb33wB~k`^<^CSDa!i8XN(dzPGadvTqg6h!>F^>^C7`I-PVx#i-erJJb{TZL(07(K%xz2E6f&tOPtc$LXsEh5Q&=rUem%Ptd-E^6of;Oq zO14=j%CmVfv{vGky1pb5N;gK5oX~e+wz?Tp>*9+)oAj zG*EZH#6hYwZFQ}lMGI|ijSJm@6h}%!QjdF+9?<#rF=+Y&_bRVRn_>3*@Bd7U6$J)G-ou-OYX`g0qq(7FC&4Y6#mGX#Mf^YhB zHieb?hAguBQE|_kLS=kdPD6q`4?Fb_pQynSaNMY+iw&x#8O4GS7 zrteBMYM{R8G6e%?G_j!17@kb$JZrg{?bA2-fMJ8!TVVdWx&JB0MM8Oy$n!doo2L37 zB%xJ_q}uf4g8j)ltA|8_TOg-@~F6Xi7gR>PDtcOIpis-I? zAO(z~YW$BLzaQQn?s^Io`+lY8kR@o7(4Ki!u^)r&wkHX{cmq`>ArR%~3{N(H3Gay5 z(=Fo~<@#}5qAaZ9m!2J13MZ&8c(V*>;&A@<4G+L(*lA;EQrP#oFnQvi5~lh-1*En3 z4JF&l^R6aZc_}t?2FqbQa;E5m+0nm-TD2C)4@DVcx0l#n)vgY%pH<@wA~jm5Ff)W! zBaOPnW_>VI#E*FJc#OH~!$xIPTpoqrCH^B2=dv-rsN3By2lb-meVRrwvrjuHZ(mgY ztbhCF+k5+}!11(0`=P*UvXp^^>GL%9Ar?MAh>Q4*y{nLj7ZYBBSyUmTM*r*ghE+I$ z*cIOSg^ENoG9_rU)Ty5eIYSN!5kM z45GM90E=d7vN};(+_hP8=Et_mBaO!88{_s(>;;*Mj zFRxZHHu^c;3#g9dp|vHU3n>SM9iBjP>wP>iOC-n3@0zyus+C1N5K33iz)+#KPEO2P z7<3>??U`oJZjJix;clYAJF-ZlC@%ANwq2y8c|mw&uB>zl>AoD)>HMj`vp7ocdKy63 zYq_>!{Ai^+kwUf``Lgn0ABW=G$D>XqD$%7;Mw2gYvrR<-?ZszmVKy{Xg`MP3p`2n3 z3Ulyi_6{i7K_ zX~o1hpVc+7nFQMtc71up6u(tg&fTVe{IdaYc>S*Q##DiL7F|o3K-|w6@6FsiDNmUV z)hi|~Yu4}oo|kQs_gpaAmp^zX`X3h%%ufHz7QakTNzbEZBZ*fVzy_gGI67Y9RC^{!mTyqK5 zI(yjZLGOr>VB4U?evnowrb*RX=9k<2apXChEBcpzq-jG+(CodUvob$%e?((}G3w zUohkHgz)MkRZ1i4+U@RK_6DZ$KmNP_#Q74hW7Nw>=UR{{ctsQlH&OG_qfw|H=`@X3Pj0OT2 z%E*Raw9L*W3)qh+WCNECV&s-3LMqN`cGHGYQ7@tn%W1d+5$GEOo?`8=?|x(=UD;h4 zXAjAeiieYFhZYa<09ZGeo+Q*uY($Egqqv_cFH@)QXIdBKoZWKfB{cBJ9J9cDej9KRp& zXth&xN@?nRN>p(4_-6^-{F05A+8^t>30!w6IYDlSHL=~Fa=6k%)_QCfHJ#Ln9QfhW~|J`!aRRdi6_fYG{+ZOtilQvB=yllwjcT)#&SXAru zXa<`vIqiLi`rKVxn^u2t7OX_VB+hWwS?N}V1G9Q+^u&DLWbb(hwgso$TZ3be3Qw^` z-6hn-HISos`9;UUZw}wzz;}zPO%Feb`8U72>V17U()1$(qTUukO)O=KAsyVBV^cG0 zR?wP5IbR4W)Y(gL8TVt+1Tbe&_ zin4-9d)@LgwJMvWD8~p{croeqov_~Wdv3;O(|SM0sak}#1j1pr;r zH+JHITB(%qDQ3E9D|Vwc>jlg6_B>aZdPZAa!By3lR^ZK z3Yd8&8P`hjAjwh?w7iVlzYq(OReZHbLOt1i_K~IUNG8lkgZ=Vc;!RPprk|$@U-*E^tVS^}v z;RX{j$^gsUdVd{M3m_u0Do9XI=LG)TQ4!OMDU-npX7L^@i^{<|(iE3a-s>D+2mTuq z)HOZF&knkvKfTUYM^Q zP-Zl(YeRK-TD6=On!@mYe-7##U|llWH9%Z1Hs86l+Zl*RcwgY4sva61OC{V+c0sS^ z2SE2tRc6GJFO_F_c^A27c<%FC&L{k$1_u3fwXcJO>BaewC8-8Wi_okoD&_*nQTj$6 zSOmN)?yBB*)dJw?y@hyM2d$ATb=c+3UEzO?d1CI+0B)8p3<`(l!iZfjoI1<7I;a$Yxa?S{%z)oq$ijT z@5pk9*4|Ut&C<+V z;5g7@PaK1Zjw}TZhzYoe(8(Kfgnw!%)r)OQep8H_C{J8MRty*TT!J^y-zoesRlSoa z{4pf*(ruDlu4kN2DjM(o^UuSM80wKsLgQo1apf0s^;V`1zmsw{Swgo1Rmy|3*eHt$ zYa5OUHi&h}QyP>$rc&sy1GyLm^krXSfVps>wWS9$b0p=1{9vy~8UHpxCZMHtalcv8 zRtGNdGgI4E-V3x~pua?NwIF`XEgba<|ExQ9em6qxGPFx`oxamuOnrUUE-|frY-WY7 zti*mw^0{z7%Q+nI8LR4z!j$>xI$-Hi4MBfTvuo5rccUbfp#qX_^|KcWF z4e>*Q6nk&rl)ry>m3YwY6h4!Yko9n|41VeY)U2m}$19cGMQH9_5u9i2u~y?Nf6I;4 zwIW!6Gb5@8Y=G38l4R~CGVJu^^Z^_2UayxQ3;KUF$Vz_&LAT-6d_e`Aho3>KA%A1D zu~#kry+zNLig{sh&P0!v6f9#0rpaM;5QrVD+J24~sc6kNiLS^t#jp7Al2FOQZT4$<{c#EG#RXYloXUDD(eop?|KFxO zzg62JT~53JG7pp%2n{~@`{nqgJ>pG<#E~h@btN`OzqNl1&73%p0l!++lF`qR4pp3L z@^O64U>6nZ?I4Dq4m6yJB24n(?*2LPskTwH3m3oBXCc40>-Ex}bV@6X@i^5eKh<#P zLBEUM`Tgs1H9l)^qm{Wec|A%Qt7OaPYFQ)~bHpL?i?DAuq+v(alKEkxQa>M7;Mj;*)b^}*gFKl|JCS%{gEk|)Vr#pu&$5z7L zSx(3DD1Hb-^rfBsfi~5O)s3gGE@jCdgO)Ob1t6UbrJfnD-jjO~M&Yrc8!)Rfi%_Dg z?)|a*`mTM1##8ap*2@Ml^g687pb<|z6L(fyNfiPWlQ2k&ljGA8w2L}gRkj%Mlw3d(Vocb zyasu8pw=ICsrh2t4}M1Y3mX={&?qxw45AgjanvZNZYlq=OiTCr({eG~Zen~rx7_*a zPnG%K1><_W53Sf57(SDI{b(B_`vmS2yO77L_U`1K$LD3MO`;HS8VMq|eTt5m=T2Y1 zXYZ)o_TRDtd>SO&cA|lM8zQb4JX>}u=4i`;pxQMqDx8B4h85?GKXIyRtPg3Q0?KNZ z!){lWDu8q%YfHW6t#qeP{`kZDL)1=^$*Oqo=e&^cEbK3>`3UihcT=fKL z4=>D$>U2y*0}dS^vt1GoBg+BTLQfE5Z; zUbZqt5S+cg4cEF=C9Zg|O~<{pFH^&?1LG&l)uz+wTC&UQ*KW^@d6&`(u z5`V>Tkp0kPj#AojNmc@$5ZOk8MEXv6$-7j=pP9e(idGB%VqRG9_~(^1$c<}QK*9Qq z)F4;aB@JD0D3(@}o5ynJOf|=$gBW-t7M&rcx3Ugm_>TU5mq#`)r?Nk<& z1=R!oT|Gt7w+A57YMxgJFhXJnA)US8$lufGxz_PO@__Wki6JmVgP zrqy+aD#pLqeO0J^)u(kE{><4VM4<5E^GP~~c~HN3YEBz9jhgieb@F@N7f~7-?|go0 zM;A@|aH1b0i=TCDNYGO5{0K0XE#3IHA*jzDzNVFx*-e%40Rvbl$ru{a^YR?OtCA(n zVB!Pr$_Py2)fNJU_Dxe}RG+BBicrGso&9!3&v4&{viA1yS76WnBPXrUl7zIkO^KnI zcE3+Hjq;WRb8qnLbAXkgr|I(sMXr^^Tf0PmBmTTRJPdmnmK<;h4lk* zxTa^N2mxPLW)EJx#upN7;F~1*r=`}dNdlJ(;L>7`6%N)bp5r4`qR9*_ZNl!_X`zMe z#8tel3U(lz$&%0+_P~55rp4_975t936;JafPerey&8qprr&=Zi%T0=+=C?~3=OAnO zQC{xl@Xbr&cp~Mu6CuHNMdtqw&!>te~(otLj@n?K}TD zQlJARvb>i)nnqu6xQ3pQRtSX)|ZYrXjrD124jR!uh8UMxHF7->ykrurS zeSh5<7odxkeib7X^-B@FSCT!TILZR7?z=D1E(EbXiisTFXHD2Y% zpyHsWlLnchfhmxm=bTgWx;u1sFnJ!iX7eJ_`|1<9)Q=J3tCfkN+N4MUnbK@>{zqZU#WgfTG+0C1OcyZ%hu^PhfHMGEB zU-BfIDGhnv`+9EKJmLbXz01Dt&)LB2s#qc6;s)jBjNfRRt@n{`&!pw~X8OcBCQu!# zTQ&FGn~z7IBzeZ?>ketJ`V#K=tgi&dQyGOZgzg%z-z-S{X#tCDqE$T==RGzVwqF>- zJWs!PR$uW$mD_&0y3@JUFL+VU&#rF!a<`@b%IoiTM~%4(%o@4^nIL2~eHrVI`Kk!c zCOrm`^3(1fG_bt)4&P=EWRFvx_@I;i+UbmBQ|fO{N+s`#_K<#sS_wc}w*@-eMjktFlsEc2c38cAF8;?p3#&e}umdN>$dWHcTk zr}r8UmN*Frlt5Q~#Ne3qXiUHBlZ(Q7kOsliuESrZzq^5-?_I*OHDS)Ckl`D}JK6*U zohzRB8>;P7t<1y$r@Gpqx}+1`=Ig%+4@?rea(Ri}SGgXW8>=S^b@C3oAEbVT@s_($ zZy}nQOnBNsTkEwhOdd-s38Ie_Co}PW|86CWb;WQTGJxFz5aYvfW|h<>?F$Rw@y4!h zTWuS!^Yo!y3*C0(T4SH@^q)BeMwDyau}YWKHH}}4p4W{w4DxzLZ{)7rFI~26(i&*F zH2lvhqXt&q#A+cqU(B^2r*jg8wDdG#T+i%b-b*$9)5cv2K`O8t827m(Ld*nxag^1ax0ic=maFRrth}+po)bK;NhU9#C+;Iv{ z^%W!#0I&!c?ex}E$r3d*mT=2&c}g0TxB;=Vz0|IDp*KJpw`x7H=mx`{kkSot&UpG1_C9y`%(`s6Y7KWR`Z_zKl*56>pA%yfcl6^-K0w-tQzxf7d`r90y zlVB(%K~*~N)6W{BnBAwO%NiVBQ*@ckA`eKh8TD;%xf*nyTdZ?SEzihC+C-L%wpDT~ z{J$)KI3+s4?(n~&8AfZE#$4sKpZ#=KzTZx>qfUC6EaDdWn#gZ`91>0yvhOB?@g}GnvCRvbsSQL!nQ}3=0?whn6#|3@l$^G)<7e{KAw0KE;y58)17Pi#Y znJv$_K^Ifn!h<{I4?o@)F^Q98;nBS`uaEOjgBd>xzUR)KerX)x%mqc z5-6>7_xc&@q*qq|hYmQg zrTLDDr|LS`43%q4DaL&4{}$P-Q5UCaT0&9I!CJK*n>UT2k5)S5hT>>=L_9f^BN9#=?^am{G$|JT z2g^V-zpn7Fa2O*Gk2IuB$%gw;ZKQ<4imE$Hpw&bR&tVPM%;0s5MkM{U#sWn&%(iG9 zZ6uAuAln6}_hVEfi+*91CLVE$o5jl2Dpe#y!kU`WdT5+EtDh|g(dk3Ks13+bwR#8{ zFN)3*E^?*XQdlcB78WYp+V0WuE_NQgRJ}?3WK8lq1`t**2VA{@gOqyK?Dv>)G%V$< zX3BL>85kp@M0yUI&xqSq zN$&#Bo#G2ls9H3XxnA#Mv>ekjBztca9f9enZGy~j2;SJ$feC9aJsdUFw0iH(u~f5` zS{KzgykP2@uxJ5<+$g8-Yrf^K^a0sFe z32gaP?!wNh?mkF#2}4|H7i>^E*jpQhl@oPwWnz{kSHTFy>R0#dK%QGZs`3)Yw80Gq44F67!wcdj zhX3{VUIkD*$aiQUS6cFzN#aV83)HtmHMB41mKotf9L(G3sd=+=d$7<7PnhE(HhZu_ z%PQmJ-`6+i&)d`TZr-?5=C6K$r?9;}^15l>zswTbFoo@UXLjz6n{n~QZcS*4Y(hd? z>!a^z64=UPf0D2?HNHLvN-^B5{fP@nhT=_;3mR_t+NZedGK8WjX!) zuj59?SI|%L(Z;w@*?SzHSaYj<{Qmz}-u%fwM*sbZErgqz=6o5xt+V)2LQ)KmRu`}c z^8x1D9+%m@N3lTfIeVJk-u=(wiFXIeGtYh(H&;G~IYHj!${jTDq(bFeAw`=lJK*NH z=9AwZuCCa&y9;+Oln@FwVJBf0F~STn!S7HyMnXJ9)6+Zou-z+Z$6 z@K+Om*Ya}-zO}XA(~t3;$>9Tg%kyWBme*dyZd_QPw~TGt`JL_cn|I3xpIj^FF5E0v zuHD5A0eJc`wzHk$BlEV8FFgaBTA=r7qx27T7$_qj=JJOd^7q(DdSb=bW2oaHF0c7n zHtXqSCv0EOe4kfYKFBA(Eb~i9*15u!Psf|a9ahH=@4?Srdx0InQ;TMIPurKmKY#Z^ z`Ptj&v6Ue2%)pn@cq+~GPHdG6+l{urrLX>AOp0Yve6ktC(l*-(n>kgcZW4#=bnS7O zLzsNcF>Hj*cI%aM4C~>xs@rU=!u9Q$6jv|r>tk_F!i>o&{qhvzvmKOn5>shJiHPoxoonYW$7i) zxLzmBdRN)^wvyA9I@L2yGGTb7EMq9+A*^(p#kqB}SDjaJNhTkv=PR+&_jVaKDXxqe zD`&gS_#sEOQJvQuA2FQvzy)^(KEz}W78==;tGF=lVEOg$y;hz*aj48<@!j=XxFZmE z1@dXG=P%zWx3DO08IwO;sPq`Wzcld}ZxM85hx{pS$-meuTgCUP=obWKaZ!wet=8NK zCrWg9HfYuQGpPj|?dv=S3>d!nZJRHi!#2UlE&gM1)O0y`@JKm+@;g{O^;hx4ycf&< zgJ-cg?^#TE;!YPXpu@H(%AIu*NiEbCujiUiI>h!0H$TS2?%TNQ`jaw${q1u7%8#*C z%1yhA_VMHU_>94R0)^eh$q(c)X=t>KlWushzaDbC(}Fm#9mcQy#(!PD{;j`T=Jp)6 z*f?g?r;3wz1wX`su3!A!9w+X&_@j~S@8(2V0nd0M>i^^8<8d&BX%XXK$I=yrF zC0BRhiFq@-_uwwvZa=h+wy8o+>`ZJq|LI6_YyAD<7-7*-*Um8Z+nX|qZcv)w%1%l5NyHE z9+t!bwLg=+b*ia}^&qSi@rc5;I5SCxL)nFXbr*g%Tb_uKjaS_TbQVJx87D;?`#OSL z0#~3gvk6`%PZ{wr|HWsP5O8$I&c#O?5N(_ZbF(M0Q2uY14q|wTAlh9BxQmgSMg!N~P z7hV*XY?_7hn0>fWjx2_dQ7<`t`$2CGV7nB9i4=&S^$JDqJd_*MORkob_~uP$Uqq5H zgs94L^sg0pvO}@9nx#D25TJUArdvHzS3q@0Bq9q?ekBR(s)P|a6&?f`1h3Z&t3u&= zxQ79l6tZrX!xHoz#QHgbT-`+`dg2JMhKndGgUD2&SCUK>vh*6I*ctTuHaH}gJxxbk z3Ed*Dqf)Bc=GIn~Kx!dHT(GFt*c2w9h|geX?o*nRU?QYjhsY3LO+05AW0a?x)#P>ze#VbIElE{Q4ldW zy?uW6vFSiTSj(?Z@3WeTuy-4t$J*AkBc&9BxQX)j--s)jDK0KJtYvFEjqoF3*swav z0)?RV-XAp|zpn9kf@pA&C53C*9hxw;50WhSOp?SE8qCtGiGtSGSp;e007^?igk5LlWzjRK1JA$%HJ zzV5_gyqQ`4&5iABckjki%D6C(PbUcuwg%-#_h$C!u@8CsCuA(@?^;^*!yWPcBevv4JGl@L9qS~Y)7jcuJ0z1TA(F{&Zqt}d_%H5iV;l5+ zejjr86U_fj;i*IX&h{x>kM5r>#}3ZLceZ=W!F_YKGsL+|H_8uxajsmuexoeh!_Fmm z{p`eI-JMg&2y(2JeQ~YWX5P-gb_TXHu$_Uw2pO>7iuk?0iq8uxSbVqqfDedRDSPI2 z+IO}uKle;Ia%fMPzqw#P+q*;#o$cg z_JSwyDCJ{E50tyu_V&s=w!6inlW*R+U+ygO%VBH{h9?^>;ql5`D6y681#`RCi;cPH zv1)_ta=xgOT)3Vi^eFq7iSa@kMfm#=Ii z8LwmZlVtKCJ?$l1k1Jz{+vv!#_GQ^l;v9=jGTErU5tsD3=2#@-Rea*ly0NnG(rsiX zzuAs9xlS(eNUpm4=qkPH8Q1HCnRLyz#K?Y3E}1aIOEzYkaT!aw?AF83CLglNvfiG$ zF&yQ>Qm-=Q`H<}-ea80X%$v(SF4ucp@85pcU?B__b??Lk!>;M6GP7%{%;9Gr9^cQc zY|orJY71rfguDw^`1@wD++BQ7=5JvuTl}t>!UO}Kn)hiIhUZv6|FUM_d^}jr(;N^d zUo|O`r$;m?J9#3>Bkgl34%uU*VB*Z5Jcc?MrHH8V=+2dG`Nnyiw-nj@uLSY;w##Ei1a1pi*<-p1wJ;%WY5Y-MfOxw)5pW?zp~j{ljwm=0|u+ z-epV_Vtd<#3uP7C4cjCVwy}817X0}W?5q`uQ}O41U|Tp*xs1hsi}&u8AN=)yP`>%y zzlV5V!6L$0&!CNWT6w(p7Oq~cOKG-el_eSB9blmQii}W7Gll1sBzbEbBYk)sT zjz3?Hop`Anwek02If|$1O;68aQE1-gL=9(NFzYCVl}mHR z6M*<1r7%|{tOk*3oXxcv17#2x8a@W@4C>ibKCC7R#Y7V1j~vvC%UpM-mM@WXfLhJ* zhY>XCjSX=?5`araDXo^DFgas~M^RiZ;`k-*E0$fQJj$ zwZ3bfhaOpzSC(u0L`2~h4$o}_6?S^RAERNrEH(v*x~vBw7*L=bF7L7=~Z~Eq{*|!ked#!NI zXdrQMB*E-cLMjpk^I|8LTDxc+@fne`MX|-zbgksS#{!0MqP^p5zqb)1qh6CJ8Lwig*-uK>_5D-l<Fv=#+xl%Zj#Ey*xRTg!YRTIix8j29NG?S_sS>b?V91jyg4kQ;&!&&`W3N6 z42+la1S`w;v6U^Ju66Yz>~e4cH&=d$Ih=D5)RTANt^-_%WZT+eaTvF?wVQI)A?h%u zMCIHJPZe}1Ou{6*-mjE(A7ever>w-uI*No3)KTGONVUfH(W3`8hsW(-UwZAg%2%;K z@92rImf5)@=*rlhILH~>%F-g9fcW$B=AZp``O&}lAIkLHUR#Le@psNrTpGI?we|!* z%;oeQ`t#L7J%^PIl}f9773 ztW65kr)%Mgd(c^csV<9@IElXCtH1B`aWtp)ltL`lTf)}2dv@MCw*zk2;c9fJ%*bncHp}~>~eN(0S{JL z#P9n1WgcsV7VfWsVttYUbC-U7EhL`ES+LfU032J>4ec$oW!dv9j}vk zGIk6j{Ul!b=xwtdF{%sSU$Z^2s*l-8*K=&8E?#ntmBp3ylx5$t6Lyk5+XzeBEFa?% zHpd{FjF(J!=_FTO`d-euFu5%JRDCd;gnPGfqrM?QJbKW*)f*oTYdA?|KjUEP66wNqt3wx~FG z_=R%j`5$1b*ze(P*cUN*JBtNR%pr`8qItFmo!0Vf4shaA?Ut7?!G^_f=Rf|F^5J{G zSKj``|5Fz3UPGUhf(Pw_7k9x<&7jWD0hnN$)UM#Cx?-X&Vd4w)78mgJyuVXk|Hgk* zUch#@GqZ=Bmy@kL>~-^~Tt5Hf@~{8!zc1(C|D$qi{=+hZNlHGU&A0-}7Ow3l&%2YQ zSS%^hIf2DlV}!1jW!|#k5AZRzN>xs?ESLN zgKEWMk(pch_Q=GwnSN7SEJZO{JM8L8*d#mZe(HOWa9@D=WMUHITm;tz_}u$ zUO@oP2Pt0kWb-}{RuQ!9zf2=!sj3l)r$zKysc>4k^XYs#VLLLY@*n@n)x^Qgg< zWDXz@2jy{5-&t?s{LV{(t5n9=0yd_nl*@pW@rmV)gDttK!XNR;r`Vg zuba4@j5X}o2J>fj4xw$$vbm|Xifa7}U8v1Y@yO0}sIcDoY9c2H8ViECQyx7ko*Tm5 zv^rI+)yP3^7zlG}D;I==P#Fx5pfoOEBz7gvNx+*5cu>@09kA|h`m*CGBq@!`MizK- zir-@&1ZODeGd9fQ5;h$`ZP)m)x;x2<9iLxQw@kk%ph^p5H{Um>K)EP?b{I0b2W;+jDmbm7OBylQ|mlVv8e2jJk z^tla#{`>#gd$T4xlI%>(3qWQf6NxRcF9j5;3SHGZ*&HrC2*_;ORs;3dH73)r#};XAtN_ zXJ&Q-Ad`bBV}GxkicnjT`cQUTSS@P7+fl34$5RYy;ATEa3PS5gsU0;R)fo%5GE%bh zLQ-sX#yYQ(-0=KtBF?y@U5rufP!5a(7N$W=HJ0Ebu3jJDQ`p@$gad_4+Ykkf9gHg} zatw;A8B!aB<`)P%VZjLgeys9U;|)O<$H1JIgF|fd4+bxqFBcE*&9SS@JUA39VyqH! z1&MjA$>ZP^YzfP%N96^l=AGE3;xQa~Ltx9OqMWZ$(}_%m6zXEAQIRcTq}r>#PnGH` z^lYK!)cm4<2QOolyI_Tdf|dUuZXY<=Yp1O9sPo6ul>a;?m~7Rj6}-m1?8HO7Hjg4= z0fSq0*lIpp7nVu}wPD`1k_W^USmlZukE>x+5>|B{X;50|-l&Qy<85IC|5&aI^ugxhm)-pVpopukeLKl|%{i&@P6+!ZhIp8aeuegvMu zi>7lJe5lnjlp+f~&W-g|b((adm`0|7`R%22Y|C{m3ZP!2y7zyV_OZgWBfa~kSX3W( zfEp51=am%N8{qE5sx0-EbsjIe=G0*?EflP-u-{e z^z2~_*aQw2k!B2Reo}{M!rH$nvee<4<#UeBjfb{M#_}?r|G|{Y;J*3I-!GTH{*SO4 z!tY^FCY@00T2-iPJk(z|%J z01RH-+_QI{htYFv<%rcornIp>g!n-#?PY=`4~w~qRus%4~ilabVU5^6LDEbN9HLf!z%3W?(l1f4eilPeOiHEw5qFK0YMo@H_0elZUXv+0pXy znPcVE3n$A028I9WUw=^k{73JVAO7TBOouybGwmMWGPeB9HhwGk+nvc5xAi+3px1AP zzSQ^^Z2x99>B~*u{jyn}6`_-@m^cJ-EM|#G4tfVGzZ_5&SWTK@+#J^dP_Z@$HPO9H@u->dKn^AvJA- z_1t6MhvFC~m;SRH)pl}BYJcBHj!7I_*=L)~C;NU3L--+C=2BUl+lfgwTVaJ8I_9`? zc`NLYzAxLVEz-*_w_{u^ljP*d9OBK#Nj@`fQdu0^@wF93e#W&+dL1)PaY&bsp8R$RpNef9Z=>a^3Qwq{%|6SskZEo*qw?}X$<7~DZoHbn*j=fwC9e%kSJanNPI&!`oS-4;W^bQ_AgJoh*;Gx<)`o&(@ zboo@ZJVMDC@HRL2Mk@w?;jPv?w|`!4T>Vk`_=7*e61LaMGTwxJ@?;UX4e;SDXFgQp z7jX>E!w%$ZP4`Kf9*lK8do5#$d&*hNFz9UVz?t&$rQa#P`w#y`Il6Gkmf@uZ5Z0f# zyzWQu{k!s$KmV7Q)$axdQmxsnZ0&7hV%56lD@!~|{^;P+e5Pt#OjzeBR%USC&OWP^ z2FYqY3URQ0-OE{rGb)PHs^Hwjptbc_?$(xmMywso5Xo04J9zjc-U`1|UU}`;u-x)x zoY%9M$&qjV??;MjGT4RelCkR?{mhnZ#zSci(nH1mc{|-E;A2`AyuH5A2WJlYKquNqbG+8jH5O_xD!@5)AVrxXMl`R{<4in;=qX1;+6{@ z8H2Zlmab{7gHm9l>FH@`q7I0`7`mT4p~P<Fto>+7y(v8!Vq6=By_%$U^iWWATi3N8TAe5BfUGthw;fuKrz`5zEZ&ghV9izX zXU@9Ob!q`^7G4rP&2{SNjaqltoVJq3)3{hqXcSqK-p`}k_`b0>X`pdXU+kmgKakCV z6pd_hDRN~Xv-N04#A30;$Sbe{Dq&I)?^Lp^M?st_YKf`>SqOe!*Rsp{h&Dv5rA@I> zG&XMNg4hqqORv}{+>h0hJDvBfL>)bnYBj+l4hvMo zMG1An$+ycw${(vKsGG|bUq>Yjl$Ddl1`?k@ek6}_X$b9tm)N0pMi z87%~TxnXibg3E0KEhCYn)>RP=YNZyOX6#-#IZ#WQ!3d%zOPau18)PS)^Y+e$KsYwP z-t27WWl3Zti*_%Qgor*G9uQZ>N}WwayrUyp6kJM2r!74$VLRd+gK;T1GU^Sb=*(eXncDxs2Bao54VXQid8U*CKJZ zy}kS~T@kqKgp_?GrR5oIgiigW(`UlAlY^iaEsxaGGY`tS#St+mN7+WX9%^#yS8gFr z)@bMc;@rfSDmBkAG%!KLp5U4u3%Z=6gTqLmbboz4I%0!E&5-mJV- zZBga9;CbN2o;B8IRse4NIa%a#63;+KCyxXK&I&1TDY{TfJ6r8DG!?c}&Xta-wN56c z@y=*Pk3?tN^U$y@GfmdG-L};&5j|kevpWnc!v)wx-E+{RsZOd-6*@o)(H6+?B+O>a z<5HtDm@RKV2IbAnVU+~DkjUBe+{}P>Vz_ke$_jp?xpNHzqCUVgoOdu|-Y4bp!#maY zi&)^&ylj&#Yl~U)Y&lO|+-#)s#NfTrtr=M-sbNCl>6GRHefFFWOO% z;(6l*%-nd$o-OW!e?GJIEjx0FokHxmG%sKHee(E@z4ZG41N83R`2f!u-@%F;AC^b= zKPg-p;~55<*|T2`*0X1eLA~RH8rqLZauN3Rl7G3!dSBdzTi%*^Kib4UJ~!pFu{qom z=JD^yfm!@!d%T=EdDxz_-^WU5T!L^3zu9tPmFtU`#c&zV)?hn}0Tp`javN@UA-fsa z&A@I3b~Erxm;wH_v5wztH~70EerkE;{6aZ>;xJ~*o5OFn2g^Jr?&4R4t2Z8$4?o3k zwhta+mCeUk%KizyQ|!fWfn0(e-&XNiudi*tglYPvXwv64K#$p&^u0`nC<8UF4iM> zqX%Ea0pHx%C?8(EQ?6WFDi0n$!P0bVm}!qc-20FA97U`4h?~d0@2vYg%Fn}}ZTh(w z7gM&$7}@vRK7=2VWv*P_E>`|&+m9jhO)6(!;v2`6v6DEnZjz1s5ubfZo@L@3$2ZAF zwmb?a*^m$ECLPDck!{9dhU7!C%*8S>+!V9wChYBm>4~kn9bW|Q1V{WhpX1`p_;DEW zY9qdn%QEo|VZQVa?5l@~qt41?cAcwKJ&ox~oJZv$sbMqpqb$0_lW=aRpBr;gx( z?b&kn{I~HS?;DsY?+q+@a2!iioW#Sl)AlAZ2h!Eq1bBktexZkSU zVKCl<2cMMN*WWJJul~4vdgU){CNmD)pPIpg(^%$0t;;Rd<8fwzTlZ15%e2o}$FUD> zVi4dHwB_67AOF+;v79;g-7-6O#4@z8jscuc?w0r7{=dryzxX%h%6tFDrr~~RZ+k#E zE}t%U&wj^L6zyzL;%6=pIbRj7R)@e1ywX$UYfh|cda~QDqYB_QY1NFGLd4+1urw6>Iflde`+WJM~5v7PN6h z{voszIAejG<${5v&6<>AO{odi#zZgHKH#k^9vG>;1K}CUBZR%3e6Qt8K^>^ZNFsyD zQqFj!%o-FJ%H)~-#5cE8jj?wW&LSC9o`x~~EKKd7kZ7wi6^W}>Oy*}>&@QaiR3c7g z(vI82H>6izqfV!Hh}ZlmF`2Li5QXlca^^s*354pC^xyh=c0f(jMWvfh6B&O7Ii>Naei=n4yvkjhYf=em!YR;x40gQtv=wfi|`R-$A@Aswdx$p3bJP9oosEGs+DyXM^rl4&xWO4mhX_Es z@e!09nvL%~`i@b#&c?TQ#wvMb7=AuqaYE4s}-4QQCG*S=8&5`c9Br` z?*Q*7ZfoC_DNc#6v6>t^<6^}(^-o13Iq#Knn~mx{l*+ylMCvDvM2@%Rjr&JZy*d!x6^Gja*Z;p86Cc7c;+{ z!Fkgdm^Xv=@p(XJW8bI=3r+MFyRP%OkfG_rXe7r1ZRZ${0-EnBU26rHf|aTK2gJnE zi5G2n_Us7;r@d4zy!MaFrPu#ax%9Q)$0`y>alYrQt8+Dr8=wAV`NMzxFK~X}#_V|q zA?M(@L}o=f6nPvIEF?HMHmm&V!Qe0SDK3$`&L8bxnkg4A{T80ze5)KedZ8?wc%vM` zac=IIt}m@ZAjTW?(l1yBT=FGr-Rc{{6s73HYfvgWvaN@ZEA2lW|>s<%A9LK5_`FZvn@Z zH18}uDnI?jjq=V1w=iqoGkjOuV-v8v;LY3(ua|*s4bU6+WrT8W^$+tNdDg2u3Ewl{ zQC0jo7Rqvch){OYjl&B!PA~hN9zPFW#`F~PT=UC0-;kNca(3VU-lg)!YnV~*@Vw37 z^BBv4-F@)5{N$ahsn=!9PX+GF4{Er8^t9YXogY55J z$6Krzly~!Ic;os9<;Io&faOsza~R&F*@p)_;>dYaSvh7aTDzi6acFY>MNoxZ5NgcF z|d5oKKR43x{No%xzxrUthF1Q zmanLo&j@@dMmJK+6bkx|$ZD&*r-c@VfluXt*4c;H8du9rqOEaf547pzE661CsyCQ< zS?BU7oK0^HOWtw>=L@^xF{>?Of;ay$$n)FZ`~C9Tn|~LB_0D1tCf>%Lp0ytuQc`M5 zd?{xP&_i1}tDXMw;*EQ@;*fY+vC7$&-;24s${$lW>5oBS+H`_ zoGTJbsa4!RL0BZpwzuEoGpzjP zn8Vn)qTMxHmDvi4yW6HpDyBHPT=}7p5_t%a3B*g8=b&mm7h&XF;ZOY+eOW&Gku(;S zfKbvp;1*Rap`iO$r@hs#aXHQ{MK6#2qxBDEp*kaq6`nD*$Lr00h(8fT(Jt*~)JlRf zeg&BmA7{tBTi;Vd!_Z_Xvno|%H_8rO=&CEajdr&-CIS`{*j*?WaW42Ij4;JwwY;;& zjSI2*_dFX@GKMc}K(sdwk(Pv0Q8=_F@}c%wg0j7tND%fK_+cT3&cm%E$6R*z0WY>hjad9nHE#BVl^ z2pYH#-fH4eP+$2`aVc#BkCNrnB-}ZHatukzGi$MdOT3MfIt2i|p!8MCM7O$&U1sIz z_r@9ba2q@sPQ>F_uUT%A2l6{(;&n&152}bCHA`Y}@jM0>+vnJ7;hM-1&$*cftMU;> z?ok1gLS+?rK0?Mr6jkSSMNCq~C0T+(-3d&z)&%kzwr6O)C}b^1 z5l3aPs8Ac?cjFpYN---r6O9R* zX^OVGY9`Hy=e2S}Y|*rx6K=WpHYBEIK~=c3bCZ&$d<(4W-s{b<;w*E5cfk&*V*)0I`C|`Vr!@+p_F=O5| zmU^9;*>AJW@8R-cI&tJhc@G9Itzi(=%JM_ZlDAYo{pd${iSa*RWuqtfyA{s_I0O9) zy0QQ8`QJ2_wDo_}wNDM7E@nIu8i$dl>7xnx;T35bk#%Lsf$j7>bddT{?!EO~ncgY`ZxOIW#M@#b6T$A5*vW_K_^5i>~c zJ%HzYd=b~TcC9nCAwNR$*wBtu4AgC9OU!5AnD^m)e0FXz*lEI!epc6UuJHN8mtw#B zwbSK$Z=S_WNXQ}LSjLKHTmkL<{9caK+ue3Eu$zJ14D4p$ zmp%hr6^JXHaTUyI{1*F-%V#h!?^L;X?wEasGXea{@YZ|R$}c{=RjysXhfnQicwvCQ zGjd=zK3jJ`GXBQ(ld%Kz@)wo*WpVI}T>3IOK|kqBPxdvG!R_c}x0N5_CSkp_%)MP)w#oc`?ml0x(=Ows8H%TTgVfwx#A3FA9 z6PGwtr)^)RI&~SxIydTvzi@CMWT#s+^gC9LI>O5Sc zNv6RAF}}gdrEI6B_LqYP7s{c9H}LiMRb#-Nm<>Eltcfo$>ZO2kUXBH@|LnK+@>Gw9?7bua#5hE|=5i-Yh3hzEX}JKZgg? z=*M`d$c=4iEnq_Nry&O*;vWa-*&sb0Ava+3?`{JcLpyVhP{!@g3#)XoIt|oYjxl?l z&8o*4^*CTJmcC`2c0NT2dKELHd4)i{44V4UzTokVC+X=~RU_osAgGuel`MCsh8WrP z1GZo#L?!dkSgKZV%5*>1XNT_P9g0pY9gUG2y?lgwRq03;YJLtQcL&$QGK^VQTvh0i z+n%;coeQu}j#=!Ux29|*v#c?)R(o^;S)B1>u8Wh>DYn=XuS}Q|$0v~sfD5R3=$Y~M zW%6tn0?%bcZ!%}Bd)kEDa!18DoQy{F;D^lfA&_ViIQeR*R#N!M0?H=!w!~*u1=T_& zk_82$+6o(6a2wwn7mDJcciuJfmTST_3nBwy%0Ofzku~FL;;IBz=T4#;oOp1ot^&tH zpFY?91d<>Vv(Bx9bdmN_4VfiV!6*{6UQ^G=g`po@!!sxPP>kxNosHDoP9`XogIPlML0i(M>G*xp7Cs)NzInfwRQ1 z*?}5|W}?`d2v2~S?+THf3oO^kxk-fu)X@~kE`dji#K_o{sts9Zo#g}|$vO>@5PJLi z8Du8L&Z;4EuLMSL0g7)?5K|y!V5(Qi%_!lb-V&Pv1zVNpvn*~N#oJ2q;HYkGT>oh7 z;}CQ;>We;PJFJZ;6b3-TQKYh?D0pTAYNlqmlr0D4&0^*}49vsC2z&QV*|MMQZx%3c zz!U~It*$)A;HDc`F7&F+oVRrQV=RMu#a|-h-^MTGhRorjn!?}u(|Eo#6-(OkSzt>H zGo<7VG-62AF=-hd%=ZZ81j6kR1Y`Kkbe*_H95(@%U(vUH&~h zC;3j9J8-!C?63Y+`RgD2&*jFopWzwAiuI9d+>cT997hHB3oTbo(+vGS!WR`Ws~w)X z@FX2PbPg+ZoWaYL3*{(g(>s3po8{=yi)H@MX~cR0eU~dE_{+1p4^?ha!!32|Ymd;6 zAK>}pz4GYc4ZJXXrQAXrmll797l+@1>=F91Z`UKj|pEFgZ5qvP?J~ ziIF~+sh7R5#7QQbakwFQ=E-{g$!|+Mg5Q>(gn+Yh%wQRUQ&9mNcJ7qQ&yn^+?Bcgo4r-^PQnQ)OoVVH?QCH;}o} zW^jXhJ6BldWNrO%dGcrx55Vxo>Rmj@z4Jj?y!~Feg{5ro-1<2l+;I6^EQx?+Mqa{f zeAwYLTk(tC9ZAjyY^^LzJ5py4=$Z%tq0*~bK!c5z29|l9;*j86e4BL zfPGL|8^;XLQ*P*wTaMKw+$T@Xfs5sX*FPObz1c#9lm8S0*VZ_b9{z3OTys#_i@wdy z9ku~_XU@Nglqm0u zo|T$r(#N@TnUjc0-sQt*3SGyW$g6lr&ZTcLXb`^b`eUCXo$b9Rsct+B5lSyRGxxcV z$SU6xht7GYt?t~sHaQ)+50^#R9D0Oq5PM-8fj7j=%&5nYYEYWOC?PPY^OG`CV&}-D zmVhuMJVH@>S*@X1*|Dzoa+lYpvlXlSzzO9Gm$3)^R&_`4Q}+)SWN=GGU;7P)Y)*e4v_Rc!KI`@~4X5|cvB zfk`bL>oH<8=g|2b)XZC3&y&pG002M$Nklww;QDYChbT!MF>k zEDRiRGqEX@>Wo;)vz>BYxvK>VX5B$dR@CdS zMo&v|u|Ku%{N8f0dCvTL_m-O;cv)vAkB2&8>BYHR;t1{KMRhx-SS(+IhGA(p47avo zB~$^%VNWj4AMlhJq!zN!2*qHPNjnQN0NEGw53%tG!yvUurg|LZw;?M_59!tJxe0$6 z2|spUgpAzc?U$=b4FCm|tCEErj|1#{(~qg;p&500L{eMqqzGBcST!CAL4tI``MFbR zs~!|8SGMp~<+V_ujKSg})XrD#!QRGt>T2BXOP_y!gU(9VW1kBs*Qib7c3JG#Unnb@ z{@XcaOwOaYiLd(BM4(uySAo;gv^K~^jtW-j^LY}T?k#lPGbf?>m%Mg+7%Q6qG(*O~ zjpWsVNhaD@x10O1TZD1Y; zB>C?q!B#C-mmgu7(4}(k&NV#4`54b{xSZ_=csBBo&R*b+ZQX+(htb~Y{kCK;pJ7>^ za$+n)`2_TuG^9e3{FErWZj)(;uRHb@C+u2AEuH6H6a!TT(G!!VFY7^S70)*|HXh+Q z>0vA(`?d15Z~c>U@%6t~j-9wv{_GF`hw`U?_}^pYkpmd8$=L?|Wk$2@HZpgxNNO_pH9>rNLB=sE@Za$ z43HdSHzN=2cFJRkzAy!IpnKit>4coi%Y32qDF$C0C}&R}E*H)$;P=}D<>;XUv7CE4kXDD+%mhdpmK0ndNpouqnVk_4S)A5Z+c^vX>#s`-C= zXA=jsTYpd=>J|AsxeQS66;_ z_Z#@OcA!m~=9c;(=+}BF9vxw|5#CZZKQ|vE zd)@1|I@D=$4wgF1V+xl{ILLGr+NqL&;kPOomcaK>QNAFSt~*?+i- zP3HoCJy}x8O)xNXFNEl&HgKmB^YgZ5Uu_*dc+UPnXxyr9`!K+=rsw_;6jM>c0reUc z;St`kH#Hr7zgE&u+F{`-c6!tKQhjj{rga+J{now#r0A?Fx}z5f&v@*aN?Z|^@EUCL5Nnt1RIpn8ctR|c4YYm%y!b9#sd!5E znKF*~TQ~gNV46)o`*_#=90FVFx-v<_s(o_M7e4=%nFCw@BMXY{SGoS2>?o?hr^Qx* z%~mn5L%U^mlH>9;O}l94<>B!38u@Z{0NZ;$-=HUi_ja(Aet~9m9qdzh_<_4!x`dDx zQfW8NFK)F8n)1zxr+#DIq{@B^;1WCs9r3MVzg(?7*Me{S;d!YyYj@3_pKlal2Z{kZ zcspkwvyd@H9IZ#XFb9u9X`9Y(%K_qx3TIzgc}cqJ@WZoPYj?#X-yNR8`xA@4h^E)Q z`^sU|W`iWxhYw+L5aKblw{nBVKM9XPu^P8J0z=Mm$7qN8_NHO|<+`I#xTERaj^p~q z$%YLsPdaZJ0VwkyLJVFl;5%D)a$3bPztM-=jyhH)L^^>+NYY4_|<8q+St+G)V;Bg z+^(zVy4ZmOsZUG-wNq&3*;N)Xok!lJcgAsG_g2-fPW=U0??~_c#Okak_BvkJ4WhwI zJFB*acDzSqB}cE5aO29~FuoUnY*z)wzKoQfk))QdOybW9Zy4dgIb>UjUPmby4yRHZ z7;KRHFElSHklT>hCIjCIDQJkjsXrVIseJyGhmEs;?<9?eANk5krA4{CF4#hAhcH{d ziC$b+j9;*$wErMh2-?PhIo^dN-s||q$3fQdAa@nR3KFe2yB}jiYPx+5?=i+VVDqfZ z38(55DTPI#EF6&WS6UgSU?yiM!k6S|P~#YJZ~62-bf!hsMVly+_{7D$v8$`s+n2Q} ztICTaeN{Fv^*VeK)J<-btV4+$l1#PoOlgY6w3rxKsWC9S@!qyENYGB#J|s||mM_M@ zWurHDcDR5vGpjozCw-|EhZ?uZr2C_Nan#~u;`>WIS1b+`;l3Al8q0j7?6KkrJnO-< zO`G?eXahEW{K8~fT@K}POY*wM83_x&$9jMMhz>f(P_q+=3m%E=eldO@#-T?urh{(g zOt}By10NT96Wof6oKi5Y2t)5y357EaDNB1%z~Y9u!eJY;)?C5^XXMwESt(QjgK=NfgSS z6?~n4TVB|C8qwLJoufN+G|t$=`H_TJ_BP)+wZO3Um2JwmITj_|W~B-kGpIkke_QCiPqN2BMHIltW{F=K{D2BB|tC*3a6y#Gc${Y%|)>2dm# z|0|qXr97P03s_HMOY3tf`{CEur97(D{>Mh@eWH#3`d;6ALM$%pPWa&rozR+>Bja{g;8KJ zKT-VDax{n)7ZEH&OIK5!;ENU>L2=EnnHke%G|_qC0S4Flc0Jy5KhIwj(LQo#6yQRT zrW7v@2n_y$)yjA)X8U-rn*Qc4DEV9m1H#8h^Sqp{`}`iREzml!5H^%!m&cj;I`LCV z#66lZi)0Y(9Uc@`q@qNs(K%tX3vm-{`G;;j>vQ2rz>oG!26=w=yF1Hy=@9R>^Y%On zeuX@6KV{5O4i1X;btw0I@&*0&#c951P+pR#IG=e?A;L+s6bmhNP?A*oAUzED@(2iF z7p@?fUn?)l(8gKy`kLx$N63u z4e=8Y%>9ol>Y;48N51RQl-v;EKBWWEOfFY}&XFIF|%-OVRp5A&V zmHcz*T=t>;xcCW3G^B3#;JkaU<4ayic0WXqJ94i#z}a!|-&wpWSyj=jxmWdHjO{Y- z0~s40bSkfI>oO&wZ@81T`RrGvIv2D~{716=pk*${E;7;PynU3bIDXHQHwpiriQ~N9 z^?KGvGbvmCvRrY0l#r0z&D<9hw8n9;(E+I*`5X0@g2%#wNN8y5OW0kOv1S{Y3a-rh8Lz<5)qf0=v< zRt)h}A|}RuaM~1$($&x-GMZ55kGUP9Hb2FsmJc|YCem?S`ttvzrZQzYpG<5j>Tzwp zNt5*I68Ap)q3`7~{qs;y9kj6S#Z}M?Gba{u;lXbeQ7Ky%m1x)UvCWYdwa-x0p_u%E zz4ccO+x?q?0ViPMg~D3r!J*Z4Ye;Zwtq8q2k-0M|Mh`j{soD8iqssP*&~opLU6-j5H# z)uH?EF8e-JaK>ylyhEz?UUO2y)9)}r;#Lx%CioghI+l@-Er-p`m|;R+{UY{F@RH0jI>wE^m?@@!c?M+1t}o`^xPEE7hCzwJw0Xq;{&6-p4HJ0S z**+KI(~tTpvwI~gb6~)GdI^xJZ4d*KVKLK#{LL1<`Gu4uKgwBXm;G~g;?1Z8`^75$ zcKvcef%|ur@WajzL)S%!+^Vr z13^Yms@?8_!^#paU1Xnyp+T?o!3(;_HU87cxD}6+9m@0Fw~lZP?}_%}d=AjpOUsCE z)jcX<_BKP2Um^)(Nmp1bZZroS^vGIJd&7_3w)R(6xb&*^a`jD2`-F~dgT@tWI7d`E zpPFMT05Z$?UvewT_CE!^eet+23LKG&nPZM)$B(hGSRA5bKzGy)!ZGka-09x28f_yWv z#~?QJ)zwwW_V+6onEvgEt%V&ZoI#0$AY!7e97Q)as7X++^z&oQo634j{#voMf>(2* zlkkF;tE2;5Wac=>N%ZlE$cHSN`DCAQasDKEi`jM%dBla{o5b$2N~G4zyVQ zgjW6q$+Z=#jR>>7CQe3CfV_D#K`_q}7_!?5`h+bSZh>lUK3su}u_rz&c7xoI45Woc zme-3Jf$a}bS8NBmU8v5_pXGgGdR`?h9vPgYP0_b6P-Zy$=Ln>q}eXn%1rl$qz zAZd*QbP_ejb?C@BKVRMWdimBaoBTadfpYnqgBK-Re2AnvYskWHN!_*@ z5FVXP7to`SeF*+ERta`C>v5^@yic6%jrxvn*j4K;3ih_L9~3mzTds9j`o-4Z-YcQP$yjr^{ zvV++_0e$h_?MzY_K~Fo+oRAf7F#H?$s$BQEWx2zrTHMpAvh>>M_D!*d&Fdb>nW z`Vk_%8x7Ehj2dwOT3UWW(gc0a`U5IqE|Fl*ct_Rr(jG4WpMFW`~xc(gqzt?YTk6e+RsbyC+`7|>T`@V%DZ2BwgOF%HXq)f8;oX&^2^*rgYv`A4 z7HpbQ1=gqGU}kja1AC9=#K9%eOM2($4nO1exjo41)Y`ob-~1Io!=H9V-_E0-ZY2vF z6DU4)g^Ak#V9Y$?X9hzWtc;-meJ;FxrfJQ3n4b3gl3H43qBT;tqh?!{8s$!A=xK`W zuRP_yRDJ5c4q@Z%$&U%s_CuYtvY@2+$F2JbeCuqxNX8+qr$oO&O>RW#mNp5ueZ(jt zUDZZ!7q9&2XE>-Ngv-`>I!P0lVljOrcS#_UI6)kntIe)l|- zm>Bv^+nWND0KH#7iA_RFam~@{`4RDdU2s&g2{(ZI*;~YI$3X7_?zS8#Z3^k}6x56K zZlNRQt#UD)+(AoUHRJ+oq>WOG&v@@DzEt==ZkVJP2uaqVnw6Fq_tJeFGjH=C*+s1_ zIH0G+a9JD1+o~|hZ>@)5P@1*K81@(Q)svLCh0dU@m(kO{9Nza_V6%hCCc9!YJ+SVf z*sC0Qj_s_*cca&t8s2EEv0(B0n%J^P_C-2BmWo`w96#h<_0U~3^)ZcW%C96{yCIG= zLk3+z%(|cgletV<9ALwtz!wZ{I3@V$A3rY@3n*OZK)2o!1O0%t6XP~a@@(T+DBy}V zZGQFEU*zE;Yp)jGQo3HiwT2Y0_B6rYk7Ayt&T;yHjTfEg+?|UvSCl5=QUvgfbhzs` z-c7mst-cqQN=s9{w5dex3TA{Ww@wV9S$7!@*m<+--MiaUhcKRZ%tKtQDRgygJ)dnq zSZiRx$uFWCg~^n3x$zn|o&r4w?Ecc7$!_;h?v%JdzTHoaq;4{>1fI#(%yli8Q0J6W z;bMkSm5rNkPfjI{Okh7PYnw%f@#IQ112~)dM$9$T8TqlXf47IgSJye|Q+nKxXcUIG z=ivt+xysdg)b#rdsK{rvCifYrbRg3p{nUIWBL<6oE*b7AHJ1SL(U?Y4X{uw~^n-&E zr*WDZkALtI6UjAn!if7x$q>YGhD&ryJc4%{#W%G@YyeTzTi&lK%k z7f)wi`u@vXtJFkFJZ74EtP6}IbXgGj5Vb{^EEX8guYgZm5-w=K^Vyoh(b68vufnM2 zR}%wXsWS!~@f)=~==B{i{v(OUu`o{Nn(4(_&v~T(zCzC{)MVYV)c;*5OL3`+{3q1_ zI6oKrckRZvKRI)zUoAJ!SYSKdv2Uu1lD3 zYUyt%cXqV+1S_t{@j`r>a)e!4xLw3bb|xz}H9AcZ^c%+r{>>`Uk}BvZp5xjI`EhGy@Ywd=X-3|`t#eP#1(pFEHM!D> zTy9hA^TZX$T@y00Wti4|KptIeN6j_-Qz*|ivsW+Bv=Rdqw|&2%P9D6M>hiJ4Ou|^D z@QhFHNk5U-L6<+lPH~&X?hzm3L5$&TEHY)aTgl&6=NDEEMhMxWVhV>j|EclO_g*Q> z%XB}&s>n+yB}%HHWc3TWCZK&lOtFfV7!e(R5!pe0B*qNVQmE!@jx@3DoCBghxk7B7GFGhGrk4Uqn z9oF8W4UM_7E`?ZGpCF-e{6OPbT+hq$CJ2~!Scf$<%_?T_p60l~Lb^C;+u_V7F}TlY zc6LGla8?gLRh9@IGJ$IzmECLCDshxRabSZBD*m2?$sGtzdT%(1JJ&<=UZC9>uv^XF_64 zr^uN#7Dq8{FDsA`yy4j)ZZOcmJhsyk++%7B8KrQT9U_TOP3QJ6^*ua&6au|Hp)wX+ z6&jEZ-@9?I_ALA^{5TGWINqqJ&x4sxTFcf|k49`K(&xfftd0>MwCVaYxZkrKx71US@I9zMt^wi#z43B2T845RdVHIZ9I5u^iqY!Sp4Swt z0<^`FD)7x|li(%v)^qp`?jZ77K)YdMGMW+A*xLrgb;uq_i?9rk^@4ANSKPO=$)iJR z^32PpezYq)@58hN*$?U&;beh+`22fnTl8bo7pp_f=ZcSo9d0*djwV%RPy4;g`|Y)<@yXoJa0WkypOVOZ@fi(FU8|) zH$8jO9;g&42r>e2iqtA6g@q!bYb_id7IL65b)@h z#`-BQ8n*q}*unytcvmk0UV~Haq&>5P@0m5;YoD@{5HYbs%plPFw%BUAySNC66abaM zFm&WL0djV0>I0#fxY*6QqLc%{2C2>$v8#Mr{+tqj%2N?c0drXZJClcb%?P9xHRzkj z_~GAM@6pqwg6*3K9Kjpw6vjhcIFDO+wMEa(=J#xu+#7M{$t^S!X3!;F-mKMm@Cbix zDl&Tfwe@E?JNGrK*zfgz=mXutl=%ps7!L-?4XdN;3kQEhB2@bB$YK~PvfS%L@6fC_ zrSpzD1DO$#Y6e8_bSrWBwM9h~2o}-UDV}r{WBJo?%&lWbyk1}6R5)X#m)G_1J&Icz z-Ke_{OT>XI0D}VvkYW~XRB^8im0IJ9Q1L?r$dasmOeec$*NAnZ-2zOyHJRWtb-RI}?RG z(41lN%a9BPYO2@uAbi6gm@?Rt9L3(ll6Koeh5rV(0i!`Wn^DOkE6Zy4-x*o4_S@a) zbIsKIc-i|~o9_sH_{JzY>(o0hI1)K)Wf;%vR8wgMu^9Ljhwp`_W{91XHRdv0LPU}p zO}wDiAOb$<=?ABZIlVq{bEG%!=PDiVmiUXda5gydITqApyM6I#1626wV$pL&>d=dy zIVsrTGX6^?Kes@cuI4SU7^$`;Ou3*X>J%;lXBo~ql^FkA^IJoTR&>{2L*>0Mp?^U>h0mRkfa#=nHWwE=wB2V^;@^yyRD^sndH6D^L zsWFWn*dFd_*pxWs%=BBbL&!$=^uN^^=99sjeXAgzO8Wwy5plWt)~M^TIJh$3&!2ot zG3j;R4cJ5ZSZr`m2hyNw5bDz|9r~JI3Zs?1YSk1#RgxYSewzk(Yl>6b3yah3MduVu zOHvh`Qp|&Z?ykyYvN-GjmcPF=T?@)S9Yy^!L{PR~?ca0w!#V6mo43RQs1`!}#Xaezw^q$TBtxg>z4EDN^Numu zh<0hsHMdS90X|n1i!5K5Gu85IVoCI+Hpkj=U%`$5(3pADzaLc@-9g?|`7^C(J&Ii# z=go}iK)(wz_Akqw!K55(4VmzhwGLw%srI7p#(RuQ159?U#nq7CLLr87K8z8vICYiG z7pC{>GJS-CxiJJ2U$7}Q;(p%8E-5=l#*{|BKZzt}PI%QQB#6Ws~Q4FoN zSap`VEl|KP;~JVI*HW^d8-epDS$0z8#drbGMlAe=r-#*tLYHzfp8^AIT_$u5SJVVR zc25LE?&F81({hRC%uJuAZ1IO2m*>q|Gq+G7a6#X0T|1n>5DWe6^^Si`)5`qYpmr}= z!MX!FY#W65L~v&U)*#T6PxeMk8;1=>J<&J92>;8wi2%kK~OuQm)Pp^KlE>n$JC+Wu3d87hCUq&Ee&=WEdJlnGs4Er&5zt(>dIH;_UOT7h2jvphNr6caJdBYxWGqa+Os5wd3!3D({z^DD1$OPmF}d0`xBoYPm#!EM z^AfadYpN|Wxv}NeV^wNR#4Vz*$+ zRkPI*{`&{Ae_AD=o7Q*C)=EPOpC;b*N=x|W3yX=xNG;r?7Qy*psp%SsdUM=#S$Kn> z)^hipO2q>jky_?yTSlW9bna$^Yu*8qNvp&K z5V2Fogmf(cH^RET#!{-@6)#=QJbJWT;E9}tXPPO^l%rbePiE`G zk1wx3jI%6t|NUJQ_)<-+4#VD2!TphyvD_b)Bg{hR%!T?Eo%fBlQMmdIExnhAWoN5f zt?Y0if&U8+(rpfcMezMg`fwL`=s1YU5VN{&@b_t1P78&c+S&44s4kyqw-iMNzpj#H zle0@i{7FzLrqBh?^VyO!o-@VJ`Wc#l7Loa`J)^z{zcSF7bMmYfQeJ~9tLdbS9Dna) z*h1d#R_Nl>E7n@u_}4;{E{}jCZn>@y9YuLQZYfh%fyQob_250jmlb-1e}gQZl4mh! z{=V@-pdU4MMxBywxp>w-;v%D*!56OE`u1a-&tz}owueb7Yn3IV$bokMsg1E_wrmZG zMtGp|K^$OQd^nDv>5WQ}m(Ch~I{BSC1B!KCP$C6|+=M{18`0XX>yGBFG4C+H{$m#L zQH92hmELd$Wx3~v_e7zMuZ!KdPm*XnnSWOKp%(_f1out!3}b%x@5PGp#WF7n{B|g zBqW5r2?`oel)lOC4*=#E7asMqm(VP_6xgShZW2Zh3eiyjk3Z}IqU%;dKi&lNqvxnQVEAaJH z*RI@IboD;s8(wM~2A7b!FIO|7H|hNv_v^igczbZdfHcKDpXCF|FE4UeBy5$keMa;v zs#h}_F;jn~^)^f=Vw)<{Dn3cD)haNum}`2uSRGM2;d^!>cD3ZTgct|J2bC88@VP<8eB^-4WQm(o^bXZl6BQs-h zHSk&rz2$Co>t}0}*tbvL>NsHI^^)j`m_|}g>@_JpT^e+jp zH|4sVT@$9R7oZ)4Vlfzj#Mu9QF88dxJWnZVlDfEbrJj*fPNDg8IAh{1L9c3jc{4*S zOn~}V-|uGa?tLqsUMevyCfV~IYB%mK(R8G@{GOY1)zjb^KlW$N`vj4R0NR_bvd_j3 zBXMo8)u+@8q^?)uy()LvYBbFvhHGToyu+RB%l|u5A!5jnhTAbOg0GNHHe4I{tw@qf z$JEs}gqFfwuD1!W$FiE%sM6|H?>2KHGQL{XKVFgw@l1wy$#;h8!*3}^cd4OVYcbhD z6G#$h_g;P?1b8A{W!$0{yolyBXVF1{Mtdv9yhiFF&N1PqM3d3*qrnmPQHhDt6=A2o z_54s`>~?m@bxH`_73LAJ)pW5qK!k`g=W`-o?~T0aJd)s&x}8$gqdEUOHNIXQD}uc} zJ*ekFINt(F;)cieS;d+!90hY%mt1p2|BbwQMCY!_F zspXUrv_LQ)G)l$IEIi)8tQkQhSJpdto8Z)#Y8MH}mbB;^b#WN_T;=zKUqTxI(sW<} z!;a7J8M&a>e&t+LhkkZ*%G~<2?2^<`hZdIXvGL^1#MY6z;#={+sqON4ba~ z&3@PG<5DL?-F}w@Ix9dAy^_w_e=sG#Bj8k>vR3g(Y@!CwaB&kxMfl#O3z1n4@?icB zPfdsw_}o~ZlQ1~sd(o3}cl1tH^Iyy1l)xuLZ@M4Mv?KqUAyx|(i3XUr#14W zo1E(RU)SzPAkzMjT_+iu&J8Q+bGDS_*vV`8{V~6acZy-ur@wWSHXxR9{X#l#`Dh4# zT3P0PxK;L`Gk2FDUBabt*c@r1&(#mUo>HV^_en`>)dq>>1ErjPfu#cK3%f?i8wp}I zHnhz>m5ZG|rl#r=GWkri{=GXw*Ne0<8$)c-k+@Y=tGrYWt%(3XuSf>8eH`M+icfQc zU$0sNwDx#!X<{Xsf5D3n#783Xu$ZkgkKN!-;osPK7t08+Aaj~B*4HU!mBKoEW7PFB z%C~h4?iX}834Z!DSo4-_ZVt>F)8Doos-FZL$fM$oZL{CrTl>+~<+x*Z7quvH#b=*> z_o&?v&=_sgBu^)DPfNz8bwBwW&!+ZGhP4sx(;*H($vS+Mr zEeoJ#TQZp*1B-p+^H)5u2e+ae$X1Ib?$a-ApW&bWW2+P#MDu&k-M=65-I$1kH~-sm zQYQ2W&{tjaY*{rgZd{8;Z)5Uznz61_QyHB|3uTDNZM|ywo)ZdQZ5g*C7oS6l6JyuEg&Sx<+j0 zL7Nqf35{U}M1?+otk@?ahLU==HLEzvRbv(VvK#$bC1wIqIF%PkE}sF3zVuGSMcoxH z^RfD$;w-zL1F|XS-CfdE^~TycPeyIRrr^?fjs0oSge@$WTva|f{Da#V#wt_c4m=m1 z?Oo}2|7FBh@yR1Ac-c}0IZ}xN+SfvHvRP&BwQ)tgF$)}ZRD;xGgUeq6aMMGkU{Mi7&5a(N3~BK zOOw_t5tV5)?|xeM$@yXAc&SYKFM3w?&1=lKRqD@ul_^UsY}@|M6f)!Mnf2;si2y+| z`!`Z4E|>C;GS& z2}PPoyYh?I>yEY)zlZc+m)2Kt0;a(BHT9qmp&0gB4vIfR>A(fe9A8zzQF3ZuI`o4O zGvIZZmWg?Xz1gg&w${G+pMFeL#av%VzLS#mI>ch$(YLGqpT=0AV!6|Yy2&g(%wh*c zxIiM50+}zUv$Hj-$NLa$1!?H8dL9PDX24xR#Dtv*y9H~kit(MXyW&GUm8Zfh0c3o6Uc0Y@QwA}d`@WsIVg8PK`ttTq?CBuskPyTgtSIi$r?z}-&Rdh}v z_DWkAmw&_4$b%39HntbtaWSs#z5}>{dNgrMjvC?puU6c>Rx50=Iyy?>zvTI?^J=j5 z;%Xj}0jSL_6KL6(NpTlrRkL|ddt@huFEOT>*ZK@7RA6(P9l-h3zl*lh z4m;QFk+6bC<+QEiUFO5cykf{g0r1f(o*tTbtZuornZoVD8t%N1b4%Gfn*NB-n0cnp z3@x@;GhWEK72u?oN56;q{g^)#gRUSw|_z zv*oAY!P%%kIhRk~JH=dEp)g*@e#++Emt=a0Rzy4@ z=pU8u&6iehD_QI+)UL{QUmMIxmY_?76$ zZrjTx;$^rByZE|pA!+X2-R%*04(rl0ik|pa4z13|)fcxtvp^8=D~&{Z6!%ervvFM` z1{zmKD02{RB{$Cy`!iZ)Q!%I>1EbT=rOa5mi&dssy!^S^TwGzb$`d7n`*Po3}%A!JNKb`dY z`(*P|cB5%K>N;pTB~Wqj82Gyp@q?XNZA|=Ak>{SR?R>kvZ}-SKVqE^EbxFW4=A9)v zf+PL{Mlb!jl7f56s1oPzvXC<^s6&YIpi+6-309BtG2#5@9qr#lf6Dw7oh#a)-a2T%A)5MP zsn9)7o3)+k7e#r^ks+)nPh*HSt-ez+R3BYwk4Hy~-)8dntHDZ#ekjm9SkKLTI~c}M zD)V=awz9tyJehyZxL73lm=pF9QP(RoB?#D}sh^(vz*S9F8!k^3tAa6_U8E>(ZV^o1 zX-E6DpRH)F#ECic4h5tExZCqy{i|?7P-w<~ubW_B-`#H90(7n37K>v4^U+cXEx0(L z;xeDuy%_yg$0qJqRY8Y_NJnuMOmM36FaP(QbC+oV$!?RLB|}pDtJ>>s{k8rn7rSU0 zKy5|#Z(&i;CSaS*;1ku`RUhecw&$e3C_H#lHsRD(5uaW}T?`u>ndaYVk#5IoVInt7 zVTJ@iTZYM7(+$TQPV_M<&ZcmUDAUC(^0@aEa?C!e(a{%O+Oev&fJ^9*tXsJ}b=Z7*t% zrMggF>+^w}jIfU>z~xmfqUVM)SXW*vP;C^^2G3JAe6c*crzW~zuk!po^Sv-KZzSBKK`r>viq`sYF~Q2NwBB-i=EL*&?H7=* zK9CDwE4V?hYfHpE@m~`8Uz3Nu>FQOb9nT%1_8L<0sA2?Hxm(p z-%qdOqQ|iTMgOk?+8e`Y& z($Aq7ly-*4b~n$D#5wJx-N(CP6-hY~#*YR2P_ix_E&#mylMt}5 zYS^B@MC6!uBh{%%Q*D~bW57mgvU9FL@?^0rJ&t2>&n5_C-!*3u(tl4mn1!&2_r^{G zjy`)0`zr}rwh@twM3^IHp|TWi8&QDlf^#RI4zDG&*kxy|CFjC&={T+76tY>H=Xf67 zP^;_fcbAw`xB10rs1pa4>7gVw0;<*WV}I$lQeCS(koEm%<+D*t)OA3Yi<%CUa6bCn>U3?1zI~{kq%R(F+FNF|%GI{atD<;vyFSz2gBVPVL<;Kxgg^din z0);ATHw%c;#9OZfX}0}8r;)623)OTP&FP+Q#aUqo=Pf2c;kV%+ZURRNOR28?tnGA`{bDTfIy z4ZjlTjW`r;^n|UMN4XQcYsOK|RB1T1nSV9<_ps@o$BcuL!}$)77e&bL2a(Q|do0-$ zbL4K7%OQeGg(vmKDW3-L4=g@@GQffOD4wAqFb=f69LXEB)pmbsyq_9SY@g{{X-hox zKZRX;iuRyYZ@eX%kjt|e^qN_L?2kTgqAa!vyk>t`OxSXUKKh9zCQ;3z1nM!6kfD=n zTIM2TQkAf(YR7eS@`i&{oG=wF;f17szlv9|mUDdy$`o;0?dI#1u{ZdPk>eod zDXp)8-lgbdjw`Q9r{y!pI283YaTKsN7`{{?dA_53MrFnO2ZT{+?z$Z>`7!vIT^^w8 z8-pXY+E1`@=@7BL7|?>MmFCIzta+#=)0^wa4n~AOIy|%h^Xa~)RYvh!&)e$R>4Z%e zqqb6%5t0E17KT5*>%Oy+uc*D0l`|fc1{IEQTWfjiRH?7$NUJ$srxQ$C@V(6`ujZl~ z4JC>2&2W3CWj|S!?@%gnG~LR7vfVeqe6YOiC^suXrr1sOnEjBitwc?7ubVe6GR*r| zTJ95{hFXMT>Ve8@su9OIGxpc;SNbQo#J`RdV@5y8dI+=ZWa661IeZuo*1jMU9yPX5 zKx;lK#tV$9s&dSqu2Uz1o2PE`p4njTf6F7(LVzz1`!X>4r9v>$^<@%5?8$q9(o2~j z*N>(pi8up^^Ut&(KVkcFLat1$Pb9w=bW{{nHMgVWwRGedv6J|l8Zr|bLDh##R{gEM zh8)C`!uWY`M#&6qlh`aLjLNZh^_4PjQ}7v>n(@UcIGes3_OLrxRPtJ)pJWl1Vs8rH z-o-A!_{Suu25|oDR~F3b^WzTm6@?x0X00XOWn#Hd%>=Oi=nR&^t%-TyWhar_sv%^| zCwLCyd}=LTX`njI`0aqDr?7#g?d{CUzLETzc!~>R5}EHR-f`J%7U!~g6PT-Zkt&cY zgePN$BZ}7Fez1&!X_!D2Iog-XWx8}l`JhT)!To!6pO&#!GHIZX2Fu2E_?5Bke8L=_zw8P8hS zf&>`7nhoWOdnT)S(T^*M-6n`*9kkflcp!zc^g?6(U(g1HnHVERYnlZw;oMpuy^Ie< zQrkV=g=cLAOKsXba_Rc`S%bQx@it~b$|a0%XU-p;6dXIN{^H@8S@Y02Iy~c}Q4drZ zWn=_$f4XSP%+Yf4t~5*NLKiw~zTHw(fjzGrdX~5ntGM}pb};lqhxpKJIm*9CYjc@w zGr|#wc6uYxL+GqZN-MXF(Wj4v=!~H=(%A zN8wh+OFQV5=B9!YT=wAfOj?QdAVBeHTEgm+)M&M>6#Gs3N$NO&{j9@mo-^GV)&zz* zh6Emg-0mwv17hOEwcAZNxgS*8hvFwv`ENU}k5~~*=vyS?oxBE2o!hKq;z!9dI9`qU zh(Y^f+XiIBSQNeQD*qv?8BSH9c6r&g_UsaZo`!2=V5)Or) zfvOLNDzah;=(r)d=1;a9RzCN);;UoAU)3$%@}z5RSE*&2Z6`cGlERK}YSWftRP^XQ zMy*kPXD)^?q93?9^7)xu@^azH>Iv9n$6ap1oGTygBUIYO21Xq!$wDcIH0~zncE?*f z)vtRkpWbzmGsjoQLlwDhjtB3?Ta>TK)4{Us`e|SwSU3 zSXKbqBzN)MBV+jNGt>D7MvKP3N0%qepL;LA2YB>_M7KU^!EOR>+F%Gme@@?n+ zA|Ni{A^l~Zj5l~$!uY4j3D1rHdGT9a;r25DI)ZaF8)h@!&_;PQ@l#)nY(^G*_kiPH7%Jt4mYHB#q6jLVhz5cWz-@;}UEbfg(zsWsP%9IPWsDGt z=C&^r;&`zF+(ZVgo2!$@0pCB`6qy$c>uH&2x9_;-ugr0+H$|yD8?9nX;9Z8m*JACi z1)qiMf}aPVm;H>xG2WKOcOgS>{ZWLS&d6xCjaf=3+M|2C(|J{3S)TBF9)GIu;MIyJ zyM$%}{q=mg_JJpCJlEqqg}Gl@A5YG}<4SmeRHgHRA)W+24K7}9aY)uDQZ10LJh=xu zUN`JHBdE=(+F3sq{ogg)wh%T4tLGY$H|s7?7h6->n&79M!@4ED9Pxgf`Z*rAvJevY zUZE$4Dua&g(GC3K_{4mrW`UZrH}mkBmr$3Ti%!+14WUQ}RV~OkbdrMJRF$6u--v^f z3~qd_(dPNp0Vl?7a=qxdv317G8-lxF#xLRy7JN}-o6;TXPDkv4P2w&m$@3k4CwixnBzo5tg!N#}<_rn8u5Ib?nM7J<2v;%`rw9RyB$ zY)zd;xgq3Go2<`8{UB$5Hl#9}1p=83x3-!JHp??zd5)+e%%Y&;A2KnbW=7NZIun`< zbtR2C-_)Cp=a3{4QmZC14YNe*BO0;OmFM0oG z+x_94Sv$D|!QbS_kw9$r)bdF4?AR%x17g{UYFHFLQu~XrI-u!7RPCteTfl5-+0rid z+wh&rTuKZdXCec?ncyK)aW&iXhW$`frV#7#{FEq$W+QJK3H0wdut^Ea_(YUEIH>(I z6|f(6JyYK#hh)*iLUC|U`Ev?`j{=D2q5C4`CXDa>=0MI}$zbUDzSY#E+A_;&10VLK;#p!CQEW9vBqzNN@%phy*5E@4n@^d1%oQ}GsHHo=+7XI6WYgItr=t9wRTaO zaH&}F)kl>~FJ!Fc0&yZ2lYQ=v{5s@$G*YxN%q%LE<&ajrj5;67p%I83qxKlVpkhqo zGL24dRG2@{x7(=9T6~>0Mg?P0#{kOTv+4*8PB$_ZFV=l8W8LHM5-L&Xn>A-&wp2LUjLx1Jh)ZX*B)Sk(WZYtQ=_5{-_!%!#J{;0Fe&jc z9uVcqdCXJWqJo3^#Kw^@-W@Gf&CpBIXSr-m<^UTfg8}3Q<}2CSIhE>kELo4Ouz^2G z#{z>+;!zuVbA^10ZSqK>AQ|I=S{xeRlN=!dR(&FyA(jplI5oo<6?3x@c1mDV#AYvD zoI^GyP(E&YGsGxDgM%8kP+8>78 z#Hx8~SZ(NjxpVWq^4UlKq5SUs{{#0?e_QZYR@{%}ih6vIdJ*R;o^V#KEy%s^*atb5 z-7-9{-ER3`1jpYDcp2hbS)L+42Qf+V0w(KSz?;zcyDfjSJ$V9?Co$Ra=fD1}{NgvC zl`n4H!7FlKL2j{<=pjs89{J7oMPk~w-Os>&2KF=XUC01GPxz`@{${(074V+mq5e{~ z%isLPyX9Z~3~%GYnKv<#SBPyZXf=T&bVo`?fE>z|R-|_g%=?ej9hn zz|;wPI~o2<>N$Y9?e+`SZoX9Q2Gh5BuW@Ai7qQJeUqyLZEc~*^9B*S8W&Uw{6z{`c zn4c@h7U#>^Q;RlH@BGrq@_3yqIIWd?4<43#57)6w=Z5`|$z?h>@xvyU={$g?mf34# zz9<{(%Q39lJIzM=y&S(vuHNrnxo6?28{2a3GA7H%>~G4o~Jm|VPj;p}&jkDV3I7*^ck?dzFu zTDiATop|!HK8ru=$9!Zyaq{SPajoTg_1F&Mdbzl81lL+zZ*e?*a-p2TYNn@79J9%L z4<4@KOWq2uw`*mcE9k9mmd9&bxcSCE%=t82yVe>Zc8Xnf#iO=S-eV}s?aDUPag&VY z%*Fb@{Kg43STPS1P;cn`r4|_QT5j5kfk4Ts$O~N+iiAm8QgvB z9t^H6DPD1!H`wtB{w<6*uBgXJdVJ-o_=No?AAL2wljZn{CA{|bTse8_5}v|-*H+h? z$7^rxwL&_-(2$lwl=#Oh+FLkLPb=%$AGEwyQa{;pQmHGW$0k7555!R;@B|9E2jD64 z|MZEksJD$(_4tn~>hb2X`axsNewR&cac)L|&6Qr}KF2;5XqZ)l9xnyu!WsrUtA|i_ ztd8l)BTOrRL!_>+Z6to6tH#-ypc)oU%n6}6EY%xDAYD)y6pD!hoIBdA8h4PicFA72 z?wDy!Wq3%#mCGQU00xoasV9K{OpuO^C~L!da2V>{u2~aWvx0Ky1qiqDK6PlzqkyW5 zNA)i0QJ;~uF^~vBCbhANZgxU=BmUC zLx28xM-Aoj?PafNgippVrWvtlaCLnlKORx+K_^C+L=+=n$2CPO(K1-*dq~)HXlYd& zCIiJrnabqV*2x1h%#Zk7*;#KBpm?e4@m- zV4dCSGq(!E9Wg9YKZq5=VV-h4imXRWL}Hxbsw!n&1@s>k=@f4}Cp|{OY@8D)BeNY; zs!Du>3=r0LY7@^bC-kdRvuZZ2Br9`k#90SHHIIVzM@5emvc0a4f@Pg#$pu7)5Ul!U zzJOERVo;^=jFpHIo$FOoY^XO;Yi=bYj(`M`j-qhTVhg0g%Yh3o`^rgy3{W&zy+4n_ zP=W&D=Ww&Hs)-zlk63JWvPc$IV;62>2@^5v-8NMn^qQ>b*K`-g8OjE9Y_qEhW2%(^ zq?zE(N&^g?87VOMzn-(v!Jg-^cL;0^IOs(Z5~cXUf?|`Y7d84!F=MAD^gJ_6%zxG^ z7Rtn9-*2*_I;x1EU_Apv%!7$TS5E+!vz_`3cMIagO-ZYJPN;2qw{MnS^1X1f8^jm1 zyw-B^9Ve=AQra9Q=kay6M;5SR9#(Ka2pLz7!jzVFT|v#$fZsjvK-}8u{c`vAr{(UQ ztG0UHqX)OkBTUZQ+Qh1PSlRw%*$&|T6IV4mjMefE;}yO~a374Tk@@FQ^k2%o6(t^P zWb39$ZMGZWvSQNAp#uYqkZd#5O)@0O0Gc@xhDE9d*iIkn84O|25%ru#5SU)qyR1}Z z)N4m0%1E6XL&His6f3_w#Uv}x@FC|fpKU^d`GXmrhh+*ayCJZr1ButMdB<@0O5#444)EvpZ% zVRbm%Pu;kKiLwuH&xQAL(Mr7aWNbw}h5B_I=~tf%UkCqJ!O029Pw;R*=8D)GcNX!R z?E-#7ICFfyoWo?jD;JmSH{F%T>*emfhgijFy*zlli5zd?ciS!ep8EtVYjU#Q@VfX_ z#J10|pMm`h>}TNHlmT9AIWdoun-`88E=RF&)#Ch-a^fhixu+KJ+btdjnLAwWKU~3E z&eqEEgLQnCZI;!wO}p+M!jHAIJ!lV!e48?}-@72aEVXWx%084lt#4B-q z33c`ZoN^cAK>UnFaj?8oT*PS09Fp}%ajT8HvFSPb`d7g*i?in+YlH07F4lXyF9e)+*C zu;I-|+<3&2$cOOs8=suheHQtV4;{zaFc#NNZ4n=jW1Lyu$D(?+%Y0+yX)%srO~!ok zznI9w+|J>Z?MJaX-r0+PhNnE=E$1%&2se`c1g~efgh}M{$ba;MZd#Eje_i3n7Cz)S z;g3&yuCLuI_wRj*C#wIxT>tbxm8+lpysSLDZR_OW{x3KW;il*U`W#Ow;0-%sn78c$C&h48aTFFoB54;qi;k_&k}z!Xtqw@WYlx>ytc7iO`y&g$*qmF;`k2^ z%2ZbTMy?&WBo|J;bG?`4=reB6t~T>R-s*V?r;vF&2g#Pmv|hNzY z%Tu_~i>IUc)OA{!&U+)0&wOzOLENDI^2sLh%^Ss-xaUdm77J>5X(ICU+tl$f0Zd2` z@wK;GSk-g`H#7NNn3E;_>1Ff;$9Tlh#t{f>02&`WuPAo~O}xZ?B_P7LED9TQvcr!6O6}XZWcWiA=QrkTPx5AEE(&$jH+!eBadRq zj@_!~f?=krWyTP!HL&crWl+~7M{7?aC$M-ZwrY@8r^1I`{tYiIDkQ=GHtv&&7foSB zd-bmTRvbyi#WFWvJy=a^BpyVgX={fy!kjP*BH6 z!mW6+y`9Q&J`Y|OZ6GjuFmB?Gyi7&#R(jK@#tMynPpOSFlW6C95fQ_7Q_6OWskV!9 zlLoKMX&1hDurRT!=Yr|68(0S}{84Kg>OO{=eR64j6jvl(mXk;3XkwFfjd%tksS;Hi zw`y$Q?AX++7tFJ&n&GRqX%}WZT2}xjM|7eEWn7;l0l2aV{v(%=S3h9BfhsXKGQRj*Z?KE z(aOU0I(NM(&{5so1qnm|RPNBHX4AD{*bfhF(ZJVDYC0U!j0P~1HdDIS^Z-Xx3DV} zJxM3~cJ9NEpJWk`BB6zs>zIV7_9JX6vW`ufK#ICqvs^cefB^jSvW*|=X&=xk3nbOt z2Js49kFIBIe8}?f5ll{*UnmQUSTzrSUmnDbnOAVnOj~QOwaogG4_$F`-s+=eTQTp` z5C6V=e)ZQfd8v+;E9Bx3R?3^>!>O2{$THt-q%QH^i$2L+Gttu4UlS_XNs*wg@=lGe z6{;SaIpRuvSphLlT78!#`}_koJC?H)<4$Ze5k-uYolIOWUuGSZsea{QlA4z3DpSva z@mw|g{#?m*M~(P|GvWLk^tjEJ;3h%^*S@FE*75A*7T!$q3~zbBq_E?c%J;tiS9l}I zzb=pZ@6e z^1av2+V%dchU z@$-;h{rS1f&!3}s!`ZphN6Y2&_{|ot80VWNd3^ulo8=epeOi9?{_o4mI@UHlbOaOm z4%&*PTuf#EQ>&(RztCmig;&t4f24K9xw;#S@EM!G6x2T^9`wPV)~7~ z$K=H2xN@E4-S{JpTjm$1Yi95F;$t>biy`}0Tv;}a=j)0k$Dp!eU|f>zCPvv!JBq8< zPvaiL_t=b)ZkkN`-oNU!%b3hVOxDSU$BY}pNZ;$G$uoCfo@Ud_S?~RctH(;Nqx7tg zab&%0gv&a~h@qTyUQapcnXj;X1ru(*|JLR5>ZK*DPDaJ9puw$HKc4M;4F|9nyPI=3= zy^QwHFoFCTCh>6M%)(Jl%KL+|bncz9bp8jJnD-N`oOc9s3GS z5I1%jKTh$V{J3~>ywb!l1kBpNsT>k!eTkg#Ki?V)OIaR(Jsm2C2RN{8p1h3;!Q^Z^ zH-%-zR57LR94Z5sDQ@>KTy9Sg4B{e3_>ilh9kP3_M zIW(CA%_OJ5%fcI7Qq3Lf_WZ8WZ*=FuUF z+pphYMi=2@a#E4J0#M^N-w_DzWM&_`U^AEMKgtqy zg;yT}QX`-lIYQ^Q!PpkUrjeRfWW3G*Fsm)T6ixw&a4y@DDS$_*~^DUmi$0 z-y@xb`bI91Ln55i?dUN?7z8%ephP)Q*idYjqJ~$gayh#0!#bK4d=OT75Y&3L94V;( zO_eQj4r44fSBCZ~ZQNmPGDNz@hNZKMo)#%aVSLPl!YtUP^c{uC^8dqrKmrRoh*)s+ zej`zmC&w*DVjM-N;&B;mebd-tZ7>c3VzCB)9Y+XQcxWd-(psx>o_xiBtcXt(;v}J6J+x10D{eb&8H^zn1)KXH?J0}EBZR6=F4`p zvjD=IWyuXK8nbrdkk$uFXrX*qTZeIbKQ)M%r?$wwX!9|=Mn0rT{86-Y2TC?XH>6A1 zszIotOkmpB^~iM1PSD&#ofc;`fa&=<3mge`nWt^;hPXvljoPC$a{kav2xkm zVf^NTRr3yG#X#nVtLbTDj_q7hGS$(l3#cRY(g_+3mat~zJ- zce2pQ!AQ}%wGK=Nh}+^MHK@oYFC#R26M>A}y6ze|8N%0WGDVRhN_NtcL+xN$InVj5 zeu>qEk>I+CjlQs%OUa^chsi!QKQ*uLjt3=qGEv0aFdTOrZwm{&zw=tT{MKLN9?u`y z%6jwjco6f*F`SbNmJ^GeZX*nGu-~ljIJpe>aJRM|;lXFz`@Vk_t66@4HY}9Q*AtOb$Vt58-;S zfEAsV@VeV~e{cyCNlxJHXh+K32aoXj-Frge9S&2m+x6RVe@e}{)Erm z@+aNDS)yXx+)BEgg6@%oNgR-7i zIN9}h>Bo-KxQLZbyp)Y`OFyQg4Yyes=^Q!qk#*wd{RmDRdxlpe@&@$Lql=j2e59N_ zI)^WN=gRrBcopQ)d3!zh>e@zGzPD07`s{Z3@YCB21UEkp;B^9g5^?;RmF0|yetVhZ zJaUs>F{ce#5q0Wvl~Sq%b4L>m_`DT*6b=NBx|N9~3;FY7}am z3~T=|S=lD(p>C7(kOTWm*t>-mo)z^luZLA={ouJ3_n>P`*euC}TG&6uB)uo`x?4`t z+eV-8Rk)P%pZ7`Q9g+o?)QTzSB_70P7;lX?La1~MwYOSiG{hEp@uCCk7!7DOH-jB^ z0>jqNZQ$)Bk)qj4!bobHBCLjr)C!yxQTB6?Btuuj@!{aXIh?piKjJzeTG2w0K?JU< zp=>Y(-oPyL-1p7TZPh}!mjg{Jub0h#{SmowI}H1b&%VqM0-5wU>1osZH69s=z=REJ z5xkGXHV!1u zL3p;hP|SgI*YTMGub%~#(CrnCog={KVM>JgQt%SE=^$=zR0qs~M*A8L0+FlFa~o=$ zjkA_m5FcZSb{d^x)3L{n$OrvryqR1EI1(5eb8I#2m_z2z#u@bq*DMfn#3_z?-oafm zf!ev8Dl^DZM~}1mbpt8mDEnv=tMq3F=~Qo~P2;k#wu=174>?#9OV(uB zu-!rl;l!%N$ju-tQ;$Nxgr==>90Ez+-h-Hm5`sz1-0|>kS8Ewo7^Jd261a@V-Prj*TF85d;r$1&h)! zI_Hxt+8BwMO6#OP=`1GZaq`LB+!6bC7^}t2&Cg@v4qs)f2PNT&?TRtPBVSd!g&$DY z*B;{awO`olZ13LrwA{b@IablTgZp%R8207acph>R9{;xHq`W!Y=Q@N}+j4T=u-~Fh zOt4ZdK(&SpbrZk6?uF(zjt_gSX-t^F)=0L85_OANQ%e3v}u9(;C| z8)1<1keV=^Sy9Mf(k>(N31>d7A3wxB+I76G3l;}>ZyYP@jcoS$8|Qv5 z3#!jBT^}U>wjBAp11Fa}!Q_%TOk{o&6H6{)m8WxOPGEICyaF7*E$|Ht*S~mx74;tZ z>u*=qvGUV~-GR0H+WjxTnc8$5@ISZoS^6LtMsW`z-J6vQ9QxCVV~;w-YAg$I9|W-A-H?E3Pq{ zjH6!lT$X+IOWYV=#;Pn%)iV#vSuf5pne4MpvV0Urmi0DVcAnSE?QN_mihC1(_`^5L zkKegc-g)C<*}#P6C!F+*Ka&6HgKOp2AAVlGxOI<#VzQY}dKRCKV?KNT!i?ESMjPQ+ zmYgz`Dc3fJmF}PW$XK2|T}SS)%H8HGnDFzX^5)xrg;lx!6gP!_jGIHR<29OR?NtkW z-7VnQc(j7u9`X{}&HwC+G$x?qwE&M-m&?Nk*UGKy|5QHv=s%X<{qlduN?Dub0A68x zWML6EK8KSf2W=;5|D7DNoX;8q`k0&QR>4kKTXD-Ei7>c$V=UY0DQU|1AGc&>ZIs-U zrf&$haE;w0Lvb2ja^h5`OT}%nAQYE;+OfH4iQa8`esNq$wX`AV;EoKRmolY-mwZ|j5l=11;@k|t|J@x#|e9b*DZMLEx8~2u_mp?AN8^nlQ29|%bS|^$2vDr&Ak$C|Cdyg~jE(jYn9wHM8*MNXjX|P6E@rh{bH9@kA4y17;f`2u%N(uBvOn ztVnO0e*YXy$O!@-JjMXGjtt}EN&M8remWhYvVsh?^%|eGjRM>2IKTy!YcU#7_8M ze~`ILm*<=-gl0__zPfZh*Q|LhGRE3s7X#Z6&YB3#jEdQKIrCb(gb#DD%gWRl?@;uX zE(kR?gSHsF>fRV^FQDxgUo}Chn6FF4R>azgbs2j zf|dcn7*d!IBG2cod{Nez2Eo*d?A(*1xtEXD7>zhLm44_!-e@lb8JfI0t_9#~;{u0V zC@#XpB_2DX@?`*Zgdzkm~?iS_hGOi6er`&Ei9CIzRDJ_5#z)>+?;X4Oa_Ua zoZ#T(ytUQ)n3#9F+`o4NuhaZZ`Q*dDqZ6WD%8e88I4RF2D&n=a2k{n%I&OB`B6h)8 z3R>4UeU^zIwt3iRi27Pk9yJcKtl^S1i#0A5lCBz8z__~NnmSq3Sm?j8>za~`rxh(m zF$ROl+#nTO0H%@+#bFmdnug6E##O_RNd|Ykjs=h(9@ z<`B-wx8MC=%9S_&8V_Us0p4PA3HM~rm-)q$Hi6bo(Kt_eji}=X7gdg5{^RMqzIG4y zV{c+*%iHDN@<-*1>pw5oKKk2o@Ams;Zt)eI`&>=$=+v0&xzJw|rdoXa6wL3Fr(c#w zSRoJ3v*XQXN6NqXQ~VC`)&-la_XPK?Ha52~LGN+-hxe|RpTB%Nx7OHKbb$dzFdCs zn``9;COEHSA*$mij$l$A9*odu^LH(A`|W+94A=y{rKKf|zx*u?-`_BzvP@rU=q<~i z*Sq+>3a;J6Fy?Ps8}|~^7_Z{#`6Yjm<1Efuu{^KOS^hF7U%U1M7qBn!W%}^pgXQ$` z`EqvYc)5TT^|*xcG0ZmR((@~;SP=@ZyZ!w3{c`=ropR^?BRoZeC%mxye4bsrTfaGm zT%Kk>O`dIcTb{)=OE<<(Jhwdd#yGMpm&G~8Bi-{H$9zoV8N+5-E>Gi-d>WQ=Zpu?% zAG4o^5w<^0^PO=$W;Z@$GlrEc`%oLROywRw3oD!csCcqG%W|2R>~ogCjH4Z~Jy*{s z9FKYHxu~1voBHf4+xK=ECp#Xs^?1p3%-obs!<(PbZ{hW{dEch74FbhJ~6$zatkZw-N36A_{8Q1<<8B2!m4@K>{ScvYqyaP z9@(ErOX@pQ#!Lmot>v#NLF)C9(N&X zJ61jIC$?xzWp#l3QcRERSCd&>jf9Mfhf=p=DN9)&+xS!$CWT>Q8drPdN_uTIy)pbZ zTXyWkSv*~R1~+a`mkU>ZST5qkNTEd{0PaF^!UmZuB^w& zdN|gtPc#+kx(f1wiPhJ6yimse8`-RXp|{oZFv*(B+;45;HN?0+*{XV+80_zWaf%p9 zgW)h1qxq80Fv|Y3XwV)f^Kf}SadVlCvS?pB8-i4>3@$4d`rc)hJYkiq>_Qzx8F4aW zy;fY2RrR_LA?0#mW)r)F@L#Nf&)1IZ808?d&l%%#tbF7+78Rh96X4+VsyT_fVx;CfA=oIb%}eM&CWYn-TNu6`I>NCKO!5y1*u zjn}LFo@$!p&0ouQ#~IK7Tw+EqCVGJxYMr&tx~!-eHU!N{$GhTQY( zle9=~G}uK61t%Ql7BY$xk5khc3X#ZP90%jO>I`fq7K`y#r4^htaDH$AvD!kOo2u~K z9>Bz1WwP&%Mn{71(Z*o|dTv{tr9ntEPXdo8&cP(l4CYq?HNbe;BLl5ac2qx$w)Gbz z5{5;sf=-;deJ+Cpz{X=iMBffv@4jOBl=o;0t~urpV#Z))sC=xD+JdZ22rI)jp-UCF z2Sk~Cxx_eYP8$=oLT0JTZp}R;CXC#e0$wwaM5uc{T`{Q+i3Udo7^m-y%5>OOEXxTb z+bTm26%b@qtWxNDGu92uo$MjWIF;qypcO%>yd`8MmK9MRqp4&9wc>V>@4Zi9qxO>q zhVH>XSG>$u+2S>gxbJcZzcb8XmAu1PG4Jq^BeohBe@o!XaYd8TV)b=vYl>lg?GYZl zypI*Qmdo<3-`hXF#&+$o%UUa5q7w4LiFsT#kN=nl|8Y1qJo=84QuBn=U9}Q4WKuuW zLZ+WdR4pe^ejYWk`n^fQNeX~Ng+rKQ$Ht8rg^xa_5F4L#kZHJOb@S=%RjVBXZ?I>) z8sC*uB;;3_%H+?mc_*F8+{k#@rfTR0)jz6kHZZQNHsKiHp?wah5_p_rG=z`IO*~ZS z+^qrB0yMba@yy6iJ-kuw-oiavtg3hC*3WIC?c%1I}DCWeztPU1J(rBjRcd+mArcFVV-@weQ^m}v4~Wdkef@i*J$ za_z=#Oz2yH4%e9d6`@|z-ro;5E0I z_;mWzaV!CP3KR8C*!#IpVKNG@S9c$*mQSwTF4t}@mj|ocHo@X4e#pePX-uNP^6W48 z#cx{N)8sjiD({4yrJtn}e?E@kcarsb*0pI2_ZaDQl#TF`>p084*Y$OBWEsocrui~u z?6O^!vz&FgoN-y6%Na9PCYIYyF^+ML$+C~E&t+jK%iFXVdK_)0VKZM}o)&|+dVTL# zxG`SIgz34c-wj`np`AEc9^107Y@h8UACr+Y%g203HpU|z{{Y7Sc9Vj9e2V?#v3We9 zcD%fH`D}Uh;*z}%e0^i9Jj62ccOO13*Kgh{H}BlXE5RRPLK$9BVSfbs6By*kZId6x zpgNDjN;a!K**@d4uQ7~dy^Va19s9V5$$qjv%bkm#E^*Tbx!_zEPQc;Q(nl69l=Bzg zDVMJN#GcSRz4Sdy@;PU(SU7mlSKwl2IKO#yN}Y0`-dMj^*4H1DjkO2%8U?;e;nDri z@v4&#@j8X~@fwB4@c9f+X&*t%cq$qHfzfz$GfZ_S-{syZ>iJ;#OmVq`P7pSB7mIfc zvC+5UFhR&vb`)=N`S_qbw?TCt8~w12YF}?BTj^P^o5-pF1qK6oj#svlsZO&jQ<-vY z!cwPOdp17V=O&39q0R(&E#*PieOVJ%QBfRiN8@d*9+mkGUW-eySO+9b_5*Q_*HP2= zeva@aC{}R8xxm+~Zedjb-W27kZZ?*tn|~uUocMYOs~?=b@K!m0>HGNiHeP-EI&S1% z#;d)L+7B`O@qu}=Yz_K_lk_-24{d+Ch3f++>~V!XUMv7q$E)S65A0oGV#@6o@z%3` zZuTT1BbDr2XregmU($~~nZZG5AixXcad(l`&7m9{0FK#w&?Y_Up^tcFDaB6ju_ zZ{`~#bs-uk88p@|>yv~|e3@$iGadlhDC1)M;HcrT5JYh>UFemI3Lmu5V=3$78(Igf+Q|h{vXWF+0?9QypOu zhbD}q7(I$%63ekmf0m1|ZSlFlbhRyV;GEnm-kLADkz1MID3SG%DoNiuFO=dkKb{eT zN5a(Cl+hx=4c6~G^^R1Vt-aY0Wvlv$feP?796#TqTH8)PwWlH|=XGMy%Z_~XZ`Teh zJA*l{7ZO?FV;>W zBjjNy5JtN_FN!C#K&IS~12KupKQHH=(D}WpRz5Kwy^B=DsP2MxjLiUTZQl3Foz+ZNLQ59EOwRs}=!cf7iB& zi!zOw+7JuETt*;X-pig`!Rww;#dz^ChbYGU)0sEN(iO!nW|xYm8kj{3aB}>)U2Q+0 zl^?O>rdN#t7;D50kZjCTw81%Dll7r?D19)-p`#jb#pb+Oxk74Kt*MAhd>|l)ZlR34Yr<{@+4F9Ak+BJI7=ui zDkc&*1&*+%od4DFk`vRHGii>4a>Zg8R~m14V_92^!2L_!A2mUb@@zjy!mJo+s5qN@ zN^Ki*w#-OBd30yI53`Lol6?8)HpXfxXU_j&Svu#hyj?nv2Qkn601sK?LCl3y_NEiY z!0u!yEew5TG536H0q?EwEhl%%gS)>kpMUnB%Z=;5!bH7~aR28n^c%P*hE?@&Nw5{t zHE}mF=6E%xvh3SlW{!EkYJGj%7Ew8KY5|k<@SE*f{APRVs7=&cT$n3&@2}eLwI6?W zv#f4BE$f?nTf~+?|??c2}5eg?j+8Q|wRCoJp!J$^VS3wX^L=dHYU z3eEjHj7ndGpnCwrbR))%Eg!{oM!U zqfc&>JInX6OcbAlJ5(0t4%y7()KqJZ{Hoo%Y2$9%BOdXyKFxJ@?1sf3I2%XL=isy_dov+e_a0jr*C7j@`-W_U+g~p{7$*G{16lK9$@vn zhq%e;HxdrmkBt11=j|1D_LbX1{WM)KS6%k3t1Lg$_%b&0s7$+ATw}WIYnq-Id~HS^ z@!EtZ>$apc=78fi%a?^K<^Ak0{@3!_TYp*3;?=gkS{|2{#w$*214=*IqVAub{sr3o z@k{x$^4Z7#FJ5u-|Ci-Ezc2UiyjLFH{}_|z+5Qm;?SdXamjK z$F<{=Y|PGNc3TQ?UH`ngY@Rmc0{%qS>~qrRUZ5CvBjfCnPH8S z<3oJpvCZhC@#y=Bj;3lr=amT_XRG@hZxtHVqSNh!C*tDne z?A5m%RV1_>Y?^~wc7I|dcWfCe8IQFMvV@F94=Kj9R|e3~8_&Y{y zgF{}kZ!tI*QLb*+OmkyG?C31)!@Y>(NCF4w`tC6YzxfgY=|UbN!zNiO8h`%%(6JOV zQ^AAa+@DL$D0UhaevUzC1H$brjsUdiJO`|kGyCY{rhC~b61sI4k_bVK%Q$8#7sd2s zvUVMj6>(}E+h82#zzkae)I{Pe7a~aVun1of9}fweF)BA+wLqFU236*%lqgu-z<6Af zVDXd9&L`%HFe7m$Vc2wTRu8ISJ{@2<6qyoYWjAbW5ZC~VGReb7be9g)3^u}KW&H5t z^%g^ovjtE&hH+J~=Lt-ii=K=XJiKYF5UW)Z*gY52+c*G|jSUA`Of??LNGxhVu+vQvaHQ#_%pR{+MQjsXDtpX)c)()DaxD-P66<|_T*3_sT002M$ zNklD1-MVjtsijAuet;s2>!;*P)uRaT<;Rc?js z*aY;)&D+RQ&WAar7HSJYa00PaD-Y6zO*-U?fRtyAD(7L%*|=BR)rOi^=@drr!w3j*=4qWz{{h0m+KDz*8SU6M(pq8? zA!XQHKPh+Z_};S}$)jl$%~2HxCl1R0ATDbVm<$$JbLRxyyR^2z9C_bTLpe9LFFtdl zZU^p^8_8i{F~FSe+nCXaA)9aRAu0gNP5_NVkCIj%tr%PqGJH09|1nm`A}-@F7s>-G zjLM^^F_?dEqv=wR@;b?B#Kz~PWEA|8m>!kBjbh-Deur67+X6+h7K{r`7GBV1|L()w zzk&&Qhw@#`u6SpWvitZnWbAUsP|buV4}nUSoUx z1N{3f9>V+#6NVn!Yi>DtPx&yzmuPnY?VrO0#d)qKhzHvaaDt+x5N%K6z?&4g-9`Io z%gy>2Mb_%+(bK3Ot6Tya(Kyvfij956t&I_@j_fMwq)sZ)cYfq!niuI61M6euNyjc3 z!)8vEqp8zqAV&jg8yZaZ^5Ysc+FR9Xtt7nJS8?*uT*O$(f>c&a6jh#``;LAUgBv=L zUoUuF;zNp0Fjg^kF(L2d>9;XK?J}RGn@yH(RIE1fU-1rgs3Mc1TZGX3)ffvibo+jw+#?|0UeHe_% zcEj~`V0QYlwUhqqh-o*R|J-ry;VZ!&t*(`O4_5HX+cjHFYHb}$g|6`n_fz~xi!an)<3%sGRqbM1vOG2G zg^E3AU(e*zbd+=J^DWz*^gV7C7j@#3-88?_zs$$Jt!&G*_6pnUr^)x47xHB8uZnl4 zIOJzq+okVg%k^14`gYC2_Bz>)#V`#kxolMDv5#4pUY>1O7EW^Exnw-QSvqj>)M1YB${G!Q>X+d?~LyvnTu(7B7~?<9sE;Yj`!n zWlYL@r(AyHPwh1c^NVNj_x-kj$|zbBZ)wDRa?xqb7!a`ltHFAwg0 zS{|?5C|jHN?cet13jE?`657wFlR4Q0A4F&#dD{(}#@l3l4h9&)T4=2-qeR2VWh=EE zBLt@s*#J>1x=}Xdk&`ABt{T=Ln`AE~F&nSkB?9RwvN6V-+W!ugzR!W;q+aDlJDCgd z5aW1ZhJM0%qspbBiEFYfK9d>$*p}On%fgPyq|?!3_nO+I5CGXgCclK?(I)BP2M2rI zEmm-Q!kd>b=|nyL_;C2hVp&`~iSzvo&hc~Q^jW;d8msFqoqrSc^EfwgMZ54-UrSJrE*HYIxLj*pK)Pl*GUlb$0mN)q#Sn!QTUiwfh?irk@ZDxIJc5+bmT#qXMl{V*OmPB4gi zX?RH>6qB&xQIME2mgTQ$hBU1v7PoS_SiFhO?Vly}5)uS9W>9vPX4m+0NxmfW-jQ?| zw{zKeL~*h`VU>+VyQiZQs4H_8#%S940j-%LG9EoilO|n!f>CU3GClGjMKJIjHxYNh z5Y|E5ec;={2!V|;-4r7w$4)YLP{h*vpPakb5Ms?N9!+b3V}xvSw~-t*7NI60GJ*nx zy@?z8KAB7GG(jdIj&?1zN*AkT4Ts{m6MNJ%rOJ!XOy&3I)P{; z6>8!Lpv4sfhu+} z>$%T5w-1C@h*u1zaP8nMCRd=HEh31DxoAM(h*!DV zig}uxXO0vzZNk(%tgEn95M^r5|HL?h?lM0&sX; zE$;1d^H4G$WH^ucZ*v{5sb0BR?k|5(?k?l?)wi#f$B%Gtcl8F&`DHvXzYgD6#Sd>@ zsgrw+b$`c(~M-E})?_pa^<>b*hTUGDXi)ZaO-L-YBAa(y?x%+6{UV-~~ zZPR|oSi|I&BXhhq`I~?Cv$~&w{S549;G3F(C!C{#-}yL4h1a^{_}%sxUiW>1Z#%;T z4UGJ}Ao%9^*}Ce1+{+;hWDM;dfhpTpl{;6O-%T1K-rf?#J~~Wnf~0o^DR{ ze|YV-RkZJI#Yv7{w_DyGm;K~&kL&AW*euKC-gYruCQfIo^zw zUNZgw$;o<~+x`P(dA5aDJf1D@ym_ho(T`p$r|@J3v7g_%XUm1&xP8AY-(4+_9&d0- zDF$MCE>kV;?E6K^{9`fF?@k!$ zWv@DIxk>Id=c7zG=~-8q*j}Gy;^I*@!e`yIvV56s++;;a@Oc1vnkx&(PGLgcpOo{L ze^xHyY0Gn1G4D7g>G5^IOkJJm0tf9(_MF-cTTWbhvWeH&-pAy;&&wA#-YXyf_WvyJ z{o;Q!3%+^*-_gN`o1J(vkWX8Z$zxE|WR89scyXCfJo3gBZ&>j^ZPvD^vaI|NL%hiX zFMHb7+>kS_Qx;d^mXCZKa=6A~up+#sSh8Kt;7~qZ%WWY-ZKi+!-*X{r>8(BzgY-Fm zVxu>Q{30D3om4z-+j6H>@tPi<*dOtq#Vx*w&#LNX%A#z26Ikb9r5RZpYM;50^xBs+U zdi6);7$)u=nLkzzVFdy2OXh~9XeaY-ZAS+2gUpw>IcxvE#AH272>aZzd48NyB6HTg z&m2oDPblgHMKzL)@2&JJ7C*|K6ZN>7-WFC@-NIzOC-@=h8E%SlawWH6U!xu_Tt`RR zk^>bZj=}k8V-J8#1TPx<%~a!zSZ}ZCxRYBGg#RQXH8JKY)Pr*&VTWAaT8{-~5rs?A zIyK@Aa>qn`5s%tS2+JXEZG_Ct(MIzT@merKGjQdAWowcbuoudGgh1^gs`6S8lK^5x zLOeEBMPcSPX@;5@&2T1KD+E|=U{2Ip<&f6SS#*22}g33O<_Ba#iOY? ztZ^{bmNVshkOOO@_$K)oi%s?d#D6DsjAs+tWUuI*4K)VGEfVKZx`>)wcj@%Kxcc+Q zEJ##cC03I$4(OA3!b{RuD#*M&M6;dA3WR2ogoF5^-MkWJGcia;j$LmGEXA5_>0jeU zt1VmKMJycEYD9>#xvOeG?Ho{IeSEr!$M4`TF*ux?$Y>cIL~#&<)U>E^$wQbJ20Q4@ z19xFpk)&7dtIkZc1Hk_lq1t_+1e@m9G~SOEuj{i9mK~*3wVGv{Rc_H1uV@ct$gL$2 zuQ4EgYnJSd!hKnugTnjNE497ECVdnUr<2i_l~P-YX|7mu2JtY?0e{48u^0$D#vcZ? z7-Gf1cnPUo3Sm{|k(RpF zscw2T22L~tX_Aaa?GR3BsK3LGXuz~9$d6`8}eI?F4ATOUZ#Gm(0Om*K z`lo#D<;P|1G2YVi5^p+p?0y&~DIPg|1e0MfSurLmdjIBYF&bM>67^C_faPBG+9Tef zta8g{3F69r??xOW92>U5zK$6PGzMj9SQ`yZqSCI9{y{eiwA$v>Ix2UK?&oNjYNdEr4NJrlB9?DD1K<`_FChDCI51nE|FO&0*Y1@E_dms(QGQWw-T23H_tvj4VfO}B)5LWE4}7y9zqALsIho8S z|0&w<`jPqL{q$|ztFnhlF|l#!)UootH!hby{K2c`^7)gv?z~cN-+gGm+1|YKu-w9L zws#+_T`!Yuj#skYcFb>&$^5z8+dfY{^C`wLo*YxQ$ynKEtn|Ij#+FZn`4W?1 z-hSg^dF!?FxQ-pOf2WQw+6sA}USBT%@T;p>C2s?De{vAX1|@DL;JyGk$Nq?=|Ljk3 zDHa|TV~>+OW3&BNrSJX9pZI&9xt_62&XuOH>en`&bbNx<2aKkpvS0say|S`$tK9hPKbNb&|9^1P z_A@+Lc?W(T0>_nF9nV<&hK+6mRm!d)efHWxqNy5 z#YJ3mFG`oSplp1iI?f1m1fagKY$P()rIR=7J38WqeyQgGlOfl^k?q}105>Cb+x&;?6Ia!HSzmh_O|$czuQ-}tIEJUKuavic_!pS4`wpJA zzJe7N@aiM{aB&b5pFQ_AX=ZLRO#H=6J)f+{)%3W!AZ`-dwWH3^^B*~AWZ8}+YfKv1 zpifLPV;6C7LI&(u<0}cDVnQA#=V?{FElk?uiqo8^=e~eMp^mdOrVM~Ob>1$y4_`J$ zP=-Gr51}V9?HGeWxigRmsPS^0U1aEF<7$_pTnjFzeXxe0@tb?`3!aYIGSjl`o0o`V zyK8Qs6vq3W0!X4MZtLulQJptIvv^EK8_CrVl+n)QO-ZqBuB!-%VH4N>>NGHwqe5$e zstT`C6_^(`J$NY=S2aSxJHO`7G9#R*W*j5RK*%sjbMC3Gz7uK0VCv8tQ{BZlO(sTb z`o?KqB}-Vgskv<>jgLHblqBVjb+eCiK=kHoGNH*Ui;k}6W~XJ;(4PrB1mJy8F=y}djrLy@|BsPfiBQ}MdpAKLv2zSX z?<<#a3}`%L^Hv@@(3o+ShGNONvdo#AP1a48IUH&%bn7Ad_)s~Si8r^W+9%wwn2D1{ z{A1&rx)C#PFBN`e7h4}g!wJ!jwC1L^u$zRjjDD{ffP%;UFH! zwwv(`0h?bRl_-~e*Y->+b81K4C*yCncw@y8OlaY&Z-4sZE4HfM0{MrU>@JzZdKjcU8O-*G~FM z)D?EJqkan)K|Yy**%3BV`PGX{nACz<%eY9MK7}Q%f#Ey9uim_e*Wa#|hmY3mqRCf6 z@{h2&!w2mNj?VRSR~0jl{iYj1!u0xFm%VJVt}hEqd5<6SmGNUX)A%#@7_aQ6>GmSe z*j$#6ukyGP|CpXKZaF7?OuNDKHoK{dm)L3k$1svjvzg_4nl0r$-!xf{cMQ|}m(OfB zhUxwHm=~$%7&1@xIjt?S&DhMrCEz*Hg_Ao@oH$la9$PG@jvc{kyRl;4(n))@EmuZ* zxVll6@jBb9pWiFL{p7ZNq2trRTs4nRpUv8`Z^fy&vYl|kP?yWX&axFowmgbgx-om{ zgzqurkW754XIrz!=fJZsxx(F-m}G;crH?P+X3Y6=^7I?!{FOf{XD|G)oH_eeIeP3o zZqOXJrzwq--!1H%wz~4tFm)29@=!+t^_k**_Jw!IH*F zukjlI%hg~kXJ>9$&!Osr!3VC7S-d)id#@(DN|54qbqk>SgEdzQP)FNNd-<2tsaJE1|DKg2O3oRaP8HT}6TGp@~we{>N1ht`a$UfY}x=CZYuOUdkgl zDXl1L&z&J53VL&$db&48cADR6Gl6OXp?j>$Lu=Qw7K8OyrErYw#YeTva9y^7G^h*- zDw7ICichk9J;}V6@ zMumsTlxZXdX?6~<`xF~7#%Mey#f~VRLi}{m#*!l?>5tMPx8$&@WEtCXZ(1V`9OHv= z^Xhg^OHQ=VDz%*c4g(F})r(o#I!CMBlVHAt~g=*g-aQZf|**Sm~H`@fVB3c^C+J2(_{~p|O7aAKIAtg<2yJGWPs^*4_sE#KDVA=@U z@xVn~W|7QQG_^1iI(_WH6!~?vm>am2=+5KFlr_ddFi!911k1EYOsQDSD60Vnz&@9v zoW>^^U0RJOYUZrOd0{P5z_c>MDj;(DrjF0C8+zP+Td&7btW)*d<;2+HrULJj;zfb7a^B(R4G$6)F!Mh+@x`t4UjT4W7ab;%962K zojcTGqd1_o9skyAMNoLy;&EX#!Au4kD%4KXDvr!9i!5U!a+wmTlt(J%TFa9c9Jb;ghXNRyV++qBC#a7JYy&0}< z1+v^IBgaEdeDI7b=54Lv0nCTxAtvYDTfU0>J)dGC&L?GaV>S7H`HCFt{Kv_8oS1jW z{v9fZ@DLg&Hrj?} zToPwjDksuD_P(B=si=rMLRL+c9fz586DIx*(9VMzENk+?Cb&_SrC=&jxTfpk$i5Y) z;-XR3HV1icwwOF|0IOvlJ8=s4fZr~smfpd?H_H;jCc>@y#|o=k)oxr-Xcq!B6o}DJQo)!SA+5vEtOLm(G;4Txklw*)E;HN~c&n zh4&h-fAIjT>0yx-y#96>6ZTf|`dg$E6N+E4SKoe%Q?%c-{S549;2W3$zQNJ&vp>aa zYv;>JES|#G*`CKlWBzW-GA~jqxK=ISTP-(m4g2u3<+8T9UGQ;G<}u%dud?0W_A~I$ zm4T~QuYP?K^z4u5Xvx?4MQ4_utYauSj%i=OvQ5VCwNs|?@0I84#P21>F>IExtZgrO z<9F&!d>LO~$HY4I zw*1uS-x-Q$FR{pm?HIEgYcFwQb{VU@^v7)1>&E=|m|Pc^?6Uu9@W<;@ zytAg4FUzIZ{uC?cy^9+(uVG@|xiY_a5_4x_ zbt(2m+^Vv8!{IBq>&!_uoIts@avM)h-YgFvTq~b_^nYX3yuU49-24zfy07E&gHHn< zMZ8#n@42@$);Tb^L8O~jtSUy9XKk{vRxywK4}IL{Z^)7S4;8g0|KfLr$C_p47fLcx z9<8Z7eL=S3ALwZup;>=VkPQWuOX8G6wqHJk%f6_qV@mb_xQ#3(^CZs9qkN*a*zW#AJedZFb85berlhy|#r^-eQOhWKlq-SjI z%gw?MlG^&KpMKv^p@VK&GHPAI=^TStW%6 zetD2D$7`<~QR_%(6t^Ybk>*PcrUL`FSb5%LF7a2=E^g_`;IxUW2I8nX4)_p6=*%u` zske}mkJbycX-49zxvfNy5T-iH%81$(EFP2v;$Y%r7D_ubyP>5pip9>`fQZY-KaeEm zKT+rine4OPd@2~+#B}?7F151aNKL6C739NWtin`K&J`C`<{tw4UkYWTHN{v$orap5 z%Z2efNZpw2G-)~OJf9*CB2+_S*GX>|ubH^#5XMyx6SbQc0i{!VGP;z#MQI>{$NH{% zBN;8QiO32`n2JNN6mIT3Awyib%>#N=Y#fc2K-@nahD_A^@zP zQr?mYbv2(!K0IrpUDcOGHqpi!N5r55V+n>@@V;?`pHGfZ+|Kp^<>IqF}AveE1~=~?4hl?iXL#~ekMY2KSw1Qb!LRtjH>o= zX9_f8m=lv$-{zN}{SYe#%d2t^xnhu>!TD=^TYo#Nk$ADj(qw8A0acBHx_}Y1K5cSg z^XqRdhQ?w(8J}9tHml1+X@_cJdS^EOHeeao0kGgd3gwuV5xk9CRh-d$su8)3p|*pl zvYs;4v%d8PwbF7A zc$Fe--t=-IQzjYE9*2o{hB?+~c`}NjCS&{kxx+8`s{)3VPSEI@Y}gd`U|j zC;Z2i^5!sck^c_jZ(YhbCT#4bw9Fb3gA~|>HVl(}9cN=Q5=fp0%7A#-|5E83@wzZh z=3_&|ve?B@9~OjW^CA6!h;e8vW<{1O)*()$$yv_ffDBk&<8xdIBk|Bj0}Nwuw6Hl= zk49`^KZpylaIA2%Ca|tDj-4rCd);!Zxjad30qlVuEylf=XHOrOC%C8n$}4l_a8VG`dREIE4Z=H2q?^;_lsqsRWl9sbxqxWkH6WBw^q+gQ%+8_PqFmyI~`n30ah zv90IN9AkLNdOOK=6p!jW=5}TIUd!U{`EpFTyjOnVcj8bS!uRn={yfLMa_3lcc_-iU z)5k8k?4_4nb?Ng_7|MjBOy!K-OF47w#>W_j{xXm1JicgrPlyqPL($4QGFVoxM`bgr|EnCjPLzX*UP5y_Hs@#c!r5F8ynm9 z%F5H2RCD19Us?G__Bva>(%|UPv-Xta!9%!ti{w97Cu$eYg zY9DXZ`HisF_G%5gd?Sy!$$cjrBju(WYAg@zD2UDFjvNU~Fgw~Er= zWWn9e1J4)znb(RVLA03unbXSIqA^)bc9<|)nCiTDGmq8%s2t}_RPE55SO(~q8(qk% z6`a`uqms5bDmu%X>;uY!iNcTE$-L?1%@Izm9C)sbQv3kOf}&<+D0)@F;;VgzhVeHS z=^LuTO-FgPsS=l2djar#J41wJB2~O`0_YO4Uc9I*rx|ErjcoLX{ef(kXKXSTYo@G zy*Q%U@|YBap;{-oQiVPdLT*%tHZ}vQQf0=)+F)=+Y!OpaK>!2e?&38HG{UsW6>U8S z&_K@dQ%zlAGUFsk$UGiM6Bn$Bu+6TyttugoSt!;l3+MpZdcxWoX0mpRw4bU7(r815 zY1~n!-jaS09Rg4^l-G1*D;Y8*fDFg7wyH-V@=aA03#TI#&8HLA^Cif$Ek)c+hMnci zMJ^u!Tl{+>WRnV>;h1(JtXI;Y&D@IF(QWv5nB48!5IsfLq`Hi3{Y;uVkEI>{i# z4hoiG+~(6o#-4e@V0eh2?Q=y9khA2+;`D%q_NsZ9R;%uFW4m(A-OwUxATyF+8u6>V z)x;`}-5$jW7vc(UU`Tz-8pk4e+s9>U%`Dkyw3Is?$y1-n@?iI1V+5#*H+C# zZa4|aa)88GpT$5@&!ujN!GZ)xml%9YXa`v8RPLWRTUG3 z7V`*Ejy0E!UryC9P)ihEhTK#S0Xa75vTPz)C6NO=x&3eKy=jvjNpdX~jjga0s<5w( zUZ+qmECvvz03GreRxm5xDH}_26u;S?v;oBjF#{-nv%Q05xxc(}7ZX5X#?J>ByuGoG&yT#% z{=_DL;=@t>d&o3@JMx_jyvz)IB?I&rpnVyBhDhxLWBLF|(=l15q@LR>UzsPDY1?zC zE?(8geDqwj>9?)yd+b!OR$N=@W^pKv%+be`bWE#Jmaz=?5qeLz4aN&AK|UxN!iV1(Ra_m1k2ez=f;mIEe9!{u^6ZL z&`17uL#qQOT4Bs*l}A357MVj|@r;N1Boj<#ii2|6<+`w$&&#qFA{`l@xgE#Yc_dYB zi-)OxBwNjI#ICp$i#%p|AHQ_u#T#Il$hlX0KIW?WCPa4GOjN-*?cl%H(07;H5iaFb zD!EWS_hW4r!y`GiEHgpuXzz&Dx3uB)Y!k0T&ZcL}-rArqAH0_BU&Zp?eFw0_?Gao< zE|lXZF8C4UMa-&q088T9EDfBYAu?k}>m#<71zeaokdL$KVMe{D$c^Qh=YpLNo>9YE zf@&{S#wIbqj2-#h!uH9SHwpaD2j|rRdtCY!eQ57ZFz@Y}L+4JEHO5wGXq{&!!RyBD z(0b62iJM(>y$E@nS8)wCJ$9C_z<6va0wZ#SJT!J^X!a3*@*hDNJMHz}WH0mZS%X!5 z;ux(lqbMhgU#C1~MedORRAWKG8)ud*GVW|;%*;`cm=^WQRU2c-8bQ-D-!QJ~mYE6; zzpS@>Di(L#a>ROYRL<6??B}sf*Tf0Eeq5SjH^yZ$;B!kNJ+4A~`RU94hr*+V8e-*= zV8!Dpa+{h37r9Lrk z?phF4&t)+VYvhqP#0JEsHdoo)H^ zZcuB7fo9sSIh877OqkDZn8D6nKn^U=pBA6{Xy+9C zqcHaw3%oChDNw-=jBPHV}&HGtmUE}rzvD?dIt$w+X#WZ#uLBHtf5ws zm4jGQ)=VQcu(FlNV$7h(H{}D20o#tRN#|;$CW{Wtw#3ys3(dskRp~l zT@rg!1bJ0N-2!k+zot>(TYJs09+=SaV91_UdgQYxd<1N}5E7cmOOM~PlLo+>9aS#ZnVvDiaS`(_70#G`(;I&luCsUTJ! z?4pl|wGCWqo|UKwfuG+|4gRo_srk$@6Es3mLF@#T-Z0!z5?d;oWN@Mi#AC-5D*C87 zsj?{8A-qR?3bVSc1<$!++l7I4yYM5Z4K~}uC0a2+Z*C6vbueQd9;n)l0eZV|f9Ewm zco~E8IIAKzHo<8b*=zW&g9nwcl zkD|>-#_VYBJ0e!)x0bVjOsbEWJD*}uerUx_a+ambGRlV=eCain+zv2mLe__@z6S{- zLt;&qjiltUH^{nBkCS-`x8r#J0eU&Ujx?*DtIYcy_?-_c3T$pX zEk{q_;mc!hV5NjNFi7tMyJyS?x(^>dQw|(Dj>}0rc&#rL2#0O<_XAJLlPBa_wHfu6 z?tWRWU;U?Y>GOXq*RTA%EH8bIdoOF~0=^b~)=!(+b5@@`SKD4)Bc4{gN42uDiHQpK zl%q!umeVH|;y2qv}25U%>X}la}kIqyl;;CSuHN_t@ke{cpBkZ_9SPe;YGk1N4p`KW?9-@|WRR$>4{ajMtao zY&WLQGGTiz$!8tqbBuE<+iaK1z0LE+B_46B-p4tOf0|r8JZA2mx5qt?Ui{N+r|Ek+ z>(e+CXKzC}ZFr&Ota=>Hd~ERmmb{%Wr%z($s`Hq+>dn*T0G`;mdSj{l<=^}gv*}?_ z#=Qpx-}vpr;2Z{GHq`$JED@ z?WdKq{Z?gh_n5KxU#72~UvWsDedXACT-LEHeU^(;dewzxJ(siXtn!#$mgREKMO}_1 zmxGTdN`L3z*OH^-%zvP|)18Ovi> z%7x!52I9DhW6UPo_hq+kzA~3|?TFhWcpCEY<_ex(+$gKK_ksH*nBnSN`Q!iizb$Wn z_s`4evp+`uI8z>IG*izRG-i6r^~4` z@07Ezzh7Q|>xX#S`Wy!EE#R7tC$p`um<;87I(tb=Nw z2Sx2){@}z!TjG`U_Ij-@Lq*S|ta39{Ts-$U(=?~)#{(?5Nxh1PQr7VI7!Rf}2HJW+ zJYu7P3%jzbogNsvFq{z>g$d2vWrWmrniE(WaRaboE7-AA zQxq=DKhJae(#Dv)P97nl7sNy^Peps*Af7VIo%ykvQ;W-dxjn&E9C){IXDKY_$oonW z1#3sR!r`?bj))jkb4Gn?P-O^*){e0EesJCz4-D$S0OG2GIPa6n2|qQDfm14-l@>y$ zxacFv4Qowsto2cti9hpF)xx4iVE4-S2l!CC)@`c@1!1c!@iPTA1_4Q=0L)01etkA| zGcxX3RKikUH}{QNb+pj`RbCqx6R5FH)+ps$S{DBVsX^@DLMOdb`Y zk$7C0&!PEsna&fD^T=N!Z4-G^L(FqT?X~koe$g-s!|Ef(@`jCbxpaVyBw4-4pqZ4@b4eQzg$ia(+LOyxF%{TfXXh6#UCV{@pU|)$r69 z3tPki^|+I;7FB5etqpKY-yXB?`CXkd<7#G*d6bHA=pVouW6P6lEIc>2evDm2%>&|! zwpl!ti@Z+V^a(3mFj=@MM`)PYyIKLTW=I#u-rMVPY3qOt3rbR0n-8>(v8BB5{9t|1 z4>AMT&mRczUv`5aqLK$Pdjo<*O<*-d=02Cnfx5oK@wHZc<$Nhmj7#P4&x18N3VItN zj&0+n*8;ia-ry1AQ+&v%81eW1S!b-&hn@J%N!@`dJAvFxL*``?*?)`C+w1_wk$J_{ zBCL+wPsBn#Zsb=E#aI<22JxX{54PgSQjc&cKIpFa#+N(FXD|2fS~?0e+krj|20rUj7Ycx%#cmn)m3z zEey(g1pmx84U;hmUcrrnRSw`Gv$=g3#E1ucy&skI)}VT>va`9dPrVdXujxEsimOfS z{A7@e$sO%wS0SVxVBDAd_cn@Qg2xCOzi`!0Ed;aq49C{r!#FCuD~Na8Kbz8Gkm*1M zmwtXZ)-b94OuSyN10M>T_5_8YOq(QB-N9{yb|q!GJx$w6x|X^mr{pVo4W!w+h#Oe3SoA3Us^2R&= z3Nv%!`hshRJ#VSD5BxJ0&rO|GZ*#RQ-T9(i{O$i;{`N2byK)ERC%8OeksMshIGFel zIQejQ5weWd!{dFAGwwKW^o)Y$hBt}DX*;_uH z!7r@ySpN3N{9L(!B_@w8&g0Ab?(*Q_GDbr`EH`dH#B|D7;&6GTJjAr79N@=kmNk$g z=cJD{>%PwNR_B6ZW_-%=v{;`NyA@v_({{KsrjIfE5-!VnJnMZ7W0>vAUX*LQF>f~> z;uMp(DPv9~XMJqTce2-CmEKfbB_#{_d z96WApVrD5`GxqMnm-4x?Fu%VX#jJV9ICVRo3gb;iK7DbWPh8x2h$Xf$dncz*#*ClK zkN7kg?>G9uF|{bSQ@#{~>ito%Wt?OgJ4>IjD$8b!PZ%CEE|-OoO!fZQ_>of#0K#x< zo{xFN-Qx3|*A7;$* zc-1Sz^RJKH+{SBfsB88XSW@PjW)d?8^ebJJ4{j7m>PKRAGcT8^%{(o?vPWzYp7~WJ z)cuXUA)jShMCL~^RG}4e1j7vESo`vjgLciIG4jnmHY>3yaT7VmZB1%WnbO< zQcuwKu*ADFwP!4^L=EDKK5&~#A=d_1^LcKVhiLz>Y0bAAJ^NnmjF7Bf2P{z7Y?ipD zZE}VJ%&51)+4MLtE%nL3<`t@Nxs$zf2k-;YLOD2pw9GFYw^{X0V!+D3`b;$Dzv>8+@WJnqBlSPc{>|DwhHfo@nOgn5$ zL7m6Lrvo43y^71H^Us#SMIZ9>@>BltAF#DeQ^WC?>v_m<)ePE1xKU}XVz5;xFrIVIl&OQ)uHoXKP4+jfjm93k@Kq_IPAzj*^JQ>K)LS)7(HM#83idu4-xwmtGCa9W^TF^R}<1K6tGXU4(axlg23=+?L$5PK1Y?X)Qh*} zh*|~QxXdVt&3z`LAnQX{`%F4BoP>sW>OnCRP~Ap&SvQqH3X204Q;bZ^9dKR9Vi-B{ z^@nW2!0o4yo26L8iODj)=_?`%hQiJewyOw_$pDOD0I~t0q3W(V?n~!c#YD^f6+wSQ zNITMeil(URkB8FNB*7-88gXvLGEPOUQ*iHCO6(fJI?r=x3}ujpk+{A_B>>KCAOKvi z7ni!d^ybGZIoga&Tx3N%ec%X#zRHqaYy2a}1#>I{N~sJ(V2FrtXNzOY?sZKF!= zaAa3#Q}$H%qgkQYdvuQyb6=LmS6cZanVo~mdX?e|d@=a8h%WCVRS?Ht#G_$GV@59Vnb3wZS z)>Q7_&Z$&KappG47yV_%aF*PorLe$%kD*24=YhL>8l7lbbtqIGC2X_I0;rmdavupx zc1L2LO(GgU&xlHD+V`9CPF3q%2G;7POFZQ0WUhrUlFc-Iy_t4_&^t3xm#Z z&)x>-nPCgIo@4lK#qXsXnB9tl@@`!I5X(;9EDs;tDa%;KcID9$X3TqFvtRMM>Z`u- z9u;TJkxBZneJ|4NF5@->@;KL;L5gJ;iK#PoMHW3?7Q#dk6#a=5d`ljQc$2 z%EGaC%bV}~Dem!nfCn<)#&u{vu06Q!@CH3LyOwvMvYouQys^Gq9xh!imvB${_N`Aa zJLko+eE$oq&T^@&JiH7H3}54Gb3cybug~Xg)2nXBw>#!`6ZfF_JIDe2_G*K)@!Rd; z1ADOQ(6Ms%^f4O@^9WDq-^HXVH?i{2T`Yn7@ZoBCfZtG7*Eka&Cs^UV`&T_9JMr&i zU?&4#WdKvC<Vzs^Wu!aYI~jPk41DDS^caplEcT)08x_Kg$$C51iSrLx{thnt zt+3f{4A;x#V=FtwyOsYjUdd#aeM$GK9&=lA`9<2pA0BxjUc(6JC-^~f?4_42>)XL@g&|k3--M;K?fi1P zVa$Z*7{c5RIiB7s+w|qk!E%c^G{|+{>>gO$cphNUKIJaQGO+FXz+-Vljw{E+mD_bMM3kDe)K&V8p`c;_eOZhYH@;^zubLm+f)Alukftea46Lfr&P9GMwSJHjz*J1OO{DY0ycv|b1g(B@ zA)QL#7h6UoagdF3O6nZS+wgUz_h~jozR*&dqUy5XNqyE@WH1ZPescrVSgs$RbiPhVxu2o+t#r0(Zky80U6k!ZpR{Q%_Q0*5ut{ZCy&FHObNUp2(rcs zopWlyU?C2Ff_YTlHo>aSDO(i)wh;$AH`Kok5c!T+AaB`ow>gNc%W{+A$Zi$JjuR949n}z4`jm4#2P}{wWum=zSRhvi+4-+v!#_&Aq=Gd`r=w%_qN^Fx2 z%g*CDbdK6RDFDkC+pG;x^97MvrQCQ5gG=~r9IJ&9xUb}~CM3sF`wsl=JZR1g8*Q(H zgQ}y3>r2^AI_v-7QY!Lm(c9g!%goS~TH@ag!?_Wl?El+&6(lVMje%@@9LF};tL-jv zUd2F|oma;1QPR#(p`)E!;TVQBho%fIV-+#wb!!Y#n+g?ctJ!(`tA6ky3%cO*!6V_1 z2f8GgLTJTmLv`H($9kBXs){XCulW*S5a2%tQ*aqp4k+7)m2Kv*jP3pd92CQWESx2e z1M_f?heyspr8e0I8O4b9vdvg!VSSy;PChITmTr_AS3WLZeDe3@AqMEJu42}#Z_vi; zAir-icYC?|4`((2u@Fc6Ws$H0g6FF+c5U@NtBIuYWB9f9s!tRPZE6`6m4^&gv+GSK z&pD(@aVf6@IJgJbp?Hm@rZ_xe5QjLal#JypXT9HAyt$DXH7#Ko^K51Ih1!S${&O;p zbv&H?823SNecyAieCPc?FR#D-C*{ofALAbLo8{2_Y24dC6a)1zK)CinYaw~9pAqt7 z+~>J}_foli^Fv$m_SUtZVP%$oz~JEfc71yC*s-tKZ;Cs!>h(Ry{^4&c><@d0bZrGI zpdBsmym6*{@b0;C0>7_t$@A;C?v-m;W|F_z-ng?|?mbuq6&`-2J??LDW-H#;-`RFD zu#kFhFncq2~o7ei>YugJo{@LmA4F^>WsyZJBc# z=2ehyx9!_$uefq<$8sPXb+fQz_LR-y*h!#^@8{)FvH2s)de#eg6Y0H@1c4Ue-$CUj^Pp*vVxwB2y$)2_< zPveq)njh(>*~>3+(>R2gb(F83XPTURV}6CFzUQK>-=^WmlW%|lF=*x#U~r_M73@49b6og)h8Y? z5N2)lJ_hF9D)%sJ-o;P?Zt=waZAn zalE;nIjG_}-=tX_4{*Dh*VZcI~h$CHj6*!UV~_xaoFQP(89PRa+cyqUR)S@bxg>n5HK z+~5)YXan}hnXq1@EQF?4IbsujqzX%H1Vky@o6dK3P8tN*#HVY#M9dK9BkVWIg{4k- z#XZac?4v!z85?)?2MSsHXA!Cf+@^L6#&gYmojGOG_N=A~0G;)6Gd^()A72B!I$of#4fYRmqA+g7K$@qt-U)t20P} zAzGds7hD-u2H8k#U$muvq93LmUhl4#?AdC5{wt^_A2t z%1*Sbt2|S0z(6NoqXZ;N#Dueb{4vxNNoV~5xwR|t3Q}XV9N8BqvPV80Ka+1-y@I+^;sou7(C zzKzWD+?@&ZrFe2#?wm<{@F2$!)w5l~j=1@UgM5%jMCLo{EW?qo z$ytq0d1SnXgN@1JvzPSdUa`m*gvKAlHuxwo?lUUw!|*i9?&Eh+JQTbq2IhI5TFZ1EcuzHXUm|Ye{3VDfSHi85Wl_e#w&;7oD9fBJ7KgXJ z44XuDIWD(%O|xLBo^6$Yr+R(O=*3dCC}cB{P10EWikqHuS?4opMIjd!_u%ywnjT1T z_M7!t9v=D_hVd{C5)2H(u^)tIO*tL_`j%uW#g=75W8<{8Z!3nLNDe$Yr_Qp@*>hI; z8fn)glF$dD#tFj*!1#dT)5myi&Fw4miwDYgKKO4iSntp6VemtTPT+ye1zeXns18>3 zB(rMT@m|;_?uD&l_PpEW-ks0Ot?R!om%scE<@%+cmD^W8!F6sOOSJNO$E9!aL*&jj z$bj_<`@{;o!^86h1Cr5yZ=O3=e)0qV&6cx<-o|gX_wbu7XSw?Li<>xNCc{S?STT+7 zEqebL(C<`sGO&|@Z(#;D@DTd?2JUsQZsIrEgXIU`Jy%X2Uns|q&f8=vycd3V=~4OH zf52~r*Y1`T{INVG4Je{0d~?D;uEqGTN>$D*OPI%i8?UvOdP*bOe5JN9ITV#x9Jvc8_kF65zIkVGhb`#e~{y3r;_1yNGu|IHdu^d17hAn-21T*WM zz_PbIaw*)o{ai9-4g-uifEd}e>r~1j>s;oRgY#@AJ)cp}%lsjUGuS<2o_KDDpKR$p z$OyUn$%sVqGD+xyw94D zIY0#V14`36of;qVrj91+YA^O-x&*JG0VWv>#?GCD1gbrx9JohC^=42-MxmYIDtH7# za%_U&$YSqWVNvN&-mQ;vF@L(dz#gQ9Xbx}}o zsAd>VYkpB~I=!wO!Tq+k>N_kNX`?WVf&pN}MtxC@*aq2EVJeU=`RXZ&l)CCFpES-# z-&s=e$z3jPg{3a%xJ8i}yzGA4`(d^00{|&S)vxKj)_gg_{Wo9LrirZ7yD*`^d<|p( z5^dcPuMHjq9oVrFrnPSdRO$0Y5Y+3!fG9gRh3`hi5LRaqSLf5I2N9ddo8zR?=9*+m zhU;3}RZkJNBFJ@3qp)IYK~V3(=1X!OAvFas6LyZ1oz#6jvjt;|g@W3dvJ8u!FvQu| z&IzM%urr8ia!TN$uSOQ$hL*K7w^*wTcX%UK`ZcX92=-Wn5fk&uVYq8K~lOt>1?wg>1X<8W0a zEWn4Z^uudjfb+o&Wo?GgdfifQK{q~Eg`PYzp_OPt9yF{?Y(ZP)jd7?^tx>m9HD(al z8b_^|JYMZ614k%;lf00jBWyM!49eq-ZycCsD@R~(#a_((#-(iAGPay0kIUF{#ys3? zAA495e2+IVTl?l3X1-c2D=QB$OWv(=`^M)O40FY1&s(~C6%S@Uv^c*hJI$EKRUap{{5?CV|dAjimD(yuXWsJ}1~U9rtC{*SNyXCaz0Q@Id$#d7yV<*@VSTqe7whVF!F)6+C@6G0oN#IfY+(jwfp7K@~v|H@;~EV^smb8>mOs{ z4=jDVe5E{k@BpYMSZ0?q^Kszc^w%}J_yeJ(33%4UAS7Wg@i@=4R}_z`|P*yjl6g-vGVOYv{F zy~NGp?1PpM<|vni8RhzmJCbU(yJ z`Z7jR|J^@*QSRPfwjV3^?$y-E_z>QG56SIPO!WU%_3bntWAZFhPPP-5?51I7>E(;Z zXTfGWm9u>=OaHvb?Q#*PJjf>dlrGD%O_r%}|9B^w^IT`p(LSXunk$w#)&`fM*O zWx~vo$$lDtwih<@XW1CG=N-djj;+dDagmpQoPCpnD|X=tx%b{ai)+R?Ouc@}XdkU$ z)+h}8y@cz<&py0VZr-|EDC3eHyeY{+y*a*|LzNX@kIQvoWJ6spKdWB&{y4@fKVw+R zs2__}Iv%s0<)=?Elhl({3;@}WL3roO8{he}a_+((VL96$l!YVb%b~+3AwO0Jh{!%) zdUb1rOGiGzprD8Mub2DxE|ojCep|ly_TNxOnS*ENjLdIZMm&C*^uWbS1aPe1#b^2PgS1^lU%N}wl@+JPH=?} z$I|667S_2{|A9B5?IhsQ8&_>m*AjNtK0%vtE5TInzmXz~r2$}!zT{^46j%VL>XI)a zfKIWvor8yAwDxiaOq%Ar<$kgA$N^5oy@lltt1mi8F$ipNM?THa%^P&YZs4D-iFTVj zeDoOk)6ss@;_JW!zGr}nw}skDrei+%n#Q0m&7{Yp26tt@^7X184<0&FF1-B*<>Z-n zFp&5{Id=SY8>qJ*1NAKT_>rk)P`$`=4^q6B*uXr_@_+$xzVt0;)#D&N=CC1zJ+4ILZI4RT^wwba44)zt=TSOZ%%um&VWsCl3*w>(C$ z#m(1^>s=;ZmqkLolF3Zs%$0v&iV6ecpE>Y+x7TRILmomErO2%H?v_Qr`Z%4LfV0`q zaW!-!io~G=8A|;~Qv4;I8&c$4ey&xi3Sc#I^Y5jh_bT&?OSQ8Cluh4M^l0)frq(Ky zqQWJE7(d}FQTfa$n|Waa_HxggPd{@ zeRAXR$*nxma!gi3N&X3}ev`cRSthN#Frpp*LGMeJc$w0+DT<+l2>Rry>5(Rzk)q=Q zJJ5T@x!hO7=61y^EW%Nr*XUFLUCxEQYePrQDzE-&6cyPs#Jq)?2a8L#fgj|+bi;lQ z$IfUZK`tKg;@H$WkDSZgPUUaGkw+-5(H*NvO|Ua_*_dRbK&iBU;SGgo=nu;d%ogi~DpS=IekX8_gSvU8an z{BxZ5E$V*qh%z0avjBl-4no5v)m2VlK*-H)=)+}gZ|^=jetHf~!n z;veJg0&;;Ek|Lm^N)7c zHAX=G0HhEMAivcS3&N(jLhqbT()tt1;I(r-?Cso>->@gM&dFi!0+0_mR@#M=f!~UU zHsNa?)S;$}c3BYB&4Szr6}02MA}%OigbO(d$BU-t16TQ2g^!@K1H2If6jx>@et zzEm#$_8&3mw1 z9*(5WF5B6~P+UWtFx0EuyPT{iq?{!XF9&+Btzw#tVNK`}HMO1jX4+y*KF5fNZeX*m zyCC*9W}e=p=~})Rn<7)@XeU|t#lf#5auyA4ltc`2AhyaCCN=ITRJ}1cVU`glu7QKF zH@~&~)fDu8q4%9>Ui5n6HHHt3KU!Wv$WO~627|rv&VO!yi^txy`_hLO&fpq`hsA9$ zapX0wjY=!|><J>OXD>S?9Mix z0rub8`s4C&1^3Eu{X28waQU+zT`1=`0Ol5yr#_zLSErw$| ze(}+ja`DRDavKAD=jZp~;Sv01yK}AhW}FP)vQ|tC(A#eN^#MqD*0s(0@Fd*t^4Q1w zGJQWU_p{h#?6h*8ylxXK7?tf zPhz>-bNImb`k7-kn1eSM*72e6>dkxQi_165r(awx4<2FaQ#}2*7ayj_X49kZF`v_7 zAHz=LP`i4YzAoJJ9A7qUAdzSLE#PF=V#52y`$5>t5v)YyOkg<|y znK=8Su#ziA)_Z$lvwTb@u4%{Tu_2f2SXK;UGV1${xNI|4?rr)yKiq9>te1`T^>XUu zF)U|$qP+hWW_dewpv++gHu|`JYYEo|JSBJQURip$T2`_A?dB#2TwtCyTN-wSAEBVP8hRb6@>$F_{4K642#S>{$*7#=eh^}GqPy1HsNLgw~7 zE^of~Uz7{){&_i#S@RZ;Ua+NX_h8w`-MjbUX5^l@K}S1h^~-_Z0HI`kZMiHzz|C4) zQsBdKBid_T)h`D>;{*8Q)fg@6nn*-;~W6CsL=L#>^z2HUvJ^2 z-grY6DvUS7a@`bENAt$?lxeHBM%m;Q(p*(e+SYuD$YLV1b5!1{S3wAHNEAnEdb@$F zZ&y7Tf<KKkz&gx1TV64y$p|T(yNzvxgBa6{)kd+(;hO-64ENtY*bK!h;`*I+L*~ zOzgZ;tcD>E3f68G#<$jL!pCvM@E<V%cpK&Iagi} zZMh-!nT%fXBWKFv61Q`62g?B*Uw`YzSo-#DT*G`uJr3aGvbfy%eqiSs5?8Zn=O|A& z4$AYK;jSWz9JrhY>&5wNL}rX^M=o0rn6~cJA9PmxGlBhFcY*zA5jyWbpEb{B(c@q} z4!Xo(Jud5ROW<-j82D#cdd)P)-a78By;9URC*XW%_qVVCO}rLG8dXn$moh2tBp5m!oYwwx@LJkT{*44az4H3{ES0S z;YB%iAr*uEQ7m>nsufzJg8z!e47D?rgPA)o4d3%*@BU;sc|G@E$LI(cClc6?A*U)s zJS#&eYs!bm4J#QMl(}h6j03v?B-9Pn`^cJzf(O}I2QpR&`5*GycQwAyC2BLrj#Rdx zpjg=!m4n$J;&pwq1K25&T|US8PF4eUWUbSZSj8b91`BM9W!UG!G`*39*cr|9NPM#8 zQND5VrFJKT*$2u9sq2zwaL7Crr~zmuE~{e0afdcb&uRdKt8N_G0K~?oTn+lebdANE z?sfN9jm>5DLk%&JE(5l@I1OdGTyib;{@gs@!^r*oO1$tR`xcik z1l6ty6Ip`DzG6OV0F@5CK|`oCmqlkOJmdxYec?FG-29|+N(4f zj1(0=RkAj+S{X5_A_f(PJZhtDu1h|0bpP&bJ1#xvB-*Bf-lxnMdX$HLTTQuX5+_q` zrOz%rJ}1tDs9SQ0zqbr7{6bi(E^AQtZ!ckWivxH+!r*W|%(t`cWMC%) z-^dK`+Q56&E05MNW8Qo@kFgx@y>$x9+Rm2)`|;Tm-%pk>W8UYNZ$vC6 z``VnTa}R&B#WiB*D)EgxJ-+dcdT9gnm??dzV|no9m4dH%Q*19gt{35LA0lTbCu66T zGs|}4+74IdWjWjQx?GH70&3_C0K-ewy2ES}$mep+mO z8@7r?9K!TDl>Awb&l1D4a4TNMnag7w(o?scb_w5ODeq-t{wNa%%ZhQD?HKQvj9k+; z`J3irmYwv9HP=<{kJ)FAH_Lk)&a8>C2l(UCgDBvu;E}@z%h{9o0(kaBIep>?mc%|( zRxo|~eNKyf7tm89SyI$Fw$%$!W6{ z7xme%@L48%$ym>2+O*B{`IyTD^MZ>n*9Q-sE{lt2%kdKzFgWjDmopds7)#rpw`C&v zbYe{)zLe|JG6!5Rua;YgS61#}$$+cnK9)7Pd+U>O^ZGB!(w#5Mz1v@+{yE%pFg2fs z*=IgmJi}6seW>{=Yy1j;M!dkLuMzFGP@-g>^3AmHBO6xzrcx-BWLEX*G&WZx7G>C=tBgs(gVH~MBQS?Fsuk@x3ij-&F^Cye#Ok}$3p>o5clv*{wE z9$Se0M<4{ldWy+yr|;=kF&Ii0lo=ekJp=pXN|;%CnoPquT?I0mb(QsJ$%Fg({oiv|h!dyZ!E(1}%lzSEHlyB=W9Q1@#S^&J9l>m>hjAUklZW;N$ZW%ZZ_7*;v(kyH22&wS($UKXRt zEON7JU%0Bc4nv%U!b0AHmS+%6L~d9QAkaKlyl6NF=cwG)FxZXwH|wy=hUP20OdP7y zARK^)pNAFh0Ky8kI9PcY!lZ^ga_f0( z@Q9uR<(4@xm+sds!|x#etk3)^Q{T46;3Z2P)d6dI$WT$eF?dO=llqXz7C^_!!tL$+ z2S%q3rE%4DKUNT#C6@{?Ohy3~=Y8b^3>9tB~;A$fm3b-Z?R?tnvCr6de3-HTOhK+gjf*p3XhkHnHSA#7%&Hceg6ZA*pnCf_C` zI0|fYY$~~(r$ag(D;3JsOa&RqfbuYKPmw=53D*WyUCw1+d)C2wFhvn(b>jo6oF#7` zXUp5;gYq~qj}J2K!jh~Q?SflBe6SVYgX~98J|LDIGOUOQ_utmnAC*-sW6PQHxPa7*-_Tlg^Qy@n*;gpU=vl3-o>&uzqK-9k^}%vi$9^J6`OB&wk?;_$hITj zRz6!4jIY(21Ld~GKh#M+IY2M5XY|p(_J;x&6F&Q}|7cR?K&T`hZjoC@Zqx4xejVohbq)eakk;^Z_`K-3N z-HIi1JWE{S8siaXk71nyNY*zVW8lXI2IBx?91Yu z=66=yvtsG(GhRN`hK$W+;dspU*^WBarI(KKyiLPWy!r9~)87B`<4fhrwL9g~)w|`hi+9QE$}Wo#dBu9vkH&Z>n=CT7R`(NF$u`QDHI z%kth2{=#O;dktTh?Wt-Kjo#$pg%!7laPIT|4hOPazx=cE*~kA^xpw)N<>s}Ym%BIq z34=x+1A`l!@ZrnZzPtaH-BRl1}<)igSmJN9sZ`|gIgWb={W%&x3+4nZW@R(Bo z8~L{Qq!WMSm^b{WuQ&F@pLktY4J%gh^!DOXK)R_YO6hyK>FFk7&AhcOj4wj*47!1F z@|?kD1D&iys*tgrVJI1ol@~dXo~FW7nGG6kTtVM(u-6u0s9NKdfSlqtnbV?J3llbO z%Xq!S0aM&8{^q*2LHx}Z4I5DH5>8xpt{9Fth#jw}GM4y$;}_^n{Ft)7xmH#&IeqTCW#2x3LfQtk@w~u)X4CSBot1RHu^%~590MDl zAa7iDW!O`ZCKDZU{gyQO2S;Ihe&Z@`uSQtdd ziO8d$GcIfAjGsUBqgenAD*}bgx`LsMye9KlxYy=tC&{5M6k?-MKwTjE=3WyWTd3pEdFZlsW;7Ek4ig0<1?T8^pm9P?U$ zzwAq8AoyBU-&k9jnc%7c7?~S!uXt#yeyp)8Z~zB!87FN~2>(3N-Z{CS9sU8vSJjB7R?tj5*H$~5V!HF;+FY5h_rf~0W*D7 zu#K)zgnkydjC+?$8v-SUxeu;b@O*eKeWiwI3v|Tr&teRpSw>!S1kBc3aMp7Y=xU=Jc{b_GwxIO5#V@QwlsUCb zu}aH4n68?G&HVCwQEog})-E@+J-H1-2K(IMaS_MObx}5n@8rsBY73-Mq&yWTjZEnX z`EpR(%ii19TYD@v`>`xKMWs=WU;Z!cTXwc7eip6dR64OD>BCb!IUh zX0Wowg`p1Ff0P+;jiJig`Ac8kE_bBX$cFspgDmjlZKT9E!$rmPp<$?U*T8ICHLR@{ z&f2F6w1Ns~zI1(sGSXpw;Lf%dnfDi4uYM7t5{tkD!9f2Zr-ZhA-TA|khyeGz4&uJ} zfdg~62R?^;Ii44Op5|b@U3)lV9+rc|U^58ZC1VJu-i8pv6$ehRrJI`@HdEep%#?TS z@<-*?_0LfMB7ooQ!$Hi*z8^EM+rT{KtaWH@JacIItCHb4V*M4moNu?|nSg#sycts| zlOFKl#uKua4H2jjV)i!+yj|>96?e}&EuJ1X)~@Yf zdb?RRWBM@}d3!nQ+{Qj=XY3d*>y2b&XBT|V`hpAoJ}hCm57YGX3BdUSd+}7pTzT`{ zsdAK4rOxdu_wGL|x9>f~^0)Wx1K65t(&J~<>5tnOSB{04G3=O(SZ=+&OSj(lHo3l)ugoeO zWw~9ME919Yo@Sr@jdA4|s26@ro@E)A%kq(Bxhx-iy>lj@H4Mt-YyA*k=a@0?=;Fb0 z0 zD(oz--mb5=w)j~O2jv)+37E&zhsYi~9kjUR!vjN$izjaa@k2OgHGG1b84vDXDYtHXSU&yDU*V?h zM_AhS8fJC6hrb6HSh*agesh?%PRNL7Z)~gDAoQ3_x@SG|e2dFT(YMTn_chYMGIN8^ zqffEa3J2(M$rU@+yhRU2=h;A};8K6`nEP1?o{*csygP)xjl^~`vvY%feEHiaIAT^r zyocHnxEmNuh#!8f9WbxwQKGFxD)c(mxzzwuPE6ch^PVh2`-#aSH@dn-jQRk!Aimy%ph3p zmmmsRccy&Zq^9327Yz`)Izq!_RGs95h9Eq9WmkBrp!^_QqDv$B~C^8gcL0)JSi zf{gm3`~;t=8KBF19t*wg45$7#Q|#0}*3}PI=5AuE9+&}m4Zn8qvVX2Hr?75#_uKm1 zsGGVX-BQdzAwbF$x0q?4P^`91yaDFx)Z-!#mFhnfQ5BDQGMkV* zNt7*rHJfY`P2AVexYcOtI18yNXd1M&>FTEnss(e@ER3`&TS0`B$b{vP57fx3rJj1* zymhMSSZyLtIc{OHOm1yia;sE^8b=#-EQawTEuBx-A>y1*etJvOTEl?IAShVXkLj{g zs|UGnj$z>b952F#Zj$D%w@|&u`ul6Vc5#waJ$%}=T|q_+0~vK9W|_vs^(PzZeodeX zAhYrK%^&MlupODOWn!1O%=FXcZsheXtwr0$pxn)$h=^JX_@b*f9(;=%O2_}h&oY$@a5I?hhS!FaFj z;-6-L;=nx42BjR)nfyZ*-wk=c==$Xk@v!A@ZI-;1M@u$?-U^nm)h{vGNAG{ac0#}g$aCMA3>K#4Oxl5e3DePIOac`fr6krCYxh`KBX}CNzN<7{ zL56+%DDP#R=`&Ub=XA0ilaI-g35r}PV8gCe408`2w%oJlApQ=O{qyI_krVHgx8MD< zauWBo7mvLK`F?xgoU`EB`wDDy$s@1JkX>B+xC+eDoiEDG8=satxVL@p*2h=|_cJWh zcpcZgCGg-r5wEA5d30wRWPsNv=8}^ua2BjNF0GFDr2SYq=g{1)a^c)b{El%tw!Ur`2rZwBbu2OKnV_6MsM&jfvNlh0X<+lgsAm@z-I>~gFs zkHtQVhc?^6^fAt|8Pku+rup9vb__En>toM#KAB-NYGLpWe-zz=*|c6id(vjo`{3QP zWk1$4=MR&&?mj5jFkR~Po18NBVOd_mtRnc)lJ-1ncG)!5o=fp>_dFtgTHKU#BUdh) zu-nbjcko)>%8t5cX$MKSV>5=yvc6n{MIpzNaT)VGNSI=$HQ+2It+dII*fQZiwJ%ZOmrsGa>o4No{cE*?Y#(+li(;gFOMB2QRl+2#N$H>UuMbs>m7Deok6%OqwN}+Gae79=%b$7*}#no%kz={ce7n zq%UQg%s&V`jD+odRXaFSZrBEH9->E)nwa8(R0_ZUFHpCORIsorjG6fLo&(m`u@uZEmovsRsgDs02UfFBy?^PTZPVha!6pEs zvk=T23dNSft$@%`#Ug3A$=+M)Qt7A{C1uglYRiK3#8!m?9nF4|oeEx0`T>bMQe33f z`ETa{%&gsT)_5Xr`U)<`C?@nw*wr9i02c9hE@V%?{btlkjyd-TTDy79xK+<%a*Gf8 z+Fp^-h5_PF1WCoBOsLMTX*qq1_;{#%#%3m=4MJye_W~n}6_oWY9!eXdV|<@{6T`Nf zLY$~UCQn0_){h=f@WO-~*`Mph361?v_F+nS-zO1{5Ec(T5a-vMu!$h(mz#L3ZaUbQ z!eiq;B;IaQ2E8%F&e{`7h%854!n2J20Wzj$rW^%^3M+CK+)>Oi zI7q(T)_p}Z;h#0491#V(9zzzwp2cj;s9I6vTY!z864b>L)$kR42MTL%v_<{V$VPm) z()Xd}!CZ%bHKY7Pwa4Mbof}S^)jbL<6Vt^=yhYe{ zDUwRiWWe)WH@HbvN>~ftP0VfV{5)j}8rEf~H&LX>j6mYf=q1}Z`>;8NAyMK|mBP_J zqX*?HlH-2l!Sz)Z&24?4xQ00POdXgLHjR;Sr`KyJ1~SWGGqj#YyX2Q4TN-IgSgiyZ zMXeYm_BH)tGaDCMtR|4SS!PI;QnJkf_d@Q?Sf{XX_zHJ^D< z-ufz*8pB|`hxc!nOP~L&T)Xs}a)0SMzDHoTs&DAVzULojwB$X=y}S2ea0&h}3!(aC z&~)ppfn=hRdN*`~UNxBwa$eUOyGhqtJw#H4q?GMkL{aT6;Aph`{cEQ2F=_{_Ij*LQ4lDaGm#_d zd%MxPNWv)cItq>G#$w8CoUjEo&#ker>a~bQ5lWS@E~C&Hw`5lF-L!l*2#qp{G}GkL zKl^cTPABs*`Is!342!tu^7QGNU7t4b`{2grQ_OgGyu9bW4{%(8oj!&$@hlTljE`Pt+jTsC%cxPwZ$-qtqem^q6k0v%z2VSGRR?lJl z#Uaevd}MyV4az%t^sr5yv>(6aa)!KLeR`#Q^4aBb`TA{J4AKcGX4sore{m!fV z_oH8TqIkI(c&P*QwiDVjeQ??iAu!L%Nj$gFFHXVtB6`|#d)Bzzs_hjgCDUSU=w~a5 zdyMy4;~wLZY&)?F+s85{ACqmj<$P1HFF|qS2XTI#+q>&Y`QsnITfYCD^Y{RGtSn<~ z^E*q+W$D3k`RvlI@*6CJyZms)2I+C?e}7YGUKvv#d(--Pp!zi2Si3TBUnU>9wqrNT zH}#oc`2INNtH-=ZUHs&jmM7|Rthp>5k6YPOznx9-WG>0Feb#6B%PMD|^7XRYo%!TX zzP4(|81`AX#_YH9H>Tr^dHmzjf#**hDL?w&o8{!Og>nKP@{TPYvZpPs-Mm-+`tLr% z44L;ZKH@*!Ed^_ylJ zcw(9Z^X@NQEjOD+?_D7E^Hfk8N%*z3@C{ zW|dtYHdC(`fyP1YWxwgQM_52r^zDzz*$eMuklq5Wi}PhKp33$m zp4d+qc!w>Y5_ZoXJIfIUF56%|JQc|6E-L;8DTegLD7c%?a-R;d>)}g1~^vg ziv(D2tOpj9#%(n0Z|4vzYc6uhct4PqdLSK!a@0wHP2%d(q^S9S=o4P~P%xEYXl>%y zWsQ6d^aMb`b3=Wc_1~i7u@F3nC#>bn0#KzfvtKC9eqH8RdyNVnz9LkU8g2A| zHGopYp|G4i2*%>0s)C&@(Dh*XKsbZf4SR$#ps1&%#RZ{cJo;u5E7lO2(hd7I$P`Dn zj@alFMaj&k)npJjRcQ~K(LOb}tE z-eOAY^#bLIc+4!UaIiiTE*eCeet_Q892p;W&Am(H0@n`jadYB2VAaokJ}iXLvuu8& z?`7j8+Bbf>9JV)d5r%3jEe&VRSiv94R#n^tNzXSC1nTw<7{-Da*br`EpNp1H+8dz7 zn4p-QWEbw!@Q)9pVgMNjvtDw^R3|QN76UNmGcY{Jj3t>i zS&Ln5Nt97^t0C7Us=faYoDT9)0{}!hqhFQ9IxUt#{DVqZrXbm$#xrbeONi{{R7c@W zAC>q#mD`wOmI)~od)ABEj`Ev~JO_@lxb+&#Sd~Ra`(9?E_Bt0d$Kr02bAjrSq+97L z(5`2LYbjKN_#1R;iFj_h{$*g#opmakJ@Tb?nGGr&Txa)Sa9uupDr9O21R?%cy~w)dCu z8_$ycW_$bIN?Bdo#0*?~ufv%f=SySWDeYunCj(zc1~?-gf3sa*+bHuGocH7JpD(YU zIcmSz9>v5eyn;T&IE=ske}7v({QO2)$Aj#9_dG58u)3MOdEvnKrk$7c*U@`BK7Usk zc?F}HcqjSx6kQTx)+UO8uv6g{Yj@dRi{oe zmS1!u?<_v~-Y!lt&B6;e?Z|13`GnLH%6VkV}CWfeDW@80?pOP>4`OWOVxM(SnE{`nX8UnVLFqsZq0ZhCUSMnEw3}WTKgMtRF@@N>qlX>Bo?P{I%TBkp z8jI^Dk(P}H+ipoj32&{LXi^A9@X6GQIp)@q7N2CK8lt1(d?aK^h@lF0@sPWt3Oex9=BZaIi6mc8ZRJ-xoF zyKhi|*Q~{(XUf8n(`8}tWI2vyZ;zjR17%((Iam*$C2U9{o%CKr{m{toGGp?>sur}gFT4gX1 zR^vicAAm$%`$`NIU3moMQ7D!hlC>#Y1=WHBjYw>v>mXAYw-Ae<pO)!Xkp8MsAulOVNhFMQwY1VMNWRaYdECvyscI zh$g{O(HQb>!oLIr!7x`tn6(UosG8DAoLfqbvk`%k{dWnW zbrV5gB(JY`6VT2m(4R7JnJLmv((pP1oeZNGr*+iC#;P+)q9_75^YAQo!fr`wpg`7y z%Tle6ut_8p+YxG=Wy&dO{4+uG>~5y`k>3VaCnbN?aYl`niDYQQ4N;ncQAXeZG_04@ z7BV;o<=nysRY_djeA;3Da-m4T!_b5;^hU~QEfQNydera ztp?iJg63(&rM0Na!K|_henKgTsYmNEnzsf{#ZFaNTjE?H2z<4z(AChYFzEr`+O?5a z*R3Z*1hvt&ojLTeIn}dcP&G-!VFwDTZDMbxtu2hTMlw=}hVb*V&6I~hdA5YD2Ipbv z+T9pcxR3GSJ5C4|lOHQd`-#>`OHFhkVka_`P% zJcN0_Jh*=o%RF91UiG~o@q9yymg5+hw+A`ggNL>D;5P#ff}~DYA~rYmsb zPMzCq(#ewaL1YKp^}43mGRy$Gl#Nv@BYw0?D;W1bty;5-=RlTbX(R+@Gc29m8{&0uP2b|1@>j*?%A zU&$kSgEO$-<;CVuZZez+W2oG+)29WLij%-f859F%wM)_ohWcN>HDF5&mvRSX2e zcye1ZmrH5xY&#j)$-wV61Aea<4}D{Gmi?Hu`6OPuoIvF`{#ZVYRnPdq2w$@|?>s0s zZayd+Z*lLz3O}BfeV8qCFaCD6oeX?iGVsC!^q8n=U##noEL}FI;X7EZlIK}%s&QAy zT)EuAYE^A`S^g{WS6q0WxM$^KmVT^B+p)+)c(b0)%*O??eY&lzgU)6*X&1V&P@Fpr&xuR;?BN$nc5_|>a-b? z^;{V*n_f1{ChK#V{_rUg1p%4(3?L&4-(~DRHITz5NBI`Txgq@wflJ+`REQuD5sb zAsw?-@4+lnSjtu#<5G;0O*?wr@;eQ+J$avx?O9Djjh{0}^Ol>n-SXF9*2cK)!(Oo) zK~(2vkMU-G$LZz{$efnGq0I^qqRp#?ZAK(x?$s zzq~;7TY}d70V5m2CHH(dK{C^6Z7y|w=5tkL##hfJhC&ibhRENWYn- z)91ch&Yb@)u9p|^ch&~)%^yDg|Fid|OLipJnV@4!Y>C9aR~2gGB8rqqiZqQ{)@>Ry zX@0f!%sfgz#k|P8!c0%wSlcYEo|Z-;sfAlntPT4PB(~hJ^!J@}KKB!T1{C2$$I!B4-$rHpCfL# zoz1wLRJE;7hHtKjs_HX<@~DH!kjeVc7iOc5RE@0BDduuuL z*OKg30y3%>of+dxc7SApowWI4C#WQzvQ}{wggPtDvKcq$sjo7!tbZyRx@M}uCu19* zr)Z@{9Jp<|JVDgj7n$0Bos6?%=ze7Y;kJL@^TNQb)OTt>aU-;5?zfsNN88S=_iB^Q zr)n6)@XM4UFDTf~*b_h9E)9@T>4Ms#0srMk$7FKwj zezC-*+h~3S)tP;b5*~mGH)eN~M@7!*Zi7cFp|2 zMu<%$5+`!FkYo@!GfDp>DbH1Z*YX$(A)$rO*>jIUrXX+d(r+#p63E6u!L|G>slkeu zB;vwWJgul$L(S_UO~>VSPejfKJ`6L$v~5-~IPELt z?Z%7bnE1h!^Ei17<6nGHXa@(cEyegYcP&792cLps!kJ%Hj5dr$NZdzwhtE#Fz`2G= zd5<35#`wASHT*bV}c^>DRV6ZG7eWU`6WwQJje?W6U?+KIOssmM2N5VHr86!n` zO2?t=Rb{4Gv;j4VY#<;rsR??F$NduUh))?H=d)<`G0W7vP6``^mVD?i1g=2hE)g3Zd+PVN3YwT&k|ebXrX%w9p8@b3lUb>ZyM^5gHE$3#6$ zro=Xx{488qT`#xpJuH9!%d1EqA@)zvI?rsPp0;>@)8e*W-|Yx|*%9Ei%CTWPa3AD& zzzM99_pJ}k;F;}lEQ)l{R?Az%E-jC6AN+W#2z5y*&@HY@5ahn2QV@3)bYdRJihq7gTDj&_t_xF zGpxFI>Drz0@ueH(H=kU=GOf??7R>^5uw`cY5Sb?yS%!HYxxF%fT9Bsc^;l#2^We{` zw^gj{ch2|r#&9!Dy1BkRKgU$~Sw^(6bY3~>W_jj$jM*nW^K+iO{@9G)%fD<{c6$9> z2gzhwFW;BPbV%C_Bg^pG+`!6itE(&J)XAge!nqUWJ0G4aNAM;U-@G6e-@N$b(oIat z!Q88G*@*3(+4KKNveApUp|s4zs6|fkLgiI^3tcQj;xbu%8%)1ot&3WpH8zc zdM~f?b4;dtiZ=#V@q?FJ*q*=e!*cFz3__gwb~$?NJq%7@S^vEUaSvR;uz`Y@2V_7+ zdvYM+DYhEsRC!bA^+|6v`bAyS z3qi$PI5f}0O}j1S2jDJdSdpz-@{javtI4HY@)sQl3@2Efsa@NE^ax0(H@^}dvkk@> zeYq1?QL$m!A5>)LBsG~vbdBNpp-dlJaus*x&B~amF~5VBeIp@zvM3(i&D<;AB1AV;G0ASd8KQzS1}mZvore^6l$mCoh!K=e~i7dgpC* zy~RUEF{pbSgAQ26p98uU4@_Q+%6e>70-F;XF(Dn((vf1a9%7j%>q#>AC2vP@9JpOW zDXO_4lQ^?vc{@6z^$4~f-S$=W^2&NPVGpb8t>ZJAEA81tn7HA!`=0E}AS@{>mZf2r zN%+a67qw?R#vk6N)E9@4(3U?28a5&yX56~5CahIo29B6x!g7nX4M3(zA<2gM7esG6 z>AM8$r8-nBKpU3yQssi={#hZ#BP&5ixV18rmt({1h;;tE0hSC~Tv1RNnsyj2PE}A0 zrBa|^&Wk^u>K_fFk>l_Up)m z>d8?c$Nzzq=B7d2iGJNyHEVx5Aiiyycs!gV9#V z3f%)j<@gI7xix@bAu}wWi4_%gB2O91D{4*<+oY;YgZCVyzzg*%%8cu`Ga)=CL9@mKMnaMcG{ZZbP;~Y9f6);E=HLNIjBnHYTNQrc5wVe7{ z7GoWBczY|xK?S7x8qu4$`gOBOo|iI4i;L_q&HXd$$RCfJTWfJ{9{A@`cvB5K5-pk| zi9H~gBMr-nA!~V_9Ez1UlC^1NGSjzzVye+iw*uAJiDDPCDxB0EeMqR@X3Po%*(o@x z2wiG@#tLS@+7?BJ@-roMC`XR^f$>7L-a{ZLe76MwQx;V=0@(l^)1atiieWM3g-~so zZcrg^{g$wd0rl#Ea`1(Lq8;A8LCW?! zb5=Bdz*W70ATZ-Ttu1=VZuh$8i?A9FS^(;`%MeP~RI3FMCkV7u>)<%D=U~)O+bb4B zwCE4n2@?R0`UH8%53L)r`ACkouU)`6*lzyW3VEEA=iA!a1Uyd2!z40J$g`afSmw(@ zNZS$qBk>%f6~1j?a>?q-LrgHafypJ;@Y3aF?1XU1%A9zzvA%-wxzz^sHOw&(WhUWe;NDa6M4Kfy!`7TE`wY=NaYU!* z_vH~4Bh8k>I0kos-~^K$&vBiDi>l@e(e06mhtBih%aq{0_jQm&S;-`OGZe0C2o;6~^FILjyR^!{p z^ayB0F7`f5`@xa|JIk3#Ub}sbzG5S6tN-3cV0(hzj4ym&bgRz(VmQBb;_%3W9rI$( zE7yB7rZ-0G^}gz5#&83xgYd!QBEH}q#qz5sjvT_4w~OWYkwsgQbpsQA9zI?v*KXV` zS8gtsYnbW1W~=Jqn;?F?+n@+n*3)HP#rCqFO zjyKqFJ#vyAzRWEx?#GX>WB8(X+$PZSZK-vP@e$F(}9zA!Ge zb?Q|6{1w9k-OmQk&_4WcN9&SNjC)21RC#b~Ub>($>s28aWwtxgd^m@)Jts>!esiqx zr0mu$4M4phma3dZavRHj<}>dxU~VWu+%Zf_^2o7sir1SjKRn}> zNZdLblg+pSDkn{6UnKu^OF0>D>Bt#uhjO$WS~^h<9X^G@+_N@W@4(^_ydiiPE4wZh zt)OCkz=9mn=Jm>LqAcY^a}E|G#h(oXGf%!9Gg@d`Kcb#GjDu7c)YgsdlpT1e;+G>+ ze>;+?Z9Rf^-N1lM+XmMMTkO6QKz=_;zBU8lfs(KTvy;BK44?1RXB44E*-=P39rtzN zQAM(%S-G7ODzXEiU|HfQ4J+=0Akd`ft`Q~U4FT(?YgJ%|GEOk8;7OC1RlJE-ntk4R zgHaT8Ne4n#9O8;;s_+C{kcd%z87jD7K9fI$$=qP}LK(CZr$*E#nzIvtF8Jdnom#&G zSY0}GYErCoA$wTE31YoVl4v?1X)x{cBwHc8j*4qoPWAc)Aql&z0ZVaH@j^Yj2ep`m8WfZp!>v-oC;?r2D#+^pwXvi;K#W3Eh;cM1I+5cM_;gvg)s{iH@6?f~ zwUvW~5jh|UNAgnoonk&X)iju8Mk&#p-5Rk9kSVg;lc>{CB7Ov!9>``2M=Ebx9&*oN{7AvAjR!BJ(odQ z@&KR>*%3xt6%Ui}Fd>gCkN-NR~m_wb+2D9gHhFAH*AVDCRRs|zNnNhx!U5?Fh+o-8?lLx$Hy8cTVq8%+{1V5;7(qQb#9Bz>-Hu{uv_q%?HU#r zIJCI89A4UAj^SDDiKD)K<}s|+#n`Q`Zj`Gx?v!iT^7i^2Jgazu1sw3~XeXZ$;d7B& z-&%;@grIHLaXSKEKm>TJ^IcXj=8wgUI7Yp&u&ca%n$K*Hm2;;t4vu*hJh)okt$Wzc z7OPv{!ER^wmY-m!vo+jXxiT@nyKY~M7N_mgn;U__1U-hqzXaeb-^iCC(!}3P_Nx|B z`JL-U8q0mXvhQPhnXkOCRGv5841Y5|?esD)>&uwWJyvfk>-4hXWu0kx%1+Z4&b0Jp z^`_yG&hGyVZ_TW)Q+V3N>r`=I%!k1m)0{C6K=vfl0T zXl1=T#~10nd$3nJ21oe$Lf>B2KkDho{Z_QCroww2SiS9Obt|XlGJUKr znLp-pPn(88xv_TY@yGOKLp)*iWzt0(vt@jo5K;Joi85F*@99(A}S2gDtC@8$m8i+Bt0Z!pmCvvT9gPs+WUKP&6l z3g!88Y`20HrTmRRzaLU@s*e>n;E}LiWAdV@-c4gN*UPdjy?O9^eOeHWd0{a>c1YH3 zxr9Zqh0%xuU%=yl8V?Qbv8=o?Mo!rlg#DNj!ycc)#Lsm1K$O6Xq*0q+S)Y9QE7{B! z&)~vW<3~SGUC1~6h>1;H0wo{T2C8pjsBMTlftTc%l|S+|voo~SE3OM`GidUtdZ~Yz z&7rFlq%r@aVjNa?GFksHSZR>wbH-Y{X z{f=)TKjok#?mO#Pap5^9n~mL9di}3Sc>$~D9l+o%x4k`n^6hf!%zL=!eiQk3P{U|% z)-{@sQSJ|mIb&uM^{}EIRx5phnEE6>?*r=Vy{_Ss7_i=toIuEG&+hp~sIruDTzF91 zOg)0!CQ3dhU~XXW^U3Ne(hb`J*W8Aj*@L7E#j?az+VE zuS1eC0?G_hYEu)pI=ZZjCBVpP5N~MTV`FVt8=5%MXFSY;(Q-3V!u2TM;=;UaSlnNVH(RC_Vpa!Is~yK- z%;Ynpz#~*FUr&@1R+5O&XINM1Kv=BtfReibiV{4V^a$pB*RwP5n0l>b ztMy8!XaACoTJf-fBx4a(NLH(m_upK>5eEE~4@7ehTgK`PBI(qu^`tNl;aI_}1=(di zh82~Spi;!hEjRn5Q@x9qY&3YYAzyO5$7m+3Qy8GFg-Uc5>t*}@UXfD!!f;J?LwYehSmUjmaD^(9NYfkNDxrHOxX+c zLGtp4<>~`X)ygNl$QrE459JIK%!&_l^3PSPGYvHPQteZ3U_a}55VHRfT43rv#P)C# zUcXwXcC&s8Qqv2*0uZgE!ZEoTIFOsdc=Ct)((zAb5b66J>VZ-iX=Vi zLrkxMkxesOK@TnuF%j?f&CkpA%fG?+&UH-6!bDcZTu8R7U(JT3FbN zv3;zZhsk;Mg_(JMs*k&Z$AiAKDxm`RJ5TQu5D-R=-))@+<;?)I)@(4<(eD}5n~p6cbq}j|+;oVe>pfe~aN^76K+X!)!X#^gB3Uw~`#hmG zgFye(4iX!qiT5&Tje%~~Ge9`j^5Qwh&M~=e*KVxRcHkH$=Y6Z3I`>EA+*>~=Z@u@U zvUHSVI$Qw{@fd_8#l&tLU;^(sUSNIl_%^n?{m1fO{=@%Lu3Y-ZvW78~{rjI`hb~UC zJ^(CE)FW61L~;A55#VpPbu5{;f~}K|VzS=pV+Zlf_7JwxJXtX+ALwr2$w;h=FU?B(oVkVM_@YwQzKw^HQYOya^A!Sb{1S;*}#Rg zf$Q&J`FDT$tG|2%fGOwRW{^*qKCFS^)({lXqhV!>OAd@FHr-)^j=x4*pm)@i(@ai|>J zzqj0fh?(G+1%BnmGL}$&j1|0AY*jD5{j#vYi7vgh&CB$M$Fz3cj4uoG%1K`7JbILw z=AUq0CVd&)tTT`AFXI<==E2UsjM*9EkI|$%W>YlEyskEw=EF34_D?>H`87t9Zcd+K zLf#rC#qPl*S8g-Sx1UaM70aWG_Vxt_B_BOrE0?a`D%bG^@4+KX_P~lg8~9PShRJyx zK$uaMm$U`{>~fy!rRnn3(sIa{2S0 zWBL9&7`#}<8#GU>U%q(3H@PuTTz~vd-VC#!v^TI#LL8DKKi60M*O}6m{K&R)ezrZ< zGY!Z@96~?f#uT*m=obg|=lwQWk*fr0XoB43S@WMHQ7$!5~ zH=eL7R(RslAjuk1;3Ub#u)S)ihx*a2C4c{f4o3CBBo)H|^dWw;UeMb1+ zfDA8$;7Fy@P=+I;mR+{llvWjRH|;DQ3d;5YXbjKQ_=d@<*I+4951O^I6LVZVuI`tM z;LM_Ya1{fnS+eoxXZ84wW1d;;tuTk%>@40Y+68LzIj+*Q{DrR zJaH)5tsVkGG?$n>yX*`pucFnNeo@kNS`ZMf(07*+ZZX4N&>ciRIFV;b_jyW+CoQFd z^lQ)sUEeiSBP)R+dAc^}Po_#KNJL^bM&wAR2oJ-nYEqO{%1P4Yb#4-(xNN0|qvj_? zP|j^n0!0ix$2s(jK^=q7M1dK@0O(_Hm)vYSq{E<>YRtuyADubHSyoy^TCVrhAb}%D znQ*7vfW9wVsoF$Ms7HlPnf@Bt&cT;nL`tlQlz>$h*~nWTk80S~Y7?*!3_mJ_lxd{% z(MUKMd_6(alpRZ1N>QXjm^=}1kQjDErd|M9U$UY{Y9cFLvi*B|_(X&-=BMY?NO=?tT+*qhhu?ZbwRT~luNZAx$c3D}#oavf zD!M)T>%&51ZQSLl-=A?$*v(11yRg&D?jz;I$z$d0TYpsE{pSCOiF)sr{RfZXd4x}n zX3U16SKsAJjd*!=Wx3qG{>yUn`mf5Z>mOnFnUBlE<*zKDA+#(B7r>#hT^JH7)= zU*|Wi%-Hf75GM(7k(51rZp&w|yLXg>dv})i-Z@jwoIF|%A3A_t(4Js|-Xl!d!z7{Q z$9Qi0q%1$gA}i3}k5#*e~*d*$NQo8{pOp4DOkAIGg%@e((8Ug7GM_-`0<8{QV&j=Q4r6$)N>hF3@#$6T zzL~G{;?I)f14T|A;$$E!J%#<#ca^iJkCzYLK2_d-`-E+WyMZNLIXUk-mQcQV=RvuH zrCT3kS>-i6kmNq>#OHv@ykGie`NKMsuVYr6B{xe(I7+3%@+`g0d=%bRQ`x8Od2M95 zoZqVL&E&Ih(_}ZZKgJ)UO~WT$elS0)jmV$(nC8PYx^Odp%&#$;boGOZgM_;=n7Duc zJ}gzf50hXIlyfH!+t$)ciwEr5xqbg}`3>H{`1H~>yT}a=>I~*}HciCWb7P{fj5?#@`Rh>GR*m zig`aMhcQ9#z`^5?i2(!t(&e8H3oIvGaQnhF+s^jMqg&<9txw9$Yd^>2y#InNZGTnn z-@AdWKVIMulkzZV;R6Y5m-bn$yr7x(^&6APa^&-`*UOl%k{9lnA7ge|y&Nva(T@~E zBlrN#81AUQR1YZB*asrnG<9%{Y}pz?AS+yGP#~I08!Vz`#u2`3nmA*gY-f413?aqM z%AH>g%g|f|XW248D~pDrP-&qO^&!0ZpmIe-CUoS_z}LhZ(6cX&9HFCuCYjBNsybM^ z4m*N*MLGE*-;9yK)F-OkK+vQIC)$*KGUh1@AnS>yboK;2O1U=exlMeh31yZ|!Y&B< zRGY#j;zA*v4|{Bq9tI}u;SdKs5xZ7@SNXc1_hO6N_rLk$a^lpxWf9xpatmB;hr1u! z-|oVLp`G?nh~FF8;qh1d7t4r_#f$?USW%A;&TR0Q-%~-u%BE}ue<1GdrFg3A5yEjz z6nSqo+H+HqlxC!6JLPL{%J5ze>-}?+b*B1ox z_@9S`!?X2l^$ETuMgSWH%JZm@j^_P$49vx;QFEU0dRgA$1b)JAj)k7o<%=m=O^_R2 zjTB`pDs^beJ;}BV%RO|xAXIw7sx)R2-E53F2@n-s~w@&8{*g1SLve}U5j$$Rsgdal-Dg{U{BzBj&EfS-S%z;=xdjh9<;<6-L zZ%O797fOsBBd!m{ST6{foYgTAC~3v8g1mejfSBHZVu~V5$73J1LR?kpwds(>sRhfv zij2pkX?s`TJyX`*o=I3Gn_-D`WETj>h~lf}IG*A?@WX^$O?;LD!rIIO31Y}n-(*qF zZBGHW1by{uf>k2rN#Mauvr!*41}mPUAFStCrdPbPGzFDFbsojRVxWRkYjmjK;r&lVUuaR42#^#w=Z@Ynlmv!k5r zJHOGKYU(mjD?=#}T_c6k2nvsLlN%%uw5ms(Owbt(i`Q4vweM3kj#Q{1rah#DrX|C= znVf(-M;SjuwNuy*f1@{7TgvD@Qu(1$G^cqaRrs#SlQx;(rHy)4u270%>Y z{DC~%F8M_n`p1{SZ2}%97;yVqj&X5f-X3nrhnK~z4p`aE>aO1iNHOqYlk_l4k3jB* z44Maw8efok<`eVqn_=b21KYy((Sti>`Tli`>wJzeoqsOtn4I?}9gHdexTAz^WxHp= z;%zUGfuNxP06+jqL_t(cfvzK(ySAt3Ic1EkZ4yy>i8PUY^%vs$PRf+{%mXZ%SzC$0f^cM5kZ{?9HYgJ{TJ1XDkUO)QT z{Ax;#MqJUSRCZuzdfb|Wp&;0Sz8B0d-Vb`2nJ)Cg#F9Va2|)C)hwDc7H>b>h=C$~; zy1kXxiB+*}%!*?e*eQV9tge@G0IS{|Klx#K`@?@%&c6L8<>cva;l;~Scq#KB?%R72 zc1)P&y##HbNZEad4Py5gtX#T$t=zf!QMq;FqjKlQFEC;HpKyP^g^AJ+Fi{#W+2gPm zIBmBB#lL#Bok0ZHZ)`O$Jo|XMv2Hs}oIi82TzKnb`S6`ngoS1j>?!$89 z_JeZ!9=6$hvS#CwPi>1%tb>TjeQclYV><%d5%37`4B5^c>Vdk&vCcU+Y#7~K#RcC33^Rr zdQKl&uj>ocbW5{2^yAC$Vcz=6ANnMnX*CdkGyjE29O*DWO;0i}%O|#uF|09~NwhhI#MTw8n)e)U=AOWc zc|Z8W_sY3bNAV?Z36t~=;$i-?a_ip1@}K_tm*wM&w`{W60^V@q+flrZ44WChIL%er z%XOSa%QAgE<>MGV*JU1l#*#17FsAV{T{g%3m#r5sF!|-# z;RuZ+YlbS7*woMp5Eow1bCEi^Y(@9mbx8;T;iGLcCgpf!ni1k8H5Mf+{!piJvrCQI zY>?J;M=K!{qp7t^7$J^qG|Z-EJXHcZy^M6JK}g<7<_G0$wNkbre5`sS zzL>A-f7L&3W6o6>_{WLF_`}MagBZyC*ED5ZxWXvkvgTy-55Dzh<(>Eb7!#K>0v`z{^V^a6Vc(^A z28!8tQPwX+K*)7y{2D}(C~ryFZ_3sNDyph3>saoC=dHbr(5NlvGCJ$}vG%M6zzv1w zHLl@sOR*w`>PzwI*H9=5l!~luoOO3-e#3mVg?#u$(vGOS5Di2pbHWQ$ahXKSN|_8y z7VEuDcp)!3$&}L}IHRdx5+2o&aC*D|VE$G7h@*hRbGN1z5|hyiMJ8t4Bgo<)hE@)} zaNM*bhSbfm50OytM0Mxj=omj7=8S5~FPGt67JTluM4YC=9g$NR>v}25r)0M^u?>TT!Bp>&sdIFYuhPiK zE>XxaOCjJqM{y4n`zGx<`hug@lK|0C?eH=^jk+J?o*1eDj|k zy03xe60l!o3?Fit=7xogG-EhWkTOAJB2Wf?Bb^G_;&6pCvg)_ZX^xw z-w;rI9GC>pS>2g6Tb{8PM?4xV;+^bQJ=8am4b)XbGdal#$W_lIM@}-B^r{qjptFSr z^$A%fQg?o8OS9~C5OshNM&uu5vJ1kkHdx6JuxuX$!*eY=3n1+9ca*Kt78CP!V-nsT zZezQ5!RXu>f-1fRHhkfvJp1D|wtJAr#5|k);egRx5uY%E`#S5$$$6Z7x3Rv0F`Xw^ zt?A=(_0liO#ZP`}zrnb=30J;u*FflhjT81|u1d(&^0tHpi+CH^l*CQr_ z<9$b(!LJ==QeG7LLS6owwYBX)PMuF6Gt!|8T}KQ(^>(j=6GiKxvy9?Z-7)xa8$FKc zNHoGmn;Bm|SZu;bR#CoP`Lq&LUXx8=!u$jwe3+mB#Xvg0j}kiIVUVGbz#^;rjp{T3 zn=woWwZ`>3RbBwelt`PUeVixdvon)iuU{8~F|jX+rq3qDc$V3%$d2Q%UC80Rm6P?J zV$6LixQ0yZqB~{nAg$%}YPSy?M3l!OQkLcJg^5S4iDHrbWP;#oLG5-#*22+vk`}`NQwN zQ@;P5x69k-j@ua2J*=X)jOVr&uih=c`1m^Vn5c&_sY3_3dTRSUG|kR7eLDhgN(4CZ zb&V5}SD%*MxOx5XyKj~Euu##-V@tMr-flb#eDH9k{O7;>wEX1fm&>yq&+vU}2j)s> zH<-RN|ePN>)^L)V@_x8)9%HJ=; z*VowwUsQx%r;lS;?9N;TgsXqxt2S2oI)tTM7qN8mL44uEGS^2B;sa@Sxq~l#x9&W& zFMd~VF5}DJJ^LNA8#BjWcI(@*n2q^3CihjN(Qh8P-%hK`H2N1^C;6ED9HV{FeryF_ z{^hv#`eX4NqxE`yd73TJv%LJ_7rbYOhyOn%_%ON)z3W$D;A%F&bGE~n0Xs~kV|jdBdz*&e_udiGY|u7wyZ@FA8q zjugIPpmuHbL3#M#dbxY^x7hjwg9KOpxjaJo(ZlQI$&*{QMD;FCGQr@c4*4d(NEb~q znJ3y8k<#wGwt64s&%FAQpD#s>_1(zBJzZ^N?5hq^QkVQOjI1ZVX!?~T9|C~5V~5n; zP%k4V$n}Q_8DrW2;552)E5A3_1ibE1rhI=4>m&XUu-6;v5YhD{S7RT>9Iav2Xn-W0 zfujx55F|6+|AZ#r!|04e{;3|qr^?V*)vJL`>a=<(4#J_fyc2@FlnKzC1}oWbElmTh$$ z-=Vq9F;|)9WPDB1Bh9wLg-pFB;(pccF1_LAsKq2~iK^Qj%hsdq6kMBBV;-74UN2dG zr~^e4rGd%`pq7k;z?sx>Q@R*IkTYP2*PsSDvW0D;A@6aLvhW6~QDEN~F_Ryd2aM3D zP!U07d}_P&Ca5%+m>^@uFrdhpCgW__GLR~l>E0gkz)rH+HgJe*NAM*k-OL=uEA)_L z-x`%UpoM`KR==Rp2yG9InS3QtbmT}?t=z#%BCy#oyyw$zcO#ss5`qAoz{chYd4hzv zu_7-GPt`9HQ^rHkV89mHBvpV+^LB{Lk}k0%KJcq*ObD2EuqQ1fZ!3glbO8e|YuRU^ z)xj7R7V(W&Ej2jVu=Y}cY$G5Y&_K>*ljEr!#vYLwRGoa_Thdfhu6bLpMPUjkV$k|b zQcqd8F&Tvl16j+X5vx@YvIg^RUT8ERN=OEvOH?)mryjCF0bOIwFUQX^0&IwpH9zIGHXq@x@b_e9~0TdDAt9xJ)5oAUF+pCheyw ze4!*EWD_;AL-oWrMzTXkbp2%Vw0!7il_6xZHNY5?f@~J&b&`Rre)RVNn~ILKs(_4iGCx_P z3;4Ga6Dv4|WxEYjLD52 zckIT2ajacY=A=m-YE`wba~fZAO4-KVS4DZrbeah{BtFZ^%TzC8bP**qUz(xkLztl< zRu3e4=hEPs?|7`V)P``wQdOhy6jqgCrk9hEfe;Iufk}~a%1bYoN#oJv)Q6ldUX-$G z??UD^RiVr=I$ zcCa~K_F^X+yPwZwiMlxT0Qp4+74&O z@!a+hR?pkNZ=u}4e21I2eMQRa+&&sBRPMkvvl}Z>E@0x`_OTs-?Fh^lfv@ZYJ^xWT z`NeP=eZJ}rrKVw#-X9YK3cbMZxGbM%`8ln=TXp?);mGbZ->1=~H!GdT$9ZLFXDqJL z=R?Gml?{CH!_TP~JInX~@O=5^d#7wR`Mw3b)rju&WOcoKa`ATg=U-nfE4FILQ!EjS zZEx|xMVvek!16Sk#uz!tml4)inl$G37>$*fM*pHKNV_`J55{PcQ7Refi2h}zo5gF) zzs+=d--SVXU(d`FpE7(f&jCdaPVU1Cs%P*;?}H0x%iCvoC zpJkkm`wu5I*n}5+cJADd0fJNI%-Iji`S<=+IrG*JF<5X8%k7`2tL7Ol+C*56!~X7X zS=$@dYY)r4JD--@SZ@Ey=RYl%Kl%T#o$W6z7JS3o-sH1uq+KV(9b%fLO7l{Sr^;Vc zN?&s9X4#S7z0AD&z259HVy1@`!|+Y2O@19hE2Vmo9~grH&8#OIqNRJA`{4q_9Xq7% zhFr#FX_4G`m}NO(IEOl8xboHNq6#GA;u}ej;>)3qE{hTy+MQJD<*7H+5mJ+sN$VFV zsH~hv!}WIJp-|7a`lk2`dA$E+H1~g6yyU6)s)z8&7tLiI&(BT~pE@n?yxPeOXS^gV z>JIG^qo+~Yqhph^FhOsFE9r4%JxtijZyHnkO*&3v6}?lqe=i+ARhEw659y)9xPR|o z!s;4(@fqq{;MP{)?1kFVJgy!;eCFCjy_m3vJU%S!&)U~?s-lM&#T_vlf-N@lXiey1 zAwF=Y0vNEBObtc$d7F@D6ZG(wvu%fq$(lB44-XePNss^NqMrh-H%u}gNW$ar!NesU zG!_ZfVuifb)di%V`>u(bQYgZJS)TwRe~4dwah5aX%RoPfNCs@_MM|FPWpZ|k_;j+< ziq`I6%8tp|eJUzxP7hn&C#5-`_dUZ1aoM4oSBc1piK5+4!g0$7zv>W#Dz$Qs!J@fC zet0}~pQN-4x?RCI*|^N&5T9wL8?)NV`p%(v=pZo^2?55CjLn>J<4j(oRV+%m*uV=; z&}$kYASD#3Q@@WGtjjbD$W`E+cM#VXU`y5SEdVmhl)yMC=DEF$C0R(der23mHUwb- zz_6s}q7)&($RT3%IZ1@xtW{=O*xj=lH=2qV5k`Hv@jF{)6H;)+%+?@;Z(SXX+R@~; z($=R*Y4H`GhO8^lJ7OseNkAs7E|kw=ln;UGchwDEa-Uh+F_oZ4;<)Y%@{p+f_&g^` z@l^YoEkkpCD?<_gol1Mkk>+h=VuO>@{Tc$pAl!j`AIT~Wl~gY&MouBkF<7=EwW)EU zKJA78sfd9Zq(caxJ{hq^s7{PnJxwC=$M3tMdYaL#3fU>uK`90BXF8$*W?-8#Jlv>f zg=wtdHzCWZSlh98Ms{o5fs=e_ATx5EO7>);Jqcqz4Dq}wI7gjy&8z?R(v2?;72Ps> zX6={84p7Svy`w>WqB2H)OOPc#k~$lT8T2guDgp1fhv}p6;~Ow7Fs)$H~0j zn22YS^f)mOgJ!mUZERsnJGv)T2S7v2_>vR1_v3FVPRe`q;5K$ZxR1$rcd%{ZCELpO z{+-KJ>rKfprfdUFYP79v@jHsEydeKx zIgAzc_AVT%OY-xZle*QHw}ZMJfvVg^Bho?83co`ZL?FbKJJ;wjF_2jlj$adinqwAGFxq z^sDM`Zee~2`l5P_`IY%&sGIR$m(Cc@7flr0Kf~#eSGm|`OT+S%P)U>37>g*`(+Q8 z)Nfz-WOp;0^ktT8j$zKL4r4U&W~KA!QHDpZgJ?b71ZSM2N35Kmp;B3t&puQ}WP1Kq z@q0X#dtA|9XZor(R9E_>81TESIm{DR=HYDz-i)+MRYdU@05IdzrMaBQbiK?2Ofq@|n&uj~>IR zo1e3-obNGb;X9AZxATW2Cb&G`Rle~De^oAg_^)hB+at#=U}D~BOzPN=IC6m3ug~V4 z4{^Eu9rlHsTiM>f^SMpTyN0FpuVdA`8&`jWZ|mHy8gmEGMwdU zs~#y^oe>kfLH`N8q5rCZl$1KK*Ne2FT#h|i#MI@I{WE&lIU0 z2mDkg>wAF+gN4qEXpS_S??rP);o;P&%M#GX|*}T zsz$8F4wP6y&$fx>7P$D_<;xg}Ai$7-G%f>z5|pdHi>Yd+Vuf~Jg;I)L z0vlkY0z{fyqK95i#FBCHKuay-{lk22yxgX$BYm_3NsB#cGU9BP3mOuKwkychsm}ov z9$>0%OioZ?iXzPxOFYnW1G7?c9?z1o4~Qq)%`hgMQ|uk@JY& zK+5^q`vH5b&^iypP!IWPBmuHufkGNIt5;|x^{%cIfC6n*edS%tqE&+#fWn1Nm@^kG zU$Icib*K#xHVq?r#%zE|&0MOslWYjOspO~pYTmdca2?(auuUs$V6i`hc|&RaDmi8- zVI&gDsV}i@=yS%m_Hq|39)7)vKoD4!VUdj_;?Q0#iETPk47@7D40#f64EL3uGZw)Q ztlH*+1-sTUVU5vbA;(3uY3aZR3MISB%SVr2MuKy6pMj5R=)SF@Lk1|td0)@W3g<-g zr=xLJ7VG4p^0BL3c&N`88adX*?Q8kxE9hZ`JdSN?e9JmACh1|)m`}#TOWrmi0OKu6 ztsT4?CQeuAvK1@?+t176hj(ov-bzf)yNk(q53pk1V@%Y0iq)C;$5oo%w1boII5BTw z0l%+oQXb;X*fNL~Lyt)Gf@uEK-umudXWtFG$UtvU=ps#C-?tKNxRP>`O)_$K0Fw){ zZ>BS&^94n#znN+GsVrsJddMm79WP5oUUqpAC?729%GQYH1gWX}b=3cWi8) zyCU%L&o1^D#nrDAVm-%1>$O!*w7!OwPd~%h{J)edpZ-nx^q2oT=4?MJJMk=W*X~8! z!f0#zm=yurodF@{?TOE2ci~yanNv&Ut+PkUyV&-25i5f2#rE>-OINPn!*i+Y<(I$t z+$Q$z+ke0&@l9JSVwT-)-gX3jUn0Oe5XWL4V*=iO%xQS*?D6uQ56_i%&K<*?h26Fo z$jZtZ<}}P(~tO`zg_;_L?HKpDky;1vCdLsgpM3l>0h=a=PMw$uCBm$NMMY75b4T^Hm%MW^P2_7}E#&x^8%YvTR4PnPp% zj+fJzz{)Ruyk59!-c2lxd<&EE?qZVO@`J}^1*=Z(#t%*ooW0KPczqpJr(BnLX^O+E zq+_;-*V9?fKH{!~GGoitJ6YOQ4jx=AhmO2cj-2>Tx$wcCl{4qRR}LS2t1MtS{oT9w zSuE|RKYx|l;rBQHp@~(Dunh{gvR%G+5d*uQ*yOz1SS|12gNqpKy;&YVet;jQ7~I54 zhVU>jhRK-Uk{J5$)V^NQm(J@><--?choQ~)ZiU+IDehaWmDD2SK4fD(0IlG}HlUNR zr3aG5nJjaNI>Dr~ksZaLF8*a+xIwgZ$P*_u`Elu_IGLqC$;B!_R~1l-B;Tt{%2bcC z3n?Zh8g}HbbXd;$1e>yws3AH;lBzN!Oz}~&mZI+A`lNgaPS(l8I>6}) z&9vuQ4QXbQ)~Sb2O)kfgvgC}_Khv4FNm>{jc#1*EXZZZ!A1CZ_Rfsq7*t-u~zb_uO zt#1z=I#w335~@wq!>W3VD3f;ZASUbW@$F9RvjKt9_0nRF7U4C-KYlLr51%7GK~Iz4 zSY|6sKfEtED$f-^Zhpp}fN+BPRAWQi06-xTw&^4PIM8ET;KI*mPdRCi{eW9`A;pBf zXCY^v3Q%x_G6v!45f~(xEhdo(DanRP)24BK*)H~?i~%QN_IYkg!Ge%fv)jG zXmH;P+CX@og0)ZxhEYNxD;FHuqUMYW+jLtDQ7yDZJqQwbm6q6(Oi4jVa&imh>7Un~ zFqJb&dBKNUDn@r@mrI8VibP@|>s-Z!yfL{Pqe*>f7#2=e7LhjOLRBR}0 zf}%zvv)T+Hlm$t)Q7JDu6645nP9jCdB*ipIDr-KVkRH-!je*XPQBBAtS`;jE)>7^` z+-@AUW;@(ZQL@)g6Q^ldPGnPHAvA;T(Tl#4K-H%l4Z^~aJ{T?XQmjI*rZdR3|2Cis z&5SgKWK6l5V&Y29@c2kW2rj`hDdIM&=O(2?!fZjy|GQtEed$ia?rr7rm`}pXax?Q% z7;~WW)o^tENKergUB)_H6DLh_$a_1=FnU0zLnz}yN9L>yIh9SI7G!)%+Dr`vNFT+J zop%2dg#b>?6wrVps4;VsrkcLXORKr)cyVnTxwinD7TcyXiRF%&=7bZ%Yx?84>|4`e zPO<3q%q>&H$y9JkFumWC_rzB37OCFKE0m)emY847-T*cCx!mbeT|DdOvA*2!!YFZ& z^?EfLC*)~myxn*Ko8u-bb7G#2@nT?$TitR39>=*LfVgrSFl}qgi5B?7wNtM%`ASou z``8$+E!2%G<*ht#U zm;GMQ=*)Q^p8y>SsYEgJ1E)Dvq}r!V-F?ga#-q7vG>eSqrKng*Y8HfaB3+L?fHuBlup_$evM5nCsJ+!eV<2fQW0+O8=*Z4B2`YuVEv+vnordP6s z*2nnD(+%thvGE+^G>7qb5@R;!%F&bGEXPmr?^|UF%kO+qHy^lrp~&$N zzNENn_i#?syLJ7S*x{uE<>V1OyT#uz{IP#=SEn1d?v<;zmdll!5AiGniy>ePY8O63?6V#p z#CDm!fic{!^4B8*ysO)IE5=u!Vyu;8!@G99DDR%fYI&HndhRsFpRtn}`#D$5yM6z0 zxrt}C+~I5)TS6~CT*XTdeEEdmXSeS;Uyoj~U5npU1YXMoJ$`ugUy89OZ=htm#8s1pl$7s|aqsxEcs~)1OEdDf{%+Gn@kT!;uWnX80tgf^0<)?hys{R=E z>%br`zx45;;@Y~+oGGV{FJdWJ{9y^@58pYB54s~|X>ot~`=5VSe)fyc%C#HVdH_o< zKgVFq9vi%I@9aa4{gr^-k$uhiPHdt`SsjX<%3Dpd-Iu}5`qOL+_f>tJhCeU8_jO*s zdc84wv#{pT7v^TNugaGlrDJ}xozz{i>$6X&z{CnJ8~zryvHh!m{rz(0#1Z?F_nS|z zl}lJH@8<30a_Q=FS$@2Rx0d*ZF}47)A3T%yYT2X@>JwwMSw3cY$z`m~^3oxWWJSyA zW_-%ZmmX(~Es8ha5?|>x5|6p{EPCzcm8cTv~(8BOCPpnsoU0c zx{tJb0DX9Y)swg!KPTZ`y@YLSKmWUO`}(iTy_^4nRrEeZdvNR+OI7baNRO#9IOHpd zs)uMSPsjBMoYiD5XhaS2QD-!-3orp$3Lw@%x z_XgCFdRNS(lyR&M-J8awsqMz}4N5L%o92QO3|Fzy19{X-YQ*SNjVGG)%~lWHGCPK{Q-^c>ZR z&2Ks)zr-U!b`47v4+uAgvnrqHZc2U6vHiLPqT#qT^9f}^7?sMhH6`EVyLeTGXNX^+ z%vHsM?MH_dUt^Nc4tFui0bKE;PSDToP*nz1$T9)|Y|TPA6glBTcuL6&wyQ`lh^&#< zB4Z*yh+V3N^b{Bq1u<~d8aZ~Z4FSq{;H-_*r_5J5Y2LbQs{@&CV9@4Hs*tRy*M}Ab z7(yiBV5xE@^vMKQDrVLTA`zn=H3n{1bm0@rAi$_~K|b@C`y7 z)dAm0!*e!}IcAJD!U%XKTJcT5e5;?oF5oE8Q#)qZRZdwLCIL#i?EwQ!CIMccj3m zl?*-tq3pjhE3{PrzQ4O0Kbix7wPUIakoG}F% z7ma1lE1vG>4s2hD722=@4_D#YgBKb(DUTB`JfzfdUFNt_-VRL8+kx-(J9h3N!ThoM zS#8{|+%uCPnSc1+ZZ9ukD_gFvbO&44-oEiEUb4Jd?%lqG7cD=7g*W$L4DBD%J&5l< z>>}aY*ygyx3xVeNv#}bt={H)+`Gw~MIgr??D%N*YBZ+#C zQz=6kP-v@OyJHem58N@iK^uA{$50wgzVwwCql;#K zI)r@K)bD07Y5O(X$9Et)C@8KZhBNgh&`72tQN*g=DLdAg;(J z%C2n+_ZkJ%KL}at9{6?-{araWQAbXq#$p^#*UIyar{&<03wW9S!*c5E_sW^KzK@ml zzE_T5qTU{i?f4Eg1Ylq%fb5#!%c|>Zk1+B2PWi=8|JU*_zxqI2Zgrch8@&2}(ywVUN3CMgvxe86X0yW+ZIm~(%8(_^|_|F3=ocze|61M|ENo?<@2jvdd+!TmeS z|L||VU%vJJdE^&twY<+R-@<|_%jGgAs@}Nwr0m-9EM>upNQd6M?}5 zz5GQc=QlI8nQZ>B>c2c~hW)zuvwWPz+p7I}u@sY8@t7CuReapc#%BC^Y>#1#(dNOL zS0>}<{5<-U=b^UQs=jRSgO0P&`EL)lGtf2#2lwu@$$IZ&q8?WfJFstWS$_1mT<4n^ zH}03an2mn_0cN8=Si#^9w!hs2-SBCaFOrep@*}6S^s`)_AJZG7^?GCF*Qqy#HAWlr zV_q8Zc+85cuzK5@m9yR~8}eb6jIfku+tQz>4$>b>g(IHuWQ+ZM1(Q-axsKaB^9`hv zSUK_}e&leg=Yt3Kl{@I)7q8wf7q8qdkDp)*Lq1@~#j=4PtkmBb6I6vMTQ8GN^Y>-2 zvW{?B|D2ahrZKNLiT^UG<8YaKtQz!m6?So7*?SP%99}4=-}+%W_s)+oNrP|Vyeg$tI^KCrd*TxRjSv=J_)5qc^U!*7bEGyn-Y1Ya4F}=X8^+f+ z$HCj3+@=YW@c75An)c$eU|}z|Fvo+wbMJhoeEYk9fz|eof_A|Ak!|6`dpN>ktz&(x z79v;{XvCV6zxf8`Q+$cVWIZm?YE$IU($H&nk-jHHKe2~N>NDOKpcpow6KZk0FuHB$ zWFo0wS5YbmC;9@uD{z&?XM8|uZ+!ACXuMr+6ZSZ155DrR+70ta;!-h4U4~vSIVNkg z37RlM*!wkmy$dkNR^BpK)d89KnMbjyJ*lYyyi*eEHIe4=ip$(@{5aMxB_&lIb zG*0;nBFQc_F$W%e<~nI0?AK@jR$O=zvZkN#T$zF+U<2gILcJcL?s>?VO*ibeWV_)r z=VC5L(~MPUS)SGuBFlr@$CGqF)K?>%x$Ohh@T&{3B-7TWL?Iyrf=8-fH|RB>DTbu$ zsG=d@2r|Up*8HxrPaS$9J~AQJ2#U=_{XDwy^;Oo_)IE9hX{iaQH%GM-NW^HoIbdpF~3 zwpa<~@jV>h-#IC7Cnr&G^}L|7mi`xJt=`O&&bH#qDEw~z!dAmu#+J2rF%j?H-7DC> z_J*yTx4wqeeKyu?{T{5Cw`b1+9v)+oBx24#YYS@+Mb#o}VPfx8Kl5wQ zhi8?o1-m^t->4Yy}Wm&h^ zRzP@9$H@es9Qp8Q#WLa1ZDB zJn#QIcI+>^7M98(jNO0lhyQaqf8j^v$np2$nW5jW>Sr`+n)?mM#JKhC}s}xZJzIzPd>j|9`xdtA8H%B4^KrmU|& zvh7!}yD;uI96;boOa$2cI>_->oJAWe_dK(bt~c@J{$?p{NGIpV^m>0@R%T33w9R~A zOx=&>w{&NwYEo|!v~ftI%)uMgZGa(J5lyEN^WY_8NQ_KtYHlnK5|QUf)X2wBN<8zi zleD7Tp&;^EyGNdomsw&!f6Vupc+yX)rKI|H0iH#2Rbf4>bIexPY@PIMwYzl?Glt6Qk*{Y!z2Q1P!|(+{bthz z`Ebg&ImMPCoUDxRQ??y0A6n&moeaK#>8Z0HmJh!D-C1*Fo0bQO)vNr&oW-(6)fBcuS?7Q zP@-=h_h5)m=^-!(4b#DNrl2df97xvwgrOe!70~7D3_I|o*ozHxpvHq(katmGddbdF zahtb6LL4SZJr{M8#t#DIGMem2-?$VA0Lwh(%=_jZa*I2i1|qGr@ZDIEqJH&-2{$4Bi;XS;74#%)s;D z5yV>yT+rxCJ|yNeFS)$zIxxk_U`5z*2`SmCMSVV9K_4ErD{^u_uu{jhi&ad1*EGgkr1)EyKDjOqX)*cBx3Tqc+1+>vo8PUul`ZoO9{>5*8BW0SNqCr* zhipvHlQ3?xy7HhrxPQa{uu|TAOwwcd@uRyYHvo9U z3f@MXh=&Ol)*hIkhnH1%pCohOM;7hjp;r_d7092l5hV^rr@WmU~44|kNO9Q>?3$!yprH4LwqUC(BUrQ4B zgV@U_+3pRAFM5@048=zxs=vyfR>c8%;7jIEyqc()EHTWF!@CUCXYq$fsWfn66OcK4 zOvqc!T7TF5Iw?=1Cy}ps7CitH=lGK82A}ca`N;0QSgq~De~t0{AC}YSzEhTtU9fF$ z7xpdT{)O$h`GP$Dd6VL78TLZ_!^g`8#_sRl`V_0E{uVFYe^l?U2OMx7|*xxky0K$ zUd3u+5AE+ZRug-)vR2mC)@|Yvo!oB^1rd;`E@0e|Fo4Xx> z?FhU?1O^lIwyMuBNIde7&)e%cqUSH+)(D<3oeWo%`AVdk{wzl@EHm-E6RZL9I` z{mQssbY6Z>^LaD=7wz9X_`Q!go;g1Yi?hz3;7d7|x<0nFul)I+e7k)6gY)G)-p;uD z;4xOudyL6?kIT<~eHly2-Y$#{see;PewJjFh?Pu9_w@gwHY{@vwIzV`u^ z1V3rpIUmK_7(DJSKPms`U;n)P!!Ivcnf-V_-bk7U-xRvjEIsnZ+C&gNe-@T>rlmdB zG`U&yY(M9-4&SJhCkkgJFULi@0@2HdL8k} z%eXAhb4Z7>8EZ^evSV`M^*E$UUNS7_d~d7Qk$#qw{-iyZimy{P*fKuQ7jlpgmieX8 zyw>D~L21dkCd!gEf`5ODo?$%YO*dpk8|otV_?KV^j>Aq)7vD;v%Vv|TIHnzPOai`# zRGkJfPVki<069in-A?tOUFE}JZ+Bs3e?q`)CcgBTimdv0S=1JesI8a#=6anKxd*=2(wmQB2-sOwkN4I=-+-8y;@Vv2xy5 zP+P}$sHa#>&nN3)LfKgChJbHG$@(g4di>P8syFOtvWJd|7mXAwObf zBF?vdP`v~r)LXq9O*Uyuaz<;FseZA2C8IjBEL_9rPBdLt&|^O8NHLiZ&01YV3Umim zn_ZMhaH30EJ}PSz68x~3AuC&=Gc_N_0ufMdQ&<__QLO&(n;ge-dOoY2tN&sSY|ua0 zkqKra9y{^9ka@$;t|(92UF^(e@Qs&nW!~ZeI#t``UK$RGDfo(`Oww=Jr%F@j_2MN# zv823Ni30B(Y#{GXu0wpuIwkzF#3?PdjaPk%ZYXtKMNx-Tm^?>HMFVFTs5M50b5mHej)XKQfps1)rFu~Sq)H)Mzsv5<*Cx^ zPtdB9%0LI%T9OU3>1Y-%0`X{qUJjD%(gPP^|8^>CneV$6M*|Ho+H3$xFHC@M800`t z@$~4)O2`sNb*M=G8UZETyX04>)oPhc5Gfs4*`c;Wgj9uVT21hQDultC6qP%3x6~k~vlQBq1Fm)%@2DDLK0NXH(ICMq!WZ%@H zzI3_i1u-yYCEn(&BLQTzX;hoFhTuUI|LtftSC9sksS*!F#-)JNut z*-)3INo`rMzA8|P4V9fE^~@8^(7jOm7HQ}o&4$Aa)f?ZrJh%IDqYa}We~GQUYi5lZ z`S9EH?M;{w?Nql>BvmBIwd`2EMp1@D0T5gHp+qgI-imK!bd4uD@+W1bm5q&5tYT(4 zM|1?zU9a8`@{KI3W%!(zD=(dn8JPwqSvfG28?8eUna1zwEce#~VKObj{$6CcGAbwQ zJ;HOp0|$?kGv_`i-}vtTRL;NqXIM?|-Lh~05v&A?m+#eQ&=2aW#_ALHxv?8#9jlnc zeYae{{Ey|@#h;d|7yrIo`{XA!QE%TO?uoni`o!?xi^zO6oYd}6>v(Sa0?%*%{Kp@Z zKmLQa%6o60ERUY7m;1|jZi}}2?dLbk8XHCy002M$Nkl_3-WV1e!5nh+=j5(H{W=B3#XUy--_|%&w!?!QE z8ssxP{N`4+XYppy8LXCf3U6L4VN%{fY}?F1Om5kH{nq_*>&|kyi4`FqJYKUOEnG2= zeEBu44)kFw$LqjRo#mVC_t`WQAg?2Tie0Nn1w*{|O3>~H4vI*P-~$n-k1%JgTNtyy}ni??~f5A!?} zdoQIk1O`~yU=qvw0X{hLN_+~7#=PjF(WP+@hW)$OBNmTbCHJl&3fCzf z(`Q^=0xPI-0<*26#?{lfs^0dBdKOPk#yfiKT-m#S(FT5xpSnB9`5c)pirJ!`Jasp(Lya)wBGIkBtMKcXoV^;*$GvcupRiULPK_W5zKyW zBrqzLsuCE2HLMPx+Yn!S;aCWJ@@6t`;|t zW{GBdFc?=q(i>-}jqRAMxH1YIWycv2zrlCol>vwWf2cA7XS|^HnGqD%6EoE!`C##< zFGlOx%6+Qp01y9%jthyop6%2ori*uf1!O{H3mLsNT zn;UDkRP93y%DcC86|*BksQ1YRB8SEzHlTbQ zFcw2eGR_^6nc$2$D4pbOsEn8teaMkSYhr#ap^xMIPiiKk`eeH08|Vm21d4Ry&?uD2 z*u(xzVMNCA-qc`Oq0BtypAl9t)5cHo&EPnPn&hjJtH*rr$7c;St5^yE*}Wog1HER=q!z+t>d;&>xho&Byqtf5EOzuTBhJT~8QdzW3tuBd#Q|h*`%t z^VmVWyS;e!NI8FU5$|N@Y(~A?_a0%u-ij@M``MK{R;fAME9{-eJwsfNUfr6`Jo`RI zfag7qIoRCXvJbP)oxvdB6Nk&mV~gd;;zBuy8T9t;-Gea)%jFuDZN72qAy)FkjGXAl zxV^G-(mdYV&eALbvj}`M5f~YuH}1yfn{j7)EBza-etCK%-p@?FY3uX1EAPbiB-y^5 zNwVX3Ll{G}o#<0`Cm7?fhv>vrnsg&QjeTKF!l5n6F+WZ=%TSJKoUC)F-|~iUZH-f1 z7rVjx#gEUHH(x(tquKZ3D@}%Eb$t^{gWj^gM{8@Cc9v5pZ`uP0JtUgmVv`(-onC?eCMXZeWn!-2kFMUB7bo-g248kC!>Do2o-R`jBVy3(CPAvfAK$* z*Wdh0EM<$CkT5WB&pc)*wC5(+@E2q~eR_rifFEyd;QaCc4|K0#raTPF|WZ00jRBqQ25ojyh`kMkwVyo@}3 zmER4YZD5VlNF%u>W4^Zrbjil{J|{fFdK0@`E+>=OFUx2Bo-Yjo!1%BF3_Xafv_ZM# zK(*n{ay!JWVgs4M2a0_Vhac5Wa@FUa6h-WugbW{eWjT8Q25io}r&J$`1;ON_Vs6g2 z?zanh-s!^OzS^>5*KHUS%a#H-O5APNjzoQyqkNIO_lKtDJ%;}B&zE=i0ePHJ&;HO~>|+D$X_f>u|a@ObMa6iK1% zh*QQueDJU75mX4mqJ$?=SaM8c49EACF9ix#V$vZQ^omg}1cQ345BErDJ>*q#+PH1= zmZn|D`amx>!7&)Rn`Nsd=op?0xXF~KoM9WSYPpSRT$MASPR7N4@Al5#_yQIfK)j;1)!XwuHtWO(ENRB8r;<~s{H zHZ`U-L%#G?w`jpJ^^r79d0v!C2zoeUf=PxZk*ce-6MdpnZx=H`k1700qFsyxVfLaf zVZ>q!g=)s=!#+ty+<>T2vT_jVS42zRLN04E-}gK5X-ozxLduX`Z3UfWL0HB;u-i1? zku?HoI@pXbh~;2{l!(gJR#JDBY%4M8rU?WRcEiU61zVE6(8^|Op3;MSQ^qEOMH%6l z1r;(Q=PPgsa+k3pMk5{2z_~GnTJlVs>rwFj2N~f|z$$``X#KKes!N_xf3v=)&H*s> zJLZ@O+pfb#ij5@QugqaRsBNxbZ?2)hvZE$x;v2ooY5GE;jvpsYvgMMs1RnMvrYcqq zX2K^!9&xft2442Vb&UbNfC{<13mvIe$jD$f;+NE`2+y|xP-zkx+J_KXREuKmJ3}fH zXsn(33dQm<=xWZTkMQ>#p(U0ax-w719TE^?^b;{O9K$%uyNIq!X zjlhr~osQuVNr*MUZbX`*WL$d`qZ%7>#%R(>0?ll<0-4@iuw2p(uU%MvdmG3WRhaJr z_S?^Z^QJ4_I05B8WE-@QuQ6Sp8USo8tHMsi$- zoEeYsZE|$Y>7VaO@6*0bKgZdA`g8-&|JTau>Jxlec>*8h|CxQ1fAZ{4%c*mJhG#Zs zFo18-KH7f`gOM#oXR#LJxexzY*Ga~%@gIl3ny&VsQnA`wyfgpZ*EtNYfK}0W-qaPrfM%On1ZEL<=@EG50ebmHC+D-r zq%-kiro76=NE`8oZl1>BC-og=C)wH#=tcYTtoEL#4%^w>DgSwVnG{dq(x0Jzu~R?$ z^1|N^dySquU7zX0>&aQOIGX(wMz_E7#))$7^if+6elG?$@rB|iUtKRBe0sTj z@bRTGk1t?2=!r9u^)I0P3X-{LQlC4?XD*3Yt8MtYPTmzVqyUv}94R##W?<D87&LLRyz}#aQ_jEnFYp8X=NMG+cG-XMIKnz#+i}zc zZl>dh_QpCM?&1r&+c!TgSHAkE^1(m-FJ<}u6;up^d-0XtF8czU50Y8m{uyKxS(F?L z(sG=#jObZD({f&V+wHg0*^a**{>$Mrw$nb~O6`E-mCe$BsHUGwdSFSPJ2GKPeQKmm z$sKu>tQuM$J7eUBpIJ^a#OnI!r6$|8CcUA()r)ap8l%>=<)Z8dqSrn21%*!Ug4_nN zUoYu3!Iq;(Lg?#J%46T;g;iq^0K_1#LvL!9Gkp1*44Yo9kSis&A(QIZ?yYUZaUCm^ z$^OY7vum|-1Eq^WCfrRQ2m>XY5Lij z3MYnJF@grycGmObOeH>Rp1z9aOpF?|x5*`O@rAj4i~#%YLiik3R2m)>05BR#DHSExoHQ&@XHEE~euPS<3DF zRclCJ!pUkEWoZ&e_!gG1Wfkk$tv=D51yBmer`QWI7G2 z1|L98w1a9eR@YH7bf5*_$ZGmtH7ZtLSg)jC>C|H!vaFS2lVX&~HJ=TW7&hpUp$2x9 zmq7Kl=@=>bC>{A~?Hu~%bCQ)zg{QIt!n|aeR}$a)NEB=rJuB(dYTzIhSvmS58rwp4 z4HmXsH}IpaMqChq@{yN537&T=YOkb%EO`FEwx>JgI+ATA&sD6(fLbf)XfRrky}pu> zx54SoL+; zymfiihjodb5=|R?!MfoL;Cb*Rq8!Wh7}33t6HQ0)s*1#}M2bW^f?mxged<-eA+6Y% zEajN0q8IBn_*%}~=+db|<1@8h5Ru*Oq&>E7ME!_59MHvOYUk%MzzZ)hxuS`^K(`t4 zI13vG?+HCw262}ySBnoDJA?Al^AyH&AhC_@1%2?jg2C#e`&hpA76ww>DUTj3m8Cmh zm-~0Gl@*k?@R8+{7{Hf(AAtTrkobIygC+Oj!_qnYmU)e{C_%VnJ3v$&>k$uv3%%ZOsUL! zy5&b}<@VBp^4ZsS%Jtj#%cDmtxUk~El1=T;hd;QuehW8$vZ-Tgnl_h?+D^JEKhIv+ zFA{T{KYd&M@^*USKaa1p+1DSU$=1-`<#9g8Kfb)?Ol}-B$`{&PR{6(opTQ51ljY2* zqxLXU1M_a)!Ia2%xE%8%EM>b|R`Au(ZhnDm4>~8`genLjUQ-uQKS{q4WPkMVcPB9^nA+v`ganjide7!ObA zrTg}p{Se<+#}w;JUt&p=&+ygUhnU*_)3Ssq?w4+Vf`JDsc5}WPU(#?15Wfu{?>TYD z)mQNtuifqoBs+xF=c(Ms&AQ`c`Z}*tx%uDvRqA5antW*15+CJzS#8~T-JCyc<4>|) z5{WR%OGKvn^q#1EkQ(>f`;(af}%8yI3Ckd4)U-iKB_IGZjLq_sO6Nrq#IQb(ybtcrsQ>~#ilgBP2J5?Yu z*_Y-`@;&6Te%8tKKDNh>RQZ`LH0g}jsLFNg)-cJ(bqm)n8+64NTX+y*GgNTtTU^Ix z3Gqf>*@I=%7cfZgz~XUSbFef$2Ig_V-oeFVmOp&-wC(rtYi+#9gXIvck#(1 zr`fa|e6R2Qs?Csbfrih@PLo1i;!*5s3TlW6p1s_ZuLLqV7qG;>s(&IY*As+P5@|?`?jsQk zAi{BCsJ240=roEqZiwgIIHv4SHe|T7>RCC6ils0ua9LRl?xExVTRk#6M7XPdXQ*md zL=tkInx0G)+14k-;a=QK3#x3OUP2zPk@SZl2W$a;r4a|D9x^f>gA1z~-R3e*g+O&~5 zJ)2}zVP!z97nh#=gklG3g50*O9RTt89)Av^CDmzLs}P{!e#-ho{E*MAamRU*q0gN` zkBOH(VB4E)>@#mPKLjHG#Z!4y`&FV?yN-$DB;Ko3hwKaA4R95l*LFWGb65dk@zC4l z@UgeC^zA$4#F?L#gEqMu2B%f9{Rt zY|$s5@Z;$6xV(Ge3|2Wih1v3sU@-2S!9RGoTDTPRb-c5Ez<0JNKg2*jew;GL34bt% zVAdUq)uYYsHazAx%4Eo$w?qfBM=eFbb+sR|N(Zc|}Q^yaLx8FEv`-2A-%HRL)b9|X`rCh&tr>t-7!b?U@ zos7#g?;3Gw=EJ6MDk+|{r+ze4w;_MBZr0DT87t?r4D*?naplvHJZY2o<9HcMwj|px zQ+5(fSkhN{Cwt=e^@nJaFxhq3dzfsG%`UuDpTk!b$ByhTfAj12F*Wk>GLOM`pJEBy zOV{pTu;@c9WqZHexwnE@9e3Le9tY+%B!jcfxES>PZW`lr87t?9G=})3$?iD5u(F); z;^lqDBz*|i<@unNgBMuB3`3Y|{z=)7FXE1t<0np)_kQv3%3DAFH|5ZgbNFGt-v;0M zk8fgjBg-opd0U%nHkJLoJ71NnU;RV5e&zT0A^uT$aOW}}{N5|`81&157JQ&QZR*#j zuc!L;=_=<~jl&wE58?K)WaC-(#2T_Ogf-;fkX)aZZDilk03q6Z&@sn@uK5Li$m(HY z#w1VlobUJ{VD=fiF3`H$w|ePlr(APO)GFJ-M8RPN?wIn0vVzKbvP-#~uhQlNIM%@% zws56GOwq>STbyVm9plwbcrQH3WWPGN2}5?NJCv86uvu13M3bHvw6Az&AF5x`ZWWvH zA#Tu3$1ob*LRnFzDPQD~x0~!VDbH0uQKSt0%`{3?&%t+pTH6{*tC$r{CuF^@POdm8 zW`FwjDoD*VP1TAfm8VY{sJF4Xjyz{{18V$Lm|4ai03SMXw(LJ}7!MkcmNT!vSB{-{ zy&U3_>ie+-E*=o?!OVMnh~w7~H7@%>H(7U4oH4MiaIhW*OFqR6vADT~Fk8r5$+nQ4 zoPy6?7^6zH>jn)%NY;-6)Q;8);eC|4)~~U*{y=~HKm26Bc~Uku@lYEh=lRDO_DCbI z<9pbM>Ht%C^p95Duy38FeoenTQL>#=8o$;Ml9cVr+3mhwirIjrz@roWR09SKh#8*T zb7;c1WHKNHilHfntPjv51%R?^fguf+DJNT|(rqr|%+sd*G>%pkx#8S0U z3qg}@&|ORCE=TbCK*;>E4Fn-6QW&!MRd(}$_hOXii+uGfw*y7cJshrRJ~}y2y$_hY z$+}$C4=L@4uOtsV9;DI-uUq!jDjjOvL1cU~0cR_oCSJ`NvI``f`VukGvac4~#2f2C zvd;#vwgIi0r=k_;2l;roEK-148O~=N;kYt6?u<*dxOroZaa4eApyCsoB>s*c(d-bs zted#L$(Hkf^?#u+Ep^tg=O^$mE7I|JK6`TEPe2TBIvPmpDa(q9nkP( z#7}wVeUiA;Z`2l#TJvBMs>!u{xMl{k3oh&)duxPGu_H$|Du=!>AZEyYBhP#U-FO`% z-fQHS;p9R#pg?!5x-hpc%B|C^zQ{J7p|?$ly0r8m4?AiT@dzTf4KIALS?#hA(IGhM zwlH0o{W{#NwweKtxkCX-wp0_S3g0hR)qz^K+9y;O_{4fy{OO`0gq~Hb`D1p$uU?QJ zK2!r^G8LZ=$}Krd%!FfRDMr7{AA&Mh6-bCAFfY0Uee@(;z7$iQ9Ft?!EYn9;4Ji;n zR#(r5iWTgBg#`Y^pynJFBfFa02Z6M@MZu~LJ8nV-LkT9e#ssZElpiOIONWJeOIzbs5iW zmM~BcgY+KUv}ZU8;Rl_ywm^I}J06#?-8;Vz&%}Kx+fI9_9qf4Yu!+QyS|-z|uYRjO zS^JVfKw63p+1(Os5}(r9pcNZIwkc*n@JHFI7x-lLO;Os8ODdUaSEBZ1%8h7KiE3eU zL_&0z8`+9&Cf%;ZbBrC?8u0_Fx!NOkdIae=zng&l=4c0GV+`cR`l=A>#%Vxzl%!xZ z4*hJ@mTMBoWpZuEj)WJIoZ;p=CZ04E8~4El6gSC??gxOZEk##gs7YEbkD|RyNP&aKB!%ZbDL>HxiquVW=N430W-=s@`hD@^_V!%OALwOeIn9nZsF z!wj%MW6$L~Uxs_O{Ow41XM_n6VBc%!cRvR{-h6_7^aSr|pOk;~>z|eP-@9NFtX#cu zuY7U&w!O2xcKczud~3Pvo5!Rec)!buf!Y~#g25SU7J*p=UU~#N1N5H9nr@7SZi>d? zej7JS+u5Z5s>_hyV}Vbeqmvc{&4l&H@B;tJ9(tM{pOi+{>-tmuy?LJSY9ocuP>FW zH}Beo_wM}_8x6k~qvhxDK#De%a(<|iA+gef%&60Q!hsX`BJQnn?#;i%pol*&r_cQi%MG5xpa}fn<^y4Vxy`Wg zmv&7c;J**rLp!WKC|`epAKM@O=kn>F{!`gpdx$UMmLaod0}7tvp$;E1t8Us8Zm2He z5AmuCQ)E#$@e(6VS*+JsRn*7XtM!Lr9^d+j1AcHwC54wBv$6Red^3h<7OjNsanX>H44 zYN9UY1E|?N^3*dP@)a*!wRWg}cYS2Z zI?1*Zs%{ibqZ5b5AXrTnN0YXIR5~-}chuLgOi#*PA53;6cF=+tdFs=uW5rK8lvB!b zqG>;Di(JcsP6l*QP_b)^iCk{Ncr?CB_*U9bR`&<5;lsguyxA%s^7dl5|4V z{G<%^w6~e{V(H{98?fht_KZ)fF6jF>MjYR2-5o3we$lSi5?i|Mg|?>;WEd|bDavw) zn_cEBCixIhxe9xn98B8H6e-od*+Ut7VwvT(G11P3a`D(V@mf7ZDY;q`wVRCHm4ny!l@$&((t9=AXeb#6YHQOqf zJ?hJ&z5}R{)z$fuB1x{8^%@UK7$tyW=R(F&w5v(z`f)+orkm^9>Hz#I;u&t`msyK~ zTq3P{1>r%zO|d~!hJMnn;iq`fFZnZQ8${BXysm-aq{?&PAewB;H~qkZA7+;#VAeX2 zj(<+3R#ZN$R2`P{aToP6#n^C(+X@^8P*c=ZQeF?YFFSw@0i!TB6qgc+q&p3sHZ{|u zZf<8;>iX?eVkC8mV(%QYL%UVTC?;q2nN!AOuz|d3hfYxi)j*=4b~n)gZ;0x7&JDOu{QETNTI?B@WWUNXlTt*_DDJyEXMQ8A zdYOO>B>mPoSl!l_6>nwSpWVZR19A%3FFt}_jrnkYK ze|(0@XE5-XywIb(d(T`;6yt2mw(ev9$@F{%Ld9FfAuM&_EM)2s<$T48@5Uh2Unv}i&wNf*nIubMICDEl- zCuBfk1N*Kux`xBCn^a%XRqjgFQm(uFB2)3mb*M_yWNNh#+Yu)L(D5rdTeHgajk%)y zBe(B&gKjxT0cNzP2TuGkw!{t>a;$=6yG+mdEYs~RJzx`*59BG|Mfj#my2kYFj-yL% zJHF&z{XKda`Yu-_V7K8l?CDdig0mYR@6R7D@BZX(@j?IJl=E-?OMK*i2r})xUp{Ww zns$Z(7MD$5UAa>p-oILIUi+Y2|N6I>Rqr3LtooO@W-URI*UNpdwfEHz&nw^|Pc3XC zzDM&VY;jY8#@e&%aXGrUue|%#d7Iqg7~Z2jSY9bNu=?5M>vzkYyN_&gi=_u^7(|7^ zQh4_^8>IINH0XE01s=~E7>vi&&i2mjE=Lb7l#@pf;N9(^a_;mIdvD9t%`RQNT|WN& za=Ev>X_NWv#{CMPeQ#ngDOOvy{-(>(cc9uc7iSUp-bdht4bc0pL~6U6HC|jPGn7Oh zy1}2MH-t4rn}k0kKMifW`c8xKB4l>r$4+F&`Sv0(hOoE8-HtzmxgCucV15D13+WbK z4i8b^O#+e0yzE&iC`;5shl#vg8n4=v(L zpZ!=~d0#nm;!rt(nHvx6-)l?0J$ST^!FjjJ*SOicdh-t4@|W}KTmGYhJ$8JO-SHCq zO*8r+WgLw-{qygnzBPo?_k}VUcL;A1&UhYfitz*cF<$P^?^`H)7dR`?F-%eaUO98& zm*wp1e^!p4dJnVaoyQMv%yfrPb{^R3(gZrEujY(-Yisw*gQZJ&@cUu;>a*XJtN4+9 z>)MC*Rn~6YdGEo1-q-MzR@nu-lvGF6GqEfy{!rcfvXjb+MITRP>Sh~~&zO>>obs9f zBJ)G`<==L;rbvKBP8|77kcEP0ssKZ9U`N|bgOb&* zKe}|u0gwRjxY}bQ7s{Z$e0|syp#3`Zt(bfbaPkDWBG-27#G*aW?qj;!UB1_p&CqO! z=K75;ye!+=oN}ym+Zkk#NLD|r_=#urrGowMpGUhsR9S-76!x*}$5!^be$!ibpii%m z$PC+R5^l9sL|9H4r8muPu1PvwIFhoZOmVH-#Mj97QjE*D<3R=gSYP!8$n zZyV&vWs`9&k6HEXg)DBSIEasV-m_cHp+h~-SI?Sb#_?WCne0lHrt?g3!m9Np34ay` z5VNjgV>(&id}-&daW-kLo5F{Vl_U{$3>PcIw5k#F)_C-`Au>i)#WPJbD%s`dI%m-c zXBD={SNkB#P6W$$4Wa7?gcVn0r7CdO%2d<}M8!VeOGwJ?Wj~w}Jw(to%2`joS&YSi zSIVjfG)jdtnQFU{y-x`^(*u;dp*9`F%KG7#e4+zGepSc%fU1-!g4yFU#_m@zP&QvX zK6s%*u`avJ2_jzPz&T#D2}(+q6JcJ}? zdPL|Dyd(qego9HobO_C?G+EBc#6ZaWEZ<-x>DpDs?AKDY>wSf?9vSAc4VH7t=sov4 zihT^T3}S9l(+7jD8Llz=nqeJelc%H!2Z4TDxp`>_Br4{y=UKhUs=oCs;IQF?VPI2_ zEhDIkasLoBTCQ@<4N~#PzNPC4%KqP^l6ul|9ValaBx(5#dWTKEgDajM0&n3~Z{cyD z0a%s|<|m{u)s+yIMY+pj2@bU128kVxsTCYWSdt6B%x*|{Fc(hmgz732LXmk{K0G~WNMtvg(And7$w2TEESFih$AM9F)D$CKOCH7VRp zV0N$fUA9VwMj-FB$Rt!{pP>nTW@!NszYjfdY?~cy*nc6;lq1k+&dXbAP~PRb_~bXN zK;mwyx+S3@oq)^!Ov=j_S8K}h&+E|Wldwj3P!s-0-8zt}QznYmM|BzbN3P<{y!*?X zQpOaGd*YKf&Q7qg+b-2QLNMn?^RG)#wk1}4Br>HE8FqEafe@kLC&W46e|*J zZTJU*n6#~&!cw2&)dG;6h;Qzg{ci$mA|K|X^_UvCh&CjKb9X?pa!0<;8 zuVb~HPccXjtM1(TeYt<b(31ZUIZpefF#_ZTzg z?wxzBoH}u=ypBm#UdOG zO}^iun$_+ZBCy40jrRQUi7lzDceW?-zLpcK9N3SUH1ThB{Ym)}?`tnzy;ZJWzg2L` zz-{duX4b>U*myrUTgvtsf@|88 ztY`ImoR`!3p!!OF93JVCp-#?krz0Cfa>SX$AJWORzW%g+WsISGA9qMbv~jZXg>uhR z$E=?*W&fMl=QhvwvOnYMxQ?5*)io{|wr&IT{_0onm3J|t-ti*`%Y#Q;OMbQ7y|-L` z`_Ywh`PyAfWzD5WxvT>pT1|huCtt?-k^TNA^4s~d9e*c2?i7piic`+h=1#FD`8f%z zZ&$X*`9(T@Ka_RMn#bwW_w9aKe)aRW%i9;u;3jVoH-7u^U~E@ey1!ih%RhWte)CVC z0rE8rD%(@`@1L_D7=2&I`PZip`P%na*pp->qdE$M`%IHu=8uz+ujBZ_%6@I9!yjWD zRL8-2o0!`C=&5(hsn_4ftVBOA=PvvTv*-OqS=fIR50m%Vz!CFPhOlQ{cr(q!{5odL zd-U*DS-SmM`SRocP`>#1KbD)DteZE8ToAYS@F?imORv6$S*C&6_t%Arqz=`m`e(S-;Y9T?9C?cVy}`3_cJ)s`*UOpol zmN4DIU_B0a#=tASXzSbRYR^iHzyU0Q``-J1Q_j8dQw%VF!+N2(4MzN-KVg7|A2_~3D@^Fbs^^2XGT(x#Od_2q|wz5 z{7#?r-p3g>I1rB?z}R05+_R-{?V|_2wB=wu?^AR)p9AGz^(T&{?7G!aN>wm$Q10*v z7&26`W5~**z!onGTMWe~fbs!EQch#pE-x(`R1~tLdA1$bq%V ze$YK^$mgH+Zy7eD-3{&fF_18cVN=4;7bwei_|!olw(8xoQHe}&E%|Nr3Ox@DEG4_d zl&ov|R<6*f_@oWFR`tjXMXL{s(x(snV?B_%@d_dZD5Df)!vf89Bugmg_|=F?tuH52 zX4BmgIP?HRJa2cF16LyEgBQqiIWu7#+n9>Ha9QDzZ%s=(%$q`QV@{CPF$!Uqwq39~ z61gB?&`h58;(eXNxIp#aTnK!#>|hqjgyqy0Pg6@9xnL^Ta34*dq>wd+Nm0UiCNG+( z&X%S0p_=MJNZ51z#Y#G!gwJBKmCc$paot?^V%!dC7- zGDU5VFHjm0V4;MZvScSJIY=auO7!Z9a{{kx*CG)%O|05A7-yp@z?6Q=Rtp)^QMy~! zh4pfn=9ha(*>pZ&9cpU*Ru`O(EsC0>bT9ob3z|WN-zFCqEsw!F$ixD1`%+^Jgzvx0eSYJ2vw{# z0u_DFT&Hs!h=&gscj2QF8+?EPc{V7Id#iD3P(vYBxN6RMZ8$482j{J?EthpHLAbfG zUhZRH-p#9@l+Qo@yR!1=o-59_Vn3kU^%3%h`$<BAwFr% z^|4MkP!mKs3OIJ;wvKBP7P0F>m1WKPeyc2%ge+@f*3W%P(_*RF? z^E!$bnjKuHRRt@`uJl+|K6}+@LXN$DG_cr-Im%sAjo+sS)U-SLBEMay57>8PTIB8J z>a|4|i=^V$g_mbLnfe}N(SnHK>ceH+r#>!+FhJ+s_y2AA(J%h{a`eR8W&gpGSP9_( z`Veo_*}gfhk;n1OL41$5FrmlFy>k7^?=X|zKb9+B{9UQh~#v0zU;U49$|KhFk(|6C63+Ij(POQRJ&>k*7Dj$B1)u}Gs zF83a+qfM~7(msCh%8-vebG&5FXYj951pIXQ6a(!r`yFP^_>cOd&nkN z*}t&2+`a#({Qgf@%m4m=eN-M}soS~vUF8to+46nvEX^V?i@>WNfmeD4y>I&_t#5zQ z4dpl0)P0|Z><-Bc(S~$i6m7`f5bb&Fy=c7W@rAndhpF>}HRyes2|nY7cKpRMZmw}d z&LwOQE-YZg`~tqpI9T3$`(!zG_<((Ncn5>@c%i=c;8FSbi<{-ztq11kJf=~e$6&ol z>D!F&^V)u1{h_!Jlj%ja<&d2hO&f~E6Z}}%#EauC3{2RIFDcHSJyFh|K3dLV#=L`= zopE8`UVQPhr+kfNlds&ki!XdI4fnkj{D|O>20Zx0P2L{p_0y28S0!%pBm2y@6g{`A zXp|M5w44`?XetlwIWX_Z)*9md6oc>(>$iSg&b;xfatce?9y#`AIe6%F*?-`;ec57P zYmYoo=Z`ZU_k0Na5CijW;%n?r%Js|tT)zDD|0&D&zrYf=w=mFz590a2T0h3S-fUN| zu5>%6k>0Ooj)(N7rAa>ThkO%1^F$lk)5mFLsh{h#6TQCeYAD_V2t{FM zot$^QaZ*tc)fHBhvPPq}2jcRF@=aFt4-^#XCkIbG#`#t=UvT-`$GETB;$Sfj8k?oT z2<+K2kJ+fj$(Gb6Q|z9411vMJBW`$7Vx*<2Jf|YVC%G26S(ym z_M7;#m&!IskF!Q&fE#Aj;~tF?{fiis7edRLE%x7FS<6R3G_ulZ&iYByNQ$A*m8G)F zSRZ0Pia*|4+1Jzjy47CXqHi$|-?_IL_?Ty%SXWB`^Z!xKb?N1)LRs1mb)i@}a2#43 z3cF6VDFBI>MB9+;T5hK0LxC?Yjo6O6tlg~|1w#*Zs}EAhJ~Tt(rac5HOAPLWStX=g z36^2IYH(VuR7V$jbQn8^h2wF^9u1NA5S?$SLs6Fv+uYU4lj%t$!!%OJO8|RA3%gZb zd^yw6g@!o*BH#=WWs-y*9)^}vGtC&ijq_dB-Wv+6C#Ry?lQS97HuXF44j+f?)>u*t zaxxVvUKttUUINTlAr^A#GVC2YN&JJpLvR~%8IxQ%Vxmce6!Q->oy$Vo@RaX*(Wuht zG#G_DwrQHjljy+&j3bf9oYPsV>d2@aBuyAga>InVX&6rz$Bzizg`oUzwH}C06JV?736bMfZAx zEGW6bN~e@@z3rUuu|X`L=PE&$E{e4sL`!>@PK3zHjc#)@+LhB)0$CjTVUk8euj?3z z0);zt!8t>$Zb>@oyRA1!J9$w_c0b#7qQx6hiVtm4j5cd~Fy9G6@M5RqhPv{qR?Hd& z42uT$0jtUdp@t&UP1-k(?RoPJ*(w@?Gg|eU7fsu9c`N;DJD4NqYzU4-*_7N9o>PPs zkpq$`wPH4GTx#_Fq>ySyA;?(Kj0I$}gxGqW8%<`3#DRw#WXEStTn*qg4A$ba4KI`C zWzl>lJ%296W^+%NfuDTaO$rAp*k2oz=QHH_40|qP)|>n0i=#QG*dV;kjTI~fxPoUg zkFb&fRxP=8soYz-h7Y1|<3&8_50Srvfe)Kfo9#Oh@PU?lFgV29l7sWmrf6rjwXMj( zhD=E%@3G$hy1iEtP+}HcYkPh&Filn_QnwvZlgtPMgwU(M(UVM8>%wQkDu`k+PUz#ba6gOp{#f#p;U=*G1Aj z5EaQ5kZ9WTBt4eLQ1jfF1j3Zj2NeBCaPVPYWq8uQU!P=O2h$|=m&jv(V(P@CXV3av zirF+eJ&5mUk+Zbnc@<{W+r9g7*}r%i?-kxF=imNsFq_`{$X~>Y2NXP;qZo{!7z>mT5c1I=&Zdr6$LcVhy8*XF;YvHEu0 zX1C-ZJw5|s+K(B|4jfo02M;ckA740*cW;YjVefpokAZqicxQX}{t9M3yIVfLx`Zh) zp5lG(Yh?jT-_FtxGXm^CTj)ERIL@~)(DRLR$I7`gN6>E$mqo00b^wF&_U_$NmhL^m zd)vF^#?1#tSaxPWtT)z98NUCy65 zils>x%RbDS#|!d0=C%F!^Bb7v7PIMb3gz`ROrgAK4?%2ch=Hqi-%j@7W#^yQ_Velw z)sL7;FS0#_`Lgyo7WN|n0}x2tGe1`r7v{?G!~4sd=TBlLMl4x}X}9^~;39z`w*UY@ z07*naRN*qG*Jzp+_{Rxi(S|~bhXIo;B#`JB+On%5O`6If@OQb|p`1ALetGLBe~TZ^Kf{2Aix}K+7=s=6LWV!X zT&IQ?inPlIynGn^X!%aLfA=d}&i2-oKb0F-|5&bG{vF;Zui8}TyZ3PByxn7c+OA&N zd*!9mujdeb9IdY_?EYT*q-UK>@9X3;b*7blRvX!#^4adF-+IVZTUBal&vqX@u6Vq* z)au5EaqKId#G*_DOR+H70sh!$ZtSpOyjlS%-XnD^(-)<_PtL0dAtq|3I|jE6qu~nQ z3=fby4prgNjAJ{14<-m7vMZsM>opXo=4U)i?KwgHEHmnj`yo4W!Kq%dCICS)jzOMF zsg3&W+%NZS%NFUv&b~U0lx9nE8H+TRayfPzCLIGqIxgW`;fGpg5hcpvdbyA3)Gp6) zUA@T*Cz&A$s#XUCn-3^CsEUL0G*FKN_gEj*DQVBL5dpsZTs(9N4}#B@V<+Ay`}Qwl z!1>{F`0!~AK0kr4xR2l(H(&p_d^_(anACpk>)t|f+>n6w@Bw++$6&pnm9G^E`+8&%4UXnqxeQv>oG@n_|FF6X-Qn|InxUI7w!poU!XyH z>R-MO@G!z%xKQm{tg;i5p&;m{BWDAS6%gITtRUv-KA?=z&7(wu4584Dv4)e67mtl8 zRlF+M$WEePOntftpxdlP5Mhh8B*jKij_u*sF35Dmqz|G66&jHxbnYXL<*nW7>t&Y> zVD9>{50dbDPB^q}foFEz33_L!RWbRSG5qfI7sJpd8$v$hjXG2f@lzXEogqt>RJ;wNBpm}q0lMVqnD#Pg z#6Tq_-mn_3LBz&Uyim2OUqYdwro}i|QJXO0JMx%Q&#$P4(PAji0$0Uk$2lYkKnvt6lm=dIXm2_noCAU(uo=X^4 zbyWO1I*^csUAYp8-UJfSY)KRJV96J@q*_t1>|4xxO1&o>lCC7QuPP#>z3GI$ku4_U zkPxe2(GPl{{@(l|N?4?<$)y;Zkt$JRpK4Q?-g`To(3XE;2ULrV86yq+o=Dek%J&>G zpG8b&%!J#KHTh*y1d7TxekD0jhLup57}`m^iF(er&XtVoH_1L8Ir7(}LbTSO3ZRWd znFiu`Wj_oxL!*kZ6P2`_T*EC_Izh5wbsD3&1s!A5@%|o2Fi14}2YG%3x{HJI@LYrg z@VHDZ_Z-N@dq*3j2buoTweGi}(}EAo!!r>Zpl1W~_>vMYx`DxcTfUY$qltL@;DR4n zZf&mHa(}#s@6;{ML@$P?65WUcllc6^en9Pf+S3tgKD*em+4Sz>dUgd%-hNyzfBt`BHof1Kdw0IXL;##c59e%LlReI@ zeZCKIeA#ud+4S(vmh&la_THaeJXtQDJ6cX1KUDVPogA-$8yJZ7)wO%&)35K8M|@|C zzqPehYd4=Acoy@Q-A=CpK99@CSfPU-{_e&5`~|$XJ+N=Sy!rZZESG$=oH=<2GcazH z`wv&k3YJE`cIzJc%~Dyy;5TAFj;0%d60wGaqIVm^uRA_yE0p12wgm+X(W8 zE#Hjw#b1QY7lG0D>v_t#zPe!@qHSkmntBOWcDBQu#Gi!K_eHYAdl9xL`PJw3eGxy? zb6)(s-;SSUbDnxkJE=2ed7sS-^5({-tx138)RFSu#na_1_J^@lDZkEGUE3%heQ^y_ z-+om-`SKcOH`#}qbu4|0uQPUHw=eswbpEVNXx#nf3Uo=vVs}%=E{Zhr^~N?cA>m`;j}$J;DF6#Ott+X`p4&&ZkEeeZ{y)7 zzHG&^A@;Boz_KG7JK1O5?AJ~(rjhN}dpjNZC7)E5p6D!#w;j!N`866I>hj0czWs;G zsWTVL&;H`ymp9-2+j4O6lnuL?8cf?&vVASySKk6*RK4bT*K7! z*Dn30Jh*eszR=!{8+Jadrf=@wlI@SlFLRnAkv0tNw ze-ddXYR+X%P~;hC`UT~3CC#R z*QC6A4?Q_ULN77CvYT0M-!(hF=s`N`5{*;&>D)mq4UTw9dem9Ate@1_DlplDmd|x; z)G;~6Q=KM6OurYytUVqdPLCwjw#)EHb!n9B(!HL!wz9yusC}i!GM8)S(zhF%8@R99 zM41Eix^`xz76E(d#_JGXvN5I$xaOUC9n0UIc)c9P?0Wn6V|Ly{$ID{Ox;Hn!&kk|M z+Tx#s%Q&;K)TJ!$*wle}(6ND&o4zD2UYHt>?wAghdHe=}rG)RtLNsRVT+a)vi_4>I zAm|y!lCaw$y82l?=7Q#?%^4nobif|6wHfp{c;A=CwXb6F5N;EH9B@e60MI}$zxFQK zssY`#rCr zR(*8aY_1{u78}L`e|ib-s@VxyBQ5gUQMG#Md`XL3Pp3^V`UCw0kt)~ObDDH>t;2a|d7zO`sD#PD$~Ul<>&e#RK>!&R zfpsWs-BrVpP81q8b4lMA4&X8gV}$1toh?;u2~p@-SVv;Za|Q+uBPJFW^38t7L)ENS zfnsVd8l(!7yO*#?gJ9(&)B0La4Jh(p3fC#}Bly5mPNB%Yi08gYf~CdNjB%3 zj>)PhBT^w9H2IyyB+@~JQ5{9zzflIwj_vTNk7bn>m<-gtGsIRrXw%Lo;jd%5l+LeR zJ6#;Xhq@JYJbH@;mO1CrBdul5kSFh4u5Jw+8?mdeBCw6f7qV-z59QlK;c?zsgP=O~ zm{`fkn;vNf={6jnL?#{&uBWy!z)qk7mpk*zjF~p;A+t^hvh+=-aU|Q05b#2lespT> z?t)Dtw1Z9143RXdH~7JVsY92tKDP<<0#mBt!YCdD%r6M=U)Kk>EE-F0OQ?D8vaC1# zu#|nMfyuQQsh!nN{XlUutSl;ss5AMUxK>-v;Z}8Iz7vGNGQKgnkpb4%^A>~3ZyE9_ zLi;x0jsx*<|F{RMd(7dv)BL=D$U%L3Da`?Sc4Ooij8^p>KyC+A+=muCH`&EMJj3EW zDG01c$S_X~Y{ExFG@8?iO+5Z>Zf#%y-Xknqi`nrWEZJbZhxc#!GPc(~DQl|_42QoN zIJiOgC5AcMbRxib?nb-JVF}xL`)CJkj5bxQv#^?@^{B=rueN<7TaIlvE)h%IhMTE_ z0$n8mWFrD|#~Nol*^o4`IC$-MyU_*O;Fx3m#?`&gS1fW3yQqm1zVf(~o?8x$R2T8h z4*@1V9Q2fFLF(@$B9%TrmsM}s&pc~bfeB-54ykS{+>i7BXNd{BCV=W3aHiDr0b zuGd5K9K?2Oa~1Cy?qgzrOXbUt{*Us(KmL#9_U)yzf%lAy2M=Pv7_P0nCePB2BET>+ z*8IM7ZFS35tU7agvApxfiSowTW99In{TQsb7uU+yu=?4x@`sNumEZpH^RjngKL%>; zvk4#4VX`CJGvV(}1o*sSWn~>RWIiqz-#A&`fA4(xv!A?Sr|~U3FXTIYzOViCt6SyL zm3tWU`55o@efDAYk9;ou?$mnb=_~@X2#g(|_cCsJUIu2r33pBJ?V^p?u>gdtS8pWeaD|Tf`K~$1p?M$>Rshi6e`+pW6-GyCtcM{Ls#?+g(ou9?lPW;NU*~e-9 z%la=XKNN!@8twB30Ke4P!b@O&QN;&`9BB9ckIt8OFevW~X3X1-JGhOlEn5Ql)}4pt z#_fmY{-af!WpM=q%U(l&;12}q^;3?KcsW+%{F3a;+V8~fkZiwR{W{6-Om8$SWs4i+ zO-w<}V{tbgK%RT^zba?n_$3A_ykAbA|1+Cze$O0#cyVAIzG7oT@gYe3+H(7tHSaMV z3S)_s`%9O~P0W;c{p;VBo7XXqZ zJbWAUoyt;7K%IwnLFYp~qsv9@%{p-cNH`!(eh^60GAMdfh-r=KM+WiQY zUcrN2Tnqgi!w!iSVt+z&=y)_6MkJpl%J{%M-rL{?$Nuzi8YB0dW{PZMPu9Xb&Pf5D zpF6y7&=&vIoR0r$XYMEK2TIwiy(wgNw6Xop@GpXU@VO*%v)iWcj{R_q&Q$Y&S8bWCAs?;e@oAbEEp4f5mS zQGJJ)s|(B;dL+)t4~bKzr9=&~_j^iYxu!9gQy>PJglbrxgI)jM)i5SE?pO6y>Pbf= z0=V>!JT|+v&#;tjCqVg-NU-N6@eC|&nykDOB@9_(L!0Cf$r2SR@hdH7qy&s1OA3s_ zZ{kUZP_8HK3_c-Gap5E}>jo^7XGTLP$;y|cRo-)WHY6z&xPpVfgT;ceUB-%`FF9$eoKZ!q*8>St z$e1qS_ePb7j06*(Y$S1*ukDcTn#p~}WJ6Qq(1{kO# zu}_Ew1~dYOj**f`wU%Uz4czQU3P(rV3IMI=kkB)8qmq^4=9&fNTrX>jQ&npwb2=I+ zCTbT|#&k@WIb;MvTGFdVn5i<&il1v;s;Rjk%Z!(=+TkB_Gad%$akdYiDbHz) zA9d(b_1TMV#0Ko?!w}7k#{qa8^p(mcO%P;wLgqe_WzDf5;$CuXb;UCFYLL zug`WYzn;m4(?+GcCNfgf7@w2&fCQjw{4#sMe`AlMCtby<)rtOQANu8_{2k5uJ&xBg z=f9)Xc?P%9+PK(0#>b_+JaUqSh5aYXp`#be$#cIfC(ry0GwS`w-bHZo09*DJ@_vuU zp5ncRvE%y;E_=Iy0qXbee1>-s|5$F_xKyrR`Ji0=;_u3ntu?z|&takf8w}5D?kr6m z0mgh|gR3mvf1{&M{2!EzJ>_4u*EItE1Ez5lR$cKJ@Zf+bAvKUl`K z2=_>PFu_XPH`#sC)Rt$0uUZ6n#^SR+dvA*Yd5`gK7H@FNTd$ugKYr_+O}4|yRrvhy z{)3fr8_U^Vzq4E(JY2!#DtK>;ne=#J(}X*-{_v``@XViC1g4L`jt1yG?~P7hpJgV$ zDVW~rNs@gV+sS7Bi}WMoGC$5Q+Eg6+Ixo6B&Ij4qjxV|XUjDy`_z8sK@WFB#>(#$`{&+cgY|#ee{qDoB%a>o@ zC^v51E$ds<4fZqz4S<+I&s>(s}~eiK9GKHg4jW!aqXV~#6lAIJGKPInSr zxX+3?RF^)@Ugp9Ho?ZDMlzc3DTIT1l#44sc{;OZTS1#g)j}Jk=zIq4C9^WZ@xyDCG}Vh{t~{Tf$B@R6o*5%QB>~ELo{%ldeNaB;Zzk6y=BUj14SP7A5bS zZ={ac)uE7$4`Ef9Ttw*<5WE%rMZ+|fZ9r@!t2>QEa#)sKz;hi<_s1jxM9R6?AjLJk zjHH;Dsh)i0Rc((C>!N?e$|fnADU>rB`*)3(k(?gdU0bTR*W9I>bHulSdU$EKg$E{_ zO=AlWEX^#0!ZQdr-OOEQj$x*$3!xkRJczAND-*l$_@04)0g&Gn{iMjwQ2A zd}_F!v?W6k^)q6S?#UdByhvUH-%^jL_-Fn@##DWly)CSLvWXduH__kt!A)BZx33fS z46AD{0R3DaHtZZOdyK39Hy?;3--y&N_t7b9)+1wiPzH}jP*qQz^IEI|*#W86*?=!WYz*3DgIEOf*nNv|8_HR$n9&whOs2D3izUhTBxE{wW z$2_&%ha6|CgTYl#T`uBD|J)jM={U{sFI=cvbR%?vMU(ED<+k6LUlgl#Mjp0Qba)!~ zq@3)jEWY-YAO5-Tq=rVk$hWo#lGUp!LNxNNj;TFq$oNfxrc@%^QpJd37LY5(@&egG zb_LsJYUC-Du|nE>2)f6X1=P%Ts4euoKBT#z>PHBYtPDBw)mLkqLng~phkg)Ae&@Uy zG@;m-0TEIqsH7Ba);(hp-z`YgwVSn2^GZ}%=XZSeO3E7cLUdBcMUn(e28;N!ncS2o zF(vo1q1G}o%2$&{aH`D?3kmw_mgE=t-iFc_hi3Uw$jYf+j1}9D|D=;MW~0`}@lzEX zF9@C3NeDJ|uB7d1L;U_eY#G_&*|!-8s)MnkmZDY6kgX8jJ6UIg4sWdghNx7{SHVUt zU9aEUy#2De=hZg3&ThX3&7JV7hGDCIfN>Wt$Snh-A8O~y0Lj##U`JysxJa_)lF{a( zr?`8Y3)wy3t(@897WaWZ0M7?(&CPK(H4fO~GY#O<2j7JMAh^rWHpdK$f-dJXRTV5YZ)C-+OfK18OZE# zq7KlbM##~QX3#dEo<3{Jq&l*!yfJ#4tnPL02}DsiF5fUkAJrN~mTpH@VkBCOwyy9)KmqSzdD6HgGc;Xe2UHReiO*+#h zE6nNkZpUs76}zNp*OL?*V^OZT(4@!Fn*`KcvH8t%HZ0%_miYG z?tdrX?3o5XFx})px_x*j@j6yDc(0r|^Ak)S@XK=i)O%$S1NZpM#g}|%f5?4;)%5a{ zC!4m+`@N;F%B9c#uKe~l|5I6gcmspS3u#>N8F%RsVU{LGfTv5|W3O*A zfV;}^#l4tZ<$w*+yKwH9y}Lb%@*h9BYBTCxyMC*zZDKGN-tO_t&-Z@(n3(TuhbGjR zY<Pz4qwOCoua++^-!4~f+%326Jix_& z1?$;w;)UuSeC?>4?QHGE=jA_r3Nq^yIthLNA7B&AZ{;GBthFl!!W%sYmq{ zfgE9?Z$ZS9$}EW%to9I(YA)DBGFan>YIm31u9%@N^TN-HlI1@0m2bXu?Mu(<69%Yb z^sZ7iSL?6#s5q2@p6G^48nAPF%CA1%MZ-P|k*ZIr%xrd%SRx2N`=$6%ukus>rhk%` z|7Mq+Oi3UhiQ3Ex)$5<@Th*hpW?L4V5NPnN9qE&hb6%M8nPtf_KSXzZay?~vQZl~u zDUlx&cac+_ZZUHeDas>g)2tqFll%$r>L(C*1 zDk1G2IA%qKX2&zghW{cbBnNWT03F!%_#KBDsK|;DfmBDv!zC4;FeA4WAV$Y%9!0B* zOoRclMG6!TaK)-rI#3Iig<|)(3Z$?p;cP<)lvG}F^25BXN}{@-(qSybVXoy}qSX~; z|8Hh2o&rf8sA{!;Lz_5XkN9UIZ=L)oV-K zZ~(hetV|sqcf+E7bxRZcD|x5kQ0^e`*wQ!q)&2D#>mI zXjL(i@XhrAHIWg;hDMf)Hn5I<6M$SCXNl%vas%|=;!8)!@c%}0KR|F}wF($H30l*K zu!~^NJ2m1F&Q|5X3@}{PV-NTE_|sQqpcL%lfs?l_q1_1e+z0yQ&pYUrU4!j@khAr0 zc03HueXg(YhrVj0`bjTO7U z=j?dA_tH#4+|SbF2zcA?!m@q)@L}-0-Q#m+L~m#9+1lPVJlaN@4JO)AnA&i_wCi^f z*fT}3bDDS60d01RxF(WICCLhabkUg4^zC@$=ae1G^qOZZ+<=`|`nIYZ-N?)aT;m2d z&qK8w@?-0puyt&A>fPZ-VNzVO{Di#fK|ian;roc1s+{eP2%+9=Q#z0TIiEw;phm&u z6QMI#{*hnvssD8KT3dEex(;{RgRF2Y+vsXUiH7FMy*M^4r<-QEdJ4?Af zIlnWMuJlfDD66Dvv&uirJ$fpi6`-y8vD@P<&RjZ&rEd3^i|_rn<=vnEdpvWwV9Va_ z#xoiA6^5|IuJserIEldOqouNR>+{0N18!XTeYt)8kL4j&`&n7OUFP>K;L<#K0)Zc9 zJ4~)Mk3`0o_c}hvj^`NUpKg{@m{jFQZ=El1VVTn-m{o5X?`-+L_Ur3QSPkuAxqWxF zEIn8&2k_o+e%@yyo@{N#eH9|W_T9t>xf}S~hkN5Ai~FptIhoG6(?@JVoh8ha_xYt; z<%3VY!mN3ix%n|BQNismd&gsR79O2`fAuQVXy(H#0<#FbhzPv!0ea8k)J|@^o(2D# z*3r#jySe$MJ$QBP_1n5%e$_mG)&0xy8rSc5v&X?#TNt6o2TgoodFbGQa^l#ba^dWe zau$QPP9nd77k&?M(|6-G2k0&1?@?L8)VXVGd=dTFW>vH02-7cl^|k@T2#QvKtR6Ik>`%-@=dF8(02N zu3i2^x$@;d;0N%>kVN~SZT8>`nb*e5nwRTi;5*#N7pYCc%d(T?gw>DVkiKXcZ-|yL zm7iu$T;WY4|Dt7by@;KCVSh-^lX|AxgOMI%w8(xH2d|FeYF9A*gFE_S2CEMp1qP;z zB?D%v50+5Ep0E-pq0(K)Xl}GzSEn!M+@>FTT^HqegX+5+YHW`YUjr+@XcJIhmVRtU z8}SujjV9#oei*mOG<|;%Y(7MtObYeQGSQH=pRg=(g~pM>28j}AW(RW;6Ap#wPqwf8 z5I~DxSE|uni(JR;qDagA;MOV9Z+h87ilHp$1Jdz(a+QY+4q99Cmb0Ded1%E7}Y%VE$CAkW$LY``9_rLSq=o?XiW z-%g~=d%JnNqZRN1mP_H*!FkB@GjD4rKD4$0dyumRqbxFj$gLl&R1s3L1X+#d{l~?f zntf`ThU;P-G`}7+eEMWd-@;#;P0#M-kw=+y|6`xzi)Yq>dw2vr`^P#l5ptZ-OgG`Y zk)jLnsAg&&W?AMNnB}6_saM3=G=sn0i`2fAos@H%RL^T%ye3r!{)9_z0_h*X6CZBS&tt46^`8@(e3{#+?(lm+CEeX>VYpI)&iZ{@BjILHKRew7a76jbS zss~jCWV}-SI7q^f1gH9o$$ej-;}n?#N|ls=?U-@k$`*W}xU^r}q)CmANfK%b``B3@ z3t+z{K`7glQ2|?7M2dlrT=tuegV;6%Br`~J@unWR@MwhIC#aj$x^;~8rQMis>lws^OdqjetTJ9-%jsnv21=UH!_fP?bNnPDy9VY_9icJ*NZ7{Y@B}eQy+@ z6C`6-oe^QlrV3AtC*>y!j95@JjWT)^LHEcivJNel@>l*-s_)m3jL^QlJ=>CLb(&uyiHx%mZLV)u9q z@_C!rWc;K4*5(SXmsiT&+n<-47^HXW+Mmkp8y}bTm1}t4ge6#EZ`W%W@V6^)zJC#Y zX*J?ITdsu0_e+nr*2}`)`EvZ&k#hbN-`O56r%o)|(Yw07QEuMh0KH|*u7>u&Qm2nr z*Rdon-q~UmEUtv5qxz*eI|JTv1hmTCV+_n&Jh0DZN<56gc_)q>D5o%~3YW6wyT1ny zH_DZp7>Ie}PPvJ9ww!>aU~ujH9=sFeYBB%U`^M{mF9hz(QGB!z^AatM*mKKYwx!1NQD?RxeDCjX_-g zpanncc$5Au-Sw%SjQzO&KaEVk?Iy*GvfJ_dc+XRwWJ`794ZOdM#o$YO&&Fo=YzAIH=^v1i~V->mOkaS z!*~|i?J%={#GfQTj?W*S9Gqv%={=n*M^F5yy!-yYF6ZC;6@KXcv>ZNu5d-S>`43sh zT0(o}`jEySx$Af^y!`Mcman~BKKW{$vuZ9iTC11nJ;7O?Iiua&wcth zzul41t@O$1W0RKoEax;NPk#0x>t}h+gEh$~@nEi| z{&_U}V$a_s%Pvs!aoiFpvi2|zW&X1Pp%j)ptwByPQC&?Z3-#n1CZYTxxSMMa{=&Q& zpp2Vpd?Un$c%Hh3$SPG6PTNf6SW> z+4L=?T<=77`HmImN;}*$qWtRXw=%V)kr6!K_to^wS4WTrOD^jchxGdOVVN=HOTR5# z4>+S9m&$sAL14T-vx<)9wr1PuBfvp%r!STR2ajQ0dx%g-~b^~iqHEL>SJD#mUc(dfQr)U-xAI)YrQP!Rq4maAP(o7WU2i8aMBCFT! zt&(bY^&|E!;xc(}^{wQb-f!F$4T#O8BA zI{w&g_{ECqf<+)H2b=0Tmm4qK=XuxaY3i1<>hs+KpqU+DiD@Mb~gji({rX zX*<_}Wxj-8MxzLghMM^me6cDDU8Q&sd_bXuL51J^r}lCGgHK&k1lR5fQLz6;G+YCw zT--Afr=n9#P_7!Dh$wvb&+1`*CPgzqqEJns7WuH(>k#xVyQ581Cil_tJjUdRi)e%? zqp}|ap|zL6#0FCFV%TChA`8A;mx=I!4B5(vmQS*>uX`Z!o8K%Lg_~-MdC8kB2}p9q zOTMM4E*&K!gcHdZldW|O{S+he*)^HitCU(jMKvU6r|gIy2}4-wVJ6#EXn4rcX7d09 z6{Lf*{4$>tUA8LU2t9%7cdutqENuC)3xZUMLt_D)M9qS2Lf7)9C*{m=anl!B&VuDp z2G;~!mSPgIJjv2^k+uz!$yJvPy7WxlXx$i?S!4R~<7T)OKr#iVT3-#iD_CJzi%^c0 z5t|+G1H=1&6tJ`73A%iipx&6>GCm2`DeS1GG4rK@T6y631%A~q|C*lXT3rpIy2Uuj z1TxCkObAk>qf-S#nX05oKKMP7;@ylUlTsz(D7@LR(2xzjT3+?0X^&xeAq*%rS%WdT z>Wlg8!cw7qxkFvq5a0SIp=$6Pw`m~<0NUVhOs>4(gYJA_7N0fQOnKOIVBTwdISg6z zDKKe^o-_GY9#M{i?tIjr&y0rwc=#B@2kC*%x%ucoXcD6()gmacM+%?QaAg9{lDDz3 zRyNkLI>@8@7*z0exq;d8?%ep&2J1DPS#H`0SpQ*Pz_Gs@gE{t~t?Unj^5*cT{^fY; ztzOE!E=Rory+ro{e&aOamh>`K7V7ng%90)l-uu)CU8HMQC|#Kf*2`G;4Ag9=iV-eB zDVt?gAJUYkzGTMHpJ12!yhvs~81`(xV z`+nWUltNsVcKp8ltjq}TqrVLdOy{-naXEADVtMnOe~I@P{}%5v z-oeL_XYst{ATGBY9N&6`bx(E=WOxt2LGzqt_TKF;%Jr*%C|582Q@MWWKb8A;J}Db( z%aFr+kX?9>Vegu9A2Zn5X37X~5{|VE%(S-t|Fid=O?D(%mY74NL<(pPl!!VT?4}8FDx?J3G^2T2)tdX=osUMCwccJm+3N z?l=6!d#R8p(Jz3EaKHE5bFYhdFJ6R)`*u1rKa>9K$FE@pv`ZMMw~*F1w$kd_Mq0t( zykCEOH+^<{DLsC|C5;oFL*d$t&1KhNdvrF29Z3z?rz)>Yk>JdCoG<4&?%%y}{apIt zyZ+2}aeg+<&P<{GR?|nHK1l!9-~KKwV|A_*n4n{R_9SM`V{4ALaSe=X;JaP}M>;@{ zUkJl4s76)ntGd`aYm z5AF6+7hKAeBltP9$ONw9W~Qdn?DRC2LS0PXfBQ0~*`2W)Y>#k}@OT+B>EZ2+Ut+Kx z-_qcP`ScVn6gbE3h;6@hvEMQbbG@72-SoqFDL;(uDBk_*c!9@hkvSmX7~1gW^^3NI z?R89v%!|8e4D6bkJdtkQdy+o;>H%iUTS`mI>sZnjc(fnO!8e-o>CszugN)9D6xNKH`V@6_@bD*}yyws>54Lr{*tWNxrw!*$eNci&x*rlD0p_>_1o2 z?5VRD%*waMFl!!2Y6+R~9M8M0^`~id<-Waf_Ql74mwx}-zfK#gcku&k8G}8x?Z&qT z#In7IZHkZjzOqn1OfJqoy>#s{`lF7v!+b!yJUwIw-Y^9T5=80=L zqFy$`j?s3M&tdkcQASbJ-t^d)o2S%fecLgrncKt6%0o^PNzZiL`s79C;O?zrrxS%i z*|!6)ZlqR>2qn>xavJi_F)qn|bN>=brI#!EDfJ;r9RV_ZKJUtnTIu_aB4#|nj@iPM zF)CwUpRu-YaCFxfLyVs$%8NgS+1d*$jNc2+j&prl-FnqJEX38?%aTCXMTtwDM0*iC zF2>yDt}Q4_T2$*1?I?p?j@CW})Ae4&NO+9$?3$R1CGOS%A3(GlB>0aZ+qmJ$KMa`4 zv+C{G411ipW6-K)8Rxq-Z~{xDa6sPl^t{iWhX;OV&R(;@dZ*7_!+^ar7{ESjf1F)! zVhYQua7Gn=@1nk~&Q~Kd0(uC z-anZaUOJD=3RdbR6*#jiPasb>ZUPPSxz7#6NS;t3-j1}6 zoM^9rF(r4D!AYllrgZy)L&A8AI44Tb5s9>LMjSH@&Y-RzyfzYf*7I5y|6`pQTZUOt zL{FLAO4c4ThV@eFxpYi|GqKsVWYFQi)oHFnS#*%fL@MLUJJSmUN(9nrClAac0jwfx z%9t{aX)6yClp!`DbN9*h=kY5nVa!9WXX?l$onbR4>A6|FOt21Y8)USKqE0HNg$JJ} z^~pzN-K;L$v4uvBg;1h`K(4D<@nxo_haYAv?Y=yTJqhI_lQAX}1y0sP@406f#h;zP z{snpS2JepYgT!|9^0>$>4CCHhN`Z43Hgl+Dx@T|p4-2`9QB*#rG8RB_6`85rvgnKu zv~4VFU7>EZxSSywB5C zVZa{q<8ALXU@d|==PY^r^MQF>y7;8`7i&wlN4Kro*0Uys+ZLxttlgE#$B1q9?ya7? zUGJG729eX`v_3RoKmkClO^j*4#!U>8%_F=&-A7_QyTD_)ix3Hh=9@lwm}?!k{INjU zygf9@e?SR;p5gZd zPM&;$XBP`t_2){u_QwB^uH5|Bc((BtRx`MO`!Mt9ljs}PExJ9?J~yG*!T0GJmc4!Y zp|Ar}CLC@8|Gyfs;4P%uZvu z+f(WNH!ondtT{ZxolK9h>e8@QqlWtx;o37!R?ZVs)X3U#RlX#~71gqG6^5w&H2hVOFF0H1O^(~xTxIV#Y z!|t~O}nIEuVoq~ncXZc`Xk zxo~PSon4qo*RPyQmoJ{NC2b#KY2wGYKwraK7hm3ejK8I{j6s&TuD6SNyO(*u;M7L@ z>0_f@iI133oWhO5jPet8;*xAPPU87P_5}u8*b>p0u6=g?TDo-YgLLDKKTYQ^zlS%@ z-o)~Jm(#@LEPjMe0E>tG*@4~i00;j)dyX5!xGB80@-RJq^cj}0{V3i2;^+3(+3hd> z880mzfSuPVg-rgusCCR`Y;~9q6vgZ%C^t0i>x`>P_iF8;uTBepj?~ygp*vy zVRq8u|35sNGs=)|~ z#K?0za9r#lvUbhR#ntv1i!lc*jJRw3nItQ!-2DmPXWu6i-k&??*dwcqoAQoRY%QF!dypg5>dK$R*9N!}(P@L6roZ3qb%wxIRGZ>u5%`3ro^2$ngiVTo+%B$>139gDN~C3Zv~|oS>^s7wS`C)HtJCLLl#(<@ zQQ}s8wz!@E2nofKg|(sN5_Zgkp>WhgT>D+e0B!{ACG6CLWnvI0n|l;T=FV9j3ab)X zr*@I0)MO~KQg@lR9CB%3BrFr9sgXWrBa28T=7MyV96K|v(dUfvSpjn~xFPmAF4}c< z-5l|(Uz3*LOi0z>MxeqhcnP}jQu8GUwXpCY!+&dEh@|bZAZNh^zXV9ebh~AL{yC9- zsg7x9B6EmLOO)}9Cf6b9tCYFC6)p*yW2hhdxGDLJ)5+e35MvUCGXHPar0Sc-1Ucg~ z8U@;fQYfJ}tbub2!H3IC2tYXFIx!n6Ff)%WA1p0A;|3i)M73eCi*>8m+$6_9pw15g zC?i>bn80K%?5K<5EVX#L;gZs0EMSUkbH*lqau`&2M|ER@In+dCt?`+K)ld99b{;Ex zp^{J6IZI^|r?j<1(p1OZ!gL=$JJ7!QJ|2I|*dHDU`k=fz0MC}L_^0u?}Y&a2)wh^z*&fP zYwd)gT)Y_;3l>lmbW3p3Mxm~MaZPwD=bzf2GBeS}ql zZe@Oa*JFuJ;>X*;YJda1)-mhhb6j{%O--bCZk)qFFD!eDrBP>RChVn&ZOqK}>8-~$ zK=0|wT3W&1`uaL%IL69ZyoS^F+QC8?TkWw1c$PdvTkw9{%=A<`g$Y#VXQwbg56cx} zaNdQp^OzZNCq2S*+m*FVtX#K}zPR(q23YdB{PYZ7g0PoX_ULd78`r?N2EG$DaHs)# zya?h}e%$1=3HPx(?8Vex>|?B>LfZ@1D0}Mm>Z|2BsGWFZ&wSqnvGj*+FZnylBV+9= z!%_MacdRGL;<44Yu||DNM~;_~ZvD2LyXjjzp5fs}*A6b?Hqu!P&bxN`Y4|wmygqDw;o_-lNme&-P^RgyVXUXe!pdC%QVWq&tJ5cUb0uw zA7i}oFTcEx%QP>tPaJ=dUcYfU{p9@bgzWC}s zZX6%SLu@>>-sR1U)|blM9)+V^aS0Q3efmCJ%Rvlz`sA|T%Tci$gh#f~&vOi{d$zNR z!FA8^Hra*r!+-T3(;M&p1!m3rNt(foc{VT)b#3?B?RFI}%lY$FKVWg=62E*m*B+-Q zk3Po|wm&U{@*dy^?9TQ|n!p=0zLbl<<J2A0ylye)`} zTZ{!@7Sziq-GX};0ivQ|F{uySUnIo$Y=FLZF z+p4W8;YL-$3z`H4JqJvE^qR)U3ti$*rE^&b1d-QAbxD-D4Ixm*I%heQt|3}P482AmQrek??-Z?D zNR#aYj@8F&>BU6#Ow?T|}|Bl@Lb8H|hV`*IjOSSsl?j2!)n z!xBX!X#bIR++ZuWfeN$^u=*0BgUtl|o_7$4#6*$z@7Um7{tCCMpg#j-4i{R8*^>u~ z49~6F?*2dzrm;6VHSAZR@+u@dARU;WsYU2Z9fE4iM(w4}jQawi)X!m7SS~m?m6#`se46nC--|y!=LV z;Uzo+cn}9;2bz9 z>hmDIVjL5>Vu}bQhK>J`^B4vL+)v9Gh_|}@2=~UGVz!^o@;=0&H&#opRLM_qf7&?gwi)q?2-(xQSgv1z_=N9W$*e)&-yUn08IhOwDj@GY0 zzuV_9aebJ*+8og^)}X?r2AO*_LC3)I{nk%{%?4GayQM);QC@>0pA@y`A4uE zC-+$cJV$nRUSRMYR&<(~Ojj-~V1kv4={ja-JH2=+&7YdKdpWF@+qzPwv7ECLfg>)`)^-M@4a;m%MqWpH!C(UFmLI}O1l4WCEdQaoSvd> zRyXk0F#gaG!0?Twqic^C^RV==Iv6G&#?#7auT5>EJdb_+vT5;MIc{^{$LjNEo4ANZ zJFpEf$l`nN{bhRRhyN~Jy8Z#)D!YkgZqK9JPvXZWZam`z@>?C;qrNRR#)k{^HOyl8 z_zS#I_6tn?{xke&{RQ4AyPeh{-^LOvjDMIX;^$LSF58;@k<}`l(kzQM~i9m zBbjxfl~NnM^hu-UYq7~*N=!D}XAkB>J$i`WwVJh^Zoj8d{tpIs(L z3@f9soi^}O(^XxUUW_Oseawq6AJxVZm-ft+uX0j9SZ7{GvTctMUqkltMqb%Pd?#qd zAIsu2W;#$eBFsmYQRPCX?oc<-PPVW8J??I#s)Z;WRzAszRb28Toq~vbZP3!mR$Q{R zBa5oKgCJCy!@4J70S6PuK|nvenOO(xVE|Mfus0s8=hbwW{VbNcotas{Ao*#0hvH=s zyfiY8fq5LDw}8KCER#EX>U5f0IA^#?%({m!?A$(V9S*c(maXh9So*x>tr1}Un0>7)vUVb64jplJ{`(LIvNKDJ7Yp4E@reit}+Orvi<%9*az?`$3B{^6gps~H3bNPhQv(c)jr5n|HhZ#hy zN8{kmAtbDuWbVl2G2OnTUry@QQ29!2o+Fo3fEF5y4g#8IZ2V|2T&8B(R8CRtF`Ld}`&Ok-B69Ept*y9OJnEDcEY4~f}1 z%D-!&rA)l88oICLA#rO28AXsQtl-W$O^~H(RF#cZW>2Vdsqea9r!ce3Q%%jI1Ij-8 zvApzOg;hwkOsh!(wHWR@=Ui|#FM_uug?!vjZtOCX31bkp8>>M zZD)+f>i)fbfeqn=WKhJIjJ#8-V($U81uC(06rl0gav8Id@s%^eGTv7dm&FXZ<+HHV zMTofLiy13;t_0?lj6{?V*$9&fA=$N|B`>lJro6}u>APO}u=r$Wb-_%o55r5T2$auW zfnn<_a6d;}7cER;{LW1loma+~E4X|wP-uk+r7MjPWW+UDigw?sk)>+3n3lc^2OV|D zl#pP>;!#z~jlx)Iv%C;@Q6^SDbxdJER?c{idfK9qn0f@kmrA(I9B1U<(zLc*E%P=j zp3kacgRXGD$Y#m2!B_N#2;~k5C8?1g)`QgrY&7WZ816lBARdkV-Ujou&R7!UJd@lk z>#3Y~Z!8LzrQN^)ycG<kAEweA&<-KJ>!coTN}9lwPFJVjAXn!Pz~4& zitH=6$Hn_oyqCyjY~yTsZ1dRj+ulLP>W0VA*EuV(ZvS{gl`^yZygIFKOx3Gd{lQ$Dp9QGDixZ?Dpe}l6l8EV zSOCvJFu(V-3;-tEO%TWy4&gu&){q2J)?~ zK1@#@a#DhyrCXo==XB?bzr%`7E4a6S*}*2!n~zW956@5fPU3GtjeXrA^1jXX_H%qc zA4}7iO>Y{{Y!|U)>cum&>D-yQbOuY{a{1KF%`Gf_yPAIU$=&qTou%~n$#Oc5+4N4} zkAwAiZCJ0pzV1?eJN@(1l=sYb_=lD0IN=Ra=_Xb<`_X%^;hF6sW;mR}GPgVF36>&$ zxU`aPJ-`bknB8z~eG}KTTj?3jr3pPlk00IN&MFy4J+6Uq4IHorUg-e6mvN!h$H5B( z%8z0buWowKhh$7gIrA@b>nm&ImtOH4Wh~L}VbhAc@E;3F{PhgPV3}(?gg{5y7aj|jMF8nxa+pjR2-mN%aGBBHnqck{O$)!KlY#$S9+ZM z!)})1;&}r1IH#~Xh3Tu`fA_WY7T&~QRCgaNV5&c4dx}AmD_G(UH`Mrt8*3qS z6~T`Cjj3aNfXpy|lyg(P?w4Bj!#Mi<4zqErKWIll_Ir`_e`4ZnIyre7Gw02vAN|>X zNbmpgzfY6X^LEpc%e?3wPAmgU#ka_wJ;R$bPw!)j_upa0ynn)v)1Rh?cYc+gE`5$c z8e8~b$|Y^_0~O2WYEyiXkGf&mJS#-3C>a#kD3 z<=gVz=8h%>Z}z=6Q$j32#~XrTRa^}ju8ePx(d)*I%AhVFe@3$QmO27x0FNJ8l-;) zFfRPJI_kj$cuBH!-IzS?KADXA>VP`gsK%K0SSWEn(nT(}?W-?mbQ^L3*_I13wP%e$ z>^Qb3V(v>VjmJ>WHTtpd1$;N`;O3f5peujAw%YJf}3&tg{FQy92+ zYW{SZon5fm^%l=wPNy(XZ+dnSOSMen2J^hlxOW10eh+)E2o6FUvjuHKJ6kI6tdDSD zp3SPqrEtS?xNJ$aXZfsg9`5}#)XC^_dGF?grPe!ZwYM&lx!k&ItKL*gS3KG%hn&mh zQlDpk+}X*4O7U`nX4m5^ki_8Uft3vfx4f0p99Qq2^1XS_b)e_7UJ{SFMn_(8=-dW| zqS7AHQVKrs4w<( z9Wb=%=(E}*^#0@+H|&^_)QoW;C~p;0Q_@9etOxZLXAr(8ND>rv^;V*y&ME>4`|sfL zT~h)G3B>QHEK>%gB{s^cyI?$+^jIj*M&hH$IM5!YGkVz>LF^CeixzjM(K3f*;44@# z$d#&qE~WQ+p`>metE{qIStv{Sp^{5OQD->pa@#XYK5kCodzZ#^MHslVa|Z6!D)IZ@ zKdB^~ZOC^?(2N1wH8T}iFARdWx zxr|h5NozaUHX+uih^3NS205dP2KG_{TR0`FtOI5qkpqieKv@h>S1R=99GI%c9A+DG zaEo}CNP}ERn$9Z5h1{EMAGw1aksZ9qvDBD&zST`!(p!0XU8GI+0A|V@pLo<_G;l}=z~@*4hD@vswv?$%fFz<&v=5ZtyUY&SNLUwH!AW9S~+ z5_=8lIJ>VJ@Ijomd@}uWbU-3AdPK;UB zhx3+DAkNQRr|hOya2Xo{E9Dg=#Lu!9y%Re@#AM202bQIcy+-QQUq7`oaUZ|plOtgw znRGZ(-DlE`rPg$db*d^=s#FAUt z3GwtB3`ngXbiSkpAV(tjy8IqVVZKGAoJsFFRt>^j`}o|)WE~4PF-Y%yEPMOM>FP~i z`gUga9Ihdz@pV6elBr)IslJTC`FwG42g}ZHt}mz0Kl;DZ?|=P&rdwbBKCQ3bOUKg+ zzO#9s0VZ}2hJ*R>H{SML19r`X^K)bSgRnI+HG7`P(_nsOK}a z-Ai}wJxH57$1#`;Er)CDXGlE5jhz$u<&1p?@CR(b`(b=;>(6X)-GplC>+sVM*JS^bmiyAFkT9E}!pO-Iwd+Ki~=-7kyj<;~Gc< z1N07iF|?cgFn#&)^b$XuqA#Yo(N9eG^?|65=|1dXWQv78RQ`kT^~KmH+bj2B@j=#lZPaR&;z{7y&7%cWIz4h9a z^zNJ2()ZuGYG!;hYEv?#H0@D~!W^Y@D^O~G7M)}pT)x{WEOyp|Q z#J1%Y_b5)<_36boN>3d(`C&S8#dw5?de->{21RaT@Z;>%iFE$-sdVN1Lb{3z?3-85 zr8jR}#Kql%^y`nlO8!GF*WUh%bp4G#PZzIzKh4fv zuz`B4-25BV>fAU@kiTc}zrFW~uO zlD!dAkIaanBa90%CM6YJ*yG-g7sQ9SZRuyDg{M#*I_K5eo$eK=E!dn=l3sLbD$$O5*kwG25Ju)|Eq}4&RkMzz}%SK(q%9nFxOWX`DeWKG*g1#hSnK9xL zKRIiEgh6btQZGt)d(&3-*+nj=;;vGrh}CBPa$e;zHlA;EZvQ=q)`vL67r9(l?U?tR zr-XG*M-wre%P}6K$FEX|SGMw?j^sF4W+3}DXZ+a04NMMx(u{g)6GbyV?xzMg`^yP? zxq&md_>3@|C5B6^ok)|IVej;r%jx>-AEX;^{83t1ya?H>zdu)NvHfqHyHoPWIgtl9O%PzKcm26hSeC}GU0XGvH4u=iWn)1vsWH1U;(I^rEi zUEfxHc|S!yi)haoojJSSHr@i(lDO=*KFhEJSzokOz0r$Dfpa-Ow2FhoEeeH+>dRmp zbEHO_8T8N#Fptmu@UzOk!x*fGQF%z2ACgfeSAs#*XOiQm0W9Fm?8+|t$z(cb853Vj zGEQW&RrVP@8ieGIEl6fFDGxFsvDF7+iTqPvEJ=<+Fb|TPFtuK-p52RuLobx^W5rQ2M9xBB-ed9jJk40tj-rc4 zLpjvTLq7yy7eaRv3UXjpw&KrYig@}gv{_+?9JUkszDH!fRR@I!aJ4B1bD2DUUKYL# zrmgI#lMlB?^wdYJWms2ySj#$vG6!Jwm-TG3vO%W0^)`XV%p6nwBULlSJSNPqfhv|I ztY*P)G*p^uixJ0MIEwgCPFsmJ>OS9e)!KM4(9dQZgiEU%b;yNLI+T{G&- zII9`2KC=cb`XoJ_GQt#=VA4shJdeU;9m`Z=33w*UDQj{!?5~4LOBWHtJS|17dZWBb zJtvXk(Px9j0%hS*8Du7M<#j9u*_iTxwX&BAaibV}H&)jR;9` zmbM)|h1|?u;I^<1sbp1OQ`E}d{TOYQnctqYm`$a|uQa6rtlqNT`f{!VvsVz&t`HwK zK~gz5EZ57k-~ajSz7J~m@*@APGNFfC|K$A)ez)?4(&M;SHJz^AcrU&7)}N=hfA~MA z)92s7EPLnh-GP2(-KzF2OO@`1nclWmFrfZ^`r?!Sl5S(df_q>6JUw~vTg-HOA7lA( zuja%IemhTnS1a_$%fId2<@59D%Ei;D z^E2s<8|Ty0)lC~1`HSCtlD@k4ByGRotcAyLt;P1j_YBSvejn(&`a4i9<06b}U|a)- ztbu_6dWYm2n|}jpV0^*)4QPa2La>WjV01CH3;b(U^8(^IcDB?iZ-U{WG7()9dIAV&CP9lcsDVTADZ%l{TsNcO%z&R+w27+ z`~4iZ@%9R5)8UQAv!~}!pNp89dNy6a+bg^)R6O*{o5S1N+xBMp^z2+ZefF(%_Tszg zJO<@mzW$?h?!x!*2H8bC#6OAOpu8mKrX$ndskgV+?M<=OWlSTEAE2Lq{C8>T{_oQA z(wFJ!@_qd9!M$w0R&$Iuv}rT^WQcL@RyT~GZ`-Xd`fP^D6wg6pA+I=?7EhE7lM73I z+*;eH+iRNKXOt^V(-zmz?UD_Z zm65e%UZ*X;${S;-QB>=yZyDSX>mH97@xaj^Fh0Y;E-oW#4X%F-Y%9I(_aMmcX6I!28o_e*PSm!99HUYI$AZ^YUIXgcxs zwYDuE#I?<>$538o(*qK1>3^JIk2>D;X20W+|G8tHf82N^r+6h7BA#zuj3b4Yj-1>i z_gr@`tYZdxEYw8sP2p#_Ug95b{&GnV>&KMgMkVv6XPV_kp1&W6sec}dm0Lk{-Mh0k zTw!R_(J>=sOp81mS&7fVt5=?sZbOMbZ^Y-d zF<|fiEhh?Ms*UqFzV#w$k9%iq_KGRkHHiIYEl$w&F?ai(O0-W8c0Pe!4qEfJD2AgD ziB?x?m0AJ^03CMM3Znp!FCv$DYkFELo+I33HIT@9;pMpCgSBd|tCK}NI#$7Y{x)Xv38;UbEk@436kmyvh`9+G3kJ?iNAj0j&DCmI`y}ehr7DUk``yEKnI2wwf?=ok>nflj4S) z=7_Q)b$exp(MzAb6?@4uHwR7HKju*wWa&0!_wd3aXWPLG5995n)PVOR^cS=x`-d%GYuCGcnS}#9 z@j?sxTJ9sY$EzLNepCCC2k4zxT{+6cseb2-k+&@(S*qT&%y!AUMuHE+4AZx~!ihbG z64TQ69z6)Bs)Q;(O$Oao9QBTkG8HV!hzy%w5C*}BWFO6t@~QpA7Rw>osAC(s?8VEx z(gQqwaS|j_ru|~M5mM1^k*q^tz>#sZR55R~s=UaXK@vsxR7uWstE22d8;K(Efk>@(UcyQQr|^vAYPx*mgLL)v{|-yv{weO8oX6w^3u$6<7Tt|! zj{oy+Ts!k~tQ_(5$*uJG;ip(x=%aM+*3a;ak=V#J6Osc|Xwlh+d(g| z4r+JMIFHiqWtTO$-jJbCoN$b-*x2qi_MAtCOe~Ums4oQAgV0KmPcLo)^KGni{jKz5V*-^v;`C zaiKSl3%}_!iDgJ0J$Z^ZFm9*+{P!QG&8=tlHuUVQzZE^oN2@#PI#&Jcw@&sF*Iv1I z;}Q>Q-rCq6AoGU|?)}a!o=@j4y_sHr=ilPT=O3l>m*2q;o3r+#)62vLv;WqIS-;+f z98|HBo?^zlr3b&qkIvtu-~IBh((iutS6FH|rHP42EQ>o^zEflUD4sr<^YiDHZ+%$l zc#Qg(ju_$Ok2s}UobuvgUf8|Z$zI2nC+3BT{H;uR9sBI1+siTfQkr(7c!`rfrsZ$1 zJi@of-R;?L>$O?uGR?XQcliSrDe)<0^H;l6>%Qc9dxe{Blrpb$0U=Rv45aY#GoCRr zRw1gD@#G>XzN%Hq{SiCJAdHTgOxbemM%>{k)#SDvUAC9C21&)Zd*9kFervr@Bp#-t zAIVLoy30k8uGkcPEo;OO$8CTMc7~G~Kd|z~Hfrmi_I_Af zMjfpT4S;--x9JADuPm#D@y2qisz?;&%Jaw=Vm`{KRjggPnb^+iEjzMv1wm*=y=NGx z&l&aj0F!BAjSrZ}TiM2`!_>g3`LpT##n&+q|7JRK?s}SEynumwr)*|DE^W^l_9iB< zlnWkq^Tx5Y7tWp7PojUFLDaKvp#ibufHl5^;YTDO=K!{Rw847B<-hzH?;OVBTzOx; z$0$bi4(DJv!?zN#_3z47y%k%HbkKQ#GHND04l?B|dmPNi^7-s~VNf4JF`d;*EE}sz zaUzzIhF4@ubb^#%LV`B--4kR;%EV?A>(O`7o~z||0yE=|K1hgk!DI(g5Au}=tc)xR z?d^!9^C+jhvrt-*?z+~KW2}C%3vvlBuQ}ihu2K&m`<0m4SoaQUY!$1FhN!c+Jt%&Q zGyC(V%e*0m#)DDOuGD=dANan>hF%{4vAuG+R6m)laCPLxF%oQ8ZJoNwLQ`ANUeyp6 zbEc!;OcfXlmN@%-AuVkRC~#+43?pl4mF-dL7=USfS15z5TbB_rgv7+CEqo3*0yc7J zb=6~+MLa|g76t{fkIBeY4683|b0dgGNXr2^jicLRte(vdKw$#~dl$D5lF`I`u`6+z z$?%&CQx@(Ftl}WB>L9Ww?v7tz3Rd=J=gwH8efkm% zUctuBd91MfoXOsBC_j<1h<1(vBODa5rR0UuoMsSAA_kqi z!ANcmqbSKLCh?eU7FL~wU>uZ{STn21kY?TbtY#QQ#X)hncUA`CLCIP2`0{|ipvfh& zIM|A9!idy^>5Jv6CC+XP!*s{LmZjwk|GeI@An>r<(j4f=rE7Wp&*03up$>IOHT=@Q z#gLC%TkDu14@=i#(A_qcuHD8%`PHXb_Upmt>FzB&tjB=7jrFHyJ>I>%8u0$lK2-5E!w(b#*mYQ^R>P1*r<0iO7v~N1ttU5S01C zAtjEZ^pQ8>WZn~sRQ*Q}pipk>@H(OGm-z0%;O`wwX0Wlggzx)b zrMs9(@6PA{H9ffX%e4IH^K=Z~MOtPZUsm9VC(1#i{=>H8Bp|jr*Tzn|d|^KQ@%uN^ z&1>gv8CQG;a$1VO^-iKE(^YEk4BLGGW`B>Gd0z((BhRq}Q&T#egsj%0qol zVE*HKkDjKFKE0dn;_Zv2t(O0W~RehxOd(`>;mdhWOZ(@eW zXX)hRV!HO)AEm3W{c*Z_^CuXj_bz7BxopeX^2emTE#A2S;TPw;X|l7kfghTW(=y%w z`{JX&Nnd>Y_vtonGOuGQ_h&m#^Bc(BqfBKS z<~PPF&WMfnefo%HT6ke2e~TTJk332fFWx>|;<|d>)X)jeqhyK|y-#YARyCZ-OSNNP zwVQjrxQx%bcoWd#iZ-G)mwX`Q&S+DyB>-E}(8qEFStnR0Eb$>*kt|mA5nP+!8mBB6 zsM3DqzTl!dk|`a^8QY@_&|`%QgJdkbC`Jw3Q&~zp*+@Lhc61@$iplNR*mmrov(m%f z!)34vyw;FW7^c6Dvx~ulI-XHxUaK2VXNlPt?BU3$4!!6eXzi04=p<6yc57!CLIEX(F)?;5( zJ-Xd^f7lv0fhF1|Ca3)mZ*QN#5?`E2Z{hUCbpFEYHeipl>~WADm&oOSJr20HL3{SF z+S}XuMyFxDx71#Myy?A-nen&NIkR3KsK+A*>){&PWHzvmM$W~uvY#0ZGuSFWQQB%s zlKvyDE7rOEGe+rX=Sc4PC(rrF@=z1{D9dYu^!S^>2J30y9^dfJX@KI4e8llLM*WyU ziRFq8Z1c~Hl9gCE<_DAl>DUJV0fXi?go^_jTopcx0~K|aEeVoUfIjIuYC^O@p>v!V z+h|@g^|Lg7Pq_=U0dNk*K9r?$7NSC3op`Fd7v9UD&%|0HqI0e*G#a&CQ87VE#8za6 zq!^0g&b2l}8q0zI16787GAX#LdXpAs!*MwgR{a@?mDwjxw1p!&*@U;NMffZAX6ez z0x}y2)aVAV5Rl8aXbee?m(q&zoZ)2{L>@@sAy7CossgsBHgjcVC)nk7{)8BWbwXm| zd0E{AR$$z@?Tw#ij;Oed3U$GLNxjbD5USUAHWnqIP7+B?sM08s3=E^RayW*re6=2m zZ-_+C>y>I3gYh)m%SvF`TnA=f^%jw<%(tJ5RnJdbi#A|PfE}^1ZrrNM_!*+*o*A|2 zu`4#l!MqsbQNqgCEvxj=Ca0m2R!0{g$CvePb2F~%oEE$K7I8+Pm>IT@#v$K8)VH!d8RxL$fJydA-q4Z-3B-G6bNHqa?GGaw$QMvc^J?apu3m8@KaDS#h&2_Dh8)_DeFz&Q?hRH)vjVG6JDud zb{;`R8=nV(WMd_m#LCl%GUY=$mVvN#i+!WgcFWS*#C-W)h{93n93v0%*M+;ZFK(AP z+U+BQ6MH;j%+8eMF^ZzOFuc!FzDs)m(Oxz$+XknqCcvAHxceLh{e{%*SY)_E4&W!u7x!IIz;t@kx9Q zjAzr^d+YQgd4A$@%&y5;|Hm;Xe|mZFuq#s)b~qVBjf@G2&w`RMce z=_?G*yN`i+oUn$^%5jN>>n>i;>$ADPoyO8}4UB7GTm!6u@c_N=ct6mEP%LJjEb3!= zKiFvZD$_?1<56(FiaKH3+WLMlN%yjj^uY_3Z7j9QU#=I<&ZReQoWm5q3+e3QT$-Po z;`Mo2UE55beDMeuEXy{9^76`R+QtkU_V#anXooUOIydzSDRE;}ixlZeV%f)wHs@p6)(aPG4e4+ovnIX^HycMIgDv_f0J8w;`q- zEN6>XUoc4YMLM-+d|DLc2>Mw)tMpZQ=KVmFt7p z$adIqH_kpiWxK^4G2)0Z$i7b(al_J)e;B4uPQUW07>JXswGn2RzQr@&Yojh&zXE_{ z#uavRGWO#Rj;z=+hi)oURN@jpg*{vMMr$SK10mh0r&;6{O?xx7T05Tlc9YEcsE+{( z@2KLF5UE;1<^zSwZMM-)m?1o|tkFe@AqsO^Wnp%AT=(X;Y@=NX4Y7K-kc=3h*Avoc zkvYK5$a978+9UU`EHMtn$NE&iWvnhIk)x}u-}Nwkx7-#O7@bqT+kESx8!xq0V*Q|I z%~X7mN4}!0G0SCkL|0dH;-L=?Y~p}C-r&(}dOmo`2kq&`XY@HvznmK2%z3k?PGgYX zB|A=GMm^4iJ9BC=En<0G&bl{)o6sjQn9pV)!N9(W$r;>)!_v5TxNYsneiT1*J0Ct7 zLB0NLFdna^A>-Xt4%XwWdzr`2AK^J9!Z+{ro|eowvc7u|x*|vE)gOg%EAIIxE@~ZF z^_1le<HT?Ht`+A8Xf5i(GCTgy-8ai7hm6y~ zg~ln0f1@Zu#-oaN?)DkvLH@B&xpyq8D7G(MCM|A&7TJ3QEmhB?3mM>87~jcvsv?S zJ;6NhN8n&wH30YMRVVKrr%F%;-+9G=fP?a!ud_rgWX@al6hT-{L2;TixZ%TgUZe89 z$g`bo``f|NYMcpg4NKQ8Ke>Y!8J`-4%h^))bQv?{VQ?O2(;IIuzXsUuKC2@8j}P)Z zff@5`fS&a!ABd^*j1llWZ8x1u^*pGEL3Ea|D|H6w`S+ytB{L_^I}rEPuR2xLS8qKj z!CO@)u)rmJM<0wm7#9d@by6|~9HA1r%*k1ADpwy?HateG=cOgbJ~wmLV?{YPs+2ZC zBg=ZVyUHMFJPOlo!CLN!?Msupa`Yh@S&rL^*r+_~JYQeXG-(EKE&ie;=ROjAmXvn zE;WENmjmp!w(VBM;%TfxfM+L{u5sm|_tV*n@1)b`UP}{Gr|h1HdYpQuUuPhP`x`qu zm?&X=87mL{GTr|CpVO_+eu@EmzfEgTKY@-GSZWpbUdG$YsR5f!4=Zu;lXniwv%Y!t z3})3^Oy}|Zb{=^SB;#PTPi{T70eYNSZ}}-#Lfb$);Qkh0An^;smlM!8n1%iJIqHHd z#?8!3qQ0hWDcj5E7xCP7F`dPdwp<~CgYh_P9tSqw!h|$SST1;NeG8L(aAsm&XMKYU zISy@H1LGR_#?`H9zV zU(%c3|93V$`>FX0xS*c52gZzP@Ya~uIo}}TTVG2LKEV&lpQSH9`J43FhyMw0)}U=) zoWO(nQ@97-ee0{O50ypru$MYG2$p{OxWyIqyTur>OluQY^wTF3elJJa6FZDonBDYy z@plv$#-$j=A7yQtm_dB8izSxJfY=GAeBZHC9*e|%5Noqum|LRy{YD=uxx#P#g|T|A%Bw9Kl2dWT zG=#nd2kKvjQ=ch7z=0K9<{f7S5ds=W4wcVm@&Sba&)BW>g6WtOZPt#Goj zI0cn59%1f%iaZ`oR8v;CLVdO6AqW5XAe77BZsFl4ABb{rlkB^^vp4n$Cmv=-b+*(>HR4T_Gp^pgtxrj{KalH_z*U zJr2lB&v&5WQn`87y{3?2a7OhE&&i8Ss#a)ec&tp)x_qU+0XeR&@kTr+!1r}^HbXF#^{Kwn-JmqH!ExXxIk`M4x#aJ#|GM%?jPOp2| zP!-BXQ_}};@yq-8)TAm>f3H;|kJRhXx45)W~xxv1=_IhE_A(b;bdavYttqnY6 zUrDQ&9dCX0i4DBl++4>%yd}KEcrV?%^IP0AdB{te@%9R8fbGt~c#{~|FoD@}Y#C&< zzb%!_wr|?eL#m%%YGt;3#0nvOEDCiID_yl=|H@MDQi9+pfdE0pq{MXH@COgy9W z&@d2{cuFt^b_y?Wf11+3?;!0xFqU8Z((oANv5ti8>Sd|VLFN5Oo5G=ZI`t9?q4POV zbnJt?Bw?=4AIf5kG0${~gBfhXdQN)llhCksn)vuD0ePagDky zPsKMWn9Irsq6WXB4x-+23)fKZmW?qghH67nIgSn_tH`5F#r{J-opW#xj%6S1`edXU zCf|dbG2|uGz>61H32J8o8S94>Duf6DxE%iBhBDG39oVN(g{1J zZPT?BzK?i~w28s|t1A!E(!)>Fr@#46>BFD@?`dU;gZfjNn&mSU^=BPlLhygU6g;=K zww|Yzm94b6Fl__Q-hTZemTX;2XE5+=0ncpLH@4D;pWI15{nhW&$DiHBtcJ6=cIC{w zXal>B_-gbOE!%UBDnc4%AzmpG~uP3xR|9KEM4a{p`12;Z1{OylJqB8;HKFE2jx& zJnUh^;!+t|Cx`8WyVdJ{e8qVA+k-QL@J$phPscz0aDL~FE9oacc+F-c3)N=(mqUvX-mQ zu1_}1Uh;z+`C$7wmZG%>ycj@t>FN*Cl^Y+VYj6Bfx_IRSyy^8O2IgTl9L$2lC2d)U zsz>XIR{zKFBb0B1FQxmpewFTi^$Yy{GCkysd5^z9d)`Z zB;u6r;}QokDhJb1&OEoMcgg}M!VBONiwTOBgd>Y%2bI0*Nj zvrG|AgYM1hh&52NlfBhtZ&BQzaAw}(=$5{D4rl&RZPQb%Y=r{|$YOB=$fL1}m`L;I zSZ2A!a@HuaTzKI6`h*eWop@S~=w5ioMr4Q=pi)j6TUlh8k0Rw44ZpnNR*bWdd2B=b zhUv-UxOVSi?o~oUJuf3^R3ha&TsbacI_Hv=){(OB*mXS1cNby)ayd<7L5KdHb;Pyp z*!na~rWd7SlyhZo8L1*H`{w{7K6K{tp!BN&d(SX%OEPC0?_YKe@YLWn!t~6dEr-k5 z^Eev`2j$ITN!Gqsek4i2Z!vTBD=d>L;fDMBZvLSnH9Z^=L>QrRv_B(T&ts_2*4sc+&tUGRn z(S+S%>O8U|%L_Z}@4SBF06zTjXtVM09N@Jzj+AlW9^Z8KbHIJYy`O_11gYQXMoK4}$R*DT=}Hgz-0OMMJUq zL0j7vxUD@DSoRz1-r@+MIRW;P1+JniO5y;Bn2sGHw8jYSQc0Rr$I$E|t(;h!G?q0B z4$)L$5s^=7s}f~|1;E0!d{vhEk_b4;d{ZmbeAnE-1)CEN$$XtyVupIrb_{a{ISbF1ve0?8IsUgIL2OZ?ohviu@qY&bRD~7zIaPUQJY*XQ=$hn9a@}nC8KD z7=&ko@Ax3xUOwUxw)CBH+DdQ6VjvYh>T&=k&D^vr;b7Ga8;rM_Rx$XF1MoN--sZ*{ z2IXmh-V*Xp@x9DJ^;@>gEoai>OgW{E%-eYPvTK0L)^b4JNzCNTdxt(a)Q{?4Jo5ar zd=5aL@QSDQf^L&Z9`TV!_WeiqYC00B3zt!{E^7HYv+!rTHvZzT23SPa)r&9sKnDNi zhv!-;4{_qC3>}Ip7HO4`vv6f`E0f=di4d^SO$V_%M~eOx^Q+AHLk9& zR?c!oS#?`eUFMJjgtn@in1-;s8C@%MNVFr2DE@f_zIWu__f{@_K6bvlz^pK`vsee& zsXL0e>^jCeESf=#%FsjD35JZq3}gRh$?bg1-)}LkC|eox+T!=3WaHye*8qC^^KJb3 zvh*{!_c6bCGcBHf56@SAfEOBnm@Z%cNt(pu2X_5|{>Xp4zERI)8}b?1GtQ>BxsskN zeVQKK{S}tH{l|3c^Pi`?c;+&3lFt_LU3P-28*u`J+U`eP!tW|S@11Z3w3B%5IyHp> z{Zo_aG*1-u^b^mF4{B#}lvza!q^eyj6oj_f3hQV)peS9k$aUArx2EHRTFdm?Hh=y!05)VQ3 zQcU~ei88;@H2i`lV*3J$`k3y+?p4;uLm9We3+++5z53iQt}n)~gJZ8g4-)S|Y}$At z2M4C{LWOTZZ?-!JB%# zIO4{en|&L7Y3u7J(!Gr1{(Rv_hF#!2x0|nwpKnS};eu^?@zn0NoNawXTuk@X&2D=6X?^UKtL0~& zTgx|0-seZMmK&`2!H5fNd!Wa5z?;O=r_=P@mGtJj|Gh71d;Q1uu>B;Krt=%u77a~p z(~rmJ;Mv(;$MUrwVo=_{*xS5ThK@J=xcSYJpbyxA*n$h%iKFl^ zfz9No_gLGJOjv^QXzy+Y}A{My9}0%QOMBhUKuAhpX_AXvZ4A0 zW96(@CFaNaK7=b=MFFYGc^YXkw zj{DH1LcA_5&ogn(;R46nn@h!5+6Hd=AU^A3W`cZXtTNFOP$DQe*A9XCm{`R%3F(iVBvA4zZP9p0TUGQgp$)p58 zL7ghxGHz0m8Rk+0dosw2IW#o}V5KNR7zMlRiB=_9Q?&?D7J-zLPIXXpIbk}a&Al?z z8DU^WoT?Y=`!I0s8mu@XO`$?Ua^gy{3E<{r18HZ6l6r15G)JEGd`=fo&@pMY9qHN|K(Ce)-a(uuttIro6^+z-6I4hYA zgyNt){oy`VBQ4pbzz_$`y@LI-gVun2iFZiGWmqIe2+s$f2BMYPuH9|nO0Xd7113o zvvbSBKSCyq%CAt|lXB5pOgHfi23!jvvq%}!(l^H%SJ#)js&(kCT+6Lc1_NjuFbi<3 z!|T#nF7`PqK6cjsER$tm%JijwkvzKeAUZB%tQ|$j z^kYbz{SP>yanuL0Z+McQSMl0LX@wp2l*P}t7Au?a@fFlSz8CTQ`3A0iCNUuILRvig zdOCOMy>$8JpI`~xw=tvMO}ke@JvZ;Yep09%dH;%&A@DkaOWuC|@!zLUKKvV8bKFYL zww9pdWEbe$Hv8{*pq^FaA$L4)_}q3As}xLOf|WCKQ)vOuZO@&VOIIuo^11EZrM0w$vQMGhTpIY>R`yp=y<->S z8W`8W0cxN#Krhem~VReuyGHwr<~h~1=7^~Fiu%kZ&V46jPBvcBvxMc$ZZxs+#`ew9CL46VIg{NMtJ7jg-2BTOEDk=}jl zx-IYelkeY58(0>XZ)SY-*&ST?JxX^TJV_58t=g;@(-_dEHxU?L%gxyLx(%~GXu13B zSXN%Ft#3U`v(qQjxy9LZ?b2epe)(LwdG&ny-fNfA?fZ|@&wl;6O&NXf{xU8MaUqDe zg80@2KU!4YST6aZ>?rD>&lmm3?|ytno0yJ1n5Is=F)baBQO`WLy~f2iU!L!*rX36t zJ%0RDI)Ckln0EbNr#IgH3%t$sBfQ~t*?!=$&A90&g>J&|h6pz%$L|iHCaC=P<0~ZG7Sw zX1|+Wyazq*g?|)Ay68XlBg$1aVOxE)?Mt`zeHh95u#yqeA4Be_M$*^MD07Usj7Mg{ zgnIcfy1Y}LH~WAWrcZBN>i4pN&+3*xYdt4>v zG8Y>{pI6+tPR|_{KMbMBtF{5G1Z?x7%Tr`-4B*%6PDi+G1bMjvDxAd!x?}({fR~j&v^2+~Uu)$UCF&qyLyz5Ip_J z%Rjco4sPJ{MIW3B_96^#9y9+80-JRYHo?@xU}dopvz(Y>~0lORTbV z;x$>ff^s$m8>-ZQST6TbqcOcx)neql4(`KyOJ9uQ zCthVSTa|$s6<9Kh(NJFTSsv?NysgZ9uq-(kH_aIZn5iuy0y5aJZeVqxc7*pS{kt^cDt_40C%6`R{+p?4YNAb>+`w9NbPz8 z52SIOaGZni_$L_$?^$X5;PsQ8#81nRG3|YZyO=u}8s_mY?zQ<<|h)k~8E@*nm9Dn8#W3u=Jb_(u6zq zpLS!r^vNr(cEp+@m*=`NN?kIVv-auIKk1O&IXlj$Vhy7&m9MkP4}4+@L7zdR7j_7A zl$hnKWsT)w2u@t}5gXI;&>u*rZ3IE}#yS^6=NPa;vu`M#4t7L_G`srB_9`>89=9v@ zYM*KBP-FFoeYx0-P}!nEQ0a5E^n)H;6fwR^S?kC?7jy8cw^+u=QPVIGSuD6@1WF!d zqvVx*A1RT`*>%YQay}wS& z4}N8nBBbMZK6nC?E7)v$#bTT|L=CVF_!-FK4hF5+y_Bu>^ww)v(huIgnSS)%b*zqd zGCf^cPal1LH+_x?M(*5ylI}09r!~wVcWM?d2;jQ7(}stL=crBD#pcEf)c^}Agds($u+(YhDjy>jlx^<8Y2S6!xWavkrr z4<2P1c%kRBU2xWmXLyeBJk8CWO6N|WN>|R!rR!JD<1LMO+;BXGHxkyc)ax>qyQzBz&-xnCnwTPJlui)bD%mN05VR_$+_U6e+d&A^5 zE@W@tdt!qSmoP>2<`#arU~recdBGdL`9klYeR!|!eNg_r*o}&z)wTIy{CmaGo;Qtm zP_M^va<#ma ziEEgi^0*1##}#q=O~<(9LmZ=g2|EfS9vx+)e0yxUgqK|TVRpmx!tcf-9FN43ueEV` zMDFeLkDs~R44qBn!xZfE_&0D9QuNv0u-Nt9v<$q&MJ}3)3I!|WYH`fBad@5@Y_J6& zY@JMydy+mYqC_x&GeHG*D+)?ks@#!4{M_gN1#g_F%t(A#8 zFM&%7D$8rTJD=+Zssn<;WcMLE(}vm<8#Dy-ua#$`&OzLQ!^V7NkWo0sKFdUD-vbd?@q)O-bL{{6eAaAw-tX~S1odJA zSAn5#`)xwJ)mJ`ube=F2)=NIR56|Za%7LCeLhid!^?*!7?s6Sb2EJr%1diBt#nMT18oAgGG2fSt_M^bVe6*b6h=VYJ zar42_nENbtQ&2?_Fa8YR%d- z+$o5RbyV0pg(@H@JUJNm0HiArpGlR85q9Csshm+0EfhnpmI?#CeT+DXG1Y=(WkKgy z6cXif(xWP7`$&k6Ce;m7u93d#y2xNeMYC0F*;h#eZL>854=fw@+Ox7zsnU^3=JH!Q zbL)rs(%)!UE#21lkzJhh=j|*B$+#(!$WVDW*v|L>n(5b`vAV298ug5Ye~|1j@yzrS2K}+FHqE4(@4fap^AH2jNn->sZQmeQnw7 z&EI(UN@~FSs`@AUBJLToU*fuuOWNA(jp!#nV_v5lSbXfa+WI~}rQ=aZ@1munMhGzM za}YIZ|2}bsn5J&g*bd7BHthNPkV1F;&k`l=}@K~yR!6lY&wk&f6teUwM6xCf4H5Xw7|!zF9GiOMYf z067I+<*>T$HQi09L8E=RHdbL`Wp_gpVwWS0hLJ@1E;M8HxCV~025^6YgY=%^8iLmm z&yYWT<|bZDd_TSZ-oH&3F=4{#bFX7%f%#-B81eoFKT&W_`?Ze;$7?dqrnk9{L11@& zjiqjXk?w!>)AR_>Wu83x0yFwPHJcL?Q`zo7C)H8b#J7%{AFKSF)Jka2F~V~0)J!_P zIG-+@p2MUgr?K4YBCa=%r8O+~dhgLwTLJ7Lp4&davahRanEenhMNUuoEYjb^7Gc?T z@S^4p<%cWR$CJR3^!I+{-qMSGK$9h2wEQ*l4$(w95HSXuG2-@2D~!mCs>qFv?%68x?ot zZ|$OPuj##V@U-99!kabQ+ZfQco!)=@24>W|X0yyq;EjZ5xLLS~H!;4v`#AmPle_8Z z(=}WKZrWQ8{H;wtesgrMksS@rE@H8>2b}m3!MCUxSaT6|K8?EJ63YDk;m;6H$_JOT-QHTo4eHxA z81IYU|3mr$gY!5*@7OU+Wj!^CBin6nSLJnjgQ6bHJ$KO z^bXJyPq#eA6L#_=QFcL6{COEYErcIcKIU4EE^>H|cHFvL)vMR7><6*Z#C}bgc%`pL z_oOm*`5hHwsdoJ-`sF|+UQh8m(T=!D`V8N1{GPNyOu3Bl9PVcuOOK)kcp1SZK`vf- zH(j{=4hHI7&CBF+30y3Pi)C@Arswc-$_!>%#tlP0q=ymP)&>z++RtlmO7xz5T5w=a zck`ZQ*Q@#FIXVJ5r2p9fHlE-5V^5_HM}87%%O3%rRmP`TgA6{4jJ-Acw4gGFsQNlM zS;<8V4=MsbPYSQ=2sK(uU?*PXsg}KfmdAO)nI&-!aPvW|K6sTfAJB&*Zr*D4Ne%L& z6NbUOpEC^7Xl!Tac2`uDilyM4i*0Ee4=N)ulDpQ+D!uB{a1Jn4p)xWLt@!IwRw@7r z#_h;h;sQ|W`1w<1!8{|q>Qeo|>}ltC^8s*M zZ(^go#KxG5LwU$tIotANck)`wZ2~zdAd9A86?=i1T7HYPSTo_($~c3V*^oB|?t)Ae zZT(LfBXf|-74@}Vy8LQe^P@0Yf0E}Ty+nLZXewNB)?$>8JWkq?z>|>lS#^?DNfV^9 z1VnI7^DtnGI#geyx%Ac^$XLGa2r-#a#zr#*f$prvUHhkOd9+v>-Wms5&S(rkxW!1l ze`n_VM>YVDgPQQXEq{;lz7jEIKpxV}`(OC%GFIPNKbEWf-KDI27q|s(QWqhnaxZ~t z2E)?v!azF4mS@Vd7Xhtog|VCHIED)C!}x5LX{B-fw}Tn*HaD>}7nY>m!t5y9cHBto z>&xlM(jB~%_)&Uv_ai*OguQ=FcKwWVM_vQopV1$!AFy9?P##v>z<@m7Ydn!365C9Q zrQPe969d#|nPmMaX{`t1JT0VEfmGoteYvV>BYjNU2@4pypp(<2Lmw4X`lcAHE;{%O z(77;5*FYJ7zI#k1j*PQ|_@uM&BqSm#qT%jo)=w!{Cmng6$2=BS7KAHI<}6;{(8cfu zsRALAuva_LaL`mvyV`2gl{CQ9IYwhjJeMg))!@ao3L?({|H^O}nYj5A-5#cAo!+mmk-tKYUAIh$<&w?yz zhcFSs*49c|S-P9<-~L^?_0=!Z?XUhZEk67*ZLDG0+oznAp)TwG0xR=J3B83C&^9s4 z+zwVed+p+h^uu@f&K473%ul0__zWwdZQ3BcKYVpR-G8`}mR2@#?9h3{HImmce)xi} z%(t4@|FHfr{I%Z$m<8`JXeTkL%6o5}x5-qFVOih9pmUJngNMuMKmYPeENS}~59(5y z$AH6`DNe{SPU8rSBQTD@i;cjI8#nehKu>P*BG=W~>PMGe-1n0uE_qDaciDjJiyo09q^RFMhZGTj`wq93xlz4XOjcMHxc%EZ}{*iUq`9cg` zZ#;Cu;5NK~eD{sBSo-!tI(lTzX0}*f-N081tC&XlVfxjFH!-kp+d6u)v;GA`w`cpO zvQA8+TxZx}-Pz{D&dqfB!l`ua%K7yArPFEQ$c#Neo4^+qkKz08KEIuA-+heE>q^?@ z15e=Fk}1|F>jR#8}URMrJj=2zKucpC^*uStjc@U@0RCR;afIy-sa}h zboK3jo?d(7FVlr9KS?LgTuVofUrJMW(R~Qhtn*g_AA5LG{QUvw=Ee$^ba{{-Kl~!y z`ts-Li{Jf6PRocgq`Z8dIr{kWo3haU3SP9J?4iw5OF0d9F?_? zvi)S*vXqN@nEO{+n z+`HdSkq%2yugmX>(MGQo7M>_7S6$CJzT*f@I_3!qlwp6%2I}P*RFvm_oW7k1@D!ND z06fl`cX$Fzw(un^_Qy|MvH^P>v^S3#e`jVFATyN?PhtrX$g<2qd>pvv$Jmd!u0z__ zTGbX2ZY0(&q;EPlV2`r=0v-=o%O4vNS`o_j;Fa`}a;q~F%p-%HQ+=y*fGkT+_I#$5 z%R#x(QK)FTTD;a33}lK9#T<}VlwpZ=Zm7~82xXvN7_=u@`w#>_Vt76|ttlUAe!cd3 z$QRbLop^4S(3I+U-HUjTL&b*aQNERWs!%H8f@aKV(pD%;KtvScy|Q5?*kC;@b<#-80^Gq+ zL(FKs>Q13JVqb$}oO%CX8eh)2=V)H9#*^2_W{xE1??X&DnR$Go1+_T8#*RgF$ms^>cF zsr9QB^s0N`u*5oG=0oQcC}xkX+{CFI4@U?Zr2+m*2D7;3;6hv*j{ZB(P^Jfks&y zaEEwtkKgm03E$h3j-&^ml%PY`m}plX1$Uh30yS^`L7ED%!;HOrCK${3bGyoiGL_6I zm2;94DhnUw=quSO?_lPcZ4AKMz;g}ecQF&nHlA0luP&yQ<%el$5g))l@jrgV!I||a z#{qo&)1fIQ$C+;=0zB$AAP)l$xPW^W7b z*6-3SAjlA>%0$#kb@_4)V$lR8-ImOdk*&N#nZ-hi>V=pEC7)%;*0`$bkNQIba*zsD z&wJKYaZ=oAqSCU#)~;gJXgR7lvh8S;B1PO5T;)WyEM$68?Bv%v^x`@t)=e_1qsl$| z!44O3Udqa~CF*qe+6X}4532$FR|uL>3?T9-`XE}+u1Ac_ifRWa%PTfraVwB}$hiN( zRxw6etV5KcfzHb8!Sah2<@HN_xxHAk`(;+xkN#e4jb#ry0{QncK732h4yD=o6Y2Pw zOXcEd4js;g6;Ij|AZz0B0n5^RR`4j0c-o22HW02b<2DYuN zZQ4p`cONdLUwwQlJ$$l;e)h98H$7oX$R4y`d%@$!m^RRGW6NgBd+X}ibmh`1taP@3 z-vN9NeF!uE?WB(}F^nzUdv6IdKk~}{%zjJo`x*Y$7^iUr#t|4t;6+E^`t|G2H$bnS zgL~(lR>g&(Nvj7c=-N`wjJ(;0o;0-|aL%H*3q;o;x|8 z&YeD<&YU`iWo_ruIy!&soGvY|qjS2FZedy52vM&*x!uX;tl2I2Gi z6CV&pJ@Q7r4jK-icuI8TX(sYmWSf#Ob@J&%QIRd*v8g7aQiot4lI%7gehRgZ639`0 zxO7uqsIsjx_revAWgW{Xr+iz!K}SdoCpaj1$!Sly$aDKnk-8r?m82=H8h`n25)>D! zt(qak7Gl@l+K4yOE_N@C4K~|r*Uf}!w2wdXUp4CmuN28vO3m3h6nqeexH3Jc?(R3W z2799VHGSzqmeq<#ic5CMyT?_gmYFjRZXiJ0{Myx*yT#0UHu#9kkNAvY_5jod9mT;{ zjyK+Za}nU+f%*B9SPu8NEr~mght`u*{P1G}1M{%_1|Du7J#ilUb1H zu-i0U%O>X1BC_2o<)*Bn-}5RPbQT=EK`t8I8PmuE}>>;Q*dL zxb|`!v}eoaLS8fO38%Stf3j(~UUIY)YD2leQ#MzU#TX|_cP6VtzK<9N3A)y0FevKC zjf83PZmlB7x_hQlzrDp5OuHVpg$igtZ#)>P1=Z~sI=PM3zbre_Q<)lt7KsiODR%fk zh_t3baRf+ire!_FK((!sepG}&J=U|)WQwC1H)A$^@RIZ_Rt?}*A-0D?M%)Js{f<8O zGphPIe~3o(qqnk66O@&V@=-rZN4Y{5u@E{k0&L5Q-l5VUNv|z0I)vM-hW`EwG43Aq zXh`-&Q(5*%Z_~tkIrhBo*8aliK2c9;Kl;)g@4u}Gpf%(9)6UigxiiEeNbk?m>2p{`;M5g7<2Z(o6*GsWErw`D)}7$zin?d&nU13j8fVQE;F`A(Kg z+}^mmn3um@Nspf_*}Jp}eq?IzY;`mb-oJ>*DSt{Xu;4Vtv}OkstHJV*H}{FUpr;xa2FvxQQ0` z!?2?4LFeUX^s$T9W%rx#n=i38(TUpG*-fX>d3x>Q$@J!xvlytiV4bLA=vdvn^C*4t z<(>4wM_;5x4BAVtaKIbCYLM6XX*Kuu~e-sIiYa{qn=Gy7;5iy`MeOyY#zg zgD@hm%g4xXcj@TK%W3}T8NAeOn1NjFn#^yFYz_jFKkKM&GiRqX67({zn?$kOSell^5uJ%9C?v0S?&knN4c2qvK#r#M|)j1+VXoT_p>iMZTq8Q zxAkMWVzPbnXDH5y{7HDc238$j8}l{DFKX^aCK-fNc#=4z%GyU+<;ly>19eU|I?w$w3BAlijQmcF{?a>1dml$z+2i8~rgUhy*Cbv&Pt2qi$)7 zcKAieCbamfJ6~G0<1ZdHF(y^NiW8eMd@;+u7iZDi!7M7<=-c=pBM!i7`c$>Yxo;~1oK5f8@$>lj z;I(w*=qVepH;GyFrZJn|?A%c-7jp(P?wzm!eOwlXC#y9J?%7!`=TU4w?L2Eott@Oi zY&byAXx?_PUJajqS{q{i8QPG^*mW!G5{lV8ZHcJ#%EZVr2P=mRQ*qBT0Cvg+QPP5^ zS%MU=w$!6--GVhAs7V86Ljt4`$Nc1;MgBH$nzN0F0TGPB`kCbkfn>W(b6KZ5Ny1LE ztw>ql5du*@+JP2b`cNu-(Xf{oF)eSN27y|K1~C^*TK@E4b!qg{a+7uQ!lD?DTM*|x ztXyx4zd-=mqK-_FB{kPwc)A}_TQuesI?`llzI zgUtJ9pJax>Gbde75fZ6^+kmMtkb&&7Ny#R0aQc)aI_)S1rT&nL7)7Zk69CC8&gK{k zM0AR0!xnqtkq=pln=e+Z@uJ@pF`k*UjfKP@Sj@PU=sM)f4a*0CSa0Pi(@-!%-MHmi zF1gIeRdI6mNmo)5oBAl>KgFIYM!iC$WFgNAg;;LF5X5vg@HiVDS6ATd7W`u!`5c8y zeA-NSl!22Y_dNfI4B3`<(%vqp=_GbA=%qM{Uy(}Y;YcCyIWg+oR$~wr;YVx4AjF6 zc=&K}6En@Mt~^SMk8ay+d27gz)5{Y9owIBs9GEwWK?#%i$ifHZvHhYAp}m(`?t|;G z&8WSK=B-q1wO)s;G4q5)@~u+@OMPL9=CUqLp7bKW8?7l}CAyRW3=20{v&jybyW2e1 zGEF)KqYX_=$+mg9ta>R%Z851wD3^_#wpMq!%N9|*>cZ>R9i3VNIkHMWzX;&1MXjTdiTQAlBM!-XSUw0w_OgWt4MlsYmEwZgCW0yi%wia>2g zZBr6eeOQ)EJ5RWl1B``}rIckz6gKlnlJ$~rt5(gqVk}ETfL%Pvy!gGYZ?t6nCA;@| zzHObdK-p$#m(BpQTH$|1@2A?a$Nc^Y7xr z#M8F2fOmCrJ4Ul+%eMBA+tRjugzzXWFWpIh_aFX!`uwB+D@`8Sw843YFi4NG-hOmW zeD`AYvU<$(adUGwt*mU}o$Y+Oc>Y*=xwu_jkb@Ner@MskuzT!hyEM;pS{k*KfANw_qz&HZm>j)g^06pFW2X&x^@xPCe9#-dJ z{Qbs+xa~CP*tBJzw@h1IKI{j#D@I+k{a}xx^Srj6SAP^A_af8fPZzBpW|wRit)KoN zI$vh7?HtAz3zHKQX&QfLPcNjirxvW!cjDN5nxCCcn_FAxP~A`O|Lz99%)nQ`=zJwC zUph5OJ$`B0+x`dH?c%X>$C)v<(TTI=KQK!hyL(q(J(K?WCvV%VdDByq=zy-KhmTj% z{YT5`>pP1!NN;1CONs1QUCNRaBe*myj^ry1JD-PP31`3i7lj{jyXwn&Ki%E5S@SS9 zFC9PeYC3o62kEu9{#*RG`$;-?@dq|Ak4wMV!}3N25kJI`gF1G1Ht{g~NxF+E((l}O zA4}T)Lwa=Y^R)cK<{J2E`&`N`8=z zWQf_7k9e|6IpxWZdMb-A9_gY_y*cJNj4kd3^E%4gC16Gb6Nq z2v-rWMkw0@R*hnCZgU$=WRB_R`XsETR;!fT5qxbwkZfbItW;%1>vcvllJM9dUW`Wu zidr!*JKDQW*22z~1qO}*QIRQtnl1ACfW~Eo?f4P10Jx71+P#gkQKI2zWR$#|bwZSn z`5wn}-PG2+e!ytLIjG`LY*x8qqe}*0Y&tmQEOF=XehwO0OmfKV#L4=6VUYP6z-MhL zqq!to#p;m{hSi6^MxlH?Xql&Up%lN$pd0JuJl>t@#)of1j<#+j017f zsKZ}l$$@(uus2RGM+Ep~I0xi$SzIoKd&p*y#>-ulIkVpU!f6|zH#0kD zgZ8)#E@#@~V7?**9N&8xgZ4Nx zPx$jP?PS|*wEmwYZ_|`B!|($_~-9<^8P0gE|$i%F2H9KgtCY(MR`NwU70K zGIYcd6k!xwuU7^xfnizFbT9K8w1`3RET7}$`p}~bZFoW=$%m#b=NaFf2Q;w(A~#F~ zv~1Ef8cZkUtsh@6G>$2K3o@34aOry#E={>Hk7YD01mRcAX63=pqSUr#X%Dqvj`Wza|p~<@X&m&I9?rZ4w3?w|085R zDCN8$fW$ot-CO8I_e`i^JiD7|#ODkfhcL^D2HDvRc%1Q0JPvN-z$6Y(q8p%F870=E zJW~PB+yu>k>qo;btAo^TC&Xdmj{X}?7H*hqOv5mE5n-yq~igTHZr^bpao+w_(prdd!Ro-%12*wmb|pokSl_dCr>0-z)z7lE>a%V;f>(8;*JB zQmpbxQ<-^h_sp@~VS~05+c!&+kN5^@vXxJarY!jH=&$O}W1vYyPdW~%xPbl0gp3Ha4uS257swGZ?rXP~=8O3;0#I9|QIE_dt&Jy|QE*gCzct7 zHjQ+1T7Fa)oW-qpmEt+d_TvTFSeHPyto*4*s9oDZ2D+SWTln_McCP03+s}(pWculj z=`V8x(4zU<7ayy!55*OVjvT*`PMp4m+4TM@UAX+`=`7ysOieGK%QXdAK1=ZSY%Shy zW;iqPe2SAS@O{qh8^1}nzxpNK<@`Nn)B7krd3YmX(liVTo5s7GS=bqLuDr~#dU<{0 zN@jd-%UR|o@&0uJtAw3AI-Aa(n8%>KBbZ?22v!!GPaB(C=?+#Cd;j-0)17;d@Sb!n zO<+|BzUQ5sz*P>%=6R0}TL;?+ed5Y&Tbq0byA2=a?49kEOJ{7AGY%SjyomR!v+48oxv2-$CD00x=VS9(n=JLEAy}ZY5?AbU1;|RRm5f~59`<9Q-i|TxK-B{GQ z`If7^XY=hCsczp?ecJZ-T7l=)->bc0@?HLP(fZ+Z$#&8D>5oF+#mlT6bo#b2mGbe0 znRFD>CUa`y^O#2Y{F$Sep7>Z=S;MlocOIt)4=f*1(I@}vdK8OQ zhA+Z)xO^@i5Ko|^HaUr9#nADb#Z<=&^E2th~+@Ay>;-0#gTlaVeNkymT>mFe^K}mx7L?0DSd{Pr#LoT?rm!3XquV7kj}sQ zC+YIFpQcx@{Uw%tdlknk{}8j^a`6EL9?tLwAs$-uA@&n2XZ!W__tSk0(0g$A4{7n? zC$`kf4m#?S)0j064@;|kkJ6x+N5A(Q(?NQB#gFo;hy0JcK{osG_M+dm-Gv!ty7Fxu z)}zaZNNeYXp%}0%TjIsNY(!eW{9a@SVURb-C-UWgTaM2z{PMtSgxbC5UB|B-ceyd9eqTgP!(A6VyfO|8Ud!y<0cdS1;R$eCC<_r+ln{@|A0X*ce9-c2Idq#wxfc zHEPlon(Uf#6z<43j*pB5z{UVQ1>F2};bD&{iZ20EesbTB@8a$-w;iszi22mwqNw&} zZJm;;d)BgDOU-Cmj`dP(qKfjwCtnZ+o(n(N!Sn+Fjz=sFk>^W)?4!XpmrX2(E*Au*&Pi?|VE7x&5? zeBtafe{t|o9=yk6ggn!r;~AjG*>4~Mhq0W?)XW0DMn7W9-cF+rWBnu!(3?AkfqN&? z5e(itj2T)cr=~I6-Z7Kq0KQ3l)l52H6k9^`@pHj4t#;#Wi!D@LR;rJMQsE4E?bZmS=rHr>@m4%b54s4D+7V z7PY2a5mFthq_N_rbsZ*4v18t_fT{3_Co7bx$vm~9sYLTYavF&4ASs4ROv_tQN^J|3 zUhU-^@iN=0WBAypuOn3{8lP^|b|QTf$$S?_!St~)dhiP*#e+O?C|)2+hG-}V%fQJr zf|aUPq*2Y0or*0p45lS3WLhBQEO##2wiN`-`HX91$O@juXrhq{GgDoF?bXk&POW;gSPA+o`H}+ zSt~P-KO3OO=OcL_9-ou&-ort7;u!|$-Ub{~K?6A_W}>soi(lFC%pg}7j*tOu#|{2+ z-nGh?aZnzCus1maE?%g<0^nw$^+p zx78&p@7PC$%NUrql$Wes1%DmS$u=-hZw=2iR*+w_!F*40^3TZFC|=NRhqGe$&vnJCzw{nBU2c=ss)mkj z4{{Dir8gC$-(9d(PdPb67aWTiH956O6BqIlZMxwC;z?Y4%J$<)#zcDCY+wUIe1SzL zM2i~CN0~NnZ)Ki~IxqL8IaqoW+)-o(TN!g-_6R(~JDDByTP84r?DX7mEOYyII(hb; zbn3zn(#6;Q%$B{K!gCD{V7ES(uH{xSwEuhxY@@%l{N!t_g!J=tp z-o!@>wyZ8!LgK0d{Fq{#4l)9K{=oOPn|NnCKQoz*&SN>*`Pp;|1NF{hf|awUkJ~-{ z>${i~eQ*ylT#<`o9GlpTd{()%_M&0F+YsomVcgdmo524p7^vO`hM*RYd<*Dje1>J zT{P;5K8oLx)r=Y{w{=O6{m2f7Gya#DdtJ&UmX-QO(;)j@v40WHb@>o!@`JX!j}cx# z?&1q@44&KBe40*Smb{Cv|17=n-rwR&s~_XX+#CMjJpWP?hTTc>h7URTfO``^*6v>a zAbtE#{~`Uu-~G>N7Z14Esh^p}a>ZqsEX$w!j>VxLJ5Ev>+^Uw|t=m&MmZ5i4hNZ4B~w-YJRiG^djmWlSgZA0l7+ z!ZTSbR2!5`2y%A4ORguVP`U4#{JB25u8EVT;~Tk#^iFnVOO?SdQXNS6HIgC8ry zE2VaQNtYeTXit0{Uk-L@C-W;iE??R*xmqCCT$gP0Oa4%PI!YVJ z8SK2xRNgFR)VuiF57XHTZ=};_uB4;K&taCmV;JB!XR>zg<%i+CKXBkgZCu_u`dyPS z{LY%8+9_*a+LLk6>TG5>On=OC8|Ne+(ED<^{*}DiJ9>Q#gZl9M$d=JX-uUDpzBbs;{Gne|CL%YC8t+{kIO%0_m^8?bCK(SV zT{}Im<;0z50=9AAspUD99Y;|hrZhRErIsp^a&%3{mpMbi&al9{3}&fWR+poo z3}>Zg46{4Xqb=&XFRF!E=)0km%=_^~$xMnGRRgAc0-G@~zd_3`5~8?mg#sfC`B@l_ zlW`4`d19i!l*x#TUpqjpinMM;Y~*%eGb??{1(spRcNXkeW)R7lZZ0&Abswy%r+9?+ zp#YiXu#tmMewIgMP&8t>bfIMBT2|Fd3?RYw&`#cFL4^Ui%y90^`F9}8cyAQ)g4!~MR>-L=}F_!34@h9u}&Nanr zu(BR$jwQrUVGB;ys!n7AcA3U&g5qLmlC6?5;*>6akQG)xzk_HaD|zWRm@KFh5=RJj z_+s^Gx4X7n4p$XZ{CcdG*i;F5+CFw|BlH=6MUj~f8JP?-@;Ah?KCkc9vXtsRwj)v% z$3Z$(+clbHIUXw!{TIKf^*kl3W4`yq{PSRrb;l8i5%6Vi@o@qMQEg$MSDKhhfAzQj zF1_=kf0NE!cn>QA9KnZ#oK25?d$jS;%$c>1CqR6PKGou*uhKnCzVP|)|L=4M1NI)= z`2f1J=#O&pg~@1koIl_QXt3TE-lcA1wX$e}J+jwVt_Yr2XgZz;>ekh77WW1aW@Q=rx&p_6C)L_>DBWm?7i)q zSkiW8dJ^w!zexZ1n@`h+AAgmeVK&7{%=9-m&Au7`bVVLuXv`W%U>t#Q1irxtGzRF! zhadD|+zI;zkHkxA>SbyJFOx4X!{OR*JochTS*G^2VNm^t;Sb|SY|66p#lIaq%;ZB& zzAK!Yo5eKBbC^kQK3%}FTHgnlqKA1)) z&z5M3-uLGBQg>=oo@r40_ljwN``koZ{qlky#+Tf%{VtXZ-of8AzRFoRaVecX|7Ynu z2IZZ*^uu)O{M&f3dS;nJ*#zy$#vqR~FFXtxL3$(+GtKj02(}(% zGA=PrGE#qD_X1TOq*dRn*uVmIWV0l@^3Uw6K#X2<8M(O=j(pKxQf+=$xve8z?kO+6 zbR(ZU(Ipr8u^JI8%1F=ok(2WTVE~zsZ?|FLnjcZtHGCT#0hak4uH^;|8N)z9J_$l} zl?y-kC7xu>1|4oC=}IoL#f!AIT$PEfDpoumGa)tkjHlsr)HzB@TYK_6dBs(IQLC?! zQ?+^`3o*V-wm-gD=ASJs!sT!CU_BhC@}gOW#`~8t0$dt*4m0Y_&SDng*#&%Hg5`4Y z@|d&m&CVZ93rDdO$f--1W$&oZ$cI_>4sn(j{PQJsec*zY*TsRidX^T14BMDm7wO7V z(675D1S$6GDZz5xds~V;&-=r9q992gFVLRhHv*HrON+)x!=H1#W!LskjXfmTGaLZc z^E>N<(SvJp&Zn!n%jXz!Xps4!Z?!P{h#0~6A9dzG8;qCh&)D*hznLsgKJz$#O)c{q zGQK-Rib*&oKt;EkDTT$hVgTODk(^zu}m&ytJ%s`jxr{Q9p&uUFrPgj z+f$7T%feJFvrxyb`snyor21f9RE8D7H37c{v?L%)W|PX2Fmu2hvngh@Iv}^n^!_15 zU^lbkm;nduv(576S$0JJG!L}|(Z0yQ{}Rs(Z5Vyb2(u5nwr>b#OPLX;#L@WI$tvC7aF82CPvh&j4nF)L3>rd$C1b`*T{f<8;@h67|dg z<7Ro5I}+Hf7Hhg*H^{T%S-(PMqHIVY+BFB=b;Y&1Ghj*|0i zOIg+d&h4^)gby1ybHDmW-0yH8-s)mnS$>q37w@GtEK$3@wrKq+&Y-usiKS~XBi_b3 zW*PyV1NX-1<&J=T%)mB<=SYXqt~g-zFb3wa@5GnKhcNK7e(=E78%MqYb$F(mSexZew5iQXRyTU(0)=%^Jp%dpv$7w3jrmRs z5XkYe%PnN~=3A|cj**$6FSI=@6}gsh{86EL4Z#`|SNW#84U!#VCa~pL%l9C$*DUXO zIxmOm`u(v=_0yAFKYm3R=l=L2fHQ*6m3DS)Vh(&ho{pY;J)JrKPP%mEM_484uhVG^ z)H{qNZznM0{~H}5W{dyki}${Da%*YV1hxLA@G6Lau-qV7p40$#iq*_wp-F)L=tYlbK^zW+zVz`JVw#qi{4&j zWqX*;FuriO?~>)qrVV^8v5T2p4&&wC58k?*uDy0XUBc3{94Lp^QTS?OCw+y%bf4XL zkd~L%)B461bbQ%c^yN%WFSYox*}ia{m`IpJEuF(Mx^KUJ(FW(u&*5tu{9D4TdABf2 z-!DG+Jl%b;OwaIg6Eo+bgQ?Wzt9aUvA`@kmAJhi-f+@WH?xU@Kd0`LZORjs5A8I%* z?4)x>y9s>pa^f_WvwiKy>B>9*8b8?HO2@FAEthmNJACAJQKl__jy zI4Igd+U$x!q{&vSd*qWYnRaZt>bn=&E`8C2qjGyMU)pjii{I88wM>0sMV*M-=J(Ty z`or@4(Ohnoe1@kc-_8?Aya?JQ7x~ho+z3fxR#z4J=#>YVs6dR9#NAzPC0VP$>vu^m zQi>)m+u%{Q7X4u_WnR%k_5c+?O159dFtQ(Qe^?*!#bcQ%mX(h-7(3Huo67joZODNq zy5u}Us%?FKO}Q#lQAjHoQj75*P36pw?bvl4xg0~u%ZzYy+3LrJizFd2t(dWHu^yK8GrVhcJbhiGr;(aT^y|EgZNA@x9fPUD*ib4az@}4EQNbA z9Y1*?oj!Xt%^x|DW-v%^W@a7(_@-?c+?g2;+M7?4SVDIS1N*o%uC*dui#XVUf3_42 zJK9X8L6%IDY^BzQ=RDe%1~f2_A);(}MsrZe*q}7>KxdwMmeg{24k3}19pO2pyvz1( zg%ZyWmx3mlU^p*GWF>zo01-j%zMC!|2JyLL#y1d#ne}#1a+%kkoFcoVT!F4U{LoYv$uj)c`fh@h^6>S)#&7AzKy9PN0#9I;$4niMX6plP4qY>cvCQaDbNKRa}R`zvyP2q{M_IScRYsl&Y6~HnMLq8 zqovbOHaf54K5HDyo|lXdx}%=ya}!cN1V#}@=K+b4M(myhfD%7uAVN7mwn4sRJp*qj z=zlryG!4RIiF_}+|A%d+QxNT6v1?A{bJyYBkzQwU9rceE%s<|@?7oEA@X$Zvtiu}^ zV7H0q5ZhbpR^|Xa4$xc0pga!FdxF{WxL;j<4D_Z4J?6%QmnH&!o_M?Qb0iPM!!so= z*?br)GE87_p7n>=F742UwvC!lyGaSvV^h_2-^jgfgOt)dgKEu^+p8x&ZQD6vQdQ3V zl2m&?$f_^~du51G1ZvTjw#8D;I`Ucc62lJHdLz*E(IK5Cj&QY)$4R!`UrIy^0WA;g zid^9h-@_A!>J^ofZNBK#X!Ckej1ueBM`V9w*_Tf&GcS54Y8Jpww}ouI|ZtT3+46Y8_Zv0T*4)(9K!(n0Vdb z^9&yI3Cw~wiQgy-7>xJ!wX^BMnPWBxki0d#^L>a3TYmZB&GhM44=@{JO0!dku`*hH zXKR4t%{T(%2#h1}5=OuV=$$-yvZF_OaNhD_x!2Cvesm7b!e{-`a{?u$&{biEF;E_?vYIPoR%Gpu~e&iEw0 zruoUATu$%3bv_-(R~kEbam=qX7ME7j$DiL#|M0Gx!4Ih`GuK(krVQ>BCRHPB(5pNDm$^va4uM4(h||gRIqy!P?7l-iz!g z-l`3&O%KwEvYhIi53$!bwy}Keo9W6Mf0-^`!OVHD{s>c@{{S<-a7GvOxbd_no+Y}G z@%V56=la@{^!WZ~_35y*GAnsDxVK56z?XE&Y8w}M39hpmYKdiq4at+ z&pgoZuR+Uz<~Pq${ICA7=mNA<>J`lPdoU|Ky5}<0u3=?da@(m_w=EINxdI?jkG8BX zIkkJu#jjRmh?*{`fxd{w!u5C`uar^qH%W3S(AS(fk z_b+t>_%fL_j)>h;$k|oaLQ)eo5t*Ve8A0F`8W&T#Kbg~ zqM66T^M%Is-r7FyD{3!Rj%R#mf|j+Hn=w=_;MWx9@rMVs*dPm3-m~lvzX|f5GHid8 zaT;1TWr0nE=J^IfBl#K7AW_Q{<5?CY$YY}KNOYrC(a&V37eN+10n*iiUCU-Hld5)g z-8CB-!fS;BRH~%A>>?S9{2*ZAVqEygfqLBYALLlB1N0b2M3emdM|7X94>`({j^o08 zm2;oygZ*YJHrxrnJ`o8i`^8wekK%fnHawL~K92O5>H=_IB)#XogLSLPG)-B)LpZx( zUXh)3SSO1S_SWyobT{PQdrf3o0s=%i9y|f(fgFyXb*({FJs~kla;O)|c}*WYu9a5x zjLe=X_%^CZJKGx=e3$?5Jc-X9cn#b} zersbbJ$iUEEk5QTygPU%ftSOZ>sgl{HSi;c9h+r@eW!8yZbX3XhUX$@^6|lVc~Bnu zPa2TtZL-#ft&WLJ%g+SnV?#5x@kE!L>q*uTNE4=Oc5j6hxdoR18Mapr%)pQOC2#!d zPoXnI9+TYmQr!;W^BiO9RF6dAxOks!RB#c+Al6$nmx!DSD;aWSx}uY!lDO0*EtY$W zlw$HLA~L*CDhNh*ee+5tsKC@VD}KwQ3sXE`iCFdnd=+ky?URw+wtPQ2VT|Vr@{ijFTL=o_h%P>&u*(N>=oJ+?~zmcx}@V~?Rpr55v=ibH&0LSpGV%j~n zc2-VMqp^>+yTg9iGT#6E4g>T)Om}X6n67{N^YrN6r&xh$#-3YnB8YK%aS`C5@%}Pb zL*o;fSMVNhD!qN>bo%ML7t=AUh{jolIk4_AX4AWYmCF7BgX>n-Hf(U-JZICxy^yo! zZLDvmBeT=#-8ar-a+Wjc!dVQ|#6Nz7#B1_LpWaTNeRV(G#>{!v8Zai$e&66H0QciG zj=(qq;|RR$5%@+1=GC!F7&~(%!jDo#ni9ueIM?Zs@sdKi)?fq`?lRdvR(RJG-8g5(@$1@ zwQ;)WZ2vWR!S%Wy=1IK#Y|VuD@UgF5TyvEqA0IY~SHytbU(T`Iyu2z112XyihXl2% zM^fpfxTN@9`>Nx)0S{xNt5vOnMn0*Ku^41}SG?LZ#jJw*17elC_~ONV8<)IZ-L6VR zl#nlVAPwk2HP*3cL;)VHc5I4ZGt(s&>*9P#IVaMhY!kV=6k{?-H`<6uT{`k^P^Clo zGF+3=EcvmME#dddIF#gA>gRV;vKLW$5v$*@UZ)YeeFJnnfDfsowjP(a&OR`!VG+q zQ`2bzI>ajdFEul?8^3qOvkwsBt6`TL9OcQ(f40lQXot@8wq|)7s7G<)L$0SR9AV@= zYa!drmlTQ8sqS`(NyDTAykx~!+3cFXg@R0vRAvT1WLfgdt>gng!YOY$u4oZ5amLsM z)Ys77LdZzcKdRZr_srX%y*3|EE?;%1MgaR~doL58$>co~@}fJA3EGBA&8rh}+o?#j zL!&Yk9IC0>3zY>aCgrHuv>qjzz=KLM8nt5{x+c=1sTiRlCp3}EExCgir4_TLezPRZ z=h_QW{&)CgtjxFe!&-IdnIDid-?6tgI>@;IQ^hiHz_+X$ZnQQRVSkhfRux?OfV}+~ z%cCi_lO)f*%V*K5TQDd{N?&6?@k7~4ZO=`lc$>NmimF-Ydt$rYH18&MMu^qg3*9ZY^`sg7AwaJj zsck9JModzbJ8d=bNgqWf>PtRiM1CVL;Rdms->P?vw`ffbm7>vZY?q93(jBx1R#fSN z?&3$mEF`@AW)J#=76@2t2co7&dWL+Qq6#SjP11yXoAke}TWBApZtt)O!`rCC(xS zI48ns!Ecs$P3N_oOXqH^a>nA@>G8ua)7M}83KKW{GCjHfK0cUOMSqQxHfTkq?{zr8 z^~&*`?bZ$s6gt}{@Q(H(-q)Tzy^v00iP{rKXF-0&Ry@1)V8!0qK76!<2~r;0p*^~A zB%M3Ckj_Ep%&7%?$2*BhSMJ>_+f{K zsrcIY(4qXb)o9KQl~T-$FD;E>{FeAgMPW)l-uQ?w6G_<} zD;o`)%r3{Xx=v&q(cBhIDdQSa3oOQ<7#NswOJ*a>39?pfoe0n<$TT}53 zBw06CB?4mby5z+sq4WG;zy)Z+^q2lHPShb$k)j0&dp@&G(~MHrN-k%!ggFSPHTIIC zsLMODpU<-EmPwQE@`rkYH9kVmS@xcC8g$O82R>&^AdQ7Gn;*)Qx7idj29Kf5&}V`q z;?H)TiT*jqxt<4?VnxVxGK7mnepGd?ZQbJvQ}M{|y|)%~rj5ADX)Bj1c`J0;MH)27Q0LumPyi$%Gh zNl6?E7AEph(dZx;&4n0Srl?|sOo{4}Ytmu~*CfQsu4};c>lm)jWgT%{wlUB7{ccg6-xT=UAPQ%KqIkT&HNvE>Sr-r``o)>6B zxmXA3#n^Q5sVlm49M2u%HrjArrAb%x-hB*0MQRP7VWrYTiv%1)+!SB|*PcB53KKW{B7OYp{{=JY zeTI)HR?{noXV5>#Kt103_}91)5A$x!-)jVTd^R?A@bStH2HPD;7tbF{SMkpF)CmsM zn@dOW&X$v;e0lSJ`t2XS;v<6e>cz9^y*DmkLX`#dy?3z`?gl1YSx#Tvc#y8&dX(FOUu^l9{7^j}U->)8AIfy`yJ*52W#86kIi@Z@ zBY%{9+eSZ~wp_a$^}5>kvuF6xg+X~6TTd|nZyGOoPo-<`{cU>v-M__hwttf5kDjxy zrVio7h1ZimynxEzP`1HK4-|JXIPc!AkJ4wq`%n1c^^X{o_eok^S;SZG>-a&pW5?Sc z^z-Gfr|4_Uiy!y>`0i6Z_xs63tbX}6R$Fcuzb)S`5AtmguM3AXwX;!fOw@3o@T&Ww#W)l+TZU-I05-S`xf=-H~;m-DQlWPl*q?XHf<&b8uA0^ z(%G(b8pchu)*&6343qJgIZ*p5I#$1jsL8EWZW*j`{?Jz5m z)~Z^KS3b_hZ~7n)h$35}shlm?jy%z0UNWvL2~qp++ZeS9p6f!2RgJZYwuR%eD!Cs< zvo=FI{_KS_mzmu`-FPhc!W$1TaWD0#L@SO#O;J6slK9Oy)#y5(TlW|H(0=2KZ(9}) z1NF9X-`~ZQwo$I4jC0?m2yls8TNangxwD6lx0jb_>3~d zl5B~M5j=W^LPgT@L356dC3%jktzjB9ZYUQpBdaTKqER0DZYoP>E1}a2Vm-@a2d4eE46!X`TrcLW(K0{oavc~i z_cKFe8Dk4K%Sv7L*skS;cy;eelo3An9@iEid>~zNStvsp19|p47+b~;d;ED{#Aztl zeZr3*95>&&QJ!#=MKT_J@M9xyT*bD=DRhwBZ_{=y2EqQ*AAZ=kZA;W{Zmjq$c&m#x z;BISk%?9CZtgqlj;tFQK!yrBUZEY^&c>|WE#jGs0WH#@FLCSE#@p{|1b9w! zcDyO{YbMa2IeeJUpV)5w{8xL|p7rz==`}9bvJe3A?Yw0JM2d1w@oSj!l3^d7gA?$u z%=BzYKI_N3PiCEs*}1%=~y0!Nziq)R5!y6BQ4FQi%x z0=Q76QBc5&e2qnbv22xSa^mq3N3ue%cB+JO+N*CuU9`&GQ?7ALizW=X^Vw8(T>ew8?WbtDPF{>cq{P(Ai?L8%QY?ESMfr%=%s1=h zC_S2ji3}HYur7WyllA0V<2XjSJ>_Y%pF;!^e@~$M7Ungt(T3?k2aN{e^|7tfcAy_d zTW(zbV~qgLCVQr}WA|px;6HctRZQmaW;%W0hv}NVGkOmzB+cM-aao=>I(_||^bftZ zx7IKy?;%!J`Xqh+(SJ&xeE5H+$B*u!AGew2W+%`^#cX=xWp7`2L)*pbXZ+nhfm!hQ z`|BC{TxX8WrZXqz(`l@NcJ9ovbQXj0_|Dc>FFTA$RGy@J4;L|bZzbKjzl`^{Yv_mX z;^W_4dv8k||Jd_|yFWHCj=(qq;|RQv2)r}{^j=8qhJdQG7(0;tI*61duOEMisRLyn zqz@4*<`2~2mmKV%>fOd+SxIe8<$im$GxFPb{d5k3E?-CWb5xrLU58Qp+(l<^Ya3rd zW3b)?2G(6TdlY}iY;fKW-@25}o;sE`x3;mwEnWcPy#D;g{dE0n{M~w(Zr*upQzUO= zfZo*PVa%MThhP1V$FMqf@w;fF#^t5<(yv*w+<^y4%$cidk3I-b1DKeud$uiH14(+a*?zkTBa%$D~{{QYBES-Os| z-5;W}&Ov{eH4iU)aRdcW_R-InF9IPmj2}ezoqf?Z`r-GZ_p1oynMS={>_tAxDj)qA z)h2bM-=!m7#O{};{$AwUID63_Caby(_lFNC=!x1r3+%&G@0{-MRW_Uh;}JwI zILgZYZ zwmq|DZZ^-G5(}?Fw`pQWezcV}6fY|lzA}QXHIDv~8q1V!)4Iw!aBVBH*51o{wB^At~avgeQ5_>*q#EV7TV{Nt% zv@70ocQG)IUlLmz3zmozZ8OgleeH@(@5fC&CJXhpUdak(z)!n9eyRv z%r2z)Bd0;b^0;{LPM-DY@WR|0tAzzEkQerR0x@d^&-4Z^w=XeFobA#Gxz$Ss{ZzXn zur1o)EwoJTDUE%(?y9ZY-rKLs5R_%^9NVs6R^+wFllc$6OFEU?Sk9tePZW$VyM@_5 zO{@1p=AIDPoMKukHlTFKLrPe+2Gs_;Gb)wj&?#=d!{e zvfFb!(jA3o7*p@P*~m~bJr^<(6=^2}Xjgb*2;Ui?IstoP`X70uAHo0}nqXe-R5nCw z>yR&+WE@ZD1xudDk4a@aUb)Z`DSoi6m6Wp{PiYilh<(vO#`*L$LPZJX>(Qtbg}Qo? z?H0dLM&9EUc*aLL572`U#5hQg@_B$B%qZTn$i24)T6B-HsUSi-q2A2r%J~juD6x;h zWo0>dZW}Y-ZDQb^&4PzLX*)Yux|TEH;iWBS$m4)K4$k8&d8GSHd0gU_f23_#KSj2J zL*wjsECP1!<~GS^QTB&6Xb!% z7nLg`>M>7!Ns3qXqcaslxn{i_r$>W?YM??3L<*EqUOetIUA|RsqtIUSZKvemUU8mZ zKHjHepQN*0XLHht@?D+`!swD8)BZRkV1x9YZs9tFfqD~ot}uHd%^f*|+4O#z&cF7P zbm6rhV@Ch8w(63%z1F#IH$~hw@%)Jc_3qyMAl>-v@6(OXf0Y&=eU)~$9^nrYJMjG! z-W@qc*UA3J8UKT>HLqrzEM*6S^>*+Z{oIMUbOB4=UO0O^y><0`x_0?Y+T7-#%tN-? z*%vn-q_6PK_6FYB-ng@v?mk>eyLg7kL4MPCZ<}vZ2kqtrplk|H_y>VY%u(?uCm;4}TekL#Gf01wy2x&sD*Ta}1 zc{;sz{&>24;beOC0%m5reAZ;%|L7}xmBE2~$I&s|NnhW6oId^X4rb7MlI}cMO!v{@ zo0^)yA3J;ashxX#+7D|7bZ0NJlt1W1`=j`I&~}A4$i9CKjOFeyP;zSKL^^uvYP#~~ zUt!j~f0^F;(|>~>UDNjCh|5@cst+XiXoyci$m2kRhj%_sUw!)b>F58?|CH{3{X5K@ zh?lx^c$qteCDd%zJ-!5B^Kgtoewkp{v}IXl5>^oH%(nHTP#bHIY}9M#qh1@6vZ65` z{q9GnTsuiN>bGT7?vfQvGTe7zQBL%BUOEvg$_hKuy7H3aei&AiRi2pA>xVDCbiTbk z+dSK>pEJIvH-0Y2e^T7%+P-sB?>-*D(}PDnh;-C^j+s4DXem6*brQH$Ka*O!>M#9@ zezxtZOn`_hzf9MwWi~?AStntgq&?PaL7V()^Ds?q)NM-%GWpdx)JSz?w1_C7x z%-g{qU&t~43SKN@=Ga}AWt2Dp(8TmB7|1k>uZWMO$?41D-d>tKF*MWG*%2Dh6UFSIQ3nudB0*RJ_;|>r6n)m;PD?yDgvhA3Iwy@}Bu_k%Vq^}$fK_RNz$}Umhwf0`8=4A%Vg6}w<{Uxd+ZoP*RAxLBh&R* zL&-*dFRgOhIjawd)M0Fx+A%SrZL(vq-zh7@tZuF^ee|Tg<4d#`+bQxPdsM^}H={HD z{DSnVZyWxE20lImdyDDZ`=4fw~4 zRWhm1e;ioH0eLG}dUo;29V~BlD=j|!IxRlFotBm!;@m?=YMlP4BEWWO|FHMLcoX;_ z7atAaZ(;%i;mBuW%WbtC9JVVaVlb&}g+3d}M)};-O+%(C4XEfxOimreb27|8eB>-n zsYz?++Z#)0(@!vn^l^H+^&~yr;lNR}b^a#6t%Bb#9Ky#seme48oPe_&Y`{T&I!Ade zVI7bx9<{peL-s@t(ZQT&eano&=6&X=5~6Y6q0d+}V%FA^PQ-~&!IxSI7mjh3x@<+B zXp|A%>XjXKzBnG6-r4`Nuh(2F!+6UcQbwV z+y9up{KNmBZhrocY3cqam@)Ygo~O;(;CTC}SFX3zTs?`*&3JKOt9=`Lo`BMxUj9M7Kj z9cu(*cgGPJN1!JHJp=T5BtZB+bex{I4qf`Z8IV3o>eBC`je@lonPE1D@%Lh@OZG+J zw=vu0E zaW&n%hiQ~i-dNwp!>&VRu->qGiZ6c^-&p^cm;7G$2Z7PHISg|czm3;Z#=}M~Mf=Jt zc<4AilTMxc5tgz2Q9671Pt%!mZ)2+RtLezG^Y(*^aZ~L0+lscCIK$4y+LN^W>u#;(|<`zkH5qZwKf0R8G|$Zh0$o&=N>-;;koM^uwqvA5v|l!guH(d_M>gu z9b{t=ujOjrqU;T-BbEE{1-w^ZMzt&aZ)>k((9ixbe$*Q#%j=-Wh36MFdHlK8{eXNP z3(`dOjE;}u>iqDqH|rP_^7asP)F+n&>q*1UU)o^OgItw+pn{BeLU)~765sZOHx@P} z-;n5EXvy2LQ9Ow0t~bxWKrgk*w7teN#v;Zi=IMN+9DF9-UCfTk_fq2^9I}NSuvJ&% zu|AeEuHZ%HKFc(Gi+t#)ekRk1TNDg=YuKUO%I-D3RBv9BdYy=v@|PzUA1rdf9rqlZ z!9f)qgMt4RDx8(+Fmi`6z+eh}mC0$&K88VelUV)}Gl?BKGzt1~-V}7EFhGyGe89M|zL;*^_yq>C|0+GX|69DX!^21DHGQl>(P8pK>!nf$)?Ejw zs#s1k;>W`)OjEUENEODz|9l2L#shr?40Ph&X24+{_kLZ2S|-QE?_mVk@1CAHV$0)B zOk$}y&bWuRe;DnYFV*?Q^gNc$J$mBRG>4h@X6KLD61tj!k2f-&pKSY-_n9SeUP0dm zmM~_yajMNa0j<>NYPoEikOi4-+Nt^XzcIY+`XR{qOgCD_@QKV8Z^G; z9m$*^i&Atc^LP$<-k1_L8hLh2GAc_-O<7VMnp>Ci`+AluyUcf^Cfn{Gz;nV`s^@&A zi6=W&W?gt}GP6q8ob(iuc&#;eB)~lghSgGU6^n-K8Ucl9qX>nJ#T#I;uF`QaTH+tU zL@|Y2~YMsX$4b>(qdTX}@#Y8OFUv)S`F=$|s%c$v$Y@wRRGTI@mV7tvsuHE!g zh`y(Ee?AU)=C_w)_I|2qxcn>-Dp?DjMJQF0QPn zhmTj&BfPV{`)~;ptSqIqH9S|vzuB2Q!OHVGJJugZU>t#Q1is-2j0fnA81SwRBX2P3 zu=azIOqYHatsl;Q>BDRc<9FFrow|5!+J5lc7_r=i*F}pu-_?A-dXdh=<1lLK!Wx7# zh&QUuwp^DFZJ9x3`9vAkoqxOB<3TM4V@+cTthwpK7?d}c-gxb7I*kE(3-hz+Tt7|A ztLy2({eE2*Eq2~*8zBt@#iuI5Az2ZpzATWfb{V>d7eBpHM`GB3r>@b!s<7_A#jCbkU zzrwWSe~v+V@8RpO{`{~Zje@YMU zeVV2*h+zU>7js#^gHFThJ&fPaAL=RfJD>N9E!?iX>_(gY?DV6zaRF7ndJpnt7(e>jmIbsWhPdYI131nqq9?1)cVA2mo6K`c>{M42FVfnOl4;Z3aZ=q@gJ0?dUdcBk1{b7*eIp*h zRHKasx}xvLjzGOJ=x4kg2i3j!!0NEwg_xR-{LQx7IP$3(GYAL0=u^SB`a7py_PkC; z4cRP(bHa1IKDh7B%`s`jO`O)=0@Dp;TOJ#UYSBAIO8i`hT$HDH;1^7VXz(ioy3d8Y4lU3G1Khy%xTP|fkApx^EOzC zFYBgfFh~zG&T`NZm;UtSKe^{Xy;%%s;@=#Wbz8>6#?R94fAha$IryJr2ANNwGw(BN zSS9^PMR#LJjSGU+F$abG+8#0yI~_0TR6NfKhtj$pG4Izum$5J+hI|q4FCBOBFb}is zP9DNTw@F*(f=gaJ#zQ$S*~@`@D^XT8iS9u)R#wSLrV&EvQ3?WTPw_rg07l4k`RG{gwL&g@+Uv@Dt%WAT(OJ>@?c2IFYT}Sm z@^Z}JaE|7^XHYT%_sm!!(DXdRuYr{DI7*&&^7SzC%mz2`LIZZ7qIi&Fo|w7fDan>x z4p$1^>p9>~gYN9QNIG<&%jIcpVBIDL)Zrt%Ei5t1C1^R|jxuq8-Wmt!EkD8_y@zRS zm4ld8ZGgXQj`!dH2-G%*S$L-=(Px~(%qr8?R=AYf`Z}L6@j2dMthC_+^x}EE-w3tu zdFDqsL+vc-3vH)Ji~9^q*Y57F;sb=M>D;BCrFVYxZ`0|s@1&X8<7od}hIthOOxN&& zn{xc)AXfC5k>A=_H2xL_?`@&)zm0)>TQ=+7LkwEQz`pf$^r_LW#yR&22Ow}}ZhkP7 zuykPJheJ58b~b^{KgLc@zjrbl3DJ(Bs9nr z8=iP1Ba{r0<_7#Y5J{A7RM&JgxmF$T))8v8;Z{nyxC6ur>R^S3V&sWVn)0sEck5zR zST&^|%J)?ULo&thA{?Bk@J)Xu8~LKeeSmUu@3E5~RgIt?9N$Jxe}ly7tqXNCN`FaqFo=Q$3dYVnm^#dDXF9`=N^a78rCb9K_qV!It7TVWVYoT>KtHz-H6KySufu zjdbqf4{YZB3s*P^#Cz$)nK$h@0Dt$}$9Z--`PD{e7#QeNZrB76x39mSZeRaJy8Y$f zVbI=neE9Jg_e6f2!pR>F{vFfzApAqC!0x>DE70fQWq5|qHSt05Ec$3~UOi)j?v5|u z9Uzd_F&*NAM=R;0Pw%APeR>xZ&aj8$1#2z`TmS$-07*naRFHSxIFA8+Skm~+aXbse zb53+bc5vJ|IPZ(EA7O%(^|Z`)wp&}~6Zc&=l8m7!ja1;!B=M_?R*Z$APf4bU6r zCNG%mQ1wfD>1;(D;=YVYKDO)qP5K*s=!(%WewWVojW#F-qW$NFLiiPvM-`u zqhfAj;2mep92osHNAc9EWYAc0QOE=MMs}6fj{{D&Gi0nZ`zM{ zE_*va$FE9X&ZKrv@7isAFYVwUYUjRqr+Q;;9rp>$T6p%gboK3jiREnn244lgjU~&@ zr|G$4#hksg%uh*plzeLOSMb&QDrU+1I^F&HqxAVl|3|v{+23Qp!95(sUA&ORfCl>l znlQchqG2(iRy$Utb)|kW>__jyh&1^Y?e7O`KRVHmgUIj22h}^;Weh}*@rW|}$q(~O zHY0Yl-^HhF^f&6Z^AU@@et6{d!|X?=&M1l9DRtBOb*;oFo_b$8o6LSVxBStDTjqmE zdl+diud_D-$i;XJs7rY!%fqng_jSxc`mvqcy*1oYn-Kf#pVxy0_dzcpn(iwK5R((x zXIfP(8g+n5qz4zMLUT1Wja9m2`ta3`Xg4O=RlAFP@>Fi;BSycx)iGD5wn|$H_}Uh)TSyVg<+wI6=}>1i+jlyOw$c9Q?_M8I8~n%PZFi7$C)q3@M7yJ zUufZF8J3#DyFh$I;IraMkNe5V$#m=}W(J!(gO_dRF$>svEZcc1P0w-=-ZA^*K)eYI zva{v4IM{9iGl)&hFCf9Y5M z`G3UHZr4FZ9|kjr)R?%QA3N8M^{#lTxsHKzbaAm`YBn8-zQmkKXHz(~zmBrKJ5NF^ali}r{L8JmG-{`q7THYNQ-*)|UE+KWJVlQ7Ookig z>)s?onS3s^UPujj;t>M+44x(8=d5Uj;PiZky9*#uv#21aJUNC69HRoqW!;wNUD_nU zw^Ftn`2o}oW*5s&Mxi8kUB5`w3}wNL{FtX8%S<-TNZ3wlZl#zp3sXAc7X~w6N}hE! zSt&$8@EZD%VVTLqv#w~`yM)Uy7!A4#*4})-T0T_mVznn@ z_YJ8>E)R&&>0cILpP={(;hc92$>cq;)IcJx9S2vm^DFa;mSPZfLO$vQaw99V-B7gc z*0|MKBpp(8-dP24oua(CO+92xFN3qB-^wOr(A>DJ$(yV(Ouz7K$xDb5gF7b6D98+< zbFw*?DO<>uV^fF*qCRS+(oZk3u1|L{lNX-haHcyhHOs+8J3CvJ*KE4vudgoUfp5^FF?V zvvVsQKlxhvv;XFQNUy&BFVe|VSFK(9HHgR=D_=a==C^P@ZDX*(4hGrnU=}_OJlMs6 zR-0)L=Nt#Ma)xRSe%-)Qem;0_1^I{gdyEfyFwk}JleGHeGYsZSX%oL^HsB}g!^m=o z0=i82gV%^3KPYwd6S;_Bq?M<3WTJfd4L4M5F`Ks*j-?;n4O*79Y534X4+O& zm-nu_^?m<2U*?~A^WM5uUDe$)bF;f{p7ZMtW0U5S&X<8FD$|ru$?wW#sUjyOa`1&IW>*;d`m_9WI`dsDsZ+ zaUJKnvLB=^b^YR$HbWzF|NfW1cf&6^t#iu*&3M4tfO!9HA)v;g2kqg=$qV5Ow!1z5 z{I@aobUyT>KcE8(^!ScsTTxG4E*lWk4eP52&arSC!5}V$D{uenaP9p+4(nL-!AX1_ z-2*v!E7i0IY`hhnqU-CMSa^ImeCC;x;n{P?u#N2)+9FX5zYX5Kd?&nrWis5FSg^Ky z2ij=-{Nc6Y^+|eWESHA4mR4 zvgL7<|8eSk9KP56S?}K5tv?=R8LD(~rS&FOMsW4TFb26#9~(yWyFqMII}rN&x^2?k zZA`|SLBzbd`IRt@#@Z4(ZTGQ=`EozMb2cBC(iL)Q4%5W+0? zhe3o{W|W(SrL}N(W;skwFW_$uD>dd~#2VWo1M+t3!~>>R`KV^Uu6x`WaGus*lX~>c(0G|jdmALf>+pE-=nV6-BUOi}EASMh$}FN2 zQ95OvqM(sJ3dwYWPMxS3t;3Ut|2!@;!UW+b=_8#=*OmtSUI3dv%&&9$= zf{e$Z9jif;tBk|b?_`e?X-T_Isn#pwm@oJ&ndtRZ@zjOoMJ&k8Z1y)n9U;`6(VKT; z-rHn&I!sP-vT{(KD%^bLQO7_rcroNUi@06qc$24#Ntxd68d)^4eBlkm$zzCAvBg!I zj0}q{6*?avQWqk@c8y}P4<`e~?PWVU5wQ#JIYzWQj0r_;XovJ*K>*<$9oU8zlkqsw zj+2Kvuw|{C`CiN>9K`{zI1wqIkkr=JiFYR>@8yGGW&3SaF>Jhb{k3rA{XYnAzxEs0 z`fVn(BdS=uIJyS4g{<;RPQ_H@Ez;G$N=E1W87ZY-(&GKj!PE^bz!<=m7Q=(R;ppf9 z&OKqMzbEwcbfWWaHoW@ARr5br&r%jUUGLcdeT>%Y>!`9{TKP}D2k`jVs`;+&K`g+D z5%k#B>|~mg?{MpJi#x8W@9OEtWLZYx8^X5*w}$25HA@%f7FOnyILyJ^75!!4!x z{1IoBggqST{!eLox%kYK{GRCSu_(NJiHMvv_p!aay;P(LOK__xElV2xF`3dhGiA68 z#vGuA;F$@_B)Wu8Yz0#ZONZJ5QH=PBjJ1S^h$lZMEKhm0kfCW(22Kg%NNgFQW3NDY zvgXouaVkhD95_Q~iW0Cuo*@%78K2QOya^ND!82MWgcs1$3FlxgDUNtf2xCcnj#hVx z(c*#|h2bNSBaJ>FA%QZ%7;CZ#8c%kckp(=GNA`kl2^4WbLZl|h+EKbO3pAyS?2|%= zt}x%QQFR4_<1HpZ>&c}vl_fqW0G92wJc$zn@}Ab9fN>%|(^8R5&Z7~A0~Y7vL6{LZ zaFk`wdy+|HxxA7jy(Y!UCqB0YQ-t{*M#l;x-!al1Ckol5AWRG*JVwjIKq@BQZ>%q4 zOWJv(;r6rq-sje{oUlik_wjybp0c*EO()OfVHCaA@#*#epAGii+4~tU{Mbh3GZQE1 z9T^$G)@j4x>?usj$3jlrme>{zgSd+;VP<|A{d)6Z9fSE@n4BNCc8&gw_q+S%NH=(@ z1wj;RsO-K+g5N0dDArzr@tpHB7oQmZ5?t?z`%{aW;Uj`t}^m44y`^K?z8HIZBb>j@Ly3np!Xt9-r;#e2mwG94*% zb&tbxMI$`6${&$ur(Y+fC_YzN79kcD zs*K-@)VBeZ*L>4F7K>U+KdBzrMxWOMytBG`(O=Y$MX*EX!?|a^fw8BT z!!UMl>ggM?z5%ozVuh7`TcCEqyNe4&7_E7Ibw1p^^H#X?(T~H8%YTmDnyzA3t=Z6x z{=0Te5NjPfdw|dS2HIGMF+q+K={R1?zcGxl4q(i-8(Y=ho`Rn-#yUH{8s?T(Z0p+9 z)lK8E33{&nWxwDyS3cu9jUwXSIOH5VGJsvv`Vm~E*V=Jw>)T-pvhL0-gei>MPE5_9 zy@UQBEWBiZ7W{7=Ef2Ij(DJ|&>wyEEpr^*zZW?0b5xq}8i5~R!sjRe9_IsN>t%pC3 z^pj)@%l|0t;kEJC>LC0t`MM8$`F#2ybeCny7Q$tZX!dH$UiqYVw`aDS@AHQ%UU1?f zh|Z2fHi_=+>2Zsi$3fe5Ox9XhS`AY(OW2ChR3matZ_bL+k~OhPzw;fv^8{6={CGrt=8hfahJ zOc1b68eC7>XYFOzYJR}ZKy(5w%-+N*rVkKq{HORpdNo|V^yj!n_tDVC;OJp&QdzKylCf;7WJ8Xi0OwHaB`+swI2uV4pdPcvnZ4LkYcAj`O)HpouNRNkNMp7r-D zD-j%3JY`xhhiUn(inmOwe3?gi;w5Kiz5Gy(c9Sm^0WhQq;BStO&nv;{nYCnv4)9EQZ*y@giY}{E*bCY16k`VG_aIYj0WH`q|GH(O(LTB_wQ>G$i z?Li*e;jfdWWp83B*b0TF%c&^fYtHXvar|Lmuy-^?#v8;ti`&;?AQ3#YF;4#BM4z~I#$iOF z;3OiWqd_vNqMUkOotp|tUdDTfM#I7EOYi(%_~7l|4%a{Y9tPi5@curJD6{eyC212clJqOvdUj0!<`!fpjt*fF#t7PDW3~d8QIVHc z*27J-#V%c&w5TYy8aiE*dx&DFt-h=cLTOZ! zUctZ-8E&+Nr1oX@END1j5oo;d!9sCz!O%cDe-1#B;-^NlYZfx$jrZl0WZlaY!qV# z87}idM(Ev%>y-!!sw&Ki5;G&IoE8|r6K$}Fi?oGjBw-_8%w&2c#H&d30ws+G&B~Q% zOs8acSW4HR2+x2>m@j$e;2;r9i&-#EFd`afbb849BvX`JBS=4SLQ=dNSpVclOw0E< zyk1JY@`TPw4%ql@ZWHVn;R_Rd6pe)MTTaX)WsJ_XiEU;VFvz)xHV7y1U~(NN>tXQo ze%hLzJ0Nfy+EomMaw6Xbw(VS9x(ixF`~pquXnDYUz@7v?KfK5B$9HQR-m{!6uZYv} z{TrW|UM%Jq;vnwWU>F|g!J^w9Tgbb*wu!#N#qihvg3vfvq*=C}!Zv{_G1v_H?0UkBwVFPRaS(`;i>Tn85SoIL*v;h7izW_bFAzh;XF z7~!{EH<=dvs0z^8Wac8H3ed5o|0S8;; zV$0mMl^Og^W5LffqVCS`}$tm$fb)<^A+J*`NRUl2!BhCw~N_&1PO*H=AP@1H=S0YR~35m@7 zjrK>p)QYO^IcF@8@ET^Cykkm&eb$r78VW6zMQS{?q+}Y6NPL7UE$+r=FB_EQG9piT z1#O;1<}_AP7b{x}@++y!hE)=MEP=ibsA`W0UxoWv7Pw{fWAy;rrT6jv+1x-M*ybS{ ze>#2sOV}>{CG6hxOc)wFgYWkN>uZZIOg!*<=26$#lL#4`n5efncQagi^LN9wOFs;^ zK6(r9dc4=L6+G|R7`Z>T*B@(vpAhA2lUTa~35#Nl?}QGt(S`^6!r9YD!o{b?tzS=l zd9zq7@zKrcaP!U_#%&jIP1>-ak`eFfFFa;Zsbh7O{e2HGIj=XIM6i}KCq^(yuMZOe z`_S&|!Q{Nf@X@VlY?FIEEUs*Zt^0@L_Y3%~@0Hi@A?x-h#O*CJS{`V5;1lG5nhAPp z7^$K3sLCpR($BMCkHQA&qfXiIDD*bcA)7=ayB?)qq?_j_!8aW0?)F zzw(Wj!o{=4!w4FDTzN4$Js+mg*>+oi?C`<~LV% z96#D`oqO!y<4Rn1_|di>{N!?Y`w}LP&CFpdI}Es^gRnbomqA_o;e(xgRL0l4Hud0T z{)f-wn&BF4Bf{Y(LQS)K{n(k8!Yg0-dsw;jS$q^djS%DG80_SLChOX$F}pTMu+jVg z9flj|)StZl4kqROFnsjkpN09!E9mT>LC1ewRl{p!0~}2ny^{}!=h4hPyBk`W=k+&A zH*16BR-L{4Dm-C3m0>5mys`o(>!z^i;TRy1ngwnWVBtZjzun7$>GS~aOY5lRM7lIF)*@G?L1nw| zjpmR|#OuVulHPtyxJAT0i@b+PdbS-dBkN%TpKX!b-HQpiefG!6e%*}DhwXJO(jMEb zxZl&`5bsB;gW`iyxIf*!t*b+OG8*6$iOIxN86%@?6NxF9DWpTD$S%nvEntQ=+6XW*B=sCbB0)(L593ot4n^KmnhZb+{vwaJ!Fa$` zV2ow3H%)>Iok~Y3cF2u|g(nFo7wO(HUa3j~b5o=tFGS(vzZbSy- zH>xoh$evxoO$g-zhr{?Sg`dm-;vz3o(VF7(EI7*?)v#{JMbOV?E95X;&||71J(6}v zhUpcMXETGe81TfqbbWOR6LOYqGTkQHL7Y^_MF1NcjBba>cceoEFK$)4wnkb++2S7t zIynh1UVm6aoA757f$*Nr(wHP<_5#;BS{{%GcrUZxhm-L-(0*kkYUZuK4wG2z%zHgO zExd6VwZ)#z(1vI&+;yW11GxQIcspQ=Z+WkBQSVzH+{Vrr3t@R}9g*KUZ9#84wnpQF z#plc)lrNaSLj^0-GG8ktQbk!+oH?HTVQk9=Y}xnX7yn`S+Asd+XoHN|q&+?&vY|>! zUHOTa&sm&DMoLPeK%`7)%12Y;^U3XT5mJv4hB%pz{c+r;n)!P;vwq-ne-9Be|ML6) zW4Q9cA0YZ4CqXdEFbdgW2#;hPUM4x z&nIMx7sUfEyAF;g%8Z!3*y!({c%9-sm8Ub;=Ek@bP08#^@SHbdR4zK3*TfEA1_H`L z2op`h@gz`8i7AJYEkDY5<;hKd7W3ruFhfIhk=E%VyUS*JbFoD{;B~JNyOXekq(f|K)Jv%;ykcZw%l59M|LbE1qWDle$-F7xUa6ere$jBIsR0 zu!uLqTd)1UVfyx4SeT1#-myrpt)0>K(cb@*AN18We7%iv+l_U!3D8zJbZ9&L@;6=% zue@*;6X^zQ3);!M3*qkUVz@Q66mH&K!s{{i*WE|d!>rB5YHx>w*Ohfqve5?2U;F@ zQan&ILGMX1tYycO=Yeu#`^mHL@mN%D%vD*T#vW;P9JD{HbV_%l^G|=Hk^8Ux)8VRq(bn4h^E7G^%gguP|! z$ZtpIY3#&kWKAt@ug^nSlH?HXpXV}NIsYiHO*6W7qS*%JJAsD z)6DY_FVl_cT`#*z4*44$UQZ*s-Qr}|UgUdTrKw-C2vgeIA zIz{X00*M{gQJSu^VA4}Q)8@>^SPc|ON-$?~s#{s(L}{MT6+|%##>&!U zxO3~L;io_N-@=FQ{vNi|?YD_YHW5C_t|!xEBnOGcUr(7L6IB#v7LwIawy`)VkDYOB zt94_=Y!`YvyYPM=?C(ZfuNMQTeQ3{Z*mf5)*sg-GlSc5L0oJSPiqaxMp;IbncX z=eA=Tm5vTD6!9PeQxNl%2krLAsCl|$WoJv{@`lCvX~XZU1AT?HTs;$IY#)8z6ZEZ0wZ9_ego zrUf^o<%=?NxKi;<0a+?TnKztBy1~kjQN)CeyA$VW6O999I+8o{x`WpQ^hH{q175l_ za1DpEiZ~r&@URlDktkV4F%n?~MJCH9c_ydmA=UqpLbgbMUgjvOC@@k>0_0L}a-Jx? z5m9rF$+c9aNayWwe3p#)m}FZs$*^nXXbsHk16U-Egc*qv9v@-WGGWGpLsn%O8S^^KVgDkz z>S`S=57hSnpTT(F^PXm8=@>~RPQ>FRJVsV-!~0f|+5Ek)`!th}TOQ-L*4FIoJREw^ z&gAxV{e4}5J8vAt!dtc@m$9ANEc)}f3&$D;mf455!Tvlh7y&=~BK0WqDe_Ms^E^B( zLhMOQxGHSK)8=|dtP5d32D;Y}jq;fne=U6JoBt$?pLhx197kbOoD@*DiSnXd`FDGj z4$9B;%M2BW41$Rn6$|T|_znjl)6#@AWig`O?A=S@2Y>ue!{zsWAANzt*0zy|C^r9Y z_wZsm8bX=UQN-_eE)`Z~_I_we6|4lW*ppkpN#L%96*TG^c|Ul+P-i0z#AS9R$&w}6 zB#74)wEkuJ7cx@<)hg0B%Woa3-6J5s8FkxlDGub*?9{ALI0MEg_?RA^L#i)6GgQu|7?FCHG zdjUZtJ{OLjd>MUXTnx%xnaW3eE;#|6Tiz}$OoU5s{UNr!eLbwqUk|Ix69`DLV6qQ! zhm1q*WdWb~Jhz->pPu^idb>KY(4hl;d0pts>k8vXu%$5ai%ZK`Y`Yq!(Vw@5#Sk1f zT|j?c2ijL`W3{0_a4(0A*}?|e(cjY%`g=P=55|ti(2sZ&ll4xFj{tWcW5m!sJC8A2 zL`6jOy>$c!AzUU_!HH>0}|%|Xkrr(5Nfe$p{5xaSj170%P`XPUaCcR%HNS(5K{ zJ<2kATlSJJUoT&ty~wNL!DX;}g6B2u52th`Wi^78{7}67FqalHNdg z=KSgKrI*ejobo6p6W&8;=9zE}A)2`b?j%Ag&mxLnM{)H}?8s)x)tPV|jrc`Vb@nod z_kP){%ql%Q!RnRK^T}3kqsM!DJuPXPk?&7-83W*rNq}!LVpx5((s~}%~ zPw*&|_hGb!gVbw?B6IxIE8+Ri{(5*8+fE%l{u!H8<4rVbmL=(8p5EgjI$ie&bI$_l zBB{~aPbyaqh$)ekiiAZ58{zK6+u`k>{)=$sy*~_h@4RhWb23sGZBlvc+9iVuJ}N6J z=T$E7dOlst9Hvs|Iyq>{&a^HpWEena+W43v6%S(p#4tL{HZ4N&yO(cZmF+y*b(k21 z2~b^V571uoDP+ihG(~=>)gS4yiF&wx+nhYWs2@mM+XQ8hKced;tz76UV-@|>+i3u2#w=-9aW3s~G zL-`hR@sTXn(Xv+03M-%ZxZ49HbnzSssGJY2!y)fL_8cZGpLacbjv=cX?3L|hA#LAt9D^4bnUkjueN;M>qow5vKuhWDI36{+Rhid;}*(Wpt*1#Dj#% zHdJ1_5f=R}5s^m{5_!z#0us6mC`yV%f+1(*_;H{lsJRpQ#oJJIUe-unkBMrtE0S!; zeUkug;sWH-eBLSS1=;VHY>GD?8>UROR#7@SZ&mys^V=Q<PZ-01P?qgyeb|>H#w5!W=7<{~oiFqq%-yoXU!Za4h-85J} z)sGrlN6Q2I<$-h;+bzy#lF>}q#^Xf1!x%8d*0ub@z&97%5|`cWxapOq6rZMb%*40a9HQzigz35EFuTNwrCba` z+bGo@JSLbb9A*7PW-v1Qhcs~(Y)tR0{M$BX;bhl!Owc=i<_qEJm;NTcIesmS9eoZy zLI|JoK~-#E<51lq5%2`+z8w+xwzvk<=;TD2%^3tc;J?yiCY@v%L+1d+|AY{@g|hkxI0YH~v;V zE0p7(7jHzSH{J85GXcxKlRep41`r@YmUQ-t#^E#mM9aMQg~u1uUWf!2DfW`n^RMTA zFEUy@Ef0KBJitY(4-oZrZH1AZLm0utv5PPMd*Rtv{%$yeZErbN)rQ4)zAm(<5p{^Q zP4NBAhQP|=6#CQN4Oid$Zn*K`pCFpv8(=|D4;!QE#HR+IXa7upQaqTKgMQI>1f%Am z<%VD??co?A*PR?6vN7A!$H&5HjFbNOt!v@OZ(`i`Ed<-Rhjtt$LiS=STTY0xanfAu zL#OV+7EZ?FIP%uUYUn|q;d2+C3ZHx7sqmSnPuiI6G@|!ixp6n#LEj>Syi8)!Aex)# zkBr}lywI;Ud7ws|BrvZAolgt@D9=0T-wD6pwl>n& z2)EyMJPGn1cX=O$etl#&^7o^#qmlmI;8dP^HVZDg-N@c6EkhJD7U`#ZvkBZFx8ox--e{b*>mA!IY7`7MXJ`IT_(&MflFSb@HQl_T8#Dh`U* z)3Y1+PBxci?L@y5zAP`#b5+tdRu661pyDtF92qv8;lqz(RZ{=ZF$_8mSSKRyC*Q#m zzg-StVjnt6(TJX#z7}p?`*C>Z_1_6|=-^vhn!w-*wgqITtgT+=78%Wb@UT2OsCTzV z760CS)L477a+{@Vtc~B6(Kp^NRj?{KJK4|-o^rizugCNIv}gq5nMXZ2#EF*aosN36 z(o@Ax-Hq^`XE(H-Z>RK5a=m}**{a9GJu*+0-C z$&8N(@n%HD>lo{gh|GsxayBoYv^oSY!;}|ZlEs&wWp^$!!x3cYNW7-MI zr-<@B?F5l0v(dq2gLo+~qb=z)8jnxRvPZJ2&eUB+R}ao&$=-O51&zN|8cHw_I`iw^ zf5z27sM#z*v?-g^S<+X|f=BlI`(Mur2f#V`jT35yhK`4qzx&^4G6GM&vM4=LR^0={h$m9%UUBkh6nt+TXISXK2+6(Oomk*7jP1tyYzvBE ztaNS1yPjLzet2UR3muja)npX|sffUXHip`GZ7jd}KqZz|Uvj6Ls_|zgkQ5iu{_%`I zo3y9xH85}+BkgfwL&DpPpQ0{`X&o&OJd6iyJ6uH7vEEB~oa|Y$ACmChqys5sB9jm}X9x#VGMAlK(SY%P(ib&RN~Ddyk-uPr zAqYu!AB3@Ok$0+#>29(I7wEfD@x4ZiP4?PGKosCj7okI&gVrW zTa?F{Vn}LsIk=L%&MJF78q!KmN)kl!`UkL(9ZR8mK^(-9Q~8`KdyhH1y+>ptdBkS- zs4^7Cw9iY2YbOcs$VkN_v(j+*F`e;wi55}?%48V{NEp*iA)+*qH9tg)w17-HGpUY_ z5+gkRW)sKLL_3>o$BA|jp5ZJFPM-M>XGW@HR6A}%%gJ_Z2du9y+N3*9yxUw~wP<&= zOR_itXdB<+jC4mm{4U?d#63pTV`RM6(el84dVtTDwdt(yE>65-WIT&Rn_zrriH5q3 zCp{s&*LOOsK1oEP$Kzp~*3Ls4uM2&1jN06VXt!ri4Tn?X=x^-q3Tud}GLL9_i%V-^ z2K{+c=-XRElodvO<{}a6Z-i{hl^*G!O2LwPql;-h`OL*9UXbkFMe?i8=To$cbgUo5@5?F~86|XcBJk|= zhvB0S|0ulu`u`c0<|i@ncNN`un6Oa#=7{BT#!8?sK!SN+R{6LLQ(k=a?9No0PF;l@ zT1b_ATfmXO9=nQI4__2yVse(qr1Fr|m#M^BleKd5B?N(zA$}28`KC}78AFCt2Fj2; z$swu?6R!3gsq4XuDvPv+1BlRgASH&3RDhgB)Y;jS*4mbA0eXNt6_2a7ADd!htA$!O!TXmJny7@ zoRh3AwyKbM zyRhBHWW5PY)>}a|Ww!5n5Ex{?ZQHLhw&b)t(DJ}1+XJl$dV9FO^%||J$g3KZJ&0{GE}lJ( z)gq%|Y=jdDF~|mkwlN5O1rhb`++7HZ45f@P&a2oqgMB4!4CBnD8hbowFOS{SGdq=W zCwz^m93;TNHMap~WV`O3(QxF%)0mX^aya|!H^Y(R&muy|3HTxoGQwC|jDPALiUZ@@ z==@(^x{EDr5hVwc^R9pR!*K0`?}HQ_k=!B}tC1A-pvHzB0DJM17wP$Z+SBYux>?^h zOV^A|RkEsJWm!AX>?giTmWTOt*-qwn!h4>2w1n%>^Cp<;QQ;e%cSGAqP8qk8T-i)M z*#60Zow9ca)*EN9oUi?jw(xhM&RI)HcDV!!Xvk|m?N!@ zyw-^@<%!Sqi`dRNZv;zA8D3yQCYM}E383lo&J>|n;nQA49k{;+Bv+nkf1R>)$!p$M zVTNrxN2G zcpnqq4jtM;6uo~KE`H|M!r2Qyk2c8wCc|=Wxea1`7C202dSex z7L)fK+j<=sfbBsi8MnF^8te(DvC4L6pa)xyb)vKHp0$%W82Z80yI~gXrnbWmLMPe* z+%|(Uy{;rZX_b}HebODvLdqOZ2IYj0k@Xl|2%MaxmnQ7dX5?VMk2(~~re`S`&^om| za9|$bM7-XnBdoe?Q}Uw4_oS5-H_fb+Af!LobYE`?}C=+!x(rV zk4*$*8y$yD1K@*!`ay%~Q52pMg8>NuC;Zu@KZ_&~pKTM6{pcx;Pbk58%4Z7DjN1}= zKZ1l*G)vSDmGOus2gGMo&$)kAt(-}2Ce?&T4N+H2o4hOunX0qt3fd&TOc6N}15PoO z@>waE2ZMoy(^-Njr{WM=c8HCngwZ(}jpH|1LKt1bd45M;lO!u;i(Y3hH!EK}M-SqCWH$aX$@stuU(JYH^$v=Vp8IDHIJU+{N?eR*i7 zb|9L{J?xONfJu6Hurmhx@s_b2+Zv)f^OaEK~M7B7}8I9SJc`y#Z_uXMxDCKO^* zWvDW!Y&j(c!Z~4KV<}v}@@I(F{X5~(+y4yw*zw|U4+PZqcUG91jFQxt&Ak)zvVpmX zqi5nFyL1)2??_#yPi#0*g0MNXkRn=&vV;THV;4_7d_hUHB4^2&Snj+8Dq)Galr^{Bt1C^KKH5vo*b)Mmb5&uKOTtfamK zpSlrNH)es`!1s1q2v=DN?N2!#l@u18k$E}sjtf}(db_MYuLu2kXHSiWlgEawKab4@ zE^wH}qK3;K&4f4Kzk`NSKzr(tMa@%t;!)Ynem(AjwvBOHwnQ$T8@H%_<41-q;vcuy z0Jem6el6Zz;MY~HQR{VwBf^?Q2yMLzQIR=Vf`U9-pOFI}g--pkGK+e9}E%5HD2 zhXG7%d+E8e;kgSZ!`YKZ5Sn<~8h~iwh9AHEQF#B#9fTsDwFnR#+*Jo((Q^lz({+&+ zHu^Y0ZG8#G;Tg)>L7ievyQOSTXJW&@4ANIjW? zlUvV&V9HB!J-G0@Iqy~$>C5)i)3+bAvX45N)mzUV!Ry&2UG?+`|1h>lMwJe4m*`}( z&YrJ|M))e6WR&gqwB+}B@$5~mxOtAxWj*<#jC$15nRhTYvPrzLlNFk9kMfYs!dIS? zD%oWnpzuN7eH#g6k!0kS2~yN6TXHOGr^r=>Gdk$yLHk8(fb>JG?2wEqJ3LQQY10dd zWxWzErOGFv@-wqlzLt$9HcK*7OPj*|`ouD`#b}w8-hv7Te zX6m)S{0&5>3!w*-b#(Z0`Lyuy93AF;R8EV&$`-;HafFla*0IX=$Z$Wl6FZ8|ydzj) z+h^NQu#Aj`{NCk>aO2KA7GkWxPDC6=L}EoLkq({RWfZ8+IUm%j;>oXMnVk~~*$K(X zdOBmG1S1RKFK&yA(&=KB*<1OR2lm+m@!7{6YyZ=o>*@+$FHv=8caKf#>mNLZ`e+c} z9euWaZYQFCb|T_lckh6;(>alkvh^%*vIHmaF`5xqx!dFkOzzd(ZsGD6fBNOZSco4620=ZKWqZ}_omUh%k z7U0O(Adjapf&x{J&76?%A`=hsf{9B!9!>)@BNL*47jwm#bmWy|JRiwTpOhk>T^Q;F zaYoNP@#&8Vu_YRjQe7q|vl}ow;+rO8r9NW-(j^mdRG-;I4Usdu<0VM4lM%&tZQ_w0 zqa{qlG8a+-b{KnKa1tFO(q)tD5Uq~wmV5$Ue6Pgqw^%;Nh0WZC-FU#iwmOe(xfa7Z zqIPgQ+6_d<<77KV#9LjSH;eM0=%ZA41IKrj;K}Zsp_OWR;6OZJ?-o21Heno5TlrpT z(?mR;G4RPrc|7z?rn@w&9*En`9VGd#88deX)Z)`HM&I1gpx1k?zEWB{x zn6*>e(Rk*f+XY0_yFIml0p|%DfIfr;B;B2yWEgcsdGS~Ja*Y4Z@HIqE;ra6t7il-v zR`AVnFTC{Se;@sBe=9uwneTuNQM==3Q1VmW?v*w1oa<8C)z@rNXs$O z;l|oLCMw(r@4oq4SZMo4n6U5zT-!eU<=-46mId@ro~{t;Xo;=5{+>U-<~en)fv~DW zQYEQG<5zTv)RT~{9-MTB2Pg#2*^977cqg#NHx5@$)~Bsf zR_1=Rc|Om2_RE^}tk=Vr>Febi=@8F;>5{zGx#fYqdcY>?p{>2Oz7~eY&WAJ4|3Wx@ z{wv|w>6gOD*xAtDh4&b~i@A6+tJkbDVts_CAK%4{G(A1}LAZPCT}0G-0}*6@J1j3; z4+97?a_BI>mE%IVz1sLB@NvwOQS&(F$(^%ae(p53j6D&aIe!d&VzIA}3;jR1Hib6Q zt#AwddD~dv(9?@WfoS5HuO4GTSg&zn;qn@yh2O_@Lp$$VpFbB~e)_ltSmETuwT%tz zg0>RgxHJ*|;*IMVYv#7MheCfh78|HKe~e{lscw0o<$+I!2X;L{Z!d0Xrm^-U$!*q_ z-IR+j2GT$7!`tw%jqpcZmfiT<)B3b*-s|vkrxu6LVo|dI6z_v1RPihDFd@-(c8bghfV&9?S5! z54O+QpyS{(JIC0mcVxIX93305=zrrlGlVkjUBHTr>A7V@)SE{K-%PkWvtW@d+Q3uQ zxmQ)TgT8K6I?FI#J8rWP9&91{#)EMD>^H;7b6*cnUHnQI9(xK~+8#%Q4@3~+$BcRm zlMsHy@Z*L94)chZH$Cxwn7s9Nn7a8^n7sa0SY4d~ZVeGZ*bpvW-zTB$-WT3J`lQ~m z8@tQ2($PqMS#McJ8PEJ~C)kIKuY#(ES@b?i%UStzLROYt3OjkAFJYfoX>x>l`JLrq8+DD3$ig zkHit`#Y<(;fLvWJ55AV2Knq^W@1;{tPQjG;i1E0X-^FiPjTcvi^PF0-Re1?hnTxPU9W~2Y zqV&FrY004suCitHV@4w3Hnzu(4&mG%hR`Nr ztH#JAQDU@$`g0FNYjT?+4Syq@ycEk5Z5DNzCg+VQqr>&Ee|DkCkx$mM$rZ$-jpHZs zSw}DFTSvzst=8@mVI9&ijUu{cQpr&M|64j93@padMrd@easfEDHzm$Zu@4Lw^7O zKmbWZK~(=3SqcMxScJ$ZciZ@O-rQKT^L-3(ahojSIN^@Cd!Xe_ZImD@Yee7@IJ68d3^5hXa6i+ zv|O#V{D~~$8|C6tAAk8IMOl3EqqMwMNL8Q{#%OwY)T~appm_rlGqe}Tw(zY}IA z--1sTed=uBFRr#VF6M)VKX^7 zby=0Mh|ZDuWEox3lHDi1M4mrSH!ctPYyFhCWrUP7-&kC(w&WMDL*jDc2+2iPD~@>K za_${dW_PXD?7HNN8;bJxRy{&rPSVs~A33p}gnID3^3{_U;Z^-0jX8G49)!%DwYDf) z9@uXW;OWKt;2svjwBwt%w;x-zq7U!r$(OJz)aS9~?L~{I$8B%%QnPm+El>@`Z(M#W z*p|0T(_wM;7Pfi+0k*yU^Duq$&+&dFB?61IGs=B+pWuF%Xg}rAH;gjK^C+SVpG05Y zsbeEy1nr`Jw2`==_6`;<+_}3LX0X6^VR&HE$ahWUQ`lhdK03PGHRT&g5*kHE|dH73>>{eHc$ld7cEHwrps5 zpyh%6_rOC<&|^i%2HH-Iw4LyKE!IKFZPuP<^ovHmYF73^(9xFiLA$CB*}YpF=^OD@ z$@4JTQ)TOJ@#$aekqG$yjeNqU`Kh}eKZB5n<<<6+A1Fuc-VKJzhRw3YSs4co45 z1P#73$4A3)OgtPL=?}vL7~sO7^CTwbUA-}hzp1c<=yM#TzK<0qZ1mHv7#XlEJ>FM@ z(tnTs$gdxx$sEF8Z{J`TI&vaB_qo4|kl^16BS+356nLKv?!m~SiF`-k9|sgR)|bQL z+|6+Nqo0IZSAK*^dI$$T{ZUw+n+%=U;+TVja72+Gxre!CAJh>nrm}mp@n8uoIuhi+?Bkz0P`RFPAjsyyuf%4^x`9c)V=$Fwy$6 z=m_()_28bjUb<{cJ-QsO=M^2^Dd}#LoD8X*@EI}Wpw`H;S!bqhKZh5N^h#%p6$#1B zXel$P%47hR*_z3a@(fUIV?s+0vpJ_yDqGA8N?On7If;u0c_W9evt4^HQfaaHGg+(f zGD0|`6ET4z$c4#=ncqyb1mtw}Qsh^8Sq7Dd^pY*!Dyem8bn&7f6Jfd%T%|H}DwbrK zJx*EKuE>+y?S1L}U`p|m!=S;rFq#4^5*ye;uBZ1%IQi6=G!&P@2--~7+QrFVW0QEAcX zgn_cERlLa>lVLt~4v{{db2>qbF#3Ui9N^?+Hb%ee?m|0nbTFJkhu%qa=yi6sBTC6O zwhCLr;tMWVSim6X3I;qkLKh;Du#FU>=0zRmB<~}XotHLZix$Z{XFq zu0!+Vkz z^w=ql$qAfrjPDeiyq6~IaiSfcH=ga8!-L6sh7tI(NqL+ohF>90lwj5bXDj8t=0D? zm%14(p4-x5f*vN)Z6R`e9#L-#ixt;wV2?PPpl6n~ep(**czB?vyUY5wxK(X;S7#XP z>&0_)$hH()UcBo3yGQylNpGar25*PZk2i#fI0JoM)@Gf;1ih;_Cov$6XL|b({xF#qy!P&6 zT^j#q3RRw{x1?Fw9m*LOfoz2>M9_TtGk+ue>fiaF5E*v_?@@kh;iR-T-(&r5N^ZHQ!_#V!!%2QFkwB>N6 zyxoK`&!b!}!3~*yWOvNUu?d;7lygWgB%(xCFi9CAm3Fk`6IlF&=^(BOE_@|AzjsNd z@~Pa8wUn$?W@U7KldKYB%&Lg8w^6wY0z{YtrKc8?mqIk;5>zm6cP@2@RAzUr*6bP( zCAvD|XoKvjLt#cEUtF zOND0!&o`biPM+UHf8gBoRg7_cFI;}}HxZEHHu~e1@Rh+`A-MRI->dcCjgPDR(#vAe z7yOQ~ZzovZjlda0n9TMRcE31>zf;GDG2Xd_iFk}Sw~C-DoWwR4CZ?9GXkFMTEH1KD zy(j5U##v=tUEQ)a>F7w0ZP9yT9AmZ^kLKhq+dZvMSJ5_t@l%kU;ah2oGp1tj-t=Cooy>^ocQxD6_G(6~6oG`{Dh|w^8m{gq=V% z2~1Aw>R^PxI8o1u-nXPLi%&OFU>$<@H>{)e-o3+NWOO`SKnU;`zx?;Z@X_<3e{kGZ z9kIdc59X-D2H!Afx4dx2BIdpK)87lXue=s!Cq6*A*NyE#;NV}p#*$ICubdZMqjMEc z6|5|SG(7flPt}uO4_?;2A9=4!Wl?#Q-%Hu{YMXS*hGyi{;}P83Pnh!M^lo%|yQ<`P ze7!X7kd5_d_7m>Qy%)LSCvUWy_daWH-v9PnQy#+eGK#iXZpjrLp6)2qIyR7u*{I5= zjjH;mwiC3LF4vRe1fwmoi&)7M-owQ0aYbW&5uy)_CnZf+@*`!4BKNK|;4CCToQPl(Iz?aL9uKRDW5QBc-X)*d4v<6@=0uV5}p80LZD)L> z^LWo^1n$RNB%jGg$SavA5>uk3e9HB@ypJ*!Dl;hgKOW$=_d>MHY@j6ig@gZ`1c2B7 zvzVaw>`T9nsIuS0MD{W3{E5#|loGj}Oe6)9eZ=~-s0HVlJftZf?Dyk<+0?|_;ZOhV ze;=-2{XP~%#HhAre=^3GTly)hN@j#dO_4V0&G_IWM$F^NR7SuWEUv6W7bMty1-t$B%S>6~~lDwlIaSS_D4XJ`MyFkrG)aFnfhB)ov4^EH~0RM;e1$|Mc{vLj_k zlUR#rpJO@_d8mm>_VSHI5^D|B5M@fEm^ygVfFEuL%gJ%uh{lf$CdX`Ik>85-G2F+y zh!f(t_>B?hxcyAMWmH^Uu(g|n0KwfIg1cK|!QFzB#w}=YcXxt20RjYfcX#*3-DzlC zzkbg-cZ~aQ|J`G(y>`{CIiJF&ov(Q}Lb}QF9`bx9rQF(p$#Q96P6}FU{>u+#4~NcN zUg#z1gN;6sz;D>hl6v4@O*%W|12eXUdeZ}#abKCd&09Z8LB-QQPQnfS^8Io2S~=*hc5zUD_`WT_sGC*|Z(1Y8q+V^rYQ6 z9E?dbSAYnZhS3mdaPrl?6hajakML5cB1ak5UKa%)IQJ%&)|$ zzbapMRKr{LpK(zn+-I1&1LezP=^$f9zmkMlKR~rI8syRICZqb z1)E|L3+%#hEFq8_`{-4z zz7*ahuO8Cq4#7w^71?3_R82*=ebQJ_4lua;z4ql&4{y;(968m!8fkrpo}XF(2jMg)x5DSb?uhUFFZ`P)sTnwl8qgfTgNXInq(4f?6B)-IB( zU(S|I4!>%R-rTzyPM2`r+`5JpEPuM^%O4>oA^G;jEX556W>MbA+=zL+V5Tk9lJTbR zfTqzyT-y>=^k1FjeF^{>9x;HUKbbNdF(}khc~kY7jo;}FX?y;WH6+oZD=GhAV8rD` z#c0K&x-9@69xC7-^!A=9I`6!iHM`t4u=yOGSJ$!;@GRl>ZKEQOv)4`;d6xsk!cG(y z3Y6omyg2q{>85}-bW0+?#b_U(d?8R`qMX9-W0w>ehPry|CO~pv#)lN2>Cf`i5qs`! zw@isWOJcWM?k^Dd+y(eI9e9gUh!9`lT3K~lxmBC1`K9d#o>PlJ4yJ{i5gBO`GN2Gt7`x$lDeErPuQC zm*A$$gHh&8;(xkWt|(eHkS{pG#b(nfP(RD2*wQ1*bjVeNc6R^)U22N!6eg#Kj0*Cr zS_JihD6YrkyYP%7CK-Nt?5>1xCb2(!I#_%jO}|5YN|?%E@Z9@ZLFAa=V{#%A0fq`U ziy3A)bV9RT8Hr* z!4jFu>bBUYVt!NL?X*v)@qywCwi0(gF7_~>8G7IIWMy zcDGnIS%Jk(T}C=1rYs{B=05d}JY-UF!el`A{Y#3U_Xa~>JG90(M|}P{s3L9tbxq3x zq=R-V=$~@ZEDe;zg-7-EGLhV9H<3gh$2PyXRldVM-Bzyh_&)Bn>x)-%7r8z&l8qql zQ%i%>SWj^l>?S95un%iPkXncmlrbyZp#9E5lgd84!3FBa(>fe>8(M4+XL}8yghFP6 z!+ZekSgN!Hs%@SGReEV^Mr+P*jla@Q83OYDpLqtH4JAs6I1Gr>ic+?qlV@R4_wL45 zm`0~^w>qkkEa&(Os2YL=rN4Wkt)b%`_`>8B-e<#H}ld! zoGJwh7%Pd8`LY#ZltA6^)UF4j`x{w|s9EvLUgaq+256Vsb3Xa0*CH363}Grukf0O1 zqhtM0BjygH_q36fhR{TVVP$zW21pW`lDYSGdePi8A&$cJx3KY>C7!VB22ekFZer*w*rbQ7l3)aG~* zbaOKj6@u?6IvgDl4JvlU;yj5809aj4DOeX>H8nGGdlsY{N}MvrZ7H|SqNG6bX_Q&U0L87D%hLa1ySVicfhx!DnFJw6qA5rJk3v}zg#KW{X_ zfZ?SOex+6FSNC|g6po>5h*U@XZ21gxsx(8t5GsjJVnocSw36WBY>HlH18Ji68Tvkt`vGJ6^U7aX?<=;(At0_ok#3279#n0qFj5L0&Nnig$cT99-;lE z{71{gEIJyU7_pT3LjGXKzl|FwPp-`l=1ltDCo=9&|4bT}jbUIKn~KeQKT{$o{Ry)7 zwIU$#TDy){%^`;^Z7~L+W{vJghcDXy*Q*l;!wXEeY%DCqRK&zW<1@ljKYlD9q=KGz zSQk-<{XhJ0BZIT`{J5h&YJm J5U(3mQs<=;J(!d6#$Fv%WzLU=zPK7M$qBeg>M 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

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