Skip to content
This repository has been archived by the owner on Jul 6, 2018. It is now read-only.

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanfrey committed Apr 12, 2017
2 parents 855b077 + 8615dba commit 49506d3
Show file tree
Hide file tree
Showing 118 changed files with 4,433 additions and 6,091 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
vendor
.keys
proxy-basecoin
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DOC_PKGS:=./cryptostore ./mock ./proxy ./rpc ./rpc/tests ./storage ./storage/filestorage ./storage/memstorage ./tx ./util
DOC_PKGS:=./certifiers ./extensions ./extensions/basecoin ./proxy ./proxy/types ./tx
REPO:=github.com/tendermint/light-client

.PHONY: install build test list_pkg docs clean_docs get_vendor_deps tools $(DOC_PKGS)
Expand Down
95 changes: 33 additions & 62 deletions README.md

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions certifiers/client/main_test.go
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)
}
89 changes: 89 additions & 0 deletions certifiers/client/provider.go
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
}
}
56 changes: 56 additions & 0 deletions certifiers/client/provider_test.go
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)
}
154 changes: 154 additions & 0 deletions certifiers/dynamic.go
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
}
Loading

0 comments on commit 49506d3

Please sign in to comment.