github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/test/e2e/runner/setup.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"regexp"
    12  	"sort"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/BurntSushi/toml"
    17  
    18  	"github.com/line/ostracon/config"
    19  	"github.com/line/ostracon/crypto/ed25519"
    20  	cryptoenc "github.com/line/ostracon/crypto/encoding"
    21  	"github.com/line/ostracon/libs/log"
    22  	"github.com/line/ostracon/p2p"
    23  	"github.com/line/ostracon/privval"
    24  	e2e "github.com/line/ostracon/test/e2e/pkg"
    25  	"github.com/line/ostracon/test/e2e/pkg/infra"
    26  	"github.com/line/ostracon/types"
    27  )
    28  
    29  const (
    30  	AppAddressTCP  = "tcp://127.0.0.1:30000"
    31  	AppAddressUNIX = "unix:///var/run/app.sock"
    32  
    33  	PrivvalAddressTCP     = "tcp://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(testnet *e2e.Testnet, infp infra.Provider) error {
    43  	logger.Info("setup", "msg", log.NewLazySprintf("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  	err = infp.Setup()
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	genesis, err := MakeGenesis(testnet)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	for _, node := range testnet.Nodes {
    61  		nodeDir := filepath.Join(testnet.Dir, node.Name)
    62  
    63  		dirs := []string{
    64  			filepath.Join(nodeDir, "config"),
    65  			filepath.Join(nodeDir, "data"),
    66  			filepath.Join(nodeDir, "data", "app"),
    67  		}
    68  		for _, dir := range dirs {
    69  			// light clients don't need an app directory
    70  			if node.Mode == e2e.ModeLight && strings.Contains(dir, "app") {
    71  				continue
    72  			}
    73  			err := os.MkdirAll(dir, 0o755)
    74  			if err != nil {
    75  				return err
    76  			}
    77  		}
    78  
    79  		cfg, err := MakeConfig(node)
    80  		if err != nil {
    81  			return err
    82  		}
    83  		config.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), cfg) // panics
    84  
    85  		appCfg, err := MakeAppConfig(node)
    86  		if err != nil {
    87  			return err
    88  		}
    89  		err = os.WriteFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg, 0o644) //nolint:gosec
    90  		if err != nil {
    91  			return err
    92  		}
    93  
    94  		if node.Mode == e2e.ModeLight {
    95  			// stop early if a light client
    96  			continue
    97  		}
    98  
    99  		err = genesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json"))
   100  		if err != nil {
   101  			return err
   102  		}
   103  
   104  		err = (&p2p.NodeKey{PrivKey: node.NodeKey}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json"))
   105  		if err != nil {
   106  			return err
   107  		}
   108  
   109  		(privval.NewFilePV(node.PrivvalKey,
   110  			filepath.Join(nodeDir, PrivvalKeyFile),
   111  			filepath.Join(nodeDir, PrivvalStateFile),
   112  		)).Save()
   113  
   114  		// Set up a dummy validator. Ostracon requires a file PV even when not used, so we
   115  		// give it a dummy such that it will fail if it actually tries to use it.
   116  		(privval.NewFilePV(ed25519.GenPrivKey(),
   117  			filepath.Join(nodeDir, PrivvalDummyKeyFile),
   118  			filepath.Join(nodeDir, PrivvalDummyStateFile),
   119  		)).Save()
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // MakeGenesis generates a genesis document.
   126  func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) {
   127  	genesis := types.GenesisDoc{
   128  		GenesisTime:     time.Now(),
   129  		ChainID:         testnet.Name,
   130  		ConsensusParams: types.DefaultConsensusParams(),
   131  		InitialHeight:   testnet.InitialHeight,
   132  	}
   133  	for validator, power := range testnet.Validators {
   134  		genesis.Validators = append(genesis.Validators, types.GenesisValidator{
   135  			Name:    validator.Name,
   136  			Address: validator.PrivvalKey.PubKey().Address(),
   137  			PubKey:  validator.PrivvalKey.PubKey(),
   138  			Power:   power,
   139  		})
   140  	}
   141  	// The validator set will be sorted internally by Ostracon ranked by power,
   142  	// but we sort it here as well so that all genesis files are identical.
   143  	sort.Slice(genesis.Validators, func(i, j int) bool {
   144  		return strings.Compare(genesis.Validators[i].Name, genesis.Validators[j].Name) == -1
   145  	})
   146  	if len(testnet.InitialState) > 0 {
   147  		appState, err := json.Marshal(testnet.InitialState)
   148  		if err != nil {
   149  			return genesis, err
   150  		}
   151  		genesis.AppState = appState
   152  	}
   153  	return genesis, genesis.ValidateAndComplete()
   154  }
   155  
   156  // MakeConfig generates an Ostracon config for a node.
   157  func MakeConfig(node *e2e.Node) (*config.Config, error) {
   158  	cfg := config.DefaultConfig()
   159  	cfg.Moniker = node.Name
   160  	cfg.ProxyApp = AppAddressTCP
   161  	cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657"
   162  	cfg.RPC.PprofListenAddress = ":6060"
   163  	cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false))
   164  	cfg.P2P.AddrBookStrict = false
   165  	cfg.DBBackend = node.Database
   166  	cfg.StateSync.DiscoveryTime = 5 * time.Second
   167  
   168  	switch node.ABCIProtocol {
   169  	case e2e.ProtocolUNIX:
   170  		cfg.ProxyApp = AppAddressUNIX
   171  	case e2e.ProtocolTCP:
   172  		cfg.ProxyApp = AppAddressTCP
   173  	case e2e.ProtocolGRPC:
   174  		cfg.ProxyApp = AppAddressTCP
   175  		cfg.ABCI = "grpc"
   176  	case e2e.ProtocolBuiltin:
   177  		cfg.ProxyApp = ""
   178  		cfg.ABCI = ""
   179  	default:
   180  		return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
   181  	}
   182  
   183  	// Ostracon errors if it does not have a privval key set up, regardless of whether
   184  	// it's actually needed (e.g. for remote KMS or non-validators). We set up a dummy
   185  	// key here by default, and use the real key for actual validators that should use
   186  	// the file privval.
   187  	cfg.PrivValidatorListenAddr = ""
   188  	cfg.PrivValidatorKey = PrivvalDummyKeyFile
   189  	cfg.PrivValidatorState = PrivvalDummyStateFile
   190  
   191  	switch node.Mode {
   192  	case e2e.ModeValidator:
   193  		switch node.PrivvalProtocol {
   194  		case e2e.ProtocolFile:
   195  			cfg.PrivValidatorKey = PrivvalKeyFile
   196  			cfg.PrivValidatorState = PrivvalStateFile
   197  		case e2e.ProtocolUNIX:
   198  			cfg.PrivValidatorListenAddr = PrivvalAddressUNIX
   199  		case e2e.ProtocolTCP:
   200  			cfg.PrivValidatorListenAddr = PrivvalAddressTCP
   201  		default:
   202  			return nil, fmt.Errorf("invalid privval protocol setting %q", node.PrivvalProtocol)
   203  		}
   204  	case e2e.ModeSeed:
   205  		cfg.P2P.SeedMode = true
   206  		cfg.P2P.PexReactor = true
   207  	case e2e.ModeFull, e2e.ModeLight:
   208  		// Don't need to do anything, since we're using a dummy privval key by default.
   209  	default:
   210  		return nil, fmt.Errorf("unexpected mode %q", node.Mode)
   211  	}
   212  
   213  	if node.FastSync == "" {
   214  		cfg.FastSyncMode = false
   215  	} else {
   216  		cfg.FastSync.Version = node.FastSync
   217  	}
   218  
   219  	if node.StateSync {
   220  		cfg.StateSync.Enable = true
   221  		cfg.StateSync.RPCServers = []string{}
   222  		for _, peer := range node.Testnet.ArchiveNodes() {
   223  			if peer.Name == node.Name {
   224  				continue
   225  			}
   226  			cfg.StateSync.RPCServers = append(cfg.StateSync.RPCServers, peer.AddressRPC())
   227  		}
   228  		if len(cfg.StateSync.RPCServers) < 2 {
   229  			return nil, errors.New("unable to find 2 suitable state sync RPC servers")
   230  		}
   231  	}
   232  
   233  	cfg.P2P.Seeds = ""
   234  	for _, seed := range node.Seeds {
   235  		if len(cfg.P2P.Seeds) > 0 {
   236  			cfg.P2P.Seeds += ","
   237  		}
   238  		cfg.P2P.Seeds += seed.AddressP2P(true)
   239  	}
   240  	cfg.P2P.PersistentPeers = ""
   241  	for _, peer := range node.PersistentPeers {
   242  		if len(cfg.P2P.PersistentPeers) > 0 {
   243  			cfg.P2P.PersistentPeers += ","
   244  		}
   245  		cfg.P2P.PersistentPeers += peer.AddressP2P(true)
   246  	}
   247  	return cfg, nil
   248  }
   249  
   250  // MakeAppConfig generates an ABCI application config for a node.
   251  func MakeAppConfig(node *e2e.Node) ([]byte, error) {
   252  	cfg := map[string]interface{}{
   253  		"chain_id":               node.Testnet.Name,
   254  		"dir":                    "data/app",
   255  		"listen":                 AppAddressUNIX,
   256  		"mode":                   node.Mode,
   257  		"proxy_port":             node.ProxyPort,
   258  		"protocol":               "socket",
   259  		"persist_interval":       node.PersistInterval,
   260  		"snapshot_interval":      node.SnapshotInterval,
   261  		"retain_blocks":          node.RetainBlocks,
   262  		"key_type":               node.PrivvalKey.Type(),
   263  		"prepare_proposal_delay": node.Testnet.PrepareProposalDelay,
   264  		"process_proposal_delay": node.Testnet.ProcessProposalDelay,
   265  		"check_tx_delay":         node.Testnet.CheckTxDelay,
   266  	}
   267  	switch node.ABCIProtocol {
   268  	case e2e.ProtocolUNIX:
   269  		cfg["listen"] = AppAddressUNIX
   270  	case e2e.ProtocolTCP:
   271  		cfg["listen"] = AppAddressTCP
   272  	case e2e.ProtocolGRPC:
   273  		cfg["listen"] = AppAddressTCP
   274  		cfg["protocol"] = "grpc"
   275  	case e2e.ProtocolBuiltin:
   276  		delete(cfg, "listen")
   277  		cfg["protocol"] = string(node.ABCIProtocol)
   278  	default:
   279  		return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
   280  	}
   281  	if node.Mode == e2e.ModeValidator {
   282  		switch node.PrivvalProtocol {
   283  		case e2e.ProtocolFile:
   284  		case e2e.ProtocolTCP:
   285  			cfg["privval_server"] = PrivvalAddressTCP
   286  			cfg["privval_key"] = PrivvalKeyFile
   287  			cfg["privval_state"] = PrivvalStateFile
   288  		case e2e.ProtocolUNIX:
   289  			cfg["privval_server"] = PrivvalAddressUNIX
   290  			cfg["privval_key"] = PrivvalKeyFile
   291  			cfg["privval_state"] = PrivvalStateFile
   292  		default:
   293  			return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol)
   294  		}
   295  	}
   296  
   297  	if len(node.Testnet.ValidatorUpdates) > 0 {
   298  		validatorUpdates := map[string]map[string]int64{}
   299  		for height, validators := range node.Testnet.ValidatorUpdates {
   300  			updateVals := map[string]int64{}
   301  			for node, power := range validators {
   302  				pubKey := node.PrivvalKey.PubKey()
   303  				pc, err := cryptoenc.PubKeyToProto(pubKey)
   304  				if err != nil {
   305  					return nil, fmt.Errorf("invalid pubkey %q: %w", pubKey.Address(), err)
   306  				}
   307  				pcBytes, err := pc.Marshal()
   308  				if err != nil {
   309  					return nil, fmt.Errorf("invalid protobuf pubkey %q: %w", pubKey.Address(), err)
   310  				}
   311  				updateVals[base64.StdEncoding.EncodeToString(pcBytes)] = power
   312  			}
   313  			validatorUpdates[fmt.Sprintf("%v", height)] = updateVals
   314  		}
   315  		cfg["validator_update"] = validatorUpdates
   316  	}
   317  
   318  	var buf bytes.Buffer
   319  	err := toml.NewEncoder(&buf).Encode(cfg)
   320  	if err != nil {
   321  		return nil, fmt.Errorf("failed to generate app config: %w", err)
   322  	}
   323  	return buf.Bytes(), nil
   324  }
   325  
   326  // UpdateConfigStateSync updates the state sync config for a node.
   327  func UpdateConfigStateSync(node *e2e.Node, height int64, hash []byte) error {
   328  	cfgPath := filepath.Join(node.Testnet.Dir, node.Name, "config", "config.toml")
   329  
   330  	// FIXME Apparently there's no function to simply load a config file without
   331  	// involving the entire Viper apparatus, so we'll just resort to regexps.
   332  	bz, err := os.ReadFile(cfgPath)
   333  	if err != nil {
   334  		return err
   335  	}
   336  	bz = regexp.MustCompile(`(?m)^trust_height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_height = %v`, height)))
   337  	bz = regexp.MustCompile(`(?m)^trust_hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_hash = "%X"`, hash)))
   338  	return os.WriteFile(cfgPath, bz, 0o644) //nolint:gosec
   339  }