package lite
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
lerr "github.com/tendermint/tendermint/lite/errors"
|
|
"github.com/tendermint/tendermint/types"
|
|
log "github.com/tendermint/tmlibs/log"
|
|
)
|
|
|
|
var _ Certifier = (*InquiringCertifier)(nil)
|
|
|
|
// InquiringCertifier implements an auto-updating certifier. It uses a
|
|
// "source" provider to obtain the needed FullCommits to securely sync with
|
|
// validator set changes. It stores properly validated data on the
|
|
// "trusted" local system.
|
|
type InquiringCertifier struct {
|
|
logger log.Logger
|
|
chainID string
|
|
// These are only properly validated data, from local system.
|
|
trusted PersistentProvider
|
|
// This is a source of new info, like a node rpc, or other import method.
|
|
source Provider
|
|
}
|
|
|
|
// NewInquiringCertifier returns a new InquiringCertifier. It uses the
|
|
// trusted provider to store validated data and the source provider to
|
|
// obtain missing data (e.g. FullCommits).
|
|
//
|
|
// The trusted provider should a CacheProvider, MemProvider or
|
|
// files.Provider. The source provider should be a client.HTTPProvider.
|
|
func NewInquiringCertifier(chainID string, trusted PersistentProvider, source Provider) *InquiringCertifier {
|
|
return &InquiringCertifier{
|
|
logger: log.NewNopLogger(),
|
|
chainID: chainID,
|
|
trusted: trusted,
|
|
source: source,
|
|
}
|
|
}
|
|
|
|
func (ic *InquiringCertifier) SetLogger(logger log.Logger) {
|
|
logger = logger.With("module", "lite")
|
|
ic.logger = logger
|
|
ic.trusted.SetLogger(logger)
|
|
ic.source.SetLogger(logger)
|
|
}
|
|
|
|
// Implements Certifier.
|
|
func (ic *InquiringCertifier) ChainID() string {
|
|
return ic.chainID
|
|
}
|
|
|
|
// Implements Certifier.
|
|
//
|
|
// If the validators have changed since the last know time, it looks to
|
|
// ic.trusted and ic.source to prove the new validators. On success, it will
|
|
// try to store the SignedHeader in ic.trusted if the next
|
|
// validator can be sourced.
|
|
func (ic *InquiringCertifier) Certify(shdr types.SignedHeader) error {
|
|
|
|
// Get the latest known full commit <= h-1 from our trusted providers.
|
|
// The full commit at h-1 contains the valset to sign for h.
|
|
h := shdr.Height - 1
|
|
tfc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if tfc.Height() == h {
|
|
// Return error if valset doesn't match.
|
|
if !bytes.Equal(
|
|
tfc.NextValidators.Hash(),
|
|
shdr.Header.ValidatorsHash) {
|
|
return lerr.ErrUnexpectedValidators(
|
|
tfc.NextValidators.Hash(),
|
|
shdr.Header.ValidatorsHash)
|
|
}
|
|
} else {
|
|
// If valset doesn't match...
|
|
if !bytes.Equal(tfc.NextValidators.Hash(),
|
|
shdr.Header.ValidatorsHash) {
|
|
// ... update.
|
|
tfc, err = ic.updateToHeight(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Return error if valset _still_ doesn't match.
|
|
if !bytes.Equal(tfc.NextValidators.Hash(),
|
|
shdr.Header.ValidatorsHash) {
|
|
return lerr.ErrUnexpectedValidators(
|
|
tfc.NextValidators.Hash(),
|
|
shdr.Header.ValidatorsHash)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Certify the signed header using the matching valset.
|
|
cert := NewBaseCertifier(ic.chainID, tfc.Height()+1, tfc.NextValidators)
|
|
err = cert.Certify(shdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get the next validator set.
|
|
nextValset, err := ic.source.ValidatorSet(ic.chainID, shdr.Height+1)
|
|
if lerr.IsErrMissingValidators(err) {
|
|
// Ignore this error.
|
|
return nil
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create filled FullCommit.
|
|
nfc := FullCommit{
|
|
SignedHeader: shdr,
|
|
Validators: tfc.NextValidators,
|
|
NextValidators: nextValset,
|
|
}
|
|
// Validate the full commit. This checks the cryptographic
|
|
// signatures of Commit against Validators.
|
|
if err := nfc.ValidateFull(ic.chainID); err != nil {
|
|
return err
|
|
}
|
|
// Trust it.
|
|
return ic.trusted.SaveFullCommit(nfc)
|
|
}
|
|
|
|
// verifyAndSave will verify if this is a valid source full commit given the
|
|
// best match trusted full commit, and if good, persist to ic.trusted.
|
|
// Returns ErrTooMuchChange when >2/3 of tfc did not sign sfc.
|
|
// Panics if tfc.Height() >= sfc.Height().
|
|
func (ic *InquiringCertifier) verifyAndSave(tfc, sfc FullCommit) error {
|
|
if tfc.Height() >= sfc.Height() {
|
|
panic("should not happen")
|
|
}
|
|
err := tfc.NextValidators.VerifyFutureCommit(
|
|
sfc.Validators,
|
|
ic.chainID, sfc.SignedHeader.Commit.BlockID,
|
|
sfc.SignedHeader.Height, sfc.SignedHeader.Commit,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ic.trusted.SaveFullCommit(sfc)
|
|
}
|
|
|
|
// updateToHeight will use divide-and-conquer to find a path to h.
|
|
// Returns nil error iff we successfully verify and persist a full commit
|
|
// for height h, using repeated applications of bisection if necessary.
|
|
//
|
|
// Returns ErrCommitNotFound if source provider doesn't have the commit for h.
|
|
func (ic *InquiringCertifier) updateToHeight(h int64) (FullCommit, error) {
|
|
|
|
// Fetch latest full commit from source.
|
|
sfc, err := ic.source.LatestFullCommit(ic.chainID, h, h)
|
|
if err != nil {
|
|
return FullCommit{}, err
|
|
}
|
|
|
|
// Validate the full commit. This checks the cryptographic
|
|
// signatures of Commit against Validators.
|
|
if err := sfc.ValidateFull(ic.chainID); err != nil {
|
|
return FullCommit{}, err
|
|
}
|
|
|
|
// If sfc.Height() != h, we can't do it.
|
|
if sfc.Height() != h {
|
|
return FullCommit{}, lerr.ErrCommitNotFound()
|
|
}
|
|
|
|
FOR_LOOP:
|
|
for {
|
|
// Fetch latest full commit from trusted.
|
|
tfc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
|
|
if err != nil {
|
|
return FullCommit{}, err
|
|
}
|
|
// We have nothing to do.
|
|
if tfc.Height() == h {
|
|
return tfc, nil
|
|
}
|
|
|
|
// Try to update to full commit with checks.
|
|
err = ic.verifyAndSave(tfc, sfc)
|
|
if err == nil {
|
|
// All good!
|
|
return sfc, nil
|
|
}
|
|
|
|
// Handle special case when err is ErrTooMuchChange.
|
|
if lerr.IsErrTooMuchChange(err) {
|
|
// Divide and conquer.
|
|
start, end := tfc.Height(), sfc.Height()
|
|
if !(start < end) {
|
|
panic("should not happen")
|
|
}
|
|
mid := (start + end) / 2
|
|
_, err = ic.updateToHeight(mid)
|
|
if err != nil {
|
|
return FullCommit{}, err
|
|
}
|
|
// If we made it to mid, we retry.
|
|
continue FOR_LOOP
|
|
}
|
|
return FullCommit{}, err
|
|
}
|
|
}
|
|
|
|
func (ic *InquiringCertifier) LastTrustedHeight() int64 {
|
|
fc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, 1<<63-1)
|
|
if err != nil {
|
|
panic("should not happen")
|
|
}
|
|
return fc.Height()
|
|
}
|