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