44import com .beust .jcommander .Parameter ;
55import com .beust .jcommander .ParameterException ;
66import com .beust .jcommander .internal .Lists ;
7+ import com .google .common .annotations .VisibleForTesting ;
78import com .google .common .base .Strings ;
89import com .google .common .collect .Maps ;
910import com .google .common .primitives .Bytes ;
1617import java .nio .file .Paths ;
1718import java .util .Arrays ;
1819import java .util .List ;
20+ import java .util .NoSuchElementException ;
1921import java .util .Objects ;
2022import java .util .Optional ;
21- import java .util .stream .LongStream ;
2223import lombok .extern .slf4j .Slf4j ;
2324import org .apache .commons .lang3 .StringUtils ;
2425import org .rocksdb .RocksDBException ;
@@ -47,50 +48,23 @@ public class LiteFullNodeTool {
4748 private static final String INFO_FILE_NAME = "info.properties" ;
4849 private static final String BACKUP_DIR_PREFIX = ".bak_" ;
4950 private static final String CHECKPOINT_DB = "tmp" ;
50- private static final long VM_NEED_RECENT_BLKS = 256 ;
51+ private static long RECENT_BLKS = 65536 ;
5152
5253 private static final String BLOCK_DB_NAME = "block" ;
5354 private static final String BLOCK_INDEX_DB_NAME = "block-index" ;
54- private static final String TRANS_CACHE_DB_NAME = "trans-cache " ;
55+ private static final String TRANS_DB_NAME = "trans" ;
5556 private static final String COMMON_DB_NAME = "common" ;
57+ private static final String TRANSACTION_RET_DB_NAME = "transactionRetStore" ;
58+ private static final String TRANSACTION_HISTORY_DB_NAME = "transactionHistoryStore" ;
5659
5760 private static final String DIR_FORMAT_STRING = "%s%s%s" ;
5861
5962 private static List <String > archiveDbs = Arrays .asList (
6063 BLOCK_DB_NAME ,
6164 BLOCK_INDEX_DB_NAME ,
62- "trans" ,
63- "transactionRetStore" ,
64- "transactionHistoryStore" );
65- private static List <String > minimumDbsForLiteNode = Arrays .asList (
66- "DelegatedResource" ,
67- "DelegatedResourceAccountIndex" ,
68- "IncrementalMerkleTree" ,
69- "account" ,
70- "account-index" ,
71- "accountTrie" ,
72- "accountid-index" ,
73- "asset-issue" ,
74- "asset-issue-v2" ,
75- //"block_KDB",
76- "code" ,
77- //"common",
78- "contract" ,
79- "delegation" ,
80- "exchange" ,
81- "exchange-v2" ,
82- //"nullifier",
83- "properties" ,
84- "proposal" ,
85- "recent-block" ,
86- //"recent-transaction",
87- "storage-row" ,
88- //TRANS_CACHE_DB_NAME,
89- //"tree-block-index",
90- "votes" ,
91- "witness" ,
92- "witness_schedule"
93- );
65+ TRANS_DB_NAME ,
66+ TRANSACTION_RET_DB_NAME ,
67+ TRANSACTION_HISTORY_DB_NAME );
9468
9569 /**
9670 * Create the snapshot dataset.
@@ -104,13 +78,12 @@ public void generateSnapshot(String sourceDir, String snapshotDir) {
10478 long start = System .currentTimeMillis ();
10579 snapshotDir = Paths .get (snapshotDir , SNAPSHOT_DIR_NAME ).toString ();
10680 try {
81+ hasEnoughBlock (sourceDir );
10782 List <String > snapshotDbs = getSnapshotDbs (sourceDir );
10883 split (sourceDir , snapshotDir , snapshotDbs );
10984 mergeCheckpoint2Snapshot (sourceDir , snapshotDir );
110- // write genesisBlock and latestBlock
111- fillSnapshotBlockDb (sourceDir , snapshotDir );
112- // create tran-cache if not exist, for compatible
113- checkTranCacheStore (sourceDir , snapshotDir );
85+ // write genesisBlock , latest recent blocks and trans
86+ fillSnapshotBlockAndTransDb (sourceDir , snapshotDir );
11487 generateInfoProperties (Paths .get (snapshotDir , INFO_FILE_NAME ).toString (), sourceDir );
11588 } catch (IOException | RocksDBException e ) {
11689 logger .error ("create snapshot failed, " + e .getMessage ());
@@ -132,6 +105,11 @@ public void generateHistory(String sourceDir, String historyDir) {
132105 long start = System .currentTimeMillis ();
133106 historyDir = Paths .get (historyDir , HISTORY_DIR_NAME ).toString ();
134107 try {
108+ if (isLite (sourceDir )) {
109+ throw new IllegalStateException (
110+ String .format ("Unavailable sourceDir: %s is not fullNode data." , sourceDir ));
111+ }
112+ hasEnoughBlock (sourceDir );
135113 split (sourceDir , historyDir , archiveDbs );
136114 mergeCheckpoint2History (sourceDir , historyDir );
137115 generateInfoProperties (Paths .get (historyDir , INFO_FILE_NAME ).toString (), sourceDir );
@@ -155,6 +133,12 @@ public void completeHistoryData(String historyDir, String databaseDir) {
155133 long start = System .currentTimeMillis ();
156134 BlockNumInfo blockNumInfo = null ;
157135 try {
136+ // check historyDir is from lite data
137+ if (isLite (historyDir )) {
138+ throw new IllegalStateException (
139+ String .format ("Unavailable history: %s is not generated by fullNode data." ,
140+ historyDir ));
141+ }
158142 // 1. check block number and genesis block are compatible,
159143 // and return the block numbers of snapshot and history
160144 blockNumInfo = checkAndGetBlockNumInfo (historyDir , databaseDir );
@@ -183,11 +167,6 @@ private List<String> getSnapshotDbs(String sourceDir) {
183167 .filter (File ::isDirectory )
184168 .filter (dir -> !archiveDbs .contains (dir .getName ()))
185169 .forEach (dir -> snapshotDbs .add (dir .getName ()));
186- for (String dir : minimumDbsForLiteNode ) {
187- if (!snapshotDbs .contains (dir )) {
188- throw new RuntimeException ("databaseDir does not contain all the necessary databases" );
189- }
190- }
191170 return snapshotDbs ;
192171 }
193172
@@ -270,88 +249,50 @@ private long getLatestBlockHeaderNum(String databaseDir) throws IOException, Roc
270249 }
271250
272251 /**
273- * Syncing block from peer that needs latest block and genesis block,
274- * also VM need recent blocks.
252+ * recent blocks, trans and genesis block.
275253 */
276- private void fillSnapshotBlockDb (String sourceDir , String snapshotDir )
254+ private void fillSnapshotBlockAndTransDb (String sourceDir , String snapshotDir )
277255 throws IOException , RocksDBException {
278- logger .info ("-- begin to fill latest block and genesis block to snapshot" );
256+ logger .info ("-- begin to fill {} block , genesis block and trans to snapshot" , RECENT_BLKS );
279257 DBInterface sourceBlockIndexDb = DbTool .getDB (sourceDir , BLOCK_INDEX_DB_NAME );
280258 DBInterface sourceBlockDb = DbTool .getDB (sourceDir , BLOCK_DB_NAME );
281259 DBInterface destBlockDb = DbTool .getDB (snapshotDir , BLOCK_DB_NAME );
282260 DBInterface destBlockIndexDb = DbTool .getDB (snapshotDir , BLOCK_INDEX_DB_NAME );
261+ DBInterface destTransDb = DbTool .getDB (snapshotDir , TRANS_DB_NAME );
283262 // put genesis block and block-index into snapshot
284263 long genesisBlockNum = 0L ;
285264 byte [] genesisBlockID = sourceBlockIndexDb .get (ByteArray .fromLong (genesisBlockNum ));
286265 destBlockIndexDb .put (ByteArray .fromLong (genesisBlockNum ), genesisBlockID );
287266 destBlockDb .put (genesisBlockID , sourceBlockDb .get (genesisBlockID ));
288267
289268 long latestBlockNum = getLatestBlockHeaderNum (sourceDir );
290- long startIndex = latestBlockNum > VM_NEED_RECENT_BLKS
291- ? latestBlockNum - VM_NEED_RECENT_BLKS : 0 ;
292- // put the recent blocks in snapshot, VM needs recent 256 blocks.
293- LongStream .rangeClosed (startIndex , latestBlockNum ).forEach (
294- blockNum -> {
295- byte [] blockId = null ;
296- byte [] block = null ;
297- try {
298- blockId = getDataFromSourceDB (sourceDir , BLOCK_INDEX_DB_NAME ,
299- Longs .toByteArray (blockNum ));
300- block = getDataFromSourceDB (sourceDir , BLOCK_DB_NAME , blockId );
301- } catch (IOException | RocksDBException e ) {
302- throw new RuntimeException (e .getMessage ());
303- }
304- // put recent blocks index into snapshot
305- destBlockIndexDb .put (ByteArray .fromLong (blockNum ), blockId );
306- // put latest blocks into snapshot
307- destBlockDb .put (blockId , block );
308- });
269+ long startIndex = latestBlockNum - RECENT_BLKS + 1 ;
270+ // put the recent blocks and trans in snapshot
271+ for (long blockNum = startIndex ; blockNum <= latestBlockNum ; blockNum ++) {
272+ try {
273+ byte [] blockId = getDataFromSourceDB (sourceDir , BLOCK_INDEX_DB_NAME ,
274+ Longs .toByteArray (blockNum ));
275+ byte [] block = getDataFromSourceDB (sourceDir , BLOCK_DB_NAME , blockId );
276+ // put block
277+ destBlockDb .put (blockId , block );
278+ // put block index
279+ destBlockIndexDb .put (ByteArray .fromLong (blockNum ), blockId );
280+ // put trans
281+ long finalBlockNum = blockNum ;
282+ new BlockCapsule (block ).getTransactions ().stream ().map (
283+ tc -> tc .getTransactionId ().getBytes ())
284+ .map (bytes -> Maps .immutableEntry (bytes , Longs .toByteArray (finalBlockNum )))
285+ .forEach (e -> destTransDb .put (e .getKey (), e .getValue ()));
286+ } catch (IOException | RocksDBException | BadItemException e ) {
287+ throw new RuntimeException (e .getMessage ());
288+ }
289+ }
309290
310291 DBInterface destCommonDb = DbTool .getDB (snapshotDir , COMMON_DB_NAME );
311292 destCommonDb .put (DB_KEY_NODE_TYPE , ByteArray .fromInt (Constant .NODE_TYPE_LIGHT_NODE ));
312293 destCommonDb .put (DB_KEY_LOWEST_BLOCK_NUM , ByteArray .fromLong (startIndex ));
313294 }
314295
315- private void checkTranCacheStore (String sourceDir , String snapshotDir )
316- throws IOException , RocksDBException {
317- logger .info ("-- create trans-cache db if not exists." );
318- if (FileUtil .isExists (String .format (DIR_FORMAT_STRING , snapshotDir ,
319- File .separator , TRANS_CACHE_DB_NAME ))) {
320- return ;
321- }
322- // fullnode is old version, create trans-cache database
323- DBInterface recentBlockDb = DbTool .getDB (snapshotDir , "recent-block" );
324- DBInterface transCacheDb = DbTool .getDB (snapshotDir , TRANS_CACHE_DB_NAME );
325- long headNum = getLatestBlockHeaderNum (sourceDir );
326- long recentBlockCount = recentBlockDb .size ();
327-
328- LongStream .rangeClosed (headNum - recentBlockCount + 1 , headNum ).forEach (
329- blockNum -> {
330- byte [] blockId = null ;
331- byte [] block = null ;
332- try {
333- blockId = getDataFromSourceDB (sourceDir , BLOCK_INDEX_DB_NAME ,
334- Longs .toByteArray (blockNum ));
335- block = getDataFromSourceDB (sourceDir , BLOCK_DB_NAME , blockId );
336- } catch (IOException | RocksDBException e ) {
337- throw new RuntimeException (e .getMessage ());
338- }
339- BlockCapsule blockCapsule = null ;
340- try {
341- blockCapsule = new BlockCapsule (block );
342- } catch (BadItemException e ) {
343- throw new RuntimeException ("construct block failed, num: " + blockNum );
344- }
345- if (blockCapsule .getTransactions ().isEmpty ()) {
346- return ;
347- }
348- blockCapsule .getTransactions ().stream ()
349- .map (tc -> tc .getTransactionId ().getBytes ())
350- .map (bytes -> Maps .immutableEntry (bytes , Longs .toByteArray (blockNum )))
351- .forEach (e -> transCacheDb .put (e .getKey (), e .getValue ()));
352- });
353- }
354-
355296 private byte [] getGenesisBlockHash (String parentDir ) throws IOException , RocksDBException {
356297 long genesisBlockNum = 0L ;
357298 DBInterface blockIndexDb = DbTool .getDB (parentDir , BLOCK_INDEX_DB_NAME );
@@ -424,8 +365,8 @@ private void trimHistory(String databaseDir, BlockNumInfo blockNumInfo)
424365 logger .info ("-- begin to trim the history data." );
425366 DBInterface blockIndexDb = DbTool .getDB (databaseDir , BLOCK_INDEX_DB_NAME );
426367 DBInterface blockDb = DbTool .getDB (databaseDir , BLOCK_DB_NAME );
427- DBInterface transDb = DbTool .getDB (databaseDir , "trans" );
428- DBInterface tranRetDb = DbTool .getDB (databaseDir , "transactionRetStore" );
368+ DBInterface transDb = DbTool .getDB (databaseDir , TRANS_DB_NAME );
369+ DBInterface tranRetDb = DbTool .getDB (databaseDir , TRANSACTION_RET_DB_NAME );
429370 for (long n = blockNumInfo .getHistoryBlkNum (); n > blockNumInfo .getSnapshotBlkNum (); n --) {
430371 byte [] blockIdHash = blockIndexDb .get (ByteArray .fromLong (n ));
431372 BlockCapsule block = new BlockCapsule (blockDb .get (blockIdHash ));
@@ -460,12 +401,15 @@ private void mergeBak2Database(String databaseDir) throws IOException, RocksDBEx
460401 private byte [] getDataFromSourceDB (String sourceDir , String dbName , byte [] key )
461402 throws IOException , RocksDBException {
462403 DBInterface sourceDb = DbTool .getDB (sourceDir , dbName );
463- DBInterface checkpointDb = DbTool .getDB (sourceDir , "tmp" );
464- byte [] value = sourceDb .get (key );
465- if (isEmptyBytes (value )) {
466- byte [] valueFromTmp = checkpointDb .get (Bytes .concat (simpleEncode (dbName ), key ));
404+ DBInterface checkpointDb = DbTool .getDB (sourceDir , CHECKPOINT_DB );
405+ // get data from tmp first.
406+ byte [] valueFromTmp = checkpointDb .get (Bytes .concat (simpleEncode (dbName ), key ));
407+ byte [] value ;
408+ if (isEmptyBytes (valueFromTmp )) {
409+ value = sourceDb .get (key );
410+ } else {
467411 value = valueFromTmp .length == 1
468- ? null : Arrays .copyOfRange (valueFromTmp , 1 , valueFromTmp .length );
412+ ? null : Arrays .copyOfRange (valueFromTmp , 1 , valueFromTmp .length );
469413 }
470414 if (isEmptyBytes (value )) {
471415 throw new RuntimeException (String .format ("data not found in store, dbName: %s, key: %s" ,
@@ -489,8 +433,7 @@ private static boolean isEmptyBytes(byte[] b) {
489433 private void deleteSnapshotFlag (String databaseDir ) throws IOException , RocksDBException {
490434 logger .info ("-- delete the info file." );
491435 Files .delete (Paths .get (databaseDir , INFO_FILE_NAME ));
492- DBInterface destBlockIndexDb = DbTool .getDB (databaseDir , BLOCK_INDEX_DB_NAME );
493- if (destBlockIndexDb .get (ByteArray .fromLong (1 )) != null ) {
436+ if (!isLite (databaseDir )) {
494437 DBInterface destCommonDb = DbTool .getDB (databaseDir , COMMON_DB_NAME );
495438 destCommonDb .delete (DB_KEY_NODE_TYPE );
496439 destCommonDb .delete (DB_KEY_LOWEST_BLOCK_NUM );
@@ -500,6 +443,53 @@ private void deleteSnapshotFlag(String databaseDir) throws IOException, RocksDBE
500443
501444 }
502445
446+ private void hasEnoughBlock (String sourceDir ) throws RocksDBException , IOException {
447+ // check latest
448+ long latest = getLatestBlockHeaderNum (sourceDir );
449+ // check first, not 0;
450+ long first = 0 ;
451+ DBInterface sourceBlockIndexDb = DbTool .getDB (sourceDir , BLOCK_INDEX_DB_NAME );
452+ DBIterator iterator = sourceBlockIndexDb .iterator ();
453+ iterator .seekToFirst ();
454+ if (iterator .hasNext ()) {
455+ iterator .next ();
456+ if (iterator .hasNext ()) {
457+ first = Longs .fromByteArray (iterator .getKey ());
458+ }
459+ }
460+
461+ if (latest - first + 1 < RECENT_BLKS ) {
462+ throw new NoSuchElementException (
463+ String .format ("At least %d blocks in block store, actual latestBlock:%d, firstBlock:%d." ,
464+ RECENT_BLKS , latest , first ));
465+ }
466+ }
467+
468+ private boolean isLite (String databaseDir ) throws RocksDBException , IOException {
469+ DBInterface sourceDb = DbTool .getDB (databaseDir , BLOCK_INDEX_DB_NAME );
470+ DBInterface checkpointDb = DbTool .getDB (databaseDir , CHECKPOINT_DB );
471+ byte [] key = ByteArray .fromLong (1 );
472+ byte [] valueFromTmp = checkpointDb .get (Bytes .concat (simpleEncode (BLOCK_INDEX_DB_NAME ), key ));
473+ byte [] value ;
474+ if (isEmptyBytes (valueFromTmp )) {
475+ value = sourceDb .get (key );
476+ } else {
477+ value = valueFromTmp .length == 1
478+ ? null : Arrays .copyOfRange (valueFromTmp , 1 , valueFromTmp .length );
479+ }
480+ return isEmptyBytes (value );
481+ }
482+
483+ @ VisibleForTesting
484+ public static void setRecentBlks (long recentBlks ) {
485+ RECENT_BLKS = recentBlks ;
486+ }
487+
488+ @ VisibleForTesting
489+ public static void reSetRecentBlks () {
490+ RECENT_BLKS = 65536 ;
491+ }
492+
503493 private void run (Args argv ) {
504494 if (StringUtils .isBlank (argv .fnDataPath ) || StringUtils .isBlank (argv .datasetPath )) {
505495 throw new ParameterException ("fnDataPath or datasetPath can't be null" );
0 commit comments