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

Commit

Permalink
addHeaders with chunks out of sequence (#22)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Cofresi authored Jun 5, 2019
1 parent 727ad2f commit e60c36c
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 52 deletions.
179 changes: 136 additions & 43 deletions lib/spvchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,40 +69,19 @@ const SpvChain = class {
this.setAllBranches();
}

getLongestChain() {
return this.allBranches.sort((b1, b2) => b1 < b2)[0];
}

/** @private */
checkPruneBlocks() {
const longestChain = this.getLongestChain();

if (longestChain.length > this.confirmsBeforeFinal) {
while (longestChain.length > this.confirmsBeforeFinal) {
const pruneBlock = longestChain.splice(0, 1)[0];
// Children discarded as stale branches
delete pruneBlock.orphan;
this.store.put(pruneBlock);
}
}

getTipHash() {
return this.getLongestChain().slice(-1)[0].hash;
}

getTipHeader() {
return this.getLongestChain().slice(-1)[0];
}

getHeader(hash) {
return this.store.get(hash)
.then((blockInDB) => {
if (blockInDB) {
return blockInDB;
}

return this.getLongestChain().filter(h => h.hash === hash)[0];
});
}

/** @private */
findConnection(newHeader) {
const stack = [this.root];
while (stack.length > 0) {
Expand All @@ -115,6 +94,7 @@ const SpvChain = class {
return null;
}

/** @private */
setAllBranches(node = this.root, branch = []) {
this.allBranches = [];
branch.push(node);
Expand All @@ -128,22 +108,26 @@ const SpvChain = class {
}
}

/** @private */
appendHeadersToLongestChain(headers) {
const newLongestChain = this.getLongestChain().concat(headers);
this.allBranches = [];
this.allBranches.push(newLongestChain);
}

/** @private */
getAllBranches() {
return this.allBranches;
}

/** @private */
isDuplicate(compareHash) {
return this.getAllBranches().map(branch => branch.map(node => node.hash))
.concat(this.orphanBlocks.map(orphan => orphan.hash))
.filter(hash => hash === compareHash).length > 0;
}

/** @private */
orphanReconnect() {
for (let i = 0; i < this.orphanBlocks.length; i += 1) {
const connectionTip = this.findConnection(this.orphanBlocks[i]);
Expand All @@ -154,10 +138,28 @@ const SpvChain = class {
}
}

/** @private */
orphanChunksReconnect() {
this.orphanChunks.sort((a, b) => a[0].timestamp - b[0].timestamp);
this.orphanChunks.slice().forEach((chunk, index) => {
if (this.getTipHash() === utils.getCorrectedHash(chunk[0].prevHash)) {
this.appendHeadersToLongestChain(chunk);
this.orphanChunks.splice(index, 1);
}
});
}

/** @private */
getOrphans() {
return this.orphanBlocks;
}

/** @private */
getOrphanChunks() {
return this.orphanChunks;
}

/** @private */
processValidHeader(header) {
const connection = this.findConnection(header);
if (connection) {
Expand All @@ -168,20 +170,27 @@ const SpvChain = class {
}
}

addHeader(header) {
const headerNormalised = utils.normalizeHeader(header);

if (this.isValid(headerNormalised, this.getLongestChain())) {
headerNormalised.children = [];
this.processValidHeader(headerNormalised);
this.setAllBranches();
this.checkPruneBlocks();
return true;
}
return false;
/** @private
* validates a dashcore.BlockHeader object
*
* @param {Object} header
* @param {Object[]} previousHeaders
* @return {boolean}
*/
isValid(header, previousHeaders) {
return !!(Consensus.isValidBlockHeader(header, previousHeaders, this.network)
&& !this.isDuplicate(header.hash));
}

/* eslint-disable no-param-reassign */
/**
* verifies the parent child connection
* between two adjacent dashcore.BlockHeader objects
*
* @param {Object} header
* @param {Object} previousHeader
* @return {boolean}
*/
static isParentChild(header, previousHeader) {
if (utils.getCorrectedHash(header.prevHash) !== previousHeader.hash) {
return false;
Expand All @@ -197,13 +206,87 @@ const SpvChain = class {
}
/* eslint-enable no-param-reassign */

isValid(header, previousHeaders) {
return !!(Consensus.isValidBlockHeader(header, previousHeaders, this.network)
&& !this.isDuplicate(header.hash));
/**
* gets the longest chain
*
* @return {Object[]}
*/
getLongestChain() {
return this.allBranches.sort((b1, b2) => b1 < b2)[0];
}

/**
* gets the block hash of the longest chain tip
*
* @return {string} hash
*/
getTipHash() {
return this.getLongestChain().slice(-1)[0].hash;
}

/**
* gets the dashcore.BlockHeader object the longest chain tip
*
* @return {Object} header
*/
getTipHeader() {
return this.getLongestChain().slice(-1)[0];
}

/**
* gets the dashcore.BlockHeader object for a specific block hash
*
* @param {string} hash
* @return {Object} header
*/
getHeader(hash) {
return this.store.get(hash)
.then((blockInDB) => {
if (blockInDB) {
return blockInDB;
}

return this.getLongestChain().filter(h => h.hash === hash)[0];
});
}

/**
* adds a valid header to the tip of the longest spv chain.
* If it cannot be connected to the tip it gets temporarily
* added to an orphan array for possible later reconnection
*
* @param {Object[]|string[]|buffer[]} header
* @return {boolean}
*/
addHeader(header) {
const headerNormalised = utils.normalizeHeader(header);

if (this.isValid(headerNormalised, this.getLongestChain())) {
headerNormalised.children = [];
this.processValidHeader(headerNormalised);
this.setAllBranches();
this.checkPruneBlocks();
return true;
}
return false;
}

/**
* adds an array of valid headers to the longest spv chain.
* If they cannot be connected to last tip they get temporarily
* added to an orphan array for possible later reconnection
*
* @param {Object[]|string[]|buffer[]} headers
* @return {boolean}
*/
addHeaders(headers) {
const self = this;
if (headers.length === 1) {
if (!this.addHeader(headers[0])) {
throw new Error('Some headers are invalid');
} else {
return true;
}
}
const normalizedHeaders = headers.map(h => utils.normalizeHeader(h));
const isOrphan = !SpvChain.isParentChild(normalizedHeaders[0], this.getTipHeader());

Expand All @@ -212,12 +295,18 @@ const SpvChain = class {
const previousHeaders = normalizedHeaders.slice(0, index);
if (index !== 0) {
if (!SpvChain.isParentChild(header, array[index - 1])
|| !self.isValid(header, previousHeaders)) {
|| !this.isValid(header, previousHeaders)) {
throw new Error('Some headers are invalid');
}
return acc && true;
}
if (!self.isValid(header, self.getLongestChain())) {
if (isOrphan) {
if (!this.isValid(header, previousHeaders)) {
throw new Error('Some headers are invalid');
}
return acc && true;
}
if (!this.isValid(header, this.getLongestChain())) {
throw new Error('Some headers are invalid');
}
return acc && true;
Expand All @@ -227,11 +316,15 @@ const SpvChain = class {
throw new Error('Some headers are invalid');
}
if (isOrphan) {
this.orphanChunks.push(headers);
this.orphanChunks.push(normalizedHeaders);
} else {
self.appendHeadersToLongestChain(normalizedHeaders);
this.appendHeadersToLongestChain(normalizedHeaders);
}
if (this.orphanChunks.length > 0) {
this.orphanChunksReconnect();
}
this.checkPruneBlocks();
return true;
}
};

Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dashevo/dash-spv",
"version": "1.1.4",
"version": "1.1.5",
"description": "Temporary repo until spv functions moved into dashcore-lib",
"main": "index.js",
"scripts": {
Expand Down
Loading

0 comments on commit e60c36c

Please sign in to comment.