diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 45b682dc2..aac3751b5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -17,6 +17,11 @@ information in `Block#LastCommit`. `Commit` now mainly consists of a signature and a validator address plus a timestamp. Note we may remove the validator address & timestamp fields in the future (see ADR-25). +`lite2` package has been added to solve `lite` issues and introduce weak +subjectivity interface +https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md). +`lite` package is now deprecated and will be removed in v0.34 release. + Special thanks to external contributors on this release: @erikgrinaker, @PSalant726, @gchaincl, @gregzaitsev, @princesinha19, @Stumble @@ -60,6 +65,9 @@ program](https://hackerone.com/tendermint). - You will have to generate a new config for your Tendermint node(s) - [genesis] \#2565 Add `consensus_params.evidence.max_age_duration`. Rename `consensus_params.evidence.max_age` to `max_age_num_blocks`. + - [cli] \#1771 `tendermint lite` now uses new light client package (`lite2`) + and has 3 more flags: `--trusting-period`, `--trusted-height` and + `--trusted-hash` - Apps @@ -124,6 +132,7 @@ program](https://hackerone.com/tendermint). - `consensus_validator_missed_blocks`: total amount of missed blocks for a validator as gauges in prometheus for validator specific metrics - [rpc/lib] [\#4248](https://github.com/tendermint/tendermint/issues/4248) RPC client basic authentication support (@greg-szabo) +- [lite2] \#1771 Light client with weak subjectivity ### IMPROVEMENTS: diff --git a/cmd/tendermint/commands/lite.go b/cmd/tendermint/commands/lite.go index afd049a43..b86714419 100644 --- a/cmd/tendermint/commands/lite.go +++ b/cmd/tendermint/commands/lite.go @@ -1,15 +1,23 @@ package commands import ( - "fmt" - "net/url" + "net/http" + "time" "github.com/pkg/errors" "github.com/spf13/cobra" + amino "github.com/tendermint/go-amino" + dbm "github.com/tendermint/tm-db" + tmos "github.com/tendermint/tendermint/libs/os" - "github.com/tendermint/tendermint/lite/proxy" + lite "github.com/tendermint/tendermint/lite2" + httpp "github.com/tendermint/tendermint/lite2/provider/http" + lproxy "github.com/tendermint/tendermint/lite2/proxy" + lrpc "github.com/tendermint/tendermint/lite2/rpc" + dbs "github.com/tendermint/tendermint/lite2/store/db" rpcclient "github.com/tendermint/tendermint/rpc/client" + rpcserver "github.com/tendermint/tendermint/rpc/lib/server" ) // LiteCmd represents the base command when called without any subcommands @@ -32,7 +40,10 @@ var ( chainID string home string maxOpenConnections int - cacheSize int + + trustingPeriod time.Duration + trustedHeight int64 + trustedHash []byte ) func init() { @@ -45,60 +56,59 @@ func init() { "max-open-connections", 900, "Maximum number of simultaneous connections (including WebSocket).") - LiteCmd.Flags().IntVar(&cacheSize, "cache-size", 10, "Specify the memory trust store cache size") -} -func EnsureAddrHasSchemeOrDefaultToTCP(addr string) (string, error) { - u, err := url.Parse(addr) - if err != nil { - return "", err - } - switch u.Scheme { - case "tcp", "unix": - case "": - u.Scheme = "tcp" - default: - return "", fmt.Errorf("unknown scheme %q, use either tcp or unix", u.Scheme) - } - return u.String(), nil + 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 { - // Stop upon receiving SIGTERM or CTRL-C. - tmos.TrapSignal(logger, func() { - // TODO: close up shop - }) - - nodeAddr, err := EnsureAddrHasSchemeOrDefaultToTCP(nodeAddr) - if err != nil { - return err - } - listenAddr, err := EnsureAddrHasSchemeOrDefaultToTCP(listenAddr) - if err != nil { - return err - } + liteLogger := logger.With("module", "lite") + logger.Info("Connecting to Tendermint node...") // First, connect a client - logger.Info("Connecting to source HTTP client...") node, err := rpcclient.NewHTTP(nodeAddr, "/websocket") if err != nil { return errors.Wrap(err, "new HTTP client") } - logger.Info("Constructing Verifier...") - cert, err := proxy.NewVerifier(chainID, home, node, logger, cacheSize) + logger.Info("Creating client...") + db, err := dbm.NewGoLevelDB("lite-client-db", home) if err != nil { - return errors.Wrap(err, "constructing Verifier") + return err } - cert.SetLogger(logger) - sc := proxy.SecureClient(node, cert) + c, err := lite.NewClient( + chainID, + lite.TrustOptions{ + Period: trustingPeriod, + Height: trustedHeight, + Hash: trustedHash, + }, + httpp.NewWithClient(chainID, node), + dbs.New(db, chainID), + ) + if err != nil { + return err + } + c.SetLogger(liteLogger) + + p := lproxy.Proxy{ + Addr: listenAddr, + Config: &rpcserver.Config{MaxOpenConnections: maxOpenConnections}, + Codec: amino.NewCodec(), + Client: lrpc.NewClient(node, c), + Logger: liteLogger, + } + // Stop upon receiving SIGTERM or CTRL-C. + tmos.TrapSignal(liteLogger, func() { + p.Listener.Close() + }) logger.Info("Starting proxy...") - err = proxy.StartProxy(sc, listenAddr, logger, maxOpenConnections) - if err != nil { - return errors.Wrap(err, "starting proxy") + if err := p.ListenAndServe(); err != http.ErrServerClosed { + // Error starting or closing listener: + logger.Error("proxy ListenAndServe", "err", err) } - // Run forever - select {} + return nil } diff --git a/lite/doc.go b/lite/doc.go index c02b50214..ecb8d4cf6 100644 --- a/lite/doc.go +++ b/lite/doc.go @@ -1,4 +1,6 @@ /* +Package lite is deprecated and will be removed in v0.34! + Package lite allows you to securely validate headers without a full node. This library pulls together all the crypto and algorithms, so given a diff --git a/lite2/client.go b/lite2/client.go index cef0ab12a..b705a932b 100644 --- a/lite2/client.go +++ b/lite2/client.go @@ -214,6 +214,8 @@ func (c *Client) restoreTrustedHeaderAndNextVals() error { c.trustedHeader = trustedHeader c.trustedNextVals = trustedNextVals + + c.logger.Debug("Restored trusted header and next vals", lastHeight) } return nil @@ -249,6 +251,10 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error { case options.Height == c.trustedHeader.Height: primaryHash = options.Hash case options.Height < c.trustedHeader.Height: + c.logger.Info("Client initialized with old header (trusted is more recent)", + "old", options.Height, + "trusted", c.trustedHeader.Height) + action := fmt.Sprintf( "Rollback to %d (%X)? Note this will remove newer headers up to %d (%X)", options.Height, options.Hash, @@ -258,6 +264,9 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error { c.cleanup(options.Height + 1) // set c.trustedHeader to one at options.Height c.restoreTrustedHeaderAndNextVals() + + c.logger.Info("Rolled back to older header (newer headers were removed)", + "old", options.Height) } else { return errors.New("rollback aborted") } @@ -266,8 +275,8 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error { } if !bytes.Equal(primaryHash, c.trustedHeader.Hash()) { - c.logger.Info("Prev. trusted header's hash %X doesn't match hash %X from primary provider", - c.trustedHeader.Hash(), primaryHash) + c.logger.Info("Prev. trusted header's hash (h1) doesn't match hash from primary provider (h2)", + "h1", c.trustedHeader.Hash(), "h1", primaryHash) action := fmt.Sprintf( "Prev. trusted header's hash %X doesn't match hash %X from primary provider. Remove all the stored headers?", @@ -398,6 +407,8 @@ func (c *Client) ChainID() string { // // If the trusted header is more recent than one here, an error is returned. func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error) { + c.logger.Info("VerifyHeaderAtHeight", "height", height) + if c.trustedHeader.Height >= height { return nil, errors.Errorf("header at more recent height #%d exists", c.trustedHeader.Height) } @@ -425,6 +436,8 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe // // If the trusted header is more recent than one here, an error is returned. func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error { + c.logger.Info("VerifyHeader", "height", newHeader.Hash(), "newVals", newVals.Hash()) + if c.trustedHeader.Height >= newHeader.Height { return errors.Errorf("header at more recent height #%d exists", c.trustedHeader.Height) } @@ -458,6 +471,7 @@ func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.Vali // Cleanup removes all the data (headers and validator sets) stored. func (c *Client) Cleanup() error { + c.logger.Info("Cleanup everything") return c.cleanup(0) } @@ -507,6 +521,11 @@ func (c *Client) sequence(newHeader *types.SignedHeader, newVals *types.Validato return errors.Wrapf(err, "failed to obtain the header #%d", height) } + c.logger.Debug("Verify newHeader against lastHeader", + "lastHeight", c.trustedHeader.Height, + "lastHash", c.trustedHeader.Hash(), + "newHeight", interimHeader.Height, + "newHash", interimHeader.Hash()) err = Verify(c.chainID, c.trustedHeader, c.trustedNextVals, interimHeader, c.trustedNextVals, c.trustingPeriod, now, c.trustLevel) if err != nil { @@ -540,6 +559,11 @@ func (c *Client) bisection( newVals *types.ValidatorSet, now time.Time) error { + c.logger.Debug("Verify newHeader against lastHeader", + "lastHeight", lastHeader.Height, + "lastHash", lastHeader.Hash(), + "newHeight", newHeader.Height, + "newHash", newHeader.Hash()) err := Verify(c.chainID, lastHeader, lastVals, newHeader, newVals, c.trustingPeriod, now, c.trustLevel) switch err.(type) { case nil: diff --git a/lite2/proxy/proxy.go b/lite2/proxy/proxy.go index 43abac814..0bfa12bad 100644 --- a/lite2/proxy/proxy.go +++ b/lite2/proxy/proxy.go @@ -18,11 +18,12 @@ import ( // A Proxy defines parameters for running an HTTP server proxy. type Proxy struct { - Addr string // TCP address to listen on, ":http" if empty - Config *rpcserver.Config - Codec *amino.Codec - Client *lrpc.Client - Logger log.Logger + Addr string // TCP address to listen on, ":http" if empty + Config *rpcserver.Config + Codec *amino.Codec + Client *lrpc.Client + Logger log.Logger + Listener net.Listener } // ListenAndServe configures the rpcserver.WebsocketManager, sets up the RPC @@ -34,6 +35,7 @@ func (p *Proxy) ListenAndServe() error { if err != nil { return err } + p.Listener = listener return rpcserver.StartHTTPServer( listener, @@ -51,6 +53,7 @@ func (p *Proxy) ListenAndServeTLS(certFile, keyFile string) error { if err != nil { return err } + p.Listener = listener return rpcserver.StartHTTPAndTLSServer( listener, diff --git a/lite2/verifier.go b/lite2/verifier.go index bc242c803..d2f656e41 100644 --- a/lite2/verifier.go +++ b/lite2/verifier.go @@ -19,7 +19,7 @@ var ( // Verify verifies the new header (h2) against the old header (h1). It ensures that: // // a) h1 can still be trusted (if not, ErrOldHeaderExpired is returned); -// b) h2 is valid (if not, ErrInvalidNewHeader is returned); +// b) h2 is valid; // c) either h2.ValidatorsHash equals h1NextVals.Hash() // OR trustLevel ([1/3, 1]) of last trusted validators (h1NextVals) signed // correctly (if not, ErrNewValSetCantBeTrusted is returned);