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