You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
13 KiB

  1. // nolint: gosec
  2. package main
  3. import (
  4. "bytes"
  5. "encoding/base64"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "path/filepath"
  12. "regexp"
  13. "sort"
  14. "strconv"
  15. "strings"
  16. "text/template"
  17. "time"
  18. "github.com/BurntSushi/toml"
  19. "github.com/tendermint/tendermint/config"
  20. "github.com/tendermint/tendermint/crypto/ed25519"
  21. "github.com/tendermint/tendermint/p2p"
  22. "github.com/tendermint/tendermint/privval"
  23. e2e "github.com/tendermint/tendermint/test/e2e/pkg"
  24. "github.com/tendermint/tendermint/types"
  25. )
  26. const (
  27. AppAddressTCP = "tcp://127.0.0.1:30000"
  28. AppAddressUNIX = "unix:///var/run/app.sock"
  29. PrivvalAddressTCP = "tcp://0.0.0.0:27559"
  30. PrivvalAddressGRPC = "grpc://0.0.0.0:27559"
  31. PrivvalAddressUNIX = "unix:///var/run/privval.sock"
  32. PrivvalKeyFile = "config/priv_validator_key.json"
  33. PrivvalStateFile = "data/priv_validator_state.json"
  34. PrivvalDummyKeyFile = "config/dummy_validator_key.json"
  35. PrivvalDummyStateFile = "data/dummy_validator_state.json"
  36. )
  37. // Setup sets up the testnet configuration.
  38. func Setup(testnet *e2e.Testnet) error {
  39. logger.Info(fmt.Sprintf("Generating testnet files in %q", testnet.Dir))
  40. err := os.MkdirAll(testnet.Dir, os.ModePerm)
  41. if err != nil {
  42. return err
  43. }
  44. compose, err := MakeDockerCompose(testnet)
  45. if err != nil {
  46. return err
  47. }
  48. err = ioutil.WriteFile(filepath.Join(testnet.Dir, "docker-compose.yml"), compose, 0644)
  49. if err != nil {
  50. return err
  51. }
  52. genesis, err := MakeGenesis(testnet)
  53. if err != nil {
  54. return err
  55. }
  56. for _, node := range testnet.Nodes {
  57. nodeDir := filepath.Join(testnet.Dir, node.Name)
  58. dirs := []string{
  59. filepath.Join(nodeDir, "config"),
  60. filepath.Join(nodeDir, "data"),
  61. filepath.Join(nodeDir, "data", "app"),
  62. }
  63. for _, dir := range dirs {
  64. // light clients don't need an app directory
  65. if node.Mode == e2e.ModeLight && strings.Contains(dir, "app") {
  66. continue
  67. }
  68. err := os.MkdirAll(dir, 0755)
  69. if err != nil {
  70. return err
  71. }
  72. }
  73. cfg, err := MakeConfig(node)
  74. if err != nil {
  75. return err
  76. }
  77. config.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), cfg) // panics
  78. appCfg, err := MakeAppConfig(node)
  79. if err != nil {
  80. return err
  81. }
  82. err = ioutil.WriteFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg, 0644)
  83. if err != nil {
  84. return err
  85. }
  86. if node.Mode == e2e.ModeLight {
  87. // stop early if a light client
  88. continue
  89. }
  90. err = genesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json"))
  91. if err != nil {
  92. return err
  93. }
  94. err = (&p2p.NodeKey{PrivKey: node.NodeKey}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json"))
  95. if err != nil {
  96. return err
  97. }
  98. (privval.NewFilePV(node.PrivvalKey,
  99. filepath.Join(nodeDir, PrivvalKeyFile),
  100. filepath.Join(nodeDir, PrivvalStateFile),
  101. )).Save()
  102. // Set up a dummy validator. Tendermint requires a file PV even when not used, so we
  103. // give it a dummy such that it will fail if it actually tries to use it.
  104. (privval.NewFilePV(ed25519.GenPrivKey(),
  105. filepath.Join(nodeDir, PrivvalDummyKeyFile),
  106. filepath.Join(nodeDir, PrivvalDummyStateFile),
  107. )).Save()
  108. }
  109. return nil
  110. }
  111. // MakeDockerCompose generates a Docker Compose config for a testnet.
  112. func MakeDockerCompose(testnet *e2e.Testnet) ([]byte, error) {
  113. // Must use version 2 Docker Compose format, to support IPv6.
  114. tmpl, err := template.New("docker-compose").Funcs(template.FuncMap{
  115. "startCommands": func(misbehaviors map[int64]string, logLevel string) string {
  116. command := "start"
  117. // FIXME: Temporarily disable behaviors until maverick is redesigned
  118. // misbehaviorString := ""
  119. // for height, misbehavior := range misbehaviors {
  120. // // after the first behavior set, a comma must be prepended
  121. // if misbehaviorString != "" {
  122. // misbehaviorString += ","
  123. // }
  124. // heightString := strconv.Itoa(int(height))
  125. // misbehaviorString += misbehavior + "," + heightString
  126. // }
  127. // if misbehaviorString != "" {
  128. // command += " --misbehaviors " + misbehaviorString
  129. // }
  130. if logLevel != "" && logLevel != config.DefaultLogLevel {
  131. command += " --log-level " + logLevel
  132. }
  133. return command
  134. },
  135. "addUint32": func(x, y uint32) uint32 {
  136. return x + y
  137. },
  138. }).Parse(`version: '2.4'
  139. networks:
  140. {{ .Name }}:
  141. labels:
  142. e2e: true
  143. driver: bridge
  144. {{- if .IPv6 }}
  145. enable_ipv6: true
  146. {{- end }}
  147. ipam:
  148. driver: default
  149. config:
  150. - subnet: {{ .IP }}
  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 }}
  161. command: {{ startCommands .Misbehaviors .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. {{end}}`)
  175. if err != nil {
  176. return nil, err
  177. }
  178. var buf bytes.Buffer
  179. err = tmpl.Execute(&buf, testnet)
  180. if err != nil {
  181. return nil, err
  182. }
  183. return buf.Bytes(), nil
  184. }
  185. // MakeGenesis generates a genesis document.
  186. func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) {
  187. genesis := types.GenesisDoc{
  188. GenesisTime: time.Now(),
  189. ChainID: testnet.Name,
  190. ConsensusParams: types.DefaultConsensusParams(),
  191. InitialHeight: testnet.InitialHeight,
  192. }
  193. switch testnet.KeyType {
  194. case "", types.ABCIPubKeyTypeEd25519, types.ABCIPubKeyTypeSecp256k1:
  195. genesis.ConsensusParams.Validator.PubKeyTypes =
  196. append(genesis.ConsensusParams.Validator.PubKeyTypes, types.ABCIPubKeyTypeSecp256k1)
  197. default:
  198. return genesis, errors.New("unsupported KeyType")
  199. }
  200. for validator, power := range testnet.Validators {
  201. genesis.Validators = append(genesis.Validators, types.GenesisValidator{
  202. Name: validator.Name,
  203. Address: validator.PrivvalKey.PubKey().Address(),
  204. PubKey: validator.PrivvalKey.PubKey(),
  205. Power: power,
  206. })
  207. }
  208. // The validator set will be sorted internally by Tendermint ranked by power,
  209. // but we sort it here as well so that all genesis files are identical.
  210. sort.Slice(genesis.Validators, func(i, j int) bool {
  211. return strings.Compare(genesis.Validators[i].Name, genesis.Validators[j].Name) == -1
  212. })
  213. if len(testnet.InitialState) > 0 {
  214. appState, err := json.Marshal(testnet.InitialState)
  215. if err != nil {
  216. return genesis, err
  217. }
  218. genesis.AppState = appState
  219. }
  220. return genesis, genesis.ValidateAndComplete()
  221. }
  222. // MakeConfig generates a Tendermint config for a node.
  223. func MakeConfig(node *e2e.Node) (*config.Config, error) {
  224. cfg := config.DefaultConfig()
  225. cfg.Moniker = node.Name
  226. cfg.ProxyApp = AppAddressTCP
  227. if node.LogLevel != "" {
  228. cfg.LogLevel = node.LogLevel
  229. }
  230. cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657"
  231. cfg.RPC.PprofListenAddress = ":6060"
  232. cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false))
  233. cfg.P2P.AddrBookStrict = false
  234. cfg.DBBackend = node.Database
  235. cfg.StateSync.DiscoveryTime = 5 * time.Second
  236. if node.Mode != e2e.ModeLight {
  237. cfg.Mode = string(node.Mode)
  238. }
  239. switch node.ABCIProtocol {
  240. case e2e.ProtocolUNIX:
  241. cfg.ProxyApp = AppAddressUNIX
  242. case e2e.ProtocolTCP:
  243. cfg.ProxyApp = AppAddressTCP
  244. case e2e.ProtocolGRPC:
  245. cfg.ProxyApp = AppAddressTCP
  246. cfg.ABCI = "grpc"
  247. case e2e.ProtocolBuiltin:
  248. cfg.ProxyApp = ""
  249. cfg.ABCI = ""
  250. default:
  251. return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
  252. }
  253. // Tendermint errors if it does not have a privval key set up, regardless of whether
  254. // it's actually needed (e.g. for remote KMS or non-validators). We set up a dummy
  255. // key here by default, and use the real key for actual validators that should use
  256. // the file privval.
  257. cfg.PrivValidatorListenAddr = ""
  258. cfg.PrivValidatorKey = PrivvalDummyKeyFile
  259. cfg.PrivValidatorState = PrivvalDummyStateFile
  260. switch node.Mode {
  261. case e2e.ModeValidator:
  262. switch node.PrivvalProtocol {
  263. case e2e.ProtocolFile:
  264. cfg.PrivValidatorKey = PrivvalKeyFile
  265. cfg.PrivValidatorState = PrivvalStateFile
  266. case e2e.ProtocolUNIX:
  267. cfg.PrivValidatorListenAddr = PrivvalAddressUNIX
  268. case e2e.ProtocolTCP:
  269. cfg.PrivValidatorListenAddr = PrivvalAddressTCP
  270. case e2e.ProtocolGRPC:
  271. cfg.PrivValidatorListenAddr = PrivvalAddressGRPC
  272. default:
  273. return nil, fmt.Errorf("invalid privval protocol setting %q", node.PrivvalProtocol)
  274. }
  275. case e2e.ModeSeed:
  276. cfg.P2P.PexReactor = true
  277. case e2e.ModeFull, e2e.ModeLight:
  278. // Don't need to do anything, since we're using a dummy privval key by default.
  279. default:
  280. return nil, fmt.Errorf("unexpected mode %q", node.Mode)
  281. }
  282. if node.FastSync == "" {
  283. cfg.FastSyncMode = false
  284. } else {
  285. cfg.FastSync.Version = node.FastSync
  286. }
  287. if node.StateSync {
  288. cfg.StateSync.Enable = true
  289. cfg.StateSync.RPCServers = []string{}
  290. for _, peer := range node.Testnet.ArchiveNodes() {
  291. if peer.Name == node.Name {
  292. continue
  293. }
  294. cfg.StateSync.RPCServers = append(cfg.StateSync.RPCServers, peer.AddressRPC())
  295. }
  296. if len(cfg.StateSync.RPCServers) < 2 {
  297. return nil, errors.New("unable to find 2 suitable state sync RPC servers")
  298. }
  299. }
  300. cfg.P2P.Seeds = ""
  301. for _, seed := range node.Seeds {
  302. if len(cfg.P2P.Seeds) > 0 {
  303. cfg.P2P.Seeds += ","
  304. }
  305. cfg.P2P.Seeds += seed.AddressP2P(true)
  306. }
  307. cfg.P2P.PersistentPeers = ""
  308. for _, peer := range node.PersistentPeers {
  309. if len(cfg.P2P.PersistentPeers) > 0 {
  310. cfg.P2P.PersistentPeers += ","
  311. }
  312. cfg.P2P.PersistentPeers += peer.AddressP2P(true)
  313. }
  314. cfg.Instrumentation.Prometheus = true
  315. return cfg, nil
  316. }
  317. // MakeAppConfig generates an ABCI application config for a node.
  318. func MakeAppConfig(node *e2e.Node) ([]byte, error) {
  319. cfg := map[string]interface{}{
  320. "chain_id": node.Testnet.Name,
  321. "dir": "data/app",
  322. "listen": AppAddressUNIX,
  323. "mode": node.Mode,
  324. "proxy_port": node.ProxyPort,
  325. "protocol": "socket",
  326. "persist_interval": node.PersistInterval,
  327. "snapshot_interval": node.SnapshotInterval,
  328. "retain_blocks": node.RetainBlocks,
  329. "key_type": node.PrivvalKey.Type(),
  330. }
  331. switch node.ABCIProtocol {
  332. case e2e.ProtocolUNIX:
  333. cfg["listen"] = AppAddressUNIX
  334. case e2e.ProtocolTCP:
  335. cfg["listen"] = AppAddressTCP
  336. case e2e.ProtocolGRPC:
  337. cfg["listen"] = AppAddressTCP
  338. cfg["protocol"] = "grpc"
  339. case e2e.ProtocolBuiltin:
  340. delete(cfg, "listen")
  341. cfg["protocol"] = "builtin"
  342. default:
  343. return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
  344. }
  345. if node.Mode == e2e.ModeValidator {
  346. switch node.PrivvalProtocol {
  347. case e2e.ProtocolFile:
  348. case e2e.ProtocolTCP:
  349. cfg["privval_server"] = PrivvalAddressTCP
  350. cfg["privval_key"] = PrivvalKeyFile
  351. cfg["privval_state"] = PrivvalStateFile
  352. case e2e.ProtocolUNIX:
  353. cfg["privval_server"] = PrivvalAddressUNIX
  354. cfg["privval_key"] = PrivvalKeyFile
  355. cfg["privval_state"] = PrivvalStateFile
  356. case e2e.ProtocolGRPC:
  357. cfg["privval_server"] = PrivvalAddressGRPC
  358. cfg["privval_key"] = PrivvalKeyFile
  359. cfg["privval_state"] = PrivvalStateFile
  360. default:
  361. return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol)
  362. }
  363. }
  364. misbehaviors := make(map[string]string)
  365. for height, misbehavior := range node.Misbehaviors {
  366. misbehaviors[strconv.Itoa(int(height))] = misbehavior
  367. }
  368. cfg["misbehaviors"] = misbehaviors
  369. if len(node.Testnet.ValidatorUpdates) > 0 {
  370. validatorUpdates := map[string]map[string]int64{}
  371. for height, validators := range node.Testnet.ValidatorUpdates {
  372. updateVals := map[string]int64{}
  373. for node, power := range validators {
  374. updateVals[base64.StdEncoding.EncodeToString(node.PrivvalKey.PubKey().Bytes())] = power
  375. }
  376. validatorUpdates[fmt.Sprintf("%v", height)] = updateVals
  377. }
  378. cfg["validator_update"] = validatorUpdates
  379. }
  380. var buf bytes.Buffer
  381. err := toml.NewEncoder(&buf).Encode(cfg)
  382. if err != nil {
  383. return nil, fmt.Errorf("failed to generate app config: %w", err)
  384. }
  385. return buf.Bytes(), nil
  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. // FIXME Apparently there's no function to simply load a config file without
  391. // involving the entire Viper apparatus, so we'll just resort to regexps.
  392. bz, err := ioutil.ReadFile(cfgPath)
  393. if err != nil {
  394. return err
  395. }
  396. bz = regexp.MustCompile(`(?m)^trust-height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust-height = %v`, height)))
  397. bz = regexp.MustCompile(`(?m)^trust-hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust-hash = "%X"`, hash)))
  398. return ioutil.WriteFile(cfgPath, bz, 0644)
  399. }