diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b93b342..7c14c2f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,17 +3,21 @@ ## 0.10.1 (TBD) FEATURES: -- Use `--trace` to get stack traces for logged errors +- types: GenesisDoc.ValidatorHash returns the hash of the genesis validator set +- types: GenesisDocFromFile parses a GenesiDoc from a JSON file IMPROVEMENTS: - Add a Code of Conduct - Variety of improvements as suggested by `megacheck` tool - rpc: deduplicate tests between rpc/client and rpc/tests - rpc: addresses without a protocol prefix default to `tcp://`. `http://` is also accepted as an alias for `tcp://` +- cmd: commands are more easily reuseable from other tools +- DOCKER: automate build/push BUG FIXES: - Fix log statements using keys with spaces (logger does not currently support spaces) - rpc: set logger on websocket connection +- rpc: fix ws connection stability by setting write deadline on pings ## 0.10.0 (June 2, 2017) diff --git a/DOCKER/Dockerfile b/DOCKER/Dockerfile index 09b25f732..55f2e14ab 100644 --- a/DOCKER/Dockerfile +++ b/DOCKER/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.5 +FROM alpine:3.6 # This is the release of tendermint to pull in. ENV TM_VERSION 0.10.0 @@ -42,5 +42,4 @@ EXPOSE 46657 ENTRYPOINT ["tendermint"] -# By default you'll get the dummy app -CMD ["node", "--moniker=`hostname`", "--proxy_app=dummy"] +CMD ["node", "--moniker=`hostname`"] diff --git a/DOCKER/Dockerfile.develop b/DOCKER/Dockerfile.develop index 82cd884ae..2ffb68281 100644 --- a/DOCKER/Dockerfile.develop +++ b/DOCKER/Dockerfile.develop @@ -1,4 +1,4 @@ -FROM alpine:3.5 +FROM alpine:3.6 ENV DATA_ROOT /tendermint ENV TMHOME $DATA_ROOT diff --git a/DOCKER/Makefile b/DOCKER/Makefile index 612b9a694..10c972502 100644 --- a/DOCKER/Makefile +++ b/DOCKER/Makefile @@ -1,12 +1,8 @@ build: - # TAG=0.8.0 TAG_NO_PATCH=0.8 - docker build -t "tendermint/tendermint" -t "tendermint/tendermint:$(TAG)" -t "tendermint/tendermint:$(TAG_NO_PATCH)" . + @sh -c "'$(CURDIR)/build.sh'" push: - # TAG=0.8.0 TAG_NO_PATCH=0.8 - docker push "tendermint/tendermint:latest" - docker push "tendermint/tendermint:$(TAG)" - docker push "tendermint/tendermint:$(TAG_NO_PATCH)" + @sh -c "'$(CURDIR)/push.sh'" build_develop: docker build -t "tendermint/tendermint:develop" -f Dockerfile.develop . diff --git a/DOCKER/README.md b/DOCKER/README.md index 2bd2b27ea..e191abc39 100644 --- a/DOCKER/README.md +++ b/DOCKER/README.md @@ -1,6 +1,6 @@ # Supported tags and respective `Dockerfile` links -- `0.10.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/v0.10.0/DOCKER/Dockerfile) +- `0.10.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/e5342f4054ab784b2cd6150e14f01053d7c8deb2/DOCKER/Dockerfile) - `0.9.1`, `0.9`, [(Dockerfile)](https://github.com/tendermint/tendermint/blob/809e0e8c5933604ba8b2d096803ada7c5ec4dfd3/DOCKER/Dockerfile) - `0.9.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/d474baeeea6c22b289e7402449572f7c89ee21da/DOCKER/Dockerfile) - `0.8.0`, `0.8` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/bf64dd21fdb193e54d8addaaaa2ecf7ac371de8c/DOCKER/Dockerfile) @@ -24,12 +24,12 @@ A very simple example of a built-in app and Tendermint core in one container. ``` docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init -docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint +docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint node --proxy_app=dummy ``` ## mintnet-kubernetes -If you want to see many containers talking to each other, consider using [mintnet-kubernetes](https://github.com/tendermint/mintnet-kubernetes), which is a tool for running Tendermint-based applications on a Kubernetes cluster. +If you want to see many containers talking to each other, consider using [mintnet-kubernetes](https://github.com/tendermint/tools/tree/master/mintnet-kubernetes), which is a tool for running Tendermint-based applications on a Kubernetes cluster. # Supported Docker versions diff --git a/DOCKER/build.sh b/DOCKER/build.sh new file mode 100755 index 000000000..ee617cc63 --- /dev/null +++ b/DOCKER/build.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -e + +# Get the tag from the version, or try to figure it out. +if [ -z "$TAG" ]; then + TAG=$(awk -F\" '/Version =/ { print $2; exit }' < ../version/version.go) +fi +if [ -z "$TAG" ]; then + echo "Please specify a tag." + exit 1 +fi + +TAG_NO_PATCH=${TAG%.*} + +read -p "==> Build 3 docker images with the following tags (latest, $TAG, $TAG_NO_PATCH)? y/n" -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]] +then + docker build -t "tendermint/tendermint" -t "tendermint/tendermint:$TAG" -t "tendermint/tendermint:$TAG_NO_PATCH" . +fi diff --git a/DOCKER/push.sh b/DOCKER/push.sh new file mode 100755 index 000000000..32741dce8 --- /dev/null +++ b/DOCKER/push.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -e + +# Get the tag from the version, or try to figure it out. +if [ -z "$TAG" ]; then + TAG=$(awk -F\" '/Version =/ { print $2; exit }' < ../version/version.go) +fi +if [ -z "$TAG" ]; then + echo "Please specify a tag." + exit 1 +fi + +TAG_NO_PATCH=${TAG%.*} + +read -p "==> Push 3 docker images with the following tags (latest, $TAG, $TAG_NO_PATCH)? y/n" -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]] +then + docker push "tendermint/tendermint:latest" + docker push "tendermint/tendermint:$TAG" + docker push "tendermint/tendermint:$TAG_NO_PATCH" +fi diff --git a/cmd/tendermint/commands/root.go b/cmd/tendermint/commands/root.go index a02cd92bd..a63d9e46b 100644 --- a/cmd/tendermint/commands/root.go +++ b/cmd/tendermint/commands/root.go @@ -21,16 +21,26 @@ func init() { RootCmd.PersistentFlags().String("log_level", config.LogLevel, "Log level") } +// ParseConfig will setup the tendermint configuration properly +func ParseConfig() (*cfg.Config, error) { + conf := cfg.DefaultConfig() + err := viper.Unmarshal(conf) + if err != nil { + return nil, err + } + conf.SetRoot(conf.RootDir) + cfg.EnsureRoot(conf.RootDir) + return conf, err +} + var RootCmd = &cobra.Command{ Use: "tendermint", Short: "Tendermint Core (BFT Consensus) in Go", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := viper.Unmarshal(config) + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + config, err = ParseConfig() if err != nil { return err } - config.SetRoot(config.RootDir) - cfg.EnsureRoot(config.RootDir) logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) if err != nil { return err diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index d476ccd57..e99b8609a 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "io/ioutil" "time" "github.com/spf13/cobra" @@ -19,28 +18,33 @@ var runNodeCmd = &cobra.Command{ } func init() { + AddNodeFlags(runNodeCmd) + RootCmd.AddCommand(runNodeCmd) +} + +// AddNodeFlags exposes some common configuration options on the command-line +// These are exposed for convenience of commands embedding a tendermint node +func AddNodeFlags(cmd *cobra.Command) { // bind flags - runNodeCmd.Flags().String("moniker", config.Moniker, "Node Name") + cmd.Flags().String("moniker", config.Moniker, "Node Name") // node flags - runNodeCmd.Flags().Bool("fast_sync", config.FastSync, "Fast blockchain syncing") + cmd.Flags().Bool("fast_sync", config.FastSync, "Fast blockchain syncing") // abci flags - runNodeCmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or 'nilapp' or 'dummy' for local testing.") - runNodeCmd.Flags().String("abci", config.ABCI, "Specify abci transport (socket | grpc)") + cmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or 'nilapp' or 'dummy' for local testing.") + cmd.Flags().String("abci", config.ABCI, "Specify abci transport (socket | grpc)") // rpc flags - runNodeCmd.Flags().String("rpc.laddr", config.RPC.ListenAddress, "RPC listen address. Port required") - runNodeCmd.Flags().String("rpc.grpc_laddr", config.RPC.GRPCListenAddress, "GRPC listen address (BroadcastTx only). Port required") - runNodeCmd.Flags().Bool("rpc.unsafe", config.RPC.Unsafe, "Enabled unsafe rpc methods") + cmd.Flags().String("rpc.laddr", config.RPC.ListenAddress, "RPC listen address. Port required") + cmd.Flags().String("rpc.grpc_laddr", config.RPC.GRPCListenAddress, "GRPC listen address (BroadcastTx only). Port required") + cmd.Flags().Bool("rpc.unsafe", config.RPC.Unsafe, "Enabled unsafe rpc methods") // p2p flags - runNodeCmd.Flags().String("p2p.laddr", config.P2P.ListenAddress, "Node listen address. (0.0.0.0:0 means any interface, any port)") - runNodeCmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma delimited host:port seed nodes") - runNodeCmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration") - runNodeCmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable Peer-Exchange (dev feature)") - - RootCmd.AddCommand(runNodeCmd) + cmd.Flags().String("p2p.laddr", config.P2P.ListenAddress, "Node listen address. (0.0.0.0:0 means any interface, any port)") + cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma delimited host:port seed nodes") + cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration") + cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable Peer-Exchange (dev feature)") } // Users wishing to: @@ -56,27 +60,16 @@ func runNode(cmd *cobra.Command, args []string) error { // TODO: If Mintnet gets deprecated or genesis_file is // always available, remove. genDocFile := config.GenesisFile() - if !cmn.FileExists(genDocFile) { + for !cmn.FileExists(genDocFile) { logger.Info(cmn.Fmt("Waiting for genesis file %v...", genDocFile)) - for { - time.Sleep(time.Second) - if !cmn.FileExists(genDocFile) { - continue - } - jsonBlob, err := ioutil.ReadFile(genDocFile) - if err != nil { - return fmt.Errorf("Couldn't read GenesisDoc file: %v", err) - } - genDoc, err := types.GenesisDocFromJSON(jsonBlob) - if err != nil { - return fmt.Errorf("Error reading GenesisDoc: %v", err) - } - if genDoc.ChainID == "" { - return fmt.Errorf("Genesis doc %v must include non-empty chain_id", genDocFile) - } - config.ChainID = genDoc.ChainID - } + time.Sleep(time.Second) + } + + genDoc, err := types.GenesisDocFromFile(genDocFile) + if err != nil { + return err } + config.ChainID = genDoc.ChainID // Create & start node n := node.NewNodeDefault(config, logger.With("module", "node")) diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go index bafc88546..1538f08a7 100644 --- a/rpc/lib/server/handlers.go +++ b/rpc/lib/server/handlers.go @@ -510,7 +510,10 @@ func (wsc *wsConnection) readRoutine() { continue } returns := rpcFunc.f.Call(args) - wsc.Logger.Info("WSJSONRPC", "method", request.Method, "args", args, "returns", returns) + + // TODO: Need to encode args/returns to string if we want to log them + wsc.Logger.Info("WSJSONRPC", "method", request.Method) + result, err := unreflectResult(returns) if err != nil { wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) @@ -532,6 +535,7 @@ func (wsc *wsConnection) writeRoutine() { case <-wsc.Quit: return case <-wsc.pingTicker.C: + wsc.baseConn.SetWriteDeadline(time.Now().Add(time.Second * wsWriteTimeoutSeconds)) err := wsc.baseConn.WriteMessage(websocket.PingMessage, []byte{}) if err != nil { wsc.Logger.Error("Failed to write ping message on websocket", "err", err) diff --git a/scripts/tendermint-builder/Dockerfile b/scripts/tendermint-builder/Dockerfile index 84c198ee0..daf931715 100644 --- a/scripts/tendermint-builder/Dockerfile +++ b/scripts/tendermint-builder/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.7.4 +FROM golang:1.8.3 RUN apt-get update && apt-get install -y --no-install-recommends \ zip \ diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index 7c322976d..8a2702fc8 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -1,5 +1,4 @@ -# Pull base image. -FROM golang:1.7.4 +FROM golang:1.8.3 # Add testing deps for curl RUN echo 'deb http://httpredir.debian.org/debian testing main non-free contrib' >> /etc/apt/sources.list @@ -9,7 +8,7 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends \ jq bsdmainutils vim-common psmisc netcat curl -# Setup tendermint repo +# Setup tendermint repo ENV REPO $GOPATH/src/github.com/tendermint/tendermint WORKDIR $REPO diff --git a/types/genesis.go b/types/genesis.go index 75999f631..23b14c9e9 100644 --- a/types/genesis.go +++ b/types/genesis.go @@ -2,8 +2,11 @@ package types import ( "encoding/json" + "io/ioutil" "time" + "github.com/pkg/errors" + "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire/data" cmn "github.com/tendermint/tmlibs/common" @@ -17,12 +20,14 @@ var GenDocKey = []byte("GenDocKey") //------------------------------------------------------------ // core types for a genesis definition +// GenesisValidator is an initial validator. type GenesisValidator struct { PubKey crypto.PubKey `json:"pub_key"` Amount int64 `json:"amount"` Name string `json:"name"` } +// GenesisDoc defines the initial conditions for a tendermint blockchain, in particular its validator set. type GenesisDoc struct { GenesisTime time.Time `json:"genesis_time"` ChainID string `json:"chain_id"` @@ -30,7 +35,7 @@ type GenesisDoc struct { AppHash data.Bytes `json:"app_hash"` } -// Utility method for saving GenensisDoc as JSON file. +// SaveAs is a utility method for saving GenensisDoc as a JSON file. func (genDoc *GenesisDoc) SaveAs(file string) error { genDocBytes, err := json.Marshal(genDoc) if err != nil { @@ -39,11 +44,38 @@ func (genDoc *GenesisDoc) SaveAs(file string) error { return cmn.WriteFile(file, genDocBytes, 0644) } +// ValidatorHash returns the hash of the validator set contained in the GenesisDoc +func (genDoc *GenesisDoc) ValidatorHash() []byte { + vals := make([]*Validator, len(genDoc.Validators)) + for i, v := range genDoc.Validators { + vals[i] = NewValidator(v.PubKey, v.Amount) + } + vset := NewValidatorSet(vals) + return vset.Hash() +} + //------------------------------------------------------------ // Make genesis state from file +// GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc. func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) { genDoc := GenesisDoc{} err := json.Unmarshal(jsonBlob, &genDoc) return &genDoc, err } + +// GenesisDocFromFile reads JSON data from a file and unmarshalls it into a GenesisDoc. +func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) { + jsonBlob, err := ioutil.ReadFile(genDocFile) + if err != nil { + return nil, errors.Wrap(err, "Couldn't read GenesisDoc file") + } + genDoc, err := GenesisDocFromJSON(jsonBlob) + if err != nil { + return nil, errors.Wrap(err, "Error reading GenesisDoc") + } + if genDoc.ChainID == "" { + return nil, errors.Errorf("Genesis doc %v must include non-empty chain_id", genDocFile) + } + return genDoc, nil +}