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.TTL.Duration,
322 nil,
323 )
324 if err := l2DB.Reorg(batchNum); err != nil {
325 return tracerr.Wrap(fmt.Errorf("l2DB.Reorg: %w", err))
326 }
327
328 log.Info("Resetting StateDBs...")
329 if err := resetStateDBs(_cfg, batchNum); err != nil {
330 return tracerr.Wrap(fmt.Errorf("resetStateDBs: %w", err))
331 }
332
333 return nil
334}
335
336// Config is the configuration of the hermez node execution
337type Config struct {
338 mode node.Mode
339 node *config.Node
340}
341
342func parseCli(c *cli.Context) (*Config, error) {
343 cfg, err := getConfig(c)
344 if err != nil {
345 if err := cli.ShowAppHelp(c); err != nil {
346 panic(err)
347 }
348 return nil, tracerr.Wrap(err)
349 }
350 return cfg, nil
351}
352
353func getConfig(c *cli.Context) (*Config, error) {
354 var cfg Config
355 mode := c.String(flagMode)
356 nodeCfgPath := c.String(flagCfg)
357 var err error
358 switch mode {
359 case modeSync:
360 cfg.mode = node.ModeSynchronizer
361 cfg.node, err = config.LoadNode(nodeCfgPath, false)
362 if err != nil {
363 return nil, tracerr.Wrap(err)
364 }
365 case modeCoord:
366 cfg.mode = node.ModeCoordinator
367 cfg.node, err = config.LoadNode(nodeCfgPath, true)
368 if err != nil {
369 return nil, tracerr.Wrap(err)
370 }
371 default:
372 return nil, tracerr.Wrap(fmt.Errorf("invalid mode \"%v\"", mode))
373 }
374
375 return &cfg, nil
376}
377
378// ConfigAPIServer is the configuration of the api server execution
379type ConfigAPIServer struct {
380 mode node.Mode
381 server *config.APIServer
382}
383
384func parseCliAPIServer(c *cli.Context) (*ConfigAPIServer, error) {
385 cfg, err := getConfigAPIServer(c)
386 if err != nil {
387 if err := cli.ShowAppHelp(c); err != nil {
388 panic(err)
389 }
390 return nil, tracerr.Wrap(err)
391 }
392 return cfg, nil
393}
394
395func getConfigAPIServer(c *cli.Context) (*ConfigAPIServer, error) {
396 var cfg ConfigAPIServer
397 mode := c.String(flagMode)
398 nodeCfgPath := c.String(flagCfg)
399 var err error
400 switch mode {
401 case modeSync:
402 cfg.mode = node.ModeSynchronizer
403 cfg.server, err = config.LoadAPIServer(nodeCfgPath, false)
404 if err != nil {
405 return nil, tracerr.Wrap(err)
406 }
407 case modeCoord:
408 cfg.mode = node.ModeCoordinator
409 cfg.server, err = config.LoadAPIServer(nodeCfgPath, true)
410 if err != nil {
411 return nil, tracerr.Wrap(err)
412 }
413 default:
414 return nil, tracerr.Wrap(fmt.Errorf("invalid mode \"%v\"", mode))
415 }
416
417 return &cfg, nil
418}
419
420func main() {
421 app := cli.NewApp()
422 app.Name = "hermez-node"
423 app.Version = version
424 flags := []cli.Flag{
425 &cli.StringFlag{
426 Name: flagMode,
427 Usage: fmt.Sprintf("Set node `MODE` (can be \"%v\" or \"%v\")", modeSync, modeCoord),
428 Required: true,
429 },
430 &cli.StringFlag{
431 Name: flagCfg,
432 Usage: "Node configuration `FILE`",
433 Required: true,
434 },
435 }
436
437 app.Commands = []*cli.Command{
438 {
439 Name: "version",
440 Aliases: []string{},
441 Usage: "Show the application version and build",
442 Action: cmdVersion,
443 },
444 {
445 Name: "importkey",
446 Aliases: []string{},
447 Usage: "Import ethereum private key",
448 Action: cmdImportKey,
449 Flags: append(flags,
450 &cli.StringFlag{
451 Name: flagSK,
452 Usage: "ethereum `PRIVATE_KEY` in hex",
453 Required: true,
454 }),
455 },
456 {
457 Name: "genbjj",
458 Aliases: []string{},
459 Usage: "Generate a new BabyJubJub key",
460 Action: cmdGenBJJ,
461 },
462 {
463 Name: "wipesql",
464 Aliases: []string{},
465 Usage: "Wipe the SQL DB (HistoryDB and L2DB) and the StateDBs, " +
466 "leaving the DB in a clean state",
467 Action: cmdWipeSQL,
468 Flags: append(flags,
469 &cli.BoolFlag{
470 Name: flagYes,
471 Usage: "automatic yes to the prompt",
472 Required: false,
473 }),
474 },
475 {
476 Name: "run",
477 Aliases: []string{},
478 Usage: "Run the hermez-node in the indicated mode",
479 Action: cmdRun,
480 Flags: flags,
481 },
482 {
483 Name: "serveapi",
484 Aliases: []string{},
485 Usage: "Serve the API only",
486 Action: cmdServeAPI,
487 },
488 {
489 Name: "discard",
490 Aliases: []string{},
491 Usage: "Discard blocks up to a specified block number",
492 Action: cmdDiscard,
493 Flags: append(flags,
494 &cli.Int64Flag{
495 Name: flagBlock,
496 Usage: "last block number to keep",
497 Required: false,
498 }),
499 },
500 }
501
502 err := app.Run(os.Args)
503 if err != nil {
504 fmt.Printf("\nError: %v\n", tracerr.Sprint(err))
505 os.Exit(1)
506 }
507}