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.

422 lines
12 KiB

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