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