@ -3,9 +3,12 @@ package statedb
import (
import (
"errors"
"errors"
"fmt"
"fmt"
"io/ioutil"
"math/big"
"math/big"
"os"
"os"
"strconv"
"path"
"sort"
"strings"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/log"
"github.com/hermeznetwork/hermez-node/log"
@ -51,10 +54,10 @@ var (
const (
const (
// PathBatchNum defines the subpath of the Batch Checkpoint in the
// PathBatchNum defines the subpath of the Batch Checkpoint in the
// subpath of the StateDB
// subpath of the StateDB
PathBatchNum = "/ BatchNum"
PathBatchNum = "BatchNum"
// PathCurrent defines the subpath of the current Batch in the subpath
// PathCurrent defines the subpath of the current Batch in the subpath
// of the StateDB
// of the StateDB
PathCurrent = "/ current"
PathCurrent = "current"
// TypeSynchronizer defines a StateDB used by the Synchronizer, that
// TypeSynchronizer defines a StateDB used by the Synchronizer, that
// generates the ExitTree when processing the txs
// generates the ExitTree when processing the txs
TypeSynchronizer = "synchronizer"
TypeSynchronizer = "synchronizer"
@ -85,14 +88,16 @@ type StateDB struct {
// AccumulatedFees contains the accumulated fees for each token (Coord
// AccumulatedFees contains the accumulated fees for each token (Coord
// Idx) in the processed batch
// Idx) in the processed batch
AccumulatedFees map [ common . Idx ] * big . Int
AccumulatedFees map [ common . Idx ] * big . Int
keep int
}
}
// NewStateDB creates a new StateDB, allowing to use an in-memory or in-disk
// NewStateDB creates a new StateDB, allowing to use an in-memory or in-disk
// storage
func NewStateDB ( path string , typ TypeStateDB , nLevels int , chainID uint16 ) ( * StateDB , error ) {
// storage. Checkpoints older than the value defined by `keep` will be
// deleted.
func NewStateDB ( pathDB string , keep int , typ TypeStateDB , nLevels int , chainID uint16 ) ( * StateDB , error ) {
var sto * pebble . PebbleStorage
var sto * pebble . PebbleStorage
var err error
var err error
sto , err = pebble . NewPebbleStorage ( path + PathCurrent , false )
sto , err = pebble . NewPebbleStorage ( path . Join ( pathDB , PathCurrent ) , false )
if err != nil {
if err != nil {
return nil , tracerr . Wrap ( err )
return nil , tracerr . Wrap ( err )
}
}
@ -109,11 +114,12 @@ func NewStateDB(path string, typ TypeStateDB, nLevels int, chainID uint16) (*Sta
}
}
sdb := & StateDB {
sdb := & StateDB {
path : path ,
path : pathDB ,
db : sto ,
db : sto ,
mt : mt ,
mt : mt ,
typ : typ ,
typ : typ ,
chainID : chainID ,
chainID : chainID ,
keep : keep ,
}
}
// load currentBatch
// load currentBatch
@ -170,10 +176,9 @@ func (s *StateDB) MakeCheckpoint() error {
s . currentBatch ++
s . currentBatch ++
log . Debugw ( "Making StateDB checkpoint" , "batch" , s . currentBatch , "type" , s . typ )
log . Debugw ( "Making StateDB checkpoint" , "batch" , s . currentBatch , "type" , s . typ )
checkpointPath := s . path + PathBatchNum + strconv . Itoa ( int ( s . currentBatch ) )
checkpointPath := path . Join ( s . path , fmt . Sprintf ( "%s%d" , PathBatchNum , s . currentBatch ) )
err := s . setCurrentBatch ( )
if err != nil {
if err := s . setCurrentBatch ( ) ; err != nil {
return tracerr . Wrap ( err )
return tracerr . Wrap ( err )
}
}
@ -188,8 +193,11 @@ func (s *StateDB) MakeCheckpoint() error {
}
}
// execute Checkpoint
// execute Checkpoint
err = s . db . Pebble ( ) . Checkpoint ( checkpointPath )
if err != nil {
if err := s . db . Pebble ( ) . Checkpoint ( checkpointPath ) ; err != nil {
return tracerr . Wrap ( err )
}
// delete old checkpoints
if err := s . deleteOldCheckpoints ( ) ; err != nil {
return tracerr . Wrap ( err )
return tracerr . Wrap ( err )
}
}
@ -198,7 +206,7 @@ func (s *StateDB) MakeCheckpoint() error {
// DeleteCheckpoint removes if exist the checkpoint of the given batchNum
// DeleteCheckpoint removes if exist the checkpoint of the given batchNum
func ( s * StateDB ) DeleteCheckpoint ( batchNum common . BatchNum ) error {
func ( s * StateDB ) DeleteCheckpoint ( batchNum common . BatchNum ) error {
checkpointPath := s . path + PathBatchNum + strconv . Itoa ( int ( batchNum ) )
checkpointPath := path . Join ( s . path , fmt . Sprintf ( "%s%d" , PathBatchNum , batchNum ) )
if _ , err := os . Stat ( checkpointPath ) ; os . IsNotExist ( err ) {
if _ , err := os . Stat ( checkpointPath ) ; os . IsNotExist ( err ) {
return tracerr . Wrap ( fmt . Errorf ( "Checkpoint with batchNum %d does not exist in DB" , batchNum ) )
return tracerr . Wrap ( fmt . Errorf ( "Checkpoint with batchNum %d does not exist in DB" , batchNum ) )
@ -207,6 +215,55 @@ func (s *StateDB) DeleteCheckpoint(batchNum common.BatchNum) error {
return os . RemoveAll ( checkpointPath )
return os . RemoveAll ( checkpointPath )
}
}
// listCheckpoints returns the list of batchNums of the checkpoints, sorted.
// If there's a gap between the list of checkpoints, an error is returned.
func ( s * StateDB ) listCheckpoints ( ) ( [ ] int , error ) {
files , err := ioutil . ReadDir ( s . path )
if err != nil {
return nil , err
}
checkpoints := [ ] int { }
var checkpoint int
pattern := fmt . Sprintf ( "%s%%d" , PathBatchNum )
for _ , file := range files {
fileName := file . Name ( )
if file . IsDir ( ) && strings . HasPrefix ( fileName , PathBatchNum ) {
if _ , err := fmt . Sscanf ( fileName , pattern , & checkpoint ) ; err != nil {
return nil , err
}
checkpoints = append ( checkpoints , checkpoint )
}
}
sort . Ints ( checkpoints )
if len ( checkpoints ) > 0 {
first := checkpoints [ 0 ]
for _ , checkpoint := range checkpoints [ 1 : ] {
first ++
if checkpoint != first {
return nil , fmt . Errorf ( "checkpoint gap at %v" , checkpoint )
}
}
}
return checkpoints , nil
}
// deleteOldCheckpoints deletes old checkpoints when there are more than
// `s.keep` checkpoints
func ( s * StateDB ) deleteOldCheckpoints ( ) error {
list , err := s . listCheckpoints ( )
if err != nil {
return err
}
if len ( list ) > s . keep {
for _ , checkpoint := range list [ : len ( list ) - s . keep ] {
if err := s . DeleteCheckpoint ( common . BatchNum ( checkpoint ) ) ; err != nil {
return err
}
}
}
return nil
}
func pebbleMakeCheckpoint ( source , dest string ) error {
func pebbleMakeCheckpoint ( source , dest string ) error {
// Remove dest folder (if it exists) before doing the checkpoint
// Remove dest folder (if it exists) before doing the checkpoint
if _ , err := os . Stat ( dest ) ; ! os . IsNotExist ( err ) {
if _ , err := os . Stat ( dest ) ; ! os . IsNotExist ( err ) {
@ -252,7 +309,7 @@ func (s *StateDB) Reset(batchNum common.BatchNum) error {
// deleted when MakeCheckpoint overwrites them. `closeCurrent` will close the
// deleted when MakeCheckpoint overwrites them. `closeCurrent` will close the
// currently opened db before doing the reset.
// currently opened db before doing the reset.
func ( s * StateDB ) reset ( batchNum common . BatchNum , closeCurrent bool ) error {
func ( s * StateDB ) reset ( batchNum common . BatchNum , closeCurrent bool ) error {
currentPath := s . path + PathCurrent
currentPath := path . Join ( s . path , PathCurrent )
if closeCurrent {
if closeCurrent {
if err := s . db . Pebble ( ) . Close ( ) ; err != nil {
if err := s . db . Pebble ( ) . Close ( ) ; err != nil {
@ -264,6 +321,12 @@ func (s *StateDB) reset(batchNum common.BatchNum, closeCurrent bool) error {
if err != nil {
if err != nil {
return tracerr . Wrap ( err )
return tracerr . Wrap ( err )
}
}
// remove all checkpoints > batchNum
for i := batchNum + 1 ; i <= s . currentBatch ; i ++ {
if err := s . DeleteCheckpoint ( i ) ; err != nil {
return err
}
}
if batchNum == 0 {
if batchNum == 0 {
// if batchNum == 0, open the new fresh 'current'
// if batchNum == 0, open the new fresh 'current'
sto , err := pebble . NewPebbleStorage ( currentPath , false )
sto , err := pebble . NewPebbleStorage ( currentPath , false )
@ -285,7 +348,7 @@ func (s *StateDB) reset(batchNum common.BatchNum, closeCurrent bool) error {
return nil
return nil
}
}
checkpointPath := s . path + PathBatchNum + strconv . Itoa ( int ( batchNum ) )
checkpointPath := path . Join ( s . path , fmt . Sprintf ( "%s%d" , PathBatchNum , batchNum ) )
// copy 'BatchNumX' to 'current'
// copy 'BatchNumX' to 'current'
err = pebbleMakeCheckpoint ( checkpointPath , currentPath )
err = pebbleMakeCheckpoint ( checkpointPath , currentPath )
if err != nil {
if err != nil {
@ -521,9 +584,11 @@ type LocalStateDB struct {
}
}
// NewLocalStateDB returns a new LocalStateDB connected to the given
// NewLocalStateDB returns a new LocalStateDB connected to the given
// synchronizerDB
func NewLocalStateDB ( path string , synchronizerDB * StateDB , typ TypeStateDB , nLevels int ) ( * LocalStateDB , error ) {
s , err := NewStateDB ( path , typ , nLevels , synchronizerDB . chainID )
// synchronizerDB. Checkpoints older than the value defined by `keep` will be
// deleted.
func NewLocalStateDB ( path string , keep int , synchronizerDB * StateDB , typ TypeStateDB ,
nLevels int ) ( * LocalStateDB , error ) {
s , err := NewStateDB ( path , keep , typ , nLevels , synchronizerDB . chainID )
if err != nil {
if err != nil {
return nil , tracerr . Wrap ( err )
return nil , tracerr . Wrap ( err )
}
}
@ -541,9 +606,10 @@ func (l *LocalStateDB) Reset(batchNum common.BatchNum, fromSynchronizer bool) er
return nil
return nil
}
}
synchronizerCheckpointPath := l . synchronizerStateDB . path + PathBatchNum + strconv . Itoa ( int ( batchNum ) )
checkpointPath := l . path + PathBatchNum + strconv . Itoa ( int ( batchNum ) )
currentPath := l . path + PathCurrent
synchronizerCheckpointPath := path . Join ( l . synchronizerStateDB . path ,
fmt . Sprintf ( "%s%d" , PathBatchNum , batchNum ) )
checkpointPath := path . Join ( l . path , fmt . Sprintf ( "%s%d" , PathBatchNum , batchNum ) )
currentPath := path . Join ( l . path , PathCurrent )
if fromSynchronizer {
if fromSynchronizer {
// use checkpoint from SynchronizerStateDB
// use checkpoint from SynchronizerStateDB