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}