Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core-blockchain): pass IBlockData to processBlocks instead of IBlock #3426

Merged
merged 6 commits into from Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions __tests__/integration/core-blockchain/blockchain.test.ts
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
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
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
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
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
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
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