From 7cb916abcf741303e77e3294b1070a35b738f84d Mon Sep 17 00:00:00 2001 From: Sebastijan K <58827427+sebastijankuzner@users.noreply.github.com> Date: Mon, 8 Jun 2020 11:11:08 +0200 Subject: [PATCH] feat(core-manager): dispatch wallet events (#3776) --- .../core-manager/service-provider.test.ts | 16 ++++ .../unit/core-manager/watcher-wallet.test.ts | 89 +++++++++++++++++++ packages/core-manager/package.json | 1 + packages/core-manager/src/events.ts | 5 ++ packages/core-manager/src/service-provider.ts | 19 +++- packages/core-manager/src/watcher-wallet.ts | 75 ++++++++++++++++ .../src/wallets/indexers/wallet-indexes.ts | 18 ++-- packages/core-state/src/wallets/wallet.ts | 2 +- 8 files changed, 213 insertions(+), 12 deletions(-) create mode 100644 __tests__/unit/core-manager/watcher-wallet.test.ts create mode 100644 packages/core-manager/src/events.ts create mode 100644 packages/core-manager/src/watcher-wallet.ts diff --git a/__tests__/unit/core-manager/service-provider.test.ts b/__tests__/unit/core-manager/service-provider.test.ts index c53b6d2563..314845e695 100644 --- a/__tests__/unit/core-manager/service-provider.test.ts +++ b/__tests__/unit/core-manager/service-provider.test.ts @@ -4,6 +4,7 @@ import { Application, Container, Providers } from "@packages/core-kernel"; import { defaults } from "@packages/core-manager/src/defaults"; import { Identifiers } from "@packages/core-manager/src/ioc"; import { ServiceProvider } from "@packages/core-manager/src/service-provider"; +import { WatcherWallet } from "@packages/core-manager/src/watcher-wallet"; import path from "path"; import { dirSync, setGracefulCleanup } from "tmp"; @@ -33,6 +34,7 @@ beforeEach(() => { app.bind(Container.Identifiers.PluginConfiguration).to(Providers.PluginConfiguration).inSingletonScope(); app.bind(Container.Identifiers.FilesystemService).toConstantValue({}); app.bind(Container.Identifiers.EventDispatcherService).toConstantValue(mockEventDispatcher); + app.bind(Container.Identifiers.WalletAttributes).toConstantValue({}); defaults.watcher.storage = dirSync().name + "/events.sqlite"; defaults.server.https.tls.key = path.resolve(__dirname, "./__fixtures__/key.pem"); @@ -114,4 +116,18 @@ describe("ServiceProvider", () => { it("should not be required", async () => { await expect(serviceProvider.required()).resolves.toBeFalse(); }); + + it("should create wallet", async () => { + const usedDefaults = { ...defaults }; + + setPluginConfiguration(app, serviceProvider, usedDefaults); + + await expect(serviceProvider.register()).toResolve(); + + // @ts-ignore + const wallet = app.get(Container.Identifiers.WalletFactory)("123"); + expect(wallet).toBeInstanceOf(WatcherWallet); + + await expect(serviceProvider.dispose()).toResolve(); + }); }); diff --git a/__tests__/unit/core-manager/watcher-wallet.test.ts b/__tests__/unit/core-manager/watcher-wallet.test.ts new file mode 100644 index 0000000000..29f2937fe0 --- /dev/null +++ b/__tests__/unit/core-manager/watcher-wallet.test.ts @@ -0,0 +1,89 @@ +import "jest-extended"; + +import { Container, Services } from "@arkecosystem/core-kernel"; +import { WatcherWallet } from "@arkecosystem/core-manager/src/watcher-wallet"; +import { Utils } from "@arkecosystem/crypto"; +import { Sandbox } from "@packages/core-test-framework"; +import { getWalletAttributeSet } from "@packages/core-test-framework/src/internal/wallet-attributes"; + +let sandbox: Sandbox; +let wallet: WatcherWallet; +const mockEventDispatcher = { + dispatchSync: jest.fn(), +}; + +beforeEach(() => { + sandbox = new Sandbox(); + sandbox.app.bind(Container.Identifiers.EventDispatcherService).toConstantValue(mockEventDispatcher); + + const attributeMap = new Services.Attributes.AttributeMap(getWalletAttributeSet()); + + wallet = new WatcherWallet(sandbox.app, "123", attributeMap); +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe("WatcherWallet", () => { + describe("Original", () => { + it("should emit on property set", async () => { + wallet.nonce = Utils.BigNumber.make("3"); + + expect(mockEventDispatcher.dispatchSync).toHaveBeenCalledTimes(1); + }); + + it("should emit on setAttribute", async () => { + wallet.setAttribute("delegate.username", "dummy"); + + expect(mockEventDispatcher.dispatchSync).toHaveBeenCalledTimes(1); + }); + + it("should emit on forgetAttribute", async () => { + wallet.setAttribute("delegate.username", "dummy"); + wallet.forgetAttribute("delegate.username"); + + expect(mockEventDispatcher.dispatchSync).toHaveBeenCalledTimes(2); + }); + + it("should clone", async () => { + const clone = wallet.clone(); + + expect(clone).toEqual(wallet); + }); + }); + + describe("Clone", () => { + let clone: WatcherWallet; + + beforeEach(() => { + clone = wallet.clone(); + jest.clearAllMocks(); + }); + + it("should emit on property set", async () => { + clone.nonce = Utils.BigNumber.make("3"); + + expect(mockEventDispatcher.dispatchSync).toHaveBeenCalledTimes(1); + }); + + it("should emit on setAttribute", async () => { + clone.setAttribute("delegate.username", "dummy"); + + expect(mockEventDispatcher.dispatchSync).toHaveBeenCalledTimes(1); + }); + + it("should emit on forgetAttribute", async () => { + clone.setAttribute("delegate.username", "dummy"); + clone.forgetAttribute("delegate.username"); + + expect(mockEventDispatcher.dispatchSync).toHaveBeenCalledTimes(2); + }); + + it("should clone", async () => { + const anotherClone = clone.clone(); + + expect(anotherClone).toEqual(wallet); + }); + }); +}); diff --git a/packages/core-manager/package.json b/packages/core-manager/package.json index 32ef43d928..855aa04a5c 100644 --- a/packages/core-manager/package.json +++ b/packages/core-manager/package.json @@ -24,6 +24,7 @@ "@arkecosystem/core-cli": "^3.0.0-next.0", "@arkecosystem/core-database": "^3.0.0-next.0", "@arkecosystem/core-kernel": "^3.0.0-next.0", + "@arkecosystem/core-state": "^3.0.0-next.0", "@hapi/basic": "^6.0.0", "@hapi/boom": "^9.0.0", "@hapi/hapi": "^19.0.0", diff --git a/packages/core-manager/src/events.ts b/packages/core-manager/src/events.ts new file mode 100644 index 0000000000..d4a7d04431 --- /dev/null +++ b/packages/core-manager/src/events.ts @@ -0,0 +1,5 @@ +export enum WalletEvent { + PropertySet = "wallet.property.set", + AttributeSet = "wallet.attribute.set", + AttributeForget = "wallet.attribute.forget", +} diff --git a/packages/core-manager/src/service-provider.ts b/packages/core-manager/src/service-provider.ts index f966a17b89..05b08390e3 100644 --- a/packages/core-manager/src/service-provider.ts +++ b/packages/core-manager/src/service-provider.ts @@ -1,5 +1,5 @@ import { ApplicationFactory } from "@arkecosystem/core-cli"; -import { Container, Contracts, Providers, Types } from "@arkecosystem/core-kernel"; +import { Container, Contracts, Providers, Services, Types } from "@arkecosystem/core-kernel"; import { ActionReader } from "./action-reader"; import { DatabaseLogger } from "./database-logger"; @@ -12,6 +12,7 @@ import { PluginFactory } from "./server/plugins"; import { Server } from "./server/server"; import { Argon2id, SimpleTokenValidator } from "./server/validators"; import { SnapshotsManager } from "./snapshots/snapshots-manager"; +import { WatcherWallet } from "./watcher-wallet"; export class ServiceProvider extends Providers.ServiceProvider { public async register(): Promise { @@ -40,6 +41,18 @@ export class ServiceProvider extends Providers.ServiceProvider { const pkg: Types.PackageJson = require("../package.json"); this.app.bind(Identifiers.CLI).toConstantValue(ApplicationFactory.make(new Container.Container(), pkg)); + + this.app + .bind(Container.Identifiers.WalletFactory) + .toFactory((context: Container.interfaces.Context) => (address: string) => + new WatcherWallet( + context.container.get(Container.Identifiers.Application), + address, + new Services.Attributes.AttributeMap( + context.container.get(Container.Identifiers.WalletAttributes), + ), + ), + ); } /** @@ -63,11 +76,11 @@ export class ServiceProvider extends Providers.ServiceProvider { } public async dispose(): Promise { - if (this.config().get("server.http.enabled")) { + if (this.app.isBound(Identifiers.HTTP)) { await this.app.get(Identifiers.HTTP).dispose(); } - if (this.config().get("server.https.enabled")) { + if (this.app.isBound(Identifiers.HTTPS)) { await this.app.get(Identifiers.HTTPS).dispose(); } diff --git a/packages/core-manager/src/watcher-wallet.ts b/packages/core-manager/src/watcher-wallet.ts new file mode 100644 index 0000000000..86948c84a3 --- /dev/null +++ b/packages/core-manager/src/watcher-wallet.ts @@ -0,0 +1,75 @@ +import { Contracts, Services } from "@arkecosystem/core-kernel"; +import { Wallets } from "@arkecosystem/core-state"; +import { cloneDeep } from "@arkecosystem/utils"; + +import { WalletEvent } from "./events"; + +export class WatcherWallet extends Wallets.Wallet { + public constructor( + private app: Contracts.Kernel.Application, + address: string, + attributes: Services.Attributes.AttributeMap, + ) { + super(address, attributes); + + const handler: ProxyHandler = { + set(target, key, value) { + target.app?.events.dispatchSync(WalletEvent.PropertySet, { + publicKey: target.publicKey, + key: key, + value: value, + previousValue: target[key], + wallet: target, + }); + + target[key] = value; + return true; + }, + }; + + return new Proxy(this, handler); + } + + public setAttribute(key: string, value: T): boolean { + const isSet = super.setAttribute(key, value); + + this.app?.events.dispatchSync(WalletEvent.AttributeSet, { + publicKey: this.publicKey, + isSet: isSet, + key: key, + value: value, + wallet: this, + }); + + return isSet; + } + + public forgetAttribute(key: string): boolean { + const previousValue = super.getAttribute(key); + const isForget = super.forgetAttribute(key); + + this.app?.events.dispatchSync(WalletEvent.AttributeForget, { + publicKey: this.publicKey, + isForget: isForget, + key: key, + previousValue: previousValue, + wallet: this, + }); + + return isForget; + } + + public clone(): WatcherWallet { + const clone = new WatcherWallet(this.app, this.address, cloneDeep(this.attributes)); + + for (const key of Object.keys(this)) { + if (key === "app") { + continue; + } + + clone[key] = cloneDeep(this[key]); + } + + return clone; + } +} diff --git a/packages/core-state/src/wallets/indexers/wallet-indexes.ts b/packages/core-state/src/wallets/indexers/wallet-indexes.ts index 19552548f7..3c7a5c2192 100644 --- a/packages/core-state/src/wallets/indexers/wallet-indexes.ts +++ b/packages/core-state/src/wallets/indexers/wallet-indexes.ts @@ -43,13 +43,15 @@ export const registerIndexers = (app: Contracts.Kernel.Application): void => { }; export const registerFactories = (app: Contracts.Kernel.Application): void => { - app.bind(Container.Identifiers.WalletFactory).toFactory( - (context: Container.interfaces.Context) => (address: string) => - new Wallet( - address, - new Services.Attributes.AttributeMap( - context.container.get(Container.Identifiers.WalletAttributes), + if (!app.isBound(Container.Identifiers.WalletFactory)) { + app.bind(Container.Identifiers.WalletFactory).toFactory( + (context: Container.interfaces.Context) => (address: string) => + new Wallet( + address, + new Services.Attributes.AttributeMap( + context.container.get(Container.Identifiers.WalletAttributes), + ), ), - ), - ); + ); + } }; diff --git a/packages/core-state/src/wallets/wallet.ts b/packages/core-state/src/wallets/wallet.ts index 2d2af2765d..c8af377a3b 100644 --- a/packages/core-state/src/wallets/wallet.ts +++ b/packages/core-state/src/wallets/wallet.ts @@ -35,7 +35,7 @@ export class Wallet implements Contracts.State.Wallet { */ public constructor( public readonly address: string, - private readonly attributes: Services.Attributes.AttributeMap, + protected readonly attributes: Services.Attributes.AttributeMap, ) {} /**