Skip to content

Commit

Permalink
feat(core-state): in-memory storage for last N blocks and transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
faustbrian committed Apr 26, 2019
1 parent 35b7869 commit 03ddb47
Show file tree
Hide file tree
Showing 34 changed files with 429 additions and 75 deletions.
9 changes: 6 additions & 3 deletions __tests__/integration/core-p2p/mocks/core-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,12 @@ jest.mock("@arkecosystem/core-container", () => {

if (name === "state") {
return {
getLastBlock: () => genesisBlock,
cacheTransactions: jest.fn().mockImplementation(txs => ({ notAdded: txs, added: [] })),
removeCachedTransactionIds: jest.fn().mockReturnValue(null),
getStore: () => ({
getLastBlock: () => genesisBlock,
getLastHeight: () => genesisBlock.data.height,
cacheTransactions: jest.fn().mockImplementation(txs => ({ notAdded: txs, added: [] })),
removeCachedTransactionIds: jest.fn().mockReturnValue(null),
}),
};
}

Expand Down
2 changes: 1 addition & 1 deletion __tests__/unit/core-blockchain/mocks/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const container = {
if (name === "state") {
stateStorageStub.blockchain = blockchainMachine.initialState;

return stateStorageStub;
return { getStore: () => stateStorageStub };
}

return null;
Expand Down
4 changes: 2 additions & 2 deletions __tests__/unit/core-blockchain/stubs/state-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { State } from "@arkecosystem/core-interfaces";
import { Blocks, Interfaces } from "@arkecosystem/crypto";

export class StateStorageStub implements State.IStateStorage {
export class StateStoreStub implements State.IStateStore {
public blockchain: any;
public lastDownloadedBlock: Interfaces.IBlock | null;
public blockPing: any;
Expand Down Expand Up @@ -76,4 +76,4 @@ export class StateStorageStub implements State.IStateStorage {
}
}

export const stateStorageStub = new StateStorageStub();
export const stateStorageStub = new StateStoreStub();
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { State } from "@arkecosystem/core-interfaces";
import { Blocks, Interfaces } from "@arkecosystem/crypto";

export class StateStorageStub implements State.IStateStorage {
export class StateStoreStub implements State.IStateStore {
public blockchain: any;
public lastDownloadedBlock: Interfaces.IBlock | null;
public blockPing: any;
Expand Down Expand Up @@ -65,4 +65,4 @@ export class StateStorageStub implements State.IStateStorage {
public setLastBlock(block: Blocks.Block): void {}
}

export const stateStorageStub = new StateStorageStub();
export const stateStorageStub = new StateStoreStub();
2 changes: 1 addition & 1 deletion __tests__/unit/core-database/mocks/core-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jest.mock("@arkecosystem/core-container", () => {
}

if (name === "state") {
return stateStorageStub;
return { getStore: () => stateStorageStub };
}

return {};
Expand Down
6 changes: 4 additions & 2 deletions __tests__/unit/core-p2p/mocks/state.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { genesisBlock } from "../../../utils/fixtures/unitnet/block-model";

export const state = {
getLastBlock: () => genesisBlock,
forkedBlock: null,
getStore: () => ({
getLastBlock: () => genesisBlock,
forkedBlock: null,
}),
};
7 changes: 6 additions & 1 deletion __tests__/unit/core-p2p/network-monitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,18 @@ describe("NetworkMonitor", () => {

const spySuspend = jest.spyOn(processor, "suspend");

state.forkedBlock = { ip: "1.1.1.1" };
const spyStateStore = jest.spyOn(state, "getStore").mockReturnValueOnce({
...state.getStore(),
...{ forkedBlock: { ip: "1.1.1.1" } },
});

await monitor.refreshPeersAfterFork();

expect(monitor.resetSuspendedPeers).toHaveBeenCalled();
expect(spySuspend).toHaveBeenCalledWith("1.1.1.1");
expect(connector.disconnect).toHaveBeenCalled();

spyStateStore.mockRestore();
});
});

Expand Down
7 changes: 0 additions & 7 deletions __tests__/unit/core-state/mocks/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@ export const container = {
}),
};
},
resolve: name => {
if (name === "state") {
return {};
}

return {};
},
resolvePlugin: name => {
if (name === "logger") {
return logger;
Expand Down
4 changes: 2 additions & 2 deletions __tests__/unit/core-state/state-storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { logger } from "./mocks/logger";
import { Blocks as cBlocks, Interfaces } from "@arkecosystem/crypto";
import delay from "delay";
import { defaults } from "../../../packages/core-state/src/defaults";
import { StateStorage } from "../../../packages/core-state/src/state-storage";
import { StateStore } from "../../../packages/core-state/src/stores/state";
import "../../utils";
import { blocks101to155 } from "../../utils/fixtures/testnet/blocks101to155";
import { blocks2to100 } from "../../utils/fixtures/testnet/blocks2to100";
Expand All @@ -15,7 +15,7 @@ const blocks = blocks2to100.concat(blocks101to155).map(block => BlockFactory.fro

let stateStorage;
beforeAll(async () => {
stateStorage = new StateStorage();
stateStorage = new StateStore();
});

beforeEach(() => {
Expand Down
6 changes: 4 additions & 2 deletions __tests__/unit/core-transaction-pool/mocks/state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const state = {
cacheTransactions: () => null,
removeCachedTransactionIds: () => null,
getStore: () => ({
cacheTransactions: () => null,
removeCachedTransactionIds: () => null,
}),
};
174 changes: 174 additions & 0 deletions __tests__/unit/core-utils/ordered-capped-map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import "jest-extended";

import { OrderedCappedMap } from "../../../packages/core-utils/src/ordered-capped-map";

describe("Ordered Capped Map", () => {
it("should set and get an entry", () => {
const store = new OrderedCappedMap<string, number>(100);
store.set("foo", 1);
store.set("bar", 2);

expect(store.get("foo")).toBe(1);
expect(store.count()).toBe(2);
});

it("should get an entry", () => {
const store = new OrderedCappedMap<string, number>(2);
store.set("1", 1);
store.set("2", 2);

expect(store.get("1")).toBe(1);
expect(store.get("3")).toBeUndefined();

store.set("3", 3);

expect(store.has("1")).toBeFalse();
expect(store.has("2")).toBeTrue();
expect(store.has("3")).toBeTrue();
});

it("should set entries and remove ones that exceed the maximum size", () => {
const store = new OrderedCappedMap<string, number>(2);
store.set("foo", 1);
store.set("bar", 2);

expect(store.get("foo")).toBe(1);
expect(store.get("bar")).toBe(2);

store.set("baz", 3);
store.set("faz", 4);

expect(store.has("foo")).toBeFalse();
expect(store.has("bar")).toBeFalse();
expect(store.has("baz")).toBeTrue();
expect(store.has("faz")).toBeTrue();
expect(store.count()).toBe(2);
});

it("should update an entry", () => {
const store = new OrderedCappedMap<string, number>(100);
store.set("foo", 1);

expect(store.get("foo")).toBe(1);

store.set("foo", 2);

expect(store.get("foo")).toBe(2);
expect(store.count()).toBe(1);
});

it("should return if an entry exists", () => {
const store = new OrderedCappedMap<string, number>(100);
store.set("1", 1);

expect(store.has("1")).toBeTrue();
});

it("should remove the specified entrys", () => {
const store = new OrderedCappedMap<string, number>(100);
store.set("1", 1);
store.set("2", 2);

expect(store.delete("1")).toBeTrue();
expect(store.has("1")).toBeFalse();
expect(store.has("2")).toBeTrue();
expect(store.delete("1")).toBeFalse();
expect(store.count()).toBe(1);
});

it("should remove the specified entrys", () => {
const store = new OrderedCappedMap<string, number>(2);
store.set("1", 1);
store.set("2", 2);

expect(store.count()).toBe(2);
expect(store.delete("1")).toBeTrue();
expect(store.has("1")).toBeFalse();
expect(store.has("2")).toBeTrue();

store.delete("2");

expect(store.count()).toBe(0);
});

it("should remove all entrys", () => {
const store = new OrderedCappedMap<string, number>(3);
store.set("1", 1);
store.set("2", 2);
store.set("3", 3);

expect(store.count()).toBe(3);

store.clear();

expect(store.count()).toBe(0);
});

it("should return the first value", () => {
const store = new OrderedCappedMap<string, number>(2);
store.set("1", 1);
store.set("2", 2);

expect(store.first()).toBe(1);
});

it("should return the last value", () => {
const store = new OrderedCappedMap<string, number>(2);
store.set("1", 1);
store.set("2", 2);

expect(store.last()).toBe(2);
});

it("should return the keys", () => {
const store = new OrderedCappedMap<string, number>(3);
store.set("1", 1);
store.set("2", 2);
store.set("3", 3);

expect(store.keys()).toEqual(["1", "2", "3"]);
});

it("should return the values", () => {
const store = new OrderedCappedMap<string, number>(3);
store.set("1", 1);
store.set("2", 2);
store.set("3", 3);

expect(store.values()).toEqual([1, 2, 3]);
});

it("should return the entry count", () => {
const store = new OrderedCappedMap<string, number>(100);
store.set("1", 1);
store.set("2", 2);

expect(store.count()).toBe(2);

store.delete("1");

expect(store.count()).toBe(1);

store.set("3", 3);

expect(store.count()).toBe(2);
});

it("should resize the map", () => {
const store = new OrderedCappedMap<string, number>(3);
store.set("1", 1);
store.set("2", 2);
store.set("3", 3);

expect(store.count()).toBe(3);

store.set("4", 4);

expect(store.count()).toBe(3);

store.resize(4);
store.set("5", 5);

expect(store.count()).toBe(4);
});
});
4 changes: 2 additions & 2 deletions packages/core-blockchain/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const { BlockFactory } = Blocks;
export class Blockchain implements blockchain.IBlockchain {
/**
* Get the state of the blockchain.
* @return {IStateStorage}
* @return {IStateStore}
*/
get state(): State.IStateStorage {
get state(): State.IStateStore {
return stateMachine.state;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/core-blockchain/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ export const plugin: Container.PluginDescriptor = {
async register(container: Container.IContainer, options: Container.IPluginOptions) {
const blockchain = new Blockchain(options);

container.resolvePlugin<State.IStateStorage>("state").reset(blockchainMachine);
container
.resolvePlugin<State.IStateService>("state")
.getStore()
.reset(blockchainMachine);

if (!process.env.CORE_SKIP_BLOCKCHAIN) {
await blockchain.start();
Expand Down
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 @@ -15,10 +15,10 @@ const { BlockFactory } = Blocks;
const config = app.getConfig();
const emitter = app.resolvePlugin<EventEmitter.EventEmitter>("event-emitter");
const logger = app.resolvePlugin<Logger.ILogger>("logger");
const stateStorage = app.resolvePlugin<State.IStateStorage>("state");
const stateStorage = app.resolvePlugin<State.IStateService>("state").getStore();

/**
* @type {IStateStorage}
* @type {IStateStore}
*/
blockchainMachine.state = stateStorage;

Expand Down
5 changes: 1 addition & 4 deletions packages/core-database/src/database-service-factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { app } from "@arkecosystem/core-container";
import { Database, State } from "@arkecosystem/core-interfaces";
import { Database } from "@arkecosystem/core-interfaces";
import { DatabaseService } from "./database-service";
import { BlocksBusinessRepository } from "./repositories/blocks-business-repository";
import { DelegatesBusinessRepository } from "./repositories/delegates-business-repository";
Expand All @@ -24,7 +23,5 @@ export const databaseServiceFactory = async (

await databaseService.init();

app.resolvePlugin<State.IStateStorage>("state").setLastBlock(await databaseService.getLastBlock());

return databaseService;
};
Loading

0 comments on commit 03ddb47

Please sign in to comment.