Skip to content

Commit

Permalink
fix(core-blockchain): pass IBlockData to processBlocks instead of IBl…
Browse files Browse the repository at this point in the history
…ock (#3426)
  • Loading branch information
air1one authored and faustbrian committed Jan 28, 2020
1 parent 627a496 commit 117bb9e
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 53 deletions.
6 changes: 4 additions & 2 deletions __tests__/integration/core-blockchain/blockchain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const addBlocks = async untilHeight => {
const lastHeight = blockchain.getLastHeight();

for (let height = lastHeight + 1; height < untilHeight && height < 155; height++) {
const blockToProcess = Blocks.BlockFactory.fromData(allBlocks[height - 2]);
const blockToProcess = allBlocks[height - 2];
await blockchain.processBlocks([blockToProcess], () => undefined);
}
};
Expand Down Expand Up @@ -218,7 +218,9 @@ describe("Blockchain", () => {
transactions,
};

return Blocks.BlockFactory.make(data, Identities.Keys.fromPassphrase(generatorKeys.secret));
const blockInstance = Blocks.BlockFactory.make(data, Identities.Keys.fromPassphrase(generatorKeys.secret));

return { ...blockInstance.data, transactions: blockInstance.transactions.map(tx => tx.data) };
};

it("should restore vote balances after a rollback", async () => {
Expand Down
76 changes: 48 additions & 28 deletions __tests__/integration/core-transaction-pool/processor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import "jest-extended";

import { Blockchain, Container, State, TransactionPool } from "@arkecosystem/core-interfaces";
import { Handlers } from "@arkecosystem/core-transactions";
import { Blocks, Identities, Interfaces, Managers, Utils } from "@arkecosystem/crypto";
import { Blocks, Crypto, Identities, Interfaces, Managers, Utils } from "@arkecosystem/crypto";
import { generateMnemonic } from "bip39";
import { TransactionFactory } from "../../helpers/transaction-factory";
import { delegates, genesisBlock, wallets, wallets2ndSig } from "../../utils/fixtures/unitnet";
import { delegates, wallets, wallets2ndSig } from "../../utils/fixtures/unitnet";
import { generateWallets } from "../../utils/generators/wallets";
import { setUpFull, tearDownFull } from "./__support__/setup";
// import { Crypto, Enums, Managers } from "@arkecosystem/crypto";
Expand Down Expand Up @@ -736,46 +736,64 @@ describe("Transaction Guard", () => {
describe("Transaction replay shouldn't pass validation", () => {
afterEach(async () => blockchain.removeBlocks(blockchain.getLastHeight() - 1)); // resets to height 1

const addBlock = async transactions => {
let totalAmount = Utils.BigNumber.ZERO;
let totalFee = Utils.BigNumber.ZERO;
const addBlock = async (generatorKeys: any, transactions: Interfaces.ITransactionData[]) => {
const timestamp = () => {
const lastBlock = blockchain.state.getLastBlock();
return Crypto.Slots.getSlotTime(Crypto.Slots.getSlotNumber(lastBlock.data.timestamp) + 1);
};

const transactionData = {
amount: Utils.BigNumber.ZERO,
fee: Utils.BigNumber.ZERO,
ids: [],
};

for (const transaction of transactions) {
totalAmount = totalAmount.plus(transaction.amount);
totalFee = totalFee.plus(transaction.fee);
transactionData.amount = transactionData.amount.plus(transaction.amount);
transactionData.fee = transactionData.fee.plus(transaction.fee);
transactionData.ids.push(Buffer.from(transaction.id, "hex"));
}

// makes blockchain accept a new block with the transactions specified
const block = {
id: "17882607875259085966",
const lastBlock = blockchain.state.getLastBlock();
const data = {
timestamp: timestamp(),
version: 0,
timestamp: 46583330,
height: 2,
reward: Utils.BigNumber.make(0),
previousBlock: genesisBlock.id,
numberOfTransactions: 1,
previousBlock: lastBlock.data.id,
previousBlockHex: lastBlock.data.idHex,
height: lastBlock.data.height + 1,
numberOfTransactions: transactions.length,
totalAmount: transactionData.amount,
totalFee: transactionData.fee,
reward: Utils.BigNumber.ZERO,
payloadLength: 32 * transactions.length,
payloadHash: Crypto.HashAlgorithms.sha256(transactionData.ids).toString("hex"),
transactions,
totalAmount,
totalFee,
payloadLength: 0,
payloadHash: genesisBlock.payloadHash,
generatorPublicKey: delegates[0].publicKey,
blockSignature:
"3045022100e7385c6ea42bd950f7f6ab8c8619cf2f66a41d8f8f185b0bc99af032cb25f30d02200b6210176a6cedfdcbe483167fd91c21d740e0e4011d24d679c601fdd46b0de9",
createdAt: "2019-07-11T16:48:50.550Z",
};
const blockVerified = Blocks.BlockFactory.fromData(block);
blockVerified.verification.verified = true;

await blockchain.processBlocks([blockVerified], () => undefined);
const blockInstance = Blocks.BlockFactory.make(
data,
Identities.Keys.fromPassphrase(generatorKeys.secret),
);

await blockchain.processBlocks(
[
{
...blockInstance.data,
transactions: blockInstance.transactions.map(tx => tx.data),
},
],
() => undefined,
);
};

it("should not validate an already forged transaction", async () => {
const transfers = TransactionFactory.transfer(wallets[1].address, 11)
.withNetwork("unitnet")
.withPassphrase(wallets[0].passphrase)
.create();
await addBlock(transfers);

const forgerKeys = delegates[0];
await addBlock(forgerKeys, transfers);

const result = await processor.validate(transfers);

Expand All @@ -789,7 +807,9 @@ describe("Transaction Guard", () => {
.withNetwork("unitnet")
.withPassphrase(wallets[0].passphrase)
.create();
await addBlock(transfers);

const forgerKeys = delegates[0];
await addBlock(forgerKeys, transfers);

const originalId: string = transfers[0].id;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Wallets } from "@arkecosystem/core-state";
import { Handlers } from "@arkecosystem/core-transactions";
import { Blocks, Identities, Utils } from "@arkecosystem/crypto";
import { generateMnemonic } from "bip39";
import { Blockchain as BlockchainClass } from "../../../packages/core-blockchain/src";
import { BlockProcessor } from "../../../packages/core-blockchain/src/processor";
import { WalletManager } from "../../../packages/core-transaction-pool/src/wallet-manager";
import { TransactionFactory } from "../../helpers/transaction-factory";
import { delegates, genesisBlock, wallets } from "../../utils/fixtures/unitnet";
Expand Down Expand Up @@ -209,8 +211,9 @@ describe("Apply transactions and block rewards to wallets on new block", () => {
};
const blockWithRewardVerified = Blocks.BlockFactory.fromData(blockWithReward);
blockWithRewardVerified.verification.verified = true;
const processor = new BlockProcessor(blockchain as BlockchainClass);

await blockchain.processBlocks([blockWithRewardVerified], () => undefined);
await processor.process(blockWithRewardVerified);

const delegateWallet = poolWalletManager.findByPublicKey(generatorPublicKey);

Expand Down
18 changes: 11 additions & 7 deletions __tests__/unit/core-blockchain/blockchain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,21 @@ describe("Blockchain", () => {
const mockCallback = jest.fn(() => true);
blockchain.state.blockchain = {};

await blockchain.processBlocks([BlockFactory.fromData(blocks2to100[2])], mockCallback);
await blockchain.processBlocks([blocks2to100[2]], mockCallback);
await delay(200);

expect(mockCallback.mock.calls.length).toBe(1);
});

it("should process a valid block already known", async () => {
const mockCallback = jest.fn(() => true);
const lastBlock = blockchain.getLastBlock();
const lastBlock = blockchain.getLastBlock().data;

await blockchain.processBlocks([lastBlock], mockCallback);
await delay(200);

expect(mockCallback.mock.calls.length).toBe(1);
expect(blockchain.getLastBlock()).toEqual(lastBlock);
expect(blockchain.getLastBlock().data).toEqual(lastBlock);
});

it("should process a new block with database saveBlocks failing once", async () => {
Expand All @@ -139,7 +139,7 @@ describe("Blockchain", () => {
database.saveBlocks = jest.fn().mockRejectedValueOnce(new Error("oops"));
jest.spyOn(blockchain, "removeTopBlocks").mockReturnValueOnce(undefined);

await blockchain.processBlocks([BlockFactory.fromData(blocks2to100[2])], mockCallback);
await blockchain.processBlocks([blocks2to100[2]], mockCallback);
await delay(200);

expect(mockCallback.mock.calls.length).toBe(1);
Expand All @@ -151,7 +151,7 @@ describe("Blockchain", () => {
jest.spyOn(database, "saveBlocks").mockRejectedValueOnce(new Error("oops saveBlocks"));
jest.spyOn(blockchain, "removeTopBlocks").mockReturnValueOnce(undefined);

await blockchain.processBlocks([BlockFactory.fromData(blocks2to100[2])], mockCallback);
await blockchain.processBlocks([blocks2to100[2]], mockCallback);
await delay(200);

expect(mockCallback.mock.calls.length).toBe(1);
Expand All @@ -162,8 +162,10 @@ describe("Blockchain", () => {
jest.spyOn(Utils, "isBlockChained").mockReturnValueOnce(true);

const mockCallback = jest.fn(() => true);
const lastBlock = blockchain.getLastBlock();
lastBlock.data.timestamp = Crypto.Slots.getSlotNumber() * 8000;
const lastBlock = blockchain.getLastBlock().data;
const spyGetSlotNumber = jest
.spyOn(Crypto.Slots, "getSlotNumber")
.mockReturnValue(Math.floor(lastBlock.timestamp / 8000));

const broadcastBlock = jest.spyOn(getMonitor, "broadcastBlock");

Expand All @@ -172,6 +174,8 @@ describe("Blockchain", () => {

expect(mockCallback.mock.calls.length).toBe(1);
expect(broadcastBlock).toHaveBeenCalled();

spyGetSlotNumber.mockRestore();
});
});

Expand Down
25 changes: 14 additions & 11 deletions packages/core-blockchain/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class Blockchain implements blockchain.IBlockchain {

this.queue = async.queue((blockList: { blocks: Interfaces.IBlockData[] }, cb) => {
try {
return this.processBlocks(blockList.blocks.map(b => Blocks.BlockFactory.fromData(b)), cb);
return this.processBlocks(blockList.blocks, cb);
} catch (error) {
logger.error(
`Failed to process ${blockList.blocks.length} blocks from height ${blockList.blocks[0].height} in queue.`,
Expand Down Expand Up @@ -392,14 +392,14 @@ export class Blockchain implements blockchain.IBlockchain {
/**
* Process the given block.
*/
public async processBlocks(blocks: Interfaces.IBlock[], callback): Promise<Interfaces.IBlock[]> {
public async processBlocks(blocks: Interfaces.IBlockData[], callback): Promise<Interfaces.IBlock[]> {
const acceptedBlocks: Interfaces.IBlock[] = [];
let lastProcessResult: BlockProcessorResult;

if (
blocks[0] &&
!isBlockChained(this.getLastBlock().data, blocks[0].data, logger) &&
!Utils.isException(blocks[0].data)
!isBlockChained(this.getLastBlock().data, blocks[0], logger) &&
!Utils.isException(blocks[0])
) {
// Discard remaining blocks as it won't go anywhere anyway.
this.clearQueue();
Expand All @@ -408,14 +408,17 @@ export class Blockchain implements blockchain.IBlockchain {
}

let forkBlock: Interfaces.IBlock;
let lastProcessedBlock: Interfaces.IBlock;
for (const block of blocks) {
lastProcessResult = await this.blockProcessor.process(block);
const blockInstance = Blocks.BlockFactory.fromData(block);
lastProcessResult = await this.blockProcessor.process(blockInstance);
lastProcessedBlock = blockInstance;

if (lastProcessResult === BlockProcessorResult.Accepted) {
acceptedBlocks.push(block);
acceptedBlocks.push(blockInstance);
} else {
if (lastProcessResult === BlockProcessorResult.Rollback) {
forkBlock = block;
forkBlock = blockInstance;
}

break; // if one block is not accepted, the other ones won't be chained anyway
Expand Down Expand Up @@ -459,11 +462,11 @@ export class Blockchain implements blockchain.IBlockchain {
lastProcessResult === BlockProcessorResult.Accepted ||
lastProcessResult === BlockProcessorResult.DiscardedButCanBeBroadcasted
) {
const currentBlock: Interfaces.IBlock = blocks[blocks.length - 1];
const blocktime: number = config.getMilestone(currentBlock.data.height).blocktime;
// broadcast last processed block
const blocktime: number = config.getMilestone(lastProcessedBlock.data.height).blocktime;

if (this.state.started && Crypto.Slots.getSlotNumber() * blocktime <= currentBlock.data.timestamp) {
this.p2p.getMonitor().broadcastBlock(currentBlock);
if (this.state.started && Crypto.Slots.getSlotNumber() * blocktime <= lastProcessedBlock.data.timestamp) {
this.p2p.getMonitor().broadcastBlock(lastProcessedBlock);
}
} else if (forkBlock) {
this.forkBlock(forkBlock);
Expand Down
6 changes: 3 additions & 3 deletions packages/core-blockchain/src/replay/replay-blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class ReplayBlockchain extends Blockchain {
return this.disconnect();
}

const blocks: Interfaces.IBlock[] = await this.fetchBatch(startHeight, batch, lastAcceptedHeight);
const blocks: Interfaces.IBlockData[] = await this.fetchBatch(startHeight, batch, lastAcceptedHeight);

this.processBlocks(blocks, async (acceptedBlocks: Interfaces.IBlock[]) => {
if (acceptedBlocks.length !== blocks.length) {
Expand All @@ -89,14 +89,14 @@ export class ReplayBlockchain extends Blockchain {
startHeight: number,
batch: number,
lastAcceptedHeight: number,
): Promise<Interfaces.IBlock[]> {
): Promise<Interfaces.IBlockData[]> {
this.logger.info("Fetching blocks from database...");

const offset: number = startHeight + (batch - 1) * this.chunkSize;
const count: number = Math.min(this.targetHeight - lastAcceptedHeight, this.chunkSize);
const blocks: Interfaces.IBlockData[] = await this.localDatabase.getBlocks(offset, count);

return blocks.map((block: Interfaces.IBlockData) => Blocks.BlockFactory.fromData(block));
return blocks;
}

private async processGenesisBlock(): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion packages/core-interfaces/src/core-blockchain/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export interface IBlockchain {
* @param {Function} callback
* @return {(Function|void)}
*/
processBlocks(blocks: Interfaces.IBlock[], callback: any): Promise<any>;
processBlocks(blocks: Interfaces.IBlockData[], callback: any): Promise<any>;

/**
* Called by forger to wake up and sync with the network.
Expand Down

0 comments on commit 117bb9e

Please sign in to comment.