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