You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1216 lines
44 KiB

  1. # Fastsync
  2. Fastsync is a protocol that is used by a node to catch-up to the
  3. current state of a Tendermint blockchain. Its typical use case is a
  4. node that was disconnected from the system for some time. The
  5. recovering node locally has a copy of a prefix of the blockchain,
  6. and the corresponding application state that is slightly outdated. It
  7. then queries its peers for the blocks that were decided on by the
  8. Tendermint blockchain during the period the full node was
  9. disconnected. After receiving these blocks, it executes the
  10. transactions in the blocks in order to catch-up to the current height
  11. of the blockchain and the corresponding application state.
  12. In practice it is sufficient to catch-up only close to the current
  13. height: The Tendermint consensus reactor implements its own catch-up
  14. functionality and can synchronize a node that is close to the current height,
  15. perhaps within 10 blocks away from the current height of the blockchain.
  16. Fastsync should bring a node within this range.
  17. ## Outline
  18. - [Part I](#part-i---tendermint-blockchain): Introduction of Tendermint
  19. blockchain terms that are relevant for FastSync protocol.
  20. - [Part II](#part-ii---sequential-definition-of-fastsync-problem): Introduction
  21. of the problem addressed by the Fastsync protocol.
  22. - [Fastsync Informal Problem
  23. statement](#Fastsync-Informal-Problem-statement): For the general
  24. audience, that is, engineers who want to get an overview over what
  25. the component is doing from a bird's eye view.
  26. - [Sequential Problem statement](#Sequential-Problem-statement):
  27. Provides a mathematical definition of the problem statement in
  28. its sequential form, that is, ignoring the distributed aspect of
  29. the implementation of the blockchain.
  30. - [Part III](#part-iii---fastsync-as-distributed-system): Distributed
  31. aspects of the fast sync problem, system assumptions and temporal
  32. logic specifications.
  33. - [Computational Model](#Computational-Model):
  34. timing and correctness assumptions.
  35. - [Distributed Problem Statement](#Distributed-Problem-Statement):
  36. temporal properties that formalize safety and liveness
  37. properties of fast sync in distributed setting.
  38. - [Part IV](#part-iv---fastsync-protocol): Specification of Fastsync V2
  39. (the protocol underlying the current Golang implementation).
  40. - [Definitions](#Definitions): Describes inputs, outputs,
  41. variables used by the protocol, auxiliary functions
  42. - [FastSync V2](#FastSync-V2): gives an outline of the solution,
  43. and details of the functions used (with preconditions,
  44. postconditions, error conditions).
  45. - [Algorithm Invariants](#Algorithm-Invariants): invariants over
  46. the protocol variables that the implementation should maintain.
  47. - [Part V](#part-v---analysis-and-improvements): Analysis
  48. of Fastsync V2 that highlights several issues that prevent achieving
  49. some of the desired fault-tolerance properties. We also give some
  50. suggestions on how to address the issues in the future.
  51. - [Analysis of Fastsync V2](#Analysis-of-Fastsync-V2): describes
  52. undesirable scenarios of Fastsync V2, and why they violate
  53. desirable temporal logic specification in an unreliable
  54. distributed system.
  55. - [Suggestions](#Suggestions-for-an-Improved-Fastsync-Implementation) to address the issues discussed in the analysis.
  56. In this document we quite extensively use tags in order to be able to
  57. reference assumptions, invariants, etc. in future communication. In
  58. these tags we frequently use the following short forms:
  59. - TMBC: Tendermint blockchain
  60. - SEQ: for sequential specifications
  61. - FS: Fastsync
  62. - LIVE: liveness
  63. - SAFE: safety
  64. - INV: invariant
  65. - A: assumption
  66. - V2: refers to specifics of Fastsync V2
  67. - FS-VAR: refers to properties of Fastsync protocol variables
  68. - NewFS: refers to improved future Fastsync implementations
  69. # Part I - Tendermint Blockchain
  70. We will briefly list some of the notions of Tendermint blockchains that are
  71. required for this specification. More details can be found [here][block].
  72. #### **[TMBC-HEADER]**
  73. A set of blockchain transactions is stored in a data structure called
  74. *block*, which contains a field called *header*. (The data structure
  75. *block* is defined [here][block]). As the header contains hashes to
  76. the relevant fields of the block, for the purpose of this
  77. specification, we will assume that the blockchain is a list of
  78. headers, rather than a list of blocks.
  79. #### **[TMBC-SEQ]**
  80. The Tendermint blockchain is a list *chain* of headers.
  81. #### **[TMBC-SEQ-GROW]**
  82. During operation, new headers may be appended to the list one by one.
  83. > In the following, *ETIME* is a lower bound
  84. > on the time interval between the times at which two
  85. > successor blocks are added.
  86. #### **[TMBC-SEQ-APPEND-E]**
  87. If a header is appended at time *t* then no additional header will be
  88. appended before time *t + ETIME*.
  89. #### **[TMBC-AUTH-BYZ]**
  90. We assume the authenticated Byzantine fault model in which no node (faulty or
  91. correct) may break digital signatures, but otherwise, no additional
  92. assumption is made about the internal behavior of faulty
  93. nodes. That is, faulty nodes are only limited in that they cannot forge
  94. messages.
  95. <!-- The authenticated Byzantine model assumes [TMBC-Sign-NoForge] and -->
  96. <!-- [TMBC-FaultyFull], that is, faulty nodes are limited in that they -->
  97. <!-- cannot forge messages [TMBC-Sign-NoForge]. -->
  98. > We observed that in the existing documentation the term
  99. > *validator* refers to both a data structure and a full node that
  100. > participates in the distributed computation. Therefore, we introduce
  101. > the notions *validator pair* and *validator node*, respectively, to
  102. > distinguish these notions in the cases where they are not clear from
  103. > the context.
  104. #### **[TMBC-VALIDATOR-PAIR]**
  105. Given a full node, a
  106. *validator pair* is a pair *(address, voting_power)*, where
  107. - *address* is the address (public key) of a full node,
  108. - *voting_power* is an integer (representing the full node's
  109. voting power in a given consensus instance).
  110. > In the Golang implementation the data type for *validator
  111. > pair* is called `Validator`.
  112. #### **[TMBC-VALIDATOR-SET]**
  113. A *validator set* is a set of validator pairs. For a validator set
  114. *vs*, we write *TotalVotingPower(vs)* for the sum of the voting powers
  115. of its validator pairs.
  116. #### **[TMBC-CORRECT]**
  117. We define a predicate *correctUntil(n, t)*, where *n* is a node and *t* is a
  118. time point.
  119. The predicate *correctUntil(n, t)* is true if and only if the node *n*
  120. follows all the protocols (at least) until time *t*.
  121. #### **[TMBC-TIME-PARAMS]**
  122. A blockchain has the following configuration parameters:
  123. - *unbondingPeriod*: a time duration.
  124. - *trustingPeriod*: a time duration smaller than *unbondingPeriod*.
  125. #### **[TMBC-FM-2THIRDS]**
  126. If a block *h* is in the chain,
  127. then there exists a subset *CorrV*
  128. of *h.NextValidators*, such that:
  129. - *TotalVotingPower(CorrV) > 2/3
  130. TotalVotingPower(h.NextValidators)*;
  131. - For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n,
  132. h.Time + trustingPeriod)*.
  133. #### **[TMBC-CORR-FULL]**
  134. Every correct full node locally stores a prefix of the
  135. current list of headers from [**[TMBC-SEQ]**][TMBC-SEQ-link].
  136. # Part II - Sequential Definition of Fastsync Problem
  137. ## Fastsync Informal Problem statement
  138. A full node has as input a block of the blockchain at height *h* and
  139. the corresponding application state (or the prefix of the current
  140. blockchain until height *h*). It has access to a set *peerIDs* of full
  141. nodes called *peers* that it knows of. The full node uses the peers
  142. to read blocks of the Tendermint blockchain (in a safe way, that is,
  143. it checks the soundness conditions), until it has read the most recent
  144. block and then terminates.
  145. ## Sequential Problem statement
  146. *Fastsync* gets as input a block of height *h* and the corresponding
  147. application state *s* that corresponds to the block and state of that
  148. height of the blockchain, and produces
  149. as output (i) a list *L* of blocks starting at height *h* to some height
  150. *terminationHeight*, and (ii) the application state when applying the
  151. transactions of the list *L* to *s*.
  152. > In Tendermint, the commit for block of height *h* is contained in block *h + 1*,
  153. > and thus the block of height *h + 1* is needed to verify the block of
  154. > height *h*. Let us therefore clarify the following on the
  155. > termination height:
  156. > The returned value *terminationHeight* is the height of the block with the largest
  157. > height that could be verified. In order to do so, *Fastsync* needs the
  158. > block at height *terminationHeight + 1* of the blockchain.
  159. Fastsync has to satisfy the following properties:
  160. #### **[FS-SEQ-SAFE-START]**
  161. Let *bh* be the height of the blockchain at the time *Fastsync*
  162. starts. By assumption we have *bh >= h*.
  163. When *Fastsync* terminates, it outputs a list of all blocks from
  164. height *h* to some height *terminationHeight >= bh - 1*.
  165. > The above property is independent of how many blocks are added to the
  166. > blockchain while Fastsync is running. It links the target height to the
  167. > initial state. If Fastsync has to catch-up many blocks, it would be
  168. > better to link the target height to a time close to the
  169. > termination. This is captured by the following specification:
  170. #### **[FS-SEQ-SAFE-SYNC]**
  171. Let *eh* be the height of the blockchain at the time *Fastsync*
  172. terminates. There is a constant *D >= 1* such that when *Fastsync*
  173. terminates, it outputs a list of all blocks from height *h* to some
  174. height *terminationHeight >= eh - D*.
  175. #### **[FS-SEQ-SAFE-STATE]**
  176. Upon termination, the application state is the one that corresponds to
  177. the blockchain at height *terminationHeight*.
  178. #### **[FS-SEQ-LIVE]**
  179. *Fastsync* eventually terminates.
  180. # Part III - FastSync as Distributed System
  181. ## Computational Model
  182. #### **[FS-A-NODE]**
  183. We consider a node *FS* that performs *Fastsync*.
  184. #### **[FS-A-PEER-IDS]**
  185. *FS* has access to a set *peerIDs* of IDs (public keys) of peers
  186. . During the execution of *Fastsync*, another protocol (outside
  187. of this specification) may add new IDs to *peerIDs*.
  188. #### **[FS-A-PEER]**
  189. Peers can be faulty, and we do not make any assumptions about the number or
  190. ratio of correct/faulty nodes. Faulty processes may be Byzantine
  191. according to [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link].
  192. #### **[FS-A-VAL]**
  193. The system satisfies [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link] and
  194. [**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link]. Thus, there is a
  195. blockchain that satisfies the soundness requirements (that is, the
  196. validation rules in [[block]]).
  197. #### **[FS-A-COMM]**
  198. Communication between the node *FS* and all correct peers is reliable and
  199. bounded in time: there is a message end-to-end delay *Delta* such that
  200. if a message is sent at time *t* by a correct process to a correct
  201. process, then it will be received and processed by time *t +
  202. Delta*. This implies that we need a timeout of at least *2 Delta* for
  203. remote procedure calls to ensure that the response of a correct peer
  204. arrives before the timeout expires.
  205. ## Distributed Problem Statement
  206. ### Two Kinds of Termination
  207. We do not assume that there is a correct full node in
  208. *peerIDs*. Under this assumption no protocol can guarantee the combination
  209. of the properties [FS-SEQ-LIVE] and
  210. [FS-SEQ-SAFE-START] and [FS-SEQ-SAFE-SYNC] described in the sequential
  211. specification above. Thus, in the (unreliable) distributed setting, we
  212. consider two kinds of termination (successful and failure) and we will
  213. specify below under what (favorable) conditions *Fastsync* ensures to
  214. terminate successfully, and satisfy the requirements of the sequential
  215. problem statement:
  216. #### **[FS-DIST-LIVE]**
  217. *Fastsync* eventually terminates: it either *terminates successfully* or
  218. it *terminates with failure*.
  219. ### Fairness
  220. As mentioned above, without assumptions on the correctness of some
  221. peers, no protocol can achieve the required specifications. Therefore,
  222. we consider the following (fairness) constraint in the
  223. safety and liveness properties below:
  224. #### **[FS-SOME-CORR-PEER]**
  225. Initially, the set *peerIDs* contains at least one correct full node.
  226. > While in principle the above condition [FS-SOME-CORR-PEER]
  227. > can be part of a sufficient
  228. > condition to solve [FS-SEQ-LIVE] and
  229. > [FS-SEQ-SAFE-START] and [FS-SEQ-SAFE-SYNC] in the distributed
  230. > setting (their corresponding properties are given below), we will discuss in
  231. > [Part V](#part-v---analysis-and-improvements) that the
  232. > current implementation of Fastsync (V2) requires the much
  233. > stronger requirement [**[FS-ALL-CORR-PEER]**](#FS-ALL-CORR-PEER)
  234. > given in Part V.
  235. ### Safety
  236. > As this specification does
  237. > not assume that a correct peer is at the most recent height
  238. > of the blockchain (it might lag behind), the property [FS-SEQ-SAFE-START]
  239. > cannot be ensured in an unreliable distributed setting. We consider
  240. > the following relaxation. (Which is typically sufficient for
  241. > Tendermint, as the consensus reactor then synchronizes from that
  242. > height.)
  243. #### **[FS-DIST-SAFE-START]**
  244. Let *maxh* be the maximum
  245. height of a correct peer [**[TMBC-CORR-FULL]**][TMBC-CORR-FULL-link]
  246. in *peerIDs* at the time *Fastsync* starts. If *FastSync* terminates
  247. successfully, it is at some height *terminationHeight >= maxh - 1*.
  248. > To address [FS-SEQ-SAFE-SYNC] we consider the following property in
  249. > the distributed setting. See the comments below on the relation to
  250. > the sequential version.
  251. #### **[FS-DIST-SAFE-SYNC]**
  252. Under [FS-SOME-CORR-PEER], there exists a constant time interval *TD*, such
  253. that if *term* is the time *Fastsync* terminates and
  254. *maxh* is the maximum height of a correct peer
  255. [**[TMBC-CORR-FULL]**][TMBC-CORR-FULL-link] in *peerIDs* at the time
  256. *term - TD*, then if *FastSync* terminates successfully, it is at
  257. some height *terminationHeight >= maxh - 1*.
  258. > *TD* might depend on timeouts etc. We suggest that an acceptable
  259. > value for *TD* is in the range of approx. 10 sec., that is the
  260. > interval between two calls `QueryStatus()`; see below.
  261. > We use *term - TD* as reference time, as we have to account
  262. > for communication delay between the peer and *FS*. After the peer sent
  263. > the last message to *FS*, the peer and *FS* run concurrently and
  264. > independently. There is no assumption on the rate at which a peer can
  265. > add blocks (e.g., it might be in the process of catching up
  266. > itself). Hence, without additional assumption we cannot link
  267. > [FS-DIST-SAFE-SYNC] to
  268. > [**[FS-SEQ-SAFE-SYNC]**](#FS-SEQ-SAFE-SYNC), in particular to the
  269. > parameter *D*. We discuss a
  270. > way to achieve this below:
  271. > **Relation to [FS-SEQ-SAFE-SYNC]:**
  272. > Under [FS-SOME-CORR-PEER], if *peerIDs* contains a full node that is
  273. > "synchronized with the blockchain", and *blockchainheight* is the height
  274. > of the blockchain at time *term*, then *terminationHeight* may even
  275. > achieve
  276. > *blockchainheight - TD / ETIME*;
  277. > cf. [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link], that is,
  278. > the parameter *D* from [FS-SEQ-SAFE-SYNC] is in the range of *TD / ETIME*.
  279. #### **[FS-DIST-SAFE-STATE]**
  280. It is the same as the sequential version
  281. [**[FS-SEQ-SAFE-STATE]**](#FS-SEQ-SAFE-STATE).
  282. #### **[FS-DIST-NONABORT]**
  283. If there is one correct process in *peerIDs* [FS-SOME-CORR-PEER],
  284. *Fastsync* never terminates with failure. (Together with [FS-DIST-LIVE]
  285. that means it will terminate successfully.)
  286. # Part IV - Fastsync protocol
  287. Here we provide a specification of the FastSync V2 protocol as it is currently
  288. implemented. The V2 design is the result of significant refactoring to improve
  289. the testability and determinism in the implementation. The architecture is
  290. detailed in
  291. [ADR-43](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-043-blockchain-riri-org.md).
  292. In the original design, a go-routine (thread of execution) was spawned for each block requested, and
  293. was responsible for both protocol logic and IO. In the V2 design, protocol logic
  294. is decoupled from IO by using three total threads of execution: a scheduler, a
  295. processer, and a demuxer.
  296. The scheduler contains the business logic for managing
  297. peers and requesting blocks from them, while the processor handles the
  298. computationally expensive block execution. Both the scheduler and processor
  299. are structured as finite state machines that receive input events and emit
  300. output events. The demuxer is responsible for all IO, including translating
  301. between internal events and IO messages, and routing events between components.
  302. Protocols in Tendermint can be considered to consist of two
  303. components: a "core" state machine and a "peer" state machine. The core state
  304. machine refers to the internal state managed by the node, while the peer state
  305. machine determines what messages to send to peers. In the FastSync design, the
  306. core and peer state machines correspond to the processor and scheduler,
  307. respectively.
  308. In the case of FastSync, the core state machine (the processor) is effectively
  309. just the Tendermint block execution function, while virtually all protocol logic
  310. is contained in the peer state machine (the scheduler). The processor is
  311. only implemented as a separate component due to the computationally expensive nature
  312. of block execution. We therefore focus our specification here on the peer state machine
  313. (the scheduler component), capturing the core state machine (the processor component)
  314. in the single `Execute` function, defined below.
  315. While the internal details of the `Execute` function are not relevant for the
  316. FastSync protocol and are thus not part of this specification, they will be
  317. defined in detail at a later date in a separate Block Execution specification.
  318. ## Definitions
  319. > We now introduce variables and auxiliary functions used by the protocol.
  320. ### Inputs
  321. - *startBlock*: the block Fastsync starts from
  322. - *startState*: application state corresponding to *startBlock.Height*
  323. #### **[FS-A-V2-INIT]**
  324. - *startBlock* is from the blockchain
  325. - *startState* is the application state of the blockchain at Height *startBlock.Height*.
  326. ### Variables
  327. - *height*: kinitially *startBlock.Height + 1*
  328. > height should be thought of the "height of the next block we need to download"
  329. - *state*: initially *startState*
  330. - *peerIDs*: peer addresses [FS-A-PEER-IDS](#fs-a-peer-ids)
  331. - *peerHeights*: stores for each peer the height it reported. initially 0
  332. - *pendingBlocks*: stores for each height which peer was
  333. queried. initially nil for each height
  334. - *receivedBlocks*: stores for each height which peer returned
  335. it. initially nil
  336. - *blockstore*: stores for each height greater than
  337. *startBlock.Height*, the block of that height. initially nil for
  338. all heights
  339. - *peerTimeStamp*: stores for each peer the last time a block was
  340. received
  341. - *pendingTime*: stores for a given height the time a block was requested
  342. - *peerRate*: stores for each peer the rate of received data in Bytes/second
  343. ### Auxiliary Functions
  344. #### **[FS-FUNC-TARGET]**
  345. - *TargetHeight = max {peerHeigts(addr): addr in peerIDs} union {height}*
  346. #### **[FS-FUNC-MATCH]**
  347. ```go
  348. func VerifyCommit(b Block, c Commit) Boolean
  349. ```
  350. - Comment
  351. - Corresponds to `verifyCommit(chainID string, blockID
  352. types.BlockID, height int64, commit *types.Commit) error` in the
  353. current Golang implementation, which expects blockID and height
  354. (from the first block) and the
  355. corresponding commit from the following block. We use the
  356. simplified form for ease in presentation.
  357. - Implementation remark
  358. <!-- - implements the check from -->
  359. <!-- [**[TMBC-SOUND-DISTR-PossCommit]**][TMBC-SOUND-DISTR-PossCommit--link], -->
  360. <!-- that is, that *c* is a valid commit for block *b* -->
  361. - implements the check that *c* is a valid commit for block *b*
  362. - Expected precondition
  363. - *c* is a valid commit for block *b*
  364. - Expected postcondition
  365. - *true* if precondition holds
  366. - *false* if precondition is violated
  367. - Error condition
  368. - none
  369. ----
  370. ### Messages
  371. Peers participating in FastSync exchange the following set of messages. Messages are
  372. encoded using the Amino serialization protocol. We define each message here
  373. using Go syntax, annoted with the Amino type name. The prefix `bc` refers to
  374. `blockchain`, which is the name of the FastSync reactor in the Go
  375. implementation.
  376. #### bcBlockRequestMessage
  377. ```go
  378. // type: "tendermint/blockchain/BlockRequest"
  379. type bcBlockRequestMessage struct {
  380. Height int64
  381. }
  382. ```
  383. Remark:
  384. - `msg.Height` > 0
  385. #### bcNoBlockResponseMessage
  386. ```go
  387. // type: "tendermint/blockchain/NoBlockResponse"
  388. type bcNoBlockResponseMessage struct {
  389. Height int64
  390. }
  391. ```
  392. Remark:
  393. - `msg.Height` > 0
  394. - This message type is included in the protocol for convenience and is not expected to be sent between two correct peers
  395. #### bcBlockResponseMessage
  396. ```go
  397. // type: "tendermint/blockchain/BlockResponse"
  398. type bcBlockResponseMessage struct {
  399. Block *types.Block
  400. }
  401. ```
  402. Remark:
  403. - `msg.Block` is a Tendermint block as defined in [[block]].
  404. - `msg.Block` != nil
  405. #### bcStatusRequestMessage
  406. ```go
  407. // type: "tendermint/blockchain/StatusRequest"
  408. type bcStatusRequestMessage struct {
  409. Height int64
  410. }
  411. ```
  412. Remark:
  413. - `msg.Height` > 0
  414. #### bcStatusResponseMessage
  415. ```go
  416. // type: "tendermint/blockchain/StatusResponse"
  417. type bcStatusResponseMessage struct {
  418. Height int64
  419. }
  420. ```
  421. Remark:
  422. - `msg.Height` > 0
  423. ### Remote Functions
  424. Peers expose the following functions over
  425. remote procedure calls. The "Expected precondition" are only expected for
  426. correct peers (as no assumption is made on internals of faulty
  427. processes [FS-A-PEER]). These functions are implemented using the above defined message types.
  428. > In this document we describe the communication with peers
  429. via asynchronous RPCs.
  430. ```go
  431. func Status(addr Address) (int64, error)
  432. ```
  433. - Implementation remark
  434. - RPC to full node *addr*
  435. - Request message: `bcStatusRequestMessage`.
  436. - Response message: `bcStatusResponseMessage`.
  437. - Expected precondition
  438. - none
  439. - Expected postcondition
  440. - if *addr* is correct: Returns the current height `height` of the
  441. peer. [FS-A-COMM]
  442. - if *addr* is faulty: Returns an arbitrary height. [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link]
  443. - Error condition
  444. - if *addr* is correct: none. By [FS-A-COMM] we assume communication is reliable and timely.
  445. - if *addr* is faulty: arbitrary error (including timeout). [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link]
  446. ----
  447. ```go
  448. func Block(addr Address, height int64) (Block, error)
  449. ```
  450. - Implementation remark
  451. - RPC to full node *addr*
  452. - Request message: `bcBlockRequestMessage`.
  453. - Response message: `bcBlockResponseMessage` or `bcNoBlockResponseMessage`.
  454. - Expected precondition
  455. - 'height` is less than or equal to height of the peer
  456. - Expected postcondition
  457. - if *addr* is correct: Returns the block of height `height`
  458. from the blockchain. [FS-A-COMM]
  459. - if *addr* is faulty: Returns arbitrary or no block [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link]
  460. - Error condition
  461. - if *addr* is correct: precondition violated (returns `bcNoBlockResponseMessage`). [FS-A-COMM]
  462. - if *addr* is faulty: arbitrary error (including timeout). [**[TMBC-AUTH-BYZ]**][TMBC-Auth-Byz-link]
  463. ----
  464. ## FastSync V2
  465. ### Outline
  466. The protocol is described in terms of functions that are triggered by
  467. (external) events. The implementation uses a scheduler and a
  468. de-multiplexer to deal with communicating with peers and to
  469. trigger the execution of these functions:
  470. - `QueryStatus()`: regularly (currently every 10sec; necessarily
  471. interval greater than *2 Delta*) queries all peers from *peerIDs*
  472. for their current height [TMBC-CORR-FULL]. It does so
  473. by calling `Status(n)` remotely on all peers *n*.
  474. - `CreateRequest`: regularly checks whether certain blocks have no
  475. open request. If a block does not have an open request, it requests
  476. one from a peer. It does so by calling `Block(n,h)` remotely on one
  477. peer *n* for a missing height *h*.
  478. > We have left the strategy how peers are selected unspecified, and
  479. > the currently existing different implementations of Fastsync differ
  480. > in this aspect. In V2, a peer *p* is selected with the minimum number of
  481. > pending requests that can serve the required height *h*, that is
  482. > with *peerHeight(p) >= h*.
  483. The functions `Status` and `Block` are called by asynchronous
  484. RPC. When they return, the following functions are called:
  485. - `OnStatusResponse(addr Address, height int64)`: The full node with
  486. address *addr* returns its current height. The function updates the height
  487. information about *addr*, and may also increase *TargetHeight*.
  488. - `OnBlockResponse(addr Address, b Block)`. The full node with
  489. address *addr* returns a block. It is added to *blockstore*. Then
  490. the auxiliary function `Execute` is called.
  491. - `Execute()`: Iterates over the *blockstore*. Checks soundness of
  492. the blocks, and
  493. executes the transactions of a sound block and updates *state*.
  494. > In addition to the functions above, the following two features are
  495. > implemented in Fastsync V2
  496. #### **[FS-V2-PEER-REMOVE]**
  497. Periodically, *peerTimeStamp* and *peerRate* and *pendingTime* are
  498. analyzed.
  499. If a peer *p*
  500. has not provided a block recently (check of *peerTimeStamp[p]*) or it
  501. has not provided sufficiently many data (check of *peerRate[p]*), then
  502. *p* is removed from *peerIDs*. In addition, *pendingTime* is used to
  503. estimate whether the peer that is responsible for the current height
  504. has provided the corresponding block on time.
  505. #### **[FS-V2-TIMEOUT]**
  506. *Fastsync V2* starts a timeout whenever a block is
  507. executed (that is, when the height is incremented). If the timeout expires
  508. before the next block is executed, *Fastsync* terminates.
  509. If this happens, then *Fastsync* terminates
  510. with failure.
  511. ### Details
  512. <!--
  513. > Function signatures followed by pseudocode (optional) and a list of features (required):
  514. > - Implementation remarks (optional)
  515. > - e.g. (local/remote) function called in the body of this function
  516. > - Expected precondition
  517. > - Expected postcondition
  518. > - Error condition
  519. ---->
  520. ```go
  521. func QueryStatus()
  522. ```
  523. - Expected precondition
  524. - peerIDs initialized and non-empty
  525. - Expected postcondition
  526. - call asynchronously `Status(n)` at each peer *n* in *peerIDs*.
  527. - Error condition
  528. - fails if precondition is violated
  529. ----
  530. ```go
  531. func OnStatusResponse(addr Address, ht int64)
  532. ```
  533. - Comment
  534. - *ht* is a height
  535. - peers can provide the status without being called
  536. - Expected precondition
  537. - *peerHeights(addr) <= ht*
  538. - Expected postcondition
  539. - *peerHeights(addr) = ht*
  540. - *TargetHeight* is updated
  541. - Error condition
  542. - if precondition is violated: *addr* not in *peerIDs* (that is,
  543. *addr* is removed from *peerIDs*)
  544. - Timeout condition
  545. - if `OnStatusResponse(addr, ht)` was not invoked within *2 Delta* after
  546. `Status(addr)` was called: *addr* not in *peerIDs*
  547. ----
  548. ```go
  549. func CreateRequest
  550. ```
  551. - Expected precondition
  552. - *height < TargetHeight*
  553. - *peerIDs* nonempty
  554. - Expected postcondition
  555. - Function `Block` is called remotely at a peer *addr* in peerIDs
  556. for a missing height *h*
  557. *Remark:* different implementations may have different
  558. strategies to balance the load over the peers
  559. - *pendingblocks(h) = addr*
  560. ----
  561. ```go
  562. func OnBlockResponse(addr Address, b Block)
  563. ```
  564. - Comment
  565. - if after adding block *b*, blocks of heights *height* and
  566. *height + 1* are in *blockstore*, then `Execute` is called
  567. - Expected precondition
  568. - *pendingblocks(b.Height) = addr*
  569. - *b* satisfies basic soundness
  570. - Expected postcondition
  571. - if function `Execute` has been executed without error or was not
  572. executed:
  573. - *receivedBlocks(b.Height) = addr*
  574. - *blockstore(b.Height) = b*
  575. - *peerTimeStamp[addr]* is set to a time between invocation and
  576. return of the function.
  577. - *peerRate[addr]* is updated according to size of received
  578. block and time it has passed between current time and last block received from this peer (addr)
  579. - Error condition
  580. - if precondition is violated: *addr* not in *peerIDs*; reset
  581. *pendingblocks(b.Height)* to nil;
  582. - Timeout condition
  583. - if `OnBlockResponse(addr, b)` was not invoked within *2 Delta* after
  584. `Block(addr,h)` was called for *b.Height = h*: *addr* not in *peerIDs*
  585. ----
  586. ```go
  587. func Execute()
  588. ```
  589. - Comments
  590. - none
  591. - Expected precondition
  592. - application state is the one of the blockchain at height
  593. *height - 1*
  594. - **[FS-V2-Verif]** for any two blocks *a* and *b* from
  595. *receivedBlocks*: if
  596. *a.Height + 1 = b.Height* then *VerifyCommit (a,b.Commit) = true*
  597. - Expected postcondition
  598. - Any two blocks *a* and *b* violating [FS-V2-Verif]:
  599. *a* and *b* not in *blockstore*; nodes with Address
  600. receivedBlocks(a.Height) and receivedBlocks(b.Height) not in peerIDs
  601. - height is updated height of complete prefix that matches the blockchain
  602. - state is the one of the blockchain at height *height - 1*
  603. - if the new value of *height* is equal to *TargetHeight*, then
  604. Fastsync
  605. **terminates
  606. successfully**.
  607. - Error condition
  608. - none
  609. ----
  610. ## Algorithm Invariants
  611. > In contrast to the temporal properties above that define the problem
  612. > statement, the following are invariants on the solution to the
  613. > problem, that is on the algorithm. These invariants are useful for
  614. > the verification, but can also guide the implementation.
  615. #### **[FS-VAR-STATE-INV]**
  616. It is always the case that *state* corresponds to the application state of the
  617. blockchain of that height, that is, *state = chain[height -
  618. 1].AppState*; *chain* is defined in
  619. [**[TMBC-SEQ]**][TMBC-SEQ-link].
  620. #### **[FS-VAR-PEER-INV]**
  621. It is always the case that the set *peerIDs* only contains nodes that
  622. have not yet misbehaved (by sending wrong data or timing out).
  623. #### **[FS-VAR-BLOCK-INV]**
  624. For *startBlock.Height <= i < height - 1*, let *b(i)* be the block with
  625. height *i* in *blockstore*, it always holds that
  626. *VerifyCommit(b(i), b(i+1).Commit) = true*. This means that *height*
  627. can only be incremented if all blocks with lower height have been verified.
  628. # Part V - Analysis and Improvements
  629. ## Analysis of Fastsync V2
  630. #### **[FS-ISSUE-KILL]**
  631. If two blocks are not matching [FS-V2-Verif], `Execute` dismisses both
  632. blocks and removes the peers that provided these blocks from
  633. *peerIDs*. If block *a* was correct and provided by a correct peer *p*,
  634. and block b was faulty and provided by a faulty peer, the protocol
  635. - removes the correct peer *p*, although it might be useful to
  636. download blocks from it in the future
  637. - removes the block *a*, so that a fresh copy of *a* needs to be downloaded
  638. again from another peer
  639. By [FS-A-PEER] we do not put a restriction on the number
  640. of faulty peers, so that faulty peers can make *FS* to remove all
  641. correct peers from *peerIDs*. As a result, this version of
  642. *Fastsync* violates [FS-DIST-SAFE-SYNC].
  643. #### **[FS-ISSUE-NON-TERM]**
  644. Due to [**[FS-ISSUE-KILL]**](#fs-issue-kill), from some point on, only
  645. faulty peers may be in *peerIDs*. They can thus control at which rate
  646. *Fastsync* gets blocks. If the timeout duration from [FS-V2-TIMEOUT]
  647. is greater than the time it takes to add a block to the blockchain
  648. (LTIME in [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link]), the
  649. protocol may never terminate and thus violate [FS-DIST-LIVE]. This
  650. scenario is even possible if a correct peer is always in *peerIDs*,
  651. but faulty peers are regularly asked for blocks.
  652. ### Consequence
  653. The issues [FS-ISSUE-KILL] and [FS-ISSUE-NON-TERM] explain why
  654. does not satisfy the property [FS-DIST-LIVE] relevant for termination.
  655. As a result, V2 only solves the specifications in a restricted form,
  656. namely, when all peers are correct:
  657. #### **[FS-ALL-CORR-PEER]**
  658. At all times, the set *peerIDs* contains only correct full nodes.
  659. With this restriction we can give the achieved properties:
  660. #### **[FS-VC-ALL-CORR-NONABORT]**
  661. Under [FS-ALL-CORR-PEER], *Fastsync* never terminates with failure.
  662. #### **[FS-VC-ALL-CORR-LIVE]**
  663. Under [FS-ALL-CORR-PEER], *Fastsync* eventually terminates successfully.
  664. > In a fault tolerance context this is problematic,
  665. > as it means that faulty peers can prevent *FastSync* from termination.
  666. > We observe that this also touches other properties, namely,
  667. > [FS-DIST-SAFE-START] and [FS-DIST-SAFE-SYNC]:
  668. > Termination at an acceptable height are all conditional under
  669. > "successful termination". The properties above severely restrict
  670. > under which circumstances FastSync (V2) terminates successfully.
  671. > As a result, large parts of the current
  672. > implementation of are not fault-tolerant. We will
  673. > discuss this, and suggestions how to solve this after the
  674. > description of the current protocol.
  675. ## Suggestions for an Improved Fastsync Implementation
  676. ### Solution for [FS-ISSUE-KILL]
  677. To avoid [FS-ISSUE-KILL], we observe that
  678. [**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link] ensures that from the
  679. point a block was created, we assume that more than two thirds of the
  680. validator nodes are correct until the *trustingPeriod* expires. Under
  681. this assumption, assume the trusting period of *startBlock* is not
  682. expired by the time *FastSync* checks a block *b1* with height
  683. *startBlock.Height + 1*. To do so, we first need to check whether the
  684. Commit in the block *b2* with *startBlock.Height + 2* contains more
  685. than 2/3 of the voting power in *startBlock.NextValidators*. If this
  686. is the case we can check *VerifyCommit (b1,b2.Commit)*. If we perform
  687. checks in this order we observe:
  688. - By assumption, *startBlock* is OK,
  689. - If the first check (2/3 of voting power) fails,
  690. the peer that provided block *b2* is faulty,
  691. - If the first check passes and the second check
  692. fails (*VerifyCommit*), then the peer that provided *b1* is
  693. faulty.
  694. - If both checks pass, we can trust *b1*
  695. Based on this reasoning, we can ensure to only remove faulty peers
  696. from *peerIDs*. That is, if
  697. we sequentially verify blocks starting with *startBlock*, we will
  698. never remove a correct peer from *peerIDs* and we will be able to
  699. ensure the following invariant:
  700. #### **[NewFS-VAR-PEER-INV]**
  701. If a peer never misbehaves, it is never removed from *peerIDs*. It
  702. follows that under [FS-SOME-CORR-PEER], *peerIDs* is always non-empty.
  703. > To ensure this, we suggest to change the protocol as follows:
  704. #### Fastsync has the following configuration parameters
  705. - *trustingPeriod*: a time duration; cf.
  706. [**[TMBC-TIME-PARAMS]**][TMBC-TIME-PARAMS-link].
  707. > [NewFS-A-INIT] is the suggested replacement of [FS-A-V2-INIT]. This will
  708. > allow us to use the established trust to understand precisely which
  709. > peer reported an invalid block in order to ensure the
  710. > invariant [NewFS-VAR-TRUST-INV] below:
  711. #### **[NewFS-A-INIT]**
  712. - *startBlock* is from the blockchain, and within *trustingPeriod*
  713. (possible with some extra margin to ensure termination before
  714. *trustingPeriod* expired)
  715. - *startState* is the application state of the blockchain at Height
  716. *startBlock.Height*.
  717. - *startHeight = startBlock.Height*
  718. #### Additional Variables
  719. - *trustedBlockstore*: stores for each height greater than or equal to
  720. *startBlock.Height*, the block of that height. Initially it
  721. contains only *startBlock*
  722. #### **[NewFS-VAR-TRUST-INV]**
  723. Let *b(i)* be the block in *trustedBlockstore*
  724. with b(i).Height = i. It holds that
  725. for *startHeight < i < height - 1*,
  726. *VerifyCommit (b(i),b(i+1).Commit) = true*.
  727. > We propose to update the function `Execute`. To do so, we first
  728. > define the following helper functions:
  729. ```go
  730. func ValidCommit(VS ValidatorSet, C Commit) Boolean
  731. ```
  732. - Comments
  733. - checks validator set based on [**[TMBC-FM-2THIRDS]**][TMBC-FM-2THIRDS-link]
  734. - Expected precondition
  735. - The validators in *C*
  736. - are a subset of VS
  737. - have more than 2/3 of the voting power in VS
  738. - Expected postcondition
  739. - returns *true* if precondition holds, and *false* otherwise
  740. - Error condition
  741. - none
  742. ----
  743. ```go
  744. func SequentialVerify {
  745. while (true) {
  746. b1 = blockstore[height];
  747. b2 = blockstore[height+1];
  748. if b1 == nil or b2 == nil {
  749. exit;
  750. }
  751. if ValidCommit(trustedBlockstore[height - 1].NextValidators, b2.commit) {
  752. // we trust b2
  753. if VerifyCommit(b1, b2.commit) {
  754. trustedBlockstore.Add(b1);
  755. height = height + 1;
  756. }
  757. else {
  758. // as we trust b2, b1 must be faulty
  759. blockstore.RemoveFromPeer(receivedBlocks[height]);
  760. // we remove all blocks received from the faulty peer
  761. peerIDs.Remove(receivedBlocks(bnew.Height));
  762. exit;
  763. }
  764. } else {
  765. // b2 is faulty
  766. blockstore.RemoveFromPeer(receivedBlocks[height + 1]);
  767. // we remove all blocks received from the faulty peer
  768. peerIDs.Remove(receivedBlocks(bnew.Height));
  769. exit; }
  770. }
  771. }
  772. ```
  773. - Comments
  774. - none
  775. - Expected precondition
  776. - [NewFS-VAR-TRUST-INV]
  777. - Expected postcondition
  778. - [NewFS-VAR-TRUST-INV]
  779. - there is no block *bnew* with *bnew.Height = height + 1* in
  780. *blockstore*
  781. - Error condition
  782. - none
  783. ----
  784. > Then `Execute` just consists in calling `SequentialVerify` and then
  785. > updating the application state to the (new) height.
  786. ```go
  787. func Execute()
  788. ```
  789. - Comments
  790. - first `SequentialVerify` is executed
  791. - Expected precondition
  792. - application state is the one of the blockchain at height
  793. *height - 1*
  794. - [NewFS-NOT-EXP] *trustedBlockstore[height-1].Time > now - trustingPeriod*
  795. - Expected postcondition
  796. - there is no block *bnew* with *bnew.Height = height + 1* in
  797. *blockstore*
  798. - state is the one of the blockchain at height *height - 1*
  799. - if height = TargetHeight: **terminate successfully**
  800. - Error condition
  801. - fails if [NewFS-NOT-EXP] is violated
  802. ----
  803. ### Solution for [FS-ISSUE-NON-TERM]
  804. As discussed above, the advantageous termination requirement is the
  805. combination of [FS-DIST-LIVE] and [FS-DIST-NONABORT], that is, *Fastsync*
  806. should terminate successfully in case there is at least one correct
  807. peer in *peerIDs*. For this we have to ensure that faulty processes
  808. cannot slow us down and provide blocks at a lower rate than the
  809. blockchain may grow. To ensure that we will have to add an assumption
  810. on message delays.
  811. #### **[NewFS-A-DELTA]**
  812. *2 Delta < ETIME*; cf. [**[TMBC-SEQ-APPEND-E]**][TMBC-SEQ-APPEND-E-link].
  813. > This assumption implies that the timeouts for `OnBlockResponse` and
  814. > `OnStatusResponse` are such that a faulty peer that tries to respond
  815. > slower than *2 Delta* will be removed. In the following we will
  816. > provide a rough estimate on termination time in a fault-prone
  817. > scenario.
  818. > In the following
  819. > we assume that during a "long enough" finite good period no new
  820. > faulty peers are added to *peerIDs*. Below we will sketch how "long
  821. > enough" can be estimated based on the timing assumption in this
  822. > specification.
  823. #### **[NewFS-A-STATUS-INTERVAL]**
  824. Let Sigma be the (upper bound on the)
  825. time between two calls of `QueryStatus()`.
  826. #### **[NewFS-A-GOOD-PERIOD]**
  827. A time interval *[begin,end]* is *good period* if:
  828. - *fmax* is the number of faulty peers in *peerIDs* at time *begin*
  829. - *end >= begin + 2 Delta (fmax + 3)*
  830. - no faulty peer is added before time *end*
  831. > In the analysis below we assume that the termination condition of
  832. > *Fastsync* is
  833. > *height = TargetHeight* in the postcondition of
  834. > `Execute`. Therefore, [NewFS-A-STATUS-INTERVAL] does not interfere
  835. > with this analysis. If a correct peer reports a new height "shortly
  836. > before termination" this leads to an additional round trip to
  837. > request and add the new block. Then [NewFS-A-DELTA] ensures that
  838. > *Fastsync* catches up.
  839. Arguments:
  840. 1. If a faulty peer *p* reports a faulty block, `SequentialVerify` will
  841. eventually remove *p* from *peerIDs*
  842. 2. By `SequentialVerify`, if a faulty peer *p* reports multiple faulty
  843. blocks, *p* will be removed upon trying to check the block with the
  844. smallest height received from *p*.
  845. 3. Assume whenever a block does not have an open request, `CreateRequest` is
  846. called immediately, which calls `Block(n)` on a peer. Say this
  847. happens at time *t*. There are two cases:
  848. - by t + 2 Delta a block is added to *blockStore*
  849. - at t + 2 Delta `Block(n)` timed out and *n* is removed from
  850. peer.
  851. 4. Let *f(t)* be the number of faulty peers in *peerIDs* at time *t*;
  852. *f(begin) = fmax*.
  853. 5. Let t_i be the sequence of times `OnBlockResponse(addr,b)` is
  854. invoked or times out with *b.Height = height + 1*.
  855. 6. By 3.,
  856. - (a). *t_1 <= begin + 2 Delta*
  857. - (b). *t_{i+1} <= t_i + 2 Delta*
  858. 7. By an inductive argument we prove for *i > 0* that
  859. - (a). *height(t_{i+1}) > height(t_i)*, or
  860. - (b). *f(t_{i+1}) < f(t_i))* and *height(t_{i+1}) = height(t_i)*
  861. Argument: if the peer is faulty and does not return a block, the
  862. peer is removed, if it is faulty and returns a faulty block
  863. `SequentialVerify` removes the peer (b). If the returned block is OK,
  864. height is increased (a).
  865. 8. By 2. and 7., faulty peers can delay incrementing the height at
  866. most *fmax* times, where each time "costs" *2 Delta* seconds. We
  867. have additional *2 Delta* initial offset (3a) plus *2 Delta* to get
  868. all missing blocks after the last fault showed itself. (This
  869. assumes that an arbitrary number of blocks can be obtained and
  870. checked within one round-trip 2 Delta; which either needs
  871. conservative estimation of Delta, or a more refined analysis). Thus
  872. we reach the *targetHeight* and terminate by time *end*.
  873. # References
  874. <!--
  875. > links to other specifications/ADRs this document refers to
  876. ---->
  877. [[block]] Specification of the block data structure.
  878. <!-- [[blockchain]] The specification of the Tendermint blockchain. Tags refering to this specification are labeled [TMBC-*]. -->
  879. [block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md
  880. <!-- [blockchain]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md -->
  881. [TMBC-HEADER-link]: #tmbc-header
  882. [TMBC-SEQ-link]: #tmbc-seq
  883. [TMBC-CORR-FULL-link]: #tmbc-corrfull
  884. [TMBC-CORRECT-link]: #tmbc-correct
  885. [TMBC-Sign-link]: #tmbc-sign
  886. [TMBC-FaultyFull-link]: #tmbc-faultyfull
  887. [TMBC-TIME-PARAMS-link]: #tmbc-time-params
  888. [TMBC-SEQ-APPEND-E-link]: #tmbc-seq-append-e
  889. [TMBC-FM-2THIRDS-link]: #tmbc-fm-2thirds
  890. [TMBC-Auth-Byz-link]: #tmbc-auth-byz
  891. [TMBC-INV-SIGN-link]: #tmbc-inv-sign
  892. [TMBC-SOUND-DISTR-PossCommit--link]: #tmbc-sound-distr-posscommit
  893. [TMBC-INV-VALID-link]: #tmbc-inv-valid
  894. <!-- [TMBC-HEADER-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-header -->
  895. <!-- [TMBC-SEQ-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-seq -->
  896. <!-- [TMBC-CORR-FULL-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-corrfull -->
  897. <!-- [TMBC-Sign-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-sign -->
  898. <!-- [TMBC-FaultyFull-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-faultyfull -->
  899. <!-- [TMBC-TIME-PARAMS-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-time-params -->
  900. <!-- [TMBC-SEQ-APPEND-E-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-seq-append-e -->
  901. <!-- [TMBC-FM-2THIRDS-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-fm-2thirds -->
  902. <!-- [TMBC-Auth-Byz-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-auth-byz -->
  903. <!-- [TMBC-INV-SIGN-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-inv-sign -->
  904. <!-- [TMBC-SOUND-DISTR-PossCommit--link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-sound-distr-posscommit -->
  905. <!-- [TMBC-INV-VALID-link]: https://github.com/informalsystems/VDD/tree/master/blockchain/blockchain.md#tmbc-inv-valid -->
  906. [LCV-VC-LIVE-link]: https://github.com/informalsystems/VDD/tree/master/lightclient/verification.md#lcv-vc-live
  907. [lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md
  908. [failuredetector]: https://github.com/informalsystems/VDD/blob/master/liteclient/failuredetector.md
  909. [fullnode]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md
  910. [FN-LuckyCase-link]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#fn-luckycase
  911. [blockchain-validator-set]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#data-structures
  912. [fullnode-data-structures]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#data-structures
  913. [FN-ManifestFaulty-link]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#fn-manifestfaulty