Browse Source

Merge branch 'master' into erik/curve25519-x25519

pull/4449/head
Erik Grinaker 4 years ago
committed by GitHub
parent
commit
abb96cc227
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 111 additions and 110 deletions
  1. +107
    -106
      lite2/client.go
  2. +4
    -4
      lite2/client_test.go

+ 107
- 106
lite2/client.go View File

@ -121,9 +121,9 @@ type Client struct {
// Where trusted headers are stored.
trustedStore store.Store
// Highest trusted header from the store (height=H).
trustedHeader *types.SignedHeader
latestTrustedHeader *types.SignedHeader
// Highest next validator set from the store (height=H+1).
trustedNextVals *types.ValidatorSet
latestTrustedNextVals *types.ValidatorSet
// See UpdatePeriod option
updatePeriod time.Duration
@ -164,13 +164,13 @@ func NewClient(
return nil, err
}
if c.trustedHeader != nil {
if c.latestTrustedHeader != nil {
if err := c.checkTrustedHeaderUsingOptions(trustOptions); err != nil {
return nil, err
}
}
if c.trustedHeader == nil || c.trustedHeader.Height < trustOptions.Height {
if c.latestTrustedHeader == nil || c.latestTrustedHeader.Height < trustOptions.Height {
if err := c.initializeWithTrustOptions(trustOptions); err != nil {
return nil, err
}
@ -253,8 +253,8 @@ func (c *Client) restoreTrustedHeaderAndNextVals() error {
return errors.Wrap(err, "can't get last trusted next validators")
}
c.trustedHeader = trustedHeader
c.trustedNextVals = trustedNextVals
c.latestTrustedHeader = trustedHeader
c.latestTrustedNextVals = trustedNextVals
c.logger.Debug("Restored trusted header and next vals", lastHeight)
}
@ -283,24 +283,24 @@ func (c *Client) restoreTrustedHeaderAndNextVals() error {
func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error {
var primaryHash []byte
switch {
case options.Height > c.trustedHeader.Height:
h, err := c.signedHeaderFromPrimary(c.trustedHeader.Height)
case options.Height > c.latestTrustedHeader.Height:
h, err := c.signedHeaderFromPrimary(c.latestTrustedHeader.Height)
if err != nil {
return err
}
primaryHash = h.Hash()
case options.Height == c.trustedHeader.Height:
case options.Height == c.latestTrustedHeader.Height:
primaryHash = options.Hash
case options.Height < c.trustedHeader.Height:
case options.Height < c.latestTrustedHeader.Height:
c.logger.Info("Client initialized with old header (trusted is more recent)",
"old", options.Height,
"trustedHeight", c.trustedHeader.Height,
"trustedHash", hash2str(c.trustedHeader.Hash()))
"trustedHeight", c.latestTrustedHeader.Height,
"trustedHash", hash2str(c.latestTrustedHeader.Hash()))
action := fmt.Sprintf(
"Rollback to %d (%X)? Note this will remove newer headers up to %d (%X)",
options.Height, options.Hash,
c.trustedHeader.Height, c.trustedHeader.Hash())
c.latestTrustedHeader.Height, c.latestTrustedHeader.Hash())
if c.confirmationFn(action) {
// remove all the headers (options.Height, trustedHeader.Height]
c.cleanup(options.Height + 1)
@ -314,13 +314,13 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error {
primaryHash = options.Hash
}
if !bytes.Equal(primaryHash, c.trustedHeader.Hash()) {
if !bytes.Equal(primaryHash, c.latestTrustedHeader.Hash()) {
c.logger.Info("Prev. trusted header's hash (h1) doesn't match hash from primary provider (h2)",
"h1", c.trustedHeader.Hash(), "h1", primaryHash)
"h1", hash2str(c.latestTrustedHeader.Hash()), "h2", hash2str(primaryHash))
action := fmt.Sprintf(
"Prev. trusted header's hash %X doesn't match hash %X from primary provider. Remove all the stored headers?",
c.trustedHeader.Hash(), primaryHash)
c.latestTrustedHeader.Hash(), primaryHash)
if c.confirmationFn(action) {
err := c.Cleanup()
if err != nil {
@ -449,14 +449,7 @@ func (c *Client) TrustedHeader(height int64, now time.Time) (*types.SignedHeader
// 2) Get header from store.
h, err := c.trustedStore.SignedHeader(height)
switch {
case errors.Is(err, store.ErrSignedHeaderNotFound):
// 2.1) If not found, try to fetch header from primary.
h, err = c.fetchMissingTrustedHeader(height, now)
if err != nil {
return nil, err
}
case err != nil:
if err != nil {
return nil, err
}
@ -523,15 +516,25 @@ func (c *Client) ChainID() string {
return c.chainID
}
// VerifyHeaderAtHeight fetches the header and validators at the given height
// and calls VerifyHeader.
// VerifyHeaderAtHeight fetches header and validators at the given height
// and calls VerifyHeader. It returns header immediately if such exists in
// trustedStore (no verification is needed).
//
// If the trusted header is more recent than one here, an error is returned.
// If the header is not found by the primary provider,
// provider.ErrSignedHeaderNotFound error is returned.
// It returns provider.ErrSignedHeaderNotFound if header is not found by
// primary.
// It returns ErrOldHeaderExpired if header expired.
func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error) {
if c.trustedHeader.Height >= height {
return nil, errors.Errorf("header at more recent height #%d exists", c.trustedHeader.Height)
if height <= 0 {
return nil, errors.New("negative or zero height")
}
h, err := c.TrustedHeader(height, now)
switch err.(type) {
case nil: // Return already trusted header
c.logger.Info("Header has already been verified", "height", height, "hash", hash2str(h.Hash()))
return h, nil
case ErrOldHeaderExpired:
return nil, err
}
// Request the header and the vals.
@ -540,10 +543,12 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe
return nil, err
}
return newHeader, c.VerifyHeader(newHeader, newVals, now)
return newHeader, c.verifyHeader(newHeader, newVals, now)
}
// VerifyHeader verifies new header against the trusted state.
// VerifyHeader verifies new header against the trusted state. It returns
// immediately if newHeader exists in trustedStore (no verification is
// needed).
//
// SequentialVerification: verifies that 2/3 of the trusted validator set has
// signed the new header. If the headers are not adjacent, **all** intermediate
@ -555,7 +560,7 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe
// intermediate headers will be requested. See the specification for details.
// https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md
//
// If the trusted header is more recent than one here, an error is returned.
// It returns ErrOldHeaderExpired if newHeader expired.
//
// If, at any moment, SignedHeader or ValidatorSet are not found by the primary
// provider, provider.ErrSignedHeaderNotFound /
@ -565,21 +570,49 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe
// validator set at height newHeader.Height+1 (i.e.
// newHeader.NextValidatorsHash).
func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error {
h, err := c.TrustedHeader(newHeader.Height, now)
switch err.(type) {
case nil: // Return already trusted header
// Make sure it's the same header.
if !bytes.Equal(h.Hash(), newHeader.Hash()) {
return errors.Errorf("existing trusted header %X does not match newHeader %X", h.Hash(), newHeader.Hash())
}
c.logger.Info("Header has already been verified",
"height", newHeader.Height, "hash", hash2str(newHeader.Hash()))
return nil
case ErrOldHeaderExpired:
return err
}
return c.verifyHeader(newHeader, newVals, now)
}
func (c *Client) verifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error {
c.logger.Info("VerifyHeader", "height", newHeader.Height, "hash", hash2str(newHeader.Hash()),
"vals", hash2str(newVals.Hash()))
if c.trustedHeader.Height >= newHeader.Height {
return errors.Errorf("header at more recent height #%d exists", c.trustedHeader.Height)
}
var err error
switch c.verificationMode {
case sequential:
err = c.sequence(c.trustedHeader, c.trustedNextVals, newHeader, newVals, now)
case skipping:
err = c.bisection(c.trustedHeader, c.trustedNextVals, newHeader, newVals, now)
default:
panic(fmt.Sprintf("Unknown verification mode: %b", c.verificationMode))
// 1) If going forward, perform either bisection or sequential verification
if newHeader.Height >= c.latestTrustedHeader.Height {
switch c.verificationMode {
case sequential:
err = c.sequence(c.latestTrustedHeader, c.latestTrustedNextVals, newHeader, newVals, now)
case skipping:
err = c.bisection(c.latestTrustedHeader, c.latestTrustedNextVals, newHeader, newVals, now)
default:
panic(fmt.Sprintf("Unknown verification mode: %b", c.verificationMode))
}
} else {
// 2) Otherwise, perform backwards verification
// Find the closest trusted header after newHeader.Height
var closestHeader *types.SignedHeader
closestHeader, err = c.trustedStore.SignedHeaderAfter(newHeader.Height)
if err != nil {
return errors.Wrapf(err, "can't get signed header after height %d", newHeader.Height)
}
err = c.backwards(closestHeader, newHeader, now)
}
if err != nil {
c.logger.Error("Can't verify", "err", err)
@ -596,6 +629,7 @@ func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.Vali
if err != nil {
return err
}
return c.updateTrustedHeaderAndNextVals(newHeader, nextVals)
}
@ -652,8 +686,8 @@ func (c *Client) cleanup(stopHeight int64) error {
}
}
c.trustedHeader = nil
c.trustedNextVals = nil
c.latestTrustedHeader = nil
c.latestTrustedNextVals = nil
err = c.restoreTrustedHeaderAndNextVals()
if err != nil {
return err
@ -682,8 +716,8 @@ func (c *Client) sequence(
}
c.logger.Debug("Verify newHeader against trustedHeader",
"trustedHeight", c.trustedHeader.Height,
"trustedHash", hash2str(c.trustedHeader.Hash()),
"trustedHeight", c.latestTrustedHeader.Height,
"trustedHash", hash2str(c.latestTrustedHeader.Hash()),
"newHeight", interimHeader.Height,
"newHash", hash2str(interimHeader.Hash()))
err = VerifyAdjacent(c.chainID, trustedHeader, interimHeader, trustedNextVals,
@ -709,7 +743,7 @@ func (c *Client) sequence(
}
// 2) Verify the new header.
return VerifyAdjacent(c.chainID, c.trustedHeader, newHeader, newVals, c.trustingPeriod, now)
return VerifyAdjacent(c.chainID, c.latestTrustedHeader, newHeader, newVals, c.trustingPeriod, now)
}
// see VerifyHeader
@ -779,8 +813,8 @@ func (c *Client) updateTrustedHeaderAndNextVals(h *types.SignedHeader, nextVals
return errors.Wrap(err, "failed to save trusted header")
}
c.trustedHeader = h
c.trustedNextVals = nextVals
c.latestTrustedHeader = h
c.latestTrustedNextVals = nextVals
return nil
}
@ -799,77 +833,44 @@ func (c *Client) fetchHeaderAndValsAtHeight(height int64) (*types.SignedHeader,
return h, vals, nil
}
// fetchMissingTrustedHeader finds the closest height after the
// requested height and does backwards verification.
func (c *Client) fetchMissingTrustedHeader(height int64, now time.Time) (*types.SignedHeader, error) {
c.logger.Info("Fetching missing header", "height", height)
closestHeader, err := c.trustedStore.SignedHeaderAfter(height)
if err != nil {
return nil, errors.Wrapf(err, "can't get signed header after %d", height)
}
// Perform backwards verification from closestHeader to header at the given
// height.
h, err := c.backwards(height, closestHeader, now)
if err != nil {
return nil, err
}
// Fetch next validator set from primary and persist it.
nextVals, err := c.validatorSetFromPrimary(height + 1)
if err != nil {
return nil, errors.Wrapf(err, "failed to obtain the vals #%d", height)
}
if !bytes.Equal(h.NextValidatorsHash, nextVals.Hash()) {
return nil, errors.Errorf("expected next validator's hash %X, but got %X",
h.NextValidatorsHash, nextVals.Hash())
}
if err := c.trustedStore.SaveSignedHeaderAndNextValidatorSet(h, nextVals); err != nil {
return nil, errors.Wrap(err, "failed to save trusted header")
}
return h, nil
}
// Backwards verification (see VerifyHeaderBackwards func in the spec)
func (c *Client) backwards(toHeight int64, fromHeader *types.SignedHeader, now time.Time) (*types.SignedHeader, error) {
func (c *Client) backwards(trustedHeader *types.SignedHeader, newHeader *types.SignedHeader,
now time.Time) error {
var (
trustedHeader = fromHeader
untrustedHeader *types.SignedHeader
err error
interimHeader *types.SignedHeader
err error
)
for i := trustedHeader.Height - 1; i >= toHeight; i-- {
untrustedHeader, err = c.signedHeaderFromPrimary(i)
for trustedHeader.Height > newHeader.Height {
interimHeader, err = c.signedHeaderFromPrimary(trustedHeader.Height - 1)
if err != nil {
return nil, errors.Wrapf(err, "failed to obtain the header #%d", i)
return errors.Wrapf(err, "failed to obtain the header at height #%d", trustedHeader.Height-1)
}
if err := untrustedHeader.ValidateBasic(c.chainID); err != nil {
return nil, errors.Wrap(err, "untrustedHeader.ValidateBasic failed")
if err := interimHeader.ValidateBasic(c.chainID); err != nil {
return errors.Wrap(err, "untrustedHeader.ValidateBasic failed")
}
if !untrustedHeader.Time.Before(trustedHeader.Time) {
return nil, errors.Errorf("expected older header time %v to be before newer header time %v",
untrustedHeader.Time,
if !interimHeader.Time.Before(trustedHeader.Time) {
return errors.Errorf("expected older header time %v to be before newer header time %v",
interimHeader.Time,
trustedHeader.Time)
}
if HeaderExpired(untrustedHeader, c.trustingPeriod, now) {
return nil, ErrOldHeaderExpired{untrustedHeader.Time.Add(c.trustingPeriod), now}
if HeaderExpired(interimHeader, c.trustingPeriod, now) {
return ErrOldHeaderExpired{interimHeader.Time.Add(c.trustingPeriod), now}
}
if !bytes.Equal(untrustedHeader.Hash(), trustedHeader.LastBlockID.Hash) {
return nil, errors.Errorf("older header hash %X does not match trusted header's last block %X",
untrustedHeader.Hash(),
if !bytes.Equal(interimHeader.Hash(), trustedHeader.LastBlockID.Hash) {
return errors.Errorf("older header hash %X does not match trusted header's last block %X",
interimHeader.Hash(),
trustedHeader.LastBlockID.Hash)
}
trustedHeader = untrustedHeader
trustedHeader = interimHeader
}
return trustedHeader, nil
return nil
}
// compare header with all witnesses provided.
@ -899,7 +900,7 @@ func (c *Client) compareNewHeaderWithWitnesses(h *types.SignedHeader) error {
}
if !bytes.Equal(h.Hash(), altH.Hash()) {
if err = c.trustedNextVals.VerifyCommitTrusting(c.chainID, altH.Commit.BlockID,
if err = c.latestTrustedNextVals.VerifyCommitTrusting(c.chainID, altH.Commit.BlockID,
altH.Height, altH.Commit, c.trustLevel); err != nil {
c.logger.Error("Witness sent us incorrect header", "err", err, "witness", witness)
witnessesToRemove = append(witnessesToRemove, i)
@ -989,7 +990,7 @@ func (c *Client) RemoveNoLongerTrustedHeaders(now time.Time) {
// 3) Remove all headers that are outside of the trusting period.
//
// NOTE: even the latest header can be removed. it's okay because
// c.trustedHeader will retain it in memory so other funcs like VerifyHeader
// c.latestTrustedHeader will retain it in memory so other funcs like VerifyHeader
// don't crash.
for height := oldestHeight; height <= latestHeight; height++ {
h, err := c.trustedStore.SignedHeader(height)


+ 4
- 4
lite2/client_test.go View File

@ -732,7 +732,7 @@ func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) {
assert.Equal(t, 1, len(c.Witnesses()))
}
func TestClient_TrustedHeaderFetchesMissingHeader(t *testing.T) {
func TestClient_BackwardsVerification(t *testing.T) {
c, err := NewClient(
chainID,
TrustOptions{
@ -752,16 +752,16 @@ func TestClient_TrustedHeaderFetchesMissingHeader(t *testing.T) {
defer c.Stop()
// 1) header is missing => expect no error
h, err := c.TrustedHeader(2, bTime.Add(1*time.Hour).Add(1*time.Second))
h, err := c.VerifyHeaderAtHeight(2, bTime.Add(1*time.Hour).Add(1*time.Second))
require.NoError(t, err)
if assert.NotNil(t, h) {
assert.EqualValues(t, 2, h.Height)
}
// 2) header is missing, but it's expired => expect error
h, err = c.TrustedHeader(1, bTime.Add(1*time.Hour).Add(1*time.Second))
h, err = c.VerifyHeaderAtHeight(1, bTime.Add(1*time.Hour).Add(1*time.Second))
assert.Error(t, err)
assert.Nil(t, h)
assert.NotNil(t, h)
}
func TestClient_NewClientFromTrustedStore(t *testing.T) {


Loading…
Cancel
Save