arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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}