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