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.

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