bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
551 lines (520 loc) • 19.7 kB
text/typescript
import { expect } from 'chai';
import logger from '../../../src/logger';
import { BitcoinBlockStorage } from '../../../src/models/block';
import { CoinStorage } from '../../../src/models/coin';
import { TransactionStorage } from '../../../src/models/transaction';
import { SpentHeightIndicators } from '../../../src/types/Coin';
import { TEST_BLOCK } from '../../data/test-block';
import { resetDatabase } from '../../helpers';
import { intAfterHelper, intBeforeHelper } from '../../helpers/integration';
async function insertBlocks() {
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 5,
hash: '528f01c17829622ed6a4af51b3b3f6c062f304fa60e66499c9cbb8622c8407f7',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 6,
hash: '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 7,
hash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 8,
hash: '3420349f63d96f257d56dd970f6b9079af9cf2784c267a13b1ac339d47031fe9',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
}
describe('Block Model', function() {
const suite = this;
this.timeout(30000);
before(intBeforeHelper);
after(() => intAfterHelper(suite));
beforeEach(async () => {
await resetDatabase();
});
describe('addBlock', () => {
it('should add a block when incoming block references previous block hash', async () => {
await insertBlocks();
await BitcoinBlockStorage.addBlock({
block: TEST_BLOCK,
chain: 'BTC',
network: 'regtest',
initialSyncComplete: false
});
const blocks = await BitcoinBlockStorage.collection
.find({ chain: 'BTC', network: 'regtest' })
.sort({ height: 1 })
.toArray();
expect(blocks.length).to.equal(5);
const ownBlock = blocks[4];
expect(ownBlock.chain).to.equal('BTC');
expect(ownBlock.hash).to.equal('64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929');
expect(ownBlock.network).to.equal('regtest');
expect(ownBlock.bits).to.equal(545259519);
expect(ownBlock.height).to.equal(9);
expect(ownBlock.merkleRoot).to.equal('08e23107e8449f02568d37d37aa76e840e55bbb5f100ed8ad257af303db88c08');
expect(ownBlock.nonce).to.equal(2);
expect(ownBlock.previousBlockHash).to.equal('3420349f63d96f257d56dd970f6b9079af9cf2784c267a13b1ac339d47031fe9');
expect(ownBlock.reward).to.equal(0.09765625);
expect(ownBlock.size).to.equal(264);
expect(ownBlock.version).to.equal(536870912);
// TODO: assertion for block times
expect(ownBlock.transactionCount).to.equal(1);
expect(ownBlock.processed).to.equal(true);
logger.info('new block was successfully added with hash: %o', ownBlock.hash);
const transaction = await TransactionStorage.collection
.find({
chain: 'BTC',
network: 'regtest',
blockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929'
})
.toArray();
expect(transaction.length).to.equal(1);
expect(transaction[0].chain).to.equal('BTC');
expect(transaction[0].network).to.equal('regtest');
expect(transaction[0].txid).to.equal('08e23107e8449f02568d37d37aa76e840e55bbb5f100ed8ad257af303db88c08');
expect(transaction[0].blockHash).to.equal('64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929');
expect(transaction[0].blockHeight).to.equal(9);
expect(transaction[0].coinbase).to.equal(true);
expect(transaction[0].locktime).to.equal(0);
expect(transaction[0].size).to.equal(0);
// TODO: assertion for block times
expect(transaction[0].wallets.length).to.equal(0);
logger.info(`tx: ${transaction[0].txid} was successfully stored in the TX model`);
});
});
describe('handleReorg', () => {
it("should not reorg if the incoming block's prevHash matches the block hash of the current highest block", async () => {
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 1335,
hash: '528f01c17829622ed6a4af51b3b3f6c062f304fa60e66499c9cbb8622c8407f7',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 1336,
hash: '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 1337,
hash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
await BitcoinBlockStorage.handleReorg({
header: {
prevHash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
hash: '12c719927ce18f9a61d7c5a7af08d3110cacfa43671aa700956c3c05ed38bdaa',
time: 1526326785,
version: 536870912,
merkleRoot: '8c29860888b915715878b21ce14707a17b43f6c51dfb62a1e736e35bc5d8093f',
bits: parseInt('207fffff', 16),
nonce: 3
},
chain: 'BTC',
network: 'regtest'
});
const result = await BitcoinBlockStorage.collection.find({ chain: 'BTC', network: 'regtest' }).toArray();
expect(result.length).to.equal(3);
});
it('should not reorg if localTip height is zero', async () => {
await BitcoinBlockStorage.handleReorg({
header: {
prevHash: '12c719927ce18f9a61d7c5a7af08d3110cacfa43671aa700956c3c05ed38bdaa',
hash: '4c6872bf45ecab2fb8b38c8b8f50fc4a8309c6171d28d479b8226afcb1a99920',
time: 1526326785,
version: 536870912,
merkleRoot: '8c29860888b915715878b21ce14707a17b43f6c51dfb62a1e736e35bc5d8093f',
bits: parseInt('207fffff', 16),
nonce: 3
},
chain: 'BTC',
network: 'regtest'
});
const result = await BitcoinBlockStorage.collection.find({ chain: 'BTC', network: 'regtest' }).toArray();
expect(result.length).to.equal(0);
});
it('should successfully handle reorg', async () => {
// setting the Block model
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 5,
hash: '528f01c17829622ed6a4af51b3b3f6c062f304fa60e66499c9cbb8622c8407f7',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 6,
hash: '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '528f01c17829622ed6a4af51b3b3f6c062f304fa60e66499c9cbb8622c8407f7',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
await BitcoinBlockStorage.collection.insertOne({
chain: 'BTC',
network: 'regtest',
height: 7,
hash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
version: 100,
merkleRoot: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
time: new Date(1526326784),
timeNormalized: new Date(1526326784),
transactionCount: 1,
reward: 50,
nonce: 3,
previousBlockHash: '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86',
nextBlockHash: '',
size: 264,
bits: parseInt('207fffff', 16),
processed: true
});
// setting TX model
await TransactionStorage.collection.insertOne({
txid: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919g',
chain: 'BTC',
network: 'regtest',
blockHash: '528f01c17829622ed6a4af51b3b3f6c062f304fa60e66499c9cbb8622c8407f7',
blockTime: new Date(1526326784),
value: 100000,
fee: 100,
coinbase: true,
locktime: 0,
size: 145,
inputCount: 1,
outputCount: 1,
wallets: [],
blockHeight: 5
});
await TransactionStorage.collection.insertOne({
txid: '8c29860888b915715878b21ce14707a17b43f6c51dfb62a1e736e35bc5d8093f',
chain: 'BTC',
network: 'regtest',
blockHash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
blockTime: new Date(1526326785),
value: 100000,
fee: 100,
coinbase: true,
locktime: 0,
size: 145,
inputCount: 1,
outputCount: 1,
wallets: [],
blockHeight: 6
});
await TransactionStorage.collection.insertOne({
txid: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
chain: 'BTC',
network: 'regtest',
blockHash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
blockTime: new Date(1526326784),
value: 100000,
fee: 100,
coinbase: true,
locktime: 0,
size: 145,
inputCount: 1,
outputCount: 1,
wallets: [],
blockHeight: 7
});
await TransactionStorage.collection.insertOne({
txid: '8a351fa9fc3fcd38066b4bf61a8b5f71f08aa224d7a86165557e6da7ee13a826',
chain: 'BTC',
network: 'regtest',
blockHash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
blockTime: new Date(1526326785),
value: 100000,
fee: 100,
coinbase: true,
locktime: 0,
size: 145,
inputCount: 1,
outputCount: 1,
wallets: [],
blockHeight: 7
});
// setting the Coin model
await CoinStorage.collection.insertOne({
network: 'regtest',
chain: 'BTC',
mintTxid: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919g',
spentTxid: '',
mintIndex: 0,
spentHeight: SpentHeightIndicators.unspent,
mintHeight: 5,
coinbase: true,
script: Buffer.from(''),
wallets: [],
value: 500.0,
address: 'mkjB6LmjiNfJWgH4aP4v1GkFjRcQTfDSfj'
});
await CoinStorage.collection.insertOne({
network: 'regtest',
chain: 'BTC',
mintTxid: 'a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919e',
spentTxid: '',
mintIndex: 0,
spentHeight: SpentHeightIndicators.unspent,
mintHeight: 7,
coinbase: true,
script: Buffer.from(''),
wallets: [],
value: 500.0,
address: 'mkjB6LmjiNfJWgH4aP4v1GkFjRcQTfDSfj'
});
await CoinStorage.collection.insertOne({
network: 'regtest',
chain: 'BTC',
mintTxid: '8a351fa9fc3fcd38066b4bf61a8b5f71f08aa224d7a86165557e6da7ee13a826',
spentTxid: '',
mintIndex: 0,
spentHeight: SpentHeightIndicators.unspent,
mintHeight: 7,
coinbase: true,
script: Buffer.from(''),
wallets: [],
value: 500.0,
address: 'mkjB6LmjiNfJWgH4aP4v1GkFjRcQTfDSfj'
});
await CoinStorage.collection.insertOne({
network: 'regtest',
chain: 'BTC',
mintTxid: '8c29860888b915715878b21ce14707a17b43f6c51dfb62a1e736e35bc5d8093f',
mintIndex: 0,
spentHeight: 8,
mintHeight: 7,
coinbase: true,
script: Buffer.from(''),
wallets: [],
value: 500.0,
address: 'mkjB6LmjiNfJWgH4aP4v1GkFjRcQTfDSfj',
spentTxid: 'eec8570a0c960b19fa6c86c71a06ebda379b86b5fe0be0e64ba83b2e0a3d05a3'
});
await BitcoinBlockStorage.handleReorg({
header: {
prevHash: '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86',
hash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
time: 1526326785,
version: 536870912,
merkleRoot: '8c29860888b915715878b21ce14707a17b43f6c51dfb62a1e736e35bc5d8093f',
bits: parseInt('207fffff', 16),
nonce: 3
},
chain: 'BTC',
network: 'regtest'
});
// check for removed block after Reorg in db
const blocks = await BitcoinBlockStorage.collection
.find({
chain: 'BTC',
network: 'regtest'
})
.toArray();
expect(blocks.length).to.equal(1);
const removedBlock = await BitcoinBlockStorage.collection
.find({
chain: 'BTC',
network: 'regtest',
height: {
$gte: 7
}
})
.toArray();
expect(removedBlock.length).to.equal(0);
// check for removed tx after Reorg in db
const transaction = await TransactionStorage.collection
.find({
chain: 'BTC',
network: 'regtest'
})
.toArray();
expect(transaction.length).to.equal(1);
const removedTransaction = await TransactionStorage.collection
.find({
chain: 'BTC',
network: 'regtest',
blockHeight: {
$gte: 7
}
})
.toArray();
expect(removedTransaction.length).to.equal(0);
// check for removed coin after Reorg in db
const coinModel = await CoinStorage.collection
.find({
chain: 'BTC',
network: 'regtest'
})
.toArray();
expect(coinModel.length).to.equal(1);
const removedCoin = await CoinStorage.collection
.find({
chain: 'BTC',
network: 'regtest',
mintHeight: {
$gte: 7
}
})
.toArray();
expect(removedCoin.length).to.equal(0);
// check for unspent coins in the db
const unspentCoins = await CoinStorage.collection
.find({
chain: 'BTC',
network: 'regtest',
spentHeight: SpentHeightIndicators.unspent
})
.toArray();
expect(unspentCoins.length).equal(1);
expect(unspentCoins[0].chain).to.equal('BTC');
expect(unspentCoins[0].network).to.equal('regtest');
expect(unspentCoins[0].mintTxid).to.equal('a2262b524615b6d2f409784ceff898fd46bdde6a584269788c41f26ac4b4919g');
expect(unspentCoins[0].mintIndex).to.equal(0);
expect(unspentCoins[0].mintHeight).to.equal(5);
expect(unspentCoins[0].coinbase).to.equal(true);
expect(unspentCoins[0].value).to.equal(500.0);
expect(unspentCoins[0].address).to.equal('mkjB6LmjiNfJWgH4aP4v1GkFjRcQTfDSfj');
expect(unspentCoins[0].spentTxid).to.equal('');
expect(unspentCoins[0].spentHeight).to.equal(SpentHeightIndicators.unspent);
});
it('should detect a fault in the block hashes', async () => {
const chain = 'BTC';
const network = 'regtest';
await insertBlocks();
const badHash = '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312';
await BitcoinBlockStorage.collection.updateOne(
{ chain, network, hash: badHash },
{ $set: { previousBlockHash: 'aaaaa' } }
);
const invalidChain = await BitcoinBlockStorage.validateLocatorHashes({ chain, network });
expect(invalidChain[1].hash).to.eq(badHash);
});
it('should detect a missing block', async () => {
const chain = 'BTC';
const network = 'regtest';
await insertBlocks();
const badHash = '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312';
const lastKnown = '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86';
await BitcoinBlockStorage.collection.deleteOne({ chain, network, hash: badHash });
const invalidChain = await BitcoinBlockStorage.validateLocatorHashes({ chain, network });
expect(invalidChain[1].hash).to.eq(lastKnown);
});
});
});