You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
11 KiB

  1. // Package kvdb provides a key-value database with Checkpoints & Resets system
  2. package kvdb
  3. import (
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path"
  8. "sort"
  9. "strings"
  10. "github.com/hermeznetwork/hermez-node/common"
  11. "github.com/hermeznetwork/hermez-node/log"
  12. "github.com/hermeznetwork/tracerr"
  13. "github.com/iden3/go-merkletree/db"
  14. "github.com/iden3/go-merkletree/db/pebble"
  15. )
  16. const (
  17. // PathBatchNum defines the subpath of the Batch Checkpoint in the
  18. // subpath of the KVDB
  19. PathBatchNum = "BatchNum"
  20. // PathCurrent defines the subpath of the current Batch in the subpath
  21. // of the KVDB
  22. PathCurrent = "current"
  23. )
  24. var (
  25. // KeyCurrentBatch is used as key in the db to store the current BatchNum
  26. KeyCurrentBatch = []byte("k:currentbatch")
  27. // keyCurrentIdx is used as key in the db to store the CurrentIdx
  28. keyCurrentIdx = []byte("k:idx")
  29. )
  30. // KVDB represents the Key-Value DB object
  31. type KVDB struct {
  32. path string
  33. db *pebble.Storage
  34. // CurrentIdx holds the current Idx that the BatchBuilder is using
  35. CurrentIdx common.Idx
  36. CurrentBatch common.BatchNum
  37. keep int
  38. }
  39. // NewKVDB creates a new KVDB, allowing to use an in-memory or in-disk storage.
  40. // Checkpoints older than the value defined by `keep` will be deleted.
  41. func NewKVDB(pathDB string, keep int) (*KVDB, error) {
  42. var sto *pebble.Storage
  43. var err error
  44. sto, err = pebble.NewPebbleStorage(path.Join(pathDB, PathCurrent), false)
  45. if err != nil {
  46. return nil, tracerr.Wrap(err)
  47. }
  48. kvdb := &KVDB{
  49. path: pathDB,
  50. db: sto,
  51. keep: keep,
  52. }
  53. // load currentBatch
  54. kvdb.CurrentBatch, err = kvdb.GetCurrentBatch()
  55. if err != nil {
  56. return nil, tracerr.Wrap(err)
  57. }
  58. // make reset (get checkpoint) at currentBatch
  59. err = kvdb.reset(kvdb.CurrentBatch, false)
  60. if err != nil {
  61. return nil, tracerr.Wrap(err)
  62. }
  63. return kvdb, nil
  64. }
  65. // DB returns the *pebble.Storage from the KVDB
  66. func (kvdb *KVDB) DB() *pebble.Storage {
  67. return kvdb.db
  68. }
  69. // StorageWithPrefix returns the db.Storage with the given prefix from the
  70. // current KVDB
  71. func (kvdb *KVDB) StorageWithPrefix(prefix []byte) db.Storage {
  72. return kvdb.db.WithPrefix(prefix)
  73. }
  74. // Reset resets the KVDB to the checkpoint at the given batchNum. Reset does
  75. // not delete the checkpoints between old current and the new current, those
  76. // checkpoints will remain in the storage, and eventually will be deleted when
  77. // MakeCheckpoint overwrites them.
  78. func (kvdb *KVDB) Reset(batchNum common.BatchNum) error {
  79. return kvdb.reset(batchNum, true)
  80. }
  81. // reset resets the KVDB to the checkpoint at the given batchNum. Reset does
  82. // not delete the checkpoints between old current and the new current, those
  83. // checkpoints will remain in the storage, and eventually will be deleted when
  84. // MakeCheckpoint overwrites them. `closeCurrent` will close the currently
  85. // opened db before doing the reset.
  86. func (kvdb *KVDB) reset(batchNum common.BatchNum, closeCurrent bool) error {
  87. currentPath := path.Join(kvdb.path, PathCurrent)
  88. if closeCurrent {
  89. if err := kvdb.db.Pebble().Close(); err != nil {
  90. return tracerr.Wrap(err)
  91. }
  92. }
  93. // remove 'current'
  94. err := os.RemoveAll(currentPath)
  95. if err != nil {
  96. return tracerr.Wrap(err)
  97. }
  98. // remove all checkpoints > batchNum
  99. for i := batchNum + 1; i <= kvdb.CurrentBatch; i++ {
  100. if err := kvdb.DeleteCheckpoint(i); err != nil {
  101. return tracerr.Wrap(err)
  102. }
  103. }
  104. if batchNum == 0 {
  105. // if batchNum == 0, open the new fresh 'current'
  106. sto, err := pebble.NewPebbleStorage(currentPath, false)
  107. if err != nil {
  108. return tracerr.Wrap(err)
  109. }
  110. kvdb.db = sto
  111. kvdb.CurrentIdx = 255
  112. kvdb.CurrentBatch = 0
  113. return nil
  114. }
  115. checkpointPath := path.Join(kvdb.path, fmt.Sprintf("%s%d", PathBatchNum, batchNum))
  116. // copy 'BatchNumX' to 'current'
  117. err = pebbleMakeCheckpoint(checkpointPath, currentPath)
  118. if err != nil {
  119. return tracerr.Wrap(err)
  120. }
  121. // open the new 'current'
  122. sto, err := pebble.NewPebbleStorage(currentPath, false)
  123. if err != nil {
  124. return tracerr.Wrap(err)
  125. }
  126. kvdb.db = sto
  127. // get currentBatch num
  128. kvdb.CurrentBatch, err = kvdb.GetCurrentBatch()
  129. if err != nil {
  130. return tracerr.Wrap(err)
  131. }
  132. // idx is obtained from the statedb reset
  133. kvdb.CurrentIdx, err = kvdb.GetCurrentIdx()
  134. if err != nil {
  135. return tracerr.Wrap(err)
  136. }
  137. return nil
  138. }
  139. // ResetFromSynchronizer performs a reset in the KVDB getting the state from
  140. // synchronizerKVDB for the given batchNum.
  141. func (kvdb *KVDB) ResetFromSynchronizer(batchNum common.BatchNum, synchronizerKVDB *KVDB) error {
  142. if synchronizerKVDB == nil {
  143. return tracerr.Wrap(fmt.Errorf("synchronizerKVDB can not be nil"))
  144. }
  145. if batchNum == 0 {
  146. kvdb.CurrentIdx = 0
  147. return nil
  148. }
  149. synchronizerCheckpointPath := path.Join(synchronizerKVDB.path,
  150. fmt.Sprintf("%s%d", PathBatchNum, batchNum))
  151. checkpointPath := path.Join(kvdb.path, fmt.Sprintf("%s%d", PathBatchNum, batchNum))
  152. currentPath := path.Join(kvdb.path, PathCurrent)
  153. // use checkpoint from synchronizerKVDB
  154. if _, err := os.Stat(synchronizerCheckpointPath); os.IsNotExist(err) {
  155. // if synchronizerKVDB does not have checkpoint at batchNum, return err
  156. return tracerr.Wrap(fmt.Errorf("Checkpoint \"%v\" not exist in Synchronizer",
  157. synchronizerCheckpointPath))
  158. }
  159. if err := kvdb.db.Pebble().Close(); err != nil {
  160. return tracerr.Wrap(err)
  161. }
  162. // remove 'current'
  163. err := os.RemoveAll(currentPath)
  164. if err != nil {
  165. return tracerr.Wrap(err)
  166. }
  167. // copy synchronizer'BatchNumX' to 'current'
  168. err = pebbleMakeCheckpoint(synchronizerCheckpointPath, currentPath)
  169. if err != nil {
  170. return tracerr.Wrap(err)
  171. }
  172. // copy synchronizer'BatchNumX' to 'BatchNumX'
  173. err = pebbleMakeCheckpoint(synchronizerCheckpointPath, checkpointPath)
  174. if err != nil {
  175. return tracerr.Wrap(err)
  176. }
  177. // open the new 'current'
  178. sto, err := pebble.NewPebbleStorage(currentPath, false)
  179. if err != nil {
  180. return tracerr.Wrap(err)
  181. }
  182. kvdb.db = sto
  183. // get currentBatch num
  184. kvdb.CurrentBatch, err = kvdb.GetCurrentBatch()
  185. if err != nil {
  186. return tracerr.Wrap(err)
  187. }
  188. // get currentIdx
  189. kvdb.CurrentIdx, err = kvdb.GetCurrentIdx()
  190. if err != nil {
  191. return tracerr.Wrap(err)
  192. }
  193. return nil
  194. }
  195. // GetCurrentBatch returns the current BatchNum stored in the KVDB
  196. func (kvdb *KVDB) GetCurrentBatch() (common.BatchNum, error) {
  197. cbBytes, err := kvdb.db.Get(KeyCurrentBatch)
  198. if tracerr.Unwrap(err) == db.ErrNotFound {
  199. return 0, nil
  200. }
  201. if err != nil {
  202. return 0, tracerr.Wrap(err)
  203. }
  204. return common.BatchNumFromBytes(cbBytes)
  205. }
  206. // setCurrentBatch stores the current BatchNum in the KVDB
  207. func (kvdb *KVDB) setCurrentBatch() error {
  208. tx, err := kvdb.db.NewTx()
  209. if err != nil {
  210. return tracerr.Wrap(err)
  211. }
  212. err = tx.Put(KeyCurrentBatch, kvdb.CurrentBatch.Bytes())
  213. if err != nil {
  214. return tracerr.Wrap(err)
  215. }
  216. if err := tx.Commit(); err != nil {
  217. return tracerr.Wrap(err)
  218. }
  219. return nil
  220. }
  221. // GetCurrentIdx returns the stored Idx from the KVDB, which is the last Idx
  222. // used for an Account in the KVDB.
  223. func (kvdb *KVDB) GetCurrentIdx() (common.Idx, error) {
  224. idxBytes, err := kvdb.db.Get(keyCurrentIdx)
  225. if tracerr.Unwrap(err) == db.ErrNotFound {
  226. return 0, nil
  227. }
  228. if err != nil {
  229. return 0, tracerr.Wrap(err)
  230. }
  231. return common.IdxFromBytes(idxBytes[:])
  232. }
  233. // SetCurrentIdx stores Idx in the KVDB
  234. func (kvdb *KVDB) SetCurrentIdx(idx common.Idx) error {
  235. kvdb.CurrentIdx = idx
  236. tx, err := kvdb.db.NewTx()
  237. if err != nil {
  238. return tracerr.Wrap(err)
  239. }
  240. idxBytes, err := idx.Bytes()
  241. if err != nil {
  242. return tracerr.Wrap(err)
  243. }
  244. err = tx.Put(keyCurrentIdx, idxBytes[:])
  245. if err != nil {
  246. return tracerr.Wrap(err)
  247. }
  248. if err := tx.Commit(); err != nil {
  249. return tracerr.Wrap(err)
  250. }
  251. return nil
  252. }
  253. // MakeCheckpoint does a checkpoint at the given batchNum in the defined path.
  254. // Internally this advances & stores the current BatchNum, and then stores a
  255. // Checkpoint of the current state of the KVDB.
  256. func (kvdb *KVDB) MakeCheckpoint() error {
  257. // advance currentBatch
  258. kvdb.CurrentBatch++
  259. log.Debugw("Making KVDB checkpoint", "batch", kvdb.CurrentBatch)
  260. checkpointPath := path.Join(kvdb.path, fmt.Sprintf("%s%d", PathBatchNum, kvdb.CurrentBatch))
  261. if err := kvdb.setCurrentBatch(); err != nil {
  262. return tracerr.Wrap(err)
  263. }
  264. // if checkpoint BatchNum already exist in disk, delete it
  265. if _, err := os.Stat(checkpointPath); !os.IsNotExist(err) {
  266. err := os.RemoveAll(checkpointPath)
  267. if err != nil {
  268. return tracerr.Wrap(err)
  269. }
  270. } else if err != nil && !os.IsNotExist(err) {
  271. return tracerr.Wrap(err)
  272. }
  273. // execute Checkpoint
  274. if err := kvdb.db.Pebble().Checkpoint(checkpointPath); err != nil {
  275. return tracerr.Wrap(err)
  276. }
  277. // delete old checkpoints
  278. if err := kvdb.deleteOldCheckpoints(); err != nil {
  279. return tracerr.Wrap(err)
  280. }
  281. return nil
  282. }
  283. // DeleteCheckpoint removes if exist the checkpoint of the given batchNum
  284. func (kvdb *KVDB) DeleteCheckpoint(batchNum common.BatchNum) error {
  285. checkpointPath := path.Join(kvdb.path, fmt.Sprintf("%s%d", PathBatchNum, batchNum))
  286. if _, err := os.Stat(checkpointPath); os.IsNotExist(err) {
  287. return tracerr.Wrap(fmt.Errorf("Checkpoint with batchNum %d does not exist in DB", batchNum))
  288. }
  289. return os.RemoveAll(checkpointPath)
  290. }
  291. // ListCheckpoints returns the list of batchNums of the checkpoints, sorted.
  292. // If there's a gap between the list of checkpoints, an error is returned.
  293. func (kvdb *KVDB) ListCheckpoints() ([]int, error) {
  294. files, err := ioutil.ReadDir(kvdb.path)
  295. if err != nil {
  296. return nil, tracerr.Wrap(err)
  297. }
  298. checkpoints := []int{}
  299. var checkpoint int
  300. pattern := fmt.Sprintf("%s%%d", PathBatchNum)
  301. for _, file := range files {
  302. fileName := file.Name()
  303. if file.IsDir() && strings.HasPrefix(fileName, PathBatchNum) {
  304. if _, err := fmt.Sscanf(fileName, pattern, &checkpoint); err != nil {
  305. return nil, tracerr.Wrap(err)
  306. }
  307. checkpoints = append(checkpoints, checkpoint)
  308. }
  309. }
  310. sort.Ints(checkpoints)
  311. if len(checkpoints) > 0 {
  312. first := checkpoints[0]
  313. for _, checkpoint := range checkpoints[1:] {
  314. first++
  315. if checkpoint != first {
  316. return nil, tracerr.Wrap(fmt.Errorf("checkpoint gap at %v", checkpoint))
  317. }
  318. }
  319. }
  320. return checkpoints, nil
  321. }
  322. // deleteOldCheckpoints deletes old checkpoints when there are more than
  323. // `s.keep` checkpoints
  324. func (kvdb *KVDB) deleteOldCheckpoints() error {
  325. list, err := kvdb.ListCheckpoints()
  326. if err != nil {
  327. return tracerr.Wrap(err)
  328. }
  329. if len(list) > kvdb.keep {
  330. for _, checkpoint := range list[:len(list)-kvdb.keep] {
  331. if err := kvdb.DeleteCheckpoint(common.BatchNum(checkpoint)); err != nil {
  332. return tracerr.Wrap(err)
  333. }
  334. }
  335. }
  336. return nil
  337. }
  338. func pebbleMakeCheckpoint(source, dest string) error {
  339. // Remove dest folder (if it exists) before doing the checkpoint
  340. if _, err := os.Stat(dest); !os.IsNotExist(err) {
  341. err := os.RemoveAll(dest)
  342. if err != nil {
  343. return tracerr.Wrap(err)
  344. }
  345. } else if err != nil && !os.IsNotExist(err) {
  346. return tracerr.Wrap(err)
  347. }
  348. sto, err := pebble.NewPebbleStorage(source, false)
  349. if err != nil {
  350. return tracerr.Wrap(err)
  351. }
  352. defer func() {
  353. errClose := sto.Pebble().Close()
  354. if errClose != nil {
  355. log.Errorw("Pebble.Close", "err", errClose)
  356. }
  357. }()
  358. // execute Checkpoint
  359. err = sto.Pebble().Checkpoint(dest)
  360. if err != nil {
  361. return tracerr.Wrap(err)
  362. }
  363. return nil
  364. }