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.

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