mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-07 13:55:17 +00:00
lite2: use bisection for some of backward verification (#4575)
Closes: #4537 Uses SignedHeaderBefore to find header before unverified header and then bisection to verify the header. Only when header is between first and last trusted header height else if before the first trusted header height then regular backwards verification is used.
This commit is contained in:
141
lite2/client.go
141
lite2/client.go
@@ -149,7 +149,7 @@ func NewClient(
|
||||
options ...Option) (*Client, error) {
|
||||
|
||||
if err := trustOptions.ValidateBasic(); err != nil {
|
||||
return nil, errors.Wrap(err, "invalid TrustOptions")
|
||||
return nil, fmt.Errorf("invalid TrustOptions: %w", err)
|
||||
}
|
||||
|
||||
c, err := NewClientFromTrustedStore(chainID, trustOptions.Period, primary, witnesses, trustedStore, options...)
|
||||
@@ -206,13 +206,13 @@ func NewClientFromTrustedStore(
|
||||
|
||||
// Validate the number of witnesses.
|
||||
if len(c.witnesses) < 1 {
|
||||
return nil, errors.New("expected at least one witness")
|
||||
return nil, errNoWitnesses{}
|
||||
}
|
||||
|
||||
// Verify witnesses are all on the same chain.
|
||||
for i, w := range witnesses {
|
||||
if w.ChainID() != chainID {
|
||||
return nil, errors.Errorf("witness #%d: %v is on another chain %s, expected %s",
|
||||
return nil, fmt.Errorf("witness #%d: %v is on another chain %s, expected %s",
|
||||
i, w, w.ChainID(), chainID)
|
||||
}
|
||||
}
|
||||
@@ -234,18 +234,18 @@ func NewClientFromTrustedStore(
|
||||
func (c *Client) restoreTrustedHeaderAndVals() error {
|
||||
lastHeight, err := c.trustedStore.LastSignedHeaderHeight()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't get last trusted header height")
|
||||
return fmt.Errorf("can't get last trusted header height: %w", err)
|
||||
}
|
||||
|
||||
if lastHeight > 0 {
|
||||
trustedHeader, err := c.trustedStore.SignedHeader(lastHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't get last trusted header")
|
||||
return fmt.Errorf("can't get last trusted header: %w", err)
|
||||
}
|
||||
|
||||
trustedVals, err := c.trustedStore.ValidatorSet(lastHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't get last trusted validators")
|
||||
return fmt.Errorf("can't get last trusted validators: %w", err)
|
||||
}
|
||||
|
||||
c.latestTrustedHeader = trustedHeader
|
||||
@@ -300,7 +300,7 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error {
|
||||
// remove all the headers (options.Height, trustedHeader.Height]
|
||||
err := c.cleanupAfter(options.Height)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cleanupAfter(%d)", options.Height)
|
||||
return fmt.Errorf("cleanupAfter(%d): %w", options.Height, err)
|
||||
}
|
||||
|
||||
c.logger.Info("Rolled back to older header (newer headers were removed)",
|
||||
@@ -322,7 +322,7 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error {
|
||||
if c.confirmationFn(action) {
|
||||
err := c.Cleanup()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to cleanup")
|
||||
return fmt.Errorf("failed to cleanup: %w", err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("refused to remove the stored headers despite hashes mismatch")
|
||||
@@ -350,7 +350,7 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
|
||||
}
|
||||
|
||||
if !bytes.Equal(h.Hash(), options.Hash) {
|
||||
return errors.Errorf("expected header's hash %X, but got %X", options.Hash, h.Hash())
|
||||
return fmt.Errorf("expected header's hash %X, but got %X", options.Hash, h.Hash())
|
||||
}
|
||||
|
||||
err = c.compareNewHeaderWithWitnesses(h)
|
||||
@@ -365,7 +365,7 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
|
||||
}
|
||||
|
||||
if !bytes.Equal(h.ValidatorsHash, vals.Hash()) {
|
||||
return errors.Errorf("expected header's validators (%X) to match those that were supplied (%X)",
|
||||
return fmt.Errorf("expected header's validators (%X) to match those that were supplied (%X)",
|
||||
h.ValidatorsHash,
|
||||
vals.Hash(),
|
||||
)
|
||||
@@ -374,7 +374,7 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
|
||||
// Ensure that +2/3 of validators signed correctly.
|
||||
err = vals.VerifyCommit(c.chainID, h.Commit.BlockID, h.Height, h.Commit)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid commit")
|
||||
return fmt.Errorf("invalid commit: %w", err)
|
||||
}
|
||||
|
||||
// 3) Persist both of them and continue.
|
||||
@@ -436,7 +436,7 @@ func (c *Client) TrustedValidatorSet(height int64) (valSet *types.ValidatorSet,
|
||||
func (c *Client) compareWithLatestHeight(height int64) (int64, error) {
|
||||
latestHeight, err := c.LastTrustedHeight()
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "can't get last trusted height")
|
||||
return 0, fmt.Errorf("can't get last trusted height: %w", err)
|
||||
}
|
||||
if latestHeight == -1 {
|
||||
return 0, errors.New("no headers exist")
|
||||
@@ -444,7 +444,7 @@ func (c *Client) compareWithLatestHeight(height int64) (int64, error) {
|
||||
|
||||
switch {
|
||||
case height > latestHeight:
|
||||
return 0, errors.Errorf("unverified header/valset requested (latest: %d)", latestHeight)
|
||||
return 0, fmt.Errorf("unverified header/valset requested (latest: %d)", latestHeight)
|
||||
case height == 0:
|
||||
return latestHeight, nil
|
||||
case height < 0:
|
||||
@@ -499,6 +499,11 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe
|
||||
// Intermediate headers are not saved to database.
|
||||
// https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md
|
||||
//
|
||||
// If the header, which is older than the currently trusted header, is
|
||||
// requested and the light client does not have it, VerifyHeader will perform:
|
||||
// a) bisection verification if nearest trusted header is found & not expired
|
||||
// b) backwards verification in all other cases
|
||||
//
|
||||
// It returns ErrOldHeaderExpired if the latest trusted header expired.
|
||||
//
|
||||
// If the primary provides an invalid header (ErrInvalidHeader), it is rejected
|
||||
@@ -517,7 +522,7 @@ func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.Vali
|
||||
if err == nil {
|
||||
// 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())
|
||||
return fmt.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()))
|
||||
@@ -533,7 +538,7 @@ func (c *Client) verifyHeader(newHeader *types.SignedHeader, newVals *types.Vali
|
||||
|
||||
var err error
|
||||
|
||||
// 1) If going forward, perform either bisection or sequential verification
|
||||
// 1) If going forward, perform either bisection or sequential verification.
|
||||
if newHeader.Height >= c.latestTrustedHeader.Height {
|
||||
switch c.verificationMode {
|
||||
case sequential:
|
||||
@@ -544,26 +549,55 @@ func (c *Client) verifyHeader(newHeader *types.SignedHeader, newVals *types.Vali
|
||||
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)
|
||||
// 2) If verifying before the first trusted header, perform backwards
|
||||
// verification.
|
||||
var (
|
||||
closestHeader *types.SignedHeader
|
||||
firstHeaderHeight int64
|
||||
)
|
||||
firstHeaderHeight, err = c.FirstTrustedHeight()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "can't get signed header after height %d", newHeader.Height)
|
||||
return fmt.Errorf("can't get first header height: %w", err)
|
||||
}
|
||||
if newHeader.Height < firstHeaderHeight {
|
||||
closestHeader, err = c.TrustedHeader(firstHeaderHeight)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get first signed header: %w", err)
|
||||
}
|
||||
if HeaderExpired(closestHeader, c.trustingPeriod, now) {
|
||||
closestHeader = c.latestTrustedHeader
|
||||
}
|
||||
err = c.backwards(closestHeader, newHeader, now)
|
||||
} else {
|
||||
// 3) OR if between trusted headers where the nearest has not expired,
|
||||
// perform bisection verification, else backwards.
|
||||
closestHeader, err = c.trustedStore.SignedHeaderBefore(newHeader.Height)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get signed header before height %d: %w", newHeader.Height, err)
|
||||
}
|
||||
var closestValidatorSet *types.ValidatorSet
|
||||
if c.verificationMode == sequential || HeaderExpired(closestHeader, c.trustingPeriod, now) {
|
||||
err = c.backwards(c.latestTrustedHeader, newHeader, now)
|
||||
} else {
|
||||
closestValidatorSet, _, err = c.TrustedValidatorSet(closestHeader.Height)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get validator set at height %d: %w", closestHeader.Height, err)
|
||||
}
|
||||
err = c.bisection(closestHeader, closestValidatorSet, newHeader, newVals, now)
|
||||
}
|
||||
}
|
||||
|
||||
err = c.backwards(closestHeader, newHeader, now)
|
||||
}
|
||||
if err != nil {
|
||||
c.logger.Error("Can't verify", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 4) Compare header with other witnesses
|
||||
if err := c.compareNewHeaderWithWitnesses(newHeader); err != nil {
|
||||
c.logger.Error("Error when comparing new header with witnesses", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 5) Once verified, save and return
|
||||
return c.updateTrustedHeaderAndVals(newHeader, newVals)
|
||||
}
|
||||
|
||||
@@ -590,7 +624,7 @@ func (c *Client) sequence(
|
||||
} else { // intermediate headers
|
||||
interimHeader, interimVals, err = c.fetchHeaderAndValsAtHeight(height)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to obtain the header #%d", height)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,10 +638,10 @@ func (c *Client) sequence(
|
||||
err = VerifyAdjacent(c.chainID, trustedHeader, interimHeader, interimVals,
|
||||
c.trustingPeriod, now)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "verify adjacent from #%d to #%d failed",
|
||||
trustedHeader.Height, interimHeader.Height)
|
||||
err = fmt.Errorf("verify adjacent from #%d to #%d failed: %w",
|
||||
trustedHeader.Height, interimHeader.Height, err)
|
||||
|
||||
switch errors.Cause(err).(type) {
|
||||
switch errors.Unwrap(err).(type) {
|
||||
case ErrInvalidHeader:
|
||||
c.logger.Error("primary sent invalid header -> replacing", "err", err)
|
||||
replaceErr := c.replacePrimaryProvider()
|
||||
@@ -696,15 +730,15 @@ func (c *Client) bisection(
|
||||
if replaceErr != nil {
|
||||
c.logger.Error("Can't replace primary", "err", replaceErr)
|
||||
// return original error
|
||||
return errors.Wrapf(err, "verify from #%d to #%d failed",
|
||||
trustedHeader.Height, headerCache[depth].sh.Height)
|
||||
return fmt.Errorf("verify non adjacent from #%d to #%d failed: %w",
|
||||
trustedHeader.Height, headerCache[depth].sh.Height, err)
|
||||
}
|
||||
// attempt to verify the header again
|
||||
continue
|
||||
|
||||
default:
|
||||
return errors.Wrapf(err, "verify from #%d to #%d failed",
|
||||
trustedHeader.Height, headerCache[depth].sh.Height)
|
||||
return fmt.Errorf("verify non adjacent from #%d to #%d failed: %w",
|
||||
trustedHeader.Height, headerCache[depth].sh.Height, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -762,14 +796,14 @@ func (c *Client) Cleanup() error {
|
||||
// cleanupAfter deletes all headers & validator sets after +height+. It also
|
||||
// resets latestTrustedHeader to the latest header.
|
||||
func (c *Client) cleanupAfter(height int64) error {
|
||||
nextHeight := height
|
||||
prevHeight := c.latestTrustedHeader.Height
|
||||
|
||||
for {
|
||||
h, err := c.trustedStore.SignedHeaderAfter(nextHeight)
|
||||
if err == store.ErrSignedHeaderNotFound {
|
||||
h, err := c.trustedStore.SignedHeaderBefore(prevHeight)
|
||||
if err == store.ErrSignedHeaderNotFound || (h != nil && h.Height <= height) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return errors.Wrapf(err, "failed to get header after %d", nextHeight)
|
||||
return fmt.Errorf("failed to get header before %d: %w", prevHeight, err)
|
||||
}
|
||||
|
||||
err = c.trustedStore.DeleteSignedHeaderAndValidatorSet(h.Height)
|
||||
@@ -778,7 +812,7 @@ func (c *Client) cleanupAfter(height int64) error {
|
||||
"height", h.Height)
|
||||
}
|
||||
|
||||
nextHeight = h.Height
|
||||
prevHeight = h.Height
|
||||
}
|
||||
|
||||
c.latestTrustedHeader = nil
|
||||
@@ -793,16 +827,16 @@ func (c *Client) cleanupAfter(height int64) error {
|
||||
|
||||
func (c *Client) updateTrustedHeaderAndVals(h *types.SignedHeader, vals *types.ValidatorSet) error {
|
||||
if !bytes.Equal(h.ValidatorsHash, vals.Hash()) {
|
||||
return errors.Errorf("expected validator's hash %X, but got %X", h.ValidatorsHash, vals.Hash())
|
||||
return fmt.Errorf("expected validator's hash %X, but got %X", h.ValidatorsHash, vals.Hash())
|
||||
}
|
||||
|
||||
if err := c.trustedStore.SaveSignedHeaderAndValidatorSet(h, vals); err != nil {
|
||||
return errors.Wrap(err, "failed to save trusted header")
|
||||
return fmt.Errorf("failed to save trusted header: %w", err)
|
||||
}
|
||||
|
||||
if c.pruningSize > 0 {
|
||||
if err := c.trustedStore.Prune(c.pruningSize); err != nil {
|
||||
return errors.Wrap(err, "prune")
|
||||
return fmt.Errorf("prune: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,11 +853,11 @@ func (c *Client) updateTrustedHeaderAndVals(h *types.SignedHeader, vals *types.V
|
||||
func (c *Client) fetchHeaderAndValsAtHeight(height int64) (*types.SignedHeader, *types.ValidatorSet, error) {
|
||||
h, err := c.signedHeaderFromPrimary(height)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to obtain the header #%d", height)
|
||||
return nil, nil, fmt.Errorf("failed to obtain the header #%d: %w", height, err)
|
||||
}
|
||||
vals, err := c.validatorSetFromPrimary(height)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to obtain the vals #%d", height)
|
||||
return nil, nil, fmt.Errorf("failed to obtain the vals #%d: %w", height, err)
|
||||
}
|
||||
return h, vals, nil
|
||||
}
|
||||
@@ -837,6 +871,7 @@ func (c *Client) backwards(
|
||||
now time.Time) error {
|
||||
|
||||
if HeaderExpired(initiallyTrustedHeader, c.trustingPeriod, now) {
|
||||
c.logger.Error("Header Expired")
|
||||
return ErrOldHeaderExpired{initiallyTrustedHeader.Time.Add(c.trustingPeriod), now}
|
||||
}
|
||||
|
||||
@@ -849,16 +884,20 @@ func (c *Client) backwards(
|
||||
for trustedHeader.Height > newHeader.Height {
|
||||
interimHeader, err = c.signedHeaderFromPrimary(trustedHeader.Height - 1)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to obtain the header at height #%d", trustedHeader.Height-1)
|
||||
return fmt.Errorf("failed to obtain the header at height #%d: %w", trustedHeader.Height-1, err)
|
||||
}
|
||||
|
||||
c.logger.Debug("Verify newHeader against trustedHeader",
|
||||
"trustedHeight", trustedHeader.Height,
|
||||
"trustedHash", hash2str(trustedHeader.Hash()),
|
||||
"newHeight", interimHeader.Height,
|
||||
"newHash", hash2str(interimHeader.Hash()))
|
||||
if err := VerifyBackwards(c.chainID, interimHeader, trustedHeader); err != nil {
|
||||
c.logger.Error("primary sent invalid header -> replacing", "err", err)
|
||||
if replaceErr := c.replacePrimaryProvider(); replaceErr != nil {
|
||||
c.logger.Error("Can't replace primary", "err", replaceErr)
|
||||
// return original error
|
||||
return errors.Wrapf(err, "verify backwards from %d to %d failed",
|
||||
trustedHeader.Height, interimHeader.Height)
|
||||
return fmt.Errorf("verify backwards from %d to %d failed: %w",
|
||||
trustedHeader.Height, interimHeader.Height, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,7 +922,7 @@ func (c *Client) compareNewHeaderWithWitnesses(h *types.SignedHeader) error {
|
||||
witnessesToRemove := make([]int, 0)
|
||||
for attempt := uint16(1); attempt <= c.maxRetryAttempts; attempt++ {
|
||||
if len(c.witnesses) == 0 {
|
||||
return errors.New("could not find any witnesses. please reset the light client")
|
||||
return errNoWitnesses{}
|
||||
}
|
||||
|
||||
for i, witness := range c.witnesses {
|
||||
@@ -909,7 +948,7 @@ func (c *Client) compareNewHeaderWithWitnesses(h *types.SignedHeader) error {
|
||||
|
||||
// TODO: send the diverged headers to primary && all witnesses
|
||||
|
||||
return errors.Errorf(
|
||||
return fmt.Errorf(
|
||||
"header hash %X does not match one %X from the witness %v",
|
||||
h.Hash(), altH.Hash(), witness)
|
||||
}
|
||||
@@ -952,7 +991,7 @@ func (c *Client) removeWitness(idx int) {
|
||||
func (c *Client) Update(now time.Time) (*types.SignedHeader, error) {
|
||||
lastTrustedHeight, err := c.LastTrustedHeight()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't get last trusted height")
|
||||
return nil, fmt.Errorf("can't get last trusted height: %w", err)
|
||||
}
|
||||
|
||||
if lastTrustedHeight == -1 {
|
||||
@@ -962,7 +1001,7 @@ func (c *Client) Update(now time.Time) (*types.SignedHeader, error) {
|
||||
|
||||
latestHeader, latestVals, err := c.fetchHeaderAndValsAtHeight(0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "can't get latest header and vals")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if latestHeader.Height > lastTrustedHeight {
|
||||
@@ -984,7 +1023,7 @@ func (c *Client) replacePrimaryProvider() error {
|
||||
defer c.providerMutex.Unlock()
|
||||
|
||||
if len(c.witnesses) <= 1 {
|
||||
return errors.Errorf("only one witness left. please reset the light client")
|
||||
return errNoWitnesses{}
|
||||
}
|
||||
c.primary = c.witnesses[0]
|
||||
c.witnesses = c.witnesses[1:]
|
||||
@@ -1004,7 +1043,7 @@ func (c *Client) signedHeaderFromPrimary(height int64) (*types.SignedHeader, err
|
||||
if err == nil {
|
||||
// sanity check
|
||||
if height > 0 && h.Height != height {
|
||||
return nil, errors.Errorf("expected %d height, got %d", height, h.Height)
|
||||
return nil, fmt.Errorf("expected %d height, got %d", height, h.Height)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
//
|
||||
// Remember that none of these benchmarks account for network latency.
|
||||
var (
|
||||
largeFullNode = mockp.New(GenMockNode(chainID, 1000, 100, 1, bTime))
|
||||
genesisHeader, _ = largeFullNode.SignedHeader(1)
|
||||
benchmarkFullNode = mockp.New(GenMockNode(chainID, 1000, 100, 1, bTime))
|
||||
genesisHeader, _ = benchmarkFullNode.SignedHeader(1)
|
||||
)
|
||||
|
||||
func BenchmarkSequence(b *testing.B) {
|
||||
@@ -32,8 +32,8 @@ func BenchmarkSequence(b *testing.B) {
|
||||
Height: 1,
|
||||
Hash: genesisHeader.Hash(),
|
||||
},
|
||||
largeFullNode,
|
||||
[]provider.Provider{largeFullNode},
|
||||
benchmarkFullNode,
|
||||
[]provider.Provider{benchmarkFullNode},
|
||||
dbs.New(dbm.NewMemDB(), chainID),
|
||||
Logger(log.TestingLogger()),
|
||||
SequentialVerification(),
|
||||
@@ -59,8 +59,8 @@ func BenchmarkBisection(b *testing.B) {
|
||||
Height: 1,
|
||||
Hash: genesisHeader.Hash(),
|
||||
},
|
||||
largeFullNode,
|
||||
[]provider.Provider{largeFullNode},
|
||||
benchmarkFullNode,
|
||||
[]provider.Provider{benchmarkFullNode},
|
||||
dbs.New(dbm.NewMemDB(), chainID),
|
||||
Logger(log.TestingLogger()),
|
||||
)
|
||||
@@ -78,7 +78,7 @@ func BenchmarkBisection(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkBackwards(b *testing.B) {
|
||||
trustedHeader, _ := largeFullNode.SignedHeader(0)
|
||||
trustedHeader, _ := benchmarkFullNode.SignedHeader(0)
|
||||
c, err := NewClient(
|
||||
chainID,
|
||||
TrustOptions{
|
||||
@@ -86,8 +86,8 @@ func BenchmarkBackwards(b *testing.B) {
|
||||
Height: trustedHeader.Height,
|
||||
Hash: trustedHeader.Hash(),
|
||||
},
|
||||
largeFullNode,
|
||||
[]provider.Provider{largeFullNode},
|
||||
benchmarkFullNode,
|
||||
[]provider.Provider{benchmarkFullNode},
|
||||
dbs.New(dbm.NewMemDB(), chainID),
|
||||
Logger(log.TestingLogger()),
|
||||
)
|
||||
|
||||
@@ -57,7 +57,8 @@ var (
|
||||
headerSet,
|
||||
valSet,
|
||||
)
|
||||
deadNode = mockp.NewDeadMock(chainID)
|
||||
deadNode = mockp.NewDeadMock(chainID)
|
||||
largeFullNode = mockp.New(GenMockNode(chainID, 10, 3, 0, bTime))
|
||||
)
|
||||
|
||||
func TestClient_SequentialVerification(t *testing.T) {
|
||||
@@ -682,62 +683,65 @@ func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) {
|
||||
|
||||
func TestClient_BackwardsVerification(t *testing.T) {
|
||||
{
|
||||
trustHeader, _ := largeFullNode.SignedHeader(6)
|
||||
c, err := NewClient(
|
||||
chainID,
|
||||
TrustOptions{
|
||||
Period: 1 * time.Hour,
|
||||
Height: 3,
|
||||
Hash: h3.Hash(),
|
||||
Period: 4 * time.Minute,
|
||||
Height: trustHeader.Height,
|
||||
Hash: trustHeader.Hash(),
|
||||
},
|
||||
fullNode,
|
||||
[]provider.Provider{fullNode},
|
||||
largeFullNode,
|
||||
[]provider.Provider{largeFullNode},
|
||||
dbs.New(dbm.NewMemDB(), chainID),
|
||||
Logger(log.TestingLogger()),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 1) header is missing => expect no error
|
||||
h, err := c.VerifyHeaderAtHeight(2, bTime.Add(1*time.Hour).Add(1*time.Second))
|
||||
// 1) verify before the trusted header using backwards => expect no error
|
||||
h, err := c.VerifyHeaderAtHeight(5, bTime.Add(6*time.Minute))
|
||||
require.NoError(t, err)
|
||||
if assert.NotNil(t, h) {
|
||||
assert.EqualValues(t, 2, h.Height)
|
||||
assert.EqualValues(t, 5, h.Height)
|
||||
}
|
||||
|
||||
// 2) untrusted header is expired but trusted header is not => expect no error
|
||||
h, err = c.VerifyHeaderAtHeight(1, bTime.Add(1*time.Hour).Add(1*time.Second))
|
||||
h, err = c.VerifyHeaderAtHeight(3, bTime.Add(8*time.Minute))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, h)
|
||||
|
||||
// 3) already stored headers should return the header without error
|
||||
h, err = c.VerifyHeaderAtHeight(2, bTime.Add(1*time.Hour).Add(1*time.Second))
|
||||
h, err = c.VerifyHeaderAtHeight(5, bTime.Add(6*time.Minute))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, h)
|
||||
}
|
||||
{
|
||||
c, err := NewClient(
|
||||
chainID,
|
||||
TrustOptions{
|
||||
Period: 1 * time.Hour,
|
||||
Height: 3,
|
||||
Hash: h3.Hash(),
|
||||
},
|
||||
fullNode,
|
||||
[]provider.Provider{fullNode},
|
||||
dbs.New(dbm.NewMemDB(), chainID),
|
||||
Logger(log.TestingLogger()),
|
||||
)
|
||||
|
||||
// 4a) First verify latest header
|
||||
_, err = c.VerifyHeaderAtHeight(9, bTime.Add(9*time.Minute))
|
||||
require.NoError(t, err)
|
||||
|
||||
// 3) trusted header has expired => expect error
|
||||
_, err = c.VerifyHeaderAtHeight(1, bTime.Add(4*time.Hour).Add(1*time.Second))
|
||||
// 4b) Verify backwards using bisection => expect no error
|
||||
_, err = c.VerifyHeaderAtHeight(7, bTime.Add(10*time.Minute))
|
||||
assert.NoError(t, err)
|
||||
// shouldn't have verified this header in the process
|
||||
_, err = c.TrustedHeader(8)
|
||||
assert.Error(t, err)
|
||||
|
||||
// 5) trusted header has expired => expect error
|
||||
_, err = c.VerifyHeaderAtHeight(1, bTime.Add(20*time.Minute))
|
||||
assert.Error(t, err)
|
||||
|
||||
// 6) Try bisection method, but closest header (at 7) has expired
|
||||
// so change to backwards => expect no error
|
||||
_, err = c.VerifyHeaderAtHeight(8, bTime.Add(12*time.Minute))
|
||||
assert.NoError(t, err)
|
||||
|
||||
}
|
||||
{
|
||||
testCases := []struct {
|
||||
provider provider.Provider
|
||||
}{
|
||||
{
|
||||
// provides incorrect height
|
||||
// 7) provides incorrect height
|
||||
mockp.New(
|
||||
chainID,
|
||||
map[int64]*types.SignedHeader{
|
||||
@@ -750,7 +754,7 @@ func TestClient_BackwardsVerification(t *testing.T) {
|
||||
),
|
||||
},
|
||||
{
|
||||
// provides incorrect hash
|
||||
// 8) provides incorrect hash
|
||||
mockp.New(
|
||||
chainID,
|
||||
map[int64]*types.SignedHeader{
|
||||
|
||||
@@ -38,3 +38,11 @@ type ErrInvalidHeader struct {
|
||||
func (e ErrInvalidHeader) Error() string {
|
||||
return fmt.Sprintf("invalid header: %v", e.Reason)
|
||||
}
|
||||
|
||||
// errNoWitnesses means that there are not enough witnesses connected to
|
||||
// continue running the light client.
|
||||
type errNoWitnesses struct{}
|
||||
|
||||
func (e errNoWitnesses) Error() string {
|
||||
return fmt.Sprint("no witnesses connected. please reset light client")
|
||||
}
|
||||
|
||||
@@ -203,18 +203,18 @@ func (s *dbs) FirstSignedHeaderHeight() (int64, error) {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// SignedHeaderAfter iterates over headers until it finds a header after one at
|
||||
// height. It returns ErrSignedHeaderNotFound if no such header exists.
|
||||
// SignedHeaderBefore iterates over headers until it finds a header before
|
||||
// the given height. It returns ErrSignedHeaderNotFound if no such header exists.
|
||||
//
|
||||
// Safe for concurrent use by multiple goroutines.
|
||||
func (s *dbs) SignedHeaderAfter(height int64) (*types.SignedHeader, error) {
|
||||
func (s *dbs) SignedHeaderBefore(height int64) (*types.SignedHeader, error) {
|
||||
if height <= 0 {
|
||||
panic("negative or zero height")
|
||||
}
|
||||
|
||||
itr, err := s.db.Iterator(
|
||||
s.shKey(height+1),
|
||||
append(s.shKey(1<<63-1), byte(0x00)),
|
||||
itr, err := s.db.ReverseIterator(
|
||||
s.shKey(1),
|
||||
s.shKey(height),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -76,19 +76,19 @@ func Test_SaveSignedHeaderAndValidatorSet(t *testing.T) {
|
||||
assert.Nil(t, valSet)
|
||||
}
|
||||
|
||||
func Test_SignedHeaderAfter(t *testing.T) {
|
||||
dbStore := New(dbm.NewMemDB(), "Test_SignedHeaderAfter")
|
||||
func Test_SignedHeaderBefore(t *testing.T) {
|
||||
dbStore := New(dbm.NewMemDB(), "Test_SignedHeaderBefore")
|
||||
|
||||
assert.Panics(t, func() {
|
||||
dbStore.SignedHeaderAfter(0)
|
||||
dbStore.SignedHeaderAfter(100)
|
||||
_, _ = dbStore.SignedHeaderBefore(0)
|
||||
_, _ = dbStore.SignedHeaderBefore(100)
|
||||
})
|
||||
|
||||
err := dbStore.SaveSignedHeaderAndValidatorSet(
|
||||
&types.SignedHeader{Header: &types.Header{Height: 2}}, &types.ValidatorSet{})
|
||||
require.NoError(t, err)
|
||||
|
||||
h, err := dbStore.SignedHeaderAfter(1)
|
||||
h, err := dbStore.SignedHeaderBefore(3)
|
||||
require.NoError(t, err)
|
||||
if assert.NotNil(t, h) {
|
||||
assert.EqualValues(t, 2, h.Height)
|
||||
|
||||
@@ -41,10 +41,10 @@ type Store interface {
|
||||
// If the store is empty, -1 and nil error are returned.
|
||||
FirstSignedHeaderHeight() (int64, error)
|
||||
|
||||
// SignedHeaderAfter returns the SignedHeader after the certain height.
|
||||
// SignedHeaderBefore returns the SignedHeader before a certain height.
|
||||
//
|
||||
// height must be > 0 && <= LastSignedHeaderHeight.
|
||||
SignedHeaderAfter(height int64) (*types.SignedHeader, error)
|
||||
SignedHeaderBefore(height int64) (*types.SignedHeader, error)
|
||||
|
||||
// Prune removes headers & the associated validator sets when Store reaches a
|
||||
// defined size (number of header & validator set pairs).
|
||||
|
||||
Reference in New Issue
Block a user