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.

534 lines
14 KiB

Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
Fix eth events query and sync inconsistent state - kvdb - Fix path in Last when doing `setNew` - Only close if db != nil, and after closing, always set db to nil - This will avoid a panic in the case where the db is closed but there's an error soon after, and a future call tries to close again. This is because pebble.Close() will panic if the db is already closed. - Avoid calling pebble methods when a the Storage interface already implements that method (like Close). - statedb - In test, avoid calling KVDB method if the same method is available for the StateDB (like MakeCheckpoint, CurrentBatch). - eth - In *EventByBlock methods, take blockHash as input argument and use it when querying the event logs. Previously the blockHash was only taken from the logs results *only if* there was any log. This caused the following issue: if there was no logs, it was not possible to know if the result was from the expected block or an uncle block! By querying logs by blockHash we make sure that even if there are no logs, they are from the right block. - Note that now the function can either be called with a blockNum or blockHash, but not both at the same time. - sync - If there's an error during call to Sync call resetState, which internally resets the stateDB to avoid stale checkpoints (and a corresponding invalid increase in the StateDB batchNum). - During a Sync, after very batch processed, make sure that the StateDB currentBatch corresponds to the batchNum in the smart contract log/event.
3 years ago
  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. "sync"
  11. "github.com/hermeznetwork/hermez-node/common"
  12. "github.com/hermeznetwork/hermez-node/log"
  13. "github.com/hermeznetwork/tracerr"
  14. "github.com/iden3/go-merkletree/db"
  15. "github.com/iden3/go-merkletree/db/pebble"
  16. )
  17. const (
  18. // PathBatchNum defines the subpath of the Batch Checkpoint in the
  19. // subpath of the KVDB
  20. PathBatchNum = "BatchNum"
  21. // PathCurrent defines the subpath of the current Batch in the subpath
  22. // of the KVDB
  23. PathCurrent = "current"
  24. // PathLast defines the subpath of the last Batch in the subpath
  25. // of the StateDB
  26. PathLast = "last"
  27. )
  28. var (
  29. // KeyCurrentBatch is used as key in the db to store the current BatchNum
  30. KeyCurrentBatch = []byte("k:currentbatch")
  31. // keyCurrentIdx is used as key in the db to store the CurrentIdx
  32. keyCurrentIdx = []byte("k:idx")
  33. )
  34. // KVDB represents the Key-Value DB object
  35. type KVDB struct {
  36. path string
  37. db *pebble.Storage
  38. // CurrentIdx holds the current Idx that the BatchBuilder is using
  39. CurrentIdx common.Idx
  40. CurrentBatch common.BatchNum
  41. keep int
  42. m sync.Mutex
  43. last *Last
  44. }
  45. // Last is a consistent view to the last batch of the stateDB that can
  46. // be queried concurrently.
  47. type Last struct {
  48. db *pebble.Storage
  49. path string
  50. rw sync.RWMutex
  51. }
  52. func (k *Last) setNew() error {
  53. k.rw.Lock()
  54. defer k.rw.Unlock()
  55. if k.db != nil {
  56. k.db.Close()
  57. k.db = nil
  58. }
  59. lastPath := path.Join(k.path, PathLast)
  60. if err := os.RemoveAll(lastPath); err != nil {
  61. return tracerr.Wrap(err)
  62. }
  63. db, err := pebble.NewPebbleStorage(lastPath, false)
  64. if err != nil {
  65. return tracerr.Wrap(err)
  66. }
  67. k.db = db
  68. return nil
  69. }
  70. func (k *Last) set(kvdb *KVDB, batchNum common.BatchNum) error {
  71. k.rw.Lock()
  72. defer k.rw.Unlock()
  73. if k.db != nil {
  74. k.db.Close()
  75. k.db = nil
  76. }
  77. lastPath := path.Join(k.path, PathLast)
  78. if err := kvdb.MakeCheckpointFromTo(batchNum, lastPath); err != nil {
  79. return tracerr.Wrap(err)
  80. }
  81. db, err := pebble.NewPebbleStorage(lastPath, false)
  82. if err != nil {
  83. return tracerr.Wrap(err)
  84. }
  85. k.db = db
  86. return nil
  87. }
  88. func (k *Last) close() {
  89. k.rw.Lock()
  90. defer k.rw.Unlock()
  91. if k.db != nil {
  92. k.db.Close()
  93. k.db = nil
  94. }
  95. }
  96. // NewKVDB creates a new KVDB, allowing to use an in-memory or in-disk storage.
  97. // Checkpoints older than the value defined by `keep` will be deleted.
  98. func NewKVDB(pathDB string, keep int) (*KVDB, error) {
  99. var sto *pebble.Storage
  100. var err error
  101. sto, err = pebble.NewPebbleStorage(path.Join(pathDB, PathCurrent), false)
  102. if err != nil {
  103. return nil, tracerr.Wrap(err)
  104. }
  105. kvdb := &KVDB{
  106. path: pathDB,
  107. db: sto,
  108. keep: keep,
  109. last: &Last{
  110. path: pathDB,
  111. },
  112. }
  113. // load currentBatch
  114. kvdb.CurrentBatch, err = kvdb.GetCurrentBatch()
  115. if err != nil {
  116. return nil, tracerr.Wrap(err)
  117. }
  118. // make reset (get checkpoint) at currentBatch
  119. err = kvdb.reset(kvdb.CurrentBatch, true)
  120. if err != nil {
  121. return nil, tracerr.Wrap(err)
  122. }
  123. return kvdb, nil
  124. }
  125. // LastRead is a thread-safe method to query the last KVDB
  126. func (kvdb *KVDB) LastRead(fn func(db *pebble.Storage) error) error {
  127. kvdb.last.rw.RLock()
  128. defer kvdb.last.rw.RUnlock()
  129. return fn(kvdb.last.db)
  130. }
  131. // DB returns the *pebble.Storage from the KVDB
  132. func (kvdb *KVDB) DB() *pebble.Storage {
  133. return kvdb.db
  134. }
  135. // StorageWithPrefix returns the db.Storage with the given prefix from the
  136. // current KVDB
  137. func (kvdb *KVDB) StorageWithPrefix(prefix []byte) db.Storage {
  138. return kvdb.db.WithPrefix(prefix)
  139. }
  140. // Reset resets the KVDB to the checkpoint at the given batchNum. Reset does
  141. // not delete the checkpoints between old current and the new current, those
  142. // checkpoints will remain in the storage, and eventually will be deleted when
  143. // MakeCheckpoint overwrites them.
  144. func (kvdb *KVDB) Reset(batchNum common.BatchNum) error {
  145. return kvdb.reset(batchNum, true)
  146. }
  147. // reset resets the KVDB to the checkpoint at the given batchNum. Reset does
  148. // not delete the checkpoints between old current and the new current, those
  149. // checkpoints will remain in the storage, and eventually will be deleted when
  150. // MakeCheckpoint overwrites them. `closeCurrent` will close the currently
  151. // opened db before doing the reset.
  152. func (kvdb *KVDB) reset(batchNum common.BatchNum, closeCurrent bool) error {
  153. currentPath := path.Join(kvdb.path, PathCurrent)
  154. if closeCurrent && kvdb.db != nil {
  155. kvdb.db.Close()
  156. kvdb.db = nil
  157. }
  158. // remove 'current'
  159. if err := os.RemoveAll(currentPath); err != nil {
  160. return tracerr.Wrap(err)
  161. }
  162. // remove all checkpoints > batchNum
  163. list, err := kvdb.ListCheckpoints()
  164. if err != nil {
  165. return tracerr.Wrap(err)
  166. }
  167. // Find first batch that is greater than batchNum, and delete
  168. // everything after that
  169. start := 0
  170. for ; start < len(list); start++ {
  171. if common.BatchNum(list[start]) > batchNum {
  172. break
  173. }
  174. }
  175. for _, bn := range list[start:] {
  176. if err := kvdb.DeleteCheckpoint(common.BatchNum(bn)); err != nil {
  177. return tracerr.Wrap(err)
  178. }
  179. }
  180. if batchNum == 0 {
  181. // if batchNum == 0, open the new fresh 'current'
  182. sto, err := pebble.NewPebbleStorage(currentPath, false)
  183. if err != nil {
  184. return tracerr.Wrap(err)
  185. }
  186. kvdb.db = sto
  187. kvdb.CurrentIdx = common.RollupConstReservedIDx // 255
  188. kvdb.CurrentBatch = 0
  189. if err := kvdb.last.setNew(); err != nil {
  190. return tracerr.Wrap(err)
  191. }
  192. return nil
  193. }
  194. // copy 'batchNum' to 'current'
  195. if err := kvdb.MakeCheckpointFromTo(batchNum, currentPath); err != nil {
  196. return tracerr.Wrap(err)
  197. }
  198. // copy 'batchNum' to 'last'
  199. if err := kvdb.last.set(kvdb, batchNum); err != nil {
  200. return tracerr.Wrap(err)
  201. }
  202. // open the new 'current'
  203. sto, err := pebble.NewPebbleStorage(currentPath, false)
  204. if err != nil {
  205. return tracerr.Wrap(err)
  206. }
  207. kvdb.db = sto
  208. // get currentBatch num
  209. kvdb.CurrentBatch, err = kvdb.GetCurrentBatch()
  210. if err != nil {
  211. return tracerr.Wrap(err)
  212. }
  213. // idx is obtained from the statedb reset
  214. kvdb.CurrentIdx, err = kvdb.GetCurrentIdx()
  215. if err != nil {
  216. return tracerr.Wrap(err)
  217. }
  218. return nil
  219. }
  220. // ResetFromSynchronizer performs a reset in the KVDB getting the state from
  221. // synchronizerKVDB for the given batchNum.
  222. func (kvdb *KVDB) ResetFromSynchronizer(batchNum common.BatchNum, synchronizerKVDB *KVDB) error {
  223. if synchronizerKVDB == nil {
  224. return tracerr.Wrap(fmt.Errorf("synchronizerKVDB can not be nil"))
  225. }
  226. currentPath := path.Join(kvdb.path, PathCurrent)
  227. if kvdb.db != nil {
  228. kvdb.db.Close()
  229. kvdb.db = nil
  230. }
  231. // remove 'current'
  232. if err := os.RemoveAll(currentPath); err != nil {
  233. return tracerr.Wrap(err)
  234. }
  235. // remove all checkpoints
  236. list, err := kvdb.ListCheckpoints()
  237. if err != nil {
  238. return tracerr.Wrap(err)
  239. }
  240. for _, bn := range list {
  241. if err := kvdb.DeleteCheckpoint(common.BatchNum(bn)); err != nil {
  242. return tracerr.Wrap(err)
  243. }
  244. }
  245. if batchNum == 0 {
  246. // if batchNum == 0, open the new fresh 'current'
  247. sto, err := pebble.NewPebbleStorage(currentPath, false)
  248. if err != nil {
  249. return tracerr.Wrap(err)
  250. }
  251. kvdb.db = sto
  252. kvdb.CurrentIdx = common.RollupConstReservedIDx // 255
  253. kvdb.CurrentBatch = 0
  254. return nil
  255. }
  256. checkpointPath := path.Join(kvdb.path, fmt.Sprintf("%s%d", PathBatchNum, batchNum))
  257. // copy synchronizer'BatchNumX' to 'BatchNumX'
  258. if err := synchronizerKVDB.MakeCheckpointFromTo(batchNum, checkpointPath); err != nil {
  259. return tracerr.Wrap(err)
  260. }
  261. // copy 'BatchNumX' to 'current'
  262. err = kvdb.MakeCheckpointFromTo(batchNum, currentPath)
  263. if err != nil {
  264. return tracerr.Wrap(err)
  265. }
  266. // open the new 'current'
  267. sto, err := pebble.NewPebbleStorage(currentPath, false)
  268. if err != nil {
  269. return tracerr.Wrap(err)
  270. }
  271. kvdb.db = sto
  272. // get currentBatch num
  273. kvdb.CurrentBatch, err = kvdb.GetCurrentBatch()
  274. if err != nil {
  275. return tracerr.Wrap(err)
  276. }
  277. // get currentIdx
  278. kvdb.CurrentIdx, err = kvdb.GetCurrentIdx()
  279. if err != nil {
  280. return tracerr.Wrap(err)
  281. }
  282. return nil
  283. }
  284. // GetCurrentBatch returns the current BatchNum stored in the KVDB
  285. func (kvdb *KVDB) GetCurrentBatch() (common.BatchNum, error) {
  286. cbBytes, err := kvdb.db.Get(KeyCurrentBatch)
  287. if tracerr.Unwrap(err) == db.ErrNotFound {
  288. return 0, nil
  289. }
  290. if err != nil {
  291. return 0, tracerr.Wrap(err)
  292. }
  293. return common.BatchNumFromBytes(cbBytes)
  294. }
  295. // setCurrentBatch stores the current BatchNum in the KVDB
  296. func (kvdb *KVDB) setCurrentBatch() error {
  297. tx, err := kvdb.db.NewTx()
  298. if err != nil {
  299. return tracerr.Wrap(err)
  300. }
  301. err = tx.Put(KeyCurrentBatch, kvdb.CurrentBatch.Bytes())
  302. if err != nil {
  303. return tracerr.Wrap(err)
  304. }
  305. if err := tx.Commit(); err != nil {
  306. return tracerr.Wrap(err)
  307. }
  308. return nil
  309. }
  310. // GetCurrentIdx returns the stored Idx from the KVDB, which is the last Idx
  311. // used for an Account in the KVDB.
  312. func (kvdb *KVDB) GetCurrentIdx() (common.Idx, error) {
  313. idxBytes, err := kvdb.db.Get(keyCurrentIdx)
  314. if tracerr.Unwrap(err) == db.ErrNotFound {
  315. return common.RollupConstReservedIDx, nil // 255, nil
  316. }
  317. if err != nil {
  318. return 0, tracerr.Wrap(err)
  319. }
  320. return common.IdxFromBytes(idxBytes[:])
  321. }
  322. // SetCurrentIdx stores Idx in the KVDB
  323. func (kvdb *KVDB) SetCurrentIdx(idx common.Idx) error {
  324. kvdb.CurrentIdx = idx
  325. tx, err := kvdb.db.NewTx()
  326. if err != nil {
  327. return tracerr.Wrap(err)
  328. }
  329. idxBytes, err := idx.Bytes()
  330. if err != nil {
  331. return tracerr.Wrap(err)
  332. }
  333. err = tx.Put(keyCurrentIdx, idxBytes[:])
  334. if err != nil {
  335. return tracerr.Wrap(err)
  336. }
  337. if err := tx.Commit(); err != nil {
  338. return tracerr.Wrap(err)
  339. }
  340. return nil
  341. }
  342. // MakeCheckpoint does a checkpoint at the given batchNum in the defined path.
  343. // Internally this advances & stores the current BatchNum, and then stores a
  344. // Checkpoint of the current state of the KVDB.
  345. func (kvdb *KVDB) MakeCheckpoint() error {
  346. // advance currentBatch
  347. kvdb.CurrentBatch++
  348. checkpointPath := path.Join(kvdb.path, fmt.Sprintf("%s%d", PathBatchNum, kvdb.CurrentBatch))
  349. if err := kvdb.setCurrentBatch(); err != nil {
  350. return tracerr.Wrap(err)
  351. }
  352. // if checkpoint BatchNum already exist in disk, delete it
  353. if _, err := os.Stat(checkpointPath); !os.IsNotExist(err) {
  354. if err := os.RemoveAll(checkpointPath); err != nil {
  355. return tracerr.Wrap(err)
  356. }
  357. } else if err != nil && !os.IsNotExist(err) {
  358. return tracerr.Wrap(err)
  359. }
  360. // execute Checkpoint
  361. if err := kvdb.db.Pebble().Checkpoint(checkpointPath); err != nil {
  362. return tracerr.Wrap(err)
  363. }
  364. // copy 'CurrentBatch' to 'last'
  365. if err := kvdb.last.set(kvdb, kvdb.CurrentBatch); err != nil {
  366. return tracerr.Wrap(err)
  367. }
  368. // delete old checkpoints
  369. if err := kvdb.deleteOldCheckpoints(); err != nil {
  370. return tracerr.Wrap(err)
  371. }
  372. return nil
  373. }
  374. // DeleteCheckpoint removes if exist the checkpoint of the given batchNum
  375. func (kvdb *KVDB) DeleteCheckpoint(batchNum common.BatchNum) error {
  376. checkpointPath := path.Join(kvdb.path, fmt.Sprintf("%s%d", PathBatchNum, batchNum))
  377. if _, err := os.Stat(checkpointPath); os.IsNotExist(err) {
  378. return tracerr.Wrap(fmt.Errorf("Checkpoint with batchNum %d does not exist in DB", batchNum))
  379. }
  380. return os.RemoveAll(checkpointPath)
  381. }
  382. // ListCheckpoints returns the list of batchNums of the checkpoints, sorted.
  383. // If there's a gap between the list of checkpoints, an error is returned.
  384. func (kvdb *KVDB) ListCheckpoints() ([]int, error) {
  385. files, err := ioutil.ReadDir(kvdb.path)
  386. if err != nil {
  387. return nil, tracerr.Wrap(err)
  388. }
  389. checkpoints := []int{}
  390. var checkpoint int
  391. pattern := fmt.Sprintf("%s%%d", PathBatchNum)
  392. for _, file := range files {
  393. fileName := file.Name()
  394. if file.IsDir() && strings.HasPrefix(fileName, PathBatchNum) {
  395. if _, err := fmt.Sscanf(fileName, pattern, &checkpoint); err != nil {
  396. return nil, tracerr.Wrap(err)
  397. }
  398. checkpoints = append(checkpoints, checkpoint)
  399. }
  400. }
  401. sort.Ints(checkpoints)
  402. if len(checkpoints) > 0 {
  403. first := checkpoints[0]
  404. for _, checkpoint := range checkpoints[1:] {
  405. first++
  406. if checkpoint != first {
  407. log.Errorw("GAP", "checkpoints", checkpoints)
  408. return nil, tracerr.Wrap(fmt.Errorf("checkpoint gap at %v", checkpoint))
  409. }
  410. }
  411. }
  412. return checkpoints, nil
  413. }
  414. // deleteOldCheckpoints deletes old checkpoints when there are more than
  415. // `s.keep` checkpoints
  416. func (kvdb *KVDB) deleteOldCheckpoints() error {
  417. list, err := kvdb.ListCheckpoints()
  418. if err != nil {
  419. return tracerr.Wrap(err)
  420. }
  421. if len(list) > kvdb.keep {
  422. for _, checkpoint := range list[:len(list)-kvdb.keep] {
  423. if err := kvdb.DeleteCheckpoint(common.BatchNum(checkpoint)); err != nil {
  424. return tracerr.Wrap(err)
  425. }
  426. }
  427. }
  428. return nil
  429. }
  430. // MakeCheckpointFromTo makes a checkpoint from the current db at fromBatchNum
  431. // to the dest folder. This method is locking, so it can be called from
  432. // multiple places at the same time.
  433. func (kvdb *KVDB) MakeCheckpointFromTo(fromBatchNum common.BatchNum, dest string) error {
  434. source := path.Join(kvdb.path, fmt.Sprintf("%s%d", PathBatchNum, fromBatchNum))
  435. if _, err := os.Stat(source); os.IsNotExist(err) {
  436. // if kvdb does not have checkpoint at batchNum, return err
  437. return tracerr.Wrap(fmt.Errorf("Checkpoint \"%v\" does not exist", source))
  438. }
  439. // By locking we allow calling MakeCheckpointFromTo from multiple
  440. // places at the same time for the same stateDB. This allows the
  441. // synchronizer to do a reset to a batchNum at the same time as the
  442. // pipeline is doing a txSelector.Reset and batchBuilder.Reset from
  443. // synchronizer to the same batchNum
  444. kvdb.m.Lock()
  445. defer kvdb.m.Unlock()
  446. return pebbleMakeCheckpoint(source, dest)
  447. }
  448. func pebbleMakeCheckpoint(source, dest string) error {
  449. // Remove dest folder (if it exists) before doing the checkpoint
  450. if _, err := os.Stat(dest); !os.IsNotExist(err) {
  451. if err := os.RemoveAll(dest); err != nil {
  452. return tracerr.Wrap(err)
  453. }
  454. } else if err != nil && !os.IsNotExist(err) {
  455. return tracerr.Wrap(err)
  456. }
  457. sto, err := pebble.NewPebbleStorage(source, false)
  458. if err != nil {
  459. return tracerr.Wrap(err)
  460. }
  461. defer sto.Close()
  462. // execute Checkpoint
  463. err = sto.Pebble().Checkpoint(dest)
  464. if err != nil {
  465. return tracerr.Wrap(err)
  466. }
  467. return nil
  468. }
  469. // Close the DB
  470. func (kvdb *KVDB) Close() {
  471. if kvdb.db != nil {
  472. kvdb.db.Close()
  473. kvdb.db = nil
  474. }
  475. kvdb.last.close()
  476. }