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
This commit is contained in:
Daniel Ternyak 2017-12-05 22:24:40 -08:00 committed by GitHub
parent f6965abb9d
commit 7e154175f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 81 deletions

View File

@ -2,22 +2,34 @@ dist: trusty
sudo: required sudo: required
language: node_js language: node_js
services:
- docker
before_install: before_install:
- export CHROME_BIN=chromium-browser - export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0 - export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start - sh -e /etc/init.d/xvfb start
- docker pull dternyak/eth-priv-to-addr:latest
install: install:
- npm install --silent - 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: notifications:
email: email:
on_success: never on_success: never
on_failure: never on_failure: never
script:
- npm run test
- npm run tslint
- npm run tscheck
- npm run freezer
- npm run freezer:validate

View File

@ -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<any> {
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);
});

View File

@ -0,0 +1,22 @@
{
"rootDir": "../",
"transform": {
"^.+\\.tsx?$": "<rootDir>/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)$":
"<rootDir>/jest_config/__mocks__/fileMock.ts",
"\\.(css|scss|less)$": "<rootDir>/jest_config/__mocks__/styleMock.ts"
},
"testPathIgnorePatterns": ["<rootDir>/common/config"],
"setupFiles": [
"<rootDir>/jest_config/setupJest.js",
"<rootDir>/jest_config/__mocks__/localStorage.ts"
],
"automock": false,
"snapshotSerializers": ["enzyme-to-json/serializer"],
"browser": true
}

View File

@ -120,13 +120,14 @@
"build:demo": "BUILD_GH_PAGES=true webpack --config webpack_config/webpack.prod.js", "build:demo": "BUILD_GH_PAGES=true webpack --config webpack_config/webpack.prod.js",
"prebuild:demo": "check-node-version --package", "prebuild:demo": "check-node-version --package",
"test": "jest --config=jest_config/jest.config.json --coverage", "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", "updateSnapshot": "jest --config=jest_config/jest.config.json --updateSnapshot",
"pretest": "check-node-version --package", "pretest": "check-node-version --package",
"dev": "node webpack_config/server.js", "dev": "node webpack_config/server.js",
"predev": "check-node-version --package", "predev": "check-node-version --package",
"dev:https": "HTTPS=true node webpack_config/server.js", "dev:https": "HTTPS=true node webpack_config/server.js",
"predev:https": "check-node-version --package", "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/**/*", "tslint": "tslint --project . --exclude common/vendor/**/*",
"tscheck": "tsc --noEmit", "tscheck": "tsc --noEmit",
"postinstall": "webpack --config=./webpack_config/webpack.dll.js", "postinstall": "webpack --config=./webpack_config/webpack.dll.js",

View File

@ -55,9 +55,7 @@ function testRpcRequests(node: RPCNode, service: string) {
it( it(
`RPC: ${testType} ${service}`, `RPC: ${testType} ${service}`,
() => { () => {
return RPCTests[testType](node).then(d => return RPCTests[testType](node).then(d => expect(d.valid).toBeTruthy());
expect(d.valid).toBeTruthy()
);
}, },
10000 10000
); );
@ -69,24 +67,15 @@ const mapNodeEndpoints = (nodes: { [key: string]: NodeConfig }) => {
const { RpcNodes, EtherscanNodes, InfuraNodes } = RpcNodeTestConfig; const { RpcNodes, EtherscanNodes, InfuraNodes } = RpcNodeTestConfig;
RpcNodes.forEach(n => { RpcNodes.forEach(n => {
testRpcRequests( testRpcRequests(nodes[n].lib as RPCNode, `${nodes[n].service} ${nodes[n].network}`);
nodes[n].lib as RPCNode,
`${nodes[n].service} ${nodes[n].network}`
);
}); });
EtherscanNodes.forEach(n => { EtherscanNodes.forEach(n => {
testRpcRequests( testRpcRequests(nodes[n].lib as EtherscanNode, `${nodes[n].service} ${nodes[n].network}`);
nodes[n].lib as EtherscanNode,
`${nodes[n].service} ${nodes[n].network}`
);
}); });
InfuraNodes.forEach(n => { InfuraNodes.forEach(n => {
testRpcRequests( testRpcRequests(nodes[n].lib as InfuraNode, `${nodes[n].service} ${nodes[n].network}`);
nodes[n].lib as InfuraNode,
`${nodes[n].service} ${nodes[n].network}`
);
}); });
}; };

View File

@ -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<any> {
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<string> {
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<string> {
const privKeyWalletAddress = await wallet.getAddressString();
// strip checksum
return privKeyWalletAddress.toLowerCase();
}
async function getNormalizedAddressesFromWallets(wallets: IFullWallet[]): Promise<string[]> {
return Promise.all(wallets.map(getNormalizedAddressFromWallet));
}
async function testDerivation(): Promise<true> {
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);
});
});