Compare commits

..

10 Commits

Author SHA1 Message Date
Anton Kaliaev
7402276325 evidence submission 2019-08-28 16:02:45 +04:00
Anton Kaliaev
804023299a should be done outside of Verifier 2019-08-28 15:40:50 +04:00
Anton Kaliaev
09db7253d5 write about evidence handling 2019-08-28 14:20:42 +04:00
Anton Kaliaev
ed15b562df Merge branch 'master' into anton/lc-adr-2 2019-08-28 13:13:44 +04:00
Anton Kaliaev
29e148a36d one source
although we may want multiple alternative sources for a) backup in case
primary fails b) checking primary source
2019-08-27 15:36:33 +04:00
Anton Kaliaev
c696489da4 remove trustLevelAdj
no reason to set a value higher than 2/3
2019-08-27 11:20:30 +04:00
Anton Kaliaev
91f66592f3 explain trustLevel and trustLevelAdj
also

- add trustLevelAdj param to SequentialVerification
- rename LinearVerification to SequentialVerification
2019-08-26 16:16:00 +04:00
Anton Kaliaev
a79a325011 Merge branch 'master' into anton/lc-adr-2 2019-08-26 10:58:27 +04:00
Anton Kaliaev
fc8dc9bfde Client struct 2019-08-22 14:37:54 +04:00
Anton Kaliaev
c13863ad0f docs: write about implementation in Light Client ADR 2019-08-22 14:31:34 +04:00

View File

@@ -3,6 +3,7 @@
## Changelog
* 13-07-2019: Initial draft
* 14-08-2019: Address cwgoes comments
* 22-08-2019: Second version
## Context
@@ -53,22 +54,25 @@ network or when a light client that has been offline for longer than the
unbonding period connects to the network. Specifically, the node needs to
initialize the following structure before syncing from user input:
```
```go
type TrustOptions struct {
// Required: only trust commits up to this old.
// Should be equal to the unbonding period minus some delta for evidence reporting.
TrustPeriod time.Duration `json:"trust-period"`
// Required: only trust commits up to this old.
// Should be equal to the unbonding period minus some delta for evidence reporting.
TrustPeriod time.Duration `json:"trust-period"`
// Option 1: TrustHeight and TrustHash can both be provided
// to force the trusting of a particular height and hash.
// If the latest trusted height/hash is more recent, then this option is
// ignored.
TrustHeight int64 `json:"trust-height"`
TrustHash []byte `json:"trust-hash"`
// Required: validator whom we've got the TrustHeight/Hash from
ValidatorAddress types.Address `json:"validator-address"`
// Option 2: Callback can be set to implement a confirmation
// step if the trust store is uninitialized, or expired.
Callback func(height int64, hash []byte) error
// Option 1: TrustHeight and TrustHash can both be provided
// to force the trusting of a particular height and hash.
// If the latest trusted height/hash is more recent, then this option is
// ignored.
TrustHeight int64 `json:"trust-height"`
TrustHash []byte `json:"trust-hash"`
// Option 2: Callback can be set to implement a confirmation
// step if the trust store is uninitialized, or expired.
Callback func(height int64, hash []byte) error
}
```
@@ -121,6 +125,182 @@ network usage.
Check out the formal specification
[here](https://github.com/tendermint/tendermint/blob/master/docs/spec/consensus/light-client.md).
### Implementation
There are two primary modes of usage right now:
1) Trusted RPC proxy (wrapping multiple RPC clients + verification)
2) Part of the IBC light client (only verification bit, no RPC) [spec](https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics)
First, we'll need something, which will provide us secure headers & validator sets.
```go
type Provider interface {
// 0 - latest
GetFullCommit(height int64) (FullCommit, error)
}
```
In case of the proxy it will be a `http` provider (wrapping RPC client). For
IBC, it will be a `ibc` provider, receiving information from IBC transactions.
Once we have the information, we need to verify it.
```go
type mode int
const (
sequential mode = iota
bisecting
)
// default mode - DefaultBisectingVerification
type Verifier struct {
chainID string
options TrustOptions
lastVerifiedHeight int64
logger log.Logger
mode mode
trustLevel float
// Source of new FullCommit(s).
source Provider
// Alternative sources for checking the primary for misbehavior by comparing data.
// If the primary misbehaves, we report the evidence to them.
verifiers []ProviderAndEvidenceReporter
// Where trusted FullCommit(s) are stored.
trusted PersistentProvider
}
```
Since providers themselves don't know when they have received a new header (or
may choose to do so upon a request), we must add a new function to `Verifier` -
`Verify(height int64) error` (0 - latest). It will try to fetch a new header &
validator set and verify it. nop if already verified.
**Sequential vs bisecting verifier**
Verifier should use bisection by default, but provide options to choose a
different mode OR tweak bisection.
```go
func SequentialVerification() Option {
return func(v *Verifier) {
v.mode = sequential
}
}
// trustLevel - maximum change between two not consequitive headers in terms of
// validators & their respective voting power, required to trust a new header
// (default: 1/3).
func BisectingVerification(trustLevel float) Option {
if trustLevel > 1 || trustLevel < 1/3 {
panic(fmt.Sprintf("trustLevel must be within [1/3, 1], given %v", trustLevel))
}
return func(v *Verifier) {
v.mode = bisecting
v.trustLevel = trustLevel
}
}
var DefaultBisectingVerification = func() Option {
return BisectingVerification(1/3)
}
```
Once we verified the header, we will need to store it somewhere.
```
type PersistentProvider interface {
Provider
SaveFullCommit(fc FullCommit) error
}
```
In case of the proxy it will be a `db` provider (levelDB + in-memory cache in
front). For IBC, it will be a `keeper` provider.
**Minimal test for (1)**
```go
c, err := lite.NewClient(
chainID,
lite.TrustOptions{TrustPeriod: 336 * time.Hour},
rpcclient.NewHTTP(remote1, "/websocket"),
)
require.NoError(t, err)
commit, err := c.Commit()
require.NoError(t, err)
assert.Equal(t, chainID, commit.ChainID)
```
`lite.Client` here is a `struct`, which uses `Verifier` and exposes
`rpcclient.Client` API.
```go
type Client struct {
verifier *Verifier
client rpcclient.Client
}
var rpcclient.Client = (*Client)(nil)
```
**Minimal test for (2)**
```go
c, err := lite.NewVerifier(
chainID,
lite.TrustOptions{TrustPeriod: 24 * time.Hour},
ibc.New(chainID),
Trusted(ibcKeeper{}),
)
require.NoError(t, err)
err = c.Verify(height)
require.NoError(t, err)
```
**Evidence Handling and Reporting**
light client should also be able to submit evidence of malfeasance and handle
evidence coming from a full node or another source.
We'll need to add evidence to `FullCommit`.
```go
type FullCommit struct {
SignedHeader types.SignedHeader `json:"signed_header"`
Validators *types.ValidatorSet `json:"validator_set"`
NextValidators *types.ValidatorSet `json:"next_validator_set"`
Evidence types.EvidenceList `json:"evidence"`
}
```
When/if evidence is received, client should check it and disconnect from the
node if `evidence.Address == TrustOptions.ValidatorAddress`. It's unwise to
think that a node will send an evidence of its misbehavior. That's why we
should also check `verifiers` sources in the background.
_Evidence handling can be implemented in the second version._
Submitting an evidence comes down to calling `ReportEvidence(ev types.Evidence)
error` on the `verifiers` sources.
```go
type ProviderAndEvidenceReporter interface {
Provider
ReportEvidence(ev types.Evidence) error
}
```
## Status
Accepted.