|
/*
|
|
Package light allows you to securely validate headers
|
|
without a full node.
|
|
|
|
This library pulls together all the crypto and algorithms,
|
|
so given a relatively recent (< unbonding period) known
|
|
validator set, one can get indisputable proof that data is in
|
|
the chain (current state) or detect if the node is lying to
|
|
the client.
|
|
|
|
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. This is a lot of logic to get
|
|
right, to be contained in a small, easy to use library,
|
|
that does this for you, so you can just build nice UI.
|
|
|
|
We design for clients who have no strong trust relationship
|
|
with any tendermint node, just the validator set as a whole.
|
|
Beyond building nice mobile or desktop applications, the
|
|
cosmos hub is another important example of a client,
|
|
that needs undeniable proof without syncing the full chain,
|
|
in order to efficiently implement IBC.
|
|
|
|
Commits
|
|
|
|
There are two main data structures that we pass around - Commit
|
|
and FullCommit. Both of them mirror what information is
|
|
exposed in tendermint rpc.
|
|
|
|
Commit is a block header along with enough validator signatures
|
|
to prove its validity (> 2/3 of the voting power). A FullCommit
|
|
is a Commit along with the full validator set. When the
|
|
validator set doesn't change, the Commit is enough, but since
|
|
the block header only has a hash, we need the FullCommit to
|
|
follow any changes to the validator set.
|
|
|
|
Certifiers
|
|
|
|
A Certifier validates a new Commit given the currently known
|
|
state. There are three different types of Certifiers exposed,
|
|
each one building on the last one, with additional complexity.
|
|
|
|
Static - given the validator set upon initialization. Verifies
|
|
all signatures against that set and if the validator set
|
|
changes, it will reject all headers.
|
|
|
|
Dynamic - This wraps Static and has the same Certify
|
|
method. However, it adds an Update method, which can be called
|
|
with a FullCommit when the validator set changes. If it can
|
|
prove this is a valid transition, it will update the validator
|
|
set.
|
|
|
|
Inquiring - this wraps Dynamic and implements an auto-update
|
|
strategy on top of the Dynamic update. If a call to
|
|
Certify fails as the validator set has changed, then it
|
|
attempts to find a FullCommit and Update to that header.
|
|
To get these FullCommits, it makes use of a Provider.
|
|
|
|
Providers
|
|
|
|
A Provider allows us to store and retrieve the FullCommits,
|
|
to provide memory to the Inquiring Certifier.
|
|
|
|
NewMemStoreProvider - in-memory cache.
|
|
|
|
files.NewProvider - disk backed storage.
|
|
|
|
client.NewHTTPProvider - query tendermint rpc.
|
|
|
|
NewCacheProvider - combine multiple providers.
|
|
|
|
The suggested use for local light clients is
|
|
client.NewHTTPProvider for getting new data (Source),
|
|
and NewCacheProvider(NewMemStoreProvider(),
|
|
files.NewProvider()) to store confirmed headers (Trusted)
|
|
|
|
How We Track Validators
|
|
|
|
Unless you want to blindly trust the node you talk with, you
|
|
need to trace every response back to a hash in a block header
|
|
and validate the commit signatures of that block header match
|
|
the proper validator set. If there is a contant validator
|
|
set, you store it locally upon initialization of the client,
|
|
and check against that every time.
|
|
|
|
Once there is a dynamic validator set, the issue of
|
|
verifying a block becomes a bit more tricky. There is
|
|
background information in a
|
|
github issue (https://github.com/tendermint/tendermint/issues/377).
|
|
|
|
In short, if there is a block at height H with a known
|
|
(trusted) validator set V, and another block at height H'
|
|
(H' > H) with validator set V' != V, then we want a way to
|
|
safely update it.
|
|
|
|
First, get the new (unconfirmed) validator set V' and
|
|
verify H' is internally consistent and properly signed by
|
|
this V'. Assuming it is a valid block, we check that at
|
|
least 2/3 of the validators in V also signed it, meaning
|
|
it would also be valid under our old assumptions.
|
|
That should be enough, but we can also check that the
|
|
V counts for at least 2/3 of the total votes in H'
|
|
for extra safety (we can have a discussion if this is
|
|
strictly required). If we can verify all this,
|
|
then we can accept H' and V' as valid and use that to
|
|
validate all blocks X > H'.
|
|
|
|
If we cannot update directly from H -> H' because there was
|
|
too much change to the validator set, then we can look for
|
|
some Hm (H < Hm < H') with a validator set Vm. Then we try
|
|
to update H -> Hm and Hm -> H' in two separate steps.
|
|
If one of these steps doesn't work, then we continue
|
|
bisecting, until we eventually have to externally
|
|
validate the valdiator set changes at every block.
|
|
|
|
Since we never trust any server in this protocol, only the
|
|
signatures themselves, it doesn't matter if the seed comes
|
|
from a (possibly malicious) node or a (possibly malicious) user.
|
|
We can accept it or reject it based only on our trusted
|
|
validator set and cryptographic proofs. This makes it
|
|
extremely important to verify that you have the proper
|
|
validator set when initializing the client, as that is the
|
|
root of all trust.
|
|
|
|
Or course, this assumes that the known block is within the
|
|
unbonding period to avoid the "nothing at stake" problem.
|
|
If you haven't seen the state in a few months, you will need
|
|
to manually verify the new validator set hash using off-chain
|
|
means (the same as getting the initial hash).
|
|
|
|
*/
|
|
package light
|