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.

139 lines
3.6 KiB

  1. /*
  2. Package files defines a Provider that stores all data in the filesystem
  3. We assume the same validator hash may be reused by many different
  4. headers/Commits, and thus store it separately. This leaves us
  5. with three issues:
  6. 1. Given a validator hash, retrieve the validator set if previously stored
  7. 2. Given a block height, find the Commit with the highest height <= h
  8. 3. Given a FullCommit, store it quickly to satisfy 1 and 2
  9. Note that we do not worry about caching, as that can be achieved by
  10. pairing this with a MemStoreProvider and CacheProvider from certifiers
  11. */
  12. package files
  13. import (
  14. "encoding/hex"
  15. "fmt"
  16. "math"
  17. "os"
  18. "path/filepath"
  19. "sort"
  20. "github.com/pkg/errors"
  21. "github.com/tendermint/tendermint/lite"
  22. liteErr "github.com/tendermint/tendermint/lite/errors"
  23. )
  24. // nolint
  25. const (
  26. Ext = ".tsd"
  27. ValDir = "validators"
  28. CheckDir = "checkpoints"
  29. dirPerm = os.FileMode(0755)
  30. filePerm = os.FileMode(0644)
  31. )
  32. type provider struct {
  33. valDir string
  34. checkDir string
  35. }
  36. // NewProvider creates the parent dir and subdirs
  37. // for validators and checkpoints as needed
  38. func NewProvider(dir string) lite.Provider {
  39. valDir := filepath.Join(dir, ValDir)
  40. checkDir := filepath.Join(dir, CheckDir)
  41. for _, d := range []string{valDir, checkDir} {
  42. err := os.MkdirAll(d, dirPerm)
  43. if err != nil {
  44. panic(err)
  45. }
  46. }
  47. return &provider{valDir: valDir, checkDir: checkDir}
  48. }
  49. func (p *provider) encodeHash(hash []byte) string {
  50. return hex.EncodeToString(hash) + Ext
  51. }
  52. func (p *provider) encodeHeight(h int) string {
  53. // pad up to 10^12 for height...
  54. return fmt.Sprintf("%012d%s", h, Ext)
  55. }
  56. // StoreCommit saves a full commit after it has been verified.
  57. func (p *provider) StoreCommit(fc lite.FullCommit) error {
  58. // make sure the fc is self-consistent before saving
  59. err := fc.ValidateBasic(fc.Commit.Header.ChainID)
  60. if err != nil {
  61. return err
  62. }
  63. paths := []string{
  64. filepath.Join(p.checkDir, p.encodeHeight(fc.Height())),
  65. filepath.Join(p.valDir, p.encodeHash(fc.Header.ValidatorsHash)),
  66. }
  67. for _, path := range paths {
  68. err := SaveFullCommit(fc, path)
  69. // unknown error in creating or writing immediately breaks
  70. if err != nil {
  71. return err
  72. }
  73. }
  74. return nil
  75. }
  76. // GetByHeight returns the closest commit with height <= h.
  77. func (p *provider) GetByHeight(h int) (lite.FullCommit, error) {
  78. // first we look for exact match, then search...
  79. path := filepath.Join(p.checkDir, p.encodeHeight(h))
  80. fc, err := LoadFullCommit(path)
  81. if liteErr.IsCommitNotFoundErr(err) {
  82. path, err = p.searchForHeight(h)
  83. if err == nil {
  84. fc, err = LoadFullCommit(path)
  85. }
  86. }
  87. return fc, err
  88. }
  89. // LatestCommit returns the newest commit stored.
  90. func (p *provider) LatestCommit() (fc lite.FullCommit, err error) {
  91. // Note to future: please update by 2077 to avoid rollover
  92. return p.GetByHeight(math.MaxInt32 - 1)
  93. }
  94. // search for height, looks for a file with highest height < h
  95. // return certifiers.ErrCommitNotFound() if not there...
  96. func (p *provider) searchForHeight(h int) (string, error) {
  97. d, err := os.Open(p.checkDir)
  98. if err != nil {
  99. return "", errors.WithStack(err)
  100. }
  101. files, err := d.Readdirnames(0)
  102. d.Close()
  103. if err != nil {
  104. return "", errors.WithStack(err)
  105. }
  106. desired := p.encodeHeight(h)
  107. sort.Strings(files)
  108. i := sort.SearchStrings(files, desired)
  109. if i == 0 {
  110. return "", liteErr.ErrCommitNotFound()
  111. }
  112. found := files[i-1]
  113. path := filepath.Join(p.checkDir, found)
  114. return path, errors.WithStack(err)
  115. }
  116. // GetByHash returns a commit exactly matching this validator hash.
  117. func (p *provider) GetByHash(hash []byte) (lite.FullCommit, error) {
  118. path := filepath.Join(p.valDir, p.encodeHash(hash))
  119. return LoadFullCommit(path)
  120. }