Documentation ¶
Overview ¶
Package lite provides a light client implementation.
The concept of light clients was introduced in the Bitcoin white paper. It describes a watcher of distributed consensus process that only validates the consensus algorithm and not the state machine transactions within.
Tendermint light clients allow bandwidth & compute-constrained devices, such as smartphones, low-power embedded chips, or other blockchains to efficiently verify the consensus of a Tendermint blockchain. This forms the basis of safe and efficient state synchronization for new network nodes and inter-blockchain communication (where a light client of one Tendermint instance runs in another chain's state machine).
In a network that is expected to reliably punish validators for misbehavior by slashing bonded stake and where the validator set changes infrequently, clients can take advantage of this assumption to safely synchronize a lite client without downloading the intervening headers.
Light clients (and full nodes) operating in the Proof Of Stake context need a trusted block height from a trusted source that is no older than 1 unbonding window plus a configurable evidence submission synchrony bound. This is called weak subjectivity.
Weak subjectivity is required in Proof of Stake blockchains because it is costless for an attacker to buy up voting keys that are no longer bonded and fork the network at some point in its prior history. See Vitalik's post at [Proof of Stake: How I Learned to Love Weak Subjectivity](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/).
NOTE: Tendermint provides a somewhat different (stronger) light client model than Bitcoin under eclipse, since the eclipsing node(s) can only fool the light client if they have two-thirds of the private keys from the last root-of-trust.
Common structures ¶
* SignedHeader
SignedHeader is a block header along with a commit -- enough validator precommit-vote signatures to prove its validity (> 2/3 of the voting power) given the validator set responsible for signing that header.
The hash of the next validator set is included and signed in the SignedHeader. This lets the lite client keep track of arbitrary changes to the validator set, as every change to the validator set must be approved by inclusion in the header and signed in the commit.
In the worst case, with every block changing the validators around completely, a lite client can sync up with every block header to verify each validator set change on the chain. In practice, most applications will not have frequent drastic updates to the validator set, so the logic defined in this package for lite client syncing is optimized to use intelligent bisection.
What this package provides ¶
This package provides three major things:
1. Client implementation (see client.go) 2. Pure functions to verify a new header (see verifier.go) 3. Secure RPC proxy
## 1. Client implementation (see client.go)
Example usage:
db, err := dbm.NewGoLevelDB("lite-client-db", dbDir) if err != nil { // handle error } c, err := NewHTTPClient( chainID, TrustOptions{ Period: 504 * time.Hour, // 21 days Height: 100, Hash: header.Hash(), }, "http://localhost:26657", []string{"http://witness1:26657"}, dbs.New(db, ""), ) if err != nil { // handle error } h, err := c.TrustedHeader(100) if err != nil { // handle error } fmt.Println("header", h)
Check out other examples in example_test.go
## 2. Pure functions to verify a new header (see verifier.go)
Verify function verifies a new header against some trusted header. See https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/verification.md for details.
There are two methods of verification: sequential and bisection
Sequential uses the headers hashes and the validator sets to verify each adjacent header until it reaches the target header.
Bisection finds the middle header between a trusted and new header, reiterating the action until it verifies a header. A cache of headers requested by the primary is kept such that when a verification is made, and the light client tries again to verify the new header in the middle, the light client does not need to ask for all the same headers again.
refer to docs/imgs/light_client_bisection_alg.png
## 3. Secure RPC proxy
Tendermint RPC exposes a lot of info, but a malicious node could return any data it wants to queries, or even to block headers, even making up fake signatures from non-existent validators to justify it. Secure RPC proxy serves as a wrapper, which verifies all the headers, using a light client connected to some other node.
See https://docs.tendermint.com/master/tendermint-core/light-client-protocol.html for usage example. Or see https://github.com/tendermint/spec/tree/master/spec/consensus/light-client for the full spec
Index ¶
- Variables
- func HeaderExpired(h *types.SignedHeader, trustingPeriod time.Duration, now time.Time) bool
- func IBCVerify(chainID string, trustedHeader *types.SignedHeader, ...) error
- func IBCVerifyAdjacent(chainID string, trustedHeader *types.SignedHeader, ...) error
- func IBCVerifyNonAdjacent(chainID string, trustedHeader *types.SignedHeader, ...) error
- func ValidateTrustLevel(lvl tmmath.Fraction) error
- func Verify(chainID string, trustedHeader *types.SignedHeader, ...) error
- func VerifyAdjacent(chainID string, trustedHeader *types.SignedHeader, ...) error
- func VerifyBackwards(chainID string, untrustedHeader, trustedHeader *types.SignedHeader) error
- func VerifyNonAdjacent(chainID string, trustedHeader *types.SignedHeader, ...) error
- type Client
- func NewClient(chainID string, trustOptions TrustOptions, primary provider.Provider, ...) (*Client, error)
- func NewClientFromTrustedStore(chainID string, trustingPeriod time.Duration, primary provider.Provider, ...) (*Client, error)
- func NewHTTPClient(chainID string, trustOptions TrustOptions, primaryAddress string, ...) (*Client, error)
- func NewHTTPClientFromTrustedStore(chainID string, trustingPeriod time.Duration, primaryAddress string, ...) (*Client, error)
- func (c *Client) ChainID() string
- func (c *Client) Cleanup() error
- func (c *Client) FirstTrustedHeight() (int64, error)
- func (c *Client) LastTrustedHeight() (int64, error)
- func (c *Client) Primary() provider.Provider
- func (c *Client) TrustedHeader(height int64) (*types.SignedHeader, error)
- func (c *Client) TrustedValidatorSet(height int64) (valSet *types.ValidatorSet, heightUsed int64, err error)
- func (c *Client) Update(now time.Time) (*types.SignedHeader, error)
- func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error
- func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error)
- func (c *Client) Witnesses() []provider.Provider
- type ErrInvalidHeader
- type ErrNewValSetCantBeTrusted
- type ErrOldHeaderExpired
- type Option
- func ConfirmationFunction(fn func(action string) bool) Option
- func Logger(l log.Logger) Option
- func MaxClockDrift(d time.Duration) Option
- func MaxRetryAttempts(max uint16) Option
- func PruningSize(h uint16) Option
- func SequentialVerification() Option
- func SkippingVerification(trustLevel tmmath.Fraction) Option
- type TrustOptions
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // DefaultTrustLevel - new header can be trusted if at least one correct // validator signed it. DefaultTrustLevel = tmmath.Fraction{Numerator: 1, Denominator: 3} )
Functions ¶
func HeaderExpired ¶
HeaderExpired return true if the given header expired.
func IBCVerify ¶
func IBCVerify( chainID string, trustedHeader *types.SignedHeader, trustedVals *types.ValidatorSet, untrustedHeader *types.SignedHeader, untrustedVals *types.ValidatorSet, trustingPeriod time.Duration, now time.Time, maxClockDrift time.Duration, trustLevel tmmath.Fraction) error
func IBCVerifyAdjacent ¶
func IBCVerifyAdjacent( chainID string, trustedHeader *types.SignedHeader, untrustedHeader *types.SignedHeader, untrustedVals *types.ValidatorSet, trustingPeriod time.Duration, now time.Time, maxClockDrift time.Duration) error
func IBCVerifyNonAdjacent ¶
func IBCVerifyNonAdjacent( chainID string, trustedHeader *types.SignedHeader, trustedVals *types.ValidatorSet, untrustedHeader *types.SignedHeader, untrustedVals *types.ValidatorSet, trustingPeriod time.Duration, now time.Time, maxClockDrift time.Duration, trustLevel tmmath.Fraction) error
func ValidateTrustLevel ¶
ValidateTrustLevel checks that trustLevel is within the allowed range [1/3, 1]. If not, it returns an error. 1/3 is the minimum amount of trust needed which does not break the security model.
func Verify ¶
func Verify( chainID string, trustedHeader *types.SignedHeader, trustedVals *types.ValidatorSet, untrustedHeader *types.SignedHeader, untrustedVals *types.ValidatorSet, trustingPeriod time.Duration, now time.Time, maxClockDrift time.Duration, trustLevel tmmath.Fraction) error
Verify combines both VerifyAdjacent and VerifyNonAdjacent functions.
func VerifyAdjacent ¶
func VerifyAdjacent( chainID string, trustedHeader *types.SignedHeader, untrustedHeader *types.SignedHeader, untrustedVals *types.ValidatorSet, trustingPeriod time.Duration, now time.Time, maxClockDrift time.Duration) error
VerifyAdjacent verifies directly adjacent untrustedHeader against trustedHeader. It ensures that:
a) trustedHeader can still be trusted (if not, ErrOldHeaderExpired is returned) b) untrustedHeader is valid (if not, ErrInvalidHeader is returned) c) untrustedHeader.ValidatorsHash equals trustedHeader.NextValidatorsHash d) more than 2/3 of new validators (untrustedVals) have signed h2 (otherwise, ErrInvalidHeader is returned) e) headers are adjacent.
maxClockDrift defines how much untrustedHeader.Time can drift into the future.
func VerifyBackwards ¶
func VerifyBackwards(chainID string, untrustedHeader, trustedHeader *types.SignedHeader) error
VerifyBackwards verifies an untrusted header with a height one less than that of an adjacent trusted header. It ensures that:
a) untrusted header is valid b) untrusted header has a time before the trusted header c) that the LastBlockID hash of the trusted header is the same as the hash of the trusted header For any of these cases ErrInvalidHeader is returned.
func VerifyNonAdjacent ¶
func VerifyNonAdjacent( chainID string, trustedHeader *types.SignedHeader, trustedVals *types.ValidatorSet, untrustedHeader *types.SignedHeader, untrustedVals *types.ValidatorSet, trustingPeriod time.Duration, now time.Time, maxClockDrift time.Duration, trustLevel tmmath.Fraction) error
VerifyNonAdjacent verifies non-adjacent untrustedHeader against trustedHeader. It ensures that:
a) trustedHeader can still be trusted (if not, ErrOldHeaderExpired is returned) b) untrustedHeader is valid (if not, ErrInvalidHeader is returned) c) trustLevel ([1/3, 1]) of trustedHeaderVals (or trustedHeaderNextVals) signed correctly (if not, ErrNewValSetCantBeTrusted is returned) d) more than 2/3 of untrustedVals have signed h2 (otherwise, ErrInvalidHeader is returned) e) headers are non-adjacent.
maxClockDrift defines how much untrustedHeader.Time can drift into the future.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client represents a light client, connected to a single chain, which gets headers from a primary provider, verifies them either sequentially or by skipping some and stores them in a trusted store (usually, a local FS).
Default verification: SkippingVerification(DefaultTrustLevel)
func NewClient ¶
func NewClient( chainID string, trustOptions TrustOptions, primary provider.Provider, witnesses []provider.Provider, trustedStore store.Store, options ...Option) (*Client, error)
NewClient returns a new light client. It returns an error if it fails to obtain the header & vals from the primary or they are invalid (e.g. trust hash does not match with the one from the header).
Witnesses are providers, which will be used for cross-checking the primary provider. At least one witness must be given. A witness can become a primary iff the current primary is unavailable.
See all Option(s) for the additional configuration.
func NewClientFromTrustedStore ¶
func NewClientFromTrustedStore( chainID string, trustingPeriod time.Duration, primary provider.Provider, witnesses []provider.Provider, trustedStore store.Store, options ...Option) (*Client, error)
NewClientFromTrustedStore initializes existing client from the trusted store.
See NewClient
func NewHTTPClient ¶
func NewHTTPClient( chainID string, trustOptions TrustOptions, primaryAddress string, witnessesAddresses []string, trustedStore store.Store, options ...Option) (*Client, error)
NewHTTPClient initiates an instance of a lite client using HTTP addresses for both the primary provider and witnesses of the lite client. A trusted header and hash must be passed to initialize the client.
See all Option(s) for the additional configuration. See NewClient.
func NewHTTPClientFromTrustedStore ¶
func NewHTTPClientFromTrustedStore( chainID string, trustingPeriod time.Duration, primaryAddress string, witnessesAddresses []string, trustedStore store.Store, options ...Option) (*Client, error)
NewHTTPClientFromTrustedStore initiates an instance of a lite client using HTTP addresses for both the primary provider and witnesses and uses a trusted store as the root of trust.
See all Option(s) for the additional configuration. See NewClientFromTrustedStore.
func (*Client) ChainID ¶
ChainID returns the chain ID the light client was configured with.
Safe for concurrent use by multiple goroutines.
func (*Client) Cleanup ¶
Cleanup removes all the data (headers and validator sets) stored. Note: the client must be stopped at this point.
func (*Client) FirstTrustedHeight ¶
FirstTrustedHeight returns a first trusted height. -1 and nil are returned if there are no trusted headers.
Safe for concurrent use by multiple goroutines.
func (*Client) LastTrustedHeight ¶
LastTrustedHeight returns a last trusted height. -1 and nil are returned if there are no trusted headers.
Safe for concurrent use by multiple goroutines.
func (*Client) Primary ¶
Primary returns the primary provider.
NOTE: provider may be not safe for concurrent access.
func (*Client) TrustedHeader ¶
func (c *Client) TrustedHeader(height int64) (*types.SignedHeader, error)
TrustedHeader returns a trusted header at the given height (0 - the latest).
Headers along with validator sets, which can't be trusted anymore, are removed once a day (can be changed with RemoveNoLongerTrustedHeadersPeriod option). . height must be >= 0.
It returns an error if:
- there are some issues with the trusted store, although that should not happen normally;
- negative height is passed;
- header has not been verified yet and is therefore not in the store
Safe for concurrent use by multiple goroutines.
func (*Client) TrustedValidatorSet ¶
func (c *Client) TrustedValidatorSet(height int64) (valSet *types.ValidatorSet, heightUsed int64, err error)
TrustedValidatorSet returns a trusted validator set at the given height (0 - latest). The second return parameter is the height used (useful if 0 was passed; otherwise can be ignored).
height must be >= 0.
Headers along with validator sets are removed once a day (can be changed with RemoveNoLongerTrustedHeadersPeriod option).
Function returns an error if:
- there are some issues with the trusted store, although that should not happen normally;
- negative height is passed;
- header signed by that validator set has not been verified yet
Safe for concurrent use by multiple goroutines.
func (*Client) Update ¶
Update attempts to advance the state by downloading the latest header and comparing it with the existing one. It returns a new header on a successful update. Otherwise, it returns nil (plus an error, if any).
Example ¶
Automatically getting new headers and verifying them.
package main import ( "fmt" "io/ioutil" stdlog "log" "os" "time" dbm "github.com/okx/okbchain/libs/tm-db" lite "github.com/okx/okbchain/libs/tendermint/lite2" "github.com/okx/okbchain/libs/tendermint/lite2/provider" httpp "github.com/okx/okbchain/libs/tendermint/lite2/provider/http" dbs "github.com/okx/okbchain/libs/tendermint/lite2/store/db" rpctest "github.com/okx/okbchain/libs/tendermint/rpc/test" ) func main() { // give Tendermint time to generate some blocks time.Sleep(5 * time.Second) dbDir, err := ioutil.TempDir("", "lite-client-example") if err != nil { stdlog.Fatal(err) } defer os.RemoveAll(dbDir) var ( config = rpctest.GetConfig() chainID = config.ChainID() ) primary, err := httpp.New(chainID, config.RPC.ListenAddress) if err != nil { stdlog.Fatal(err) } header, err := primary.SignedHeader(2) if err != nil { stdlog.Fatal(err) } db, err := dbm.NewGoLevelDB("lite-client-db", dbDir) if err != nil { stdlog.Fatal(err) } c, err := lite.NewClient( chainID, lite.TrustOptions{ Period: 504 * time.Hour, // 21 days Height: 2, Hash: header.Hash(), }, primary, []provider.Provider{primary}, // NOTE: primary should not be used here dbs.New(db, chainID), // Logger(log.TestingLogger()), ) if err != nil { stdlog.Fatal(err) } defer func() { c.Cleanup() }() time.Sleep(2 * time.Second) // XXX: 30 * time.Minute clock drift is needed because a) Tendermint strips // monotonic component (see types/time/time.go) b) single instance is being // run. // https://github.com/tendermint/tendermint/issues/4489 h, err := c.Update(time.Now().Add(30 * time.Minute)) if err != nil { stdlog.Fatal(err) } if h != nil && h.Height > 2 { fmt.Println("successful update") } else { fmt.Println("update failed") } }
Output: successful update
func (*Client) VerifyHeader ¶
func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error
VerifyHeader verifies new header against the trusted state. It returns immediately if newHeader exists in trustedStore (no verification is needed). Else it performs one of the two types of verification:
SequentialVerification: verifies that 2/3 of the trusted validator set has signed the new header. If the headers are not adjacent, **all** intermediate headers will be requested. Intermediate headers are not saved to database.
SkippingVerification(trustLevel): verifies that {trustLevel} of the trusted validator set has signed the new header. If it's not the case and the headers are not adjacent, bisection is performed and necessary (not all) intermediate headers will be requested. See the specification for details. 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 and replaced by another provider until all are exhausted.
If, at any moment, SignedHeader or ValidatorSet are not found by the primary provider, provider.ErrSignedHeaderNotFound / provider.ErrValidatorSetNotFound error is returned.
func (*Client) VerifyHeaderAtHeight ¶
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).
height must be > 0.
It returns provider.ErrSignedHeaderNotFound if header is not found by primary.
Example ¶
Manually getting headers and verifying them.
package main import ( "fmt" "io/ioutil" stdlog "log" "os" "time" dbm "github.com/okx/okbchain/libs/tm-db" lite "github.com/okx/okbchain/libs/tendermint/lite2" "github.com/okx/okbchain/libs/tendermint/lite2/provider" httpp "github.com/okx/okbchain/libs/tendermint/lite2/provider/http" dbs "github.com/okx/okbchain/libs/tendermint/lite2/store/db" rpctest "github.com/okx/okbchain/libs/tendermint/rpc/test" ) func main() { // give Tendermint time to generate some blocks time.Sleep(5 * time.Second) dbDir, err := ioutil.TempDir("", "lite-client-example") if err != nil { stdlog.Fatal(err) } defer os.RemoveAll(dbDir) var ( config = rpctest.GetConfig() chainID = config.ChainID() ) primary, err := httpp.New(chainID, config.RPC.ListenAddress) if err != nil { stdlog.Fatal(err) } header, err := primary.SignedHeader(2) if err != nil { stdlog.Fatal(err) } db, err := dbm.NewGoLevelDB("lite-client-db", dbDir) if err != nil { stdlog.Fatal(err) } c, err := lite.NewClient( chainID, lite.TrustOptions{ Period: 504 * time.Hour, // 21 days Height: 2, Hash: header.Hash(), }, primary, []provider.Provider{primary}, // NOTE: primary should not be used here dbs.New(db, chainID), // Logger(log.TestingLogger()), ) if err != nil { stdlog.Fatal(err) } defer func() { c.Cleanup() }() _, err = c.VerifyHeaderAtHeight(3, time.Now()) if err != nil { stdlog.Fatal(err) } h, err := c.TrustedHeader(3) if err != nil { stdlog.Fatal(err) } fmt.Println("got header", h.Height) }
Output: got header 3
type ErrInvalidHeader ¶
type ErrInvalidHeader struct {
Reason error
}
ErrInvalidHeader means the header either failed the basic validation or commit is not signed by 2/3+.
func (ErrInvalidHeader) Error ¶
func (e ErrInvalidHeader) Error() string
type ErrNewValSetCantBeTrusted ¶
type ErrNewValSetCantBeTrusted struct {
Reason types.ErrNotEnoughVotingPowerSigned
}
ErrNewValSetCantBeTrusted means the new validator set cannot be trusted because < 1/3rd (+trustLevel+) of the old validator set has signed.
func (ErrNewValSetCantBeTrusted) Error ¶
func (e ErrNewValSetCantBeTrusted) Error() string
type ErrOldHeaderExpired ¶
ErrOldHeaderExpired means the old (trusted) header has expired according to the given trustingPeriod and current time. If so, the light client must be reset subjectively.
func (ErrOldHeaderExpired) Error ¶
func (e ErrOldHeaderExpired) Error() string
type Option ¶
type Option func(*Client)
Option sets a parameter for the light client.
func ConfirmationFunction ¶
ConfirmationFunction option can be used to prompt to confirm an action. For example, remove newer headers if the light client is being reset with an older header. No confirmation is required by default!
func MaxClockDrift ¶
MaxClockDrift defines how much new (untrusted) header's Time can drift into the future. Default: 10s.
func MaxRetryAttempts ¶
MaxRetryAttempts option can be used to set max attempts before replacing primary with a witness.
func PruningSize ¶
PruningSize option sets the maximum amount of headers & validator set pairs that the light client stores. When Prune() is run, all headers (along with the associated validator sets) that are earlier than the h amount of headers will be removed from the store. Default: 1000. A pruning size of 0 will not prune the lite client at all.
func SequentialVerification ¶
func SequentialVerification() Option
SequentialVerification option configures the light client to sequentially check the headers (every header, in ascending height order). Note this is much slower than SkippingVerification, albeit more secure.
func SkippingVerification ¶
SkippingVerification option configures the light client to skip headers as long as {trustLevel} of the old validator set signed the new header. The bisection algorithm from the specification is used for finding the minimal "trust path".
trustLevel - fraction of the old validator set (in terms of voting power), which must sign the new header in order for us to trust it. NOTE this only applies to non-adjacent headers. For adjacent headers, sequential verification is used.
type TrustOptions ¶
type TrustOptions struct { // tp: trusting period. // // Should be significantly less than the unbonding period (e.g. unbonding // period = 3 weeks, trusting period = 2 weeks). // // More specifically, trusting period + time needed to check headers + time // needed to report and punish misbehavior should be less than the unbonding // period. Period time.Duration // Header's Height and Hash must both be provided to force the trusting of a // particular header. Height int64 Hash []byte }
TrustOptions are the trust parameters needed when a new light client connects to the network or when an existing light client that has been offline for longer than the trusting period connects to the network.
The expectation is the user will get this information from a trusted source like a validator, a friend, or a secure website. A more user friendly solution with trust tradeoffs is that we establish an https based protocol with a default end point that populates this information. Also an on-chain registry of roots-of-trust (e.g. on the Cosmos Hub) seems likely in the future.
func (TrustOptions) ValidateBasic ¶
func (opts TrustOptions) ValidateBasic() error
ValidateBasic performs basic validation.