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