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