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