1- const rimraf = require ( 'rimraf' ) ;
21const path = require ( 'path' ) ;
32const fs = require ( 'fs' ) ;
43const logger = require ( '../logger' ) ( 'BLOCKCHAIN' ) ;
54const { Block } = require ( './block' ) ;
65const FileUtil = require ( '../common/file-util' ) ;
76const {
87 CHAINS_DIR ,
9- CHAINS_N2B_DIR_NAME ,
8+ CHAIN_SEGMENT_LENGTH ,
9+ ON_MEMORY_CHAIN_LENGTH ,
1010} = require ( '../common/constants' ) ;
11- const CHAIN_SEGMENT_LENGTH = 20 ;
12- const ON_MEM_CHAIN_LENGTH = 20 ;
11+ const CommonUtil = require ( '../common/common-util' ) ;
1312
1413class Blockchain {
1514 constructor ( basePath ) {
@@ -19,25 +18,28 @@ class Blockchain {
1918 this . initSnapshotBlockNumber = - 1 ;
2019 }
2120
21+ /**
22+ * Initializes the blockchain and returns whether there are block files to load.
23+ */
2224 init ( isFirstNode , latestSnapshotBlockNumber ) {
23- let lastBlockWithoutProposal ;
2425 this . initSnapshotBlockNumber = latestSnapshotBlockNumber ;
25- if ( FileUtil . createBlockchainDir ( this . blockchainPath ) ) {
26+ const wasBlockDirEmpty = FileUtil . createBlockchainDir ( this . blockchainPath ) ;
27+ let isGenesisStart = false ;
28+ if ( wasBlockDirEmpty ) {
2629 if ( isFirstNode ) {
2730 logger . info ( '\n' ) ;
2831 logger . info ( '############################################################' ) ;
2932 logger . info ( '## Starting FIRST-NODE blockchain with a GENESIS block... ##' ) ;
3033 logger . info ( '############################################################' ) ;
3134 logger . info ( '\n' ) ;
32- this . chain . push ( Block . genesis ( ) ) ;
33- this . writeChain ( ) ;
35+ this . writeBlock ( Block . genesis ( ) ) ;
36+ isGenesisStart = true ;
3437 } else {
3538 logger . info ( '\n' ) ;
3639 logger . info ( '#############################################################' ) ;
3740 logger . info ( '## Starting NON-FIRST-NODE blockchain with EMPTY blocks... ##' ) ;
3841 logger . info ( '#############################################################' ) ;
3942 logger . info ( '\n' ) ;
40- this . writeChain ( ) ;
4143 }
4244 } else {
4345 if ( isFirstNode ) {
@@ -53,24 +55,11 @@ class Blockchain {
5355 logger . info ( '################################################################' ) ;
5456 logger . info ( '\n' ) ;
5557 }
56- const newChain = this . loadChain ( latestSnapshotBlockNumber ) ;
57- if ( newChain ) {
58- // NOTE(minsulee2): Deal with the case the only genesis block was generated.
59- if ( newChain . length > 1 ) {
60- lastBlockWithoutProposal = newChain . pop ( ) ;
61- const lastBlockPath = FileUtil . getBlockPath (
62- this . blockchainPath , lastBlockWithoutProposal . number ) ;
63- fs . unlinkSync ( lastBlockPath ) ;
64- }
65- this . chain = newChain ;
66- }
6758 }
68- if ( ! this . getBlockByNumber ( 0 ) ) {
69- const genesisBlock = Block . genesis ( ) ;
70- FileUtil . writeBlock ( this . blockchainPath , genesisBlock ) ;
71- FileUtil . writeHashToNumber ( this . blockchainPath , genesisBlock . hash , genesisBlock . number ) ;
72- }
73- return lastBlockWithoutProposal ;
59+ return {
60+ wasBlockDirEmpty,
61+ isGenesisStart,
62+ } ;
7463 }
7564
7665 /**
@@ -83,7 +72,7 @@ class Blockchain {
8372 getBlockByHash ( hash ) {
8473 if ( ! hash ) return null ;
8574 const blockPath = FileUtil . getBlockPath ( this . blockchainPath ,
86- FileUtil . readHashToNumber ( this . blockchainPath , hash ) ) ;
75+ FileUtil . readH2nFile ( this . blockchainPath , hash ) ) ;
8776 if ( ! blockPath ) {
8877 return this . chain . find ( ( block ) => block . hash === hash ) ;
8978 } else {
@@ -99,9 +88,11 @@ class Blockchain {
9988 */
10089 getBlockByNumber ( number ) {
10190 if ( number === undefined || number === null ) return null ;
102- const blockPath = FileUtil . getBlockPath ( this . blockchainPath , number ) ;
103- if ( ! blockPath || number > this . lastBlockNumber ( ) - ON_MEM_CHAIN_LENGTH ) {
104- return this . chain . find ( ( block ) => block . number === number ) ;
91+ const blockNumber = CommonUtil . toNumberOrNaN ( number ) ;
92+ if ( ! CommonUtil . isNumber ( blockNumber ) ) return null ;
93+ const blockPath = FileUtil . getBlockPath ( this . blockchainPath , blockNumber ) ;
94+ if ( ! blockPath || blockNumber > this . lastBlockNumber ( ) - ON_MEMORY_CHAIN_LENGTH ) {
95+ return this . chain . find ( ( block ) => block . number === blockNumber ) ;
10596 } else {
10697 return Block . parse ( FileUtil . readCompressedJson ( blockPath ) ) ;
10798 }
@@ -141,6 +132,13 @@ class Blockchain {
141132 return lastBlock . timestamp ;
142133 }
143134
135+ addBlockToChain ( block ) {
136+ const LOG_HEADER = 'addBlockToChain' ;
137+
138+ this . chain . push ( block ) ;
139+ logger . info ( `[${ LOG_HEADER } ] Successfully added block ${ block . number } to chain.` ) ;
140+ }
141+
144142 addNewBlockToChain ( newBlock ) {
145143 const LOG_HEADER = 'addNewBlockToChain' ;
146144
@@ -155,56 +153,66 @@ class Blockchain {
155153 if ( ! ( newBlock instanceof Block ) ) {
156154 newBlock = Block . parse ( newBlock ) ;
157155 }
158- this . chain . push ( newBlock ) ;
159- this . writeChain ( ) ;
160- // Keep up to latest ON_MEM_CHAIN_LENGTH blocks
161- while ( this . chain . length > ON_MEM_CHAIN_LENGTH ) {
156+ this . addBlockToChain ( newBlock ) ;
157+ this . writeBlock ( newBlock ) ;
158+ // Keep up to latest ON_MEMORY_CHAIN_LENGTH blocks
159+ while ( this . chain . length > ON_MEMORY_CHAIN_LENGTH ) {
162160 this . chain . shift ( ) ;
163161 }
164162 return true ;
165163 }
166164
167- static isValidChain ( chain , latestSnapshotBlockNumber ) {
168- if ( ! chain . length ) {
169- return true ;
170- }
171- const firstBlock = Block . parse ( chain [ 0 ] ) ;
172- if ( ! firstBlock ) {
165+ static validateBlock ( block , prevBlockNumber = null , prevBlockHash = null ) {
166+ const LOG_HEADER = 'validateBlock' ;
167+
168+ if ( prevBlockNumber != null && block . number !== prevBlockNumber + 1 ) {
169+ logger . error ( `Invalid block number (expected: ${ prevBlockNumber } ) of block: ${ block . number } ` ) ;
173170 return false ;
174171 }
175- if ( latestSnapshotBlockNumber > 0 && latestSnapshotBlockNumber + 1 !== firstBlock . number ) {
176- logger . error ( `Missing blocks between ${ latestSnapshotBlockNumber + 1 } and ${ firstBlock . number } ` ) ;
172+ if ( prevBlockHash !== null && block . last_hash !== prevBlockHash ) {
173+ logger . error (
174+ `Invalid block last_hash (expected: ${ prevBlockHash } ) of block: ${ block . last_hash } ` ) ;
177175 return false ;
178176 }
179- if ( firstBlock . number === 0 && firstBlock . hash !== Block . genesis ( ) . hash ) {
180- logger . error ( `Invalid genesis block: ${ firstBlock } \n ${ Block . genesis ( ) } ` ) ;
177+ if ( ! Block . validateHashes ( block ) ) {
178+ logger . error ( `Invalid block hashes of block: ${ block . number } ` ) ;
181179 return false ;
182180 }
183- return Blockchain . isValidChainSegment ( chain ) ;
181+ logger . info ( `[${ LOG_HEADER } ] Successfully validated block: ${ block . number } / ${ block . epoch } ` ) ;
182+ return true ;
184183 }
185184
186- static isValidChainSegment ( chainSegment ) {
187- if ( chainSegment . length ) {
188- if ( ! Block . validateHashes ( chainSegment [ 0 ] ) ) {
185+
186+ static validateChainSegment ( chainSegment ) {
187+ let prevBlockNumber ;
188+ let prevBlockHash ;
189+ if ( chainSegment . length > 0 ) {
190+ const block = chainSegment [ 0 ] ;
191+ if ( ! Blockchain . validateBlock ( block ) ) {
189192 return false ;
190193 }
194+ prevBlockNumber = block . number ;
195+ prevBlockHash = block . hash ;
191196 }
192197 for ( let i = 1 ; i < chainSegment . length ; i ++ ) {
193198 const block = chainSegment [ i ] ;
194- const lastBlock = Block . parse ( chainSegment [ i - 1 ] ) ;
195- if ( block . last_hash !== lastBlock . hash || ! Block . validateHashes ( block ) ) {
199+ if ( ! Blockchain . validateBlock ( block , prevBlockNumber , prevBlockHash ) ) {
196200 return false ;
197201 }
202+ prevBlockNumber = block . number ;
203+ prevBlockHash = block . hash ;
198204 }
199205 return true ;
200206 }
201207
202- writeChain ( ) {
203- for ( let i = 0 ; i < this . chain . length ; i ++ ) {
204- const block = this . chain [ i ] ;
205- FileUtil . writeBlock ( this . blockchainPath , block ) ;
206- FileUtil . writeHashToNumber ( this . blockchainPath , block . hash , block . number ) ;
207- }
208+ writeBlock ( block ) {
209+ FileUtil . writeBlockFile ( this . blockchainPath , block ) ;
210+ FileUtil . writeH2nFile ( this . blockchainPath , block . hash , block . number ) ;
211+ }
212+
213+ deleteBlock ( block ) {
214+ FileUtil . deleteBlockFile ( this . blockchainPath , block . number ) ;
215+ FileUtil . deleteH2nFile ( this . blockchainPath , block . hash ) ;
208216 }
209217
210218 getValidBlocksInChainSegment ( chainSegment ) {
@@ -231,7 +239,7 @@ class Blockchain {
231239 return validBlocks ;
232240 }
233241 }
234- if ( ! Blockchain . isValidChainSegment ( chainSegment ) ) {
242+ if ( ! Blockchain . validateChainSegment ( chainSegment ) ) {
235243 logger . error ( `Invalid chain segment` ) ;
236244 return validBlocks ;
237245 }
@@ -244,38 +252,24 @@ class Blockchain {
244252 return validBlocks ;
245253 }
246254
247- /**
248- * Reads the block files at the chains n2b directory and returns a list of blocks starting from
249- * the latestSnapshotBlockNumber + 1.
250- * @param {Number } latestSnapshotBlockNumber
251- * @returns {list } A list of Blocks
252- */
253- loadChain ( latestSnapshotBlockNumber ) {
254- const chainPath = this . blockchainPath ;
255- const newChain = [ ] ;
256- const numBlockFiles = fs . readdirSync ( path . join ( chainPath , CHAINS_N2B_DIR_NAME ) ) . length ;
257- const blockPaths = FileUtil . getBlockPaths ( chainPath , latestSnapshotBlockNumber + 1 , numBlockFiles ) ;
258-
259- blockPaths . forEach ( ( blockPath ) => {
260- const block = Block . parse ( FileUtil . readCompressedJson ( blockPath ) ) ;
261- newChain . push ( block ) ;
262- } ) ;
255+ getNumBlockFiles ( ) {
256+ return FileUtil . getNumBlockFiles ( this . blockchainPath ) ;
257+ }
263258
264- if ( Blockchain . isValidChain ( newChain , latestSnapshotBlockNumber ) ) {
265- logger . info ( `Valid chain of size ${ newChain . length } ` ) ;
266- return newChain ;
259+ loadBlock ( blockNumber ) {
260+ const blockPath = FileUtil . getBlockPath ( this . blockchainPath , blockNumber ) ;
261+ if ( ! fs . existsSync ( blockPath ) ) {
262+ return null ;
267263 }
268- logger . error ( `Invalid chain` ) ;
269- rimraf . sync ( chainPath + '/*' ) ;
270- return null ;
264+ return Block . parse ( FileUtil . readCompressedJson ( blockPath ) ) ;
271265 }
272266
273267 /**
274268 * Returns a section of the chain up to a maximuim of length CHAIN_SEGMENT_LENGTH, starting from
275- * the `from` block number up till `to` block number.
269+ * the `from` block number (included) up till `to` block number (excluded) .
276270 *
277- * @param {Number } from - The lowest block number to get
278- * @param {Number } to - The highest block number to geet
271+ * @param {Number } from - The lowest block number to get (included)
272+ * @param {Number } to - The highest block number to geet (excluded)
279273 * @return {list } A list of Blocks, up to a maximuim length of CHAIN_SEGMENT_LENGTH
280274 */
281275 getBlockList ( from , to ) {
@@ -298,7 +292,7 @@ class Blockchain {
298292 if ( to - from > CHAIN_SEGMENT_LENGTH ) { // NOTE: To prevent large query.
299293 to = from + CHAIN_SEGMENT_LENGTH ;
300294 }
301- const blockPaths = FileUtil . getBlockPaths ( this . blockchainPath , from , to - from ) ;
295+ const blockPaths = FileUtil . getBlockPathList ( this . blockchainPath , from , to - from ) ;
302296 blockPaths . forEach ( ( blockPath ) => {
303297 blockList . push ( Block . parse ( FileUtil . readCompressedJson ( blockPath ) ) ) ;
304298 } ) ;
0 commit comments