diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f329b0e24..987a760b4 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -27,6 +27,8 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi ### FEATURES +- [\#6982](https://github.com/tendermint/tendermint/pull/6982) tendermint binary has built-in suppport for running the e2e application (with state sync support) (@cmwaters). + ### IMPROVEMENTS ### BUG FIXES diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index c174fd967..1e310f5eb 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -65,7 +65,7 @@ func AddNodeFlags(cmd *cobra.Command) { "proxy-app", config.ProxyApp, "proxy app address, or one of: 'kvstore',"+ - " 'persistent_kvstore' or 'noop' for local testing.") + " 'persistent_kvstore', 'e2e' or 'noop' for local testing.") cmd.Flags().String("abci", config.ABCI, "specify abci transport (socket | grpc)") // rpc flags diff --git a/internal/proxy/client.go b/internal/proxy/client.go index 072ddf3e0..ddb9a928d 100644 --- a/internal/proxy/client.go +++ b/internal/proxy/client.go @@ -6,11 +6,12 @@ import ( abciclient "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/abci/types" + e2e "github.com/tendermint/tendermint/test/e2e/app" ) // DefaultClientCreator returns a default ClientCreator, which will create a // local client if addr is one of: 'kvstore', -// 'persistent_kvstore' or 'noop', otherwise - a remote client. +// 'persistent_kvstore', 'e2e', or 'noop', otherwise - a remote client. // // The Closer is a noop except for persistent_kvstore applications, // which will clean up the store. @@ -21,6 +22,12 @@ func DefaultClientCreator(addr, transport, dbDir string) (abciclient.Creator, io case "persistent_kvstore": app := kvstore.NewPersistentKVStoreApplication(dbDir) return abciclient.NewLocalCreator(app), app + case "e2e": + app, err := e2e.NewApplication(e2e.DefaultConfig(dbDir)) + if err != nil { + panic(err) + } + return abciclient.NewLocalCreator(app), noopCloser{} case "noop": return abciclient.NewLocalCreator(types.NewBaseApplication()), noopCloser{} default: diff --git a/test/e2e/Makefile b/test/e2e/Makefile index 2b41cc1cd..23cf4d039 100644 --- a/test/e2e/Makefile +++ b/test/e2e/Makefile @@ -3,11 +3,8 @@ all: docker generator runner tests docker: docker build --tag tendermint/e2e-node -f docker/Dockerfile ../.. -# We need to build support for database backends into the app in -# order to build a binary with a Tendermint node in it (for built-in -# ABCI testing). -app: - go build -o build/app -tags badgerdb,boltdb,cleveldb,rocksdb ./app +node: + go build -o build/node -tags badgerdb,boltdb,cleveldb,rocksdb ./node generator: go build -o build/generator ./generator @@ -18,4 +15,4 @@ runner: tests: go test -o build/tests ./tests -.PHONY: all app docker generator runner tests +.PHONY: all docker generator runner tests node diff --git a/test/e2e/README.md b/test/e2e/README.md index d737120c1..00bce5ad8 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -142,10 +142,42 @@ Docker does not enable IPv6 by default. To do so, enter the following in } ``` -## Benchmarking testnets +## Benchmarking Testnets It is also possible to run a simple benchmark on a testnet. This is done through the `benchmark` command. This manages the entire process: setting up the environment, starting the test net, waiting for a considerable amount of blocks to be used (currently 100), and then returning the following metrics from the sample of the blockchain: - Average time to produce a block - Standard deviation of producing a block - Minimum and maximum time to produce a block + +## Running Individual Nodes + +The E2E test harness is designed to run several nodes of varying configurations within docker. It is also possible to run a single node in the case of running larger, geographically-dispersed testnets. To run a single node you can either run: + +**Built-in** + +```bash +make node +tendermint init validator +TMHOME=$HOME/.tendermint ./build/node ./node/built-in.toml +``` + +To make things simpler the e2e application can also be run in the tendermint binary +by running + +```bash +tendermint start --proxy-app e2e +``` + +However this won't offer the same level of configurability of the application. + +**Socket** + +```bash +make node +tendermint init validator +tendermint start +./build/node ./node.socket.toml +``` + +Check `node/config.go` to see how the settings of the test application can be tweaked. \ No newline at end of file diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 26b10d32a..0a0194c98 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -1,4 +1,4 @@ -package main +package app import ( "bytes" @@ -27,6 +27,55 @@ type Application struct { restoreChunks [][]byte } +// Config allows for the setting of high level parameters for running the e2e Application +// KeyType and ValidatorUpdates must be the same for all nodes running the same application. +type Config struct { + // The directory with which state.json will be persisted in. Usually $HOME/.tendermint/data + Dir string `toml:"dir"` + + // SnapshotInterval specifies the height interval at which the application + // will take state sync snapshots. Defaults to 0 (disabled). + SnapshotInterval uint64 `toml:"snapshot_interval"` + + // RetainBlocks specifies the number of recent blocks to retain. Defaults to + // 0, which retains all blocks. Must be greater that PersistInterval, + // SnapshotInterval and EvidenceAgeHeight. + RetainBlocks uint64 `toml:"retain_blocks"` + + // KeyType sets the curve that will be used by validators. + // Options are ed25519 & secp256k1 + KeyType string `toml:"key_type"` + + // PersistInterval specifies the height interval at which the application + // will persist state to disk. Defaults to 1 (every height), setting this to + // 0 disables state persistence. + PersistInterval uint64 `toml:"persist_interval"` + + // ValidatorUpdates is a map of heights to validator names and their power, + // and will be returned by the ABCI application. For example, the following + // changes the power of validator01 and validator02 at height 1000: + // + // [validator_update.1000] + // validator01 = 20 + // validator02 = 10 + // + // Specifying height 0 returns the validator update during InitChain. The + // application returns the validator updates as-is, i.e. removing a + // validator must be done by returning it with power 0, and any validators + // not specified are not changed. + // + // height <-> pubkey <-> voting power + ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"` +} + +func DefaultConfig(dir string) *Config { + return &Config{ + PersistInterval: 1, + SnapshotInterval: 100, + Dir: dir, + } +} + // NewApplication creates the application. func NewApplication(cfg *Config) (*Application, error) { state, err := NewState(filepath.Join(cfg.Dir, "state.json"), cfg.PersistInterval) @@ -134,7 +183,7 @@ func (app *Application) Commit() abci.ResponseCommit { if err != nil { panic(err) } - logger.Info("Created state sync snapshot", "height", snapshot.Height) + app.logger.Info("Created state sync snapshot", "height", snapshot.Height) } retainHeight := int64(0) if app.cfg.RetainBlocks > 0 { diff --git a/test/e2e/app/snapshots.go b/test/e2e/app/snapshots.go index 4ddb7ecdc..4ef20375f 100644 --- a/test/e2e/app/snapshots.go +++ b/test/e2e/app/snapshots.go @@ -1,5 +1,5 @@ // nolint: gosec -package main +package app import ( "encoding/json" diff --git a/test/e2e/app/state.go b/test/e2e/app/state.go index b34680c1b..441926453 100644 --- a/test/e2e/app/state.go +++ b/test/e2e/app/state.go @@ -1,5 +1,5 @@ //nolint: gosec -package main +package app import ( "crypto/sha256" diff --git a/test/e2e/docker/Dockerfile b/test/e2e/docker/Dockerfile index 68c7bc836..260df23f3 100644 --- a/test/e2e/docker/Dockerfile +++ b/test/e2e/docker/Dockerfile @@ -19,7 +19,7 @@ COPY . . RUN make build && cp build/tendermint /usr/bin/tendermint COPY test/e2e/docker/entrypoint* /usr/bin/ -RUN cd test/e2e && make app && cp build/app /usr/bin/app +RUN cd test/e2e && make node && cp build/node /usr/bin/app # Set up runtime directory. We don't use a separate runtime image since we need # e.g. leveldb and rocksdb which are already installed in the build image. diff --git a/test/e2e/node/built-in.toml b/test/e2e/node/built-in.toml new file mode 100644 index 000000000..0a2146a58 --- /dev/null +++ b/test/e2e/node/built-in.toml @@ -0,0 +1,4 @@ +snapshot_interval = 100 +persist_interval = 1 +chain_id = "test-chain" +protocol = "builtin" diff --git a/test/e2e/app/config.go b/test/e2e/node/config.go similarity index 80% rename from test/e2e/app/config.go rename to test/e2e/node/config.go index d7e776538..fa7dcc497 100644 --- a/test/e2e/app/config.go +++ b/test/e2e/node/config.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/BurntSushi/toml" + + "github.com/tendermint/tendermint/test/e2e/app" ) // Config is the application configuration. @@ -22,10 +24,21 @@ type Config struct { PrivValServer string `toml:"privval_server"` PrivValKey string `toml:"privval_key"` PrivValState string `toml:"privval_state"` - Misbehaviors map[string]string `toml:"misbehaviors"` KeyType string `toml:"key_type"` } +// App extracts out the application specific configuration parameters +func (cfg *Config) App() *app.Config { + return &app.Config{ + Dir: cfg.Dir, + SnapshotInterval: cfg.SnapshotInterval, + RetainBlocks: cfg.RetainBlocks, + KeyType: cfg.KeyType, + ValidatorUpdates: cfg.ValidatorUpdates, + PersistInterval: cfg.PersistInterval, + } +} + // LoadConfig loads the configuration from disk. func LoadConfig(file string) (*Config, error) { cfg := &Config{ diff --git a/test/e2e/app/main.go b/test/e2e/node/main.go similarity index 98% rename from test/e2e/app/main.go rename to test/e2e/node/main.go index 1d6806524..222a6883f 100644 --- a/test/e2e/app/main.go +++ b/test/e2e/node/main.go @@ -30,6 +30,7 @@ import ( grpcprivval "github.com/tendermint/tendermint/privval/grpc" privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval" rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" + "github.com/tendermint/tendermint/test/e2e/app" e2e "github.com/tendermint/tendermint/test/e2e/pkg" ) @@ -97,7 +98,7 @@ func run(configFile string) error { // startApp starts the application server, listening for connections from Tendermint. func startApp(cfg *Config) error { - app, err := NewApplication(cfg) + app, err := app.NewApplication(cfg.App()) if err != nil { return err } @@ -118,7 +119,7 @@ func startApp(cfg *Config) error { // // FIXME There is no way to simply load the configuration from a file, so we need to pull in Viper. func startNode(cfg *Config) error { - app, err := NewApplication(cfg) + app, err := app.NewApplication(cfg.App()) if err != nil { return err } diff --git a/test/e2e/node/socket.toml b/test/e2e/node/socket.toml new file mode 100644 index 000000000..2f7913e62 --- /dev/null +++ b/test/e2e/node/socket.toml @@ -0,0 +1,5 @@ +snapshot_interval = 100 +persist_interval = 1 +chain_id = "test-chain" +protocol = "socket" +listen = "tcp://127.0.0.1:26658"