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