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 // Build represents the program based on the git commit
41 Build = "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", Build)
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 cmdRun(c *cli.Context) error {
216 cfg, err := parseCli(c)
217 if err != nil {
218 return tracerr.Wrap(fmt.Errorf("error parsing flags and config: %w", err))
219 }
220 node, err := node.NewNode(cfg.mode, cfg.node)
221 if err != nil {
222 return tracerr.Wrap(fmt.Errorf("error starting node: %w", err))
223 }
224 node.Start()
225
226 stopCh := make(chan interface{})
227
228 // catch ^C to send the stop signal
229 ossig := make(chan os.Signal, 1)
230 signal.Notify(ossig, os.Interrupt)
231 const forceStopCount = 3
232 go func() {
233 n := 0
234 for sig := range ossig {
235 if sig == os.Interrupt {
236 log.Info("Received Interrupt Signal")
237 stopCh <- nil
238 n++
239 if n == forceStopCount {
240 log.Fatalf("Received %v Interrupt Signals", forceStopCount)
241 }
242 }
243 }
244 }()
245 <-stopCh
246 node.Stop()
247
248 return nil
249}
250
251func cmdDiscard(c *cli.Context) error {
252 _cfg, err := parseCli(c)
253 if err != nil {
254 return tracerr.Wrap(fmt.Errorf("error parsing flags and config: %w", err))
255 }
256 cfg := _cfg.node
257 blockNum := c.Int64(flagBlock)
258 log.Infof("Discarding all blocks up to block %v...", blockNum)
259
260 dbWrite, err := dbUtils.InitSQLDB(
261 cfg.PostgreSQL.PortWrite,
262 cfg.PostgreSQL.HostWrite,
263 cfg.PostgreSQL.UserWrite,
264 cfg.PostgreSQL.PasswordWrite,
265 cfg.PostgreSQL.NameWrite,
266 )
267 if err != nil {
268 return tracerr.Wrap(fmt.Errorf("dbUtils.InitSQLDB: %w", err))
269 }
270 var dbRead *sqlx.DB
271 if cfg.PostgreSQL.HostRead == "" {
272 dbRead = dbWrite
273 } else if cfg.PostgreSQL.HostRead == cfg.PostgreSQL.HostWrite {
274 return tracerr.Wrap(fmt.Errorf(
275 "PostgreSQL.HostRead and PostgreSQL.HostWrite must be different",
276 ))
277 } else {
278 dbRead, err = dbUtils.InitSQLDB(
279 cfg.PostgreSQL.PortRead,
280 cfg.PostgreSQL.HostRead,
281 cfg.PostgreSQL.UserRead,
282 cfg.PostgreSQL.PasswordRead,
283 cfg.PostgreSQL.NameRead,
284 )
285 if err != nil {
286 return tracerr.Wrap(fmt.Errorf("dbUtils.InitSQLDB: %w", err))
287 }
288 }
289 historyDB := historydb.NewHistoryDB(dbRead, dbWrite, nil)
290 if err := historyDB.Reorg(blockNum); err != nil {
291 return tracerr.Wrap(fmt.Errorf("historyDB.Reorg: %w", err))
292 }
293 batchNum, err := historyDB.GetLastBatchNum()
294 if err != nil {
295 return tracerr.Wrap(fmt.Errorf("historyDB.GetLastBatchNum: %w", err))
296 }
297 l2DB := l2db.NewL2DB(
298 dbRead, dbWrite,
299 cfg.Coordinator.L2DB.SafetyPeriod,
300 cfg.Coordinator.L2DB.MaxTxs,
301 cfg.Coordinator.L2DB.MinFeeUSD,
302 cfg.Coordinator.L2DB.TTL.Duration,
303 nil,
304 )
305 if err := l2DB.Reorg(batchNum); err != nil {
306 return tracerr.Wrap(fmt.Errorf("l2DB.Reorg: %w", err))
307 }
308
309 log.Info("Resetting StateDBs...")
310 if err := resetStateDBs(_cfg, batchNum); err != nil {
311 return tracerr.Wrap(fmt.Errorf("resetStateDBs: %w", err))
312 }
313
314 return nil
315}
316
317// Config is the configuration of the hermez node execution
318type Config struct {
319 mode node.Mode
320 node *config.Node
321}
322
323func parseCli(c *cli.Context) (*Config, error) {
324 cfg, err := getConfig(c)
325 if err != nil {
326 if err := cli.ShowAppHelp(c); err != nil {
327 panic(err)
328 }
329 return nil, tracerr.Wrap(err)
330 }
331 return cfg, nil
332}
333
334func getConfig(c *cli.Context) (*Config, error) {
335 var cfg Config
336 mode := c.String(flagMode)
337 nodeCfgPath := c.String(flagCfg)
338 if nodeCfgPath == "" {
339 return nil, tracerr.Wrap(fmt.Errorf("required flag \"%v\" not set", flagCfg))
340 }
341 var err error
342 switch mode {
343 case modeSync:
344 cfg.mode = node.ModeSynchronizer
345 cfg.node, err = config.LoadNode(nodeCfgPath)
346 if err != nil {
347 return nil, tracerr.Wrap(err)
348 }
349 case modeCoord:
350 cfg.mode = node.ModeCoordinator
351 cfg.node, err = config.LoadCoordinator(nodeCfgPath)
352 if err != nil {
353 return nil, tracerr.Wrap(err)
354 }
355 default:
356 return nil, tracerr.Wrap(fmt.Errorf("invalid mode \"%v\"", mode))
357 }
358
359 return &cfg, nil
360}
361
362func main() {
363 app := cli.NewApp()
364 app.Name = "hermez-node"
365 app.Version = Version
366 flags := []cli.Flag{
367 &cli.StringFlag{
368 Name: flagMode,
369 Usage: fmt.Sprintf("Set node `MODE` (can be \"%v\" or \"%v\")", modeSync, modeCoord),
370 Required: true,
371 },
372 &cli.StringFlag{
373 Name: flagCfg,
374 Usage: "Node configuration `FILE`",
375 Required: true,
376 },
377 }
378
379 app.Commands = []*cli.Command{
380 {
381 Name: "version",
382 Aliases: []string{},
383 Usage: "Show the application version and build",
384 Action: cmdVersion,
385 },
386 {
387 Name: "importkey",
388 Aliases: []string{},
389 Usage: "Import ethereum private key",
390 Action: cmdImportKey,
391 Flags: append(flags,
392 &cli.StringFlag{
393 Name: flagSK,
394 Usage: "ethereum `PRIVATE_KEY` in hex",
395 Required: true,
396 }),
397 },
398 {
399 Name: "genbjj",
400 Aliases: []string{},
401 Usage: "Generate a new BabyJubJub key",
402 Action: cmdGenBJJ,
403 },
404 {
405 Name: "wipesql",
406 Aliases: []string{},
407 Usage: "Wipe the SQL DB (HistoryDB and L2DB) and the StateDBs, " +
408 "leaving the DB in a clean state",
409 Action: cmdWipeSQL,
410 Flags: append(flags,
411 &cli.BoolFlag{
412 Name: flagYes,
413 Usage: "automatic yes to the prompt",
414 Required: false,
415 }),
416 },
417 {
418 Name: "run",
419 Aliases: []string{},
420 Usage: "Run the hermez-node in the indicated mode",
421 Action: cmdRun,
422 Flags: flags,
423 },
424 {
425 Name: "discard",
426 Aliases: []string{},
427 Usage: "Discard blocks up to a specified block number",
428 Action: cmdDiscard,
429 Flags: append(flags,
430 &cli.Int64Flag{
431 Name: flagBlock,
432 Usage: "last block number to keep",
433 Required: false,
434 }),
435 },
436 }
437
438 err := app.Run(os.Args)
439 if err != nil {
440 fmt.Printf("\nError: %v\n", tracerr.Sprint(err))
441 os.Exit(1)
442 }
443}