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.

155 lines
4.1 KiB

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