github.com/soomindae/tendermint@v0.0.5-0.20210528140126-84a0c70c8162/test/e2e/runner/setup.go (about)

     1  // nolint: gosec
     2  package main
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"text/template"
    18  	"time"
    19  
    20  	"github.com/BurntSushi/toml"
    21  
    22  	"github.com/soomindae/tendermint/config"
    23  	"github.com/soomindae/tendermint/crypto/ed25519"
    24  	"github.com/soomindae/tendermint/p2p"
    25  	"github.com/soomindae/tendermint/privval"
    26  	e2e "github.com/soomindae/tendermint/test/e2e/pkg"
    27  	"github.com/soomindae/tendermint/types"
    28  )
    29  
    30  const (
    31  	AppAddressTCP  = "tcp://127.0.0.1:30000"
    32  	AppAddressUNIX = "unix:///var/run/app.sock"
    33  
    34  	PrivvalAddressTCP     = "tcp://0.0.0.0:27559"
    35  	PrivvalAddressUNIX    = "unix:///var/run/privval.sock"
    36  	PrivvalKeyFile        = "config/priv_validator_key.json"
    37  	PrivvalStateFile      = "data/priv_validator_state.json"
    38  	PrivvalDummyKeyFile   = "config/dummy_validator_key.json"
    39  	PrivvalDummyStateFile = "data/dummy_validator_state.json"
    40  )
    41  
    42  // Setup sets up the testnet configuration.
    43  func Setup(testnet *e2e.Testnet) error {
    44  	logger.Info(fmt.Sprintf("Generating testnet files in %q", testnet.Dir))
    45  
    46  	err := os.MkdirAll(testnet.Dir, os.ModePerm)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	compose, err := MakeDockerCompose(testnet)
    52  	if err != nil {
    53  		return err
    54  	}
    55  	err = ioutil.WriteFile(filepath.Join(testnet.Dir, "docker-compose.yml"), compose, 0644)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	genesis, err := MakeGenesis(testnet)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	for _, node := range testnet.Nodes {
    66  		nodeDir := filepath.Join(testnet.Dir, node.Name)
    67  
    68  		dirs := []string{
    69  			filepath.Join(nodeDir, "config"),
    70  			filepath.Join(nodeDir, "data"),
    71  			filepath.Join(nodeDir, "data", "app"),
    72  		}
    73  		for _, dir := range dirs {
    74  			// light clients don't need an app directory
    75  			if node.Mode == e2e.ModeLight && strings.Contains(dir, "app") {
    76  				continue
    77  			}
    78  			err := os.MkdirAll(dir, 0755)
    79  			if err != nil {
    80  				return err
    81  			}
    82  		}
    83  
    84  		cfg, err := MakeConfig(node)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		config.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), cfg) // panics
    89  
    90  		appCfg, err := MakeAppConfig(node)
    91  		if err != nil {
    92  			return err
    93  		}
    94  		err = ioutil.WriteFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg, 0644)
    95  		if err != nil {
    96  			return err
    97  		}
    98  
    99  		if node.Mode == e2e.ModeLight {
   100  			// stop early if a light client
   101  			continue
   102  		}
   103  
   104  		err = genesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json"))
   105  		if err != nil {
   106  			return err
   107  		}
   108  
   109  		err = (&p2p.NodeKey{PrivKey: node.NodeKey}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json"))
   110  		if err != nil {
   111  			return err
   112  		}
   113  
   114  		(privval.NewFilePV(node.PrivvalKey,
   115  			filepath.Join(nodeDir, PrivvalKeyFile),
   116  			filepath.Join(nodeDir, PrivvalStateFile),
   117  		)).Save()
   118  
   119  		// Set up a dummy validator. Tendermint requires a file PV even when not used, so we
   120  		// give it a dummy such that it will fail if it actually tries to use it.
   121  		(privval.NewFilePV(ed25519.GenPrivKey(),
   122  			filepath.Join(nodeDir, PrivvalDummyKeyFile),
   123  			filepath.Join(nodeDir, PrivvalDummyStateFile),
   124  		)).Save()
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  // MakeDockerCompose generates a Docker Compose config for a testnet.
   131  func MakeDockerCompose(testnet *e2e.Testnet) ([]byte, error) {
   132  	// Must use version 2 Docker Compose format, to support IPv6.
   133  	tmpl, err := template.New("docker-compose").Funcs(template.FuncMap{
   134  		"misbehaviorsToString": func(misbehaviors map[int64]string) string {
   135  			str := ""
   136  			for height, misbehavior := range misbehaviors {
   137  				// after the first behavior set, a comma must be prepended
   138  				if str != "" {
   139  					str += ","
   140  				}
   141  				heightString := strconv.Itoa(int(height))
   142  				str += misbehavior + "," + heightString
   143  			}
   144  			return str
   145  		},
   146  	}).Parse(`version: '2.4'
   147  
   148  networks:
   149    {{ .Name }}:
   150      labels:
   151        e2e: true
   152      driver: bridge
   153  {{- if .IPv6 }}
   154      enable_ipv6: true
   155  {{- end }}
   156      ipam:
   157        driver: default
   158        config:
   159        - subnet: {{ .IP }}
   160  
   161  services:
   162  {{- range .Nodes }}
   163    {{ .Name }}:
   164      labels:
   165        e2e: true
   166      container_name: {{ .Name }}
   167      image: tendermint/e2e-node
   168  {{- if eq .ABCIProtocol "builtin" }}
   169      entrypoint: /usr/bin/entrypoint-builtin
   170  {{- else if .Misbehaviors }}
   171      entrypoint: /usr/bin/entrypoint-maverick
   172      command: ["node", "--misbehaviors", "{{ misbehaviorsToString .Misbehaviors }}"]
   173  {{- end }}
   174      init: true
   175      ports:
   176      - 26656
   177      - {{ if .ProxyPort }}{{ .ProxyPort }}:{{ end }}26657
   178      - 6060
   179      volumes:
   180      - ./{{ .Name }}:/tendermint
   181      networks:
   182        {{ $.Name }}:
   183          ipv{{ if $.IPv6 }}6{{ else }}4{{ end}}_address: {{ .IP }}
   184  
   185  {{end}}`)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	var buf bytes.Buffer
   190  	err = tmpl.Execute(&buf, testnet)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	return buf.Bytes(), nil
   195  }
   196  
   197  // MakeGenesis generates a genesis document.
   198  func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) {
   199  	genesis := types.GenesisDoc{
   200  		GenesisTime:     time.Now(),
   201  		ChainID:         testnet.Name,
   202  		ConsensusParams: types.DefaultConsensusParams(),
   203  		InitialHeight:   testnet.InitialHeight,
   204  	}
   205  	for validator, power := range testnet.Validators {
   206  		genesis.Validators = append(genesis.Validators, types.GenesisValidator{
   207  			Name:    validator.Name,
   208  			Address: validator.PrivvalKey.PubKey().Address(),
   209  			PubKey:  validator.PrivvalKey.PubKey(),
   210  			Power:   power,
   211  		})
   212  	}
   213  	// The validator set will be sorted internally by Tendermint ranked by power,
   214  	// but we sort it here as well so that all genesis files are identical.
   215  	sort.Slice(genesis.Validators, func(i, j int) bool {
   216  		return strings.Compare(genesis.Validators[i].Name, genesis.Validators[j].Name) == -1
   217  	})
   218  	if len(testnet.InitialState) > 0 {
   219  		appState, err := json.Marshal(testnet.InitialState)
   220  		if err != nil {
   221  			return genesis, err
   222  		}
   223  		genesis.AppState = appState
   224  	}
   225  	return genesis, genesis.ValidateAndComplete()
   226  }
   227  
   228  // MakeConfig generates a Tendermint config for a node.
   229  func MakeConfig(node *e2e.Node) (*config.Config, error) {
   230  	cfg := config.DefaultConfig()
   231  	cfg.Moniker = node.Name
   232  	cfg.ProxyApp = AppAddressTCP
   233  	cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657"
   234  	cfg.RPC.PprofListenAddress = ":6060"
   235  	cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false))
   236  	cfg.P2P.AddrBookStrict = false
   237  	cfg.DBBackend = node.Database
   238  	cfg.StateSync.DiscoveryTime = 5 * time.Second
   239  
   240  	switch node.ABCIProtocol {
   241  	case e2e.ProtocolUNIX:
   242  		cfg.ProxyApp = AppAddressUNIX
   243  	case e2e.ProtocolTCP:
   244  		cfg.ProxyApp = AppAddressTCP
   245  	case e2e.ProtocolGRPC:
   246  		cfg.ProxyApp = AppAddressTCP
   247  		cfg.ABCI = "grpc"
   248  	case e2e.ProtocolBuiltin:
   249  		cfg.ProxyApp = ""
   250  		cfg.ABCI = ""
   251  	default:
   252  		return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
   253  	}
   254  
   255  	// Tendermint errors if it does not have a privval key set up, regardless of whether
   256  	// it's actually needed (e.g. for remote KMS or non-validators). We set up a dummy
   257  	// key here by default, and use the real key for actual validators that should use
   258  	// the file privval.
   259  	cfg.PrivValidatorListenAddr = ""
   260  	cfg.PrivValidatorKey = PrivvalDummyKeyFile
   261  	cfg.PrivValidatorState = PrivvalDummyStateFile
   262  
   263  	switch node.Mode {
   264  	case e2e.ModeValidator:
   265  		switch node.PrivvalProtocol {
   266  		case e2e.ProtocolFile:
   267  			cfg.PrivValidatorKey = PrivvalKeyFile
   268  			cfg.PrivValidatorState = PrivvalStateFile
   269  		case e2e.ProtocolUNIX:
   270  			cfg.PrivValidatorListenAddr = PrivvalAddressUNIX
   271  		case e2e.ProtocolTCP:
   272  			cfg.PrivValidatorListenAddr = PrivvalAddressTCP
   273  		default:
   274  			return nil, fmt.Errorf("invalid privval protocol setting %q", node.PrivvalProtocol)
   275  		}
   276  	case e2e.ModeSeed:
   277  		cfg.P2P.SeedMode = true
   278  		cfg.P2P.PexReactor = true
   279  	case e2e.ModeFull, e2e.ModeLight:
   280  		// Don't need to do anything, since we're using a dummy privval key by default.
   281  	default:
   282  		return nil, fmt.Errorf("unexpected mode %q", node.Mode)
   283  	}
   284  
   285  	if node.FastSync == "" {
   286  		cfg.FastSyncMode = false
   287  	} else {
   288  		cfg.FastSync.Version = node.FastSync
   289  	}
   290  
   291  	if node.StateSync {
   292  		cfg.StateSync.Enable = true
   293  		cfg.StateSync.RPCServers = []string{}
   294  		for _, peer := range node.Testnet.ArchiveNodes() {
   295  			if peer.Name == node.Name {
   296  				continue
   297  			}
   298  			cfg.StateSync.RPCServers = append(cfg.StateSync.RPCServers, peer.AddressRPC())
   299  		}
   300  		if len(cfg.StateSync.RPCServers) < 2 {
   301  			return nil, errors.New("unable to find 2 suitable state sync RPC servers")
   302  		}
   303  	}
   304  
   305  	cfg.P2P.Seeds = ""
   306  	for _, seed := range node.Seeds {
   307  		if len(cfg.P2P.Seeds) > 0 {
   308  			cfg.P2P.Seeds += ","
   309  		}
   310  		cfg.P2P.Seeds += seed.AddressP2P(true)
   311  	}
   312  	cfg.P2P.PersistentPeers = ""
   313  	for _, peer := range node.PersistentPeers {
   314  		if len(cfg.P2P.PersistentPeers) > 0 {
   315  			cfg.P2P.PersistentPeers += ","
   316  		}
   317  		cfg.P2P.PersistentPeers += peer.AddressP2P(true)
   318  	}
   319  	return cfg, nil
   320  }
   321  
   322  // MakeAppConfig generates an ABCI application config for a node.
   323  func MakeAppConfig(node *e2e.Node) ([]byte, error) {
   324  	cfg := map[string]interface{}{
   325  		"chain_id":          node.Testnet.Name,
   326  		"dir":               "data/app",
   327  		"listen":            AppAddressUNIX,
   328  		"mode":              node.Mode,
   329  		"proxy_port":        node.ProxyPort,
   330  		"protocol":          "socket",
   331  		"persist_interval":  node.PersistInterval,
   332  		"snapshot_interval": node.SnapshotInterval,
   333  		"retain_blocks":     node.RetainBlocks,
   334  		"key_type":          node.PrivvalKey.Type(),
   335  	}
   336  	switch node.ABCIProtocol {
   337  	case e2e.ProtocolUNIX:
   338  		cfg["listen"] = AppAddressUNIX
   339  	case e2e.ProtocolTCP:
   340  		cfg["listen"] = AppAddressTCP
   341  	case e2e.ProtocolGRPC:
   342  		cfg["listen"] = AppAddressTCP
   343  		cfg["protocol"] = "grpc"
   344  	case e2e.ProtocolBuiltin:
   345  		delete(cfg, "listen")
   346  		cfg["protocol"] = "builtin"
   347  	default:
   348  		return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
   349  	}
   350  	if node.Mode == e2e.ModeValidator {
   351  		switch node.PrivvalProtocol {
   352  		case e2e.ProtocolFile:
   353  		case e2e.ProtocolTCP:
   354  			cfg["privval_server"] = PrivvalAddressTCP
   355  			cfg["privval_key"] = PrivvalKeyFile
   356  			cfg["privval_state"] = PrivvalStateFile
   357  		case e2e.ProtocolUNIX:
   358  			cfg["privval_server"] = PrivvalAddressUNIX
   359  			cfg["privval_key"] = PrivvalKeyFile
   360  			cfg["privval_state"] = PrivvalStateFile
   361  		default:
   362  			return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol)
   363  		}
   364  	}
   365  
   366  	misbehaviors := make(map[string]string)
   367  	for height, misbehavior := range node.Misbehaviors {
   368  		misbehaviors[strconv.Itoa(int(height))] = misbehavior
   369  	}
   370  	cfg["misbehaviors"] = misbehaviors
   371  
   372  	if len(node.Testnet.ValidatorUpdates) > 0 {
   373  		validatorUpdates := map[string]map[string]int64{}
   374  		for height, validators := range node.Testnet.ValidatorUpdates {
   375  			updateVals := map[string]int64{}
   376  			for node, power := range validators {
   377  				updateVals[base64.StdEncoding.EncodeToString(node.PrivvalKey.PubKey().Bytes())] = power
   378  			}
   379  			validatorUpdates[fmt.Sprintf("%v", height)] = updateVals
   380  		}
   381  		cfg["validator_update"] = validatorUpdates
   382  	}
   383  
   384  	var buf bytes.Buffer
   385  	err := toml.NewEncoder(&buf).Encode(cfg)
   386  	if err != nil {
   387  		return nil, fmt.Errorf("failed to generate app config: %w", err)
   388  	}
   389  	return buf.Bytes(), nil
   390  }
   391  
   392  // UpdateConfigStateSync updates the state sync config for a node.
   393  func UpdateConfigStateSync(node *e2e.Node, height int64, hash []byte) error {
   394  	cfgPath := filepath.Join(node.Testnet.Dir, node.Name, "config", "config.toml")
   395  
   396  	// FIXME Apparently there's no function to simply load a config file without
   397  	// involving the entire Viper apparatus, so we'll just resort to regexps.
   398  	bz, err := ioutil.ReadFile(cfgPath)
   399  	if err != nil {
   400  		return err
   401  	}
   402  	bz = regexp.MustCompile(`(?m)^trust_height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_height = %v`, height)))
   403  	bz = regexp.MustCompile(`(?m)^trust_hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_hash = "%X"`, hash)))
   404  	return ioutil.WriteFile(cfgPath, bz, 0644)
   405  }