tendermint/certifiers/provider.go

126 lines
3.5 KiB
Go
Raw Normal View History

2017-10-24 03:34:36 -07:00
package certifiers
import (
certerr "github.com/tendermint/tendermint/certifiers/errors"
)
// Provider is used to get more validators by other means
//
// Examples: MemProvider, files.Provider, client.Provider....
type Provider interface {
// StoreCommit saves a FullCommit after we have verified it,
// so we can query for it later. Important for updating our
// store of trusted commits
StoreCommit(fc FullCommit) error
// GetByHeight returns the closest commit with height <= h
GetByHeight(h int) (FullCommit, error)
// GetByHash returns a commit exactly matching this validator hash
GetByHash(hash []byte) (FullCommit, error)
// LatestCommit returns the newest commit stored
LatestCommit() (FullCommit, error)
}
// cacheProvider allows you to place one or more caches in front of a source
// Provider. It runs through them in order until a match is found.
// So you can keep a local cache, and check with the network if
// no data is there.
type cacheProvider struct {
Providers []Provider
}
func NewCacheProvider(providers ...Provider) Provider {
return cacheProvider{
Providers: providers,
}
}
// StoreCommit tries to add the seed to all providers.
//
// Aborts on first error it encounters (closest provider)
func (c cacheProvider) StoreCommit(fc FullCommit) (err error) {
for _, p := range c.Providers {
err = p.StoreCommit(fc)
if err != nil {
break
}
}
return err
}
/*
GetByHeight should return the closest possible match from all providers.
The Cache is usually organized in order from cheapest call (memory)
to most expensive calls (disk/network). However, since GetByHeight returns
a FullCommit at h' <= h, if the memory has a seed at h-10, but the network would
give us the exact match, a naive "stop at first non-error" would hide
the actual desired results.
Thus, we query each provider in order until we find an exact match
or we finished querying them all. If at least one returned a non-error,
then this returns the best match (minimum h-h').
*/
func (c cacheProvider) GetByHeight(h int) (fc FullCommit, err error) {
for _, p := range c.Providers {
var tfc FullCommit
tfc, err = p.GetByHeight(h)
if err == nil {
if tfc.Height() > fc.Height() {
fc = tfc
}
if tfc.Height() == h {
break
}
}
}
// even if the last one had an error, if any was a match, this is good
if fc.Height() > 0 {
err = nil
}
return fc, err
}
func (c cacheProvider) GetByHash(hash []byte) (fc FullCommit, err error) {
for _, p := range c.Providers {
fc, err = p.GetByHash(hash)
if err == nil {
break
}
}
return fc, err
}
func (c cacheProvider) LatestCommit() (fc FullCommit, err error) {
for _, p := range c.Providers {
var tfc FullCommit
tfc, err = p.LatestCommit()
if err == nil && tfc.Height() > fc.Height() {
fc = tfc
}
}
// even if the last one had an error, if any was a match, this is good
if fc.Height() > 0 {
err = nil
}
return fc, err
}
// missingProvider doens't store anything, always a miss
// Designed as a mock for testing
type missingProvider struct{}
func NewMissingProvider() Provider {
return missingProvider{}
}
func (missingProvider) StoreCommit(_ FullCommit) error { return nil }
func (missingProvider) GetByHeight(_ int) (FullCommit, error) {
return FullCommit{}, certerr.ErrCommitNotFound()
}
func (missingProvider) GetByHash(_ []byte) (FullCommit, error) {
return FullCommit{}, certerr.ErrCommitNotFound()
}
func (missingProvider) LatestCommit() (FullCommit, error) {
return FullCommit{}, certerr.ErrCommitNotFound()
}