Skip to content

Commit

Permalink
Add unit tests for isValidFor method
Browse files Browse the repository at this point in the history
  • Loading branch information
2snEM6 committed Nov 8, 2019
1 parent b79eed1 commit cccbf02
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 133 deletions.
Expand Up @@ -91,12 +91,11 @@ class BlockSynchronizationMechanism extends BaseSynchronizer {
/**
* Check if this sync mechanism is valid for the received block
*
* @param {ExtendedBlock} receivedBlock - The blocked received from the network
* @return {Promise.<Boolean|undefined>} - If the mechanism applied to received block
* @throws {Error} - In case want to abort the sync pipeline
*/
// eslint-disable-next-line no-unused-vars
async isValidFor(receivedBlock) {
async isValidFor() {
// 2. Step: Check whether current chain justifies triggering the block synchronization mechanism
const finalizedBlock = await this.storage.entities.Block.getOne({
height_eql: this.bft.finalizedHeight,
Expand Down
Expand Up @@ -64,6 +64,12 @@ describe('block_synchronization_mechanism', () => {
let loggerMock;
let storageMock;

let aBlock;
let requestedBlocks;
let highestCommonBlock;
let blockIdsList;
let blockList;

beforeEach(() => {
loggerMock = {
info: jest.fn(),
Expand Down Expand Up @@ -164,13 +170,132 @@ describe('block_synchronization_mechanism', () => {
});
});

describe('async run()', () => {
let aBlock;
let requestedBlocks;
let highestCommonBlock;
let blockIdsList;
let blockList;
beforeEach(async () => {
aBlock = newBlock({ height: 10, prevotedConfirmedUptoHeight: 0 });
// blocksModule.init will check whether the genesisBlock in storage matches the genesisBlock in
// memory. The following mock fakes this to be true
when(storageMock.entities.Block.begin)
.calledWith('loader:checkMemTables')
.mockResolvedValue({ genesisBlock: genesisBlockDevnet });
when(storageMock.entities.Account.get)
.calledWith({ isDelegate: true }, { limit: null })
.mockResolvedValue([{ publicKey: 'aPublicKey' }]);
// blocksModule.init will load the last block from storage and store it in ._lastBlock variable. The following mock
// simulates the last block in storage. So the storage has 2 blocks, the genesis block + a new one.
const lastBlock = newBlock({ height: genesisBlockDevnet.height + 1 });
when(storageMock.entities.Block.get)
.calledWith({}, { sort: 'height:desc', limit: 1, extended: true })
.mockResolvedValue([lastBlock]);
// Same thing but for BFT module,as it doesn't use extended flag set to true
when(storageMock.entities.Block.get)
.calledWith({}, { sort: 'height:desc', limit: 1 })
.mockResolvedValue([lastBlock]);
// BFT loads blocks from storage and extracts their headers
when(storageMock.entities.Block.get)
.calledWith(
{
height_gte: genesisBlockDevnet.height,
height_lte: lastBlock.height,
},
{ limit: null, sort: 'height:asc' },
)
.mockResolvedValue([genesisBlockDevnet, lastBlock]);

// Simulate finalized height stored in ChainMeta table is 0
when(storageMock.entities.ChainMeta.getKey)
.calledWith('BFT.finalizedHeight')
.mockResolvedValue(0);
jest.spyOn(blockSynchronizationMechanism, '_requestAndValidateLastBlock');
jest.spyOn(blockSynchronizationMechanism, '_revertToLastCommonBlock');
jest.spyOn(
blockSynchronizationMechanism,
'_requestAndApplyBlocksToCurrentChain',
);

when(channelMock.invoke)
.calledWith('network:getPeers', {
state: PEER_STATE_CONNECTED,
})
.mockResolvedValue(peersList.connectedPeers);

await blocksModule.init();
await bftModule.init();

// Used in getHighestCommonBlock network action payload
const blockHeightsList = computeBlockHeightsList(
bftModule.finalizedHeight,
constants.ACTIVE_DELEGATES,
10,
slots.calcRound(blocksModule.lastBlock.height),
);

blockList = [genesisBlockDevnet];
blockIdsList = [blockList[0].id];

highestCommonBlock = genesisBlockDevnet;
requestedBlocks = [
...new Array(10)
.fill(0)
.map((_, index) =>
newBlock({ height: highestCommonBlock.height + 1 + index }),
),
aBlock,
];

for (const expectedPeer of peersList.expectedSelection) {
const peerId = `${expectedPeer.ip}:${expectedPeer.wsPort}`;
when(channelMock.invoke)
.calledWith('network:requestFromPeer', {
procedure: 'getHighestCommonBlock',
peerId,
data: {
ids: blockIdsList,
},
})
.mockResolvedValue({
data: highestCommonBlock,
});

when(channelMock.invoke)
.calledWith('network:requestFromPeer', {
procedure: 'getLastBlock',
peerId,
})
.mockResolvedValue({
data: aBlock,
});
when(channelMock.invoke)
.calledWith('network:requestFromPeer', {
procedure: 'getBlocksFromId',
peerId,
data: {
blockId: highestCommonBlock.id,
},
})
.mockResolvedValue({ data: requestedBlocks });
}

when(storageMock.entities.Block.get)
.calledWith(
{
height_in: blockHeightsList,
},
{
sort: 'height:asc',
},
)
.mockResolvedValueOnce(blockList);

when(processorModule.deleteLastBlock)
.calledWith({
saveTempBlock: true,
})
.mockResolvedValueOnce(genesisBlockDevnet);

blocksModule._lastBlock = requestedBlocks[requestedBlocks.length - 1];
});

describe('async run()', () => {
const expectApplyPenaltyAndRestartIsCalled = (receivedBlock, reason) => {
expect(loggerMock.info).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -197,131 +322,6 @@ describe('block_synchronization_mechanism', () => {
});
};

beforeEach(async () => {
aBlock = newBlock({ height: 10, prevotedConfirmedUptoHeight: 0 });
// blocksModule.init will check whether the genesisBlock in storage matches the genesisBlock in
// memory. The following mock fakes this to be true
when(storageMock.entities.Block.begin)
.calledWith('loader:checkMemTables')
.mockResolvedValue({ genesisBlock: genesisBlockDevnet });
when(storageMock.entities.Account.get)
.calledWith({ isDelegate: true }, { limit: null })
.mockResolvedValue([{ publicKey: 'aPublicKey' }]);
// blocksModule.init will load the last block from storage and store it in ._lastBlock variable. The following mock
// simulates the last block in storage. So the storage has 2 blocks, the genesis block + a new one.
const lastBlock = newBlock({ height: genesisBlockDevnet.height + 1 });
when(storageMock.entities.Block.get)
.calledWith({}, { sort: 'height:desc', limit: 1, extended: true })
.mockResolvedValue([lastBlock]);
// Same thing but for BFT module,as it doesn't use extended flag set to true
when(storageMock.entities.Block.get)
.calledWith({}, { sort: 'height:desc', limit: 1 })
.mockResolvedValue([lastBlock]);
// BFT loads blocks from storage and extracts their headers
when(storageMock.entities.Block.get)
.calledWith(
{
height_gte: genesisBlockDevnet.height,
height_lte: lastBlock.height,
},
{ limit: null, sort: 'height:asc' },
)
.mockResolvedValue([genesisBlockDevnet, lastBlock]);

// Simulate finalized height stored in ChainMeta table is 0
when(storageMock.entities.ChainMeta.getKey)
.calledWith('BFT.finalizedHeight')
.mockResolvedValue(0);
jest.spyOn(blockSynchronizationMechanism, '_requestAndValidateLastBlock');
jest.spyOn(blockSynchronizationMechanism, '_revertToLastCommonBlock');
jest.spyOn(
blockSynchronizationMechanism,
'_requestAndApplyBlocksToCurrentChain',
);

when(channelMock.invoke)
.calledWith('network:getPeers', {
state: PEER_STATE_CONNECTED,
})
.mockResolvedValue(peersList.connectedPeers);

await blocksModule.init();
await bftModule.init();

// Used in getHighestCommonBlock network action payload
const blockHeightsList = computeBlockHeightsList(
bftModule.finalizedHeight,
constants.ACTIVE_DELEGATES,
10,
slots.calcRound(blocksModule.lastBlock.height),
);

blockList = [genesisBlockDevnet];
blockIdsList = [blockList[0].id];

highestCommonBlock = genesisBlockDevnet;
requestedBlocks = [
...new Array(10)
.fill(0)
.map((_, index) =>
newBlock({ height: highestCommonBlock.height + 1 + index }),
),
aBlock,
];

for (const expectedPeer of peersList.expectedSelection) {
const peerId = `${expectedPeer.ip}:${expectedPeer.wsPort}`;
when(channelMock.invoke)
.calledWith('network:requestFromPeer', {
procedure: 'getHighestCommonBlock',
peerId,
data: {
ids: blockIdsList,
},
})
.mockResolvedValue({
data: highestCommonBlock,
});

when(channelMock.invoke)
.calledWith('network:requestFromPeer', {
procedure: 'getLastBlock',
peerId,
})
.mockResolvedValue({
data: aBlock,
});
when(channelMock.invoke)
.calledWith('network:requestFromPeer', {
procedure: 'getBlocksFromId',
peerId,
data: {
blockId: highestCommonBlock.id,
},
})
.mockResolvedValue({ data: requestedBlocks });
}

when(storageMock.entities.Block.get)
.calledWith(
{
height_in: blockHeightsList,
},
{
sort: 'height:asc',
},
)
.mockResolvedValueOnce(blockList);

when(processorModule.deleteLastBlock)
.calledWith({
saveTempBlock: true,
})
.mockResolvedValueOnce(genesisBlockDevnet);

blocksModule._lastBlock = requestedBlocks[requestedBlocks.length - 1];
});

afterEach(() => {
jest.clearAllMocks();
// Independently of the correct execution of the mechanisms, `active` property should be always
Expand Down Expand Up @@ -1036,4 +1036,30 @@ describe('block_synchronization_mechanism', () => {
});
});
});

describe('isValidFor', () => {
it('should return true if the difference in block slots between the current block slot and the finalized block slot of the system is bigger than ACTIVE_DELEGATES*3', async () => {
when(storageMock.entities.Block.getOne)
.calledWith({
height_eql: bftModule.finalizedHeight,
})
.mockResolvedValue(genesisBlockDevnet);

const isValid = await blockSynchronizationMechanism.isValidFor();

expect(isValid).toBeTruthy();
});

it('should return false if the difference in block slots between the current block slot and the finalized block slot of the system is smaller than ACTIVE_DELEGATES*3', async () => {
when(storageMock.entities.Block.getOne)
.calledWith({
height_eql: bftModule.finalizedHeight,
})
.mockResolvedValue({ ...genesisBlockDevnet, timestamp: Date.now() });

const isValid = await blockSynchronizationMechanism.isValidFor();

expect(isValid).toBeFalsy();
});
});
});

0 comments on commit cccbf02

Please sign in to comment.