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.

114 lines
5.8 KiB

  1. # Light client
  2. A light client is a process that connects to the Tendermint Full Node(s) and then tries to verify the Merkle proofs
  3. about the blockchain application. In this document we describe mechanisms that ensures that the Tendermint light client
  4. has the same level of security as Full Node processes (without being itself a Full Node).
  5. To be able to validate a Merkle proof, a light client needs to validate the blockchain header that contains the root app hash.
  6. Validating a blockchain header in Tendermint consists in verifying that the header is committed (signed) by >2/3 of the
  7. voting power of the corresponding validator set. As the validator set is a dynamic set (it is changing), one of the
  8. core functionality of the light client is updating the current validator set, that is then used to verify the
  9. blockchain header, and further the corresponding Merkle proofs.
  10. For the purpose of this light client specification, we assume that the Tendermint Full Node exposes the following functions over
  11. Tendermint RPC:
  12. ```golang
  13. Header(height int64) (SignedHeader, error) // returns signed header for the given height
  14. Validators(height int64) (ResultValidators, error) // returns validator set for the given height
  15. LastHeader(valSetNumber int64) (SignedHeader, error) // returns last header signed by the validator set with the given validator set number
  16. type SignedHeader struct {
  17. Header Header
  18. Commit Commit
  19. ValSetNumber int64
  20. }
  21. type ResultValidators struct {
  22. BlockHeight int64
  23. Validators []Validator
  24. // time the current validator set is initialised, i.e, time of the last validator change before header BlockHeight
  25. ValSetTime int64
  26. }
  27. ```
  28. We assume that Tendermint keeps track of the validator set changes and that each time a validator set is changed it is
  29. being assigned the next sequence number. We can call this number the validator set sequence number. Tendermint also remembers
  30. the Time from the header when the next validator set is initialised (starts to be in power), and we refer to this time
  31. as validator set init time.
  32. Furthermore, we assume that each validator set change is signed (committed) by the current validator set. More precisely,
  33. given a block `H` that contains transactions that are modifying the current validator set, the Merkle root hash of the next
  34. validator set (modified based on transactions from block H) will be in block `H+1` (and signed by the current validator
  35. set), and then starting from the block `H+2`, it will be signed by the next validator set.
  36. Note that the real Tendermint RPC API is slightly different (for example, response messages contain more data and function
  37. names are slightly different); we shortened (and modified) it for the purpose of this document to make the spec more
  38. clear and simple. Furthermore, note that in case of the third function, the returned header has `ValSetNumber` equals to
  39. `valSetNumber+1`.
  40. Locally, light client manages the following state:
  41. ```golang
  42. valSet []Validator // current validator set (last known and verified validator set)
  43. valSetNumber int64 // sequence number of the current validator set
  44. valSetHash []byte // hash of the current validator set
  45. valSetTime int64 // time when the current validator set is initialised
  46. ```
  47. The light client is initialised with the trusted validator set, for example based on the known validator set hash,
  48. validator set sequence number and the validator set init time.
  49. The core of the light client logic is captured by the VerifyAndUpdate function that is used to 1) verify if the given header is valid,
  50. and 2) update the validator set (when the given header is valid and it is more recent than the seen headers).
  51. ```golang
  52. VerifyAndUpdate(signedHeader SignedHeader):
  53. assertThat signedHeader.valSetNumber >= valSetNumber
  54. if isValid(signedHeader) and signedHeader.Header.Time <= valSetTime + UNBONDING_PERIOD then
  55. setValidatorSet(signedHeader)
  56. return true
  57. else
  58. updateValidatorSet(signedHeader.ValSetNumber)
  59. return VerifyAndUpdate(signedHeader)
  60. isValid(signedHeader SignedHeader):
  61. valSetOfTheHeader = Validators(signedHeader.Header.Height)
  62. assertThat Hash(valSetOfTheHeader) == signedHeader.Header.ValSetHash
  63. assertThat signedHeader is passing basic validation
  64. if votingPower(signedHeader.Commit) > 2/3 * votingPower(valSetOfTheHeader) then return true
  65. else
  66. return false
  67. setValidatorSet(signedHeader SignedHeader):
  68. nextValSet = Validators(signedHeader.Header.Height)
  69. assertThat Hash(nextValSet) == signedHeader.Header.ValidatorsHash
  70. valSet = nextValSet.Validators
  71. valSetHash = signedHeader.Header.ValidatorsHash
  72. valSetNumber = signedHeader.ValSetNumber
  73. valSetTime = nextValSet.ValSetTime
  74. votingPower(commit Commit):
  75. votingPower = 0
  76. for each precommit in commit.Precommits do:
  77. if precommit.ValidatorAddress is in valSet and signature of the precommit verifies then
  78. votingPower += valSet[precommit.ValidatorAddress].VotingPower
  79. return votingPower
  80. votingPower(validatorSet []Validator):
  81. for each validator in validatorSet do:
  82. votingPower += validator.VotingPower
  83. return votingPower
  84. updateValidatorSet(valSetNumberOfTheHeader):
  85. while valSetNumber != valSetNumberOfTheHeader do
  86. signedHeader = LastHeader(valSetNumber)
  87. if isValid(signedHeader) then
  88. setValidatorSet(signedHeader)
  89. else return error
  90. return
  91. ```
  92. Note that in the logic above we assume that the light client will always go upward with respect to header verifications,
  93. i.e., that it will always be used to verify more recent headers. In case a light client needs to be used to verify older
  94. headers (go backward) the same mechanisms and similar logic can be used. In case a call to the FullNode or subsequent
  95. checks fail, a light client need to implement some recovery strategy, for example connecting to other FullNode.