Copy certifiers from light-client

This commit is contained in:
Ethan Frey
2017-10-25 16:13:04 +02:00
parent 775e100d2c
commit b4fd6e876e
22 changed files with 2258 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
package files
import (
"encoding/json"
"os"
"github.com/pkg/errors"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/tendermint/certifiers"
certerr "github.com/tendermint/tendermint/certifiers/errors"
)
const (
// MaxFullCommitSize is the maximum number of bytes we will
// read in for a full commit to avoid excessive allocations
// in the deserializer
MaxFullCommitSize = 1024 * 1024
)
// SaveFullCommit exports the seed in binary / go-wire style
func SaveFullCommit(fc certifiers.FullCommit, path string) error {
f, err := os.Create(path)
if err != nil {
return errors.WithStack(err)
}
defer f.Close()
var n int
wire.WriteBinary(fc, f, &n, &err)
return errors.WithStack(err)
}
// SaveFullCommitJSON exports the seed in a json format
func SaveFullCommitJSON(fc certifiers.FullCommit, path string) error {
f, err := os.Create(path)
if err != nil {
return errors.WithStack(err)
}
defer f.Close()
stream := json.NewEncoder(f)
err = stream.Encode(fc)
return errors.WithStack(err)
}
func LoadFullCommit(path string) (certifiers.FullCommit, error) {
var fc certifiers.FullCommit
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return fc, certerr.ErrCommitNotFound()
}
return fc, errors.WithStack(err)
}
defer f.Close()
var n int
wire.ReadBinaryPtr(&fc, f, MaxFullCommitSize, &n, &err)
return fc, errors.WithStack(err)
}
func LoadFullCommitJSON(path string) (certifiers.FullCommit, error) {
var fc certifiers.FullCommit
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return fc, certerr.ErrCommitNotFound()
}
return fc, errors.WithStack(err)
}
defer f.Close()
stream := json.NewDecoder(f)
err = stream.Decode(&fc)
return fc, errors.WithStack(err)
}
+66
View File
@@ -0,0 +1,66 @@
package files
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tendermint/certifiers"
)
func tmpFile() string {
suffix := cmn.RandStr(16)
return filepath.Join(os.TempDir(), "fc-test-"+suffix)
}
func TestSerializeFullCommits(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// some constants
appHash := []byte("some crazy thing")
chainID := "ser-ial"
h := 25
// build a fc
keys := certifiers.GenValKeys(5)
vals := keys.ToValidators(10, 0)
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, 5)
require.Equal(h, fc.Height())
require.Equal(vals.Hash(), fc.ValidatorsHash())
// try read/write with json
jfile := tmpFile()
defer os.Remove(jfile)
jseed, err := LoadFullCommitJSON(jfile)
assert.NotNil(err)
err = SaveFullCommitJSON(fc, jfile)
require.Nil(err)
jseed, err = LoadFullCommitJSON(jfile)
assert.Nil(err, "%+v", err)
assert.Equal(h, jseed.Height())
assert.Equal(vals.Hash(), jseed.ValidatorsHash())
// try read/write with binary
bfile := tmpFile()
defer os.Remove(bfile)
bseed, err := LoadFullCommit(bfile)
assert.NotNil(err)
err = SaveFullCommit(fc, bfile)
require.Nil(err)
bseed, err = LoadFullCommit(bfile)
assert.Nil(err, "%+v", err)
assert.Equal(h, bseed.Height())
assert.Equal(vals.Hash(), bseed.ValidatorsHash())
// make sure they don't read the other format (different)
_, err = LoadFullCommit(jfile)
assert.NotNil(err)
_, err = LoadFullCommitJSON(bfile)
assert.NotNil(err)
}
+134
View File
@@ -0,0 +1,134 @@
/*
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/certifiers"
certerr "github.com/tendermint/tendermint/certifiers/errors"
)
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) certifiers.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)
}
func (p *provider) StoreCommit(fc certifiers.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
}
func (p *provider) GetByHeight(h int) (certifiers.FullCommit, error) {
// first we look for exact match, then search...
path := filepath.Join(p.checkDir, p.encodeHeight(h))
fc, err := LoadFullCommit(path)
if certerr.IsCommitNotFoundErr(err) {
path, err = p.searchForHeight(h)
if err == nil {
fc, err = LoadFullCommit(path)
}
}
return fc, err
}
func (p *provider) LatestCommit() (fc certifiers.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 "", certerr.ErrCommitNotFound()
}
found := files[i-1]
path := filepath.Join(p.checkDir, found)
return path, errors.WithStack(err)
}
func (p *provider) GetByHash(hash []byte) (certifiers.FullCommit, error) {
path := filepath.Join(p.valDir, p.encodeHash(hash))
return LoadFullCommit(path)
}
+96
View File
@@ -0,0 +1,96 @@
package files_test
import (
"bytes"
"errors"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/certifiers"
certerr "github.com/tendermint/tendermint/certifiers/errors"
"github.com/tendermint/tendermint/certifiers/files"
)
func checkEqual(stored, loaded certifiers.FullCommit, chainID string) error {
err := loaded.ValidateBasic(chainID)
if err != nil {
return err
}
if !bytes.Equal(stored.ValidatorsHash(), loaded.ValidatorsHash()) {
return errors.New("Different block hashes")
}
return nil
}
func TestFileProvider(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dir, err := ioutil.TempDir("", "fileprovider-test")
assert.Nil(err)
defer os.RemoveAll(dir)
p := files.NewProvider(dir)
chainID := "test-files"
appHash := []byte("some-data")
keys := certifiers.GenValKeys(5)
count := 10
// make a bunch of seeds...
seeds := make([]certifiers.FullCommit, count)
for i := 0; i < count; i++ {
// two seeds for each validator, to check how we handle dups
// (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ...
vals := keys.ToValidators(10, int64(count/2))
h := 20 + 10*i
check := keys.GenCommit(chainID, h, nil, vals, appHash, 0, 5)
seeds[i] = certifiers.NewFullCommit(check, vals)
}
// check provider is empty
seed, err := p.GetByHeight(20)
require.NotNil(err)
assert.True(certerr.IsCommitNotFoundErr(err))
seed, err = p.GetByHash(seeds[3].ValidatorsHash())
require.NotNil(err)
assert.True(certerr.IsCommitNotFoundErr(err))
// now add them all to the provider
for _, s := range seeds {
err = p.StoreCommit(s)
require.Nil(err)
// and make sure we can get it back
s2, err := p.GetByHash(s.ValidatorsHash())
assert.Nil(err)
err = checkEqual(s, s2, chainID)
assert.Nil(err)
// by height as well
s2, err = p.GetByHeight(s.Height())
err = checkEqual(s, s2, chainID)
assert.Nil(err)
}
// make sure we get the last hash if we overstep
seed, err = p.GetByHeight(5000)
if assert.Nil(err, "%+v", err) {
assert.Equal(seeds[count-1].Height(), seed.Height())
err = checkEqual(seeds[count-1], seed, chainID)
assert.Nil(err)
}
// and middle ones as well
seed, err = p.GetByHeight(47)
if assert.Nil(err, "%+v", err) {
// we only step by 10, so 40 must be the one below this
assert.Equal(40, seed.Height())
}
// and proper error for too low
_, err = p.GetByHeight(5)
assert.NotNil(err)
assert.True(certerr.IsCommitNotFoundErr(err))
}