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 }