Browse Source

lite2: make witnesses mandatory (#4358)

* lite2: make witnesses mandatory

at least one witness is required

* lite2: return an error if there are no witnesses

https://github.com/tendermint/tendermint/pull/4358#pullrequestreview-350635444

* cmd/lite: add witnesses flag

* fix linter errors
pull/4365/head
Anton Kaliaev 5 years ago
committed by GitHub
parent
commit
1edb542f99
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 252 additions and 174 deletions
  1. +36
    -10
      cmd/tendermint/commands/lite.go
  2. +31
    -22
      lite2/client.go
  3. +176
    -136
      lite2/client_test.go
  4. +9
    -6
      lite2/example_test.go

+ 36
- 10
cmd/tendermint/commands/lite.go View File

@ -2,6 +2,7 @@ package commands
import (
"net/http"
"strings"
"time"
"github.com/pkg/errors"
@ -12,6 +13,7 @@ import (
tmos "github.com/tendermint/tendermint/libs/os"
lite "github.com/tendermint/tendermint/lite2"
"github.com/tendermint/tendermint/lite2/provider"
httpp "github.com/tendermint/tendermint/lite2/provider/http"
lproxy "github.com/tendermint/tendermint/lite2/proxy"
lrpc "github.com/tendermint/tendermint/lite2/rpc"
@ -36,9 +38,10 @@ just with added trust and running locally.`,
var (
listenAddr string
nodeAddr string
primaryAddr string
chainID string
home string
witnessesAddrs string
maxOpenConnections int
trustingPeriod time.Duration
@ -47,9 +50,17 @@ var (
)
func init() {
LiteCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888", "Serve the proxy on the given address")
LiteCmd.Flags().StringVar(&nodeAddr, "node", "tcp://localhost:26657", "Connect to a Tendermint node at this address")
LiteCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888",
"Serve the proxy on the given address")
LiteCmd.Flags().StringVar(&primaryAddr, "primary", "tcp://localhost:26657",
"Connect to a Tendermint node at this address")
LiteCmd.Flags().StringVar(&witnessesAddrs, "witnesses", "",
"Tendermint nodes to cross-check the primary node, comma-separated")
LiteCmd.Flags().StringVar(&chainID, "chain-id", "tendermint", "Specify the Tendermint chain ID")
LiteCmd.Flags().StringVar(&home, "home-dir", ".tendermint-lite", "Specify the home directory")
LiteCmd.Flags().IntVar(
&maxOpenConnections,
@ -57,19 +68,33 @@ func init() {
900,
"Maximum number of simultaneous connections (including WebSocket).")
LiteCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour, "Trusting period. Should be significantly less than the unbonding period")
LiteCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour,
"Trusting period. Should be significantly less than the unbonding period")
LiteCmd.Flags().Int64Var(&trustedHeight, "trusted-height", 1, "Trusted header's height")
LiteCmd.Flags().BytesHexVar(&trustedHash, "trusted-hash", []byte{}, "Trusted header's hash")
}
func runProxy(cmd *cobra.Command, args []string) error {
liteLogger := logger.With("module", "lite")
logger.Info("Connecting to Tendermint node...")
// First, connect a client
node, err := rpcclient.NewHTTP(nodeAddr, "/websocket")
logger.Info("Connecting to the primary node...")
rpcClient, err := rpcclient.NewHTTP(chainID, primaryAddr)
if err != nil {
return errors.Wrap(err, "new HTTP client")
return errors.Wrapf(err, "http client for %s", primaryAddr)
}
primary := httpp.NewWithClient(chainID, rpcClient)
logger.Info("Connecting to the witness nodes...")
addrs := strings.Split(witnessesAddrs, ",")
witnesses := make([]provider.Provider, len(addrs))
for i, addr := range addrs {
p, err := httpp.New(chainID, addr)
if err != nil {
return errors.Wrapf(err, "http provider for %s", addr)
}
witnesses[i] = p
}
logger.Info("Creating client...")
@ -84,7 +109,8 @@ func runProxy(cmd *cobra.Command, args []string) error {
Height: trustedHeight,
Hash: trustedHash,
},
httpp.NewWithClient(chainID, node),
primary,
witnesses,
dbs.New(db, chainID),
lite.Logger(liteLogger),
)
@ -96,7 +122,7 @@ func runProxy(cmd *cobra.Command, args []string) error {
Addr: listenAddr,
Config: &rpcserver.Config{MaxOpenConnections: maxOpenConnections},
Codec: amino.NewCodec(),
Client: lrpc.NewClient(node, c),
Client: lrpc.NewClient(rpcClient, c),
Logger: liteLogger,
}
// Stop upon receiving SIGTERM or CTRL-C.


+ 31
- 22
lite2/client.go View File

@ -75,29 +75,12 @@ func SequentialVerification() Option {
// applies to non-adjacent headers. For adjacent headers, sequential
// verification is used.
func SkippingVerification(trustLevel tmmath.Fraction) Option {
if err := ValidateTrustLevel(trustLevel); err != nil {
panic(err)
}
return func(c *Client) {
c.verificationMode = skipping
c.trustLevel = trustLevel
}
}
// Witnesses option can be used to supply providers, which will be used for
// cross-checking the primary provider. A witness can become a primary iff the
// current primary is unavailable.
func Witnesses(providers []provider.Provider) Option {
return func(c *Client) {
for _, witness := range providers {
if witness.ChainID() != c.ChainID() {
panic("Witness chainID is not equal to the Lite Client chainID")
}
}
c.witnesses = providers
}
}
// UpdatePeriod option can be used to change default polling period (5s).
func UpdatePeriod(d time.Duration) Option {
return func(c *Client) {
@ -175,11 +158,16 @@ type Client struct {
// obtain the header & vals from the primary or they are invalid (e.g. trust
// hash does not match with the one from the header).
//
// Witnesses are providers, which will be used for cross-checking the primary
// provider. At least one witness must be given. A witness can become a primary
// iff the current primary is unavailable.
//
// See all Option(s) for the additional configuration.
func NewClient(
chainID string,
trustOptions TrustOptions,
primary provider.Provider,
witnesses []provider.Provider,
trustedStore store.Store,
options ...Option) (*Client, error) {
@ -189,6 +177,7 @@ func NewClient(
verificationMode: skipping,
trustLevel: DefaultTrustLevel,
primary: primary,
witnesses: witnesses,
trustedStore: trustedStore,
updatePeriod: defaultUpdatePeriod,
removeNoLongerTrustedHeadersPeriod: defaultRemoveNoLongerTrustedHeadersPeriod,
@ -201,6 +190,24 @@ func NewClient(
o(c)
}
// Validate the number of witnesses.
if len(c.witnesses) < 1 {
return nil, errors.New("expected at least one witness")
}
// Verify witnesses are all on the same chain.
for i, w := range witnesses {
if w.ChainID() != chainID {
return nil, errors.Errorf("witness #%d: %v is on another chain %s, expected %s",
i, w, w.ChainID(), chainID)
}
}
// Validate trust level.
if err := ValidateTrustLevel(c.trustLevel); err != nil {
return nil, err
}
if err := c.restoreTrustedHeaderAndNextVals(); err != nil {
return nil, err
}
@ -523,11 +530,9 @@ func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.Vali
return errors.Errorf("header at more recent height #%d exists", c.trustedHeader.Height)
}
if len(c.witnesses) > 0 {
if err := c.compareNewHeaderWithRandomWitness(newHeader); err != nil {
c.logger.Error("Error when comparing new header with one from a witness", "err", err)
return err
}
if err := c.compareNewHeaderWithRandomWitness(newHeader); err != nil {
c.logger.Error("Error when comparing new header with one from a witness", "err", err)
return err
}
var err error
@ -733,6 +738,10 @@ func (c *Client) fetchHeaderAndValsAtHeight(height int64) (*types.SignedHeader,
// compare header with one from a random witness.
func (c *Client) compareNewHeaderWithRandomWitness(h *types.SignedHeader) error {
if len(c.witnesses) == 0 {
return errors.New("could not find any witnesses")
}
// 1. Pick a witness.
witness := c.witnesses[tmrand.Intn(len(c.witnesses))]


+ 176
- 136
lite2/client_test.go View File

@ -11,6 +11,7 @@ import (
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/lite2/provider"
mockp "github.com/tendermint/tendermint/lite2/provider/mock"
dbs "github.com/tendermint/tendermint/lite2/store/db"
"github.com/tendermint/tendermint/types"
@ -127,6 +128,11 @@ func TestClient_SequentialVerification(t *testing.T) {
tc.otherHeaders,
tc.vals,
),
[]provider.Provider{mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
)},
dbs.New(dbm.NewMemDB(), chainID),
SequentialVerification(),
)
@ -228,6 +234,11 @@ func TestClient_SkippingVerification(t *testing.T) {
tc.otherHeaders,
tc.vals,
),
[]provider.Provider{mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
)},
dbs.New(dbm.NewMemDB(), chainID),
SkippingVerification(DefaultTrustLevel),
)
@ -264,6 +275,26 @@ func TestClientRemovesNoLongerTrustedHeaders(t *testing.T) {
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(4*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -271,25 +302,8 @@ func TestClientRemovesNoLongerTrustedHeaders(t *testing.T) {
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(4*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
),
primary,
[]provider.Provider{primary},
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
@ -333,6 +347,18 @@ func TestClient_Cleanup(t *testing.T) {
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -340,17 +366,8 @@ func TestClient_Cleanup(t *testing.T) {
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
),
primary,
[]provider.Provider{primary},
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
@ -388,6 +405,18 @@ func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, vals)
require.NoError(t, err)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -395,17 +424,8 @@ func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
@ -430,6 +450,18 @@ func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header1,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -437,17 +469,8 @@ func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
Height: 1,
Hash: header1.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header1,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
@ -487,6 +510,18 @@ func TestClientRestoreTrustedHeaderAfterStartup2(t *testing.T) {
header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -494,18 +529,8 @@ func TestClientRestoreTrustedHeaderAfterStartup2(t *testing.T) {
Height: 2,
Hash: header2.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
@ -535,6 +560,19 @@ func TestClientRestoreTrustedHeaderAfterStartup2(t *testing.T) {
header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header1,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -542,18 +580,8 @@ func TestClientRestoreTrustedHeaderAfterStartup2(t *testing.T) {
Height: 2,
Hash: header2.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header1,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
@ -595,6 +623,19 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
err = trustedStore.SaveSignedHeaderAndNextValidatorSet(header2, vals)
require.NoError(t, err)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -602,18 +643,8 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
@ -650,6 +681,17 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
err = trustedStore.SaveSignedHeaderAndNextValidatorSet(header2, vals)
require.NoError(t, err)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header1,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -657,16 +699,8 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
Height: 1,
Hash: header1.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header1,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
@ -702,6 +736,26 @@ func TestClient_Update(t *testing.T) {
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -709,25 +763,8 @@ func TestClient_Update(t *testing.T) {
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
),
primary,
[]provider.Provider{primary},
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
@ -760,6 +797,26 @@ func TestClient_Concurrency(t *testing.T) {
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@ -767,25 +824,8 @@ func TestClient_Concurrency(t *testing.T) {
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
),
primary,
[]provider.Provider{primary},
dbs.New(dbm.NewMemDB(), chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),


+ 9
- 6
lite2/example_test.go View File

@ -12,6 +12,7 @@ import (
"github.com/tendermint/tendermint/abci/example/kvstore"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/lite2/provider"
httpp "github.com/tendermint/tendermint/lite2/provider/http"
dbs "github.com/tendermint/tendermint/lite2/store/db"
rpctest "github.com/tendermint/tendermint/rpc/test"
@ -33,12 +34,12 @@ func TestExample_Client_AutoUpdate(t *testing.T) {
chainID = config.ChainID()
)
provider, err := httpp.New(chainID, config.RPC.ListenAddress)
primary, err := httpp.New(chainID, config.RPC.ListenAddress)
if err != nil {
stdlog.Fatal(err)
}
header, err := provider.SignedHeader(2)
header, err := primary.SignedHeader(2)
if err != nil {
stdlog.Fatal(err)
}
@ -55,7 +56,8 @@ func TestExample_Client_AutoUpdate(t *testing.T) {
Height: 2,
Hash: header.Hash(),
},
provider,
primary,
[]provider.Provider{primary}, // TODO: primary should not be used here
dbs.New(db, chainID),
UpdatePeriod(1*time.Second),
Logger(log.TestingLogger()),
@ -99,12 +101,12 @@ func TestExample_Client_ManualUpdate(t *testing.T) {
chainID = config.ChainID()
)
provider, err := httpp.New(chainID, config.RPC.ListenAddress)
primary, err := httpp.New(chainID, config.RPC.ListenAddress)
if err != nil {
stdlog.Fatal(err)
}
header, err := provider.SignedHeader(2)
header, err := primary.SignedHeader(2)
if err != nil {
stdlog.Fatal(err)
}
@ -121,7 +123,8 @@ func TestExample_Client_ManualUpdate(t *testing.T) {
Height: 2,
Hash: header.Hash(),
},
provider,
primary,
[]provider.Provider{primary}, // TODO: primary should not be used here
dbs.New(db, chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),


Loading…
Cancel
Save