func (c *Client) LastTrustedHeight() (int64, error) {
	return c.trustedStore.LastLightBlockHeight()
}
LastTrustedHeight returns a last trusted height. -1 and nil are returned if
there are no trusted headers.

Safe for concurrent use by multiple goroutines.
package light
import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"sort"
	"sync"
	"time"
	tmsync "github.com/tendermint/tendermint/internal/libs/sync"
	"github.com/tendermint/tendermint/libs/log"
	tmmath "github.com/tendermint/tendermint/libs/math"
	"github.com/tendermint/tendermint/light/provider"
	"github.com/tendermint/tendermint/light/store"
	"github.com/tendermint/tendermint/types"
)
type mode byte
const (
	sequential mode = iota + 1
	skipping
	defaultPruningSize = 1000
	verifySkippingNumerator   = 9
	verifySkippingDenominator = 16
	defaultMaxClockDrift = 10 * time.Second
	defaultMaxBlockLag = 10 * time.Second
	defaultProviderTimeout = 10 * time.Second
)
type Option func(*Client)
func SequentialVerification() Option {
	return func(c *Client) { c.verificationMode = sequential }
}
func SkippingVerification(trustLevel tmmath.Fraction) Option {
	return func(c *Client) {
		c.verificationMode = skipping
		c.trustLevel = trustLevel
	}
}
func PruningSize(h uint16) Option {
	return func(c *Client) { c.pruningSize = h }
}
func Logger(l log.Logger) Option {
	return func(c *Client) { c.logger = l }
}
func MaxClockDrift(d time.Duration) Option {
	return func(c *Client) { c.maxClockDrift = d }
}
func MaxBlockLag(d time.Duration) Option {
	return func(c *Client) { c.maxBlockLag = d }
}
func ProviderTimeout(d time.Duration) Option {
	return func(c *Client) { c.providerTimeout = d }
}
type Client struct {
	chainID          string
	trustingPeriod   time.Duration  
	verificationMode mode
	trustLevel       tmmath.Fraction
	maxClockDrift    time.Duration
	maxBlockLag      time.Duration
	providerTimeout  time.Duration
	providerMutex tmsync.Mutex
	primary provider.Provider
	witnesses []provider.Provider
	trustedStore store.Store
	latestTrustedBlock *types.LightBlock
	pruningSize uint16
	logger log.Logger
}
func NewClient(
	ctx context.Context,
	chainID string,
	trustOptions TrustOptions,
	primary provider.Provider,
	witnesses []provider.Provider,
	trustedStore store.Store,
	options ...Option) (*Client, error) {
	lastHeight, err := trustedStore.LastLightBlockHeight()
	if err != nil {
		return nil, err
	}
	if lastHeight > 0 {
		return NewClientFromTrustedStore(
			chainID, trustOptions.Period, primary, witnesses, trustedStore, options...,
		)
	}
	if err := trustOptions.ValidateBasic(); err != nil {
		return nil, fmt.Errorf("invalid TrustOptions: %w", err)
	}
	if len(witnesses) < 1 {
		return nil, ErrNoWitnesses
	}
	c := &Client{
		chainID:          chainID,
		trustingPeriod:   trustOptions.Period,
		verificationMode: skipping,
		primary:          primary,
		witnesses:        witnesses,
		trustedStore:     trustedStore,
		trustLevel:       DefaultTrustLevel,
		maxClockDrift:    defaultMaxClockDrift,
		maxBlockLag:      defaultMaxBlockLag,
		providerTimeout:  defaultProviderTimeout,
		pruningSize:      defaultPruningSize,
		logger:           log.NewNopLogger(),
	}
	for _, o := range options {
		o(c)
	}
	if err := ValidateTrustLevel(c.trustLevel); err != nil {
		return nil, err
	}
	if err := c.initializeWithTrustOptions(ctx, trustOptions); err != nil {
		return nil, err
	}
	return c, nil
}
func NewClientFromTrustedStore(
	chainID string,
	trustingPeriod time.Duration,
	primary provider.Provider,
	witnesses []provider.Provider,
	trustedStore store.Store,
	options ...Option) (*Client, error) {
	c := &Client{
		chainID:          chainID,
		trustingPeriod:   trustingPeriod,
		verificationMode: skipping,
		trustLevel:       DefaultTrustLevel,
		maxClockDrift:    defaultMaxClockDrift,
		maxBlockLag:      defaultMaxBlockLag,
		primary:          primary,
		witnesses:        witnesses,
		trustedStore:     trustedStore,
		pruningSize:      defaultPruningSize,
		logger:           log.NewNopLogger(),
	}
	for _, o := range options {
		o(c)
	}
	if len(c.witnesses) < 1 {
		return nil, ErrNoWitnesses
	}
	if err := ValidateTrustLevel(c.trustLevel); err != nil {
		return nil, err
	}
	if err := c.restoreTrustedLightBlock(); err != nil {
		return nil, err
	}
	return c, nil
}
func (c *Client) restoreTrustedLightBlock() error {
	lastHeight, err := c.trustedStore.LastLightBlockHeight()
	if err != nil {
		return fmt.Errorf("can't get last trusted light block height: %w", err)
	}
	if lastHeight <= 0 {
		return errors.New("trusted store is empty")
	}
	trustedBlock, err := c.trustedStore.LightBlock(lastHeight)
	if err != nil {
		return fmt.Errorf("can't get last trusted light block: %w", err)
	}
	c.latestTrustedBlock = trustedBlock
	c.logger.Info("restored trusted light block", "height", lastHeight)
	return nil
}
func (c *Client) initializeWithTrustOptions(ctx context.Context, options TrustOptions) error {
	l, err := c.lightBlockFromPrimary(ctx, options.Height)
	if err != nil {
		return err
	}
	if !bytes.Equal(l.Header.Hash(), options.Hash) {
		return fmt.Errorf("expected header's hash %X, but got %X", options.Hash, l.Hash())
	}
	err = l.ValidatorSet.VerifyCommitLight(c.chainID, l.Commit.BlockID, l.Height, l.Commit)
	if err != nil {
		return fmt.Errorf("invalid commit: %w", err)
	}
	if err := c.compareFirstHeaderWithWitnesses(ctx, l.SignedHeader); err != nil {
		return err
	}
	return c.updateTrustedLightBlock(l)
}
func (c *Client) TrustedLightBlock(height int64) (*types.LightBlock, error) {
	height, err := c.compareWithLatestHeight(height)
	if err != nil {
		return nil, err
	}
	return c.trustedStore.LightBlock(height)
}
func (c *Client) compareWithLatestHeight(height int64) (int64, error) {
	latestHeight, err := c.LastTrustedHeight()
	if err != nil {
		return 0, fmt.Errorf("can't get last trusted height: %w", err)
	}
	if latestHeight == -1 {
		return 0, errors.New("no headers exist")
	}
	switch {
	case height > latestHeight:
		return 0, fmt.Errorf("unverified header/valset requested (latest: %d)", latestHeight)
	case height == 0:
		return latestHeight, nil
	case height < 0:
		return 0, errors.New("negative height")
	}
	return height, nil
}
func (c *Client) Update(ctx context.Context, now time.Time) (*types.LightBlock, error) {
	lastTrustedHeight, err := c.LastTrustedHeight()
	if err != nil {
		return nil, fmt.Errorf("can't get last trusted height: %w", err)
	}
	if lastTrustedHeight == -1 {
		return nil, nil
	}
	latestBlock, err := c.lightBlockFromPrimary(ctx, 0)
	if err != nil {
		return nil, err
	}
	if latestBlock.Height > lastTrustedHeight {
		err = c.verifyLightBlock(ctx, latestBlock, now)
		if err != nil {
			return nil, err
		}
		c.logger.Info("advanced to new state", "height", latestBlock.Height, "hash", latestBlock.Hash())
		return latestBlock, nil
	}
	return c.latestTrustedBlock, nil
}
func (c *Client) VerifyLightBlockAtHeight(ctx context.Context, height int64, now time.Time) (*types.LightBlock, error) {
	if height <= 0 {
		return nil, errors.New("negative or zero height")
	}
	h, err := c.TrustedLightBlock(height)
	if err == nil {
		c.logger.Debug("header has already been verified", "height", height, "hash", h.Hash())
		return h, nil
	}
	l, err := c.lightBlockFromPrimary(ctx, height)
	if err != nil {
		return nil, err
	}
	return l, c.verifyLightBlock(ctx, l, now)
}
func (c *Client) VerifyHeader(ctx context.Context, newHeader *types.Header, now time.Time) error {
	if newHeader == nil {
		return errors.New("nil header")
	}
	if newHeader.Height <= 0 {
		return errors.New("negative or zero height")
	}
	l, err := c.TrustedLightBlock(newHeader.Height)
	if err == nil {
		if !bytes.Equal(l.Hash(), newHeader.Hash()) {
			return fmt.Errorf("existing trusted header %X does not match newHeader %X", l.Hash(), newHeader.Hash())
		}
		c.logger.Debug("header has already been verified",
			"height", newHeader.Height, "hash", newHeader.Hash())
		return nil
	}
	l, err = c.lightBlockFromPrimary(ctx, newHeader.Height)
	if err != nil {
		return fmt.Errorf("failed to retrieve light block from primary to verify against: %w", err)
	}
	if !bytes.Equal(l.Hash(), newHeader.Hash()) {
		return fmt.Errorf("header from primary %X does not match newHeader %X", l.Hash(), newHeader.Hash())
	}
	return c.verifyLightBlock(ctx, l, now)
}
func (c *Client) verifyLightBlock(ctx context.Context, newLightBlock *types.LightBlock, now time.Time) error {
	c.logger.Info("verify light block", "height", newLightBlock.Height, "hash", newLightBlock.Hash())
	var (
		verifyFunc func(ctx context.Context, trusted *types.LightBlock, new *types.LightBlock, now time.Time) error
		err        error
	)
	switch c.verificationMode {
	case sequential:
		verifyFunc = c.verifySequential
	case skipping:
		verifyFunc = c.verifySkippingAgainstPrimary
	default:
		panic(fmt.Sprintf("Unknown verification mode: %b", c.verificationMode))
	}
	firstBlockHeight, err := c.FirstTrustedHeight()
	if err != nil {
		return fmt.Errorf("can't get first light block height: %w", err)
	}
	switch {
	case newLightBlock.Height >= c.latestTrustedBlock.Height:
		err = verifyFunc(ctx, c.latestTrustedBlock, newLightBlock, now)
	case newLightBlock.Height < firstBlockHeight:
		var firstBlock *types.LightBlock
		firstBlock, err = c.trustedStore.LightBlock(firstBlockHeight)
		if err != nil {
			return fmt.Errorf("can't get first light block: %w", err)
		}
		err = c.backwards(ctx, firstBlock.Header, newLightBlock.Header)
	default:
		var closestBlock *types.LightBlock
		closestBlock, err = c.trustedStore.LightBlockBefore(newLightBlock.Height)
		if err != nil {
			return fmt.Errorf("can't get signed header before height %d: %w", newLightBlock.Height, err)
		}
		err = verifyFunc(ctx, closestBlock, newLightBlock, now)
	}
	if err != nil {
		c.logger.Error("failed to verify", "err", err)
		return err
	}
	return c.updateTrustedLightBlock(newLightBlock)
}
func (c *Client) verifySequential(
	ctx context.Context,
	trustedBlock *types.LightBlock,
	newLightBlock *types.LightBlock,
	now time.Time) error {
	var (
		verifiedBlock = trustedBlock
		interimBlock  *types.LightBlock
		err           error
		trace         = []*types.LightBlock{trustedBlock}
	)
	for height := trustedBlock.Height + 1; height <= newLightBlock.Height; height++ {
		if height == newLightBlock.Height {  
			interimBlock = newLightBlock
		} else {  
			interimBlock, err = c.lightBlockFromPrimary(ctx, height)
			if err != nil {
				return ErrVerificationFailed{From: verifiedBlock.Height, To: height, Reason: err}
			}
		}
		c.logger.Debug("verify adjacent newLightBlock against verifiedBlock",
			"trustedHeight", verifiedBlock.Height,
			"trustedHash", verifiedBlock.Hash(),
			"newHeight", interimBlock.Height,
			"newHash", interimBlock.Hash())
		err = VerifyAdjacent(verifiedBlock.SignedHeader, interimBlock.SignedHeader, interimBlock.ValidatorSet,
			c.trustingPeriod, now, c.maxClockDrift)
		if err != nil {
			err := ErrVerificationFailed{From: verifiedBlock.Height, To: interimBlock.Height, Reason: err}
			switch errors.Unwrap(err).(type) {
			case ErrInvalidHeader:
				if err.To == newLightBlock.Height {
					c.logger.Debug("target header is invalid", "err", err)
					return err
				}
				c.logger.Info("primary sent invalid header -> removing", "err", err, "primary", c.primary)
				replacementBlock, removeErr := c.findNewPrimary(ctx, newLightBlock.Height, true)
				if removeErr != nil {
					c.logger.Debug("failed to replace primary. Returning original error", "err", removeErr)
					return err
				}
				if !bytes.Equal(replacementBlock.Hash(), newLightBlock.Hash()) {
					c.logger.Debug("replaced primary but new primary has a different block to the initial one")
					return err
				}
				height--
				continue
			default:
				return err
			}
		}
		verifiedBlock = interimBlock
		trace = append(trace, verifiedBlock)
	}
	return c.detectDivergence(ctx, trace, now)
}
func (c *Client) verifySkipping(
	ctx context.Context,
	source provider.Provider,
	trustedBlock *types.LightBlock,
	newLightBlock *types.LightBlock,
	now time.Time) ([]*types.LightBlock, error) {
	var (
		blockCache = []*types.LightBlock{newLightBlock}
		depth      = 0
		verifiedBlock = trustedBlock
		trace         = []*types.LightBlock{trustedBlock}
	)
	for {
		c.logger.Debug("verify non-adjacent newHeader against verifiedBlock",
			"trustedHeight", verifiedBlock.Height,
			"trustedHash", verifiedBlock.Hash(),
			"newHeight", blockCache[depth].Height,
			"newHash", blockCache[depth].Hash())
		err := Verify(verifiedBlock.SignedHeader, verifiedBlock.ValidatorSet, blockCache[depth].SignedHeader,
			blockCache[depth].ValidatorSet, c.trustingPeriod, now, c.maxClockDrift, c.trustLevel)
		switch err.(type) {
		case nil:
			if depth == 0 {
				trace = append(trace, newLightBlock)
				return trace, nil
			}
			verifiedBlock = blockCache[depth]
			blockCache = blockCache[:depth]
			depth = 0
			trace = append(trace, verifiedBlock)
		case ErrNewValSetCantBeTrusted:
			if depth == len(blockCache)-1 {
				pivotHeight := c.schedule(verifiedBlock.Height, blockCache[depth].Height)
				subCtx, cancel := context.WithTimeout(ctx, c.providerTimeout)
				defer cancel()
				interimBlock, providerErr := c.getLightBlock(subCtx, source, pivotHeight)
				if providerErr != nil {
					return nil, ErrVerificationFailed{From: verifiedBlock.Height, To: pivotHeight, Reason: providerErr}
				}
				blockCache = append(blockCache, interimBlock)
			}
			depth++
		default:
			return nil, ErrVerificationFailed{From: verifiedBlock.Height, To: blockCache[depth].Height, Reason: err}
		}
	}
}
func (c *Client) schedule(lastVerifiedHeight, lastFailedHeight int64) int64 {
	return lastVerifiedHeight +
		(lastFailedHeight-lastVerifiedHeight)*verifySkippingNumerator/verifySkippingDenominator
}
func (c *Client) verifySkippingAgainstPrimary(
	ctx context.Context,
	trustedBlock *types.LightBlock,
	newLightBlock *types.LightBlock,
	now time.Time) error {
	trace, err := c.verifySkipping(ctx, c.primary, trustedBlock, newLightBlock, now)
	if err == nil {
		if cmpErr := c.detectDivergence(ctx, trace, now); cmpErr != nil {
			return cmpErr
		}
	}
	var e = &ErrVerificationFailed{}
	if !errors.As(err, e) {
		return err
	}
	replace := true
	switch e.Reason.(type) {
	case ErrInvalidHeader:
		if e.To == newLightBlock.Height {
			c.logger.Debug("target header is invalid", "err", err)
			return err
		}
	case ErrOldHeaderExpired:
		return err
	default:
		if errors.Is(e.Reason, context.Canceled) || errors.Is(e.Reason, context.DeadlineExceeded) {
			return e.Reason
		}
		if !c.providerShouldBeRemoved(e.Reason) {
			replace = false
		}
	}
	c.logger.Info("primary returned error", "err", e, "primary", c.primary, "replace", replace)
	replacementBlock, removeErr := c.findNewPrimary(ctx, newLightBlock.Height, replace)
	if removeErr != nil {
		c.logger.Error("failed to replace primary. Returning original error", "err", removeErr)
		return e.Reason
	}
	if !bytes.Equal(replacementBlock.Hash(), newLightBlock.Hash()) {
		c.logger.Debug("replaced primary but new primary has a different block to the initial one. Returning original error")
		return e.Reason
	}
	return c.verifySkippingAgainstPrimary(ctx, trustedBlock, replacementBlock, now)
}