diff --git a/.circleci/config.yml b/.circleci/config.yml index dfaa56267e..78acafff47 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,6 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules - ./packages/core-error-tracker-bugsnag/node_modules - ./packages/core-error-tracker-sentry/node_modules @@ -55,6 +54,7 @@ jobs: - ./packages/core-logger-pino/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules + - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules - ./packages/core-utils/node_modules @@ -96,11 +96,11 @@ jobs: test:coverage /unit/core-database-postgres/ --coverageDirectory .coverage/unit/core-database-postgres - run: - name: core-debugger-cli - unit + name: core-event-emitter - unit command: >- cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn - test:coverage /unit/core-debugger-cli/ --coverageDirectory - .coverage/unit/core-debugger-cli + test:coverage /unit/core-event-emitter/ --coverageDirectory + .coverage/unit/core-event-emitter - run: name: core-api - integration command: >- @@ -164,7 +164,6 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules - ./packages/core-error-tracker-bugsnag/node_modules - ./packages/core-error-tracker-sentry/node_modules @@ -178,6 +177,7 @@ jobs: - ./packages/core-logger-pino/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules + - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules - ./packages/core-utils/node_modules @@ -219,11 +219,11 @@ jobs: test:coverage /unit/core-database-postgres/ --coverageDirectory .coverage/unit/core-database-postgres - run: - name: core-debugger-cli - unit + name: core-event-emitter - unit command: >- cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn - test:coverage /unit/core-debugger-cli/ --coverageDirectory - .coverage/unit/core-debugger-cli + test:coverage /unit/core-event-emitter/ --coverageDirectory + .coverage/unit/core-event-emitter - run: name: core-api - integration command: >- @@ -287,7 +287,6 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules - ./packages/core-error-tracker-bugsnag/node_modules - ./packages/core-error-tracker-sentry/node_modules @@ -301,6 +300,7 @@ jobs: - ./packages/core-logger-pino/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules + - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules - ./packages/core-utils/node_modules @@ -311,12 +311,6 @@ jobs: - run: name: Create .core/database directory command: mkdir -p $HOME/.core/database - - run: - name: core-event-emitter - unit - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn - test:coverage /unit/core-event-emitter/ --coverageDirectory - .coverage/unit/core-event-emitter - run: name: core-forger - unit command: >- @@ -422,7 +416,6 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules - ./packages/core-error-tracker-bugsnag/node_modules - ./packages/core-error-tracker-sentry/node_modules @@ -436,6 +429,7 @@ jobs: - ./packages/core-logger-pino/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules + - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules - ./packages/core-utils/node_modules @@ -539,7 +533,6 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules - ./packages/core-error-tracker-bugsnag/node_modules - ./packages/core-error-tracker-sentry/node_modules @@ -553,6 +546,7 @@ jobs: - ./packages/core-logger-pino/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules + - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules - ./packages/core-utils/node_modules @@ -563,12 +557,6 @@ jobs: - run: name: Create .core/database directory command: mkdir -p $HOME/.core/database - - run: - name: core-event-emitter - unit - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn - test:coverage /unit/core-event-emitter/ --coverageDirectory - .coverage/unit/core-event-emitter - run: name: core-forger - unit command: >- @@ -674,7 +662,6 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules - ./packages/core-error-tracker-bugsnag/node_modules - ./packages/core-error-tracker-sentry/node_modules @@ -688,6 +675,7 @@ jobs: - ./packages/core-logger-pino/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules + - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules - ./packages/core-utils/node_modules diff --git a/__tests__/integration/core-p2p/server/internal.test.ts b/__tests__/integration/core-p2p/server/internal.test.ts index a40284344d..15f0529674 100644 --- a/__tests__/integration/core-p2p/server/internal.test.ts +++ b/__tests__/integration/core-p2p/server/internal.test.ts @@ -1,8 +1,8 @@ -import { generateTransfers } from "../../../utils/generators/transactions/transfer"; import { models, Transaction } from "@arkecosystem/crypto"; -import blockFixture from "../fixtures/block.json"; +import { generateTransfers } from "../../../utils/generators/transactions/transfer"; import { setUp, tearDown } from "../__support__/setup"; import { utils } from "../__support__/utils"; +import blockFixture from "../fixtures/block.json"; const { Block } = models; diff --git a/__tests__/unit/core-debugger-cli/utils.test.ts b/__tests__/unit/core-debugger-cli/utils.test.ts deleted file mode 100644 index c853cb3541..0000000000 --- a/__tests__/unit/core-debugger-cli/utils.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { readSync } from "clipboardy"; -import "jest-extended"; - -import { copyToClipboard, handleOutput } from "../../../packages/core-debugger-cli/src/utils"; - -const dummyData = { hello: "world" }; - -describe("Utils", () => { - describe("copyToClipboard", () => { - it("should contain the copied data", () => { - copyToClipboard(dummyData); - - expect(JSON.parse(readSync())).toEqual(dummyData); - }); - }); - - describe("handleOutput", () => { - it("should copy the data", () => { - handleOutput({ copy: true }, dummyData); - - expect(JSON.parse(readSync())).toEqual(dummyData); - }); - - it("should log the data", () => { - const method = jest.spyOn(global.console, "log"); - - handleOutput({ log: true }, dummyData); - - expect(method).toHaveBeenCalledWith(dummyData); - }); - - it("should return the data", () => { - expect(handleOutput({}, dummyData)).toEqual(dummyData); - }); - }); -}); diff --git a/__tests__/unit/core-debugger-cli/__fixtures__/block.json b/__tests__/unit/core-tester-cli/__fixtures__/block.json similarity index 100% rename from __tests__/unit/core-debugger-cli/__fixtures__/block.json rename to __tests__/unit/core-tester-cli/__fixtures__/block.json diff --git a/__tests__/unit/core-debugger-cli/__fixtures__/identities.json b/__tests__/unit/core-tester-cli/__fixtures__/identities.json similarity index 100% rename from __tests__/unit/core-debugger-cli/__fixtures__/identities.json rename to __tests__/unit/core-tester-cli/__fixtures__/identities.json diff --git a/__tests__/unit/core-debugger-cli/__fixtures__/transaction-second.json b/__tests__/unit/core-tester-cli/__fixtures__/transaction-second.json similarity index 85% rename from __tests__/unit/core-debugger-cli/__fixtures__/transaction-second.json rename to __tests__/unit/core-tester-cli/__fixtures__/transaction-second.json index 1f7dc144d0..8a0ebe0907 100644 --- a/__tests__/unit/core-debugger-cli/__fixtures__/transaction-second.json +++ b/__tests__/unit/core-tester-cli/__fixtures__/transaction-second.json @@ -8,7 +8,7 @@ "asset": {}, "senderPublicKey": "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", "signature": "304402206da703bfcc11ec2ccb3f363fa0e23fc64050fdf68e1f1852b7d4a5bb07824166022031ed1d86b586a79f9c1e5010dbc4f4cb36641c62a196536f90b1dfd6be1c9868", - "secondSignature": "304402200759b6f9de5257aa3fcf54b9cd7a426a00af9368b7ea3d5ea2b13a91b97fb277022076e4d2d7deb9bdd8245b2533cab1eeeef72981e18576ef8455a61ee3e6f3fb57", + "signSignature": "304402200759b6f9de5257aa3fcf54b9cd7a426a00af9368b7ea3d5ea2b13a91b97fb277022076e4d2d7deb9bdd8245b2533cab1eeeef72981e18576ef8455a61ee3e6f3fb57", "id": "bb8054b6298d659d4b5d655e82de17b3504ba27655ec3d6e35d311f3104b1c43" }, "serialized": "ff011e00ceb47502034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b00000000000000001e0995750207ecaf0ccf251c1265b92ad84f553662304402206da703bfcc11ec2ccb3f363fa0e23fc64050fdf68e1f1852b7d4a5bb07824166022031ed1d86b586a79f9c1e5010dbc4f4cb36641c62a196536f90b1dfd6be1c9868304402200759b6f9de5257aa3fcf54b9cd7a426a00af9368b7ea3d5ea2b13a91b97fb277022076e4d2d7deb9bdd8245b2533cab1eeeef72981e18576ef8455a61ee3e6f3fb57" diff --git a/__tests__/unit/core-debugger-cli/__fixtures__/transaction.json b/__tests__/unit/core-tester-cli/__fixtures__/transaction.json similarity index 100% rename from __tests__/unit/core-debugger-cli/__fixtures__/transaction.json rename to __tests__/unit/core-tester-cli/__fixtures__/transaction.json diff --git a/__tests__/unit/core-debugger-cli/commands/deserialize.test.ts b/__tests__/unit/core-tester-cli/commands/debug/deserialize.test.ts similarity index 92% rename from __tests__/unit/core-debugger-cli/commands/deserialize.test.ts rename to __tests__/unit/core-tester-cli/commands/debug/deserialize.test.ts index 7254b0d104..3775c3b78c 100644 --- a/__tests__/unit/core-debugger-cli/commands/deserialize.test.ts +++ b/__tests__/unit/core-tester-cli/commands/debug/deserialize.test.ts @@ -1,10 +1,10 @@ import "jest-extended"; -import { DeserializeCommand } from "../../../../packages/core-debugger-cli/src/commands/deserialize"; +import { DeserializeCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/deserialize"; describe("Commands - Deserialize", () => { - const fixtureBlock = require("../__fixtures__/block.json"); - const fixtureTransaction = require("../__fixtures__/transaction.json"); + const fixtureBlock = require("../../__fixtures__/block.json"); + const fixtureTransaction = require("../../__fixtures__/transaction.json"); it("should deserialize a block (not-full)", async () => { const actual = JSON.parse(await DeserializeCommand.run(["--data", fixtureBlock.serialized, "--type", "block"])); diff --git a/__tests__/unit/core-debugger-cli/commands/identity.test.ts b/__tests__/unit/core-tester-cli/commands/debug/identity.test.ts similarity index 89% rename from __tests__/unit/core-debugger-cli/commands/identity.test.ts rename to __tests__/unit/core-tester-cli/commands/debug/identity.test.ts index 80082b1741..1362648740 100644 --- a/__tests__/unit/core-debugger-cli/commands/identity.test.ts +++ b/__tests__/unit/core-tester-cli/commands/debug/identity.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; -import { IdentityCommand } from "../../../../packages/core-debugger-cli/src/commands/identity"; +import { IdentityCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/identity"; describe("Commands - Identity", async () => { - const fixtureIdentities = require("../__fixtures__/identities.json"); + const fixtureIdentities = require("../../__fixtures__/identities.json"); it("should return identities from passphrase", async () => { const expected = { diff --git a/__tests__/unit/core-debugger-cli/commands/serialize.test.ts b/__tests__/unit/core-tester-cli/commands/debug/serialize.test.ts similarity index 76% rename from __tests__/unit/core-debugger-cli/commands/serialize.test.ts rename to __tests__/unit/core-tester-cli/commands/debug/serialize.test.ts index e0da4b4b9f..6e26821bb8 100644 --- a/__tests__/unit/core-debugger-cli/commands/serialize.test.ts +++ b/__tests__/unit/core-tester-cli/commands/debug/serialize.test.ts @@ -1,10 +1,10 @@ import "jest-extended"; -import { SerializeCommand } from "../../../../packages/core-debugger-cli/src/commands/serialize"; +import { SerializeCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/serialize"; describe("Commands - Serialize", () => { - const fixtureBlock = require("../__fixtures__/block.json"); - const fixtureTransaction = require("../__fixtures__/transaction.json"); + const fixtureBlock = require("../../__fixtures__/block.json"); + const fixtureTransaction = require("../../__fixtures__/transaction.json"); it("should serialize a block (not-full)", async () => { expect(await SerializeCommand.run(["--data", JSON.stringify(fixtureBlock.data), "--type", "block"])).toEqual( diff --git a/__tests__/unit/core-debugger-cli/commands/verify-second.test.ts b/__tests__/unit/core-tester-cli/commands/debug/verify-second.test.ts similarity index 66% rename from __tests__/unit/core-debugger-cli/commands/verify-second.test.ts rename to __tests__/unit/core-tester-cli/commands/debug/verify-second.test.ts index 2873836546..40b8236663 100644 --- a/__tests__/unit/core-debugger-cli/commands/verify-second.test.ts +++ b/__tests__/unit/core-tester-cli/commands/debug/verify-second.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; -import { VerifySecondSignatureCommand } from "../../../../packages/core-debugger-cli/src/commands/verify-second"; +import { VerifySecondSignatureCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/verify-second-signature"; describe("Commands - Verify Second", () => { - const fixtureTransaction = require("../__fixtures__/transaction-second.json"); + const fixtureTransaction = require("../../__fixtures__/transaction-second.json"); it("should verify a second signature", async () => { expect( diff --git a/__tests__/unit/core-debugger-cli/commands/verify.test.ts b/__tests__/unit/core-tester-cli/commands/debug/verify.test.ts similarity index 62% rename from __tests__/unit/core-debugger-cli/commands/verify.test.ts rename to __tests__/unit/core-tester-cli/commands/debug/verify.test.ts index 97bf79d731..8425ac3ebb 100644 --- a/__tests__/unit/core-debugger-cli/commands/verify.test.ts +++ b/__tests__/unit/core-tester-cli/commands/debug/verify.test.ts @@ -1,10 +1,10 @@ import "jest-extended"; -import { VerifyCommand } from "../../../../packages/core-debugger-cli/src/commands/verify"; +import { VerifyCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/verify"; describe("Commands - Verify", () => { - const fixtureBlock = require("../__fixtures__/block.json"); - const fixtureTransaction = require("../__fixtures__/transaction.json"); + const fixtureBlock = require("../../__fixtures__/block.json"); + const fixtureTransaction = require("../../__fixtures__/transaction.json"); it("should verify a block", async () => { expect(await VerifyCommand.run(["--data", fixtureBlock.serializedFull, "--type", "block"])).toBeTrue(); diff --git a/__tests__/unit/core-tester-cli/commands/delegate-registration.test.ts b/__tests__/unit/core-tester-cli/commands/delegate-registration.test.ts deleted file mode 100644 index 37e5292822..0000000000 --- a/__tests__/unit/core-tester-cli/commands/delegate-registration.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import "jest-extended"; - -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import superheroes from "superheroes"; -import { DelegateRegistrationCommand } from "../../../../packages/core-tester-cli/src/commands/delegate-registration"; -import { arkToSatoshi } from "../../../../packages/core-tester-cli/src/utils"; -import { toFlags } from "../shared"; - -const mockAxios = new MockAdapter(axios); - -beforeEach(() => { - // Just passthru. We'll test the Command class logic in its own test file more thoroughly - mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); - mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); - jest.spyOn(axios, "get"); - jest.spyOn(axios, "post"); -}); - -afterEach(() => { - mockAxios.reset(); -}); - -describe("Commands - Delegate Registration", () => { - it("should register as delegate", async () => { - const opts = { - delegateFee: 1, - number: 1, - }; - - const expectedDelegateName = "mr_bojangles"; - // call to delegates/{publicKey}/voters returns zero delegates - mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, { - meta: { pageCount: 1 }, - data: [], - }); - jest.spyOn(superheroes, "random").mockImplementation(() => expectedDelegateName); - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - - const flags = toFlags(opts); - await DelegateRegistrationCommand.run(flags); - - expect(axios.post).toHaveBeenCalledWith( - "http://localhost:4003/api/v2/transactions", - { - transactions: [ - expect.objectContaining({ - fee: arkToSatoshi(opts.delegateFee), - asset: { - delegate: { - username: expectedDelegateName, - }, - }, - }), - ], - }, - expect.any(Object), - ); - }); -}); diff --git a/__tests__/unit/core-tester-cli/commands/second-signature.test.ts b/__tests__/unit/core-tester-cli/commands/second-signature.test.ts deleted file mode 100644 index 996b68ea6f..0000000000 --- a/__tests__/unit/core-tester-cli/commands/second-signature.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import "jest-extended"; - -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { SecondSignatureCommand } from "../../../../packages/core-tester-cli/src/commands/second-signature"; -import { arkToSatoshi } from "../../../../packages/core-tester-cli/src/utils"; -import { toFlags } from "../shared"; - -const mockAxios = new MockAdapter(axios); - -beforeEach(() => { - // Just passthru. We'll test the Command class logic in its own test file more thoroughly - mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); - mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); - jest.spyOn(axios, "get"); - jest.spyOn(axios, "post"); -}); - -afterEach(() => { - mockAxios.reset(); -}); - -describe("Commands - Second signature", () => { - it("should apply second signature", async () => { - const opts = { - signatureFee: 1, - number: 1, - }; - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - - const flags = toFlags(opts); - await SecondSignatureCommand.run(flags); - - expect(axios.post).toHaveBeenCalledWith( - "http://localhost:4003/api/v2/transactions", - { - transactions: [ - expect.objectContaining({ - fee: arkToSatoshi(opts.signatureFee), - asset: { - signature: { - publicKey: expect.any(String), - }, - }, - }), - ], - }, - expect.any(Object), - ); - }); -}); diff --git a/__tests__/unit/core-tester-cli/commands/send/delegate-registration.test.ts b/__tests__/unit/core-tester-cli/commands/send/delegate-registration.test.ts new file mode 100644 index 0000000000..23145778c2 --- /dev/null +++ b/__tests__/unit/core-tester-cli/commands/send/delegate-registration.test.ts @@ -0,0 +1,54 @@ +import "jest-extended"; + +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import pokemon from "pokemon"; +import { DelegateRegistrationCommand } from "../../../../../packages/core-tester-cli/src/commands/send/delegate-registration"; +import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared"; + +const mockAxios = new MockAdapter(axios); + +beforeEach(() => { + // Just passthru. We'll test the Command class logic in its own test file more thoroughly + mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); + mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); + jest.spyOn(axios, "get"); + jest.spyOn(axios, "post"); +}); + +afterEach(() => { + mockAxios.reset(); +}); + +describe("Commands - Delegate Registration", () => { + it("should register as delegate", async () => { + const opts = { + delegateFee: 1, + number: 1, + }; + + const expectedDelegateName = "mr_bojangles"; + // call to delegates/{publicKey}/voters returns zero delegates + mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, { + meta: { pageCount: 1 }, + data: [], + }); + jest.spyOn(pokemon, "random").mockImplementation(() => expectedDelegateName); + + const expectedTransactions = []; + captureTransactions(mockAxios, expectedTransactions); + + await DelegateRegistrationCommand.run(toFlags(opts)); + + expect(axios.post).toHaveBeenCalledTimes(2); + + expectTransactions(expectedTransactions, { + fee: arkToSatoshi(opts.delegateFee), + asset: { + delegate: { + username: expectedDelegateName, + }, + }, + }); + }); +}); diff --git a/__tests__/unit/core-tester-cli/commands/send/second-signature.test.ts b/__tests__/unit/core-tester-cli/commands/send/second-signature.test.ts new file mode 100644 index 0000000000..f0979127b7 --- /dev/null +++ b/__tests__/unit/core-tester-cli/commands/send/second-signature.test.ts @@ -0,0 +1,45 @@ +import "jest-extended"; + +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import { SecondSignatureRegistrationCommand } from "../../../../../packages/core-tester-cli/src/commands/send/second-signature-registration"; +import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared"; + +const mockAxios = new MockAdapter(axios); + +beforeEach(() => { + // Just passthru. We'll test the Command class logic in its own test file more thoroughly + mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); + mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); + jest.spyOn(axios, "get"); + jest.spyOn(axios, "post"); +}); + +afterEach(() => { + mockAxios.reset(); +}); + +describe("Commands - Second signature", () => { + it("should apply second signature", async () => { + const opts = { + signatureFee: 1, + number: 1, + }; + + const expectedTransactions = []; + captureTransactions(mockAxios, expectedTransactions); + + await SecondSignatureRegistrationCommand.run(toFlags(opts)); + + expect(axios.post).toHaveBeenCalledTimes(2); + + expectTransactions(expectedTransactions, { + fee: arkToSatoshi(opts.signatureFee), + asset: { + signature: { + publicKey: expect.any(String), + }, + }, + }); + }); +}); diff --git a/__tests__/unit/core-tester-cli/commands/transfer.test.ts b/__tests__/unit/core-tester-cli/commands/send/transfer.test.ts similarity index 55% rename from __tests__/unit/core-tester-cli/commands/transfer.test.ts rename to __tests__/unit/core-tester-cli/commands/send/transfer.test.ts index 2f0f048f5e..aa9170fb3a 100644 --- a/__tests__/unit/core-tester-cli/commands/transfer.test.ts +++ b/__tests__/unit/core-tester-cli/commands/send/transfer.test.ts @@ -2,9 +2,8 @@ import "jest-extended"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { TransferCommand } from "../../../../packages/core-tester-cli/src/commands/transfer"; -import { arkToSatoshi } from "../../../../packages/core-tester-cli/src/utils"; -import { toFlags } from "../shared"; +import { TransferCommand } from "../../../../../packages/core-tester-cli/src/commands/send/transfer"; +import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared"; const mockAxios = new MockAdapter(axios); @@ -31,30 +30,21 @@ describe("Commands - Transfer", () => { amount: expectedTransactionAmount, transferFee: expectedFee, number: 1, - smartBridge: "foo bar", + vendorField: "foo bar", recipient: expectedRecipientId, }; - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - let expectedTransactions = []; - // @ts-ignore - jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { - expectedTransactions = transactions; - }); + const expectedTransactions = []; + captureTransactions(mockAxios, expectedTransactions); + + await TransferCommand.run(toFlags(opts)); - const flags = toFlags(opts); - await TransferCommand.run(flags); - - expect(expectedTransactions).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - vendorField: "foo bar", - amount: arkToSatoshi(expectedTransactionAmount), - fee: arkToSatoshi(expectedFee), - recipientId: expectedRecipientId, - }), - ]), - ); + expectTransactions(expectedTransactions, { + vendorField: "foo bar", + amount: arkToSatoshi(expectedTransactionAmount), + fee: arkToSatoshi(expectedFee), + recipientId: expectedRecipientId, + }); }); it("should generate n transactions", async () => { @@ -67,15 +57,10 @@ describe("Commands - Transfer", () => { recipient: expectedRecipientId, }; - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - let expectedTransactions = []; - // @ts-ignore - jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { - expectedTransactions = transactions; - }); + const expectedTransactions = []; + captureTransactions(mockAxios, expectedTransactions); - const flags = toFlags(opts); - await TransferCommand.run(flags); + await TransferCommand.run(toFlags(opts)); expect(expectedTransactions).toHaveLength(expectedTxCount); for (const t of expectedTransactions) { @@ -95,15 +80,10 @@ describe("Commands - Transfer", () => { recipient: expectedRecipientId, }; - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - let expectedTransactions = []; - // @ts-ignore - jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { - expectedTransactions = transactions; - }); + const expectedTransactions = []; + captureTransactions(mockAxios, expectedTransactions); - const flags = toFlags(opts); - await TransferCommand.run(flags); + await TransferCommand.run(toFlags(opts)); expect(expectedTransactions).toHaveLength(expectedTxCount); for (const t of expectedTransactions) { @@ -124,19 +104,14 @@ describe("Commands - Transfer", () => { recipient: expectedRecipientId, }; - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - let expectedTransactions = []; - // @ts-ignore - jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { - expectedTransactions = transactions; - }); + const expectedTransactions = []; + captureTransactions(mockAxios, expectedTransactions); - const flags = toFlags(opts); - await TransferCommand.run(flags); + await TransferCommand.run(toFlags(opts)); expect(expectedTransactions).toHaveLength(1); - for (const t of expectedTransactions) { - expect(t.signSignature).toBeDefined(); + for (const transaction of expectedTransactions) { + expect(transaction.secondSignature).toBeDefined(); } }); }); diff --git a/__tests__/unit/core-tester-cli/commands/send/vote.test.ts b/__tests__/unit/core-tester-cli/commands/send/vote.test.ts new file mode 100644 index 0000000000..19b1b8d678 --- /dev/null +++ b/__tests__/unit/core-tester-cli/commands/send/vote.test.ts @@ -0,0 +1,75 @@ +import "jest-extended"; + +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import { VoteCommand } from "../../../../../packages/core-tester-cli/src/commands/send/vote"; +import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared"; + +const mockAxios = new MockAdapter(axios); + +beforeEach(() => { + // Just passthru. We'll test the Command class logic in its own test file more thoroughly + mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); + mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); + jest.spyOn(axios, "get"); + jest.spyOn(axios, "post"); +}); + +afterEach(() => { + mockAxios.reset(); + jest.restoreAllMocks(); +}); + +afterAll(() => mockAxios.restore()); + +describe("Commands - Vote", () => { + it("should vote for specified delegate", async () => { + const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00"; + const opts = { + number: 1, + voteFee: 1, + delegate: expectedDelegate, + }; + + const expectedTransactions = []; + captureTransactions(mockAxios, expectedTransactions); + + await VoteCommand.run(toFlags(opts)); + + expect(axios.post).toHaveBeenCalledTimes(2); + + expectTransactions(expectedTransactions, { + fee: arkToSatoshi(opts.voteFee), + asset: { + votes: [`+${expectedDelegate}`], + }, + }); + }); + + it("should vote random delegate if non specified", async () => { + const expectedDelegate = "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37"; + const opts = { + number: 1, + voteFee: 1, + }; + + mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, { + meta: { pageCount: 1 }, + data: [{ publicKey: expectedDelegate }], + }); + + const expectedTransactions = []; + captureTransactions(mockAxios, expectedTransactions); + + await VoteCommand.run(toFlags(opts)); + + expect(axios.post).toHaveBeenCalledTimes(2); + + expectTransactions(expectedTransactions, { + fee: arkToSatoshi(opts.voteFee), + asset: { + votes: [`+${expectedDelegate}`], + }, + }); + }); +}); diff --git a/__tests__/unit/core-tester-cli/commands/vote.test.ts b/__tests__/unit/core-tester-cli/commands/vote.test.ts deleted file mode 100644 index 1cf7c63a11..0000000000 --- a/__tests__/unit/core-tester-cli/commands/vote.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import "jest-extended"; - -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { VoteCommand } from "../../../../packages/core-tester-cli/src/commands/vote"; -import { arkToSatoshi } from "../../../../packages/core-tester-cli/src/utils"; -import { toFlags } from "../shared"; - -const mockAxios = new MockAdapter(axios); - -beforeEach(() => { - // Just passthru. We'll test the Command class logic in its own test file more thoroughly - mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); - mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); - jest.spyOn(axios, "get"); - jest.spyOn(axios, "post"); -}); - -afterEach(() => { - mockAxios.reset(); -}); - -afterAll(() => mockAxios.restore()); - -describe("Commands - Vote", () => { - it("should vote for specified delegate", async () => { - const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00"; - const opts = { - number: 1, - voteFee: 1, - delegate: expectedDelegate, - }; - - mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates.*/).reply(200); - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - - const flags = toFlags(opts); - await VoteCommand.run(flags); - - expect(axios.post).toHaveBeenCalledWith( - "http://localhost:4003/api/v2/transactions", - { - transactions: [ - expect.objectContaining({ - fee: arkToSatoshi(opts.voteFee), - asset: { - votes: [`+${expectedDelegate}`], - }, - }), - ], - }, - expect.any(Object), - ); - }); - - it("should vote random delegate if non specified", async () => { - const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00"; - const opts = { - number: 1, - voteFee: 1, - }; - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates\/.*/).reply(200); // call to delegates/{publicKey}/voters - // call to /delegates - mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, { - meta: { pageCount: 1 }, - data: [{ publicKey: expectedDelegate }], - }); - - const flags = toFlags(opts); - await VoteCommand.run(flags); - - expect(axios.post).toHaveBeenCalledWith( - "http://localhost:4003/api/v2/transactions", - { - transactions: [ - expect.objectContaining({ - fee: arkToSatoshi(opts.voteFee), - asset: { - votes: [`+${expectedDelegate}`], - }, - }), - ], - }, - expect.any(Object), - ); - }); -}); diff --git a/__tests__/unit/core-tester-cli/shared.ts b/__tests__/unit/core-tester-cli/shared.ts index 1562862cb6..a9a6e26131 100644 --- a/__tests__/unit/core-tester-cli/shared.ts +++ b/__tests__/unit/core-tester-cli/shared.ts @@ -1,7 +1,29 @@ -const defaultOpts = ["--skipTesting", "--skipValidation"]; +import { bignumify } from "@arkecosystem/core-utils"; +import axios from "axios"; + +const defaultOpts = ["--skipProbing"]; export const toFlags = (opts: object): string[] => { return Object.keys(opts) .map(k => [`--${k}`, String(opts[k])]) .reduce((a, b) => a.concat(b), defaultOpts); }; + +export const arkToSatoshi = value => + bignumify(value) + .times(1e8) + .toFixed(); + +export const expectTransactions = (transactions, obj) => + expect(transactions).toEqual(expect.arrayContaining([expect.objectContaining(obj)])); + +export const captureTransactions = (mockAxios, expectedTransactions) => { + mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); + + // @ts-ignore + jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { + for (const transaction of transactions) { + expectedTransactions.push(transaction); + } + }); +}; diff --git a/__tests__/unit/core-tester-cli/utils.test.ts b/__tests__/unit/core-tester-cli/utils.test.ts deleted file mode 100644 index f9318aa310..0000000000 --- a/__tests__/unit/core-tester-cli/utils.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { arkToSatoshi, parseFee, satoshiToArk } from "../../../packages/core-tester-cli/src/utils"; - -describe("Utils", () => { - describe("parseFee", () => { - it("should give satoshi", () => { - expect(parseFee(0.1).toString()).toBe("10000000"); - expect(parseFee(1).toString()).toBe("100000000"); - expect(parseFee(10).toString()).toBe("1000000000"); - expect(parseFee("0.1").toString()).toBe("10000000"); - expect(parseFee("1").toString()).toBe("100000000"); - expect(parseFee("10").toString()).toBe("1000000000"); - expect(parseFee("0.001-0.005").toNumber()).toBeWithin(100000, 500000); - }); - }); - - describe("arkToSatoshi", () => { - it("should give satoshi", () => { - expect(arkToSatoshi(0.00000001).toString()).toBe("1"); - expect(arkToSatoshi(0.1).toString()).toBe("10000000"); - expect(arkToSatoshi(1).toString()).toBe("100000000"); - expect(arkToSatoshi(10).toString()).toBe("1000000000"); - }); - }); - - describe("satoshiToArk", () => { - it("should give ark", () => { - expect(satoshiToArk(1)).toBe("0.00000001 DѦ"); - expect(satoshiToArk(10000000)).toBe("0.1 DѦ"); - expect(satoshiToArk(100000000)).toBe("1 DѦ"); - expect(satoshiToArk(1000000000)).toBe("10 DѦ"); - }); - }); -}); diff --git a/packages/core-api/src/versions/2/wallets/methods.ts b/packages/core-api/src/versions/2/wallets/methods.ts index c354ff87f5..d9ccaf3904 100644 --- a/packages/core-api/src/versions/2/wallets/methods.ts +++ b/packages/core-api/src/versions/2/wallets/methods.ts @@ -122,7 +122,7 @@ export function registerMethods(server) { ...paginate(request), })) .method("v2.wallets.top", top, 30, request => paginate(request)) - .method("v2.wallets.show", show, 30, request => ({ id: request.params.id })) + .method("v2.wallets.show", show, 8, request => ({ id: request.params.id })) .method("v2.wallets.transactions", transactions, 30, request => ({ ...{ id: request.params.id }, ...request.query, diff --git a/packages/core-debugger-cli/.gitattributes b/packages/core-debugger-cli/.gitattributes deleted file mode 100644 index 60cc52db63..0000000000 --- a/packages/core-debugger-cli/.gitattributes +++ /dev/null @@ -1,11 +0,0 @@ -# Path-based git attributes -# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html - -# Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore -/README.md export-ignore diff --git a/packages/core-debugger-cli/.gitignore b/packages/core-debugger-cli/.gitignore deleted file mode 100644 index 5dc9198e84..0000000000 --- a/packages/core-debugger-cli/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test-wallets \ No newline at end of file diff --git a/packages/core-debugger-cli/README.md b/packages/core-debugger-cli/README.md deleted file mode 100644 index bc6530e60b..0000000000 --- a/packages/core-debugger-cli/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Ark Core - Debugger CLI - -

- -

- -## Documentation - -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-debugger-cli.html). - -## Security - -If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. - -## Credits - -- [Brian Faust](https://github.com/faustbrian) -- [Joshua Noack](https://github.com/supaiku0) -- [All Contributors](../../../../contributors) - -## License - -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) diff --git a/packages/core-debugger-cli/bin/run b/packages/core-debugger-cli/bin/run deleted file mode 100755 index 30b14e1773..0000000000 --- a/packages/core-debugger-cli/bin/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -require('@oclif/command').run() -.then(require('@oclif/command/flush')) -.catch(require('@oclif/errors/handle')) diff --git a/packages/core-debugger-cli/bin/run.cmd b/packages/core-debugger-cli/bin/run.cmd deleted file mode 100644 index 968fc30758..0000000000 --- a/packages/core-debugger-cli/bin/run.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -node "%~dp0\run" %* diff --git a/packages/core-debugger-cli/package.json b/packages/core-debugger-cli/package.json deleted file mode 100644 index d88bf9e8ae..0000000000 --- a/packages/core-debugger-cli/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "@arkecosystem/core-debugger-cli", - "description": "Debugger CLI for Ark Core", - "version": "2.2.0-beta.7", - "contributors": [ - "Brian Faust " - ], - "license": "MIT", - "main": "dist/index.js", - "files": [ - "/bin", - "/dist", - "/oclif.manifest.json" - ], - "bin": { - "debugger": "./bin/run" - }, - "scripts": { - "debugger": "./bin/run", - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:stable": "npm publish --tag latest", - "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", - "prepack": "oclif-dev manifest && npm shrinkwrap", - "postpack": "rm -f oclif.manifest.json", - "compile": "../../node_modules/typescript/bin/tsc", - "build": "yarn clean && yarn compile", - "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" - }, - "dependencies": { - "@arkecosystem/crypto": "^2.2.0-beta.7", - "@oclif/command": "^1.5.10", - "@oclif/config": "^1.12.6", - "@oclif/plugin-help": "^2.1.6", - "@oclif/plugin-not-found": "^1.2.2", - "@types/clipboardy": "^1.1.0", - "clipboardy": "^1.2.3" - }, - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=10.x" - }, - "oclif": { - "commands": "./dist/commands", - "bin": "debugger", - "plugins": [ - "@oclif/plugin-help", - "@oclif/plugin-not-found" - ] - } -} diff --git a/packages/core-debugger-cli/src/commands/command.ts b/packages/core-debugger-cli/src/commands/command.ts deleted file mode 100644 index 4ab327459a..0000000000 --- a/packages/core-debugger-cli/src/commands/command.ts +++ /dev/null @@ -1,12 +0,0 @@ -import Command, { flags } from "@oclif/command"; - -export abstract class BaseCommand extends Command { - public static flags = { - log: flags.string({ - description: "log the data to the console", - }), - copy: flags.string({ - description: "copy the data to the clipboard", - }), - }; -} diff --git a/packages/core-debugger-cli/src/index.ts b/packages/core-debugger-cli/src/index.ts deleted file mode 100644 index 8bdb76f9a0..0000000000 --- a/packages/core-debugger-cli/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { run } from "@oclif/command"; diff --git a/packages/core-debugger-cli/src/utils.ts b/packages/core-debugger-cli/src/utils.ts deleted file mode 100644 index 2fbd8ef2ac..0000000000 --- a/packages/core-debugger-cli/src/utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -import clipboardy from "clipboardy"; - -export function copyToClipboard(data) { - clipboardy.writeSync(JSON.stringify(data)); -} - -export function handleOutput(opts, data) { - if (opts.copy) { - return copyToClipboard(data); - } - - if (opts.log) { - // tslint:disable-next-line:no-console - return console.log(data); - } - - return data; -} diff --git a/packages/core-debugger-cli/tsconfig.json b/packages/core-debugger-cli/tsconfig.json deleted file mode 100644 index 0b089c5fa8..0000000000 --- a/packages/core-debugger-cli/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["src/**/**.ts"] -} diff --git a/packages/core-tester-cli/package.json b/packages/core-tester-cli/package.json index f7e306b34c..be1f0b7e3c 100644 --- a/packages/core-tester-cli/package.json +++ b/packages/core-tester-cli/package.json @@ -42,21 +42,19 @@ "@oclif/plugin-help": "^2.1.6", "@oclif/plugin-not-found": "^1.2.2", "@types/bip39": "^2.4.1", - "@types/clipboardy": "^1.1.0", - "@types/lodash.fill": "^3.4.4", "@types/pino": "^5.8.4", "@types/pluralize": "^0.0.29", "axios": "^0.18.0", "bip39": "^2.5.0", "clipboardy": "^1.2.3", "delay": "^4.1.0", - "lodash.fill": "^3.4.0", "pino": "^5.11.1", "pino-pretty": "^2.5.0", "pluralize": "^7.0.0", - "superheroes": "^2.0.0" + "pokemon": "^1.2.3" }, "devDependencies": { + "@types/clipboardy": "^1.1.0", "axios-mock-adapter": "^1.16.0" }, "publishConfig": { @@ -67,7 +65,18 @@ }, "oclif": { "commands": "./dist/commands", - "bin": "snapshot", + "bin": "ark-tester", + "topics": { + "debug": { + "description": "debug blocks and transactions" + }, + "send": { + "description": "send transactions of various types" + }, + "make": { + "description": "make new identities" + } + }, "plugins": [ "@oclif/plugin-help", "@oclif/plugin-not-found" diff --git a/packages/core-tester-cli/src/commands/command.ts b/packages/core-tester-cli/src/commands/command.ts index 84b04f525c..6208f857c3 100644 --- a/packages/core-tester-cli/src/commands/command.ts +++ b/packages/core-tester-cli/src/commands/command.ts @@ -1,337 +1,205 @@ import { bignumify } from "@arkecosystem/core-utils"; -import { Bignum, crypto, Transaction } from "@arkecosystem/crypto"; +import { Address, Bignum, formatSatoshi } from "@arkecosystem/crypto"; import Command, { flags } from "@oclif/command"; -import bip39 from "bip39"; -import clipboardy from "clipboardy"; import delay from "delay"; -import fs from "fs"; -import path from "path"; -import pluralize from "pluralize"; -import { config } from "../config"; -import { customFlags } from "../flags"; -import { logger, paginate, request } from "../utils"; +import { satoshiFlag } from "../flags"; +import { HttpClient } from "../http-client"; +import { logger } from "../logger"; +import { Signer } from "../signer"; export abstract class BaseCommand extends Command { - public static flags = { - number: flags.integer({ - description: "number of wallets", - default: 10, - }), - amount: customFlags.number({ - description: "initial wallet token amount", - default: 2, - }), - transferFee: customFlags.number({ - description: "transfer fee", - default: 0.1, - }), - baseUrl: flags.string({ - description: "base api url", + public static flagsConfig = { + host: flags.string({ + description: "API host", + default: "http://localhost", }), - apiPort: flags.integer({ - description: "base api port", + portAPI: flags.integer({ + description: "API port", default: 4003, }), - p2pPort: flags.integer({ - description: "base p2p port", - default: 4002, + portP2P: flags.integer({ + description: "P2P port", + default: 4000, }), + }; + + public static flagsSend = { + ...BaseCommand.flagsConfig, passphrase: flags.string({ description: "passphrase of initial wallet", + default: "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire", }), secondPassphrase: flags.string({ description: "second passphrase of initial wallet", }), - skipValidation: flags.boolean({ - description: "skip transaction validations", + number: flags.integer({ + description: "number of wallets", + default: 1, + }), + amount: satoshiFlag({ + description: "initial wallet token amount", + default: 2, }), - skipTesting: flags.boolean({ - description: "skip testing", + transferFee: satoshiFlag({ + description: "transfer fee", + default: 0.1, }), - copy: flags.boolean({ - description: "copy the transactions to the clipboard", + skipProbing: flags.boolean({ + description: "skip transaction probing", }), }; - public options: any; - public config: any; + public static flagsDebug = { + log: flags.string({ + description: "log the data to the console", + }), + copy: flags.string({ + description: "copy the data to the clipboard", + }), + }; - /** - * Init new instance of command. - * @param {Object} options - * @return {*} - */ - public async initialize(command): Promise { - // tslint:disable-next-line:no-shadowed-variable - const { flags } = this.parse(command); + protected api: HttpClient; + protected p2p: HttpClient; + protected signer: Signer; + protected network: Record; + protected constants: Record; - this.options = flags; - this.applyConfig(); - await this.loadConstants(); - await this.loadNetworkConfig(); + protected async make(command): Promise { + const { args, flags } = this.parse(command); - return { flags }; - } + this.api = new HttpClient(`${flags.host}:${flags.portAPI}/api/v2/`); + this.p2p = new HttpClient(`${flags.host}:${flags.portP2P}/`); - /** - * Copy transactions to clipboard. - * @param {Object[]} transactions - * @return {void} - */ - public copyToClipboard(transactions) { - for (const transaction of transactions) { - transaction.serialized = transaction.serialized.toString("hex"); - } + await this.setupConstants(); + await this.setupNetwork(); - clipboardy.writeSync(JSON.stringify(transactions)); - logger.info(`Copied ${pluralize("transaction", transactions.length, true)}`); + this.signer = new Signer(this.network); + + return { args, flags }; } - /** - * Generate wallets based on quantity. - * @param {Number} [quantity] - * @return {Object[]} - */ - public generateWallets(quantity: any = null) { - if (!quantity) { - quantity = this.options.number; + protected async sendTransaction(transactions: any[]): Promise> { + if (!Array.isArray(transactions)) { + transactions = [transactions]; } - const wallets = []; - for (let i = 0; i < quantity; i++) { - const passphrase = bip39.generateMnemonic(); - const keys = crypto.getKeys(passphrase); - const address = crypto.getAddress(keys.publicKey, this.config.network.version); + for (const transaction of transactions) { + let recipientId = transaction.recipientId; - wallets.push({ address, keys, passphrase }); - } + if (!recipientId) { + recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + } - const testWalletsPath = path.resolve(__dirname, "../../test-wallets"); - fs.appendFileSync(testWalletsPath, `${new Date().toLocaleDateString()} ${"-".repeat(70)}\n`); - for (const wallet of wallets) { - fs.appendFileSync(testWalletsPath, `${wallet.address}: ${wallet.passphrase}\n`); + logger.info( + `[T] ${transaction.id} (${recipientId} / ${this.fromSatoshi(transaction.amount)} / ${this.fromSatoshi( + transaction.fee, + )})`, + ); } - return wallets; + return this.api.post("transactions", { transactions }); } - /** - * Get delegate API response. - * @return {Object[]} - * @throws 'Could not get delegates' - */ - public async getDelegates() { + protected async knockTransaction(id: string): Promise { try { - const delegates = await paginate(this.config, "/api/v2/delegates"); + const { data } = await this.api.get(`transactions/${id}`); + + logger.info(`[T] ${id} (${data.blockId})`); - return delegates; + return true; } catch (error) { - const message = error.response ? error.response.data.message : error.message; - throw new Error(`Could not get delegates: ${message}`); + logger.error(error.message); + + logger.error(`[T] ${id} (not forged)`); + + return false; } } - /** - * Get transaction from API by ID. - * @param {String} id - * @return {(Object|null)} - */ - public async getTransaction(id) { - try { - const response = await request(this.config).get(`/api/v2/transactions/${id}`); + protected async knockBalance(address: string, expected: Bignum): Promise { + const actual = await this.getWalletBalance(address); - if (response.data) { - return response.data; - } - } catch (error) { - // + if (bignumify(expected).isEqualTo(actual)) { + logger.info(`[W] ${address} (${this.fromSatoshi(actual)})`); + } else { + logger.error(`[W] ${address} (${this.fromSatoshi(expected)} / ${this.fromSatoshi(actual)})`); } - - return null; } - /** - * Get delegate voters by public key. - * @param {String} publicKey - * @return {Object[]} - */ - public async getVoters(publicKey) { + protected async getWalletBalance(address: string): Promise { try { - return paginate(this.config, `/api/v2/delegates/${publicKey}/voters`); + const { data } = await this.api.get(`wallets/${address}`); + + return bignumify(data.balance); } catch (error) { - const message = error.response ? error.response.data.message : error.message; - throw new Error(`Could not get voters for '${publicKey}': ${message}`); + return Bignum.ZERO; } } - /** - * Get wallet balance by address. - * @param {String} address - * @return {Bignum} - */ - public async getWalletBalance(address) { - try { - return bignumify((await this.getWallet(address)).balance); - } catch (error) { - // - } + protected async broadcastTransactions(transactions) { + await this.sendTransaction(transactions); - return Bignum.ZERO; + return this.awaitConfirmations(transactions); } - /** - * Get wallet by address. - * @param {String} address - * @return {Object} - */ - public async getWallet(address) { - try { - const response = await request(this.config).get(`/api/v2/wallets/${address}`); + protected castFlags(values: Record): string[] { + return Object.keys(BaseCommand.flagsConfig) + .map((key: string) => { + const value = values[key]; - if (response.data) { - return response.data; - } + if (value === undefined) { + return undefined; + } - return null; - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - throw new Error(`Could not get wallet for '${address}': ${message}`); - } - } + if (value === true) { + return `--${key}`; + } - /** - * Send transactions to API and wait for response. - * @param {Object[]} transactions - * @param {String} [transactionType] - * @param {Boolean} [wait=true] - * @return {Object} - */ - public async sendTransactions(transactions: Transaction[], transactionType: any = null, wait = true) { - const response = await this.postTransactions(transactions); - - if (wait) { - const delaySeconds = this.getTransactionDelaySeconds(transactions); - transactionType = `${transactionType ? `${transactionType} ` : ""}transactions`; - logger.info(`Waiting ${delaySeconds} seconds to apply ${transactionType}`); - await delay(delaySeconds * 1000); - } + return `--${key}=${value}`; + }) + .filter(value => value !== undefined); + } - return response; + protected toSatoshi(value) { + return bignumify(value) + .times(1e8) + .toFixed(); } - /** - * Send transactions to API. - * @param {Object[]} transactions - * @return {Object} - */ - public async postTransactions(transactions: Transaction[]) { - try { - const response = await request(this.config).post("/api/v2/transactions", { - transactions: transactions.map(tx => tx.data), - }); - return response.data; - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - throw new Error(`Could not post transactions: ${message}`); - } + protected fromSatoshi(satoshi) { + return formatSatoshi(satoshi); } - /** - * Load constants from API and apply to config. - * @return {void} - */ - public async loadConstants() { + private async setupConstants() { try { - this.config.constants = (await request(this.config).get("/api/v2/node/configuration")).data.constants; + const { data } = await this.api.get("node/configuration"); + + this.constants = data.constants; } catch (error) { - logger.error("Failed to get constants: ", error.message); + logger.error(error.message); process.exit(1); } } - /** - * Load network from API and apply to config. - * @return {void} - */ - public async loadNetworkConfig() { + private async setupNetwork() { try { - this.config.network = (await request(this.config).get("/config", true)).data.network; + const { data } = await this.p2p.get("config"); + + this.network = data.network; } catch (error) { - logger.error("Failed to get network config: ", error.message); + logger.error(error.message); process.exit(1); } } - /** - * Apply options to config. - * @return {void} - */ - protected applyConfig() { - this.config = { ...config }; - - if (this.options.baseUrl) { - this.config.baseUrl = this.options.baseUrl.replace(/\/+$/, ""); - } - - if (this.options.apiPort) { - this.config.apiPort = this.options.apiPort; - } - - if (this.options.p2pPort && process.env.NODE_ENV !== "test") { - this.config.p2pPort = this.options.p2pPort; - } - - if (this.options.passphrase) { - this.config.passphrase = this.options.passphrase; - } - - if (this.options.secondPassphrase) { - this.config.secondPassphrase = this.options.secondPassphrase; - } - } - - /** - * Quit command and output error when problem sending transactions. - * @param {Error} error - * @return {void} - */ - protected problemSendingTransactions(error) { - const message = error.response ? error.response.data.message : error.message; - logger.error(`There was a problem sending transactions: ${message}`); - process.exit(1); - } - - /** - * Determine how long to wait for transactions to process. - * @param {Object[]} transactions - * @return {Number} - */ - protected getTransactionDelaySeconds(transactions) { + private async awaitConfirmations(transactions): Promise { if (process.env.NODE_ENV === "test") { - return 0; + return; } - const waitPerBlock = Math.round(this.config.constants.blocktime / 10) * 20; - - return waitPerBlock * Math.ceil(transactions.length / this.config.constants.block.maxTransactions); - } - - protected castFlags(values: Record): string[] { - return Object.keys(BaseCommand.flags) - .filter(k => !["copy"].includes(k)) - .map((key: string) => { - const value = values[key]; + const waitPerBlock = + this.constants.blocktime * Math.ceil(transactions.length / this.constants.block.maxTransactions); - if (value === undefined) { - return undefined; - } - - if (value === true) { - return `--${key}`; - } - - return `--${key}=${value}`; - }) - .filter(value => value !== undefined); + await delay(waitPerBlock * 1000); } } diff --git a/packages/core-debugger-cli/src/commands/deserialize.ts b/packages/core-tester-cli/src/commands/debug/deserialize.ts similarity index 85% rename from packages/core-debugger-cli/src/commands/deserialize.ts rename to packages/core-tester-cli/src/commands/debug/deserialize.ts index 7af2cb292d..ae92cf93fe 100644 --- a/packages/core-debugger-cli/src/commands/deserialize.ts +++ b/packages/core-tester-cli/src/commands/debug/deserialize.ts @@ -1,13 +1,13 @@ import { models } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class DeserializeCommand extends BaseCommand { public static description: string = "Deserialize the given HEX"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ description: "the HEX blob to deserialize", required: true, @@ -20,7 +20,6 @@ export class DeserializeCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(DeserializeCommand); let output; diff --git a/packages/core-debugger-cli/src/commands/identity.ts b/packages/core-tester-cli/src/commands/debug/identity.ts similarity index 91% rename from packages/core-debugger-cli/src/commands/identity.ts rename to packages/core-tester-cli/src/commands/debug/identity.ts index 0ed91c6ba3..3d257dff6e 100644 --- a/packages/core-debugger-cli/src/commands/identity.ts +++ b/packages/core-tester-cli/src/commands/debug/identity.ts @@ -1,13 +1,13 @@ import { crypto } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class IdentityCommand extends BaseCommand { public static description: string = "Get identities from the given input"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ description: "the data to get the identities from", required: true, @@ -24,7 +24,6 @@ export class IdentityCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(IdentityCommand); let output; diff --git a/packages/core-debugger-cli/src/commands/serialize.ts b/packages/core-tester-cli/src/commands/debug/serialize.ts similarity index 86% rename from packages/core-debugger-cli/src/commands/serialize.ts rename to packages/core-tester-cli/src/commands/debug/serialize.ts index d058bbbe67..0ae78aa331 100644 --- a/packages/core-debugger-cli/src/commands/serialize.ts +++ b/packages/core-tester-cli/src/commands/debug/serialize.ts @@ -1,13 +1,13 @@ import { models, Transaction } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class SerializeCommand extends BaseCommand { public static description: string = "Serialize the given JSON"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ description: "the HEX blob to serialize", required: true, @@ -23,7 +23,6 @@ export class SerializeCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(SerializeCommand); const serialized = diff --git a/packages/core-debugger-cli/src/commands/verify-second.ts b/packages/core-tester-cli/src/commands/debug/verify-second-signature.ts similarity index 83% rename from packages/core-debugger-cli/src/commands/verify-second.ts rename to packages/core-tester-cli/src/commands/debug/verify-second-signature.ts index 891265c7d5..aa6176aeb8 100644 --- a/packages/core-debugger-cli/src/commands/verify-second.ts +++ b/packages/core-tester-cli/src/commands/debug/verify-second-signature.ts @@ -1,13 +1,13 @@ import { crypto, models } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class VerifySecondSignatureCommand extends BaseCommand { public static description: string = "Verify a second signature of a transaction"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ description: "the HEX blob to deserialize and verify", required: true, @@ -19,7 +19,6 @@ export class VerifySecondSignatureCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(VerifySecondSignatureCommand); const { data } = models.Transaction.fromHex(flags.data); diff --git a/packages/core-debugger-cli/src/commands/verify.ts b/packages/core-tester-cli/src/commands/debug/verify.ts similarity index 84% rename from packages/core-debugger-cli/src/commands/verify.ts rename to packages/core-tester-cli/src/commands/debug/verify.ts index 0a246ddb9c..a7ba9736ce 100644 --- a/packages/core-debugger-cli/src/commands/verify.ts +++ b/packages/core-tester-cli/src/commands/debug/verify.ts @@ -1,13 +1,13 @@ import { models } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class VerifyCommand extends BaseCommand { public static description: string = "Verify the given HEX"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ description: "the HEX blob to deserialize and verify", required: true, @@ -19,7 +19,6 @@ export class VerifyCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(VerifyCommand); let output = false; diff --git a/packages/core-tester-cli/src/commands/delegate-registration.ts b/packages/core-tester-cli/src/commands/delegate-registration.ts deleted file mode 100644 index 2d6cf5412e..0000000000 --- a/packages/core-tester-cli/src/commands/delegate-registration.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { client } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import pluralize from "pluralize"; -import superheroes from "superheroes"; -import { customFlags } from "../flags"; -import { logger, parseFee, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; -import { TransferCommand } from "./transfer"; - -export class DelegateRegistrationCommand extends BaseCommand { - public static description: string = "create multiple delegates"; - - public static flags = { - ...BaseCommand.flags, - delegateFee: customFlags.number({ - description: "delegate registration fee", - default: 25, - }), - }; - - /** - * Run delegate-registration command. - * @return {void} - */ - public async run(): Promise { - // tslint:disable-next-line: no-shadowed-variable - const { flags } = await this.initialize(DelegateRegistrationCommand); - - const wallets = this.generateWallets(); - - for (const wallet of wallets) { - await TransferCommand.run( - ["--amount", String(this.options.amount || 25), "--recipient", wallet.address, "--skipTesting"].concat( - this.castFlags(flags), - ), - ); - } - - const delegates = await this.getDelegates(); - - logger.error( - `Sending ${this.options.number} delegate registration ${pluralize("transaction", this.options.number)}`, - ); - - if (!this.options.skipValidation) { - logger.error(`Starting delegate count: ${delegates.length}`); - } - - const transactions = []; - const usedDelegateNames = delegates.map(delegate => delegate.username); - - wallets.forEach((wallet, i) => { - while (!wallet.username || usedDelegateNames.includes(wallet.username)) { - wallet.username = superheroes.random(); - } - - wallet.username = wallet.username.toLowerCase().replace(/ /g, "_"); - usedDelegateNames.push(wallet.username); - - const transaction = client - .getBuilder() - .delegateRegistration() - .fee(parseFee(this.options.delegateFee)) - .usernameAsset(wallet.username) - .network(this.config.network.version) - .sign(wallet.passphrase) - .secondSign(this.config.secondPassphrase) - .build(); - - transactions.push(transaction); - - logger.info( - `${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.data.fee)}, username: ${ - wallet.username - })`, - ); - }); - - if (this.options.copy) { - this.copyToClipboard(transactions); - return; - } - - const expectedDelegates = delegates.length + wallets.length; - if (!this.options.skipValidation) { - logger.info(`Expected end delegate count: ${expectedDelegates}`); - } - - try { - await this.sendTransactions(transactions, "delegate", !this.options.skipValidation); - - if (this.options.skipValidation) { - return; - } - - const targetDelegates = await this.getDelegates(); - logger.info(`All transactions have been sent! Total delegates: ${targetDelegates.length}`); - - if (targetDelegates.length !== expectedDelegates) { - logger.error( - `Delegate count incorrect. '${targetDelegates.length}' but should be '${expectedDelegates}'`, - ); - } - } catch (error) { - logger.error( - `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`, - ); - } - } -} diff --git a/packages/core-tester-cli/src/commands/make/wallets.ts b/packages/core-tester-cli/src/commands/make/wallets.ts new file mode 100644 index 0000000000..91dcd98872 --- /dev/null +++ b/packages/core-tester-cli/src/commands/make/wallets.ts @@ -0,0 +1,46 @@ +import { crypto } from "@arkecosystem/crypto"; +import { flags } from "@oclif/command"; +import { generateMnemonic } from "bip39"; +import { writeFileSync } from "fs"; +import { copyToClipboard } from "../../utils"; +import { BaseCommand } from "../command"; + +export class WalletCommand extends BaseCommand { + public static description: string = "send multiple transactions"; + + public static flags = { + ...BaseCommand.flagsConfig, + quantity: flags.integer({ + description: "number of wallets to generate", + }), + copy: flags.boolean({ + description: "write the wallets to the clipboard", + }), + write: flags.boolean({ + description: "write the wallets to the disk", + }), + }; + + public async run(): Promise> { + const { flags } = await this.make(WalletCommand); + + const wallets = {}; + for (let i = 0; i < flags.quantity; i++) { + const passphrase = generateMnemonic(); + const keys = crypto.getKeys(passphrase); + const address = crypto.getAddress(keys.publicKey, this.network.version); + + wallets[address] = { address, keys, passphrase }; + } + + if (flags.copy) { + copyToClipboard(JSON.stringify(wallets)); + } + + if (flags.write) { + writeFileSync("./wallets.json", JSON.stringify(wallets)); + } + + return wallets; + } +} diff --git a/packages/core-tester-cli/src/commands/multi-signature.ts b/packages/core-tester-cli/src/commands/multi-signature.ts deleted file mode 100644 index f3f02a8b24..0000000000 --- a/packages/core-tester-cli/src/commands/multi-signature.ts +++ /dev/null @@ -1,354 +0,0 @@ -import { client } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import take from "lodash/take"; -import pluralize from "pluralize"; -import { customFlags } from "../flags"; -import { arkToSatoshi, generateTransactions, logger, parseFee, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; -import { TransferCommand } from "./transfer"; - -export class MultiSignatureCommand extends BaseCommand { - public static description: string = "create multiple multisig wallets"; - - public static flags = { - ...BaseCommand.flags, - multisigFee: customFlags.number({ - description: "multisig fee", - default: 5, - }), - min: flags.integer({ - description: "minimum number of signatures per transaction", - default: 2, - }), - lifetime: flags.integer({ - description: "lifetime of transaction", - default: 72, - }), - quantity: flags.integer({ - description: "number of signatures per wallet", - default: 3, - }), - skipTests: flags.boolean({ - description: "skip transaction tests", - }), - }; - - /** - * Run multi-signature command. - * @return {void} - */ - public async run(): Promise { - // tslint:disable-next-line: no-shadowed-variable - const { flags } = await this.initialize(MultiSignatureCommand); - - const approvalWallets = this.generateWallets(this.options.quantity); - const publicKeys = approvalWallets.map(wallet => `+${wallet.keys.publicKey}`); - const min = this.options.min ? Math.min(this.options.min, publicKeys.length) : publicKeys.length; - - const testCosts = this.options.skipTests ? 1 : 2; - const wallets = this.generateWallets(); - - for (const wallet of wallets) { - await TransferCommand.run( - [ - "--recipient", - wallet.address, - "--amount", - (publicKeys.length + 1) * 5 + testCosts, - "--skipTesting", - ].concat(this.castFlags(flags)), - ); - } - - const transactions = this.generateTransactions(wallets, approvalWallets, publicKeys, min); - - if (this.options.copy) { - this.copyToClipboard(transactions); - - return; - } - - try { - const response = await this.sendTransactions(transactions, "multi-signature", !this.options.skipValidation); - - if (!this.options.skipValidation) { - let hasUnprocessed = false; - for (const transaction of transactions) { - if (!response.accept.includes(transaction.id)) { - hasUnprocessed = true; - logger.error(`Multi-signature transaction '${transaction.id}' was not processed`); - } - } - if (hasUnprocessed) { - process.exit(1); - } - - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } - } - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - logger.error(`There was a problem sending multi-signature transactions: ${message}`); - process.exit(1); - } - - if (this.options.skipTests || this.options.skipValidation) { - return; - } - - await this.testSendWithSignatures(wallets, approvalWallets); - await this.testSendWithMinSignatures(wallets, approvalWallets, min); - await this.testSendWithBelowMinSignatures(wallets, approvalWallets, min); - await this.testSendWithoutSignatures(wallets); - await this.testSendWithEmptySignatures(wallets); - await this.testNewMultiSignatureRegistration(wallets, approvalWallets, publicKeys, min); - } - - /** - * Generate batch of transactions based on wallets - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {String[]} [publicKeys=[]] - * @param {Number} [min=2] - * @param {Boolean} [log=true] - * @return {Object[]} - */ - public generateTransactions(wallets, approvalWallets = [], publicKeys = [], min = 2, log = true) { - const transactions = []; - wallets.forEach((wallet, i) => { - const builder = client.getBuilder().multiSignature(); - - builder - .fee(parseFee(this.options.multisigFee)) - .multiSignatureAsset({ - lifetime: this.options.lifetime, - keysgroup: publicKeys, - min, - }) - .network(this.config.network.version) - .sign(wallet.passphrase); - - if (wallet.secondPassphrase || this.config.secondPassphrase) { - builder.secondSign(wallet.secondPassphrase || this.config.secondPassphrase); - } - - if (approvalWallets) { - for (let j = approvalWallets.length - 1; j >= 0; j--) { - builder.multiSignatureSign(approvalWallets[j].passphrase); - } - } - - const transaction = builder.build(); - transactions.push(transaction); - - if (log) { - logger.info( - `${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.data.fee)})`, - ); - } - }); - - return transactions; - } - - /** - * Send transactions with approver signatures. - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @return {void} - */ - public async testSendWithSignatures(wallets, approvalWallets = []) { - logger.info("Sending transactions with signatures"); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, approvalWallets, { - config: this.config, - ...this.options, - }); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions with min approver signatures. - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {Number} [min=2] - * @return {void} - */ - public async testSendWithMinSignatures(wallets, approvalWallets = [], min = 2) { - logger.info( - `Sending transactions with ${min} (min) of ${pluralize("signature", approvalWallets.length, true)}`, - ); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, take(approvalWallets, min), { - config: this.config, - ...this.options, - }); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions with below min approver signatures. - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {Number} [min=2] - * @return {void} - */ - public async testSendWithBelowMinSignatures(wallets, approvalWallets = [], min = 2) { - const max = min - 1; - logger.info( - `Sending transactions with ${max} (below min) of ${pluralize("signature", approvalWallets.length, true)}`, - ); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, take(approvalWallets, max), { - config: this.config, - ...this.options, - }); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - try { - const tx = await this.getTransaction(transaction.id); - if (tx) { - logger.error(`Transaction '${transaction.id}' should not be on the blockchain`); - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - if (message !== "Transaction not found") { - logger.error(`Failed to check transaction '${transaction.id}': ${message}`); - } - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions without approver signatures. - * @param {Object[]} wallets - * @return {void} - */ - public async testSendWithoutSignatures(wallets) { - logger.info("Sending transactions without signatures"); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, [], { - config: this.config, - ...this.options, - }); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - try { - const tx = await this.getTransaction(transaction.id); - if (tx) { - logger.error(`Transaction '${transaction.id}' should not be on the blockchain`); - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - if (message !== "Transaction not found") { - logger.error(`Failed to check transaction '${transaction.id}': ${message}`); - } - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions with empty approver signatures. - * @param {Object[]} wallets - * @return {void} - */ - public async testSendWithEmptySignatures(wallets) { - logger.info("Sending transactions with empty signatures"); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, [], { - config: this.config, - ...this.options, - }); - for (const transaction of transactions) { - transaction.data.signatures = []; - } - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - try { - const tx = await this.getTransaction(transaction.id); - if (tx) { - logger.error(`Transaction '${transaction.id}' should not be on the blockchain`); - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - if (message !== "Transaction not found") { - logger.error(`Failed to check transaction '${transaction.id}': ${message}`); - } - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions to re-register multi-signature wallets. - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {Object[]} [publicKeys=[]] - * @param {Number} [min=2] - * @return {void} - */ - public async testNewMultiSignatureRegistration(wallets, approvalWallets = [], publicKeys = [], min = 2) { - logger.info("Sending transactions to re-register multi-signature"); - - const transactions = this.generateTransactions(wallets, approvalWallets, publicKeys, min); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - try { - const tx = await this.getTransaction(transaction.id); - if (tx) { - logger.error(`Transaction '${transaction.id}' should not be on the blockchain`); - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - if (message !== "Transaction not found") { - logger.error(`Failed to check transaction '${transaction.id}': ${message}`); - } - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } -} diff --git a/packages/core-tester-cli/src/commands/second-signature.ts b/packages/core-tester-cli/src/commands/second-signature.ts deleted file mode 100644 index c72e681762..0000000000 --- a/packages/core-tester-cli/src/commands/second-signature.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { client } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import pluralize from "pluralize"; -import { customFlags } from "../flags"; -import { logger, parseFee, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; -import { TransferCommand } from "./transfer"; - -export class SecondSignatureCommand extends BaseCommand { - public static description: string = "create wallets with second signature"; - - public static flags = { - ...BaseCommand.flags, - signatureFee: customFlags.number({ - description: "second signature fee", - default: 5, - }), - }; - - /** - * Run second-signature command. - * @return {void} - */ - public async run(): Promise { - // tslint:disable-next-line: no-shadowed-variable - const { flags } = await this.initialize(SecondSignatureCommand); - - const wallets = this.generateWallets(); - - for (const wallet of wallets) { - await TransferCommand.run( - ["--recipient", wallet.address, "--amount", String(this.options.amount || 5), "--skipTesting"].concat( - this.castFlags(flags), - ), - ); - } - - logger.info(`Sending ${this.options.number} second signature ${pluralize("transaction", this.options.number)}`); - - const transactions = []; - wallets.forEach((wallet, i) => { - wallet.secondPassphrase = this.config.secondPassphrase || wallet.passphrase; - const transaction = client - .getBuilder() - .secondSignature() - .fee(parseFee(this.options.signatureFee)) - .signatureAsset(wallet.secondPassphrase) - .network(this.config.network.version) - .sign(wallet.passphrase) - .build(); - - wallet.publicKey = transaction.data.senderPublicKey; - wallet.secondPublicKey = transaction.data.asset.signature.publicKey; - transactions.push(transaction); - - logger.info(`${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.data.fee)})`); - }); - - if (this.options.copy) { - this.copyToClipboard(transactions); - return; - } - - try { - await this.sendTransactions(transactions, "second-signature", !this.options.skipValidation); - - if (this.options.skipValidation) { - return; - } - - for (const walletObject of wallets) { - const wallet = await this.getWallet(walletObject.address); - - if ( - wallet.secondPublicKey !== walletObject.secondPublicKey || - wallet.publicKey !== walletObject.publicKey - ) { - logger.error(`Invalid second signature for ${walletObject.address}.`); - } - } - } catch (error) { - logger.error( - `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`, - ); - } - } -} diff --git a/packages/core-tester-cli/src/commands/send/delegate-registration.ts b/packages/core-tester-cli/src/commands/send/delegate-registration.ts new file mode 100644 index 0000000000..af3f8f0f5e --- /dev/null +++ b/packages/core-tester-cli/src/commands/send/delegate-registration.ts @@ -0,0 +1,86 @@ +import { Address } from "@arkecosystem/crypto"; +import pokemon from "pokemon"; +import { satoshiFlag } from "../../flags"; +import { logger } from "../../logger"; +import { SendCommand } from "../../shared/send"; +import { TransferCommand } from "./transfer"; + +export class DelegateRegistrationCommand extends SendCommand { + public static description: string = "create multiple delegates"; + + public static flags = { + ...SendCommand.flagsSend, + delegateFee: satoshiFlag({ + description: "delegate registration fee", + default: 25, + }), + }; + + protected getCommand(): any { + return DelegateRegistrationCommand; + } + + protected async createWalletsWithBalance(flags: Record): Promise { + return TransferCommand.run( + [`--amount=${flags.delegateFee}`, `--number=${flags.number}`, "--skipProbing"].concat( + this.castFlags(flags), + ), + ); + } + + protected async signTransactions(flags: Record, wallets: Record): Promise { + const transactions = []; + + for (const [address, wallet] of Object.entries(wallets)) { + wallets[address].username = pokemon + .random() + .toLowerCase() + .replace(/ /g, "_"); + + transactions.push( + this.signer.makeDelegate({ + ...flags, + ...{ + username: wallets[address].username, + // @ts-ignore + passphrase: wallet.passphrase, + }, + }), + ); + } + + return transactions; + } + + protected async expectBalances(transactions, wallets): Promise { + for (const transaction of transactions) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + const currentBalance = await this.getWalletBalance(recipientId); + wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee); + } + } + + protected async verifyTransactions(transactions, wallets): Promise { + for (const transaction of transactions) { + const wasCreated = await this.knockTransaction(transaction.id); + + if (wasCreated) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + await this.knockBalance(recipientId, wallets[recipientId].expectedBalance); + await this.knockUsername(recipientId, wallets[recipientId].username); + } + } + } + + private async knockUsername(address: string, expected: string): Promise { + const { username: actual } = (await this.api.get(`wallets/${address}`)).data; + + if (actual === expected) { + logger.info(`[W] ${address} (${actual})`); + } else { + logger.error(`[W] ${address} (${expected} / ${actual})`); + } + } +} diff --git a/packages/core-tester-cli/src/commands/send/second-signature-registration.ts b/packages/core-tester-cli/src/commands/send/second-signature-registration.ts new file mode 100644 index 0000000000..cd0adcd96d --- /dev/null +++ b/packages/core-tester-cli/src/commands/send/second-signature-registration.ts @@ -0,0 +1,82 @@ +import { Address } from "@arkecosystem/crypto"; +import { satoshiFlag } from "../../flags"; +import { logger } from "../../logger"; +import { SendCommand } from "../../shared/send"; +import { TransferCommand } from "./transfer"; + +export class SecondSignatureRegistrationCommand extends SendCommand { + public static description: string = "create wallets with second signature"; + + public static flags = { + ...SendCommand.flagsSend, + signatureFee: satoshiFlag({ + description: "second signature fee", + default: 5, + }), + }; + + protected getCommand(): any { + return SecondSignatureRegistrationCommand; + } + + protected async createWalletsWithBalance(flags: Record): Promise { + return TransferCommand.run( + [`--amount=${flags.signatureFee}`, `--number=${flags.number}`, "--skipProbing"].concat( + this.castFlags(flags), + ), + ); + } + + protected async signTransactions(flags: Record, wallets: Record): Promise { + const transactions = []; + + for (const [address, wallet] of Object.entries(wallets)) { + const transaction = this.signer.makeSecondSignature({ + ...flags, + ...{ + passphrase: wallet.passphrase, + secondPassphrase: wallet.passphrase, + }, + }); + + wallets[address].publicKey = transaction.senderPublicKey; + wallets[address].secondPublicKey = transaction.asset.signature.publicKey; + + transactions.push(transaction); + } + + return transactions; + } + + protected async expectBalances(transactions, wallets): Promise { + for (const transaction of transactions) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + const currentBalance = await this.getWalletBalance(recipientId); + wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee); + } + } + + protected async verifyTransactions(transactions, wallets): Promise { + for (const transaction of transactions) { + const wasCreated = await this.knockTransaction(transaction.id); + + if (wasCreated) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + await this.knockBalance(recipientId, wallets[recipientId].expectedBalance); + await this.knockSignature(recipientId, wallets[recipientId].secondPublicKey); + } + } + } + + private async knockSignature(address: string, expected: string): Promise { + const { secondPublicKey: actual } = (await this.api.get(`wallets/${address}`)).data; + + if (actual === expected) { + logger.info(`[W] ${address} (${actual})`); + } else { + logger.error(`[W] ${address} (${expected} / ${actual})`); + } + } +} diff --git a/packages/core-tester-cli/src/commands/send/transfer.ts b/packages/core-tester-cli/src/commands/send/transfer.ts new file mode 100644 index 0000000000..b893ce07b3 --- /dev/null +++ b/packages/core-tester-cli/src/commands/send/transfer.ts @@ -0,0 +1,63 @@ +import { flags } from "@oclif/command"; +import { delay } from "bluebird"; +import { SendCommand } from "../../shared/send"; +import { WalletCommand } from "../make/wallets"; + +export class TransferCommand extends SendCommand { + public static description: string = "send multiple transactions"; + + public static flags = { + ...SendCommand.flagsSend, + recipient: flags.string({ + description: "recipient address", + }), + vendorField: flags.string({ + description: "vendor field to use", + }), + }; + + protected getCommand(): any { + return TransferCommand; + } + + protected async createWalletsWithBalance(flags: Record): Promise { + return WalletCommand.run([`--quantity=${flags.number}`].concat(this.castFlags(flags))); + } + + protected async signTransactions(flags: Record, wallets: Record): Promise { + const transactions = []; + + for (let i = 0; i < flags.number; i++) { + const vendorField = flags.vendorField || `Transaction ${i}`; + + if (flags.recipient) { + transactions.push( + this.signer.makeTransfer({ ...flags, ...{ recipient: flags.recipient, vendorField } }), + ); + } else { + for (const wallet of Object.keys(wallets)) { + transactions.push(this.signer.makeTransfer({ ...flags, ...{ recipient: wallet, vendorField } })); + } + } + } + + return transactions; + } + + protected async expectBalances(transactions, wallets): Promise { + for (const transaction of transactions) { + const currentBalance = await this.getWalletBalance(transaction.recipientId); + wallets[transaction.recipientId].expectedBalance = currentBalance.plus(transaction.amount); + } + } + + protected async verifyTransactions(transactions, wallets): Promise { + for (const transaction of transactions) { + const wasCreated = await this.knockTransaction(transaction.id); + + if (wasCreated) { + await this.knockBalance(transaction.recipientId, wallets[transaction.recipientId].expectedBalance); + } + } + } +} diff --git a/packages/core-tester-cli/src/commands/send/vote.ts b/packages/core-tester-cli/src/commands/send/vote.ts new file mode 100644 index 0000000000..1dcaad6463 --- /dev/null +++ b/packages/core-tester-cli/src/commands/send/vote.ts @@ -0,0 +1,91 @@ +import { Address } from "@arkecosystem/crypto"; +import { flags } from "@oclif/command"; +import { satoshiFlag } from "../../flags"; +import { logger } from "../../logger"; +import { SendCommand } from "../../shared/send"; +import { TransferCommand } from "./transfer"; + +export class VoteCommand extends SendCommand { + public static description: string = "create multiple votes for a delegate"; + + public static flags = { + ...SendCommand.flagsSend, + delegate: flags.string({ + description: "delegate public key", + }), + voteFee: satoshiFlag({ + description: "vote fee", + default: 1, + }), + }; + + protected getCommand(): any { + return VoteCommand; + } + + protected async createWalletsWithBalance(flags: Record): Promise { + return TransferCommand.run( + [`--amount=${flags.voteFee}`, `--number=${flags.number}`, "--skipProbing"].concat(this.castFlags(flags)), + ); + } + + protected async signTransactions(flags: Record, wallets: Record): Promise { + const transactions = []; + + for (const [address, wallet] of Object.entries(wallets)) { + const delegate = flags.delegate || (await this.getRandomDelegate()); + + const transaction = this.signer.makeVote({ + ...flags, + ...{ + delegate, + passphrase: wallet.passphrase, + }, + }); + + wallets[address].vote = delegate; + + transactions.push(transaction); + } + + return transactions; + } + + protected async expectBalances(transactions, wallets): Promise { + for (const transaction of transactions) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + const currentBalance = await this.getWalletBalance(recipientId); + wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee); + } + } + + protected async verifyTransactions(transactions, wallets): Promise { + for (const transaction of transactions) { + const wasCreated = await this.knockTransaction(transaction.id); + + if (wasCreated) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + await this.knockBalance(recipientId, wallets[recipientId].expectedBalance); + await this.knockVote(recipientId, wallets[recipientId].vote); + } + } + } + + private async knockVote(address: string, expected: string): Promise { + const { vote: actual } = (await this.api.get(`wallets/${address}`)).data; + + if (actual === expected) { + logger.info(`[W] ${address} (${actual})`); + } else { + logger.error(`[W] ${address} (${expected} / ${actual})`); + } + } + + private async getRandomDelegate() { + const { data } = await this.api.get("delegates"); + + return data[0].publicKey; + } +} diff --git a/packages/core-tester-cli/src/commands/transfer.ts b/packages/core-tester-cli/src/commands/transfer.ts deleted file mode 100644 index cd1a07489e..0000000000 --- a/packages/core-tester-cli/src/commands/transfer.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { Bignum, crypto } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import delay from "delay"; -import unique from "lodash/uniq"; -import pluralize from "pluralize"; -import { arkToSatoshi, generateTransactions, logger, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; - -export class TransferCommand extends BaseCommand { - public static description: string = "send multiple transactions"; - - public static flags = { - ...BaseCommand.flags, - recipient: flags.string({ - description: "recipient address", - }), - floodAttempts: flags.integer({ - description: "flood node with same transactions", - default: 0, - }), - skipSecondRun: flags.string({ - description: "skip second sending of transactions", - }), - smartBridge: flags.string({ - description: "smart-bridge value to use", - }), - }; - - /** - * Run transfer command. - * @param {Object} options - * @return {void} - */ - public async run(): Promise { - await this.initialize(TransferCommand); - - const primaryAddress = crypto.getAddress( - crypto.getKeys(this.config.passphrase).publicKey, - this.config.network.version, - ); - - let wallets = this.options.wallets; - if (wallets === undefined) { - wallets = this.generateWallets(); - } - - logger.info(`Sending ${wallets.length} transfer ${pluralize("transaction", wallets.length)}`); - - const walletBalance = await this.getWalletBalance(primaryAddress); - - if (!this.options.skipValidation) { - logger.info(`Sender starting balance: ${satoshiToArk(walletBalance)}`); - } - - let totalDeductions = Bignum.ZERO; - const transactionAmount = arkToSatoshi(this.options.amount || 2); - - const transactions = this.generateTransactions(transactionAmount, wallets, null, true); - for (const transaction of transactions) { - totalDeductions = totalDeductions.plus(transactionAmount).plus(transaction.fee); - } - - if (this.options.copy) { - this.copyToClipboard(transactions); - return; - } - - const expectedSenderBalance = new Bignum(walletBalance).minus(totalDeductions); - if (!this.options.skipValidation) { - logger.info(`Sender expected ending balance: ${satoshiToArk(expectedSenderBalance)}`); - } - - const runOptions = { - primaryAddress, - transactions, - wallets, - transactionAmount, - expectedSenderBalance, - skipValidation: this.options.skipValidation, - }; - - try { - if (!this.options.floodAttempts) { - const successfulTest = await this.performRun(runOptions, 1); - if ( - successfulTest && - !this.options.skipSecondRun && - !this.options.skipValidation && - !this.options.skipTesting - ) { - await this.performRun(runOptions, 2, false, true); - } - } else { - const attempts = this.options.floodAttempts; - for (let i = attempts; i > 0; i--) { - await this.performRun(runOptions, attempts - i + 1, i !== 1, i !== attempts); - } - } - } catch (error) { - const message = error.response ? error.response.data.message : error; - logger.error(`There was a problem sending transactions: ${message}`); - } - - if (this.options.skipValidation) { - return; - } - - await this.testVendorField(wallets); - await this.testEmptyVendorField(wallets); - - return; - } - - /** - * Generate batch of transactions based on wallets. - * @param {Bignum} transactionAmount - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {Boolean} [overridePassphrase=false] - * @param {String} [vendorField] - * @param {Boolean} [log=true] - * @return {Object[]} - */ - public generateTransactions( - transactionAmount, - wallets, - approvalWallets = [], - overridePassphrase = false, - vendorField = null, - log = true, - ) { - return generateTransactions(transactionAmount, wallets, approvalWallets, { - ...this.options, - config: this.config, - overridePassphrase, - vendorField: vendorField || this.options.smartBridge, - log, - }); - } - - /** - * Perform a run of transactions. - * @param {Object} runOptions - * @param {Number} [runNumber=1] - * @param {Boolean} [skipWait=false] - * @param {Boolean} [isSubsequentRun=false] - * @return {Boolean} - */ - public async performRun(runOptions, runNumber = 1, skipWait = false, isSubsequentRun = false) { - if (skipWait) { - runOptions.skipValidation = true; - this.sendTransactionsWithResults(runOptions, isSubsequentRun); - - return true; - } - - if (await this.sendTransactionsWithResults(runOptions, isSubsequentRun)) { - logger.info(`All transactions have been received and forged for run ${runNumber}!`); - - return true; - } - - logger.error(`Test failed on run ${runNumber}`); - - return false; - } - - /** - * Send transactions and validate results. - * @param {Object} runOptions - * @param {Boolean} isSubsequentRun - * @return {Boolean} - */ - public async sendTransactionsWithResults(runOptions, isSubsequentRun) { - let successfulTest = true; - - let postResponse; - try { - postResponse = await this.postTransactions(runOptions.transactions); - } catch (error) { - if (runOptions.skipValidation) { - return true; - } - - const message = error.response ? error.response.data.error : error.message; - logger.error(`Transaction request failed: ${message}`); - - return false; - } - - if (runOptions.skipValidation) { - return true; - } - - if (!isSubsequentRun && (!postResponse.accept || !postResponse.accept.length)) { - return false; - } - - if (!isSubsequentRun) { - for (const transaction of runOptions.transactions) { - if (!postResponse.accept.includes(transaction.id)) { - logger.error(`Transaction '${transaction.id}' didn't get approved on the network`); - - successfulTest = false; - } - } - } - - for (const key of Object.keys(postResponse)) { - if (key === "success") { - continue; - } - - const dataLength = postResponse[key].length; - const uniqueLength = unique(postResponse[key]).length; - if (dataLength !== uniqueLength) { - logger.error(`Response data for '${key}' has ${dataLength - uniqueLength} duplicate transaction ids`); - successfulTest = false; - } - } - - const delaySeconds = this.getTransactionDelaySeconds(runOptions.transactions); - logger.info(`Waiting ${delaySeconds} seconds to apply transfer transactions`); - await delay(delaySeconds * 1000); - - for (const transaction of runOptions.transactions) { - const transactionResponse = await this.getTransaction(transaction.id); - if (transactionResponse && transactionResponse.id !== transaction.id) { - logger.error(`Transaction '${transaction.id}' didn't get applied on the network`); - - successfulTest = false; - } - } - - if (runOptions.primaryAddress && runOptions.expectedSenderBalance) { - const walletBalance = await this.getWalletBalance(runOptions.primaryAddress); - if (!walletBalance.isEqualTo(runOptions.expectedSenderBalance)) { - successfulTest = false; - logger.error( - `Sender balance incorrect: '${satoshiToArk(walletBalance)}' but should be '${satoshiToArk( - runOptions.expectedSenderBalance, - )}'`, - ); - } - } - - for (const wallet of runOptions.wallets) { - const balance = await this.getWalletBalance(wallet.address); - if (!balance.isEqualTo(runOptions.transactionAmount)) { - successfulTest = false; - logger.error( - `Incorrect destination balance for ${wallet.address}. Should be '${satoshiToArk( - runOptions.transactionAmount, - )}' but is '${satoshiToArk(balance)}'`, - ); - } - } - - return successfulTest; - } - - /** - * Test vendor field is set correctly on blockchain. - * @param {Object[]} wallets - * @return {void} - */ - public async testVendorField(wallets) { - logger.info("Testing VendorField value is set correctly"); - - const transactions = this.generateTransactions(arkToSatoshi(2), wallets, null, null, "Testing VendorField"); - - try { - await this.sendTransactions(transactions); - - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } else if (tx.vendorField !== "Testing VendorField") { - logger.error(`Transaction '${transaction.id}' does not have correct vendorField value`); - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Test empty vendor field is set correctly on blockchain. - * @param {Object[]} wallets - * @return {void} - */ - public async testEmptyVendorField(wallets) { - logger.info("Testing empty VendorField value"); - - const transactions = this.generateTransactions(arkToSatoshi(2), wallets, null, null, null); - - try { - await this.sendTransactions(transactions); - - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } else if (tx.vendorField) { - logger.error( - `Transaction '${transaction.id}' should not have vendorField value '${tx.vendorField}'`, - ); - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } -} diff --git a/packages/core-tester-cli/src/commands/vote.ts b/packages/core-tester-cli/src/commands/vote.ts deleted file mode 100644 index 15fdead066..0000000000 --- a/packages/core-tester-cli/src/commands/vote.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { client } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import sample from "lodash/sample"; -import pluralize from "pluralize"; -import { customFlags } from "../flags"; -import { logger, parseFee, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; -import { TransferCommand } from "./transfer"; - -export class VoteCommand extends BaseCommand { - public static description: string = "create multiple votes for a delegate"; - - public static flags = { - ...BaseCommand.flags, - delegate: flags.string({ - description: "delegate public key", - }), - voteFee: customFlags.number({ - description: "vote fee", - default: 1, - }), - }; - - /** - * Run vote command. - * @return {void} - */ - public async run(): Promise { - // tslint:disable-next-line: no-shadowed-variable - const { flags } = await this.initialize(VoteCommand); - - const wallets = this.generateWallets(); - - for (const wallet of wallets) { - await TransferCommand.run( - ["--recipient", wallet.address, "--amount", String(2), "--skipTesting"].concat(this.castFlags(flags)), - ); - } - - let delegate = this.options.delegate; - if (!delegate) { - try { - delegate = sample(await this.getDelegates()).publicKey; - } catch (error) { - logger.error(error); - return; - } - } - - const voters = await this.getVoters(delegate); - logger.info(`Sending ${this.options.number} vote ${pluralize("transaction", this.options.number)}`); - - const transactions = []; - wallets.forEach((wallet, i) => { - const transaction = client - .getBuilder() - .vote() - .fee(parseFee(this.options.voteFee)) - .votesAsset([`+${delegate}`]) - .network(this.config.network.version) - .sign(wallet.passphrase) - .secondSign(this.config.secondPassphrase) - .build(); - - transactions.push(transaction); - - logger.info(`${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.data.fee)})`); - }); - - if (this.options.copy) { - this.copyToClipboard(transactions); - return; - } - - const expectedVoterCount = voters.length + wallets.length; - if (!this.options.skipValidation) { - logger.info(`Expected end voters: ${expectedVoterCount}`); - } - - try { - await this.sendTransactions(transactions, "vote", !this.options.skipValidation); - - if (this.options.skipValidation) { - return; - } - - const voterCount = (await this.getVoters(delegate)).length; - - logger.info(`All transactions have been sent! Total voters: ${voterCount}`); - - if (voterCount !== expectedVoterCount) { - logger.error(`Delegate voter count incorrect. '${voterCount}' but should be '${expectedVoterCount}'`); - } - } catch (error) { - logger.error( - `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`, - ); - } - } -} diff --git a/packages/core-tester-cli/src/config.ts b/packages/core-tester-cli/src/config.ts deleted file mode 100644 index 49fdac960a..0000000000 --- a/packages/core-tester-cli/src/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const config = Object.freeze({ - apiPort: 4003, - p2pPort: 4000, - baseUrl: "http://localhost", - passphrase: "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire", - secondPassphrase: "", -}); diff --git a/packages/core-tester-cli/src/flags.ts b/packages/core-tester-cli/src/flags.ts index a75effe727..d9820bc27a 100644 --- a/packages/core-tester-cli/src/flags.ts +++ b/packages/core-tester-cli/src/flags.ts @@ -1,14 +1,13 @@ -import { flags as oclifFlags } from "@oclif/command"; +import { flags } from "@oclif/command"; -export const customFlags = { - number: oclifFlags.build({ - parse: input => { - const value = Number(input); - if (value < 1 / 1e8) { - throw new Error(`Expected number greater than 1 satoshi.`); - } +export const satoshiFlag = flags.build({ + parse: input => { + const value = Number(input); - return value; - }, - }), -}; + if (value < 1 / 1e8) { + throw new Error(`Expected number greater than 1 satoshi.`); + } + + return value; + }, +}); diff --git a/packages/core-tester-cli/src/http-client.ts b/packages/core-tester-cli/src/http-client.ts new file mode 100644 index 0000000000..f0bc539481 --- /dev/null +++ b/packages/core-tester-cli/src/http-client.ts @@ -0,0 +1,29 @@ +import axios from "axios"; + +export class HttpClient { + private baseURL: any; + + public constructor(baseURL: string) { + this.baseURL = baseURL; + } + + public async get(path: string, params?: Record, headers?: Record): Promise { + try { + const { data } = await axios.get(`${this.baseURL}${path}`, { params, headers }); + + return data; + } catch (error) { + // do nothing... + } + } + + public async post(path: string, payload: Record): Promise { + try { + const { data } = await axios.post(`${this.baseURL}${path}`, payload); + + return data; + } catch (error) { + // do nothing... + } + } +} diff --git a/packages/core-tester-cli/src/logger.ts b/packages/core-tester-cli/src/logger.ts new file mode 100644 index 0000000000..1a857882bd --- /dev/null +++ b/packages/core-tester-cli/src/logger.ts @@ -0,0 +1,7 @@ +import pino from "pino"; + +export const logger = pino({ + name: "core-tester-cli", + safe: true, + prettyPrint: true, +}); diff --git a/packages/core-tester-cli/src/shared/send.ts b/packages/core-tester-cli/src/shared/send.ts new file mode 100644 index 0000000000..03013e3d82 --- /dev/null +++ b/packages/core-tester-cli/src/shared/send.ts @@ -0,0 +1,40 @@ +import { BaseCommand } from "../commands/command"; + +export abstract class SendCommand extends BaseCommand { + public async run(): Promise { + // Parse... + const { flags } = await this.make(this.getCommand()); + + // Prepare... + const wallets = await this.createWalletsWithBalance(flags); + + // Sign... + const transactions = await this.signTransactions(flags, wallets); + + // Expect... + if (!flags.skipProbing) { + await this.expectBalances(transactions, wallets); + } + + // Send... + await this.broadcastTransactions(transactions); + + // Verify... + if (!flags.skipProbing) { + await this.verifyTransactions(transactions, wallets); + } + + // Return... + return wallets; + } + + protected abstract getCommand(): Promise; + + protected abstract async createWalletsWithBalance(flags: Record): Promise; + + protected abstract async signTransactions(flags: Record, wallets: Record): Promise; + + protected abstract async expectBalances(transactions, wallets): Promise; + + protected abstract async verifyTransactions(transactions, wallets): Promise; +} diff --git a/packages/core-tester-cli/src/signer.ts b/packages/core-tester-cli/src/signer.ts new file mode 100644 index 0000000000..e1e7048a81 --- /dev/null +++ b/packages/core-tester-cli/src/signer.ts @@ -0,0 +1,82 @@ +import { bignumify } from "@arkecosystem/core-utils"; +import { formatSatoshi } from "@arkecosystem/crypto"; +import { client } from "@arkecosystem/crypto"; + +export class Signer { + protected network: Record; + + public constructor(network) { + this.network = network; + } + + public makeTransfer(opts: Record): any { + const transaction = client + .getBuilder() + .transfer() + .fee(this.toSatoshi(opts.transferFee)) + .network(this.network.version) + .recipientId(opts.recipient) + .amount(this.toSatoshi(opts.amount)); + + if (opts.vendorField) { + transaction.vendorField(opts.vendorField); + } + + transaction.sign(opts.passphrase); + + if (opts.secondPassphrase) { + transaction.secondSign(opts.secondPassphrase); + } + + return transaction.getStruct(); + } + + public makeDelegate(opts: Record): any { + const transaction = client + .getBuilder() + .delegateRegistration() + .fee(this.toSatoshi(opts.delegateFee)) + .network(this.network.version) + .usernameAsset(opts.username) + .sign(opts.passphrase); + + if (opts.secondPassphrase) { + transaction.secondSign(opts.secondPassphrase); + } + + return transaction.getStruct(); + } + + public makeSecondSignature(opts: Record): any { + return client + .getBuilder() + .secondSignature() + .fee(this.toSatoshi(opts.signatureFee)) + .network(this.network.version) + .signatureAsset(opts.secondPassphrase) + .sign(opts.passphrase) + .getStruct(); + } + + public makeVote(opts: Record): any { + const transaction = client + .getBuilder() + .vote() + .fee(this.toSatoshi(opts.voteFee)) + .votesAsset([`+${opts.delegate}`]) + .network(this.network.version) + .sign(opts.passphrase); + + if (opts.secondPassphrase) { + transaction.secondSign(opts.secondPassphrase); + } + + return transaction.getStruct(); + } + + private toSatoshi(value) { + return bignumify(value) + .times(1e8) + .toFixed(); + } +} diff --git a/packages/core-tester-cli/src/utils.ts b/packages/core-tester-cli/src/utils.ts index 66d5a5ff00..8ff6884a38 100644 --- a/packages/core-tester-cli/src/utils.ts +++ b/packages/core-tester-cli/src/utils.ts @@ -1,144 +1,17 @@ -import { bignumify } from "@arkecosystem/core-utils"; -import { Bignum, client, formatSatoshi } from "@arkecosystem/crypto"; -import axios from "axios"; -import pino from "pino"; +import clipboardy from "clipboardy"; -export const logger = pino({ - name: "core-tester-cli", - safe: true, - prettyPrint: true, -}); - -export function request(config) { - const headers: any = {}; - if (config && config.network) { - headers.nethash = config.network.nethash; - headers.version = "2.1.0"; - headers.port = config.p2pPort; - headers["Content-Type"] = "application/json"; - } - - return { - get: async (endpoint, isP2P = false) => { - const baseUrl = `${config.baseUrl}:${isP2P ? config.p2pPort : config.apiPort}`; - - return (await axios.get(baseUrl + endpoint, { headers })).data; - }, - post: async (endpoint, data, isP2P = false) => { - const baseUrl = `${config.baseUrl}:${isP2P ? config.p2pPort : config.apiPort}`; - - return (await axios.post(baseUrl + endpoint, data, { headers })).data; - }, - }; +export function copyToClipboard(data) { + clipboardy.writeSync(JSON.stringify(data)); } -export async function paginate(config, endpoint) { - const data = []; - let page = 1; - let maxPages = null; - while (maxPages === null || page <= maxPages) { - const response = await request(config).get(`${endpoint}?page=${page}`); - if (response) { - page++; - maxPages = response.meta.pageCount; - data.push(...response.data); - } else { - break; - } +export function handleOutput(opts, data) { + if (opts.copy) { + return copyToClipboard(data); } - return data; -} - -/** - * Generate batch of transactions based on wallets. - */ -export function generateTransactions( - amountPerTransaction: any, - wallets: any[], - approvalWallets: any[], - options: { - config: any; - overridePassphrase?: false; - vendorField?: string; - log?: boolean; - [key: string]: any; - }, -) { - const transactions = []; - wallets.forEach((wallet, i) => { - const builder = client.getBuilder().transfer(); - // noinspection JSCheckFunctionSignatures - builder - .fee(this.parseFee(options.transferFee)) - .recipientId(options.recipient || wallet.address) - .network(options.config.network.version) - .amount(amountPerTransaction) - .vendorField(options.vendorField === undefined ? `Transaction ${i + 1}` : options.vendorField) - .sign(options.overridePassphrase ? options.config.passphrase : wallet.passphrase); - - if (wallet.secondPassphrase || options.config.secondPassphrase) { - builder.secondSign(wallet.secondPassphrase || options.config.secondPassphrase); - } - - if (approvalWallets) { - for (let j = approvalWallets.length - 1; j >= 0; j--) { - builder.multiSignatureSign(approvalWallets[j].passphrase); - } - } - - const transaction = builder.build(); - transactions.push(transaction); - - if (options.log) { - logger.info( - `${i} ==> ${transaction.id}, ${transaction.data.recipientId} (fee: ${satoshiToArk( - transaction.data.fee, - )})`, - ); - } - }); - - return transactions; -} - -/** - * Parse fee based on input. - * @param {(String|Number)} fee - * @return {Bignum} - */ -export function parseFee(fee): Bignum { - if (typeof fee === "string" && fee.indexOf("-") !== -1) { - const feeRange = fee.split("-").map( - f => - +bignumify(f) - .times(1e8) - .toFixed(), - ); - if (feeRange[1] < feeRange[0]) { - return bignumify(feeRange[0]); - } - - return bignumify(Math.floor(Math.random() * (feeRange[1] - feeRange[0] + 1) + feeRange[0])); + if (opts.log) { + return console.log(data); } - return bignumify(fee).times(1e8); -} - -/** - * Convert ARK to Satoshi. - * @param {Number} ark - * @return {Bignum} - */ -export function arkToSatoshi(ark) { - return bignumify(ark * 1e8); -} - -/** - * Convert Satoshi to ARK. - * @param {Bignum} satoshi - * @return {String} - */ -export function satoshiToArk(satoshi) { - return formatSatoshi(satoshi); + return data; } diff --git a/yarn.lock b/yarn.lock index 30ae170e9e..0dff240e95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2024,13 +2024,6 @@ dependencies: "@types/lodash" "*" -"@types/lodash.fill@^3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@types/lodash.fill/-/lodash.fill-3.4.4.tgz#c54608d7da691142bf281134149b6ecf0d1f701b" - integrity sha512-D2c164uS5YG3OYmalDFW3yXlhq3DmFE8y1EdQ9MqQ9VPFBD9+73GMzZxRAdG4/G8O3ZNeERkRGXMJCgsWi7c6A== - dependencies: - "@types/lodash" "*" - "@types/lodash.flatten@^4.4.4": version "4.4.4" resolved "https://registry.yarnpkg.com/@types/lodash.flatten/-/lodash.flatten-4.4.4.tgz#7f28009ef57c8d2b1d8463c3e53fdccf780120a5" @@ -8513,11 +8506,6 @@ lodash.compact@^3.0.1: resolved "https://registry.yarnpkg.com/lodash.compact/-/lodash.compact-3.0.1.tgz#540ce3837745975807471e16b4a2ba21e7256ca5" integrity sha1-VAzjg3dFl1gHRx4WtKK6IeclbKU= -lodash.fill@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/lodash.fill/-/lodash.fill-3.4.0.tgz#a3c74ae640d053adf0dc2079f8720788e8bfef85" - integrity sha1-o8dK5kDQU63w3CB5+HIHiOi/74U= - lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" @@ -10594,6 +10582,13 @@ podium@3.x.x: hoek "6.x.x" joi "14.x.x" +pokemon@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/pokemon/-/pokemon-1.2.3.tgz#2b494263606408df10f6c499c299e9c2446ac05c" + integrity sha512-3wwtG0QKvkje3umDTnaNbPNLmL3XoxdjQwRMz3WEcA/txEagzmWHB1H1TwPNCgZvjIsTrkX7j+DK5Y9+Eu9Xyg== + dependencies: + unique-random-array "^1.0.0" + port-numbers@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/port-numbers/-/port-numbers-4.0.4.tgz#fe1c1fa7cd551f4ceb835b3bbf88c07baa0783d7"