@dashevo/dash-spv
Advanced tools
Comparing version 0.23.0-dev.10 to 0.23.0
@@ -33,5 +33,5 @@ const DashUtil = require('@dashevo/dash-util'); | ||
merkleroot: 'e0028eb9648db56b1ac77cf090b99048a8007e2bb64b68f092c03c7f56a662c7', | ||
time: 1390666206, | ||
bits: '1e0ffff0', | ||
nonce: 3861367235, | ||
time: 1417713337, | ||
bits: '207fffff', | ||
nonce: 1096447, | ||
}); | ||
@@ -44,10 +44,17 @@ }, | ||
merkleroot: 'e0028eb9648db56b1ac77cf090b99048a8007e2bb64b68f092c03c7f56a662c7', | ||
time: 1390666206, | ||
bits: '1e0ffff0', | ||
nonce: 3861367235, | ||
time: 1417713337, | ||
bits: '207fffff', | ||
nonce: 1096447, | ||
}); | ||
}, | ||
getLivenetGenesis() { | ||
throw Error('Livenet genesis not yet implemented'); | ||
return utils.normalizeHeader({ | ||
version: 1, | ||
previousblockhash: '0000000000000000000000000000000000000000000000000000000000000000', | ||
merkleroot: 'e0028eb9648db56b1ac77cf090b99048a8007e2bb64b68f092c03c7f56a662c7', | ||
time: 1390095618, | ||
bits: '1e0ffff0', | ||
nonce: 28917698, | ||
}); | ||
}, | ||
}; |
const spvChain = require('./lib/spvchain'); | ||
const merkleProof = require('./lib/merkleproofs'); | ||
const genesis = require('./config/config'); | ||
const SPVError = require('./lib/errors/SPVError'); | ||
@@ -7,2 +9,4 @@ module.exports = { | ||
MerkleProof: merkleProof, | ||
genesis, | ||
SPVError, | ||
}; |
@@ -1,41 +0,44 @@ | ||
const { hasValidTarget } = require('@dashevo/dark-gravity-wave'); | ||
// const { hasValidTarget } = require('@dashevo/dark-gravity-wave'); | ||
const merkleProofs = require('./merkleproofs'); | ||
const utils = require('./utils'); | ||
// const utils = require('./utils'); | ||
const MIN_TIMESTAMP_HEADERS = 11; | ||
const MIN_DGW_HEADERS = 24; | ||
// const MIN_TIMESTAMP_HEADERS = 11; | ||
// const MIN_DGW_HEADERS = 24; | ||
function getMedianTimestamp(headers) { | ||
const timestamps = headers.map((h) => h.time); | ||
const median = (arr) => { | ||
const mid = Math.floor(arr.length / 2); | ||
const nums = [...arr].sort((a, b) => a - b); | ||
return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2; | ||
}; | ||
return median(timestamps); | ||
} | ||
// function getMedianTimestamp(headers) { | ||
// const timestamps = headers.map((h) => h.time); | ||
// const median = (arr) => { | ||
// const mid = Math.floor(arr.length / 2); | ||
// const nums = [...arr].sort((a, b) => a - b); | ||
// return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2; | ||
// }; | ||
// return median(timestamps); | ||
// } | ||
// Must be strictly greater than the median time of the previous 11 blocks. | ||
// https://dash-docs.github.io/en/developer-reference#block-headers | ||
function hasGreaterThanMedianTimestamp(newHeader, previousHeaders) { | ||
if (previousHeaders.length < MIN_TIMESTAMP_HEADERS) return true; | ||
const headerNormalised = utils.normalizeHeader(newHeader); | ||
const normalizedLatestHeaders = previousHeaders.slice( | ||
Math.max(previousHeaders.length - MIN_TIMESTAMP_HEADERS, 0), | ||
).map((h) => utils.normalizeHeader(h)); | ||
return getMedianTimestamp(normalizedLatestHeaders) < headerNormalised.time; | ||
} | ||
// function hasGreaterThanMedianTimestamp(newHeader, previousHeaders) { | ||
// if (previousHeaders.length < MIN_TIMESTAMP_HEADERS) return true; | ||
// const headerNormalised = utils.normalizeHeader(newHeader); | ||
// const normalizedLatestHeaders = previousHeaders.slice( | ||
// Math.max(previousHeaders.length - MIN_TIMESTAMP_HEADERS, 0), | ||
// ).map((h) => utils.normalizeHeader(h)); | ||
// return getMedianTimestamp(normalizedLatestHeaders) < headerNormalised.time; | ||
// } | ||
function isValidBlockHeader(newHeader, previousHeaders, network = 'mainnet') { | ||
if (previousHeaders.length > MIN_DGW_HEADERS) { | ||
return newHeader.validProofOfWork() | ||
&& newHeader.validTimestamp() | ||
&& hasGreaterThanMedianTimestamp(newHeader, previousHeaders) | ||
&& hasValidTarget( | ||
utils.getDgwBlock(newHeader), previousHeaders.map((h) => utils.getDgwBlock(h)), network, | ||
); | ||
} | ||
return newHeader.validProofOfWork() | ||
&& newHeader.validTimestamp() | ||
&& hasGreaterThanMedianTimestamp(newHeader, previousHeaders); | ||
function isValidBlockHeader(/* newHeader, previousHeaders, network = 'mainnet' */) { | ||
// TODO: implement consensus checks properly for all networks | ||
return true; | ||
// if (previousHeaders.length > MIN_DGW_HEADERS) { | ||
// return newHeader.validProofOfWork() | ||
// && newHeader.validTimestamp() | ||
// && hasGreaterThanMedianTimestamp(newHeader, previousHeaders) | ||
// && hasValidTarget( | ||
// utils.getDgwBlock(newHeader), previousHeaders.map((h) => utils.getDgwBlock(h)), network, | ||
// ); | ||
// } | ||
// // TODO: eventually this check start failing in dashmate | ||
// return newHeader.validProofOfWork() | ||
// && newHeader.validTimestamp() | ||
// && hasGreaterThanMedianTimestamp(newHeader, previousHeaders); | ||
} | ||
@@ -42,0 +45,0 @@ |
@@ -1,17 +0,34 @@ | ||
const BlockStore = require('./blockstore'); | ||
const config = require('../config/config'); | ||
const Consensus = require('./consensus'); | ||
const utils = require('./utils'); | ||
const SPVError = require('./errors/SPVError'); | ||
const SpvChain = class { | ||
constructor(chainType, confirms = 100, startBlock) { | ||
this.root = null; | ||
this.allBranches = []; | ||
this.orphanBlocks = []; | ||
this.orphanChunks = []; | ||
// TODO: move chainType and confirms to `options` | ||
constructor(chainType, confirms = 100, startBlock, startBlockHeight) { | ||
this.confirmsBeforeFinal = confirms; | ||
this.reset(startBlockHeight); | ||
this.init(chainType, startBlock); | ||
this.store = new BlockStore(); | ||
this.hashesByHeight = new Map([[this.startBlockHeight, this.root.hash]]); | ||
this.heightByHash = new Map([[this.root.hash, this.startBlockHeight]]); | ||
// TODO: legacy - remove this | ||
this.orphanBlocks = []; | ||
} | ||
reset(fromBlockHeight = 0) { | ||
this.root = null; | ||
this.allBranches = [[]]; | ||
this.orphanChunks = []; | ||
this.prunedHeaders = []; | ||
this.hashesByHeight = new Map(); | ||
this.heightByHash = new Map(); | ||
this.orphansHashes = new Set(); | ||
this.startBlockHeight = fromBlockHeight < 0 ? 0 : fromBlockHeight; | ||
} | ||
init(chainType, startBlock) { | ||
@@ -35,2 +52,3 @@ switch (chainType) { | ||
break; | ||
case 'local': | ||
case 'regtest': | ||
@@ -73,10 +91,49 @@ this.network = 'regtest'; | ||
} else { | ||
throw new Error('Unhandled chaintype or startBlock not provided'); | ||
throw new SPVError('Unhandled chaintype or startBlock not provided'); | ||
} | ||
break; | ||
} | ||
this.root.children = []; | ||
// this.root.children = []; | ||
this.setAllBranches(); | ||
} | ||
validate() { | ||
const longestChain = this.getLongestChain(); | ||
if (!longestChain.length) { | ||
throw new SPVError('Empty SPV chain'); | ||
} | ||
const head = longestChain[longestChain.length - 1]; | ||
const tail = this.prunedHeaders[0] || longestChain[0]; | ||
const headHeight = this.heightByHash.get(head.hash); | ||
const tailHeight = this.heightByHash.get(tail.hash); | ||
if (this.orphanChunks.length) { | ||
throw new SPVError('Chain contains orphan chunks'); | ||
} | ||
if (typeof headHeight !== 'number') { | ||
throw new SPVError(`Head header '${head.hash}' height is invalid ${headHeight}`); | ||
} | ||
if (typeof tailHeight !== 'number') { | ||
throw new SPVError(`Tail header '${tail.hash}' height is invalid ${tailHeight}`); | ||
} | ||
if (this.hashesByHeight.get(headHeight) !== head.hash) { | ||
throw new SPVError(`Head header '${head.hash}' height mismatch`); | ||
} | ||
if (this.hashesByHeight.get(tailHeight) !== tail.hash) { | ||
throw new SPVError(`Tail header '${tail.hash}' height mismatch`); | ||
} | ||
const expectedChainLength = headHeight - tailHeight + 1; | ||
const actualChainLength = longestChain.length + this.prunedHeaders.length; | ||
if (expectedChainLength !== actualChainLength) { | ||
throw new SPVError(`Chain length mismatch: expected ${expectedChainLength}, actual ${actualChainLength}`); | ||
} | ||
} | ||
/** @private */ | ||
@@ -86,8 +143,7 @@ checkPruneBlocks() { | ||
while (longestChain.length > this.confirmsBeforeFinal) { | ||
const pruneBlock = longestChain.splice(0, 1)[0]; | ||
// Children discarded as stale branches | ||
delete pruneBlock.orphan; | ||
this.store.put(pruneBlock); | ||
} | ||
longestChain | ||
.splice(0, longestChain.length - this.confirmsBeforeFinal) | ||
.forEach((header) => { | ||
this.prunedHeaders.push(header); | ||
}); | ||
} | ||
@@ -103,3 +159,3 @@ | ||
} | ||
node.children.forEach((c) => { stack.push(c); }); | ||
// node.children.forEach((c) => { stack.push(c); }); | ||
} | ||
@@ -114,9 +170,9 @@ return null; | ||
node.children.forEach((c) => { | ||
this.setAllBranches(c, Array.from(branch)); | ||
}); | ||
// node.children.forEach((c) => { | ||
// this.setAllBranches(c, Array.from(branch)); | ||
// }); | ||
if (node.children.length === 0) { | ||
this.allBranches.push(branch); | ||
} | ||
// if (node.children.length === 0) { | ||
this.allBranches.push(branch); | ||
// } | ||
} | ||
@@ -126,3 +182,14 @@ | ||
appendHeadersToLongestChain(headers) { | ||
const newLongestChain = this.getLongestChain().concat(headers); | ||
const longestChain = this.getLongestChain(); | ||
const lastHeight = this.startBlockHeight | ||
+ longestChain.length + this.prunedHeaders.length; | ||
headers.forEach((header, i) => { | ||
const height = lastHeight + i; | ||
this.hashesByHeight.set(height, header.hash); | ||
this.heightByHash.set(header.hash, height); | ||
}); | ||
const newLongestChain = longestChain.concat(headers); | ||
this.allBranches = []; | ||
@@ -138,34 +205,44 @@ this.allBranches.push(newLongestChain); | ||
/** @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; | ||
isDuplicate(hash) { | ||
return this.heightByHash.has(hash) || this.orphansHashes.has(hash); | ||
} | ||
/** @private */ | ||
orphanReconnect() { | ||
for (let i = 0; i < this.orphanBlocks.length; i += 1) { | ||
const connectionTip = this.findConnection(this.orphanBlocks[i]); | ||
if (connectionTip) { | ||
connectionTip.children.push(this.orphanBlocks[i]); | ||
this.orphanBlocks.splice(i, 1); | ||
} | ||
} | ||
} | ||
// orphanReconnect() { | ||
// for (let i = 0; i < this.orphanBlocks.length; i += 1) { | ||
// const connectionTip = this.findConnection(this.orphanBlocks[i]); | ||
// if (connectionTip) { | ||
// connectionTip.children.push(this.orphanBlocks[i]); | ||
// this.orphanBlocks.splice(i, 1); | ||
// } | ||
// } | ||
// } | ||
/** @private */ | ||
orphanChunksReconnect() { | ||
// TODO: consider optimizing with map of { [chunkHeadHash]: chunkIndex } | ||
// to get rid of sorting and make the whole function of O(n) complexity | ||
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)) { | ||
for (let i = 0; i < this.orphanChunks.length; i += 1) { | ||
const chunk = this.orphanChunks[i]; | ||
const tipHash = this.getTipHash(); | ||
const chunkPrevHash = utils.getCorrectedHash(chunk[0].prevHash); | ||
if (tipHash === chunkPrevHash) { | ||
this.appendHeadersToLongestChain(chunk); | ||
this.orphanChunks.splice(index, 1); | ||
chunk.forEach((header) => { | ||
this.orphansHashes.delete(header.hash); | ||
}); | ||
this.orphanChunks.splice(i, 1); | ||
i -= 1; | ||
} | ||
}); | ||
} | ||
} | ||
/** @private */ | ||
getOrphans() { | ||
return this.orphanBlocks; | ||
} | ||
// TODO: remove | ||
// getOrphans() { | ||
// return this.orphanBlocks; | ||
// } | ||
@@ -178,11 +255,11 @@ /** @private */ | ||
/** @private */ | ||
processValidHeader(header) { | ||
const connection = this.findConnection(header); | ||
if (connection) { | ||
connection.children.push(header); | ||
this.orphanReconnect(); | ||
} else { | ||
this.orphanBlocks.push(header); | ||
} | ||
} | ||
// processValidHeader(header) { | ||
// const connection = this.findConnection(header); | ||
// if (connection) { | ||
// // connection.children.push(header); | ||
// this.orphanReconnect(); | ||
// } else { | ||
// this.orphanBlocks.push(header); | ||
// } | ||
// } | ||
@@ -197,4 +274,5 @@ /** @private | ||
isValid(header, previousHeaders) { | ||
return !!(Consensus.isValidBlockHeader(header, previousHeaders, this.network) | ||
&& !this.isDuplicate(header.hash)); | ||
const validBlockHeader = Consensus.isValidBlockHeader(header, previousHeaders, this.network); | ||
const duplicate = this.isDuplicate(header.hash); | ||
return !!validBlockHeader && !duplicate; | ||
} | ||
@@ -215,9 +293,9 @@ | ||
} | ||
if (!header.children) { | ||
header.children = []; | ||
} | ||
if (!previousHeader.children) { | ||
previousHeader.children = []; | ||
} | ||
previousHeader.children.push(header); | ||
// if (!header.children) { | ||
// header.children = []; | ||
// } | ||
// if (!previousHeader.children) { | ||
// previousHeader.children = []; | ||
// } | ||
// previousHeader.children.push(header); | ||
return true; | ||
@@ -228,8 +306,14 @@ } | ||
/** | ||
* gets the longest chain | ||
* | ||
* @return {Object[]} | ||
* Returns the longest chain of headers | ||
* @param options | ||
* @returns {*} | ||
*/ | ||
getLongestChain() { | ||
return this.allBranches.sort((b1, b2) => b1 < b2)[0]; | ||
getLongestChain(options = { withPruned: false }) { | ||
let longestChain = this.allBranches.sort((b1, b2) => b1 < b2)[0]; | ||
if (options.withPruned) { | ||
longestChain = this.prunedHeaders.concat(longestChain); | ||
} | ||
return longestChain; | ||
} | ||
@@ -243,3 +327,4 @@ | ||
getTipHash() { | ||
return this.getLongestChain().slice(-1)[0].hash; | ||
const tip = this.getTipHeader(); | ||
return tip && tip.hash; | ||
} | ||
@@ -263,10 +348,21 @@ | ||
getHeader(hash) { | ||
return this.store.get(hash) | ||
.then((blockInDB) => { | ||
if (blockInDB) { | ||
return blockInDB; | ||
} | ||
// TODO: perform lookup in pruned headers? | ||
return this.getLongestChain().filter((h) => h.hash === hash)[0]; | ||
} | ||
return this.getLongestChain().filter((h) => h.hash === hash)[0]; | ||
}); | ||
/** | ||
* Gets specified amount of headers in the confirmed chain | ||
* @param n | ||
* @return Object[] | ||
*/ | ||
getLastHeaders(n) { | ||
const longestChain = this.getLongestChain(); | ||
let headers = longestChain.slice(-n); | ||
if (headers.length < n) { | ||
const remaining = n - headers.length; | ||
headers = [...this.prunedHeaders.slice(-remaining), ...headers]; | ||
} | ||
return headers; | ||
} | ||
@@ -282,15 +378,15 @@ | ||
*/ | ||
addHeader(header) { | ||
const headerNormalised = utils.normalizeHeader(header); | ||
// 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; | ||
// } | ||
if (this.isValid(headerNormalised, this.getLongestChain())) { | ||
headerNormalised.children = []; | ||
this.processValidHeader(headerNormalised); | ||
this.setAllBranches(); | ||
this.checkPruneBlocks(); | ||
return true; | ||
} | ||
return false; | ||
} | ||
/** | ||
@@ -302,15 +398,40 @@ * adds an array of valid headers to the longest spv chain. | ||
* @param {Object[]|string[]|buffer[]} headers | ||
* @return {boolean} | ||
* @param {number} batchHeadHeight - height of the first header in the array | ||
* @return {BlockHeader[]} | ||
*/ | ||
addHeaders(headers) { | ||
if (headers.length === 1) { | ||
if (!this.addHeader(headers[0])) { | ||
throw new Error('Some headers are invalid'); | ||
} else { | ||
return true; | ||
} | ||
} | ||
addHeaders(headers, batchHeadHeight = 0) { | ||
let headHeight = batchHeadHeight; | ||
const normalizedHeaders = headers.map((h) => utils.normalizeHeader(h)); | ||
const isOrphan = !SpvChain.isParentChild(normalizedHeaders[0], this.getTipHeader()); | ||
const tip = this.getTipHeader(); | ||
// Handle 1 block intersection of batches | ||
if (tip && tip.hash === normalizedHeaders[0].hash) { | ||
normalizedHeaders.splice(0, 1); | ||
// Patch head height value after splice | ||
headHeight += 1; | ||
} | ||
if (normalizedHeaders.length === 0) { | ||
// The batch already in the chain, do nothing | ||
return []; | ||
} | ||
const firstHeader = normalizedHeaders[0]; | ||
const connectsToTip = tip && SpvChain.isParentChild(firstHeader, tip); | ||
// | ||
// Reorg detection | ||
// Get prev header hash | ||
const prevHash = utils.getCorrectedHash(firstHeader.prevHash); | ||
const prevHeaderHeight = this.heightByHash.get(prevHash); | ||
// Test on initial wallet load | ||
if (prevHeaderHeight && prevHeaderHeight > 0 && prevHeaderHeight !== headHeight - 1) { | ||
console.log('SPVCHAIN: Reorg detected.'); | ||
console.log(`------->: Batch head ${firstHeader.hash} at height ${headHeight} has parent at height ${prevHeaderHeight}`); | ||
} | ||
const isOrphan = tip ? !connectsToTip | ||
: headHeight !== this.startBlockHeight; | ||
const allValid = normalizedHeaders.reduce( | ||
@@ -320,6 +441,9 @@ (acc, header, index, array) => { | ||
if (index !== 0) { | ||
if (!SpvChain.isParentChild(header, array[index - 1]) | ||
|| !this.isValid(header, previousHeaders)) { | ||
throw new Error('Some headers are invalid'); | ||
if (!SpvChain.isParentChild(header, array[index - 1])) { | ||
throw new SPVError(`SPV: Header ${header.hash} is not a child of ${array[index - 1].hash}`); | ||
} | ||
if (!this.isValid(header, previousHeaders)) { | ||
throw new SPVError(`SPV: Header ${header.hash} is invalid`); | ||
} | ||
return acc && true; | ||
@@ -329,3 +453,3 @@ } | ||
if (!this.isValid(header, previousHeaders)) { | ||
throw new Error('Some headers are invalid'); | ||
throw new SPVError('Some headers are invalid'); | ||
} | ||
@@ -335,3 +459,3 @@ return acc && true; | ||
if (!this.isValid(header, this.getLongestChain())) { | ||
throw new Error('Some headers are invalid'); | ||
throw new SPVError('Some headers are invalid'); | ||
} | ||
@@ -342,5 +466,8 @@ return acc && true; | ||
if (!allValid) { | ||
throw new Error('Some headers are invalid'); | ||
throw new SPVError('Some headers are invalid'); | ||
} | ||
if (isOrphan) { | ||
normalizedHeaders.forEach((header) => { | ||
this.orphansHashes.add(header.hash); | ||
}); | ||
this.orphanChunks.push(normalizedHeaders); | ||
@@ -354,3 +481,3 @@ } else { | ||
this.checkPruneBlocks(); | ||
return true; | ||
return normalizedHeaders; | ||
} | ||
@@ -357,0 +484,0 @@ }; |
{ | ||
"name": "@dashevo/dash-spv", | ||
"version": "0.23.0-dev.10", | ||
"version": "0.23.0", | ||
"description": "Repository containing SPV functions used by @dashevo", | ||
@@ -8,2 +8,3 @@ "main": "index.js", | ||
"test": "mocha test/ --no-timeouts --recursive", | ||
"build": "", | ||
"lint": "eslint .", | ||
@@ -22,2 +23,3 @@ "lint:fix": "eslint . --fix" | ||
"devDependencies": { | ||
"chai": "^4.3.4", | ||
"eslint": "^7.32.0", | ||
@@ -24,0 +26,0 @@ "eslint-config-airbnb-base": "^14.2.1", |
const { MerkleBlock, Transaction } = require('@dashevo/dashcore-lib'); | ||
const should = require('should'); | ||
const Blockchain = require('../lib/spvchain'); | ||
@@ -16,7 +18,5 @@ const utils = require('../lib/utils'); | ||
require('should'); | ||
describe('SPV-DASH (forks & re-orgs) deserialized headers', () => { | ||
before(() => { | ||
chain = new Blockchain('devnet'); | ||
chain = new Blockchain('testnet'); | ||
}); | ||
@@ -38,3 +38,3 @@ | ||
it('should still contain a branch of 1 when first header is added', () => { | ||
chain.addHeader(headers[0]); | ||
chain.addHeaders([headers[0]]); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -45,4 +45,4 @@ chain.getLongestChain().length.should.equal(2); | ||
it('should discard adding of duplicate block', () => { | ||
chain.addHeader(headers[0]); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.addHeaders([headers[0]]); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getLongestChain().length.should.equal(2); | ||
@@ -52,4 +52,4 @@ }); | ||
it('should create 1 orphan', () => { | ||
chain.addHeader(headers[2]); | ||
chain.getOrphans().length.should.equal(1); | ||
chain.addHeaders([headers[2]]); | ||
chain.getOrphanChunks().length.should.equal(1); | ||
chain.getLongestChain().length.should.equal(2); | ||
@@ -59,4 +59,4 @@ }); | ||
it('should connect the orphan by adding its parent', () => { | ||
chain.addHeader(headers[1]); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.addHeaders([headers[1]]); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -68,3 +68,3 @@ chain.getLongestChain().length.should.equal(4); | ||
chain.addHeaders(headers.slice(3, 24)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -74,4 +74,5 @@ chain.getLongestChain().length.should.equal(25); | ||
it('not add an invalid header', () => { | ||
chain.addHeader(headers[25]); | ||
// TODO: restore when conesnsus rules are enabled | ||
it.skip('not add an invalid header', () => { | ||
should(() => chain.addHeaders([headers[25]])).throw(); | ||
chain.getLongestChain().length.should.equal(25); | ||
@@ -85,3 +86,3 @@ }); | ||
} catch (e) { | ||
e.message.should.equal('Some headers are invalid'); | ||
e.message.should.equal('SPV: Header 00000ce430de949c85a145b02e33ebbaed3772dc8f3d668f66edc6852c24d002 is not a child of e59b97d3d11b4d4563b557e32c62afbdb6e947e9cbb4915c94073f08f7936d5f'); | ||
done(); | ||
@@ -111,3 +112,3 @@ } | ||
it('should still contain a branch of 1 when first header is added', () => { | ||
chain.addHeader(mainnet[1]); | ||
chain.addHeaders([mainnet[1]]); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -118,4 +119,4 @@ chain.getLongestChain().length.should.equal(2); | ||
it('should discard addding of duplicate block', () => { | ||
chain.addHeader(mainnet[1]); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.addHeaders([mainnet[1]]); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getLongestChain().length.should.equal(2); | ||
@@ -125,4 +126,4 @@ }); | ||
it('should create 1 orphan', () => { | ||
chain.addHeader(mainnet[3]); | ||
chain.getOrphans().length.should.equal(1); | ||
chain.addHeaders([mainnet[3]]); | ||
chain.getOrphanChunks().length.should.equal(1); | ||
chain.getLongestChain().length.should.equal(2); | ||
@@ -132,4 +133,4 @@ }); | ||
it('should connect the orphan by adding its parent', () => { | ||
chain.addHeader(mainnet[2]); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.addHeaders([mainnet[2]]); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -147,3 +148,3 @@ chain.getLongestChain().length.should.equal(4); | ||
chain.addHeaders(testnet.slice(1, 250)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -155,3 +156,3 @@ chain.getLongestChain().length.should.equal(250); | ||
chain.addHeaders(testnet.slice(250, 500)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -164,3 +165,3 @@ chain.getLongestChain().length.should.equal(500); | ||
chain.addHeaders(testnet2.slice(1, 250)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -172,3 +173,3 @@ chain.getLongestChain().length.should.equal(250); | ||
chain.addHeaders(testnet2.slice(250, 500)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -181,3 +182,3 @@ chain.getLongestChain().length.should.equal(500); | ||
chain.addHeaders(testnet3.slice(1, 250)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -189,3 +190,3 @@ chain.getLongestChain().length.should.equal(250); | ||
chain.addHeaders(testnet3.slice(250, 500)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -196,3 +197,3 @@ chain.getLongestChain().length.should.equal(500); | ||
it('should not add an invalid header', () => { | ||
chain.addHeader(testnet[499]); | ||
chain.addHeaders([testnet[499]]); | ||
chain.getLongestChain().length.should.equal(500); | ||
@@ -203,3 +204,3 @@ }); | ||
chain.addHeaders([badRawHeaders[0], badRawHeaders[1]]); | ||
chain.getOrphanChunks().length.should.equal(1); | ||
chain.getOrphanChunks().length.should.equal(2); | ||
chain.getLongestChain().length.should.equal(500); | ||
@@ -226,3 +227,3 @@ }); | ||
chain.addHeaders(testnet.slice(1, 100)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -235,3 +236,2 @@ chain.getOrphanChunks().length.should.equal(0); | ||
chain.addHeaders(testnet.slice(200, 300)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -244,5 +244,4 @@ chain.getOrphanChunks().length.should.equal(1); | ||
chain.addHeaders(testnet.slice(400, 500)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(2); | ||
chain.getAllBranches().length.should.equal(1); | ||
chain.getOrphanChunks().length.should.equal(2); | ||
chain.getLongestChain().length.should.equal(100); | ||
@@ -253,3 +252,2 @@ }); | ||
chain.addHeaders(testnet.slice(100, 200)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -262,3 +260,3 @@ chain.getOrphanChunks().length.should.equal(1); | ||
chain.addHeaders(testnet.slice(300, 400)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -277,3 +275,3 @@ chain.getOrphanChunks().length.should.equal(0); | ||
chain.addHeaders(mainnet.slice(1, 500)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -285,3 +283,3 @@ chain.getLongestChain().length.should.equal(500); | ||
chain.addHeaders(mainnet.slice(500, 1000)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -293,3 +291,3 @@ chain.getLongestChain().length.should.equal(1000); | ||
chain.addHeaders(mainnet.slice(1000, 1500)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -300,3 +298,3 @@ chain.getLongestChain().length.should.equal(1500); | ||
it('should not add an invalid header', () => { | ||
chain.addHeader(mainnet[499]); | ||
should(() => chain.addHeaders([mainnet[499]])).throw(); | ||
chain.getLongestChain().length.should.equal(1500); | ||
@@ -329,3 +327,3 @@ }); | ||
chain.addHeaders(mainnet.slice(1, 100)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getOrphanChunks().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -338,3 +336,2 @@ chain.getOrphanChunks().length.should.equal(0); | ||
chain.addHeaders(mainnet.slice(200, 300)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -347,3 +344,2 @@ chain.getOrphanChunks().length.should.equal(1); | ||
chain.addHeaders(mainnet.slice(400, 500)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -356,3 +352,2 @@ chain.getOrphanChunks().length.should.equal(2); | ||
chain.addHeaders(mainnet.slice(100, 200)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -365,3 +360,2 @@ chain.getOrphanChunks().length.should.equal(1); | ||
chain.addHeaders(mainnet.slice(300, 400)); | ||
chain.getOrphans().length.should.equal(0); | ||
chain.getAllBranches().length.should.equal(1); | ||
@@ -374,3 +368,3 @@ chain.getOrphanChunks().length.should.equal(0); | ||
let genesisHash = null; | ||
describe('Blockstore', () => { | ||
describe.skip('Blockstore', () => { | ||
before(() => { | ||
@@ -377,0 +371,0 @@ chain = new Blockchain('testnet', 10); |
968668
19
6868
6