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