Skip to content
This repository was archived by the owner on Oct 19, 2022. It is now read-only.

Commit e60c36c

Browse files
authored
addHeaders with chunks out of sequence (#22)
* add fn orphanChunksReconnect * add out of order tests * bump version * update package lock * use this instead of self * add jsdoc * more jsdoc * use while loop in checkPruneBlocks * group private & public methods * typo
1 parent 727ad2f commit e60c36c

File tree

4 files changed

+249
-52
lines changed

4 files changed

+249
-52
lines changed

lib/spvchain.js

Lines changed: 136 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -69,40 +69,19 @@ const SpvChain = class {
6969
this.setAllBranches();
7070
}
7171

72-
getLongestChain() {
73-
return this.allBranches.sort((b1, b2) => b1 < b2)[0];
74-
}
75-
72+
/** @private */
7673
checkPruneBlocks() {
7774
const longestChain = this.getLongestChain();
7875

79-
if (longestChain.length > this.confirmsBeforeFinal) {
76+
while (longestChain.length > this.confirmsBeforeFinal) {
8077
const pruneBlock = longestChain.splice(0, 1)[0];
8178
// Children discarded as stale branches
8279
delete pruneBlock.orphan;
8380
this.store.put(pruneBlock);
8481
}
8582
}
8683

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 */
10685
findConnection(newHeader) {
10786
const stack = [this.root];
10887
while (stack.length > 0) {
@@ -115,6 +94,7 @@ const SpvChain = class {
11594
return null;
11695
}
11796

97+
/** @private */
11898
setAllBranches(node = this.root, branch = []) {
11999
this.allBranches = [];
120100
branch.push(node);
@@ -128,22 +108,26 @@ const SpvChain = class {
128108
}
129109
}
130110

111+
/** @private */
131112
appendHeadersToLongestChain(headers) {
132113
const newLongestChain = this.getLongestChain().concat(headers);
133114
this.allBranches = [];
134115
this.allBranches.push(newLongestChain);
135116
}
136117

118+
/** @private */
137119
getAllBranches() {
138120
return this.allBranches;
139121
}
140122

123+
/** @private */
141124
isDuplicate(compareHash) {
142125
return this.getAllBranches().map(branch => branch.map(node => node.hash))
143126
.concat(this.orphanBlocks.map(orphan => orphan.hash))
144127
.filter(hash => hash === compareHash).length > 0;
145128
}
146129

130+
/** @private */
147131
orphanReconnect() {
148132
for (let i = 0; i < this.orphanBlocks.length; i += 1) {
149133
const connectionTip = this.findConnection(this.orphanBlocks[i]);
@@ -154,10 +138,28 @@ const SpvChain = class {
154138
}
155139
}
156140

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 */
157153
getOrphans() {
158154
return this.orphanBlocks;
159155
}
160156

157+
/** @private */
158+
getOrphanChunks() {
159+
return this.orphanChunks;
160+
}
161+
162+
/** @private */
161163
processValidHeader(header) {
162164
const connection = this.findConnection(header);
163165
if (connection) {
@@ -168,20 +170,27 @@ const SpvChain = class {
168170
}
169171
}
170172

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));
182183
}
183184

184185
/* 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+
*/
185194
static isParentChild(header, previousHeader) {
186195
if (utils.getCorrectedHash(header.prevHash) !== previousHeader.hash) {
187196
return false;
@@ -197,13 +206,87 @@ const SpvChain = class {
197206
}
198207
/* eslint-enable no-param-reassign */
199208

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;
203272
}
204273

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+
*/
205282
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+
}
207290
const normalizedHeaders = headers.map(h => utils.normalizeHeader(h));
208291
const isOrphan = !SpvChain.isParentChild(normalizedHeaders[0], this.getTipHeader());
209292

@@ -212,12 +295,18 @@ const SpvChain = class {
212295
const previousHeaders = normalizedHeaders.slice(0, index);
213296
if (index !== 0) {
214297
if (!SpvChain.isParentChild(header, array[index - 1])
215-
|| !self.isValid(header, previousHeaders)) {
298+
|| !this.isValid(header, previousHeaders)) {
216299
throw new Error('Some headers are invalid');
217300
}
218301
return acc && true;
219302
}
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())) {
221310
throw new Error('Some headers are invalid');
222311
}
223312
return acc && true;
@@ -227,11 +316,15 @@ const SpvChain = class {
227316
throw new Error('Some headers are invalid');
228317
}
229318
if (isOrphan) {
230-
this.orphanChunks.push(headers);
319+
this.orphanChunks.push(normalizedHeaders);
231320
} else {
232-
self.appendHeadersToLongestChain(normalizedHeaders);
321+
this.appendHeadersToLongestChain(normalizedHeaders);
322+
}
323+
if (this.orphanChunks.length > 0) {
324+
this.orphanChunksReconnect();
233325
}
234326
this.checkPruneBlocks();
327+
return true;
235328
}
236329
};
237330

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dashevo/dash-spv",
3-
"version": "1.1.4",
3+
"version": "1.1.5",
44
"description": "Temporary repo until spv functions moved into dashcore-lib",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)