diff --git a/__tests__/unit/core-state/wallets/wallet-manager.test.ts b/__tests__/unit/core-state/wallets/wallet-manager.test.ts index 4923a902eb..db64d1c9b7 100644 --- a/__tests__/unit/core-state/wallets/wallet-manager.test.ts +++ b/__tests__/unit/core-state/wallets/wallet-manager.test.ts @@ -49,7 +49,7 @@ describe("Wallet Manager", () => { }); }); - describe("applyBlock", () => { + describe("block processing", () => { let delegateMock; let block2: Interfaces.IBlock; @@ -64,7 +64,13 @@ describe("Wallet Manager", () => { } beforeEach(() => { - delegateMock = { applyBlock: jest.fn(), publicKey: delegatePublicKey, isDelegate: () => false }; + delegateMock = { + applyBlock: jest.fn(), + revertBlock: jest.fn(), + publicKey: delegatePublicKey, + isDelegate: () => false, + }; + // @ts-ignore jest.spyOn(walletManager, "findByPublicKey").mockReturnValue(delegateMock); jest.spyOn(walletManager, "applyTransaction").mockImplementation(); @@ -81,78 +87,106 @@ describe("Wallet Manager", () => { walletManager.reindex(delegateMock); }); - it("should apply sequentially the transactions of the block", async () => { - await walletManager.applyBlock(block2); + describe("applyBlock", () => { + it("should apply sequentially the transactions of the block", async () => { + await walletManager.applyBlock(block2); - for (let i = 0; i < block2.transactions.length; i++) { - expect(walletManager.applyTransaction).toHaveBeenNthCalledWith(i + 1, block2.transactions[i]); - } - }); + for (let i = 0; i < block2.transactions.length; i++) { + expect(walletManager.applyTransaction).toHaveBeenNthCalledWith(i + 1, block2.transactions[i]); + } + }); - it("should apply the block data to the delegate", async () => { - await walletManager.applyBlock(block); + it("should apply the block data to the delegate", async () => { + await walletManager.applyBlock(block); - expect(delegateMock.applyBlock).toHaveBeenCalledWith(block.data); - }); + expect(delegateMock.applyBlock).toHaveBeenCalledWith(block.data); + }); - describe("when 1 transaction fails while applying it", () => { - it("should revert sequentially (from last to first) all the transactions of the block", async () => { - // @ts-ignore - jest.spyOn(walletManager, "applyTransaction").mockImplementation(tx => { - if (tx === block2.transactions[2]) { - throw new Error("Fake error"); + describe("when 1 transaction fails while applying it", () => { + it("should revert sequentially (from last to first) all the transactions of the block", async () => { + // @ts-ignore + jest.spyOn(walletManager, "applyTransaction").mockImplementation(tx => { + if (tx === block2.transactions[2]) { + throw new Error("Fake error"); + } + }); + + expect(block2.transactions.length).toBe(3); + + try { + await walletManager.applyBlock(block2); + + expect(undefined).toBe("this should fail if no error is thrown"); + } catch (error) { + expect(walletManager.revertTransaction).toHaveBeenCalledTimes(2); + // tslint:disable-next-line: ban + block2.transactions.slice(0, 1).forEach((transaction, i, total) => { + expect(walletManager.revertTransaction).toHaveBeenNthCalledWith( + total.length + 1 - i, + block2.transactions[i], + ); + }); } }); - expect(block2.transactions.length).toBe(3); + it("throws the Error", async () => { + walletManager.applyTransaction = jest.fn(tx => { + throw new Error("Fake error"); + }); - try { - await walletManager.applyBlock(block2); + try { + await walletManager.applyBlock(block2); - expect(undefined).toBe("this should fail if no error is thrown"); - } catch (error) { - expect(walletManager.revertTransaction).toHaveBeenCalledTimes(2); - // tslint:disable-next-line: ban - block2.transactions.slice(0, 1).forEach((transaction, i, total) => { - expect(walletManager.revertTransaction).toHaveBeenNthCalledWith( - total.length + 1 - i, - block2.transactions[i], - ); - }); - } + expect(undefined).toBe("this should fail if no error is thrown"); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect(error.message).toBe("Fake error"); + } + }); }); - it("throws the Error", async () => { - walletManager.applyTransaction = jest.fn(tx => { - throw new Error("Fake error"); - }); + it("should return the current block", async () => { + expect(walletManager.getCurrentBlock()).toBeUndefined(); - try { - await walletManager.applyBlock(block2); + const applyTransaction = jest.spyOn(walletManager, "applyTransaction").mockImplementationOnce(() => { + expect(walletManager.getCurrentBlock()).toBe(block2); + }); - expect(undefined).toBe("this should fail if no error is thrown"); - } catch (error) { - expect(error).toBeInstanceOf(Error); - expect(error.message).toBe("Fake error"); - } + await walletManager.applyBlock(block2); + expect(applyTransaction).toHaveBeenCalled(); + expect(walletManager.getCurrentBlock()).toBeUndefined(); }); - }); - describe.skip("the delegate of the block is not indexed", () => { - describe("not genesis block", () => { - it("throw an Error", () => {}); - }); + describe.skip("the delegate of the block is not indexed", () => { + describe("not genesis block", () => { + it("throw an Error", () => {}); + }); - describe("genesis block", () => { - it("generates a new wallet", () => {}); + describe("genesis block", () => { + it("generates a new wallet", () => {}); + }); }); }); - }); - describe.skip("revertBlock", () => { - it("should revert all transactions of the block", () => {}); + describe("revertBlock", () => { + it.todo("should revert all transactions of the block"); + + it.todo("should revert the block of the delegate"); + + it("should return the current block", async () => { + expect(walletManager.getCurrentBlock()).toBeUndefined(); - it("should revert the block of the delegate", () => {}); + const revertTransaction = jest + .spyOn(walletManager, "revertTransaction") + .mockImplementationOnce(async () => { + expect(walletManager.getCurrentBlock()).toBe(block2); + }); + + await walletManager.revertBlock(block2); + expect(revertTransaction).toHaveBeenCalled(); + expect(walletManager.getCurrentBlock()).toBeUndefined(); + }); + }); }); describe("applyTransaction", () => { diff --git a/packages/core-interfaces/src/core-state/wallets.ts b/packages/core-interfaces/src/core-state/wallets.ts index 31c6355ae1..88a1d8deda 100644 --- a/packages/core-interfaces/src/core-state/wallets.ts +++ b/packages/core-interfaces/src/core-state/wallets.ts @@ -95,6 +95,8 @@ export interface IWalletManager { reindex(wallet: IWallet): void; + getCurrentBlock(): Readonly; + clone(): IWalletManager; loadActiveDelegateList(roundInfo: IRoundInfo): IWallet[]; diff --git a/packages/core-state/src/wallets/wallet-manager.ts b/packages/core-state/src/wallets/wallet-manager.ts index e1f3cba550..2f9df25172 100644 --- a/packages/core-state/src/wallets/wallet-manager.ts +++ b/packages/core-state/src/wallets/wallet-manager.ts @@ -13,6 +13,7 @@ export class WalletManager implements State.IWalletManager { public logger: Logger.ILogger = app.resolvePlugin("logger"); private readonly indexes: Record = {}; + private currentBlock: Interfaces.IBlock; constructor() { this.reset(); @@ -174,6 +175,10 @@ export class WalletManager implements State.IWalletManager { } } + public getCurrentBlock(): Readonly { + return this.currentBlock; + } + public clone(): WalletManager { return new TempWalletManager(this); } @@ -206,6 +211,7 @@ export class WalletManager implements State.IWalletManager { } public async applyBlock(block: Interfaces.IBlock): Promise { + this.currentBlock = block; const generatorPublicKey: string = block.data.generatorPublicKey; let delegate: State.IWallet; @@ -252,6 +258,8 @@ export class WalletManager implements State.IWalletManager { } throw error; + } finally { + this.currentBlock = undefined; } } @@ -259,6 +267,7 @@ export class WalletManager implements State.IWalletManager { if (!this.has(block.data.generatorPublicKey)) { app.forceExit(`Failed to lookup generator '${block.data.generatorPublicKey}' of block '${block.data.id}'.`); } + this.currentBlock = block; const delegate: State.IWallet = this.findByPublicKey(block.data.generatorPublicKey); const revertedTransactions: Interfaces.ITransaction[] = []; @@ -290,6 +299,8 @@ export class WalletManager implements State.IWalletManager { } throw error; + } finally { + this.currentBlock = undefined; } }