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