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

feat(core-p2p): include estimateTotalCount into peers response #3825

Merged
merged 7 commits into from
Jun 29, 2020
134 changes: 70 additions & 64 deletions __tests__/unit/core-p2p/socket-server/controllers/peer.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Crypto, Blocks, Utils, Networks, Transactions, Identities, Managers } from "@arkecosystem/crypto";
import { Container } from "@arkecosystem/core-kernel";

import { PeerController } from "@arkecosystem/core-p2p/src/socket-server/controllers/peer";
import { Peer } from "@arkecosystem/core-p2p/src/peer";
import { MissingCommonBlockError } from "@arkecosystem/core-p2p/src/errors";
import { getPeerConfig } from "@arkecosystem/core-p2p/src/socket-server/utils/get-peer-config";
import { Peer } from "@arkecosystem/core-p2p/src/peer";
import { PeerController } from "@arkecosystem/core-p2p/src/socket-server/controllers/peer";
import { TooManyTransactionsError, UnchainedBlockError } from "@arkecosystem/core-p2p/src/socket-server/errors";
import { getPeerConfig } from "@arkecosystem/core-p2p/src/socket-server/utils/get-peer-config";
import { Blocks, Crypto, Identities, Managers, Networks, Transactions, Utils } from "@arkecosystem/crypto";

Managers.configManager.getMilestone().aip11 = true; // for creating aip11 v2 transactions

Expand All @@ -24,14 +23,14 @@ describe("PeerController", () => {
getLastDownloadedBlock: jest.fn(),
};
const createProcessor = jest.fn();
const appPlugins = [ { package: "@arkecosystem/core-api", options: {} } ];
const appPlugins = [{ package: "@arkecosystem/core-api", options: {} }];
const coreApiServiceProvider = {
name: () => "core-api",
configDefaults: () => ({
server: { http: { port: 4003 } }
server: { http: { port: 4003 } },
}),
};
const serviceProviders = { "@arkecosystem/core-api": coreApiServiceProvider, };
const serviceProviders = { "@arkecosystem/core-api": coreApiServiceProvider };
const configRepository = { get: () => appPlugins }; // get("app.plugins")
const serviceProviderRepository = { get: (plugin) => serviceProviders[plugin] };
const appGet = {
Expand All @@ -48,10 +47,15 @@ describe("PeerController", () => {
resolve: () => ({
from: () => ({
merge: () => ({
all: () => ({ server: { http: { port: "4003" } } })
})
})
})
all: () => ({
server: { http: { port: "4003" } },
options: {
estimateTotalCount: true,
},
}),
}),
}),
}),
};

beforeAll(() => {
Expand Down Expand Up @@ -191,7 +195,7 @@ describe("PeerController", () => {
.recipientId(Identities.Address.fromPassphrase("recipient's secret"))
.fee("100")
.sign("sender's secret")
.build()
.build(),
],
} as Blocks.Block;
const deepClone = (obj) => JSON.parse(JSON.stringify(obj));
Expand All @@ -202,7 +206,7 @@ describe("PeerController", () => {
blockTooManyTxs.data.numberOfTransactions = 350;
const blockSerialized = Blocks.Serializer.serializeWithTransactions({
...blockTooManyTxs.data,
transactions: blockTooManyTxs.transactions.map(tx => tx.data)
transactions: blockTooManyTxs.transactions.map((tx) => tx.data),
});

await expect(
Expand All @@ -215,96 +219,98 @@ describe("PeerController", () => {
const blockTooManyTxs = deepClone(block);

const transactions = [];
for(let i = 0; i < 2; i++) {
for (let i = 0; i < 2; i++) {
transactions.push(
Transactions.BuilderFactory.transfer()
.version(2)
.amount("100")
.recipientId(Identities.Address.fromPassphrase(`recipient secret ${i}`))
.fee("100")
.nonce(`${i+1}`)
.nonce(`${i + 1}`)
.sign(`sender secret ${i}`)
.build()
.build(),
);
}
blockTooManyTxs.transactions = transactions;
blockTooManyTxs.data.numberOfTransactions = 2;

const blockSerialized = Blocks.Serializer.serializeWithTransactions({
...blockTooManyTxs.data,
transactions: transactions.map(tx => tx.data),
transactions: transactions.map((tx) => tx.data),
});

// this is a trick to make the first numberOfTransactions check pass
// but then transactions.length fail
// probably some unreachable code though...
const milestone = Managers.configManager.getMilestone();
const spyGetMilestone = jest.spyOn(Managers.configManager, "getMilestone")
const spyGetMilestone = jest.spyOn(Managers.configManager, "getMilestone");
for (let i = 0; i < 71; i++) {
// yeah 71 times :wtf: before the one we are interested to mock kicks in
spyGetMilestone.mockReturnValueOnce({
...milestone,
block: {
maxTransactions: 150
}
})
maxTransactions: 150,
},
});
}
spyGetMilestone.mockReturnValueOnce({
...milestone,
block: {
maxTransactions: 1
}
})
maxTransactions: 1,
},
});

await expect(
peerController.postBlock(
{
payload: { block: { data: blockSerialized } },
info: { remoteAddress: "187.55.33.22" }
info: { remoteAddress: "187.55.33.22" },
},
{}
)
{},
),
).rejects.toBeInstanceOf(TooManyTransactionsError);

spyGetMilestone.mockRestore();
});
});

describe("when block is not chained", () => {
it.each([[true], [false]])
("should throw UnchainedBlockError only if block is not known", async (blockPing) => {
blockchain.getLastDownloadedBlock = jest.fn().mockReturnValueOnce(Networks.testnet.genesisBlock);
const blockUnchained = deepClone(block);
blockUnchained.data.height = 9;
const blockSerialized = Blocks.Serializer.serializeWithTransactions({
...blockUnchained.data,
transactions: blockUnchained.transactions.map(tx => tx.data)
});

if (blockPing) {
blockchain.pingBlock = jest.fn().mockReturnValueOnce(true);
await expect(
peerController.postBlock(
{
payload: { block: { data: blockSerialized } },
info: { remoteAddress: "187.55.33.22" },
},
{},
),
).toResolve();
expect(blockchain.handleIncomingBlock).toBeCalledTimes(0);
} else {
await expect(
peerController.postBlock(
{
payload: { block: { data: blockSerialized } },
info: { remoteAddress: "187.55.33.22" },
},
{},
),
).rejects.toBeInstanceOf(UnchainedBlockError);
}
});
it.each([[true], [false]])(
"should throw UnchainedBlockError only if block is not known",
async (blockPing) => {
blockchain.getLastDownloadedBlock = jest.fn().mockReturnValueOnce(Networks.testnet.genesisBlock);
const blockUnchained = deepClone(block);
blockUnchained.data.height = 9;
const blockSerialized = Blocks.Serializer.serializeWithTransactions({
...blockUnchained.data,
transactions: blockUnchained.transactions.map((tx) => tx.data),
});

if (blockPing) {
blockchain.pingBlock = jest.fn().mockReturnValueOnce(true);
await expect(
peerController.postBlock(
{
payload: { block: { data: blockSerialized } },
info: { remoteAddress: "187.55.33.22" },
},
{},
),
).toResolve();
expect(blockchain.handleIncomingBlock).toBeCalledTimes(0);
} else {
await expect(
peerController.postBlock(
{
payload: { block: { data: blockSerialized } },
info: { remoteAddress: "187.55.33.22" },
},
{},
),
).rejects.toBeInstanceOf(UnchainedBlockError);
}
},
);
});

describe("when block comes from forger", () => {
Expand All @@ -315,7 +321,7 @@ describe("PeerController", () => {

const blockSerialized = Blocks.Serializer.serializeWithTransactions({
...block.data,
transactions: block.transactions.map(tx => tx.data)
transactions: block.transactions.map((tx) => tx.data),
});
await peerController.postBlock(
{
Expand All @@ -339,7 +345,7 @@ describe("PeerController", () => {

const blockSerialized = Blocks.Serializer.serializeWithTransactions({
...block.data,
transactions: block.transactions.map(tx => tx.data)
transactions: block.transactions.map((tx) => tx.data),
});
await peerController.postBlock(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Managers } from "@arkecosystem/crypto";
import { getPeerConfig } from "@arkecosystem/core-p2p/src/socket-server/utils/get-peer-config";
import { Container } from "@arkecosystem/core-kernel";
import { getPeerConfig } from "@arkecosystem/core-p2p/src/socket-server/utils/get-peer-config";
import { Managers } from "@arkecosystem/crypto";

describe("getPeerConfig", () => {
const mockConfig = {
Expand All @@ -17,27 +17,33 @@ describe("getPeerConfig", () => {
const appPlugins = [
{ package: "@arkecosystem/core-api", options: {} },
{ package: "@arkecosystem/core-webhooks" },
{ package: "@arkecosystem/core-p2p" },
];
const coreApiServiceProvider = {
name: () => "core-api",
configDefaults: () => ({
server: { http: { port: 4003 } }
server: { http: { port: 4003 } },
}),
};
const coreWebhooksServiceProvider = {
name: () => "core-webhooks",
configDefaults: () => ({}),
};
const coreP2PServiceProvider = {
name: () => "core-p2p",
configDefaults: () => ({}),
};
const serviceProviders = {
"@arkecosystem/core-api": coreApiServiceProvider,
"@arkecosystem/core-webhooks": coreWebhooksServiceProvider,
}
"@arkecosystem/core-p2p": coreP2PServiceProvider,
};
const configRepository = { get: () => appPlugins }; // get("app.plugins")
const serviceProviderRepository = { get: (plugin) => serviceProviders[plugin] };
const appGet = {
[Container.Identifiers.ConfigRepository]: configRepository,
[Container.Identifiers.ServiceProviderRepository]: serviceProviderRepository,
}
};
const app = {
version: () => version,
get: (key) => appGet[key],
Expand All @@ -47,22 +53,25 @@ describe("getPeerConfig", () => {
all: () => ({
server: {
http: {
port: "4003"
}
}
})
})
port: "4003",
},
},
options: {
estimateTotalCount: true,
},
}),
}),
}),
discover: () => ({
merge: () => ({
all: () => ({
server: {
port: "4004"
}
})
})
})
})
port: "4004",
},
}),
}),
}),
}),
};

it("should return own config from config manager", () => {
Expand All @@ -81,12 +90,13 @@ describe("getPeerConfig", () => {
plugins: {
"@arkecosystem/core-api": {
enabled: true,
port: 4003
estimateTotalCount: true,
port: 4003,
},
"@arkecosystem/core-webhooks": {
enabled: true,
port: 4004
}
port: 4004,
},
},
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/core-api/src/resources/peer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class PeerResource implements Resource {
version: resource.version,
height: resource.state ? resource.state.height : resource.height,
latency: resource.latency,
plugins: resource.plugins,
};
}
}
2 changes: 1 addition & 1 deletion packages/core-kernel/src/contracts/p2p/peer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface PeerPorts {
}

export interface PeerPlugins {
[name: string]: { enabled: boolean; port: number };
[name: string]: { enabled: boolean; port: number; estimateTotalCount?: boolean };
}

export interface Peer {
Expand Down
3 changes: 3 additions & 0 deletions packages/core-p2p/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ export const replySchemas = {
enabled: {
type: "boolean",
},
estimateTotalCount: {
type: "boolean",
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const transformPlugins = (plugins: PluginConfig[]): Contracts.P2P.PeerPlugins =>
enabled: true, // default to true because "enabled" flag is in different place based on which plugin
port,
};

if (name.includes("core-api")) {
result[name].estimateTotalCount = pluginConfig.options.options.estimateTotalCount;
}
}

return result;
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1775,7 +1775,7 @@
resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-4.0.0.tgz#4aadbb88de13bce39ab8431565cd50c7ecc6327f"
integrity sha512-V6xYOrr5aFv/IJqNPneaYCu8vuGTKisamqHVRS3JJnbZr18TrpXdsJOYk9pjPhFti+M2YETPebQLUr820N5NoQ==

"@hapi/teamwork@5.x":
"@hapi/teamwork@5.x.x":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.0.tgz#7801a61fc727f702fd2196ef7625eb4e389f4124"
integrity sha512-llqoQTrAJDTXxG3c4Kz/uzhBS1TsmSBa/XG5SPcVXgmffHE1nFtyLIK0hNJHCB3EuBKT84adzd1hZNY9GJLWtg==
Expand Down