hermez-node/db/statedb/statedb.go
db/statedb/statedb.goRaw
1package statedb
2
3import (
4 "errors"
5 "fmt"
6 "math/big"
7 "os"
8 "os/exec"
9 "strconv"
10
11 "github.com/hermeznetwork/hermez-node/common"
12 "github.com/iden3/go-merkletree"
13 "github.com/iden3/go-merkletree/db"
14 "github.com/iden3/go-merkletree/db/pebble"
15)
16
17// TODO(Edu): Document here how StateDB is kept consistent
18
19var (
20 // ErrStateDBWithoutMT is used when a method that requires a MerkleTree
21 // is called in a StateDB that does not have a MerkleTree defined
22 ErrStateDBWithoutMT = errors.New("Can not call method to use MerkleTree in a StateDB without MerkleTree")
23
24 // ErrAccountAlreadyExists is used when CreateAccount is called and the
25 // Account already exists
26 ErrAccountAlreadyExists = errors.New("Can not CreateAccount because Account already exists")
27
28 // ErrToIdxNotFound is used when trying to get the ToIdx from ToEthAddr
29 // or ToEthAddr&ToBJJ
30 ErrToIdxNotFound = errors.New("ToIdx can not be found")
31
32 // KeyCurrentBatch is used as key in the db to store the current BatchNum
33 KeyCurrentBatch = []byte("k:currentbatch")
34
35 // PrefixKeyIdx is the key prefix for idx in the db
36 PrefixKeyIdx = []byte("i:")
37 // PrefixKeyAccHash is the key prefix for account hash in the db
38 PrefixKeyAccHash = []byte("h:")
39 // PrefixKeyMT is the key prefix for merkle tree in the db
40 PrefixKeyMT = []byte("m:")
41 // PrefixKeyAddr is the key prefix for address in the db
42 PrefixKeyAddr = []byte("a:")
43 // PrefixKeyAddrBJJ is the key prefix for address-babyjubjub in the db
44 PrefixKeyAddrBJJ = []byte("ab:")
45)
46
47const (
48 // PathStateDB defines the subpath of the StateDB
49 PathStateDB = "/statedb"
50 // PathBatchNum defines the subpath of the Batch Checkpoint in the
51 // subpath of the StateDB
52 PathBatchNum = "/BatchNum"
53 // PathCurrent defines the subpath of the current Batch in the subpath
54 // of the StateDB
55 PathCurrent = "/current"
56 // TypeSynchronizer defines a StateDB used by the Synchronizer, that
57 // generates the ExitTree when processing the txs
58 TypeSynchronizer = "synchronizer"
59 // TypeTxSelector defines a StateDB used by the TxSelector, without
60 // computing ExitTree neither the ZKInputs
61 TypeTxSelector = "txselector"
62 // TypeBatchBuilder defines a StateDB used by the BatchBuilder, that
63 // generates the ExitTree and the ZKInput when processing the txs
64 TypeBatchBuilder = "batchbuilder"
65)
66
67// TypeStateDB determines the type of StateDB
68type TypeStateDB string
69
70// StateDB represents the StateDB object
71type StateDB struct {
72 path string
73 currentBatch common.BatchNum
74 db *pebble.PebbleStorage
75 mt *merkletree.MerkleTree
76 typ TypeStateDB
77 // idx holds the current Idx that the BatchBuilder is using
78 idx common.Idx
79 zki *common.ZKInputs
80 i int // i is the current transaction index in the ZKInputs generation (zki)
81}
82
83// NewStateDB creates a new StateDB, allowing to use an in-memory or in-disk
84// storage
85func NewStateDB(path string, typ TypeStateDB, nLevels int) (*StateDB, error) {
86 var sto *pebble.PebbleStorage
87 var err error
88 sto, err = pebble.NewPebbleStorage(path+PathStateDB+PathCurrent, false)
89 if err != nil {
90 return nil, err
91 }
92
93 var mt *merkletree.MerkleTree = nil
94 if typ == TypeSynchronizer || typ == TypeBatchBuilder {
95 mt, err = merkletree.NewMerkleTree(sto.WithPrefix(PrefixKeyMT), nLevels)
96 if err != nil {
97 return nil, err
98 }
99 }
100 if typ == TypeTxSelector && nLevels != 0 {
101 return nil, fmt.Errorf("invalid StateDB parameters: StateDB type==TypeStateDB can not have nLevels!=0")
102 }
103
104 sdb := &StateDB{
105 path: path + PathStateDB,
106 db: sto,
107 mt: mt,
108 typ: typ,
109 }
110
111 // load currentBatch
112 sdb.currentBatch, err = sdb.GetCurrentBatch()
113 if err != nil {
114 return nil, err
115 }
116
117 // make reset (get checkpoint) at currentBatch
118 err = sdb.Reset(sdb.currentBatch)
119 if err != nil {
120 return nil, err
121 }
122
123 return sdb, nil
124}
125
126// DB returns the *pebble.PebbleStorage from the StateDB
127func (s *StateDB) DB() *pebble.PebbleStorage {
128 return s.db
129}
130
131// GetCurrentBatch returns the current BatchNum stored in the StateDB
132func (s *StateDB) GetCurrentBatch() (common.BatchNum, error) {
133 cbBytes, err := s.db.Get(KeyCurrentBatch)
134 if err == db.ErrNotFound {
135 return 0, nil
136 }
137 if err != nil {
138 return 0, err
139 }
140 return common.BatchNumFromBytes(cbBytes)
141}
142
143// setCurrentBatch stores the current BatchNum in the StateDB
144func (s *StateDB) setCurrentBatch() error {
145 tx, err := s.db.NewTx()
146 if err != nil {
147 return err
148 }
149 err = tx.Put(KeyCurrentBatch, s.currentBatch.Bytes())
150 if err != nil {
151 return err
152 }
153 if err := tx.Commit(); err != nil {
154 return err
155 }
156 return nil
157}
158
159// MakeCheckpoint does a checkpoint at the given batchNum in the defined path. Internally this advances & stores the current BatchNum, and then stores a Checkpoint of the current state of the StateDB.
160func (s *StateDB) MakeCheckpoint() error {
161 // advance currentBatch
162 s.currentBatch++
163
164 checkpointPath := s.path + PathBatchNum + strconv.Itoa(int(s.currentBatch))
165
166 err := s.setCurrentBatch()
167 if err != nil {
168 return err
169 }
170
171 // if checkpoint BatchNum already exist in disk, delete it
172 if _, err := os.Stat(checkpointPath); !os.IsNotExist(err) {
173 err := os.RemoveAll(checkpointPath)
174 if err != nil {
175 return err
176 }
177 }
178
179 // execute Checkpoint
180 err = s.db.Pebble().Checkpoint(checkpointPath)
181 if err != nil {
182 return err
183 }
184
185 return nil
186}
187
188// DeleteCheckpoint removes if exist the checkpoint of the given batchNum
189func (s *StateDB) DeleteCheckpoint(batchNum common.BatchNum) error {
190 checkpointPath := s.path + PathBatchNum + strconv.Itoa(int(batchNum))
191
192 if _, err := os.Stat(checkpointPath); os.IsNotExist(err) {
193 return fmt.Errorf("Checkpoint with batchNum %d does not exist in DB", batchNum)
194 }
195
196 return os.RemoveAll(checkpointPath)
197}
198
199// Reset resets the StateDB to the checkpoint at the given batchNum. Reset
200// does not delete the checkpoints between old current and the new current,
201// those checkpoints will remain in the storage, and eventually will be
202// deleted when MakeCheckpoint overwrites them.
203func (s *StateDB) Reset(batchNum common.BatchNum) error {
204 checkpointPath := s.path + PathBatchNum + strconv.Itoa(int(batchNum))
205 currentPath := s.path + PathCurrent
206
207 // remove 'current'
208 err := os.RemoveAll(currentPath)
209 if err != nil {
210 return err
211 }
212 if batchNum == 0 {
213 // if batchNum == 0, open the new fresh 'current'
214 sto, err := pebble.NewPebbleStorage(currentPath, false)
215 if err != nil {
216 return err
217 }
218 s.db = sto
219 s.idx = 255
220 s.currentBatch = batchNum
221 return nil
222 }
223
224 // copy 'BatchNumX' to 'current'
225 cmd := exec.Command("cp", "-r", checkpointPath, currentPath) //nolint:gosec
226 err = cmd.Run()
227 if err != nil {
228 return err
229 }
230
231 // open the new 'current'
232 sto, err := pebble.NewPebbleStorage(currentPath, false)
233 if err != nil {
234 return err
235 }
236 s.db = sto
237
238 // get currentBatch num
239 s.currentBatch, err = s.GetCurrentBatch()
240 if err != nil {
241 return err
242 }
243 // idx is obtained from the statedb reset
244 s.idx, err = s.getIdx()
245 if err != nil {
246 return err
247 }
248
249 if s.mt != nil {
250 // open the MT for the current s.db
251 mt, err := merkletree.NewMerkleTree(s.db.WithPrefix(PrefixKeyMT), s.mt.MaxLevels())
252 if err != nil {
253 return err
254 }
255 s.mt = mt
256 }
257
258 return nil
259}
260
261// GetAccount returns the account for the given Idx
262func (s *StateDB) GetAccount(idx common.Idx) (*common.Account, error) {
263 return getAccountInTreeDB(s.db, idx)
264}
265
266// GetAccounts returns all the accounts in the db. Use for debugging pruposes
267// only.
268func (s *StateDB) GetAccounts() ([]common.Account, error) {
269 idxDB := s.db.WithPrefix(PrefixKeyIdx)
270 idxs := []common.Idx{}
271 // NOTE: Current implementation of Iterate in the pebble interface is
272 // not efficient, as it iterates over all keys. Improve it following
273 // this example: https://github.com/cockroachdb/pebble/pull/923/files
274 if err := idxDB.Iterate(func(k []byte, v []byte) (bool, error) {
275 idx, err := common.IdxFromBytes(k)
276 if err != nil {
277 return false, err
278 }
279 idxs = append(idxs, idx)
280 return true, nil
281 }); err != nil {
282 return nil, err
283 }
284 accs := []common.Account{}
285 for i := range idxs {
286 acc, err := s.GetAccount(idxs[i])
287 if err != nil {
288 return nil, err
289 }
290 accs = append(accs, *acc)
291 }
292 return accs, nil
293}
294
295// getAccountInTreeDB is abstracted from StateDB to be used from StateDB and
296// from ExitTree. GetAccount returns the account for the given Idx
297func getAccountInTreeDB(sto db.Storage, idx common.Idx) (*common.Account, error) {
298 idxBytes, err := idx.Bytes()
299 if err != nil {
300 return nil, err
301 }
302 vBytes, err := sto.Get(append(PrefixKeyIdx, idxBytes[:]...))
303 if err != nil {
304 return nil, err
305 }
306 accBytes, err := sto.Get(append(PrefixKeyAccHash, vBytes...))
307 if err != nil {
308 return nil, err
309 }
310 var b [32 * common.NLeafElems]byte
311 copy(b[:], accBytes)
312 account, err := common.AccountFromBytes(b)
313 if err != nil {
314 return nil, err
315 }
316 account.Idx = idx
317 return account, nil
318}
319
320// CreateAccount creates a new Account in the StateDB for the given Idx. If
321// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
322// MerkleTree, returning a CircomProcessorProof.
323func (s *StateDB) CreateAccount(idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
324 cpp, err := createAccountInTreeDB(s.db, s.mt, idx, account)
325 if err != nil {
326 return cpp, err
327 }
328 // store idx by EthAddr & BJJ
329 err = s.setIdxByEthAddrBJJ(idx, account.EthAddr, account.PublicKey)
330 return cpp, err
331}
332
333// createAccountInTreeDB is abstracted from StateDB to be used from StateDB and
334// from ExitTree. Creates a new Account in the StateDB for the given Idx. If
335// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
336// MerkleTree, returning a CircomProcessorProof.
337func createAccountInTreeDB(sto db.Storage, mt *merkletree.MerkleTree, idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
338 // store at the DB the key: v, and value: leaf.Bytes()
339 v, err := account.HashValue()
340 if err != nil {
341 return nil, err
342 }
343 accountBytes, err := account.Bytes()
344 if err != nil {
345 return nil, err
346 }
347
348 // store the Leaf value
349 tx, err := sto.NewTx()
350 if err != nil {
351 return nil, err
352 }
353
354 idxBytes, err := idx.Bytes()
355 if err != nil {
356 return nil, err
357 }
358 _, err = tx.Get(append(PrefixKeyIdx, idxBytes[:]...))
359 if err != db.ErrNotFound {
360 return nil, ErrAccountAlreadyExists
361 }
362
363 err = tx.Put(append(PrefixKeyAccHash, v.Bytes()...), accountBytes[:])
364 if err != nil {
365 return nil, err
366 }
367 err = tx.Put(append(PrefixKeyIdx, idxBytes[:]...), v.Bytes())
368 if err != nil {
369 return nil, err
370 }
371
372 if err := tx.Commit(); err != nil {
373 return nil, err
374 }
375
376 if mt != nil {
377 return mt.AddAndGetCircomProof(idx.BigInt(), v)
378 }
379
380 return nil, nil
381}
382
383// UpdateAccount updates the Account in the StateDB for the given Idx. If
384// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
385// MerkleTree, returning a CircomProcessorProof.
386func (s *StateDB) UpdateAccount(idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
387 return updateAccountInTreeDB(s.db, s.mt, idx, account)
388}
389
390// updateAccountInTreeDB is abstracted from StateDB to be used from StateDB and
391// from ExitTree. Updates the Account in the StateDB for the given Idx. If
392// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
393// MerkleTree, returning a CircomProcessorProof.
394func updateAccountInTreeDB(sto db.Storage, mt *merkletree.MerkleTree, idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
395 // store at the DB the key: v, and value: account.Bytes()
396 v, err := account.HashValue()
397 if err != nil {
398 return nil, err
399 }
400 accountBytes, err := account.Bytes()
401 if err != nil {
402 return nil, err
403 }
404
405 tx, err := sto.NewTx()
406 if err != nil {
407 return nil, err
408 }
409 err = tx.Put(append(PrefixKeyAccHash, v.Bytes()...), accountBytes[:])
410 if err != nil {
411 return nil, err
412 }
413 idxBytes, err := idx.Bytes()
414 if err != nil {
415 return nil, err
416 }
417 err = tx.Put(append(PrefixKeyIdx, idxBytes[:]...), v.Bytes())
418 if err != nil {
419 return nil, err
420 }
421
422 if err := tx.Commit(); err != nil {
423 return nil, err
424 }
425
426 if mt != nil {
427 return mt.Update(idx.BigInt(), v)
428 }
429 return nil, nil
430}
431
432// MTGetProof returns the CircomVerifierProof for a given Idx
433func (s *StateDB) MTGetProof(idx common.Idx) (*merkletree.CircomVerifierProof, error) {
434 if s.mt == nil {
435 return nil, ErrStateDBWithoutMT
436 }
437 return s.mt.GenerateCircomVerifierProof(idx.BigInt(), s.mt.Root())
438}
439
440// MTGetRoot returns the current root of the underlying Merkle Tree
441func (s *StateDB) MTGetRoot() *big.Int {
442 return s.mt.Root().BigInt()
443}
444
445// LocalStateDB represents the local StateDB which allows to make copies from
446// the synchronizer StateDB, and is used by the tx-selector and the
447// batch-builder. LocalStateDB is an in-memory storage.
448type LocalStateDB struct {
449 *StateDB
450 synchronizerStateDB *StateDB
451}
452
453// NewLocalStateDB returns a new LocalStateDB connected to the given
454// synchronizerDB
455func NewLocalStateDB(path string, synchronizerDB *StateDB, typ TypeStateDB, nLevels int) (*LocalStateDB, error) {
456 s, err := NewStateDB(path, typ, nLevels)
457 if err != nil {
458 return nil, err
459 }
460 return &LocalStateDB{
461 s,
462 synchronizerDB,
463 }, nil
464}
465
466// Reset performs a reset in the LocaStateDB. If fromSynchronizer is true, it
467// gets the state from LocalStateDB.synchronizerStateDB for the given batchNum. If fromSynchronizer is false, get the state from LocalStateDB checkpoints.
468func (l *LocalStateDB) Reset(batchNum common.BatchNum, fromSynchronizer bool) error {
469 if batchNum == 0 {
470 l.idx = 0
471 return nil
472 }
473
474 synchronizerCheckpointPath := l.synchronizerStateDB.path + PathBatchNum + strconv.Itoa(int(batchNum))
475 checkpointPath := l.path + PathBatchNum + strconv.Itoa(int(batchNum))
476 currentPath := l.path + PathCurrent
477
478 if fromSynchronizer {
479 // use checkpoint from SynchronizerStateDB
480 if _, err := os.Stat(synchronizerCheckpointPath); os.IsNotExist(err) {
481 // if synchronizerStateDB does not have checkpoint at batchNum, return err
482 return fmt.Errorf("Checkpoint not exist in Synchronizer")
483 }
484
485 // remove 'current'
486 err := os.RemoveAll(currentPath)
487 if err != nil {
488 return err
489 }
490 // copy synchronizer'BatchNumX' to 'current'
491 cmd := exec.Command("cp", "-r", synchronizerCheckpointPath, currentPath) //nolint:gosec
492 err = cmd.Run()
493 if err != nil {
494 return err
495 }
496 // copy synchronizer-'BatchNumX' to 'BatchNumX'
497 cmd = exec.Command("cp", "-r", synchronizerCheckpointPath, checkpointPath) //nolint:gosec
498 err = cmd.Run()
499 if err != nil {
500 return err
501 }
502
503 // open the new 'current'
504 sto, err := pebble.NewPebbleStorage(currentPath, false)
505 if err != nil {
506 return err
507 }
508 l.db = sto
509
510 // get currentBatch num
511 l.currentBatch, err = l.GetCurrentBatch()
512 if err != nil {
513 return err
514 }
515 // open the MT for the current s.db
516 mt, err := merkletree.NewMerkleTree(l.db.WithPrefix(PrefixKeyMT), l.mt.MaxLevels())
517 if err != nil {
518 return err
519 }
520 l.mt = mt
521
522 return nil
523 }
524 // use checkpoint from LocalStateDB
525 return l.StateDB.Reset(batchNum)
526}