Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions packages/core-blockchain/__tests__/blockchain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,48 @@ describe("Blockchain", () => {
});
});

describe("handleIncomingBlock", () => {
it("should be ok", () => {
const dispatch = blockchain.dispatch;
const enqueueBlocks = blockchain.enqueueBlocks;
blockchain.dispatch = jest.fn(() => true);
blockchain.enqueueBlocks = jest.fn(() => true);

const block = {
height: 100,
timestamp: slots.getEpochTime(),
};

blockchain.handleIncomingBlock(block);

expect(blockchain.dispatch).toHaveBeenCalled();
expect(blockchain.enqueueBlocks).toHaveBeenCalled();

blockchain.dispatch = dispatch;
blockchain.enqueueBlocks = enqueueBlocks;
});

it("should not handle block from future slot", () => {
const dispatch = blockchain.dispatch;
const enqueueBlocks = blockchain.enqueueBlocks;
blockchain.dispatch = jest.fn(() => true);
blockchain.enqueueBlocks = jest.fn(() => true);

const block = {
height: 100,
timestamp: slots.getSlotTime(slots.getNextSlot()),
};

blockchain.handleIncomingBlock(block);

expect(blockchain.dispatch).not.toHaveBeenCalled();
expect(blockchain.enqueueBlocks).not.toHaveBeenCalled();

blockchain.dispatch = dispatch;
blockchain.enqueueBlocks = enqueueBlocks;
});
});

describe("isSynced", () => {
describe("with a block param", () => {
it("should be ok", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import "jest-extended";

import { models } from "@arkecosystem/crypto";
import { models, slots } from "@arkecosystem/crypto";
import { isBlockChained } from "../../src/utils";

describe("isChained", () => {
it("should be ok", () => {
const previousBlock = {
data: {
id: "1",
timestamp: 1,
timestamp: slots.getSlotTime(0),
height: 1,
previousBlock: null,
},
Expand All @@ -17,20 +17,20 @@ describe("isChained", () => {
const nextBlock = {
data: {
id: "2",
timestamp: 2,
timestamp: slots.getSlotTime(1),
height: 2,
previousBlock: "1",
},
} as models.IBlock;
} as models.IBlock;

expect(isBlockChained(previousBlock, nextBlock)).toBeTrue();
});

it("should not be ok", () => {
it("should not chain when previous block does not match", () => {
const previousBlock = {
data: {
id: "2",
timestamp: 2,
timestamp: slots.getSlotTime(0),
height: 2,
previousBlock: null,
},
Expand All @@ -39,8 +39,74 @@ describe("isChained", () => {
const nextBlock = {
data: {
id: "1",
timestamp: 1,
timestamp: slots.getSlotTime(1),
height: 3,
previousBlock: "1",
},
} as models.IBlock;

expect(isBlockChained(previousBlock, nextBlock)).toBeFalse();
});

it("should not chain when next height is not plus 1", () => {
const previousBlock = {
data: {
id: "1",
timestamp: slots.getSlotTime(0),
height: 1,
previousBlock: null,
},
} as models.IBlock;

const nextBlock = {
data: {
id: "2",
timestamp: slots.getSlotTime(1),
height: 3,
previousBlock: "1",
},
} as models.IBlock;

expect(isBlockChained(previousBlock, nextBlock)).toBeFalse();
});

it("should not chain when same slot", () => {
const previousBlock = {
data: {
id: "1",
timestamp: slots.getSlotTime(0),
height: 1,
previousBlock: null,
},
} as models.IBlock;

const nextBlock = {
data: {
id: "2",
timestamp: slots.getSlotTime(0),
height: 3,
previousBlock: "1",
},
} as models.IBlock;

expect(isBlockChained(previousBlock, nextBlock)).toBeFalse();
});

it("should not chain when lower slot", () => {
const previousBlock = {
data: {
id: "1",
timestamp: slots.getSlotTime(1),
height: 1,
previousBlock: null,
},
} as models.IBlock;

const nextBlock = {
data: {
id: "2",
timestamp: slots.getSlotTime(0),
height: 3,
previousBlock: "1",
},
} as models.IBlock;
Expand Down
7 changes: 7 additions & 0 deletions packages/core-blockchain/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,13 @@ export class Blockchain implements blockchain.IBlockchain {
)} from ${block.ip}`,
);

const currentSlot = slots.getSlotNumber();
const receivedSlot = slots.getSlotNumber(block.timestamp);
if (receivedSlot > currentSlot) {
logger.info(`Discarded block ${block.height.toLocaleString()} because it takes a future slot.`);
return;
}

if (this.state.started && this.state.blockchain.value === "idle") {
this.dispatch("NEWBLOCK");
this.enqueueBlocks([block]);
Expand Down
27 changes: 12 additions & 15 deletions packages/core-blockchain/src/processor/block-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
AcceptBlockHandler,
AlreadyForgedHandler,
BlockHandler,
ExceptionHandler,
InvalidGeneratorHandler,
UnchainedHandler,
VerificationFailedHandler,
Expand All @@ -35,6 +36,10 @@ export class BlockProcessor {
}

public async getHandler(block: models.Block): Promise<BlockHandler> {
if (isException(block.data)) {
return new ExceptionHandler(this.blockchain, block);
}

if (!this.verifyBlock(block)) {
return new VerificationFailedHandler(this.blockchain, block);
}
Expand Down Expand Up @@ -63,21 +68,13 @@ export class BlockProcessor {
private verifyBlock(block: models.Block): boolean {
const verified = block.verification.verified;
if (!verified) {
if (isException(block.data)) {
this.logger.warn(
`Block ${block.data.height.toLocaleString()} (${
block.data.id
}) verification failed, but accepting because it is an exception.`,
);
} else {
this.logger.warn(
`Block ${block.data.height.toLocaleString()} (${
block.data.id
}) disregarded because verification failed :scroll:`,
);
this.logger.warn(JSON.stringify(block.verification, null, 4));
return false;
}
this.logger.warn(
`Block ${block.data.height.toLocaleString()} (${
block.data.id
}) disregarded because verification failed :scroll:`,
);
this.logger.warn(JSON.stringify(block.verification, null, 4));
return false;
}

return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { BlockProcessorResult } from "../block-processor";
import { AcceptBlockHandler } from "./accept-block-handler";
import { BlockHandler } from "./block-handler";

export class ExceptionHandler extends BlockHandler {
public async execute(): Promise<BlockProcessorResult> {
// Ensure the block has not been forged yet, as an exceptional
// block bypasses all other checks.
const forgedBlock = await this.blockchain.database.getBlock(this.block.data.id);
if (forgedBlock) {
return super.execute();
}

this.logger.warn(
`Block ${this.block.data.height.toLocaleString()} (${this.block.data.id}) forcibly accepted. :exclamation:`,
);

return new AcceptBlockHandler(this.blockchain, this.block).execute();
}
}
1 change: 1 addition & 0 deletions packages/core-blockchain/src/processor/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./accept-block-handler";
export * from "./already-forged-handler";
export * from "./block-handler";
export * from "./exception-handler";
export * from "./invalid-generator-handler";
export * from "./unchained-handler";
export * from "./verification-failed-handler";
4 changes: 2 additions & 2 deletions packages/core-blockchain/src/state-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { app } from "@arkecosystem/core-container";
import { EventEmitter, Logger } from "@arkecosystem/core-interfaces";

import { roundCalculator } from "@arkecosystem/core-utils";
import { models, slots } from "@arkecosystem/crypto";
import { isException, models, slots } from "@arkecosystem/crypto";

import pluralize from "pluralize";
import { config as localConfig } from "./config";
Expand Down Expand Up @@ -320,7 +320,7 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({
}

const empty = !blocks || blocks.length === 0;
const chained = !empty && isBlockChained(lastDownloadedBlock, { data: blocks[0] });
const chained = !empty && (isBlockChained(lastDownloadedBlock, { data: blocks[0] }) || isException(blocks[0]));

if (chained) {
logger.info(
Expand Down
9 changes: 6 additions & 3 deletions packages/core-blockchain/src/utils/is-block-chained.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { models } from "@arkecosystem/crypto";
import { models, slots } from "@arkecosystem/crypto";

export const isBlockChained = (previousBlock: models.IBlock, nextBlock: models.IBlock): boolean => {
const followsPrevious = nextBlock.data.previousBlock === previousBlock.data.id;
const isFuture = nextBlock.data.timestamp > previousBlock.data.timestamp;
const isPlusOne = nextBlock.data.height === previousBlock.data.height + 1;

return followsPrevious && isFuture && isPlusOne;
const previousSlot = slots.getSlotNumber(previousBlock.data.timestamp);
const nextSlot = slots.getSlotNumber(nextBlock.data.timestamp);
const isAfterPreviousSlot = previousSlot < nextSlot;

return followsPrevious && isPlusOne && isAfterPreviousSlot;
};
6 changes: 1 addition & 5 deletions packages/core-blockchain/src/utils/validate-generator.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { app } from "@arkecosystem/core-container";
import { Logger } from "@arkecosystem/core-interfaces";
import { isException, models, slots } from "@arkecosystem/crypto";
import { models, slots } from "@arkecosystem/crypto";

export const validateGenerator = async (block: models.Block): Promise<boolean> => {
const database = app.resolvePlugin("database");
const logger = app.resolvePlugin<Logger.ILogger>("logger");

if (isException(block.data)) {
return true;
}

const delegates = await database.getActiveDelegates(block.data.height);
const slot = slots.getSlotNumber(block.data.timestamp);
const forgingDelegate = delegates[slot % delegates.length];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class BlocksRepository extends Repository {
* @return {Promise}
*/
public async findById(id) {
return this.db.one(sql.findById, { id });
return this.db.oneOrNone(sql.findById, { id });
}

/**
Expand Down
13 changes: 10 additions & 3 deletions packages/crypto/src/crypto/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Address, KeyPair, Keys, PublicKey, WIF } from "../identities";
import { configManager } from "../managers";
import { feeManager } from "../managers";
import { ITransactionData } from "../models";
import { INetwork } from "../networks";
import { Bignum } from "../utils";
import { HashAlgorithms } from "./hash-algorithms";

Expand All @@ -25,7 +24,11 @@ class Crypto {
/**
* Get the byte representation of the transaction.
*/
public getBytes(transaction: ITransactionData, skipSignature: boolean = false, skipSecondSignature: boolean = false): Buffer {
public getBytes(
transaction: ITransactionData,
skipSignature: boolean = false,
skipSecondSignature: boolean = false,
): Buffer {
if (transaction.version && transaction.version !== 1) {
throw new Error("not supported yet");
}
Expand Down Expand Up @@ -183,7 +186,11 @@ class Crypto {
/**
* Get transaction hash.
*/
public getHash(transaction: ITransactionData, skipSignature: boolean = false, skipSecondSignature: boolean = false): Buffer {
public getHash(
transaction: ITransactionData,
skipSignature: boolean = false,
skipSecondSignature: boolean = false,
): Buffer {
if (transaction.version && transaction.version !== 1) {
throw new Error("not supported yet");
}
Expand Down
3 changes: 1 addition & 2 deletions packages/crypto/src/crypto/slots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ class Slots {

/**
* Get the next slot number.
* @return {Number}
*/
public getNextSlot(): number {
return this.getSlotNumber() + 1;
Expand Down Expand Up @@ -125,4 +124,4 @@ class Slots {
}
}

export const slots = new Slots();
export const slots = new Slots();
Loading