@@ -69,40 +69,19 @@ const SpvChain = class {
69
69
this . setAllBranches ( ) ;
70
70
}
71
71
72
- getLongestChain ( ) {
73
- return this . allBranches . sort ( ( b1 , b2 ) => b1 < b2 ) [ 0 ] ;
74
- }
75
-
72
+ /** @private */
76
73
checkPruneBlocks ( ) {
77
74
const longestChain = this . getLongestChain ( ) ;
78
75
79
- if ( longestChain . length > this . confirmsBeforeFinal ) {
76
+ while ( longestChain . length > this . confirmsBeforeFinal ) {
80
77
const pruneBlock = longestChain . splice ( 0 , 1 ) [ 0 ] ;
81
78
// Children discarded as stale branches
82
79
delete pruneBlock . orphan ;
83
80
this . store . put ( pruneBlock ) ;
84
81
}
85
82
}
86
83
87
- getTipHash ( ) {
88
- return this . getLongestChain ( ) . slice ( - 1 ) [ 0 ] . hash ;
89
- }
90
-
91
- getTipHeader ( ) {
92
- return this . getLongestChain ( ) . slice ( - 1 ) [ 0 ] ;
93
- }
94
-
95
- getHeader ( hash ) {
96
- return this . store . get ( hash )
97
- . then ( ( blockInDB ) => {
98
- if ( blockInDB ) {
99
- return blockInDB ;
100
- }
101
-
102
- return this . getLongestChain ( ) . filter ( h => h . hash === hash ) [ 0 ] ;
103
- } ) ;
104
- }
105
-
84
+ /** @private */
106
85
findConnection ( newHeader ) {
107
86
const stack = [ this . root ] ;
108
87
while ( stack . length > 0 ) {
@@ -115,6 +94,7 @@ const SpvChain = class {
115
94
return null ;
116
95
}
117
96
97
+ /** @private */
118
98
setAllBranches ( node = this . root , branch = [ ] ) {
119
99
this . allBranches = [ ] ;
120
100
branch . push ( node ) ;
@@ -128,22 +108,26 @@ const SpvChain = class {
128
108
}
129
109
}
130
110
111
+ /** @private */
131
112
appendHeadersToLongestChain ( headers ) {
132
113
const newLongestChain = this . getLongestChain ( ) . concat ( headers ) ;
133
114
this . allBranches = [ ] ;
134
115
this . allBranches . push ( newLongestChain ) ;
135
116
}
136
117
118
+ /** @private */
137
119
getAllBranches ( ) {
138
120
return this . allBranches ;
139
121
}
140
122
123
+ /** @private */
141
124
isDuplicate ( compareHash ) {
142
125
return this . getAllBranches ( ) . map ( branch => branch . map ( node => node . hash ) )
143
126
. concat ( this . orphanBlocks . map ( orphan => orphan . hash ) )
144
127
. filter ( hash => hash === compareHash ) . length > 0 ;
145
128
}
146
129
130
+ /** @private */
147
131
orphanReconnect ( ) {
148
132
for ( let i = 0 ; i < this . orphanBlocks . length ; i += 1 ) {
149
133
const connectionTip = this . findConnection ( this . orphanBlocks [ i ] ) ;
@@ -154,10 +138,28 @@ const SpvChain = class {
154
138
}
155
139
}
156
140
141
+ /** @private */
142
+ orphanChunksReconnect ( ) {
143
+ this . orphanChunks . sort ( ( a , b ) => a [ 0 ] . timestamp - b [ 0 ] . timestamp ) ;
144
+ this . orphanChunks . slice ( ) . forEach ( ( chunk , index ) => {
145
+ if ( this . getTipHash ( ) === utils . getCorrectedHash ( chunk [ 0 ] . prevHash ) ) {
146
+ this . appendHeadersToLongestChain ( chunk ) ;
147
+ this . orphanChunks . splice ( index , 1 ) ;
148
+ }
149
+ } ) ;
150
+ }
151
+
152
+ /** @private */
157
153
getOrphans ( ) {
158
154
return this . orphanBlocks ;
159
155
}
160
156
157
+ /** @private */
158
+ getOrphanChunks ( ) {
159
+ return this . orphanChunks ;
160
+ }
161
+
162
+ /** @private */
161
163
processValidHeader ( header ) {
162
164
const connection = this . findConnection ( header ) ;
163
165
if ( connection ) {
@@ -168,20 +170,27 @@ const SpvChain = class {
168
170
}
169
171
}
170
172
171
- addHeader ( header ) {
172
- const headerNormalised = utils . normalizeHeader ( header ) ;
173
-
174
- if ( this . isValid ( headerNormalised , this . getLongestChain ( ) ) ) {
175
- headerNormalised . children = [ ] ;
176
- this . processValidHeader ( headerNormalised ) ;
177
- this . setAllBranches ( ) ;
178
- this . checkPruneBlocks ( ) ;
179
- return true ;
180
- }
181
- return false ;
173
+ /** @private
174
+ * validates a dashcore.BlockHeader object
175
+ *
176
+ * @param {Object } header
177
+ * @param {Object[] } previousHeaders
178
+ * @return {boolean }
179
+ */
180
+ isValid ( header , previousHeaders ) {
181
+ return ! ! ( Consensus . isValidBlockHeader ( header , previousHeaders , this . network )
182
+ && ! this . isDuplicate ( header . hash ) ) ;
182
183
}
183
184
184
185
/* eslint-disable no-param-reassign */
186
+ /**
187
+ * verifies the parent child connection
188
+ * between two adjacent dashcore.BlockHeader objects
189
+ *
190
+ * @param {Object } header
191
+ * @param {Object } previousHeader
192
+ * @return {boolean }
193
+ */
185
194
static isParentChild ( header , previousHeader ) {
186
195
if ( utils . getCorrectedHash ( header . prevHash ) !== previousHeader . hash ) {
187
196
return false ;
@@ -197,13 +206,87 @@ const SpvChain = class {
197
206
}
198
207
/* eslint-enable no-param-reassign */
199
208
200
- isValid ( header , previousHeaders ) {
201
- return ! ! ( Consensus . isValidBlockHeader ( header , previousHeaders , this . network )
202
- && ! this . isDuplicate ( header . hash ) ) ;
209
+ /**
210
+ * gets the longest chain
211
+ *
212
+ * @return {Object[] }
213
+ */
214
+ getLongestChain ( ) {
215
+ return this . allBranches . sort ( ( b1 , b2 ) => b1 < b2 ) [ 0 ] ;
216
+ }
217
+
218
+ /**
219
+ * gets the block hash of the longest chain tip
220
+ *
221
+ * @return {string } hash
222
+ */
223
+ getTipHash ( ) {
224
+ return this . getLongestChain ( ) . slice ( - 1 ) [ 0 ] . hash ;
225
+ }
226
+
227
+ /**
228
+ * gets the dashcore.BlockHeader object the longest chain tip
229
+ *
230
+ * @return {Object } header
231
+ */
232
+ getTipHeader ( ) {
233
+ return this . getLongestChain ( ) . slice ( - 1 ) [ 0 ] ;
234
+ }
235
+
236
+ /**
237
+ * gets the dashcore.BlockHeader object for a specific block hash
238
+ *
239
+ * @param {string } hash
240
+ * @return {Object } header
241
+ */
242
+ getHeader ( hash ) {
243
+ return this . store . get ( hash )
244
+ . then ( ( blockInDB ) => {
245
+ if ( blockInDB ) {
246
+ return blockInDB ;
247
+ }
248
+
249
+ return this . getLongestChain ( ) . filter ( h => h . hash === hash ) [ 0 ] ;
250
+ } ) ;
251
+ }
252
+
253
+ /**
254
+ * adds a valid header to the tip of the longest spv chain.
255
+ * If it cannot be connected to the tip it gets temporarily
256
+ * added to an orphan array for possible later reconnection
257
+ *
258
+ * @param {Object[]|string[]|buffer[] } header
259
+ * @return {boolean }
260
+ */
261
+ addHeader ( header ) {
262
+ const headerNormalised = utils . normalizeHeader ( header ) ;
263
+
264
+ if ( this . isValid ( headerNormalised , this . getLongestChain ( ) ) ) {
265
+ headerNormalised . children = [ ] ;
266
+ this . processValidHeader ( headerNormalised ) ;
267
+ this . setAllBranches ( ) ;
268
+ this . checkPruneBlocks ( ) ;
269
+ return true ;
270
+ }
271
+ return false ;
203
272
}
204
273
274
+ /**
275
+ * adds an array of valid headers to the longest spv chain.
276
+ * If they cannot be connected to last tip they get temporarily
277
+ * added to an orphan array for possible later reconnection
278
+ *
279
+ * @param {Object[]|string[]|buffer[] } headers
280
+ * @return {boolean }
281
+ */
205
282
addHeaders ( headers ) {
206
- const self = this ;
283
+ if ( headers . length === 1 ) {
284
+ if ( ! this . addHeader ( headers [ 0 ] ) ) {
285
+ throw new Error ( 'Some headers are invalid' ) ;
286
+ } else {
287
+ return true ;
288
+ }
289
+ }
207
290
const normalizedHeaders = headers . map ( h => utils . normalizeHeader ( h ) ) ;
208
291
const isOrphan = ! SpvChain . isParentChild ( normalizedHeaders [ 0 ] , this . getTipHeader ( ) ) ;
209
292
@@ -212,12 +295,18 @@ const SpvChain = class {
212
295
const previousHeaders = normalizedHeaders . slice ( 0 , index ) ;
213
296
if ( index !== 0 ) {
214
297
if ( ! SpvChain . isParentChild ( header , array [ index - 1 ] )
215
- || ! self . isValid ( header , previousHeaders ) ) {
298
+ || ! this . isValid ( header , previousHeaders ) ) {
216
299
throw new Error ( 'Some headers are invalid' ) ;
217
300
}
218
301
return acc && true ;
219
302
}
220
- if ( ! self . isValid ( header , self . getLongestChain ( ) ) ) {
303
+ if ( isOrphan ) {
304
+ if ( ! this . isValid ( header , previousHeaders ) ) {
305
+ throw new Error ( 'Some headers are invalid' ) ;
306
+ }
307
+ return acc && true ;
308
+ }
309
+ if ( ! this . isValid ( header , this . getLongestChain ( ) ) ) {
221
310
throw new Error ( 'Some headers are invalid' ) ;
222
311
}
223
312
return acc && true ;
@@ -227,11 +316,15 @@ const SpvChain = class {
227
316
throw new Error ( 'Some headers are invalid' ) ;
228
317
}
229
318
if ( isOrphan ) {
230
- this . orphanChunks . push ( headers ) ;
319
+ this . orphanChunks . push ( normalizedHeaders ) ;
231
320
} else {
232
- self . appendHeadersToLongestChain ( normalizedHeaders ) ;
321
+ this . appendHeadersToLongestChain ( normalizedHeaders ) ;
322
+ }
323
+ if ( this . orphanChunks . length > 0 ) {
324
+ this . orphanChunksReconnect ( ) ;
233
325
}
234
326
this . checkPruneBlocks ( ) ;
327
+ return true ;
235
328
}
236
329
} ;
237
330
0 commit comments