bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
306 lines (273 loc) • 10.9 kB
text/typescript
import { expect } from 'chai';
import { ObjectID } from 'mongodb';
import * as sinon from 'sinon';
import { MongoBound } from '../../../src/models/base';
import { BitcoinBlockStorage } from '../../../src/models/block';
import { IBtcBlock } from '../../../src/models/block';
import { CoinStorage } from '../../../src/models/coin';
import { TransactionStorage } from '../../../src/models/transaction';
import { ChainStateProvider } from '../../../src/providers/chain-state';
import { Storage } from '../../../src/services/storage';
import { TEST_BLOCK } from '../../data/test-block';
import { mockStorage } from '../../helpers';
import { mockCollection } from '../../helpers/index.js';
import { unitAfterHelper, unitBeforeHelper } from '../../helpers/unit';
describe('Block Model', function() {
before(unitBeforeHelper);
after(unitAfterHelper);
let addBlockParams = {
chain: 'BTC',
network: 'regtest',
block: TEST_BLOCK,
height: 1355,
initialSyncComplete: false,
processed: true
};
describe('addBlock', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
it('should be able to add a block', async () => {
let newBlock = Object.assign({ save: () => Promise.resolve() }, BitcoinBlockStorage, addBlockParams);
mockStorage(newBlock);
sandbox.stub(BitcoinBlockStorage, 'handleReorg').resolves();
sandbox.stub(TransactionStorage, 'batchImport').resolves();
const result = await BitcoinBlockStorage.addBlock(addBlockParams);
expect(result);
});
});
describe('BlockModel find options', () => {
it('should be able to create query options', () => {
const id = new ObjectID();
const { query, options } = Storage.getFindOptions<MongoBound<IBtcBlock>>(BitcoinBlockStorage, {
since: id,
paging: '_id',
limit: 100,
direction: -1
});
expect(options.limit).to.be.eq(100);
expect(query._id).to.be.deep.eq({ $lt: id });
expect(options.sort).to.be.deep.eq({ _id: -1 });
});
it('should default to descending', () => {
const id = new ObjectID();
const { query, options } = Storage.getFindOptions<MongoBound<IBtcBlock>>(BitcoinBlockStorage, {
since: id,
paging: '_id',
limit: 100
});
expect(options.sort).to.be.deep.eq({ _id: -1 });
expect(options.limit).to.be.eq(100);
expect(query._id).to.be.deep.eq({ $lt: id });
});
it('should allow ascending', () => {
const id = new ObjectID();
const { query, options } = Storage.getFindOptions<MongoBound<IBtcBlock>>(BitcoinBlockStorage, {
since: id,
paging: '_id',
limit: 100,
direction: 1
});
expect(options.sort).to.be.deep.eq({ _id: 1 });
expect(options.limit).to.be.eq(100);
expect(query._id).to.be.deep.eq({ $gt: id });
});
});
describe('getLocalTip', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
it('should return the new tip', async () => {
let newBlock = Object.assign({ save: () => Promise.resolve() }, BitcoinBlockStorage, addBlockParams);
mockStorage(newBlock);
const params = { chain: 'BTC', network: 'regtest' };
const result = await ChainStateProvider.getLocalTip(params);
expect(result!.height).to.deep.equal(addBlockParams.height);
expect(result!.chain).to.deep.equal(addBlockParams.chain);
expect(result!.network).to.deep.equal(addBlockParams.network);
});
});
describe('getPoolInfo', () => {
xit('should return pool info given a coinbase string');
});
describe('getLocatorHashes', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
it('should return 65 zeros if there are no processed blocks for the chain and network', async () => {
const params = { chain: 'BTC', network: 'regtest' };
const result = await ChainStateProvider.getLocatorHashes(params);
expect(result).to.deep.equal([Array(65).join('0')]);
});
});
describe('handleReorg', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
it('should return if localTip hash equals the previous hash', async () => {
Object.assign(BitcoinBlockStorage.collection, mockCollection(null));
Object.assign(TransactionStorage.collection, mockCollection(null));
Object.assign(CoinStorage.collection, mockCollection(null));
let blockModelRemoveSpy = BitcoinBlockStorage.collection.deleteMany as sinon.SinonSpy;
let transactionModelRemoveSpy = TransactionStorage.collection.deleteMany as sinon.SinonSpy;
let coinModelRemoveSpy = CoinStorage.collection.deleteMany as sinon.SinonSpy;
let coinModelUpdateSpy = CoinStorage.collection.updateMany as sinon.SinonSpy;
const params = {
header: {
prevHash: '3420349f63d96f257d56dd970f6b9079af9cf2784c267a13b1ac339d47031fe9',
hash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929',
time: 1526756523,
version: 536870912,
merkleRoot: '08e23107e8449f02568d37d37aa76e840e55bbb5f100ed8ad257af303db88c08',
bits: parseInt('207fffff', 16),
nonce: 2
},
chain: 'BTC',
network: 'regtest'
};
await BitcoinBlockStorage.handleReorg(params);
expect(blockModelRemoveSpy.notCalled).to.be.true;
expect(transactionModelRemoveSpy.notCalled).to.be.true;
expect(coinModelRemoveSpy.notCalled).to.be.true;
expect(coinModelUpdateSpy.notCalled).to.be.true;
});
it('should return if localTip height is zero', async () => {
let blockModelRemoveSpy = BitcoinBlockStorage.collection.deleteMany as sinon.SinonSpy;
let transactionModelRemoveSpy = TransactionStorage.collection.deleteMany as sinon.SinonSpy;
let coinModelRemoveSpy = CoinStorage.collection.deleteMany as sinon.SinonSpy;
let coinModelUpdateSpy = CoinStorage.collection.updateMany as sinon.SinonSpy;
let blockMethodParams = {
chain: 'BTC',
network: 'regtest',
block: TEST_BLOCK,
height: 1355
};
let params = Object.assign(BitcoinBlockStorage, blockMethodParams);
await BitcoinBlockStorage.handleReorg(params);
expect(blockModelRemoveSpy.notCalled).to.be.true;
expect(transactionModelRemoveSpy.notCalled).to.be.true;
expect(coinModelRemoveSpy.notCalled).to.be.true;
expect(coinModelUpdateSpy.notCalled).to.be.true;
});
it('should call blockModel deleteMany', async () => {
mockStorage({
height: 1,
previousBlockHash: '3420349f63d96f257d56dd970f6b9079af9cf2784c267a13b1ac339d47031fe9'
});
let blockMethodParams = {
chain: 'BTC',
network: 'regtest',
block: TEST_BLOCK,
height: 1355
};
let params = Object.assign(BitcoinBlockStorage, blockMethodParams);
const removeSpy = BitcoinBlockStorage.collection.deleteMany as sinon.SinonSpy;
await BitcoinBlockStorage.handleReorg(params);
expect(removeSpy.called).to.be.true;
});
it('should call transactionModel deleteMany', async () => {
mockStorage({
height: 1,
previousBlockHash: '3420349f63d96f257d56dd970f6b9079af9cf2784c267a13b1ac339d47031fe9'
});
let blockMethodParams = {
chain: 'BTC',
network: 'regtest',
block: TEST_BLOCK,
height: 1355
};
let params = Object.assign(BitcoinBlockStorage, blockMethodParams);
const removeSpy = TransactionStorage.collection.deleteMany as sinon.SinonSpy;
await BitcoinBlockStorage.handleReorg(params);
expect(removeSpy.called).to.be.true;
});
it('should call coinModel deleteMany', async () => {
mockStorage({
height: 1,
previousBlockHash: '3420349f63d96f257d56dd970f6b9079af9cf2784c267a13b1ac339d47031fe9'
});
let blockMethodParams = {
chain: 'BTC',
network: 'regtest',
block: TEST_BLOCK,
height: 1355
};
let params = Object.assign(BitcoinBlockStorage, blockMethodParams);
const collectionSpy = Storage.db!.collection as sinon.SinonSpy;
const removeSpy = CoinStorage.collection.deleteMany as sinon.SinonSpy;
await BitcoinBlockStorage.handleReorg(params);
expect(collectionSpy.calledOnceWith('coins'));
expect(removeSpy.callCount).to.eq(3);
});
it('should call coinModel update', async () => {
mockStorage({
height: 1,
previousBlockHash: '3420349f63d96f257d56dd970f6b9079af9cf2784c267a13b1ac339d47031fe9'
});
let blockMethodParams = {
chain: 'BTC',
network: 'regtest',
block: TEST_BLOCK,
height: 1355
};
let params = Object.assign(BitcoinBlockStorage, blockMethodParams);
const collectionSpy = Storage.db!.collection as sinon.SinonSpy;
const updateSpy = CoinStorage.collection.updateMany as sinon.SinonSpy;
await BitcoinBlockStorage.handleReorg(params);
expect(collectionSpy.calledOnceWith('coins'));
expect(updateSpy.called).to.be.true;
});
});
describe('_apiTransform', () => {
it('should return the transform object with block values', () => {
const block: IBtcBlock = {
chain: 'BTC',
network: 'mainnet',
height: 1,
hash: 'abcd',
version: 1,
merkleRoot: 'deff',
time: new Date(),
timeNormalized: new Date(),
nonce: 1,
previousBlockHash: 'aabb',
nextBlockHash: 'bbcc',
transactionCount: 1,
size: 255,
bits: 256,
reward: 5000000000,
processed: true
};
const result = BitcoinBlockStorage._apiTransform(block, { object: true });
expect(result.hash).to.be.equal(block.hash);
expect(result.height).to.be.equal(block.height);
expect(result.version).to.be.equal(block.version);
expect(result.size).to.be.equal(block.size);
expect(result.merkleRoot).to.be.equal(block.merkleRoot);
expect(result.time).to.equal(block.time);
expect(result.timeNormalized).to.equal(block.timeNormalized);
expect(result.nonce).to.be.equal(block.nonce);
expect(result.bits).to.be.equal(block.bits);
expect(result.previousBlockHash).to.be.equal(block.previousBlockHash);
expect(result.nextBlockHash).to.be.equal(block.nextBlockHash);
expect(result.transactionCount).to.be.equal(block.transactionCount);
expect(result).to.not.have.property('processed');
});
});
});