This repository has been archived by the owner on Jul 6, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
118 changed files
with
4,433 additions
and
6,091 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
vendor | ||
.keys | ||
proxy-basecoin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package client_test | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
meapp "github.com/tendermint/merkleeyes/app" | ||
nm "github.com/tendermint/tendermint/node" | ||
rpctest "github.com/tendermint/tendermint/rpc/test" | ||
) | ||
|
||
var node *nm.Node | ||
|
||
func TestMain(m *testing.M) { | ||
// start a tendermint node (and merkleeyes) in the background to test against | ||
app := meapp.NewMerkleEyesApp("", 100) | ||
node = rpctest.StartTendermint(app) | ||
code := m.Run() | ||
|
||
// and shut down proper at the end | ||
node.Stop() | ||
node.Wait() | ||
os.Exit(code) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package client | ||
|
||
import ( | ||
"bytes" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/tendermint/light-client/certifiers" | ||
rpcclient "github.com/tendermint/tendermint/rpc/client" | ||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | ||
"github.com/tendermint/tendermint/types" | ||
) | ||
|
||
var _ certifiers.Provider = &Provider{} | ||
|
||
type Provider struct { | ||
node rpcclient.SignClient | ||
lastHeight int | ||
} | ||
|
||
func New(node rpcclient.SignClient) *Provider { | ||
return &Provider{node: node} | ||
} | ||
|
||
func NewHTTP(remote string) *Provider { | ||
return &Provider{ | ||
node: rpcclient.NewHTTP(remote, "/websocket"), | ||
} | ||
} | ||
|
||
// StoreSeed is a noop, as clients can only read from the chain... | ||
func (p *Provider) StoreSeed(_ certifiers.Seed) error { return nil } | ||
|
||
// GetHash gets the most recent validator (only one available) | ||
// and sees if it matches | ||
// | ||
// TODO: improve when the rpc interface supports more functionality | ||
func (p *Provider) GetByHash(hash []byte) (certifiers.Seed, error) { | ||
var seed certifiers.Seed | ||
vals, err := p.node.Validators() | ||
// if we get no validators, or a different height, return an error | ||
if err != nil { | ||
return seed, errors.WithStack(err) | ||
} | ||
p.updateHeight(vals.BlockHeight) | ||
vhash := types.NewValidatorSet(vals.Validators).Hash() | ||
if !bytes.Equal(hash, vhash) { | ||
return seed, certifiers.ErrSeedNotFound() | ||
} | ||
return p.buildSeed(vals) | ||
} | ||
|
||
// GetByHeight gets the most recent validator (only one available) | ||
// and sees if it matches | ||
// | ||
// TODO: keep track of most recent height, it will never go down | ||
// | ||
// TODO: improve when the rpc interface supports more functionality | ||
func (p *Provider) GetByHeight(h int) (certifiers.Seed, error) { | ||
var seed certifiers.Seed | ||
vals, err := p.node.Validators() | ||
// if we get no validators, or a different height, return an error | ||
if err != nil { | ||
return seed, errors.WithStack(err) | ||
} | ||
p.updateHeight(vals.BlockHeight) | ||
if vals.BlockHeight > h { | ||
return seed, certifiers.ErrSeedNotFound() | ||
} | ||
return p.buildSeed(vals) | ||
} | ||
|
||
func (p *Provider) buildSeed(vals *ctypes.ResultValidators) (certifiers.Seed, error) { | ||
seed := certifiers.Seed{ | ||
Validators: vals.Validators, | ||
} | ||
// looks good, now get the commits and build a seed | ||
commit, err := p.node.Commit(vals.BlockHeight) | ||
if err == nil { | ||
seed.Header = commit.Header | ||
seed.Commit = commit.Commit | ||
} | ||
return seed, errors.WithStack(err) | ||
} | ||
|
||
func (p *Provider) updateHeight(h int) { | ||
if h > p.lastHeight { | ||
p.lastHeight = h | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package client_test | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/tendermint/light-client/certifiers" | ||
"github.com/tendermint/light-client/certifiers/client" | ||
rpctest "github.com/tendermint/tendermint/rpc/test" | ||
) | ||
|
||
func TestProvider(t *testing.T) { | ||
assert, require := assert.New(t), require.New(t) | ||
|
||
rpcAddr := rpctest.GetConfig().GetString("rpc_laddr") | ||
chainID := rpctest.GetConfig().GetString("chain_id") | ||
p := client.NewHTTP(rpcAddr) | ||
require.NotNil(t, p) | ||
|
||
// let it produce some blocks | ||
time.Sleep(500 * time.Millisecond) | ||
|
||
// let's get the highest block | ||
seed, err := p.GetByHeight(5000) | ||
require.Nil(err, "%+v", err) | ||
sh := seed.Height() | ||
vhash := seed.Header.ValidatorsHash | ||
assert.True(sh < 5000) | ||
|
||
// let's check this is valid somehow | ||
assert.Nil(seed.ValidateBasic(chainID)) | ||
cert := certifiers.NewStatic(chainID, seed.Validators) | ||
|
||
// can't get a lower one | ||
seed, err = p.GetByHeight(sh - 1) | ||
assert.NotNil(err) | ||
assert.True(certifiers.SeedNotFound(err)) | ||
|
||
// also get by hash (given the match) | ||
seed, err = p.GetByHash(vhash) | ||
require.Nil(err, "%+v", err) | ||
require.Equal(vhash, seed.Header.ValidatorsHash) | ||
err = cert.Certify(seed.Checkpoint) | ||
assert.Nil(err, "%+v", err) | ||
|
||
// get by hash fails without match | ||
seed, err = p.GetByHash([]byte("foobar")) | ||
assert.NotNil(err) | ||
assert.True(certifiers.SeedNotFound(err)) | ||
|
||
// storing the seed silently ignored | ||
err = p.StoreSeed(seed) | ||
assert.Nil(err, "%+v", err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package certifiers | ||
|
||
import ( | ||
"fmt" | ||
|
||
lc "github.com/tendermint/light-client" | ||
"github.com/tendermint/tendermint/types" | ||
) | ||
|
||
// DynamicCertifier uses a StaticCertifier to evaluate the checkpoint | ||
// but allows for a change, if we present enough proof | ||
// | ||
// TODO: do we keep a long history so we can use our memory to validate | ||
// checkpoints from previously valid validator sets???? | ||
type DynamicCertifier struct { | ||
Cert *StaticCertifier | ||
LastHeight int | ||
} | ||
|
||
func NewDynamic(chainID string, vals []*types.Validator) *DynamicCertifier { | ||
return &DynamicCertifier{ | ||
Cert: NewStatic(chainID, vals), | ||
LastHeight: 0, | ||
} | ||
} | ||
|
||
func (c *DynamicCertifier) assertCertifier() lc.Certifier { | ||
return c | ||
} | ||
|
||
// Certify handles this with | ||
func (c *DynamicCertifier) Certify(check lc.Checkpoint) error { | ||
err := c.Cert.Certify(check) | ||
if err == nil { | ||
// update last seen height if input is valid | ||
c.LastHeight = check.Height() | ||
} | ||
return err | ||
} | ||
|
||
// Update will verify if this is a valid change and update | ||
// the certifying validator set if safe to do so. | ||
// | ||
// Returns an error if update is impossible (invalid proof or TooMuchChange) | ||
func (c *DynamicCertifier) Update(check lc.Checkpoint, vals []*types.Validator) error { | ||
// ignore all checkpoints in the past -> only to the future | ||
if check.Height() <= c.LastHeight { | ||
return ErrPastTime() | ||
} | ||
|
||
// first, verify if the input is self-consistent.... | ||
err := check.ValidateBasic(c.Cert.ChainID) | ||
if err != nil { | ||
return err | ||
} | ||
vset := types.NewValidatorSet(vals) | ||
|
||
// TODO: now, make sure not too much change... meaning this commit | ||
// would be approved by the currently known validator set | ||
// as well as the new set | ||
err = VerifyCommitAny(c.Cert.VSet, vset, c.Cert.ChainID, | ||
check.Commit.BlockID, check.Header.Height, check.Commit) | ||
if err != nil { | ||
return ErrTooMuchChange() | ||
} | ||
|
||
// looks good, we can update | ||
c.Cert = NewStatic(c.Cert.ChainID, vals) | ||
c.LastHeight = check.Height() | ||
return nil | ||
} | ||
|
||
// VerifyCommitAny will check to see if the set would | ||
// be valid with a different validator set. | ||
// | ||
// old is the validator set that we know | ||
// * over 2/3 of the power in old signed this block | ||
// | ||
// cur is the validator set that signed this block | ||
// * only votes from old are sufficient for 2/3 majority | ||
// in the new set as well | ||
// | ||
// That means that: | ||
// * 10% of the valset can't just declare themselves kings | ||
// * If the validator set is 3x old size, we need more proof to trust | ||
// | ||
// *** TODO: move this. | ||
// It belongs in tendermint/types/validator_set.go: VerifyCommitAny | ||
func VerifyCommitAny(old, cur *types.ValidatorSet, chainID string, | ||
blockID types.BlockID, height int, commit *types.Commit) error { | ||
|
||
if cur.Size() != len(commit.Precommits) { | ||
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", cur.Size(), len(commit.Precommits)) | ||
} | ||
if height != commit.Height() { | ||
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height()) | ||
} | ||
|
||
oldVotingPower := int64(0) | ||
curVotingPower := int64(0) | ||
seen := map[int]bool{} | ||
round := commit.Round() | ||
|
||
for idx, precommit := range commit.Precommits { | ||
// first check as in VerifyCommit | ||
if precommit == nil { | ||
continue | ||
} | ||
if precommit.Height != height { | ||
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, precommit.Height) | ||
} | ||
if precommit.Round != round { | ||
return fmt.Errorf("Invalid commit -- wrong round: %v vs %v", round, precommit.Round) | ||
} | ||
if precommit.Type != types.VoteTypePrecommit { | ||
return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx) | ||
} | ||
if !blockID.Equals(precommit.BlockID) { | ||
continue // Not an error, but doesn't count | ||
} | ||
|
||
// we only grab by address, ignoring unknown validators | ||
vi, ov := old.GetByAddress(precommit.ValidatorAddress) | ||
if ov == nil || seen[vi] { | ||
continue // missing or double vote... | ||
} | ||
seen[vi] = true | ||
|
||
// Validate signature old school | ||
precommitSignBytes := types.SignBytes(chainID, precommit) | ||
if !ov.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { | ||
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit) | ||
} | ||
// Good precommit! | ||
oldVotingPower += ov.VotingPower | ||
|
||
// check new school | ||
_, cv := cur.GetByIndex(idx) | ||
// is this needed? | ||
if !cv.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { | ||
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit) | ||
} | ||
curVotingPower += cv.VotingPower | ||
} | ||
|
||
if oldVotingPower <= old.TotalVotingPower()*2/3 { | ||
return fmt.Errorf("Invalid commit -- insufficient old voting power: got %v, needed %v", | ||
oldVotingPower, (old.TotalVotingPower()*2/3 + 1)) | ||
} else if curVotingPower <= cur.TotalVotingPower()*2/3 { | ||
return fmt.Errorf("Invalid commit -- insufficient cur voting power: got %v, needed %v", | ||
curVotingPower, (cur.TotalVotingPower()*2/3 + 1)) | ||
} | ||
return nil | ||
} |
Oops, something went wrong.