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 }