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.

420 lines
11 KiB

  1. package main
  2. import (
  3. "encoding/hex"
  4. "fmt"
  5. "os"
  6. "os/signal"
  7. "path"
  8. "strings"
  9. ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
  10. "github.com/ethereum/go-ethereum/crypto"
  11. "github.com/hermeznetwork/hermez-node/common"
  12. "github.com/hermeznetwork/hermez-node/config"
  13. dbUtils "github.com/hermeznetwork/hermez-node/db"
  14. "github.com/hermeznetwork/hermez-node/db/historydb"
  15. "github.com/hermeznetwork/hermez-node/db/kvdb"
  16. "github.com/hermeznetwork/hermez-node/db/l2db"
  17. "github.com/hermeznetwork/hermez-node/log"
  18. "github.com/hermeznetwork/hermez-node/node"
  19. "github.com/hermeznetwork/tracerr"
  20. "github.com/iden3/go-iden3-crypto/babyjub"
  21. "github.com/jmoiron/sqlx"
  22. "github.com/urfave/cli/v2"
  23. )
  24. const (
  25. flagCfg = "cfg"
  26. flagMode = "mode"
  27. flagSK = "privatekey"
  28. flagYes = "yes"
  29. flagBlock = "block"
  30. modeSync = "sync"
  31. modeCoord = "coord"
  32. )
  33. func cmdGenBJJ(c *cli.Context) error {
  34. sk := babyjub.NewRandPrivKey()
  35. skBuf := [32]byte(sk)
  36. pk := sk.Public()
  37. fmt.Printf("BJJ = \"0x%s\"\n", pk.String())
  38. fmt.Printf("BJJPrivateKey = \"0x%s\"\n", hex.EncodeToString(skBuf[:]))
  39. return nil
  40. }
  41. func cmdImportKey(c *cli.Context) error {
  42. _cfg, err := parseCli(c)
  43. if err != nil {
  44. return tracerr.Wrap(fmt.Errorf("error parsing flags and config: %w", err))
  45. }
  46. if _cfg.mode != node.ModeCoordinator {
  47. return tracerr.Wrap(fmt.Errorf("importkey must use mode coordinator"))
  48. }
  49. cfg := _cfg.node
  50. scryptN := ethKeystore.StandardScryptN
  51. scryptP := ethKeystore.StandardScryptP
  52. if cfg.Coordinator.Debug.LightScrypt {
  53. scryptN = ethKeystore.LightScryptN
  54. scryptP = ethKeystore.LightScryptP
  55. }
  56. keyStore := ethKeystore.NewKeyStore(cfg.Coordinator.EthClient.Keystore.Path,
  57. scryptN, scryptP)
  58. hexKey := c.String(flagSK)
  59. hexKey = strings.TrimPrefix(hexKey, "0x")
  60. sk, err := crypto.HexToECDSA(hexKey)
  61. if err != nil {
  62. return tracerr.Wrap(err)
  63. }
  64. acc, err := keyStore.ImportECDSA(sk, cfg.Coordinator.EthClient.Keystore.Password)
  65. if err != nil {
  66. return tracerr.Wrap(err)
  67. }
  68. log.Infow("Imported private key", "addr", acc.Address.Hex())
  69. return nil
  70. }
  71. func resetStateDBs(cfg *Config, batchNum common.BatchNum) error {
  72. log.Infof("Reset Synchronizer StateDB to batchNum %v...", batchNum)
  73. // Manually make a checkpoint from batchNum to current to force current
  74. // to be a valid checkpoint. This is useful because in case of a
  75. // crash, current can be corrupted and the first thing that
  76. // `kvdb.NewKVDB` does is read the current checkpoint, which wouldn't
  77. // succeed in case of corruption.
  78. dbPath := cfg.node.StateDB.Path
  79. source := path.Join(dbPath, fmt.Sprintf("%s%d", kvdb.PathBatchNum, batchNum))
  80. current := path.Join(dbPath, kvdb.PathCurrent)
  81. last := path.Join(dbPath, kvdb.PathLast)
  82. if err := os.RemoveAll(last); err != nil {
  83. return tracerr.Wrap(fmt.Errorf("os.RemoveAll: %w", err))
  84. }
  85. if batchNum == 0 {
  86. if err := os.RemoveAll(current); err != nil {
  87. return tracerr.Wrap(fmt.Errorf("os.RemoveAll: %w", err))
  88. }
  89. } else {
  90. if err := kvdb.PebbleMakeCheckpoint(source, current); err != nil {
  91. return tracerr.Wrap(fmt.Errorf("kvdb.PebbleMakeCheckpoint: %w", err))
  92. }
  93. }
  94. db, err := kvdb.NewKVDB(kvdb.Config{
  95. Path: dbPath,
  96. NoGapsCheck: true,
  97. NoLast: true,
  98. })
  99. if err != nil {
  100. return tracerr.Wrap(fmt.Errorf("kvdb.NewKVDB: %w", err))
  101. }
  102. if err := db.Reset(batchNum); err != nil {
  103. return tracerr.Wrap(fmt.Errorf("db.Reset: %w", err))
  104. }
  105. if cfg.mode == node.ModeCoordinator {
  106. log.Infof("Wipe Coordinator StateDBs...")
  107. // We wipe the Coordinator StateDBs entirely (by deleting
  108. // current and resetting to batchNum 0) because the Coordinator
  109. // StateDBs are always reset from Synchronizer when the
  110. // coordinator pipeline starts.
  111. dbPath := cfg.node.Coordinator.TxSelector.Path
  112. current := path.Join(dbPath, kvdb.PathCurrent)
  113. if err := os.RemoveAll(current); err != nil {
  114. return tracerr.Wrap(fmt.Errorf("os.RemoveAll: %w", err))
  115. }
  116. db, err := kvdb.NewKVDB(kvdb.Config{
  117. Path: dbPath,
  118. NoGapsCheck: true,
  119. NoLast: true,
  120. })
  121. if err != nil {
  122. return tracerr.Wrap(fmt.Errorf("kvdb.NewKVDB: %w", err))
  123. }
  124. if err := db.Reset(0); err != nil {
  125. return tracerr.Wrap(fmt.Errorf("db.Reset: %w", err))
  126. }
  127. dbPath = cfg.node.Coordinator.BatchBuilder.Path
  128. current = path.Join(dbPath, kvdb.PathCurrent)
  129. if err := os.RemoveAll(current); err != nil {
  130. return tracerr.Wrap(fmt.Errorf("os.RemoveAll: %w", err))
  131. }
  132. db, err = kvdb.NewKVDB(kvdb.Config{
  133. Path: dbPath,
  134. NoGapsCheck: true,
  135. NoLast: true,
  136. })
  137. if err != nil {
  138. return tracerr.Wrap(fmt.Errorf("statedb.NewKVDB: %w", err))
  139. }
  140. if err := db.Reset(0); err != nil {
  141. return tracerr.Wrap(fmt.Errorf("db.Reset: %w", err))
  142. }
  143. }
  144. return nil
  145. }
  146. func cmdWipeSQL(c *cli.Context) error {
  147. _cfg, err := parseCli(c)
  148. if err != nil {
  149. return tracerr.Wrap(fmt.Errorf("error parsing flags and config: %w", err))
  150. }
  151. cfg := _cfg.node
  152. yes := c.Bool(flagYes)
  153. if !yes {
  154. fmt.Print("*WARNING* Are you sure you want to delete " +
  155. "the SQL DB and StateDBs? [y/N]: ")
  156. var input string
  157. if _, err := fmt.Scanln(&input); err != nil {
  158. return tracerr.Wrap(err)
  159. }
  160. input = strings.ToLower(input)
  161. if !(input == "y" || input == "yes") {
  162. return nil
  163. }
  164. }
  165. db, err := dbUtils.ConnectSQLDB(
  166. cfg.PostgreSQL.PortWrite,
  167. cfg.PostgreSQL.HostWrite,
  168. cfg.PostgreSQL.UserWrite,
  169. cfg.PostgreSQL.PasswordWrite,
  170. cfg.PostgreSQL.NameWrite,
  171. )
  172. if err != nil {
  173. return tracerr.Wrap(err)
  174. }
  175. log.Info("Wiping SQL DB...")
  176. if err := dbUtils.MigrationsDown(db.DB); err != nil {
  177. return tracerr.Wrap(fmt.Errorf("dbUtils.MigrationsDown: %w", err))
  178. }
  179. log.Info("Wiping StateDBs...")
  180. if err := resetStateDBs(_cfg, 0); err != nil {
  181. return tracerr.Wrap(fmt.Errorf("resetStateDBs: %w", err))
  182. }
  183. return nil
  184. }
  185. func cmdRun(c *cli.Context) error {
  186. cfg, err := parseCli(c)
  187. if err != nil {
  188. return tracerr.Wrap(fmt.Errorf("error parsing flags and config: %w", err))
  189. }
  190. node, err := node.NewNode(cfg.mode, cfg.node)
  191. if err != nil {
  192. return tracerr.Wrap(fmt.Errorf("error starting node: %w", err))
  193. }
  194. node.Start()
  195. stopCh := make(chan interface{})
  196. // catch ^C to send the stop signal
  197. ossig := make(chan os.Signal, 1)
  198. signal.Notify(ossig, os.Interrupt)
  199. const forceStopCount = 3
  200. go func() {
  201. n := 0
  202. for sig := range ossig {
  203. if sig == os.Interrupt {
  204. log.Info("Received Interrupt Signal")
  205. stopCh <- nil
  206. n++
  207. if n == forceStopCount {
  208. log.Fatalf("Received %v Interrupt Signals", forceStopCount)
  209. }
  210. }
  211. }
  212. }()
  213. <-stopCh
  214. node.Stop()
  215. return nil
  216. }
  217. func cmdDiscard(c *cli.Context) error {
  218. _cfg, err := parseCli(c)
  219. if err != nil {
  220. return tracerr.Wrap(fmt.Errorf("error parsing flags and config: %w", err))
  221. }
  222. cfg := _cfg.node
  223. blockNum := c.Int64(flagBlock)
  224. log.Infof("Discarding all blocks up to block %v...", blockNum)
  225. dbWrite, err := dbUtils.InitSQLDB(
  226. cfg.PostgreSQL.PortWrite,
  227. cfg.PostgreSQL.HostWrite,
  228. cfg.PostgreSQL.UserWrite,
  229. cfg.PostgreSQL.PasswordWrite,
  230. cfg.PostgreSQL.NameWrite,
  231. )
  232. if err != nil {
  233. return tracerr.Wrap(fmt.Errorf("dbUtils.InitSQLDB: %w", err))
  234. }
  235. var dbRead *sqlx.DB
  236. if cfg.PostgreSQL.HostRead == "" {
  237. dbRead = dbWrite
  238. } else if cfg.PostgreSQL.HostRead == cfg.PostgreSQL.HostWrite {
  239. return tracerr.Wrap(fmt.Errorf(
  240. "PostgreSQL.HostRead and PostgreSQL.HostWrite must be different",
  241. ))
  242. } else {
  243. dbRead, err = dbUtils.InitSQLDB(
  244. cfg.PostgreSQL.PortRead,
  245. cfg.PostgreSQL.HostRead,
  246. cfg.PostgreSQL.UserRead,
  247. cfg.PostgreSQL.PasswordRead,
  248. cfg.PostgreSQL.NameRead,
  249. )
  250. if err != nil {
  251. return tracerr.Wrap(fmt.Errorf("dbUtils.InitSQLDB: %w", err))
  252. }
  253. }
  254. historyDB := historydb.NewHistoryDB(dbRead, dbWrite, nil)
  255. if err := historyDB.Reorg(blockNum); err != nil {
  256. return tracerr.Wrap(fmt.Errorf("historyDB.Reorg: %w", err))
  257. }
  258. batchNum, err := historyDB.GetLastBatchNum()
  259. if err != nil {
  260. return tracerr.Wrap(fmt.Errorf("historyDB.GetLastBatchNum: %w", err))
  261. }
  262. l2DB := l2db.NewL2DB(
  263. dbRead, dbWrite,
  264. cfg.Coordinator.L2DB.SafetyPeriod,
  265. cfg.Coordinator.L2DB.MaxTxs,
  266. cfg.Coordinator.L2DB.MinFeeUSD,
  267. cfg.Coordinator.L2DB.TTL.Duration,
  268. nil,
  269. )
  270. if err := l2DB.Reorg(batchNum); err != nil {
  271. return tracerr.Wrap(fmt.Errorf("l2DB.Reorg: %w", err))
  272. }
  273. log.Info("Resetting StateDBs...")
  274. if err := resetStateDBs(_cfg, batchNum); err != nil {
  275. return tracerr.Wrap(fmt.Errorf("resetStateDBs: %w", err))
  276. }
  277. return nil
  278. }
  279. // Config is the configuration of the hermez node execution
  280. type Config struct {
  281. mode node.Mode
  282. node *config.Node
  283. }
  284. func parseCli(c *cli.Context) (*Config, error) {
  285. cfg, err := getConfig(c)
  286. if err != nil {
  287. if err := cli.ShowAppHelp(c); err != nil {
  288. panic(err)
  289. }
  290. return nil, tracerr.Wrap(err)
  291. }
  292. return cfg, nil
  293. }
  294. func getConfig(c *cli.Context) (*Config, error) {
  295. var cfg Config
  296. mode := c.String(flagMode)
  297. nodeCfgPath := c.String(flagCfg)
  298. if nodeCfgPath == "" {
  299. return nil, tracerr.Wrap(fmt.Errorf("required flag \"%v\" not set", flagCfg))
  300. }
  301. var err error
  302. switch mode {
  303. case modeSync:
  304. cfg.mode = node.ModeSynchronizer
  305. cfg.node, err = config.LoadNode(nodeCfgPath)
  306. if err != nil {
  307. return nil, tracerr.Wrap(err)
  308. }
  309. case modeCoord:
  310. cfg.mode = node.ModeCoordinator
  311. cfg.node, err = config.LoadCoordinator(nodeCfgPath)
  312. if err != nil {
  313. return nil, tracerr.Wrap(err)
  314. }
  315. default:
  316. return nil, tracerr.Wrap(fmt.Errorf("invalid mode \"%v\"", mode))
  317. }
  318. return &cfg, nil
  319. }
  320. func main() {
  321. app := cli.NewApp()
  322. app.Name = "hermez-node"
  323. app.Version = "0.1.0-alpha"
  324. app.Flags = []cli.Flag{
  325. &cli.StringFlag{
  326. Name: flagMode,
  327. Usage: fmt.Sprintf("Set node `MODE` (can be \"%v\" or \"%v\")", modeSync, modeCoord),
  328. Required: true,
  329. },
  330. &cli.StringFlag{
  331. Name: flagCfg,
  332. Usage: "Node configuration `FILE`",
  333. Required: true,
  334. },
  335. }
  336. app.Commands = []*cli.Command{
  337. {
  338. Name: "importkey",
  339. Aliases: []string{},
  340. Usage: "Import ethereum private key",
  341. Action: cmdImportKey,
  342. Flags: []cli.Flag{
  343. &cli.StringFlag{
  344. Name: flagSK,
  345. Usage: "ethereum `PRIVATE_KEY` in hex",
  346. Required: true,
  347. }},
  348. },
  349. {
  350. Name: "genbjj",
  351. Aliases: []string{},
  352. Usage: "Generate a new BabyJubJub key",
  353. Action: cmdGenBJJ,
  354. },
  355. {
  356. Name: "wipesql",
  357. Aliases: []string{},
  358. Usage: "Wipe the SQL DB (HistoryDB and L2DB) and the StateDBs, " +
  359. "leaving the DB in a clean state",
  360. Action: cmdWipeSQL,
  361. Flags: []cli.Flag{
  362. &cli.BoolFlag{
  363. Name: flagYes,
  364. Usage: "automatic yes to the prompt",
  365. Required: false,
  366. }},
  367. },
  368. {
  369. Name: "run",
  370. Aliases: []string{},
  371. Usage: "Run the hermez-node in the indicated mode",
  372. Action: cmdRun,
  373. },
  374. {
  375. Name: "discard",
  376. Aliases: []string{},
  377. Usage: "Discard blocks up to a specified block number",
  378. Action: cmdDiscard,
  379. Flags: []cli.Flag{
  380. &cli.Int64Flag{
  381. Name: flagBlock,
  382. Usage: "last block number to keep",
  383. Required: false,
  384. }},
  385. },
  386. }
  387. err := app.Run(os.Args)
  388. if err != nil {
  389. fmt.Printf("\nError: %v\n", tracerr.Sprint(err))
  390. os.Exit(1)
  391. }
  392. }