From 7e154175f7b292f9634f81a52ed5ad0e39b42330 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Tue, 5 Dec 2017 22:24:40 -0800 Subject: [PATCH] Address Derivation (CI) (#529) * improve derivation-checking performance by batching docker calls; move into spec dir * remove npm command to run derivation-checking; create 'int-test' (integration) command and hook up into jest * add integration testing to CI; configure docker / docker image (dternyak/eth-priv-to-addr) in CI * docker build -> docker pull * use travis build matrix to group tests and improve build times * remove int-test call * attempt travis 'job' with all tests running in parallel * remove typo * attempt travis 'job' with all tests running in parallel (round 2) * organize integration tests * refactor/cleanup * refactor/address comments --- .travis.yml | 28 +++++-- common/derivation-checker.ts | 57 -------------- jest_config/jest.int.config.json | 22 ++++++ package.json | 3 +- .../RpcNodeTestConfig.js | 0 .../data.spec.ts => integration/data.int.ts} | 19 +---- spec/integration/derivationChecker.int.ts | 75 +++++++++++++++++++ 7 files changed, 123 insertions(+), 81 deletions(-) delete mode 100644 common/derivation-checker.ts create mode 100644 jest_config/jest.int.config.json rename spec/{config => integration}/RpcNodeTestConfig.js (100%) rename spec/{config/data.spec.ts => integration/data.int.ts} (82%) create mode 100644 spec/integration/derivationChecker.int.ts diff --git a/.travis.yml b/.travis.yml index 8da34db3..4032617f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,22 +2,34 @@ dist: trusty sudo: required language: node_js +services: + - docker + before_install: - export CHROME_BIN=chromium-browser - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start + - docker pull dternyak/eth-priv-to-addr:latest install: - npm install --silent +jobs: + include: + - stage: test + script: npm run test + - stage: test + script: npm run test:int + - stage: test + script: npm run tslint + - stage: test + script: npm run tscheck + - stage: test + script: npm run freezer + - stage: test + script: npm run freezer:validate + notifications: email: on_success: never - on_failure: never - -script: -- npm run test -- npm run tslint -- npm run tscheck -- npm run freezer -- npm run freezer:validate + on_failure: never \ No newline at end of file diff --git a/common/derivation-checker.ts b/common/derivation-checker.ts deleted file mode 100644 index a68b2aae..00000000 --- a/common/derivation-checker.ts +++ /dev/null @@ -1,57 +0,0 @@ -import assert from 'assert'; -import { generate, IFullWallet } from 'ethereumjs-wallet'; -const { exec } = require('child_process'); -const ProgressBar = require('progress'); - -// FIXME pick a less magic number -const derivationRounds = 100; -const dockerImage = 'dternyak/eth-priv-to-addr'; -const dockerTag = 'latest'; -const bar = new ProgressBar(':percent :bar', { total: derivationRounds }); - -function promiseFromChildProcess(command): Promise { - return new Promise((resolve, reject) => { - return exec(command, (err, stdout) => { - err ? reject(err) : resolve(stdout); - }); - }); -} - -async function privToAddrViaDocker(privKeyWallet: IFullWallet) { - const command = `docker run -e key=${privKeyWallet.getPrivateKeyString()} ${ - dockerImage - }:${dockerTag}`; - const dockerOutput = await promiseFromChildProcess(command); - const newlineStrippedDockerOutput = dockerOutput.replace( - /(\r\n|\n|\r)/gm, - '' - ); - return newlineStrippedDockerOutput; -} - -async function testDerivation() { - const privKeyWallet = generate(); - const privKeyWalletAddress = await privKeyWallet.getAddressString(); - const dockerAddr = await privToAddrViaDocker(privKeyWallet); - // strip the checksum - const lowerCasedPrivKeyWalletAddress = privKeyWalletAddress.toLowerCase(); - // ensure that pyethereum privToAddr derivation matches our (js based) derivation - assert.strictEqual(dockerAddr, lowerCasedPrivKeyWalletAddress); -} - -async function testDerivationNTimes(n = derivationRounds) { - let totalRounds = 0; - while (totalRounds < n) { - await testDerivation(); - bar.tick(); - totalRounds += 1; - } -} - -console.log('Starting testing...'); -console.time('testDerivationNTimes'); -testDerivationNTimes().then(() => { - console.timeEnd('testDerivationNTimes'); - console.log(`Succeeded testing derivation ${derivationRounds} times :)`); - process.exit(0); -}); diff --git a/jest_config/jest.int.config.json b/jest_config/jest.int.config.json new file mode 100644 index 00000000..404dce04 --- /dev/null +++ b/jest_config/jest.int.config.json @@ -0,0 +1,22 @@ +{ + "rootDir": "../", + "transform": { + "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" + }, + "testRegex": "(/__tests__/.*|(\\.|/)(int))\\.(jsx?|tsx?)$", + "moduleDirectories": ["node_modules", "common"], + "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"], + "moduleNameMapper": { + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": + "/jest_config/__mocks__/fileMock.ts", + "\\.(css|scss|less)$": "/jest_config/__mocks__/styleMock.ts" + }, + "testPathIgnorePatterns": ["/common/config"], + "setupFiles": [ + "/jest_config/setupJest.js", + "/jest_config/__mocks__/localStorage.ts" + ], + "automock": false, + "snapshotSerializers": ["enzyme-to-json/serializer"], + "browser": true +} diff --git a/package.json b/package.json index a17cf6d9..50a7fa24 100644 --- a/package.json +++ b/package.json @@ -120,13 +120,14 @@ "build:demo": "BUILD_GH_PAGES=true webpack --config webpack_config/webpack.prod.js", "prebuild:demo": "check-node-version --package", "test": "jest --config=jest_config/jest.config.json --coverage", + "test:unit": "jest --config=jest_config/jest.config.json --coverage", + "test:int": "jest --config=jest_config/jest.int.config.json --coverage", "updateSnapshot": "jest --config=jest_config/jest.config.json --updateSnapshot", "pretest": "check-node-version --package", "dev": "node webpack_config/server.js", "predev": "check-node-version --package", "dev:https": "HTTPS=true node webpack_config/server.js", "predev:https": "check-node-version --package", - "derivation-checker": "webpack --config=./webpack_config/webpack.derivation-checker.js && node ./dist/derivation-checker.js", "tslint": "tslint --project . --exclude common/vendor/**/*", "tscheck": "tsc --noEmit", "postinstall": "webpack --config=./webpack_config/webpack.dll.js", diff --git a/spec/config/RpcNodeTestConfig.js b/spec/integration/RpcNodeTestConfig.js similarity index 100% rename from spec/config/RpcNodeTestConfig.js rename to spec/integration/RpcNodeTestConfig.js diff --git a/spec/config/data.spec.ts b/spec/integration/data.int.ts similarity index 82% rename from spec/config/data.spec.ts rename to spec/integration/data.int.ts index d02a2208..4287eca0 100644 --- a/spec/config/data.spec.ts +++ b/spec/integration/data.int.ts @@ -55,9 +55,7 @@ function testRpcRequests(node: RPCNode, service: string) { it( `RPC: ${testType} ${service}`, () => { - return RPCTests[testType](node).then(d => - expect(d.valid).toBeTruthy() - ); + return RPCTests[testType](node).then(d => expect(d.valid).toBeTruthy()); }, 10000 ); @@ -69,24 +67,15 @@ const mapNodeEndpoints = (nodes: { [key: string]: NodeConfig }) => { const { RpcNodes, EtherscanNodes, InfuraNodes } = RpcNodeTestConfig; RpcNodes.forEach(n => { - testRpcRequests( - nodes[n].lib as RPCNode, - `${nodes[n].service} ${nodes[n].network}` - ); + testRpcRequests(nodes[n].lib as RPCNode, `${nodes[n].service} ${nodes[n].network}`); }); EtherscanNodes.forEach(n => { - testRpcRequests( - nodes[n].lib as EtherscanNode, - `${nodes[n].service} ${nodes[n].network}` - ); + testRpcRequests(nodes[n].lib as EtherscanNode, `${nodes[n].service} ${nodes[n].network}`); }); InfuraNodes.forEach(n => { - testRpcRequests( - nodes[n].lib as InfuraNode, - `${nodes[n].service} ${nodes[n].network}` - ); + testRpcRequests(nodes[n].lib as InfuraNode, `${nodes[n].service} ${nodes[n].network}`); }); }; diff --git a/spec/integration/derivationChecker.int.ts b/spec/integration/derivationChecker.int.ts new file mode 100644 index 00000000..487ffe78 --- /dev/null +++ b/spec/integration/derivationChecker.int.ts @@ -0,0 +1,75 @@ +import { generate, IFullWallet } from 'ethereumjs-wallet'; +import { stripHexPrefix } from '../../common/libs/values'; +const { exec } = require('child_process'); + +// FIXME pick a less magic number +const derivationRounds = 500; +const dockerImage = 'dternyak/eth-priv-to-addr'; +const dockerTag = 'latest'; + +function promiseFromChildProcess(command: string): Promise { + return new Promise((resolve, reject) => { + return exec(command, (err, stdout) => { + err ? reject(err) : resolve(stdout); + }); + }); +} + +function getCleanPrivateKey(privKeyWallet: IFullWallet): string { + return stripHexPrefix(privKeyWallet.getPrivateKeyString()); +} + +function makeCommaSeparatedPrivateKeys(privKeyWallets: IFullWallet[]): string { + const privateKeys = privKeyWallets.map(getCleanPrivateKey); + return privateKeys.join(','); +} + +async function privToAddrViaDocker(privKeyWallets: IFullWallet[]): Promise { + const command = `docker run -e key=${makeCommaSeparatedPrivateKeys( + privKeyWallets + )} ${dockerImage}:${dockerTag}`; + const dockerOutput = await promiseFromChildProcess(command); + const newlineStrippedDockerOutput = dockerOutput.replace(/(\r\n|\n|\r)/gm, ''); + return newlineStrippedDockerOutput; +} + +function makeWallets(): IFullWallet[] { + const wallets: IFullWallet[] = []; + let i = 0; + while (i < derivationRounds) { + const privKeyWallet = generate(); + wallets.push(privKeyWallet); + i += 1; + } + return wallets; +} + +async function getNormalizedAddressFromWallet(wallet: IFullWallet): Promise { + const privKeyWalletAddress = await wallet.getAddressString(); + // strip checksum + return privKeyWalletAddress.toLowerCase(); +} + +async function getNormalizedAddressesFromWallets(wallets: IFullWallet[]): Promise { + return Promise.all(wallets.map(getNormalizedAddressFromWallet)); +} + +async function testDerivation(): Promise { + const wallets = makeWallets(); + const walletAddrs = await getNormalizedAddressesFromWallets(wallets); + const dockerAddrsCS = await privToAddrViaDocker(wallets); + const dockerAddrs = dockerAddrsCS.split(','); + expect(walletAddrs).toEqual(dockerAddrs); + return true; +} + +describe('Derivation Checker', () => { + beforeEach(() => { + // increase timer to prevent early timeout + jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000; + }); + + it(`should derive identical addresses ${derivationRounds} times`, () => { + return testDerivation().then(expect); + }); +});