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

Commit

Permalink
Transaction validation (#23)
Browse files Browse the repository at this point in the history
* typos in comment

* add isValidTransaction fn

* refactor validateTxProofs

* add serialized merkleblocks

* add tests

* bump version

* update package lock

* rename isValidTransaction to areValidTransactions

* only allow tx array as param

* using MerkleBlock object as input param

* use correct MerkleBlocks and fix tests

* move merkleBlock creation to before hook

* improve jsdoc

* move merkleBlock creation to beforeEach

* jsdoc: instance name instead of object

* refactor validateTxProofs

* more precise jsdoc param definition

* test with empty transactions array

* jsdoc for validateTxProofs
  • Loading branch information
Cofresi authored Jun 18, 2019
1 parent e60c36c commit 57b06f4
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 44 deletions.
21 changes: 21 additions & 0 deletions lib/consensus.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { hasValidTarget } = require('@dashevo/dark-gravity-wave');
const merkleProofs = require('./merkleproofs');
const utils = require('./utils');

const MIN_TIMESTAMP_HEADERS = 11;
Expand Down Expand Up @@ -39,6 +40,26 @@ function isValidBlockHeader(newHeader, previousHeaders, network = 'mainnet') {
&& hasGreaterThanMedianTimestamp(newHeader, previousHeaders);
}

/**
* validates an array of tx hashes or Transaction instances
* against a merkleblock and the local header chain
* @param {Transaction[]|string[]} transactions
* @param {MerkleBlock} merkleBlock - a MerkleBlock instance
* @param {SpvChain} headerChain - an instance of an SpvChain
* @return {boolean}
*/
async function areValidTransactions(transactions, merkleBlock, headerChain) {
if (!Array.isArray(transactions) || transactions.length <= 0) {
throw new Error('Please check that transactions parameter is a non-empty array');
}
const localHeader = await headerChain.getHeader(merkleBlock.header.hash);
if (!localHeader) {
return false;
}
return merkleProofs.validateTxProofs(merkleBlock, transactions);
}

module.exports = {
isValidBlockHeader,
areValidTransactions,
};
20 changes: 17 additions & 3 deletions lib/merkleproofs.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
const merkleproofs = {
const DashUtil = require('@dashevo/dash-util');

validateTxProofs: (merkleBlock, transactions) => merkleBlock.validMerkleTree()
&& transactions.filter(t => merkleBlock.hasTransaction(t)).length === transactions.length,
const merkleproofs = {
/**
* validates an array of tx hashes or Transaction instances
* against a merkleblock
* @param {MerkleBlock} merkleBlock - a MerkleBlock instance
* @param {Transaction[]|string[]} transactions
* @return {boolean}
*/
validateTxProofs: (merkleBlock, transactions) => {
let txToFilter = transactions.slice();
if (typeof transactions[0] === 'string') {
txToFilter = txToFilter.map(tx => DashUtil.toHash(tx).toString('hex'));
}
return merkleBlock.validMerkleTree
&& txToFilter.filter(tx => merkleBlock.hasTransaction(tx)).length === transactions.length;
},
};

module.exports = merkleproofs;
20 changes: 10 additions & 10 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.5",
"version": "1.1.6",
"description": "Temporary repo until spv functions moved into dashcore-lib",
"main": "index.js",
"scripts": {
Expand Down
32 changes: 9 additions & 23 deletions test/data/merkleproofs.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,15 @@ const merkleJSON = {
],
flags: [219, 63],
},
mnProof: { // Mainnet Block 300100
header: {
hash: '00000000000051b99f3fa12da6997bb54327c7b81509829467b499a7ce03136a',
version: 3,
prevHash: '0000000000154ef1f88653a6757f1182680a4d5831d815010d5fa646ae79ce35',
merkleRoot: 'a0055d45ad9b35e77fb01c59a4feb9976921493d2557a5ac0798b49e82ea1e99',
time: 1436550250,
bits: 454590659,
nonce: 2601264896,
},
numTransactions: 12,
hashes: [
'9d0a368bc9923c6cb966135a4ceda30cc5f259f72c8843ce015056375f8a06ec',
'39e5cd533567ac0a8602bcc4c29e2f01a4abb0fe68ffbc7be6c393db188b72e0',
'cd75b421157eca03eff664bdc165730f91ef2fa52df19ff415ab5acb30045425',
'2ef9795147caaeecee5bc2520704bb372cde06dbd2e871750f31336fd3f02be3',
'2241d3448560f8b1d3a07ea5c31e79eb595632984a20f50944809a61fdd9fe0b',
'45afbfe270014d5593cb065562f1fed726f767fe334d8b3f4379025cfa5be8c5',
'198c03da0ccf871db91fe436e2795908eac5cc7d164232182e9445f7f9db1ab2',
'ed07c181ce5ba7cb66d205bc970f43e1ca11996d611aa8e91e305eb8608c543c',
],
flags: [219, 63],
},
// merkleblock for inserted tx hash
// '7262476912a96b9a6226cfa3a8f231ba3e2b1f75c396e88367e532c79c43c95b' in testnet block 10000
// (bloom filter: '03359be1100000000000000001')
rawMerkleBlock: '000000200e71587f863213690c24b8ad07d0753b38abe6ff1328ef7661689404000000000f1e7ce895614047d5c8d3e7b537b26b0482302301aa104947eb09c55521b316b10b1e5cff7d191c166b25b80500000002a8f781d45a5b12abffcf52bac3b3fc7824cc91ef4f9bb04bc97500a1d98d91305bc9439cc732e56783e896c3751f2b3eba31f2a8a3cf26629a6ba91269476272011d',
// merkleblock for inserted tx hashes
// '7262476912a96b9a6226cfa3a8f231ba3e2b1f75c396e88367e532c79c43c95b'
// '3f3517ee8fa95621fe8abdd81c1e0dfb50e21dd4c5a3c01eee2c47cf664821b6' in testnet block 10000
// (bloom filter: '0797b88e7d21b31f130000000000000001')
rawMerkleBlock2: '000000200e71587f863213690c24b8ad07d0753b38abe6ff1328ef7661689404000000000f1e7ce895614047d5c8d3e7b537b26b0482302301aa104947eb09c55521b316b10b1e5cff7d191c166b25b80500000004acff07e45bfb50dd8e2fb10109f1988423cbcc9423ff07b7acb09715bd1a11e89153210725b8e219b91e433180092226c7b56649843fc51bc012c587f50030a1b6214866cf472cee1ec0a3c5d41de250fb0d1e1cd8bd8afe2156a98fee17353f5bc9439cc732e56783e896c3751f2b3eba31f2a8a3cf26629a6ba9126947627202eb01',
};

module.exports = merkleJSON;
88 changes: 81 additions & 7 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
const dashcore = require('@dashevo/dashcore-lib');
const { MerkleBlock, Transaction } = require('@dashevo/dashcore-lib');
const Blockchain = require('../lib/spvchain');
const utils = require('../lib/utils');
const merkleProofs = require('../lib/merkleproofs');

const consensus = require('../lib/consensus');
const {
testnet, testnet2, testnet3, mainnet, badRawHeaders,
} = require('./data/rawHeaders');
const headers = require('./data/headers');
const merkleData = require('./data/merkleproofs');

let chain = null;
let merkleBlock = null;
let merkleBlock2 = null;

require('should');

Expand Down Expand Up @@ -365,17 +367,89 @@ describe('Blockstore', () => {
});

// TODO:
// Create scenarios where chain splits occur to form competing brances
// Difficult with current chain provided by chainmanager as this is actual hardcoded
// Create scenarios where chain splits occur to form competing branches
// Difficult with current chain provided by chainmanager as this is actually hardcoded
// Dash testnet headers which requires significant CPU power to create forked chains from

describe('MerkleProofs', () => {
it('should validate tx inclusion in merkleblock', () => {
const merkleBlock = new dashcore.MerkleBlock(merkleData.merkleBlock);
const validTx = '45afbfe270014d5593cb065562f1fed726f767fe334d8b3f4379025cfa5be8c5';
merkleBlock = new MerkleBlock(merkleData.merkleBlock);
const validTx = 'c5e85bfa5c0279433f8b4d33fe67f726d7fef1625506cb93554d0170e2bfaf45';
const invalidTx = `${validTx.substring(0, validTx.length - 1)}0`;

merkleProofs.validateTxProofs(merkleBlock, [validTx]).should.equal(true);
merkleProofs.validateTxProofs(merkleBlock, [invalidTx]).should.equal(false);
});
});

describe('Transaction validation', () => {
before(() => {
chain = new Blockchain('testnet', 10000, utils.normalizeHeader(testnet[0]));
chain.addHeaders(testnet.slice(1, 500));
});

beforeEach(() => {
merkleBlock = new MerkleBlock(Buffer.from(merkleData.rawMerkleBlock, 'hex'));
merkleBlock2 = new MerkleBlock(Buffer.from(merkleData.rawMerkleBlock2, 'hex'));
});

it('should throw an error if wrong type is passed', async () => {
const validTx = '7262476912a96b9a6226cfa3a8f231ba3e2b1f75c396e88367e532c79c43c95b';
const invalid = Buffer.from(validTx);
try {
await consensus.areValidTransactions(invalid, merkleBlock, chain);
throw new Error('Transaction validation failed to throw an error');
} catch (e) {
e.message.should.equal('Please check that transactions parameter is a non-empty array');
}
});

it('should throw an error if empty transactions array', async () => {
const transactions = [];
try {
await consensus.areValidTransactions(transactions, merkleBlock, chain);
throw new Error('Transaction validation failed to throw an error');
} catch (e) {
e.message.should.equal('Please check that transactions parameter is a non-empty array');
}
});

it('should not validate an array of raw transactions for a merkleblock that was generated with a filter containing only one of them', async () => {
const validTx = new Transaction('020000000100de7192338db34fe9bb25f34122893d94f3b43bd4c881e37924c8e95a068cc8000000006b483045022100b185b4b86b613e3ffc796db90f95dc88f82561c50ba49fa610d8090f61f38ff002201473466bddee2672ed0dba75b81c07bdade734441e005c8c7fdf12a039ff9312012102bdfedbfe6ea05de8094d18442e08c98ebd695acf489f1bdf68fe1e3aff6f488effffffff010000000000000000016a00000000');
const validTx2 = new Transaction('0200000001ccc68ff58b7b02247f3e05440ab7fc7c8c599453de4a49e35393981890a1e984010000006b483045022100e141365c4916fa09d03aac58f215b926b777a3acec918a6becdbb03d59d28d9f02204edc1ce68d596e28e55f114c67a270302d488707e754af1261fdfc043891651c012102d5b7c0dfb2fd9591a4a98555ce806e17842f401979f2e0cc0689c91d6ca9ef87feffffff025d4c0000000000001976a9146d14b25994e4036d70eeafd4a706640337db5a5e88ac409c0000000000001976a9148b4a9da5a46c7b89b26b649bac8e34e7aa5aa63188acc2260000');
const transactions = [];
transactions.push(validTx);
transactions.push(validTx2);
const result = await consensus.areValidTransactions(transactions, merkleBlock, chain);
result.should.equal(false);
});

it('should validate an array of raw transactions for a merkleblock that was generated with a filter containing both of them', async () => {
const validTx = new Transaction('020000000100de7192338db34fe9bb25f34122893d94f3b43bd4c881e37924c8e95a068cc8000000006b483045022100b185b4b86b613e3ffc796db90f95dc88f82561c50ba49fa610d8090f61f38ff002201473466bddee2672ed0dba75b81c07bdade734441e005c8c7fdf12a039ff9312012102bdfedbfe6ea05de8094d18442e08c98ebd695acf489f1bdf68fe1e3aff6f488effffffff010000000000000000016a00000000');
const validTx2 = new Transaction('0200000001ccc68ff58b7b02247f3e05440ab7fc7c8c599453de4a49e35393981890a1e984010000006b483045022100e141365c4916fa09d03aac58f215b926b777a3acec918a6becdbb03d59d28d9f02204edc1ce68d596e28e55f114c67a270302d488707e754af1261fdfc043891651c012102d5b7c0dfb2fd9591a4a98555ce806e17842f401979f2e0cc0689c91d6ca9ef87feffffff025d4c0000000000001976a9146d14b25994e4036d70eeafd4a706640337db5a5e88ac409c0000000000001976a9148b4a9da5a46c7b89b26b649bac8e34e7aa5aa63188acc2260000');
const transactions = [];
transactions.push(validTx);
transactions.push(validTx2);
const result = await consensus.areValidTransactions(transactions, merkleBlock2, chain);
result.should.equal(true);
});

it('should not validate an array of transactions hashes for a merkleblock that was generated with a filter containing only one of them', async () => {
const validTxHash = '7262476912a96b9a6226cfa3a8f231ba3e2b1f75c396e88367e532c79c43c95b';
const validTxHash2 = '3f3517ee8fa95621fe8abdd81c1e0dfb50e21dd4c5a3c01eee2c47cf664821b6';
const transactions = [];
transactions.push(validTxHash);
transactions.push(validTxHash2);
const result = await consensus.areValidTransactions(transactions, merkleBlock, chain);
result.should.equal(false);
});

it('should validate an array of transactions hashes for a merkleblock that was generated with a filter containing both of them', async () => {
const validTxHash = '7262476912a96b9a6226cfa3a8f231ba3e2b1f75c396e88367e532c79c43c95b';
const validTxHash2 = '3f3517ee8fa95621fe8abdd81c1e0dfb50e21dd4c5a3c01eee2c47cf664821b6';
const transactions = [];
transactions.push(validTxHash);
transactions.push(validTxHash2);
const result = await consensus.areValidTransactions(transactions, merkleBlock2, chain);
result.should.equal(true);
});
});

0 comments on commit 57b06f4

Please sign in to comment.