hermez-node/db/statedb/statedb.go
db/statedb/statedb.goRaw
1package statedb
2
3import (
4 "errors"
5 "fmt"
6 "os"
7 "os/exec"
8 "strconv"
9
10 "github.com/hermeznetwork/hermez-node/common"
11 "github.com/iden3/go-merkletree"
12 "github.com/iden3/go-merkletree/db"
13 "github.com/iden3/go-merkletree/db/pebble"
14)
15
16// ErrStateDBWithoutMT is used when a method that requires a MerkleTree is
17// called in a StateDB that does not have a MerkleTree defined
18var ErrStateDBWithoutMT = errors.New("Can not call method to use MerkleTree in a StateDB without MerkleTree")
19
20// ErrAccountAlreadyExists is used when CreateAccount is called and the Account
21// already exists
22var ErrAccountAlreadyExists = errors.New("Can not CreateAccount because Account already exists")
23
24// KeyCurrentBatch is used as key in the db to store the current BatchNum
25var KeyCurrentBatch = []byte("currentbatch")
26
27// PathStateDB defines the subpath of the StateDB
28const PathStateDB = "/statedb"
29
30// PathBatchNum defines the subpath of the Batch Checkpoint in the subpath of
31// the StateDB
32const PathBatchNum = "/BatchNum"
33
34// PathCurrent defines the subpath of the current Batch in the subpath of the
35// StateDB
36const PathCurrent = "/current"
37
38// StateDB represents the StateDB object
39type StateDB struct {
40 path string
41 currentBatch common.BatchNum
42 db *pebble.PebbleStorage
43 mt *merkletree.MerkleTree
44 // idx holds the current Idx that the BatchBuilder is using
45 idx common.Idx
46 zki *common.ZKInputs
47 i int // i is the current transaction index in the ZKInputs generation (zki)
48}
49
50// NewStateDB creates a new StateDB, allowing to use an in-memory or in-disk
51// storage
52func NewStateDB(path string, withMT bool, nLevels int) (*StateDB, error) {
53 var sto *pebble.PebbleStorage
54 var err error
55 sto, err = pebble.NewPebbleStorage(path+PathStateDB+PathCurrent, false)
56 if err != nil {
57 return nil, err
58 }
59
60 var mt *merkletree.MerkleTree = nil
61 if withMT {
62 mt, err = merkletree.NewMerkleTree(sto, nLevels)
63 if err != nil {
64 return nil, err
65 }
66 }
67
68 sdb := &StateDB{
69 path: path + PathStateDB,
70 db: sto,
71 mt: mt,
72 }
73
74 // load currentBatch
75 sdb.currentBatch, err = sdb.GetCurrentBatch()
76 if err != nil {
77 return nil, err
78 }
79
80 // make reset (get checkpoint) at currentBatch
81 err = sdb.Reset(sdb.currentBatch)
82 if err != nil {
83 return nil, err
84 }
85
86 return sdb, nil
87}
88
89// DB returns the *pebble.PebbleStorage from the StateDB
90func (s *StateDB) DB() *pebble.PebbleStorage {
91 return s.db
92}
93
94// GetCurrentBatch returns the current BatchNum stored in the StateDB
95func (s *StateDB) GetCurrentBatch() (common.BatchNum, error) {
96 cbBytes, err := s.db.Get(KeyCurrentBatch)
97 if err == db.ErrNotFound {
98 return 0, nil
99 }
100 if err != nil {
101 return 0, err
102 }
103 return common.BatchNumFromBytes(cbBytes)
104}
105
106// setCurrentBatch stores the current BatchNum in the StateDB
107func (s *StateDB) setCurrentBatch() error {
108 tx, err := s.db.NewTx()
109 if err != nil {
110 return err
111 }
112 err = tx.Put(KeyCurrentBatch, s.currentBatch.Bytes())
113 if err != nil {
114 return err
115 }
116 if err := tx.Commit(); err != nil {
117 return err
118 }
119 return nil
120}
121
122// 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.
123func (s *StateDB) MakeCheckpoint() error {
124 // advance currentBatch
125 s.currentBatch++
126
127 checkpointPath := s.path + PathBatchNum + strconv.Itoa(int(s.currentBatch))
128
129 err := s.setCurrentBatch()
130 if err != nil {
131 return err
132 }
133
134 // if checkpoint BatchNum already exist in disk, delete it
135 if _, err := os.Stat(checkpointPath); !os.IsNotExist(err) {
136 err := os.RemoveAll(checkpointPath)
137 if err != nil {
138 return err
139 }
140 }
141
142 // execute Checkpoint
143 err = s.db.Pebble().Checkpoint(checkpointPath)
144 if err != nil {
145 return err
146 }
147
148 return nil
149}
150
151// DeleteCheckpoint removes if exist the checkpoint of the given batchNum
152func (s *StateDB) DeleteCheckpoint(batchNum common.BatchNum) error {
153 checkpointPath := s.path + PathBatchNum + strconv.Itoa(int(batchNum))
154
155 if _, err := os.Stat(checkpointPath); os.IsNotExist(err) {
156 return fmt.Errorf("Checkpoint with batchNum %d does not exist in DB", batchNum)
157 }
158
159 return os.RemoveAll(checkpointPath)
160}
161
162// Reset resets the StateDB to the checkpoint at the given batchNum. Reset
163// does not delete the checkpoints between old current and the new current,
164// those checkpoints will remain in the storage, and eventually will be
165// deleted when MakeCheckpoint overwrites them.
166func (s *StateDB) Reset(batchNum common.BatchNum) error {
167 checkpointPath := s.path + PathBatchNum + strconv.Itoa(int(batchNum))
168 currentPath := s.path + PathCurrent
169
170 // remove 'current'
171 err := os.RemoveAll(currentPath)
172 if err != nil {
173 return err
174 }
175 if batchNum == 0 {
176 // if batchNum == 0, open the new fresh 'current'
177 sto, err := pebble.NewPebbleStorage(currentPath, false)
178 if err != nil {
179 return err
180 }
181 s.db = sto
182 s.idx = 0
183 s.currentBatch = batchNum
184 return nil
185 }
186
187 // copy 'BatchNumX' to 'current'
188 cmd := exec.Command("cp", "-r", checkpointPath, currentPath) //nolint:gosec
189 err = cmd.Run()
190 if err != nil {
191 return err
192 }
193
194 // open the new 'current'
195 sto, err := pebble.NewPebbleStorage(currentPath, false)
196 if err != nil {
197 return err
198 }
199 s.db = sto
200
201 // get currentBatch num
202 s.currentBatch, err = s.GetCurrentBatch()
203 if err != nil {
204 return err
205 }
206 // idx is obtained from the statedb reset
207 s.idx, err = s.getIdx()
208 if err != nil {
209 return err
210 }
211
212 if s.mt != nil {
213 // open the MT for the current s.db
214 mt, err := merkletree.NewMerkleTree(s.db, s.mt.MaxLevels())
215 if err != nil {
216 return err
217 }
218 s.mt = mt
219 }
220
221 return nil
222}
223
224// GetAccount returns the account for the given Idx
225func (s *StateDB) GetAccount(idx common.Idx) (*common.Account, error) {
226 return getAccountInTreeDB(s.db, idx)
227}
228
229// getAccountInTreeDB is abstracted from StateDB to be used from StateDB and
230// from ExitTree. GetAccount returns the account for the given Idx
231func getAccountInTreeDB(sto db.Storage, idx common.Idx) (*common.Account, error) {
232 vBytes, err := sto.Get(idx.Bytes())
233 if err != nil {
234 return nil, err
235 }
236 accBytes, err := sto.Get(vBytes)
237 if err != nil {
238 return nil, err
239 }
240 var b [32 * common.NLeafElems]byte
241 copy(b[:], accBytes)
242 return common.AccountFromBytes(b)
243}
244
245// CreateAccount creates a new Account in the StateDB for the given Idx. If
246// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
247// MerkleTree, returning a CircomProcessorProof.
248func (s *StateDB) CreateAccount(idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
249 return createAccountInTreeDB(s.db, s.mt, idx, account)
250}
251
252// createAccountInTreeDB is abstracted from StateDB to be used from StateDB and
253// from ExitTree. Creates a new Account in the StateDB for the given Idx. If
254// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
255// MerkleTree, returning a CircomProcessorProof.
256func createAccountInTreeDB(sto db.Storage, mt *merkletree.MerkleTree, idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
257 // store at the DB the key: v, and value: leaf.Bytes()
258 v, err := account.HashValue()
259 if err != nil {
260 return nil, err
261 }
262 accountBytes, err := account.Bytes()
263 if err != nil {
264 return nil, err
265 }
266
267 // store the Leaf value
268 tx, err := sto.NewTx()
269 if err != nil {
270 return nil, err
271 }
272
273 _, err = tx.Get(idx.Bytes())
274 if err != db.ErrNotFound {
275 return nil, ErrAccountAlreadyExists
276 }
277
278 err = tx.Put(v.Bytes(), accountBytes[:])
279 if err != nil {
280 return nil, err
281 }
282 err = tx.Put(idx.Bytes(), v.Bytes())
283 if err != nil {
284 return nil, err
285 }
286
287 if err := tx.Commit(); err != nil {
288 return nil, err
289 }
290
291 if mt != nil {
292 return mt.AddAndGetCircomProof(idx.BigInt(), v)
293 }
294
295 return nil, nil
296}
297
298// UpdateAccount updates the Account in the StateDB for the given Idx. If
299// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
300// MerkleTree, returning a CircomProcessorProof.
301func (s *StateDB) UpdateAccount(idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
302 return updateAccountInTreeDB(s.db, s.mt, idx, account)
303}
304
305// updateAccountInTreeDB is abstracted from StateDB to be used from StateDB and
306// from ExitTree. Updates the Account in the StateDB for the given Idx. If
307// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
308// MerkleTree, returning a CircomProcessorProof.
309func updateAccountInTreeDB(sto db.Storage, mt *merkletree.MerkleTree, idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
310 // store at the DB the key: v, and value: account.Bytes()
311 v, err := account.HashValue()
312 if err != nil {
313 return nil, err
314 }
315 accountBytes, err := account.Bytes()
316 if err != nil {
317 return nil, err
318 }
319
320 tx, err := sto.NewTx()
321 if err != nil {
322 return nil, err
323 }
324 err = tx.Put(v.Bytes(), accountBytes[:])
325 if err != nil {
326 return nil, err
327 }
328 err = tx.Put(idx.Bytes(), v.Bytes())
329 if err != nil {
330 return nil, err
331 }
332
333 if err := tx.Commit(); err != nil {
334 return nil, err
335 }
336
337 if mt != nil {
338 return mt.Update(idx.BigInt(), v)
339 }
340 return nil, nil
341}
342
343// MTGetProof returns the CircomVerifierProof for a given Idx
344func (s *StateDB) MTGetProof(idx common.Idx) (*merkletree.CircomVerifierProof, error) {
345 if s.mt == nil {
346 return nil, ErrStateDBWithoutMT
347 }
348 return s.mt.GenerateCircomVerifierProof(idx.BigInt(), s.mt.Root())
349}
350
351// LocalStateDB represents the local StateDB which allows to make copies from
352// the synchronizer StateDB, and is used by the tx-selector and the
353// batch-builder. LocalStateDB is an in-memory storage.
354type LocalStateDB struct {
355 *StateDB
356 synchronizerStateDB *StateDB
357}
358
359// NewLocalStateDB returns a new LocalStateDB connected to the given
360// synchronizerDB
361func NewLocalStateDB(path string, synchronizerDB *StateDB, withMT bool, nLevels int) (*LocalStateDB, error) {
362 s, err := NewStateDB(path, withMT, nLevels)
363 if err != nil {
364 return nil, err
365 }
366 return &LocalStateDB{
367 s,
368 synchronizerDB,
369 }, nil
370}
371
372// Reset performs a reset in the LocaStateDB. If fromSynchronizer is true, it
373// gets the state from LocalStateDB.synchronizerStateDB for the given batchNum. If fromSynchronizer is false, get the state from LocalStateDB checkpoints.
374func (l *LocalStateDB) Reset(batchNum common.BatchNum, fromSynchronizer bool) error {
375 if batchNum == 0 {
376 l.idx = 0
377 return nil
378 }
379
380 synchronizerCheckpointPath := l.synchronizerStateDB.path + PathBatchNum + strconv.Itoa(int(batchNum))
381 checkpointPath := l.path + PathBatchNum + strconv.Itoa(int(batchNum))
382 currentPath := l.path + PathCurrent
383
384 if fromSynchronizer {
385 // use checkpoint from SynchronizerStateDB
386 if _, err := os.Stat(synchronizerCheckpointPath); os.IsNotExist(err) {
387 // if synchronizerStateDB does not have checkpoint at batchNum, return err
388 return fmt.Errorf("Checkpoint not exist in Synchronizer")
389 }
390
391 // remove 'current'
392 err := os.RemoveAll(currentPath)
393 if err != nil {
394 return err
395 }
396 // copy synchronizer'BatchNumX' to 'current'
397 cmd := exec.Command("cp", "-r", synchronizerCheckpointPath, currentPath) //nolint:gosec
398 err = cmd.Run()
399 if err != nil {
400 return err
401 }
402 // copy synchronizer-'BatchNumX' to 'BatchNumX'
403 cmd = exec.Command("cp", "-r", synchronizerCheckpointPath, checkpointPath) //nolint:gosec
404 err = cmd.Run()
405 if err != nil {
406 return err
407 }
408
409 // open the new 'current'
410 sto, err := pebble.NewPebbleStorage(currentPath, false)
411 if err != nil {
412 return err
413 }
414 l.db = sto
415
416 // get currentBatch num
417 l.currentBatch, err = l.GetCurrentBatch()
418 if err != nil {
419 return err
420 }
421 // open the MT for the current s.db
422 mt, err := merkletree.NewMerkleTree(l.db, l.mt.MaxLevels())
423 if err != nil {
424 return err
425 }
426 l.mt = mt
427
428 return nil
429 }
430 // use checkpoint from LocalStateDB
431 return l.StateDB.Reset(batchNum)
432}