github.com/MagHErmit/tendermint@v0.282.1/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/MagHErmit/tendermint/config" 23 "github.com/MagHErmit/tendermint/crypto/ed25519" 24 "github.com/MagHErmit/tendermint/libs/log" 25 "github.com/MagHErmit/tendermint/p2p" 26 "github.com/MagHErmit/tendermint/privval" 27 e2e "github.com/MagHErmit/tendermint/test/e2e/pkg" 28 "github.com/MagHErmit/tendermint/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("setup", "msg", log.NewLazySprintf("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 69 dirs := []string{ 70 filepath.Join(nodeDir, "config"), 71 filepath.Join(nodeDir, "data"), 72 filepath.Join(nodeDir, "data", "app"), 73 } 74 for _, dir := range dirs { 75 // light clients don't need an app directory 76 if node.Mode == e2e.ModeLight && strings.Contains(dir, "app") { 77 continue 78 } 79 err := os.MkdirAll(dir, 0755) 80 if err != nil { 81 return err 82 } 83 } 84 85 cfg, err := MakeConfig(node) 86 if err != nil { 87 return err 88 } 89 config.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), cfg) // panics 90 91 appCfg, err := MakeAppConfig(node) 92 if err != nil { 93 return err 94 } 95 err = ioutil.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 = (&p2p.NodeKey{PrivKey: node.NodeKey}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json")) 111 if err != nil { 112 return err 113 } 114 115 (privval.NewFilePV(node.PrivvalKey, 116 filepath.Join(nodeDir, PrivvalKeyFile), 117 filepath.Join(nodeDir, PrivvalStateFile), 118 )).Save() 119 120 // Set up a dummy validator. Tendermint requires a file PV even when not used, so we 121 // give it a dummy such that it will fail if it actually tries to use it. 122 (privval.NewFilePV(ed25519.GenPrivKey(), 123 filepath.Join(nodeDir, PrivvalDummyKeyFile), 124 filepath.Join(nodeDir, PrivvalDummyStateFile), 125 )).Save() 126 } 127 128 return nil 129 } 130 131 // MakeDockerCompose generates a Docker Compose config for a testnet. 132 func MakeDockerCompose(testnet *e2e.Testnet) ([]byte, error) { 133 // Must use version 2 Docker Compose format, to support IPv6. 134 tmpl, err := template.New("docker-compose").Funcs(template.FuncMap{ 135 "misbehaviorsToString": func(misbehaviors map[int64]string) string { 136 str := "" 137 for height, misbehavior := range misbehaviors { 138 // after the first behavior set, a comma must be prepended 139 if str != "" { 140 str += "," 141 } 142 heightString := strconv.Itoa(int(height)) 143 str += misbehavior + "," + heightString 144 } 145 return str 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 eq .ABCIProtocol "builtin" }} 170 entrypoint: /usr/bin/entrypoint-builtin 171 {{- else if .Misbehaviors }} 172 entrypoint: /usr/bin/entrypoint-maverick 173 command: ["node", "--misbehaviors", "{{ misbehaviorsToString .Misbehaviors }}"] 174 {{- end }} 175 init: true 176 ports: 177 - 26656 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 // set the app version to 1 207 genesis.ConsensusParams.Version.AppVersion = 1 208 for validator, power := range testnet.Validators { 209 genesis.Validators = append(genesis.Validators, types.GenesisValidator{ 210 Name: validator.Name, 211 Address: validator.PrivvalKey.PubKey().Address(), 212 PubKey: validator.PrivvalKey.PubKey(), 213 Power: power, 214 }) 215 } 216 // The validator set will be sorted internally by Tendermint ranked by power, 217 // but we sort it here as well so that all genesis files are identical. 218 sort.Slice(genesis.Validators, func(i, j int) bool { 219 return strings.Compare(genesis.Validators[i].Name, genesis.Validators[j].Name) == -1 220 }) 221 if len(testnet.InitialState) > 0 { 222 appState, err := json.Marshal(testnet.InitialState) 223 if err != nil { 224 return genesis, err 225 } 226 genesis.AppState = appState 227 } 228 return genesis, genesis.ValidateAndComplete() 229 } 230 231 // MakeConfig generates a Tendermint config for a node. 232 func MakeConfig(node *e2e.Node) (*config.Config, error) { 233 cfg := config.DefaultConfig() 234 cfg.Moniker = node.Name 235 cfg.ProxyApp = AppAddressTCP 236 cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657" 237 cfg.RPC.PprofListenAddress = ":6060" 238 cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false)) 239 cfg.P2P.AddrBookStrict = false 240 cfg.DBBackend = node.Database 241 cfg.StateSync.DiscoveryTime = 5 * time.Second 242 243 switch node.ABCIProtocol { 244 case e2e.ProtocolUNIX: 245 cfg.ProxyApp = AppAddressUNIX 246 case e2e.ProtocolTCP: 247 cfg.ProxyApp = AppAddressTCP 248 case e2e.ProtocolGRPC: 249 cfg.ProxyApp = AppAddressTCP 250 cfg.ABCI = "grpc" 251 case e2e.ProtocolBuiltin: 252 cfg.ProxyApp = "" 253 cfg.ABCI = "" 254 default: 255 return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol) 256 } 257 258 // Tendermint errors if it does not have a privval key set up, regardless of whether 259 // it's actually needed (e.g. for remote KMS or non-validators). We set up a dummy 260 // key here by default, and use the real key for actual validators that should use 261 // the file privval. 262 cfg.PrivValidatorListenAddr = "" 263 cfg.PrivValidatorKey = PrivvalDummyKeyFile 264 cfg.PrivValidatorState = PrivvalDummyStateFile 265 266 switch node.Mode { 267 case e2e.ModeValidator: 268 switch node.PrivvalProtocol { 269 case e2e.ProtocolFile: 270 cfg.PrivValidatorKey = PrivvalKeyFile 271 cfg.PrivValidatorState = PrivvalStateFile 272 case e2e.ProtocolUNIX: 273 cfg.PrivValidatorListenAddr = PrivvalAddressUNIX 274 case e2e.ProtocolTCP: 275 cfg.PrivValidatorListenAddr = PrivvalAddressTCP 276 default: 277 return nil, fmt.Errorf("invalid privval protocol setting %q", node.PrivvalProtocol) 278 } 279 case e2e.ModeSeed: 280 cfg.P2P.SeedMode = true 281 cfg.P2P.PexReactor = true 282 case e2e.ModeFull, e2e.ModeLight: 283 // Don't need to do anything, since we're using a dummy privval key by default. 284 default: 285 return nil, fmt.Errorf("unexpected mode %q", node.Mode) 286 } 287 if node.Mempool != "" { 288 cfg.Mempool.Version = node.Mempool 289 } 290 291 if node.FastSync == "" { 292 cfg.FastSyncMode = false 293 } else { 294 cfg.FastSync.Version = node.FastSync 295 } 296 297 if node.StateSync { 298 cfg.StateSync.Enable = true 299 cfg.StateSync.RPCServers = []string{} 300 for _, peer := range node.Testnet.ArchiveNodes() { 301 if peer.Name == node.Name { 302 continue 303 } 304 cfg.StateSync.RPCServers = append(cfg.StateSync.RPCServers, peer.AddressRPC()) 305 } 306 if len(cfg.StateSync.RPCServers) < 2 { 307 return nil, errors.New("unable to find 2 suitable state sync RPC servers") 308 } 309 } 310 311 cfg.P2P.Seeds = "" 312 for _, seed := range node.Seeds { 313 if len(cfg.P2P.Seeds) > 0 { 314 cfg.P2P.Seeds += "," 315 } 316 cfg.P2P.Seeds += seed.AddressP2P(true) 317 } 318 cfg.P2P.PersistentPeers = "" 319 for _, peer := range node.PersistentPeers { 320 if len(cfg.P2P.PersistentPeers) > 0 { 321 cfg.P2P.PersistentPeers += "," 322 } 323 cfg.P2P.PersistentPeers += peer.AddressP2P(true) 324 } 325 return cfg, nil 326 } 327 328 // MakeAppConfig generates an ABCI application config for a node. 329 func MakeAppConfig(node *e2e.Node) ([]byte, error) { 330 cfg := map[string]interface{}{ 331 "chain_id": node.Testnet.Name, 332 "dir": "data/app", 333 "listen": AppAddressUNIX, 334 "mode": node.Mode, 335 "proxy_port": node.ProxyPort, 336 "protocol": "socket", 337 "persist_interval": node.PersistInterval, 338 "snapshot_interval": node.SnapshotInterval, 339 "retain_blocks": node.RetainBlocks, 340 "key_type": node.PrivvalKey.Type(), 341 } 342 switch node.ABCIProtocol { 343 case e2e.ProtocolUNIX: 344 cfg["listen"] = AppAddressUNIX 345 case e2e.ProtocolTCP: 346 cfg["listen"] = AppAddressTCP 347 case e2e.ProtocolGRPC: 348 cfg["listen"] = AppAddressTCP 349 cfg["protocol"] = "grpc" 350 case e2e.ProtocolBuiltin: 351 delete(cfg, "listen") 352 cfg["protocol"] = "builtin" 353 default: 354 return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol) 355 } 356 if node.Mode == e2e.ModeValidator { 357 switch node.PrivvalProtocol { 358 case e2e.ProtocolFile: 359 case e2e.ProtocolTCP: 360 cfg["privval_server"] = PrivvalAddressTCP 361 cfg["privval_key"] = PrivvalKeyFile 362 cfg["privval_state"] = PrivvalStateFile 363 case e2e.ProtocolUNIX: 364 cfg["privval_server"] = PrivvalAddressUNIX 365 cfg["privval_key"] = PrivvalKeyFile 366 cfg["privval_state"] = PrivvalStateFile 367 default: 368 return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol) 369 } 370 } 371 372 misbehaviors := make(map[string]string) 373 for height, misbehavior := range node.Misbehaviors { 374 misbehaviors[strconv.Itoa(int(height))] = misbehavior 375 } 376 cfg["misbehaviors"] = misbehaviors 377 378 if len(node.Testnet.ValidatorUpdates) > 0 { 379 validatorUpdates := map[string]map[string]int64{} 380 for height, validators := range node.Testnet.ValidatorUpdates { 381 updateVals := map[string]int64{} 382 for node, power := range validators { 383 updateVals[base64.StdEncoding.EncodeToString(node.PrivvalKey.PubKey().Bytes())] = power 384 } 385 validatorUpdates[fmt.Sprintf("%v", height)] = updateVals 386 } 387 cfg["validator_update"] = validatorUpdates 388 } 389 390 var buf bytes.Buffer 391 err := toml.NewEncoder(&buf).Encode(cfg) 392 if err != nil { 393 return nil, fmt.Errorf("failed to generate app config: %w", err) 394 } 395 return buf.Bytes(), nil 396 } 397 398 // UpdateConfigStateSync updates the state sync config for a node. 399 func UpdateConfigStateSync(node *e2e.Node, height int64, hash []byte) error { 400 cfgPath := filepath.Join(node.Testnet.Dir, node.Name, "config", "config.toml") 401 402 // FIXME Apparently there's no function to simply load a config file without 403 // involving the entire Viper apparatus, so we'll just resort to regexps. 404 bz, err := ioutil.ReadFile(cfgPath) 405 if err != nil { 406 return err 407 } 408 bz = regexp.MustCompile(`(?m)^trust_height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_height = %v`, height))) 409 bz = regexp.MustCompile(`(?m)^trust_hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_hash = "%X"`, hash))) 410 return ioutil.WriteFile(cfgPath, bz, 0644) 411 }