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.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}