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.

362 lines
10 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. "strings"
  15. "text/template"
  16. "time"
  17. "github.com/BurntSushi/toml"
  18. "github.com/tendermint/tendermint/config"
  19. "github.com/tendermint/tendermint/crypto/ed25519"
  20. "github.com/tendermint/tendermint/p2p"
  21. "github.com/tendermint/tendermint/privval"
  22. e2e "github.com/tendermint/tendermint/test/e2e/pkg"
  23. "github.com/tendermint/tendermint/types"
  24. )
  25. const (
  26. AppAddressTCP = "tcp://127.0.0.1:30000"
  27. AppAddressUNIX = "unix:///var/run/app.sock"
  28. PrivvalAddressTCP = "tcp://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(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 = ioutil.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. err := os.MkdirAll(dir, 0755)
  63. if err != nil {
  64. return err
  65. }
  66. }
  67. err = genesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json"))
  68. if err != nil {
  69. return err
  70. }
  71. cfg, err := MakeConfig(node)
  72. if err != nil {
  73. return err
  74. }
  75. config.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), cfg) // panics
  76. appCfg, err := MakeAppConfig(node)
  77. if err != nil {
  78. return err
  79. }
  80. err = ioutil.WriteFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg, 0644)
  81. if err != nil {
  82. return err
  83. }
  84. err = (&p2p.NodeKey{PrivKey: node.Key}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json"))
  85. if err != nil {
  86. return err
  87. }
  88. (privval.NewFilePV(node.Key,
  89. filepath.Join(nodeDir, PrivvalKeyFile),
  90. filepath.Join(nodeDir, PrivvalStateFile),
  91. )).Save()
  92. // Set up a dummy validator. Tendermint requires a file PV even when not used, so we
  93. // give it a dummy such that it will fail if it actually tries to use it.
  94. (privval.NewFilePV(ed25519.GenPrivKey(),
  95. filepath.Join(nodeDir, PrivvalDummyKeyFile),
  96. filepath.Join(nodeDir, PrivvalDummyStateFile),
  97. )).Save()
  98. }
  99. return nil
  100. }
  101. // MakeDockerCompose generates a Docker Compose config for a testnet.
  102. func MakeDockerCompose(testnet *e2e.Testnet) ([]byte, error) {
  103. // Must use version 2 Docker Compose format, to support IPv6.
  104. tmpl, err := template.New("docker-compose").Parse(`version: '2.4'
  105. networks:
  106. {{ .Name }}:
  107. driver: bridge
  108. {{- if .IPv6 }}
  109. enable_ipv6: true
  110. {{- end }}
  111. ipam:
  112. driver: default
  113. config:
  114. - subnet: {{ .IP }}
  115. services:
  116. {{- range .Nodes }}
  117. {{ .Name }}:
  118. container_name: {{ .Name }}
  119. image: tendermint/e2e-node
  120. {{- if eq .ABCIProtocol "builtin" }}
  121. entrypoint: /usr/bin/entrypoint-builtin
  122. {{- end }}
  123. init: true
  124. ports:
  125. - 26656
  126. - {{ if .ProxyPort }}{{ .ProxyPort }}:{{ end }}26657
  127. volumes:
  128. - ./{{ .Name }}:/tendermint
  129. networks:
  130. {{ $.Name }}:
  131. ipv{{ if $.IPv6 }}6{{ else }}4{{ end}}_address: {{ .IP }}
  132. {{end}}`)
  133. if err != nil {
  134. return nil, err
  135. }
  136. var buf bytes.Buffer
  137. err = tmpl.Execute(&buf, testnet)
  138. if err != nil {
  139. return nil, err
  140. }
  141. return buf.Bytes(), nil
  142. }
  143. // MakeGenesis generates a genesis document.
  144. func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) {
  145. genesis := types.GenesisDoc{
  146. GenesisTime: time.Now(),
  147. ChainID: testnet.Name,
  148. ConsensusParams: types.DefaultConsensusParams(),
  149. InitialHeight: testnet.InitialHeight,
  150. }
  151. for validator, power := range testnet.Validators {
  152. genesis.Validators = append(genesis.Validators, types.GenesisValidator{
  153. Name: validator.Name,
  154. Address: validator.Key.PubKey().Address(),
  155. PubKey: validator.Key.PubKey(),
  156. Power: power,
  157. })
  158. }
  159. // The validator set will be sorted internally by Tendermint ranked by power,
  160. // but we sort it here as well so that all genesis files are identical.
  161. sort.Slice(genesis.Validators, func(i, j int) bool {
  162. return strings.Compare(genesis.Validators[i].Name, genesis.Validators[j].Name) == -1
  163. })
  164. if len(testnet.InitialState) > 0 {
  165. appState, err := json.Marshal(testnet.InitialState)
  166. if err != nil {
  167. return genesis, err
  168. }
  169. genesis.AppState = appState
  170. }
  171. return genesis, genesis.ValidateAndComplete()
  172. }
  173. // MakeConfig generates a Tendermint config for a node.
  174. func MakeConfig(node *e2e.Node) (*config.Config, error) {
  175. cfg := config.DefaultConfig()
  176. cfg.Moniker = node.Name
  177. cfg.ProxyApp = AppAddressTCP
  178. cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657"
  179. cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false))
  180. cfg.P2P.AddrBookStrict = false
  181. cfg.DBBackend = node.Database
  182. cfg.StateSync.DiscoveryTime = 5 * time.Second
  183. switch node.ABCIProtocol {
  184. case e2e.ProtocolUNIX:
  185. cfg.ProxyApp = AppAddressUNIX
  186. case e2e.ProtocolTCP:
  187. cfg.ProxyApp = AppAddressTCP
  188. case e2e.ProtocolGRPC:
  189. cfg.ProxyApp = AppAddressTCP
  190. cfg.ABCI = "grpc"
  191. case e2e.ProtocolBuiltin:
  192. cfg.ProxyApp = ""
  193. cfg.ABCI = ""
  194. default:
  195. return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
  196. }
  197. // Tendermint errors if it does not have a privval key set up, regardless of whether
  198. // it's actually needed (e.g. for remote KMS or non-validators). We set up a dummy
  199. // key here by default, and use the real key for actual validators that should use
  200. // the file privval.
  201. cfg.PrivValidatorListenAddr = ""
  202. cfg.PrivValidatorKey = PrivvalDummyKeyFile
  203. cfg.PrivValidatorState = PrivvalDummyStateFile
  204. switch node.Mode {
  205. case e2e.ModeValidator:
  206. switch node.PrivvalProtocol {
  207. case e2e.ProtocolFile:
  208. cfg.PrivValidatorKey = PrivvalKeyFile
  209. cfg.PrivValidatorState = PrivvalStateFile
  210. case e2e.ProtocolUNIX:
  211. cfg.PrivValidatorListenAddr = PrivvalAddressUNIX
  212. case e2e.ProtocolTCP:
  213. cfg.PrivValidatorListenAddr = PrivvalAddressTCP
  214. default:
  215. return nil, fmt.Errorf("invalid privval protocol setting %q", node.PrivvalProtocol)
  216. }
  217. case e2e.ModeSeed:
  218. cfg.P2P.SeedMode = true
  219. cfg.P2P.PexReactor = true
  220. case e2e.ModeFull:
  221. // Don't need to do anything, since we're using a dummy privval key by default.
  222. default:
  223. return nil, fmt.Errorf("unexpected mode %q", node.Mode)
  224. }
  225. if node.FastSync == "" {
  226. cfg.FastSyncMode = false
  227. } else {
  228. cfg.FastSync.Version = node.FastSync
  229. }
  230. if node.StateSync {
  231. cfg.StateSync.Enable = true
  232. cfg.StateSync.RPCServers = []string{}
  233. for _, peer := range node.Testnet.ArchiveNodes() {
  234. if peer.Name == node.Name {
  235. continue
  236. }
  237. cfg.StateSync.RPCServers = append(cfg.StateSync.RPCServers, peer.AddressRPC())
  238. }
  239. if len(cfg.StateSync.RPCServers) < 2 {
  240. return nil, errors.New("unable to find 2 suitable state sync RPC servers")
  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. return cfg, nil
  258. }
  259. // MakeAppConfig generates an ABCI application config for a node.
  260. func MakeAppConfig(node *e2e.Node) ([]byte, error) {
  261. cfg := map[string]interface{}{
  262. "chain_id": node.Testnet.Name,
  263. "dir": "data/app",
  264. "listen": AppAddressUNIX,
  265. "protocol": "socket",
  266. "persist_interval": node.PersistInterval,
  267. "snapshot_interval": node.SnapshotInterval,
  268. "retain_blocks": node.RetainBlocks,
  269. }
  270. switch node.ABCIProtocol {
  271. case e2e.ProtocolUNIX:
  272. cfg["listen"] = AppAddressUNIX
  273. case e2e.ProtocolTCP:
  274. cfg["listen"] = AppAddressTCP
  275. case e2e.ProtocolGRPC:
  276. cfg["listen"] = AppAddressTCP
  277. cfg["protocol"] = "grpc"
  278. case e2e.ProtocolBuiltin:
  279. delete(cfg, "listen")
  280. cfg["protocol"] = "builtin"
  281. default:
  282. return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
  283. }
  284. if node.Mode == e2e.ModeValidator {
  285. switch node.PrivvalProtocol {
  286. case e2e.ProtocolFile:
  287. case e2e.ProtocolTCP:
  288. cfg["privval_server"] = PrivvalAddressTCP
  289. cfg["privval_key"] = PrivvalKeyFile
  290. cfg["privval_state"] = PrivvalStateFile
  291. case e2e.ProtocolUNIX:
  292. cfg["privval_server"] = PrivvalAddressUNIX
  293. cfg["privval_key"] = PrivvalKeyFile
  294. cfg["privval_state"] = PrivvalStateFile
  295. default:
  296. return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol)
  297. }
  298. }
  299. if len(node.Testnet.ValidatorUpdates) > 0 {
  300. validatorUpdates := map[string]map[string]int64{}
  301. for height, validators := range node.Testnet.ValidatorUpdates {
  302. updateVals := map[string]int64{}
  303. for node, power := range validators {
  304. updateVals[base64.StdEncoding.EncodeToString(node.Key.PubKey().Bytes())] = power
  305. }
  306. validatorUpdates[fmt.Sprintf("%v", height)] = updateVals
  307. }
  308. cfg["validator_update"] = validatorUpdates
  309. }
  310. var buf bytes.Buffer
  311. err := toml.NewEncoder(&buf).Encode(cfg)
  312. if err != nil {
  313. return nil, fmt.Errorf("failed to generate app config: %w", err)
  314. }
  315. return buf.Bytes(), nil
  316. }
  317. // UpdateConfigStateSync updates the state sync config for a node.
  318. func UpdateConfigStateSync(node *e2e.Node, height int64, hash []byte) error {
  319. cfgPath := filepath.Join(node.Testnet.Dir, node.Name, "config", "config.toml")
  320. // FIXME Apparently there's no function to simply load a config file without
  321. // involving the entire Viper apparatus, so we'll just resort to regexps.
  322. bz, err := ioutil.ReadFile(cfgPath)
  323. if err != nil {
  324. return err
  325. }
  326. bz = regexp.MustCompile(`(?m)^trust_height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_height = %v`, height)))
  327. bz = regexp.MustCompile(`(?m)^trust_hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_hash = "%X"`, hash)))
  328. return ioutil.WriteFile(cfgPath, bz, 0644)
  329. }