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.

163 lines
4.4 KiB

  1. package lite
  2. import (
  3. "github.com/tendermint/tendermint/types"
  4. liteErr "github.com/tendermint/tendermint/lite/errors"
  5. )
  6. var _ Certifier = (*InquiringCertifier)(nil)
  7. // InquiringCertifier wraps a dynamic certifier and implements an auto-update strategy. If a call
  8. // to Certify fails due to a change it validator set, InquiringCertifier will try and find a
  9. // previous FullCommit which it can use to safely update the validator set. It uses a source
  10. // provider to obtain the needed FullCommits. It stores properly validated data on the local system.
  11. type InquiringCertifier struct {
  12. cert *DynamicCertifier
  13. // These are only properly validated data, from local system
  14. trusted Provider
  15. // This is a source of new info, like a node rpc, or other import method
  16. Source Provider
  17. }
  18. // NewInquiringCertifier returns a new Inquiring object. It uses the trusted provider to store
  19. // validated data and the source provider to obtain missing FullCommits.
  20. //
  21. // Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source
  22. // provider should be a client.HTTPProvider.
  23. func NewInquiringCertifier(chainID string, fc FullCommit, trusted Provider,
  24. source Provider) (*InquiringCertifier, error) {
  25. // store the data in trusted
  26. err := trusted.StoreCommit(fc)
  27. if err != nil {
  28. return nil, err
  29. }
  30. return &InquiringCertifier{
  31. cert: NewDynamicCertifier(chainID, fc.Validators, fc.Height()),
  32. trusted: trusted,
  33. Source: source,
  34. }, nil
  35. }
  36. // ChainID returns the chain id.
  37. // Implements Certifier.
  38. func (ic *InquiringCertifier) ChainID() string {
  39. return ic.cert.ChainID()
  40. }
  41. // Validators returns the validator set.
  42. func (ic *InquiringCertifier) Validators() *types.ValidatorSet {
  43. return ic.cert.cert.vSet
  44. }
  45. // LastHeight returns the last height.
  46. func (ic *InquiringCertifier) LastHeight() int64 {
  47. return ic.cert.lastHeight
  48. }
  49. // Certify makes sure this is checkpoint is valid.
  50. //
  51. // If the validators have changed since the last know time, it looks
  52. // for a path to prove the new validators.
  53. //
  54. // On success, it will store the checkpoint in the store for later viewing
  55. // Implements Certifier.
  56. func (ic *InquiringCertifier) Certify(commit Commit) error {
  57. err := ic.useClosestTrust(commit.Height())
  58. if err != nil {
  59. return err
  60. }
  61. err = ic.cert.Certify(commit)
  62. if !liteErr.IsValidatorsChangedErr(err) {
  63. return err
  64. }
  65. err = ic.updateToHash(commit.Header.ValidatorsHash)
  66. if err != nil {
  67. return err
  68. }
  69. err = ic.cert.Certify(commit)
  70. if err != nil {
  71. return err
  72. }
  73. // store the new checkpoint
  74. return ic.trusted.StoreCommit(NewFullCommit(commit, ic.Validators()))
  75. }
  76. // Update will verify if this is a valid change and update
  77. // the certifying validator set if safe to do so.
  78. func (ic *InquiringCertifier) Update(fc FullCommit) error {
  79. err := ic.useClosestTrust(fc.Height())
  80. if err != nil {
  81. return err
  82. }
  83. err = ic.cert.Update(fc)
  84. if err == nil {
  85. err = ic.trusted.StoreCommit(fc)
  86. }
  87. return err
  88. }
  89. func (ic *InquiringCertifier) useClosestTrust(h int64) error {
  90. closest, err := ic.trusted.GetByHeight(h)
  91. if err != nil {
  92. return err
  93. }
  94. // if the best seed is not the one we currently use,
  95. // let's just reset the dynamic validator
  96. if closest.Height() != ic.LastHeight() {
  97. ic.cert = NewDynamicCertifier(ic.ChainID(), closest.Validators, closest.Height())
  98. }
  99. return nil
  100. }
  101. // updateToHash gets the validator hash we want to update to
  102. // if IsTooMuchChangeErr, we try to find a path by binary search over height
  103. func (ic *InquiringCertifier) updateToHash(vhash []byte) error {
  104. // try to get the match, and update
  105. fc, err := ic.Source.GetByHash(vhash)
  106. if err != nil {
  107. return err
  108. }
  109. err = ic.cert.Update(fc)
  110. // handle IsTooMuchChangeErr by using divide and conquer
  111. if liteErr.IsTooMuchChangeErr(err) {
  112. err = ic.updateToHeight(fc.Height())
  113. }
  114. return err
  115. }
  116. // updateToHeight will use divide-and-conquer to find a path to h
  117. func (ic *InquiringCertifier) updateToHeight(h int64) error {
  118. // try to update to this height (with checks)
  119. fc, err := ic.Source.GetByHeight(h)
  120. if err != nil {
  121. return err
  122. }
  123. start, end := ic.LastHeight(), fc.Height()
  124. if end <= start {
  125. return liteErr.ErrNoPathFound()
  126. }
  127. err = ic.Update(fc)
  128. // we can handle IsTooMuchChangeErr specially
  129. if !liteErr.IsTooMuchChangeErr(err) {
  130. return err
  131. }
  132. // try to update to mid
  133. mid := (start + end) / 2
  134. err = ic.updateToHeight(mid)
  135. if err != nil {
  136. return err
  137. }
  138. // if we made it to mid, we recurse
  139. return ic.updateToHeight(h)
  140. }