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.

275 lines
8.4 KiB

lite: follow up from #3989 (#4209) * rename adjusted to adjacent Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r352140829 * rename ErrTooMuchChange to ErrNotEnoughVotingPowerSigned Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r352142785 * verify commit is properly signed * remove no longer trusted headers * restore trustedHeader and trustedNextVals * check trustedHeader using options Refs https://github.com/tendermint/tendermint/pull/4209#issuecomment-562462165 * use correct var when checking if headers are adjacent in bisection func + replace TODO with a comment https://github.com/tendermint/tendermint/pull/3989#discussion_r352125455 * return header in VerifyHeaderAtHeight because that way we avoid DB call + add godoc comments + check if there are no headers yet in AutoClient https://github.com/tendermint/tendermint/pull/3989#pullrequestreview-315454506 * TestVerifyAdjacentHeaders: add 2 more test-cases + add TestVerifyReturnsErrorIfTrustLevelIsInvalid * lite: avoid overflow when parsing key in db store! * lite: rename AutoClient#Err to Errs * lite: add a test for AutoClient * lite: fix keyPattern and call itr.Next in db store * lite: add two tests for db store * lite: add TestClientRemovesNoLongerTrustedHeaders * lite: test Client#Cleanup * lite: test restoring trustedHeader https://github.com/tendermint/tendermint/pull/4209#issuecomment-562462165 * lite: comment out unused code in test_helpers * fix TestVerifyReturnsErrorIfTrustLevelIsInvalid after merge * change defaultRemoveNoLongerTrustedHeadersPeriod and add docs * write more doc * lite: uncomment testable examples * use stdlog.Fatal to stop AutoClient tests * make lll linter happy * separate errors for 2 cases - the validator set of a skipped header cannot be trusted, i.e. <1/3rd of h1 validator set has signed (new error, something like ErrNewValSetCantBeTrusted) - the validator set is trusted but < 2/3rds has signed (ErrNewHeaderCantBeTrusted) https://github.com/tendermint/tendermint/pull/4209#discussion_r360331253 * remove all headers (even the last one) that are outside of the trusting period. By doing this, we avoid checking the trustedHeader's hash in checkTrustedHeaderUsingOptions (case #1). https://github.com/tendermint/tendermint/pull/4209#discussion_r360332460 * explain restoreTrustedHeaderAndNextVals better https://github.com/tendermint/tendermint/pull/4209#discussion_r360602328 * add ConfirmationFunction option for optionally prompting for user input Y/n before removing headers Refs https://github.com/tendermint/tendermint/pull/4209#discussion_r360602945 * make cleaning optional https://github.com/tendermint/tendermint/pull/4209#discussion_r364838189 * return error when user refused to remove headers * check for double votes in VerifyCommitTrusting * leave only ErrNewValSetCantBeTrusted error to differenciate between h2Vals.VerifyCommit and h1NextVals.VerifyCommitTrusting * fix example tests * remove unnecessary if condition https://github.com/tendermint/tendermint/pull/4209#discussion_r365171847 It will be handled by the above switch. * verifyCommitBasic does not depend on vals Co-authored-by: Marko <marbar3778@yahoo.com>
4 years ago
lite: follow up from #3989 (#4209) * rename adjusted to adjacent Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r352140829 * rename ErrTooMuchChange to ErrNotEnoughVotingPowerSigned Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r352142785 * verify commit is properly signed * remove no longer trusted headers * restore trustedHeader and trustedNextVals * check trustedHeader using options Refs https://github.com/tendermint/tendermint/pull/4209#issuecomment-562462165 * use correct var when checking if headers are adjacent in bisection func + replace TODO with a comment https://github.com/tendermint/tendermint/pull/3989#discussion_r352125455 * return header in VerifyHeaderAtHeight because that way we avoid DB call + add godoc comments + check if there are no headers yet in AutoClient https://github.com/tendermint/tendermint/pull/3989#pullrequestreview-315454506 * TestVerifyAdjacentHeaders: add 2 more test-cases + add TestVerifyReturnsErrorIfTrustLevelIsInvalid * lite: avoid overflow when parsing key in db store! * lite: rename AutoClient#Err to Errs * lite: add a test for AutoClient * lite: fix keyPattern and call itr.Next in db store * lite: add two tests for db store * lite: add TestClientRemovesNoLongerTrustedHeaders * lite: test Client#Cleanup * lite: test restoring trustedHeader https://github.com/tendermint/tendermint/pull/4209#issuecomment-562462165 * lite: comment out unused code in test_helpers * fix TestVerifyReturnsErrorIfTrustLevelIsInvalid after merge * change defaultRemoveNoLongerTrustedHeadersPeriod and add docs * write more doc * lite: uncomment testable examples * use stdlog.Fatal to stop AutoClient tests * make lll linter happy * separate errors for 2 cases - the validator set of a skipped header cannot be trusted, i.e. <1/3rd of h1 validator set has signed (new error, something like ErrNewValSetCantBeTrusted) - the validator set is trusted but < 2/3rds has signed (ErrNewHeaderCantBeTrusted) https://github.com/tendermint/tendermint/pull/4209#discussion_r360331253 * remove all headers (even the last one) that are outside of the trusting period. By doing this, we avoid checking the trustedHeader's hash in checkTrustedHeaderUsingOptions (case #1). https://github.com/tendermint/tendermint/pull/4209#discussion_r360332460 * explain restoreTrustedHeaderAndNextVals better https://github.com/tendermint/tendermint/pull/4209#discussion_r360602328 * add ConfirmationFunction option for optionally prompting for user input Y/n before removing headers Refs https://github.com/tendermint/tendermint/pull/4209#discussion_r360602945 * make cleaning optional https://github.com/tendermint/tendermint/pull/4209#discussion_r364838189 * return error when user refused to remove headers * check for double votes in VerifyCommitTrusting * leave only ErrNewValSetCantBeTrusted error to differenciate between h2Vals.VerifyCommit and h1NextVals.VerifyCommitTrusting * fix example tests * remove unnecessary if condition https://github.com/tendermint/tendermint/pull/4209#discussion_r365171847 It will be handled by the above switch. * verifyCommitBasic does not depend on vals Co-authored-by: Marko <marbar3778@yahoo.com>
4 years ago
  1. package lite
  2. import (
  3. "bytes"
  4. "fmt"
  5. "sync"
  6. log "github.com/tendermint/tendermint/libs/log"
  7. lerr "github.com/tendermint/tendermint/lite/errors"
  8. "github.com/tendermint/tendermint/types"
  9. )
  10. const sizeOfPendingMap = 1024
  11. var _ Verifier = (*DynamicVerifier)(nil)
  12. // DynamicVerifier implements an auto-updating Verifier. It uses a
  13. // "source" provider to obtain the needed FullCommits to securely sync with
  14. // validator set changes. It stores properly validated data on the
  15. // "trusted" local system.
  16. // TODO: make this single threaded and create a new
  17. // ConcurrentDynamicVerifier that wraps it with concurrency.
  18. // see https://github.com/tendermint/tendermint/issues/3170
  19. type DynamicVerifier struct {
  20. chainID string
  21. logger log.Logger
  22. // Already validated, stored locally
  23. trusted PersistentProvider
  24. // New info, like a node rpc, or other import method.
  25. source Provider
  26. // pending map to synchronize concurrent verification requests
  27. mtx sync.Mutex
  28. pendingVerifications map[int64]chan struct{}
  29. }
  30. // NewDynamicVerifier returns a new DynamicVerifier. It uses the
  31. // trusted provider to store validated data and the source provider to
  32. // obtain missing data (e.g. FullCommits).
  33. //
  34. // The trusted provider should be a DBProvider.
  35. // The source provider should be a client.HTTPProvider.
  36. func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provider) *DynamicVerifier {
  37. return &DynamicVerifier{
  38. logger: log.NewNopLogger(),
  39. chainID: chainID,
  40. trusted: trusted,
  41. source: source,
  42. pendingVerifications: make(map[int64]chan struct{}, sizeOfPendingMap),
  43. }
  44. }
  45. func (dv *DynamicVerifier) SetLogger(logger log.Logger) {
  46. logger = logger.With("module", "lite")
  47. dv.logger = logger
  48. dv.trusted.SetLogger(logger)
  49. dv.source.SetLogger(logger)
  50. }
  51. // Implements Verifier.
  52. func (dv *DynamicVerifier) ChainID() string {
  53. return dv.chainID
  54. }
  55. // Implements Verifier.
  56. //
  57. // If the validators have changed since the last known time, it looks to
  58. // dv.trusted and dv.source to prove the new validators. On success, it will
  59. // try to store the SignedHeader in dv.trusted if the next
  60. // validator can be sourced.
  61. func (dv *DynamicVerifier) Verify(shdr types.SignedHeader) error {
  62. // Performs synchronization for multi-threads verification at the same height.
  63. dv.mtx.Lock()
  64. if pending := dv.pendingVerifications[shdr.Height]; pending != nil {
  65. dv.mtx.Unlock()
  66. <-pending // pending is chan struct{}
  67. } else {
  68. pending := make(chan struct{})
  69. dv.pendingVerifications[shdr.Height] = pending
  70. defer func() {
  71. close(pending)
  72. dv.mtx.Lock()
  73. delete(dv.pendingVerifications, shdr.Height)
  74. dv.mtx.Unlock()
  75. }()
  76. dv.mtx.Unlock()
  77. }
  78. //Get the exact trusted commit for h, and if it is
  79. // equal to shdr, then it's already trusted, so
  80. // just return nil.
  81. trustedFCSameHeight, err := dv.trusted.LatestFullCommit(dv.chainID, shdr.Height, shdr.Height)
  82. if err == nil {
  83. // If loading trust commit successfully, and trust commit equal to shdr, then don't verify it,
  84. // just return nil.
  85. if bytes.Equal(trustedFCSameHeight.SignedHeader.Hash(), shdr.Hash()) {
  86. dv.logger.Info(fmt.Sprintf("Load full commit at height %d from cache, there is not need to verify.", shdr.Height))
  87. return nil
  88. }
  89. } else if !lerr.IsErrCommitNotFound(err) {
  90. // Return error if it is not CommitNotFound error
  91. dv.logger.Info(fmt.Sprintf("Encountered unknown error in loading full commit at height %d.", shdr.Height))
  92. return err
  93. }
  94. // Get the latest known full commit <= h-1 from our trusted providers.
  95. // The full commit at h-1 contains the valset to sign for h.
  96. prevHeight := shdr.Height - 1
  97. trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, prevHeight)
  98. if err != nil {
  99. return err
  100. }
  101. // sync up to the prevHeight and assert our latest NextValidatorSet
  102. // is the ValidatorSet for the SignedHeader
  103. if trustedFC.Height() == prevHeight {
  104. // Return error if valset doesn't match.
  105. if !bytes.Equal(
  106. trustedFC.NextValidators.Hash(),
  107. shdr.Header.ValidatorsHash) {
  108. return lerr.ErrUnexpectedValidators(
  109. trustedFC.NextValidators.Hash(),
  110. shdr.Header.ValidatorsHash)
  111. }
  112. } else {
  113. // If valset doesn't match, try to update
  114. if !bytes.Equal(
  115. trustedFC.NextValidators.Hash(),
  116. shdr.Header.ValidatorsHash) {
  117. // ... update.
  118. trustedFC, err = dv.updateToHeight(prevHeight)
  119. if err != nil {
  120. return err
  121. }
  122. // Return error if valset _still_ doesn't match.
  123. if !bytes.Equal(trustedFC.NextValidators.Hash(),
  124. shdr.Header.ValidatorsHash) {
  125. return lerr.ErrUnexpectedValidators(
  126. trustedFC.NextValidators.Hash(),
  127. shdr.Header.ValidatorsHash)
  128. }
  129. }
  130. }
  131. // Verify the signed header using the matching valset.
  132. cert := NewBaseVerifier(dv.chainID, trustedFC.Height()+1, trustedFC.NextValidators)
  133. err = cert.Verify(shdr)
  134. if err != nil {
  135. return err
  136. }
  137. // By now, the SignedHeader is fully validated and we're synced up to
  138. // SignedHeader.Height - 1. To sync to SignedHeader.Height, we need
  139. // the validator set at SignedHeader.Height + 1 so we can verify the
  140. // SignedHeader.NextValidatorSet.
  141. // TODO: is the ValidateFull below mostly redundant with the BaseVerifier.Verify above?
  142. // See https://github.com/tendermint/tendermint/issues/3174.
  143. // Get the next validator set.
  144. nextValset, err := dv.source.ValidatorSet(dv.chainID, shdr.Height+1)
  145. if lerr.IsErrUnknownValidators(err) {
  146. // Ignore this error.
  147. return nil
  148. } else if err != nil {
  149. return err
  150. }
  151. // Create filled FullCommit.
  152. nfc := FullCommit{
  153. SignedHeader: shdr,
  154. Validators: trustedFC.NextValidators,
  155. NextValidators: nextValset,
  156. }
  157. // Validate the full commit. This checks the cryptographic
  158. // signatures of Commit against Validators.
  159. if err := nfc.ValidateFull(dv.chainID); err != nil {
  160. return err
  161. }
  162. // Trust it.
  163. return dv.trusted.SaveFullCommit(nfc)
  164. }
  165. // verifyAndSave will verify if this is a valid source full commit given the
  166. // best match trusted full commit, and if good, persist to dv.trusted.
  167. // Returns ErrNotEnoughVotingPowerSigned when >2/3 of trustedFC did not sign sourceFC.
  168. // Panics if trustedFC.Height() >= sourceFC.Height().
  169. func (dv *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error {
  170. if trustedFC.Height() >= sourceFC.Height() {
  171. panic("should not happen")
  172. }
  173. err := trustedFC.NextValidators.VerifyFutureCommit(
  174. sourceFC.Validators,
  175. dv.chainID, sourceFC.SignedHeader.Commit.BlockID,
  176. sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit,
  177. )
  178. if err != nil {
  179. return err
  180. }
  181. return dv.trusted.SaveFullCommit(sourceFC)
  182. }
  183. // updateToHeight will use divide-and-conquer to find a path to h.
  184. // Returns nil error iff we successfully verify and persist a full commit
  185. // for height h, using repeated applications of bisection if necessary.
  186. //
  187. // Returns ErrCommitNotFound if source provider doesn't have the commit for h.
  188. func (dv *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) {
  189. // Fetch latest full commit from source.
  190. sourceFC, err := dv.source.LatestFullCommit(dv.chainID, h, h)
  191. if err != nil {
  192. return FullCommit{}, err
  193. }
  194. // If sourceFC.Height() != h, we can't do it.
  195. if sourceFC.Height() != h {
  196. return FullCommit{}, lerr.ErrCommitNotFound()
  197. }
  198. // Validate the full commit. This checks the cryptographic
  199. // signatures of Commit against Validators.
  200. if err := sourceFC.ValidateFull(dv.chainID); err != nil {
  201. return FullCommit{}, err
  202. }
  203. // Verify latest FullCommit against trusted FullCommits
  204. FOR_LOOP:
  205. for {
  206. // Fetch latest full commit from trusted.
  207. trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, h)
  208. if err != nil {
  209. return FullCommit{}, err
  210. }
  211. // We have nothing to do.
  212. if trustedFC.Height() == h {
  213. return trustedFC, nil
  214. }
  215. // Try to update to full commit with checks.
  216. err = dv.verifyAndSave(trustedFC, sourceFC)
  217. if err == nil {
  218. // All good!
  219. return sourceFC, nil
  220. }
  221. // Handle special case when err is ErrNotEnoughVotingPowerSigned.
  222. if types.IsErrNotEnoughVotingPowerSigned(err) {
  223. // Divide and conquer.
  224. start, end := trustedFC.Height(), sourceFC.Height()
  225. if !(start < end) {
  226. panic("should not happen")
  227. }
  228. mid := (start + end) / 2
  229. _, err = dv.updateToHeight(mid)
  230. if err != nil {
  231. return FullCommit{}, err
  232. }
  233. // If we made it to mid, we retry.
  234. continue FOR_LOOP
  235. }
  236. return FullCommit{}, err
  237. }
  238. }
  239. func (dv *DynamicVerifier) LastTrustedHeight() int64 {
  240. fc, err := dv.trusted.LatestFullCommit(dv.chainID, 1, 1<<63-1)
  241. if err != nil {
  242. panic("should not happen")
  243. }
  244. return fc.Height()
  245. }