- /*
- Package files defines a Provider that stores all data in the filesystem
-
- We assume the same validator hash may be reused by many different
- headers/Commits, and thus store it separately. This leaves us
- with three issues:
-
- 1. Given a validator hash, retrieve the validator set if previously stored
- 2. Given a block height, find the Commit with the highest height <= h
- 3. Given a FullCommit, store it quickly to satisfy 1 and 2
-
- Note that we do not worry about caching, as that can be achieved by
- pairing this with a MemStoreProvider and CacheProvider from certifiers
- */
- package files
-
- import (
- "encoding/hex"
- "fmt"
- "math"
- "os"
- "path/filepath"
- "sort"
-
- "github.com/pkg/errors"
-
- "github.com/tendermint/tendermint/lite"
- liteErr "github.com/tendermint/tendermint/lite/errors"
- )
-
- // nolint
- const (
- Ext = ".tsd"
- ValDir = "validators"
- CheckDir = "checkpoints"
- dirPerm = os.FileMode(0755)
- filePerm = os.FileMode(0644)
- )
-
- type provider struct {
- valDir string
- checkDir string
- }
-
- // NewProvider creates the parent dir and subdirs
- // for validators and checkpoints as needed
- func NewProvider(dir string) lite.Provider {
- valDir := filepath.Join(dir, ValDir)
- checkDir := filepath.Join(dir, CheckDir)
- for _, d := range []string{valDir, checkDir} {
- err := os.MkdirAll(d, dirPerm)
- if err != nil {
- panic(err)
- }
- }
- return &provider{valDir: valDir, checkDir: checkDir}
- }
-
- func (p *provider) encodeHash(hash []byte) string {
- return hex.EncodeToString(hash) + Ext
- }
-
- func (p *provider) encodeHeight(h int) string {
- // pad up to 10^12 for height...
- return fmt.Sprintf("%012d%s", h, Ext)
- }
-
- // StoreCommit saves a full commit after it has been verified.
- func (p *provider) StoreCommit(fc lite.FullCommit) error {
- // make sure the fc is self-consistent before saving
- err := fc.ValidateBasic(fc.Commit.Header.ChainID)
- if err != nil {
- return err
- }
-
- paths := []string{
- filepath.Join(p.checkDir, p.encodeHeight(fc.Height())),
- filepath.Join(p.valDir, p.encodeHash(fc.Header.ValidatorsHash)),
- }
- for _, path := range paths {
- err := SaveFullCommit(fc, path)
- // unknown error in creating or writing immediately breaks
- if err != nil {
- return err
- }
- }
- return nil
- }
-
- // GetByHeight returns the closest commit with height <= h.
- func (p *provider) GetByHeight(h int) (lite.FullCommit, error) {
- // first we look for exact match, then search...
- path := filepath.Join(p.checkDir, p.encodeHeight(h))
- fc, err := LoadFullCommit(path)
- if liteErr.IsCommitNotFoundErr(err) {
- path, err = p.searchForHeight(h)
- if err == nil {
- fc, err = LoadFullCommit(path)
- }
- }
- return fc, err
- }
-
- // LatestCommit returns the newest commit stored.
- func (p *provider) LatestCommit() (fc lite.FullCommit, err error) {
- // Note to future: please update by 2077 to avoid rollover
- return p.GetByHeight(math.MaxInt32 - 1)
- }
-
- // search for height, looks for a file with highest height < h
- // return certifiers.ErrCommitNotFound() if not there...
- func (p *provider) searchForHeight(h int) (string, error) {
- d, err := os.Open(p.checkDir)
- if err != nil {
- return "", errors.WithStack(err)
- }
- files, err := d.Readdirnames(0)
-
- d.Close()
- if err != nil {
- return "", errors.WithStack(err)
- }
-
- desired := p.encodeHeight(h)
- sort.Strings(files)
- i := sort.SearchStrings(files, desired)
- if i == 0 {
- return "", liteErr.ErrCommitNotFound()
- }
- found := files[i-1]
- path := filepath.Join(p.checkDir, found)
- return path, errors.WithStack(err)
- }
-
- // GetByHash returns a commit exactly matching this validator hash.
- func (p *provider) GetByHash(hash []byte) (lite.FullCommit, error) {
- path := filepath.Join(p.valDir, p.encodeHash(hash))
- return LoadFullCommit(path)
- }
|