Documentation ¶
Overview ¶
package light 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 light 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 light 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 light 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 light 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("light-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/tendermint/blob/master/spec/light-client/verification/README.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://github.com/tendermint/tendermint/tree/master/spec/light-client for the light client specification.
Index ¶
- Variables
- type Client
- func NewClient(ctx context.Context, chainID string, primary provider.Provider, ...) (*Client, error)
- func NewClientAtHeight(ctx context.Context, height int64, chainID string, primary provider.Provider, ...) (*Client, error)
- func NewClientFromTrustedStore(chainID string, primary provider.Provider, witnesses []provider.Provider, ...) (*Client, error)
- func NewHTTPClient(ctx context.Context, chainID string, primaryAddress string, ...) (*Client, error)
- func NewHTTPClientFromTrustedStore(chainID string, primaryAddress string, witnessesAddresses []string, ...) (*Client, error)
- func (c *Client) AddProvider(p provider.Provider)
- 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) Status(_ctx context.Context) *types.LightClientInfo
- func (c *Client) TrustedLightBlock(height int64) (*types.LightBlock, error)
- func (c *Client) Update(ctx context.Context, now time.Time) (*types.LightBlock, error)
- func (c *Client) VerifyHeader(ctx context.Context, newHeader *types.Header, now time.Time) error
- func (c *Client) VerifyLightBlockAtHeight(ctx context.Context, height int64, now time.Time) (*types.LightBlock, error)
- func (c *Client) Witnesses() []provider.Provider
- type DashCoreVerifier
- type ErrInvalidHeader
- type ErrNewValSetCantBeTrusted
- type ErrOldHeaderExpired
- type ErrVerificationFailed
- type Option
Constants ¶
This section is empty.
Variables ¶
var ErrFailedHeaderCrossReferencing = errors.New("all witnesses have either not responded, don't have the " +
" blocks or sent invalid blocks. You should look to change your witnesses" +
" or review the light client's logs for more information")
ErrFailedHeaderCrossReferencing is returned when the detector was not able to cross reference the header with any of the connected witnesses.
var ErrLightClientAttack = errors.New(`attempted attack detected.
Light client received valid conflicting header from witness.
Unable to verify header. Evidence has been sent to both providers.
Check logs for full evidence and trace`,
)
ErrLightClientAttack is returned when the light client has detected an attempt to verify a false header and has sent the evidence to either a witness or primary.
var ErrNoDashCoreClient = errors.New("no dash core client. please reset light client")
ErrNoDashCoreClient means that there is no dash core client to connect to
var ErrNoWitnesses = errors.New("no witnesses connected. please reset light client")
ErrNoWitnesses means that there are not enough witnesses connected to continue running the light client.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client represents a light client, connected to a single chain, which gets light blocks 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( ctx context.Context, chainID string, primary provider.Provider, witnesses []provider.Provider, trustedStore store.Store, dashCoreRPCClient dashcore.QuorumVerifier, options ...Option) (*Client, error)
NewClient returns a new light client. It returns an error if it fails to obtain the light block from the primary, or they are invalid (e.g. trust hash does not match with the one from the headers).
Witnesses are providers, which will be used for cross-checking the primary provider. At least one witness should be given when skipping verification is used (default). A verified header is compared with the headers at same height obtained from the specified witnesses. A witness can become a primary iff the current primary is unavailable.
See all Option(s) for the additional configuration.
func NewClientAtHeight ¶
func NewClientFromTrustedStore ¶
func NewClientFromTrustedStore( chainID string, primary provider.Provider, witnesses []provider.Provider, trustedStore store.Store, dashCoreRPCClient dashcore.QuorumVerifier, options ...Option) (*Client, error)
NewClientFromTrustedStore initializes an existing client from the trusted store. It does not check that the providers have the same trusted block.
func NewHTTPClient ¶
func NewHTTPClient( ctx context.Context, chainID string, primaryAddress string, witnessesAddresses []string, trustedStore store.Store, dashCoreRPCClient dashcore.QuorumVerifier, options ...Option) (*Client, error)
NewHTTPClient initiates an instance of a light client using HTTP addresses for both the primary provider and witnesses of the light 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, primaryAddress string, witnessesAddresses []string, trustedStore store.Store, dashCoreRPCClient dashcore.Client, options ...Option) (*Client, error)
NewHTTPClientFromTrustedStore initiates an instance of a light 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) AddProvider ¶
AddProvider adds a providers to the light clients set
NOTE: The light client does not check for uniqueness
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) TrustedLightBlock ¶
func (c *Client) TrustedLightBlock(height int64) (*types.LightBlock, error)
TrustedLightBlock returns a trusted light block at the given height (0 - the latest).
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) Update ¶
Update attempts to advance the state by downloading the latest light block and verifying it. It returns a new light block on a successful update. Otherwise, it returns nil (plus an error, if any).
func (*Client) VerifyHeader ¶
VerifyHeader verifies a 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, verifySkipping 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/tendermint/blob/master/spec/light-client/README.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) verifySkipping 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, a LightBlock is not found by the primary provider as part of verification then the provider will be replaced by another and the process will restart.
func (*Client) VerifyLightBlockAtHeight ¶
func (c *Client) VerifyLightBlockAtHeight(ctx context.Context, height int64, now time.Time) (*types.LightBlock, error)
VerifyLightBlockAtHeight fetches the light block at the given height and verifies it. It returns the block immediately if it exists in the trustedStore (no verification is needed).
height must be > 0.
It returns provider.ErrlightBlockNotFound if light block is not found by primary.
It will replace the primary provider if an error from a request to the provider occurs
type DashCoreVerifier ¶
type DashCoreVerifier struct {
// contains filtered or unexported fields
}
DashCoreVerifier is used to verify signatures of light blocks
func NewDashCoreVerifierClient ¶
func NewDashCoreVerifierClient( host string, rpcUsername string, rpcPassword string, defaultQuorumType btcjson.LLMQType, ) (*DashCoreVerifier, error)
NewDashCoreVerifierClient returns an instance of SignerClient. it will start the endpoint (if not already started)
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 ErrVerificationFailed ¶
ErrVerificationFailed means either sequential or skipping verification has failed to verify from header #1 to header #2 due to some reason.
func (ErrVerificationFailed) Error ¶
func (e ErrVerificationFailed) Error() string
func (ErrVerificationFailed) Unwrap ¶
func (e ErrVerificationFailed) Unwrap() error
Unwrap returns underlying reason.
type Option ¶
type Option func(*Client)
Option sets a parameter for the light client.
func DashCoreVerification ¶
func DashCoreVerification() Option
DashCoreVerification option configures the light client to go to the last block and verify it was signed by the Quorum that is has in quorum hash.
func MaxBlockLag ¶
MaxBlockLag represents the maximum time difference between the realtime that a block is received and the timestamp of that block. One can approximate it to the maximum block production time
As an example, say the light client received block B at a time 12:05 (this is the real time) and the time on the block was 12:00. Then the lag here is 5 minutes. Default: 10s
func MaxClockDrift ¶
MaxClockDrift defines how much new header's time can drift into the future relative to the light clients local time. Default: 10s.
func PruningSize ¶
PruningSize option sets the maximum amount of light blocks that the light client stores. When Prune() is run, all light blocks that are earlier than the h amount of light blocks will be removed from the store. Default: 1000. A pruning size of 0 will not prune the light client at all.