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 return err
183}
184
185// GetAccount returns the account for the given Idx
186func (s *StateDB) GetAccount(idx common.Idx) (*common.Account, error) {
187 vBytes, err := s.db.Get(idx.Bytes())
188 if err != nil {
189 return nil, err
190 }
191 accBytes, err := s.db.Get(vBytes)
192 if err != nil {
193 return nil, err
194 }
195 var b [32 * common.NLEAFELEMS]byte
196 copy(b[:], accBytes)
197 return common.AccountFromBytes(b)
198}
199
200// CreateAccount creates a new Account in the StateDB for the given Idx. If
201// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
202// MerkleTree, returning a CircomProcessorProof.
203func (s *StateDB) CreateAccount(idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
204 // store at the DB the key: v, and value: leaf.Bytes()
205 v, err := account.HashValue()
206 if err != nil {
207 return nil, err
208 }
209 accountBytes, err := account.Bytes()
210 if err != nil {
211 return nil, err
212 }
213
214 // store the Leaf value
215 tx, err := s.db.NewTx()
216 if err != nil {
217 return nil, err
218 }
219
220 _, err = tx.Get(idx.Bytes())
221 if err != db.ErrNotFound {
222 return nil, ErrAccountAlreadyExists
223 }
224
225 tx.Put(v.Bytes(), accountBytes[:])
226 tx.Put(idx.Bytes(), v.Bytes())
227
228 if err := tx.Commit(); err != nil {
229 return nil, err
230 }
231
232 if s.mt != nil {
233 return s.mt.AddAndGetCircomProof(idx.BigInt(), v)
234 }
235 return nil, nil
236}
237
238// UpdateAccount updates the Account in the StateDB for the given Idx. If
239// StateDB.mt==nil, MerkleTree is not affected, otherwise updates the
240// MerkleTree, returning a CircomProcessorProof.
241func (s *StateDB) UpdateAccount(idx common.Idx, account *common.Account) (*merkletree.CircomProcessorProof, error) {
242 // store at the DB the key: v, and value: leaf.Bytes()
243 v, err := account.HashValue()
244 if err != nil {
245 return nil, err
246 }
247 accountBytes, err := account.Bytes()
248 if err != nil {
249 return nil, err
250 }
251
252 tx, err := s.db.NewTx()
253 if err != nil {
254 return nil, err
255 }
256 tx.Put(v.Bytes(), accountBytes[:])
257 tx.Put(idx.Bytes(), v.Bytes())
258
259 if err := tx.Commit(); err != nil {
260 return nil, err
261 }
262
263 if s.mt != nil {
264 return s.mt.Update(idx.BigInt(), v)
265 }
266 return nil, nil
267}
268
269// MTGetProof returns the CircomVerifierProof for a given Idx
270func (s *StateDB) MTGetProof(idx common.Idx) (*merkletree.CircomVerifierProof, error) {
271 if s.mt == nil {
272 return nil, ErrStateDBWithoutMT
273 }
274 return s.mt.GenerateCircomVerifierProof(idx.BigInt(), s.mt.Root())
275}
276
277// LocalStateDB represents the local StateDB which allows to make copies from
278// the synchronizer StateDB, and is used by the tx-selector and the
279// batch-builder. LocalStateDB is an in-memory storage.
280type LocalStateDB struct {
281 *StateDB
282 synchronizerStateDB *StateDB
283}
284
285// NewLocalStateDB returns a new LocalStateDB connected to the given
286// synchronizerDB
287func NewLocalStateDB(path string, synchronizerDB *StateDB, withMT bool, nLevels int) (*LocalStateDB, error) {
288 s, err := NewStateDB(path, withMT, nLevels)
289 if err != nil {
290 return nil, err
291 }
292 return &LocalStateDB{
293 s,
294 synchronizerDB,
295 }, nil
296}
297
298// Reset performs a reset in the LocaStateDB. If fromSynchronizer is true, it
299// gets the state from LocalStateDB.synchronizerStateDB for the given batchNum. If fromSynchronizer is false, get the state from LocalStateDB checkpoints.
300func (l *LocalStateDB) Reset(batchNum uint64, fromSynchronizer bool) error {
301 if batchNum == 0 {
302 l.idx = 0
303 return nil
304 }
305
306 synchronizerCheckpointPath := l.synchronizerStateDB.path + PATHBATCHNUM + strconv.Itoa(int(batchNum))
307 checkpointPath := l.path + PATHBATCHNUM + strconv.Itoa(int(batchNum))
308 currentPath := l.path + PATHCURRENT
309
310 if fromSynchronizer {
311 // use checkpoint from SynchronizerStateDB
312 if _, err := os.Stat(synchronizerCheckpointPath); os.IsNotExist(err) {
313 // if synchronizerStateDB does not have checkpoint at batchNum, return err
314 return fmt.Errorf("Checkpoint not exist in Synchronizer")
315 }
316
317 // remove 'current'
318 err := os.RemoveAll(currentPath)
319 if err != nil {
320 return err
321 }
322 // copy synchronizer'BatchNumX' to 'current'
323 cmd := exec.Command("cp", "-r", synchronizerCheckpointPath, currentPath)
324 err = cmd.Run()
325 if err != nil {
326 return err
327 }
328 // copy synchronizer-'BatchNumX' to 'BatchNumX'
329 cmd = exec.Command("cp", "-r", synchronizerCheckpointPath, checkpointPath)
330 err = cmd.Run()
331 if err != nil {
332 return err
333 }
334
335 // open the new 'current'
336 sto, err := pebble.NewPebbleStorage(currentPath, false)
337 if err != nil {
338 return err
339 }
340 l.db = sto
341
342 // get currentBatch num
343 l.currentBatch, err = l.GetCurrentBatch()
344 if err != nil {
345 return err
346 }
347 return nil
348 }
349 // use checkpoint from LocalStateDB
350 return l.StateDB.Reset(batchNum)
351}