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