diff --git a/.circleci/config.yml b/.circleci/config.yml index 9e6d58aabe..fdd960990a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,8 +2,8 @@ version: 2.1 jobs: build: environment: - CI_NO_CORDA: false - CI_CONTAINERS_WAIT_TIME: 60 + CI_NO_CORDA: 'false' + CI_CONTAINERS_WAIT_TIME: '60' machine: image: ubuntu-1604:201903-01 resource_class: xlarge @@ -34,19 +34,7 @@ jobs: - run: name: Run Hyperledger Cactus CI script - command: | - ./packages/core/tools/ci.sh - - # Upload test results for display in Test Summary: - # https://circleci.com/docs/2.0/collect-test-data/ - - store_test_results: - path: packages/core/examples/simple-asset-transfer/corda/logs/ - - # Upload test results for display in Artifacts: - # https://circleci.com/docs/2.0/artifacts/ - - store_artifacts: - path: packages/core/examples/simple-asset-transfer/corda/logs/ - when: always + command: ./tools/ci.sh workflows: version: 2 diff --git a/.travis.yml b/.travis.yml index 5a6810b9d8..fa06da0327 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ services: - docker script: - - CI_CONTAINERS_WAIT_TIME=12 CI_NO_CORDA=true ./packages/core/tools/ci.sh + - CI_CONTAINERS_WAIT_TIME=12 CI_NO_CORDA=true ./tools/ci.sh after_script: - docker ps -a diff --git a/package.json b/package.json index a3a4d69b6a..15fa368c83 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "build:backend": "npm-run-all lint clean generate-sdk tsc webpack", "build:dev:pkg:cmd-api-server": "lerna exec --stream --scope '*/*api-server' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:pkg:test-tooling": "lerna exec --stream --scope '*/*test-tooling' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:pkg:bif-plugin-ledger-connector-quorum": "lerna exec --stream --scope '*/*connector-quorum' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:pkg:sdk": "lerna exec --stream --scope '*/*sdk' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "webpack": "npm-run-all webpack:web:dev webpack:node:dev webpack:web:prod webpack:node:prod", "webpack:web:prod": "lerna exec --stream --ignore '*/*{cockpit,server,test-tooling}' -- webpack --env=prod --target=web --config ../../webpack.config.js", diff --git a/packages/bif-plugin-ledger-connector-quorum/package.json b/packages/bif-plugin-ledger-connector-quorum/package.json index 5e677847f3..06df3b543e 100644 --- a/packages/bif-plugin-ledger-connector-quorum/package.json +++ b/packages/bif-plugin-ledger-connector-quorum/package.json @@ -52,7 +52,8 @@ "@hyperledger-labs/bif-common": "0.2.0", "@hyperledger-labs/bif-core-api": "^0.2.0", "joi": "14.3.1", - "web3": "1.2.7" + "web3": "1.2.7", + "web3-utils": "1.2.7" }, "devDependencies": { "@hyperledger-labs/bif-common": "0.2.0", diff --git a/packages/bif-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts b/packages/bif-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts index c8e8227d6b..963a323885 100644 --- a/packages/bif-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts +++ b/packages/bif-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts @@ -1,8 +1,9 @@ +import { asciiToHex } from 'web3-utils'; import { IPluginLedgerConnector } from '@hyperledger-labs/bif-core-api'; import { Logger, LoggerProvider } from '@hyperledger-labs/bif-common'; import Web3 from 'web3'; -import { Contract, ContractSendMethod, ContractOptions, DeployOptions } from 'web3-eth-contract/types/index'; +import { Contract, ContractSendMethod, ContractOptions, DeployOptions, SendOptions } from 'web3-eth-contract/types/index'; import { PromiEvent, TransactionReceipt } from 'web3-core/types/index'; export interface IPluginLedgerConnectorQuorumOptions { @@ -24,7 +25,7 @@ export class PluginLedgerConnectorQuorum implements IPluginLedgerConnector { @@ -37,34 +38,76 @@ export class PluginLedgerConnectorQuorum implements IPluginLedgerConnector { - const contract: Contract = this.instantiateContract(options.contractJsonArtifact); - - const contractSendMethod: ContractSendMethod = contract.deploy({ - data: options.contractJsonArtifact.bytecode, - arguments: [] - }); - - const promiEventContract: PromiEvent = contractSendMethod.send({ - from: options.from || this.web3.eth.defaultAccount, - gas: options.gas || 10000000, - }); - - promiEventContract - .on('confirmation', (confNumber: number, receipt: TransactionReceipt) => { - this.log.debug(`deployContract() - confirmation: `, { confNumber, receipt }); - }) - .on('error', (error: Error) => { - this.log.error(`deployContract() - error: `, error); - }) - .on('receipt', (receipt: TransactionReceipt) => { - this.log.error(`deployContract() - receipt: `, { receipt }); - }) - .on('transactionHash', (receipt: string) => { - this.log.error(`deployContract() - transactionHash: `, { receipt }); - }); - - const deployedContract: Contract = await promiEventContract; - this.log.debug(`Successfully deployed contract.`, { deployedContract }); + try { + const ethPassword = ''; + const unlocked: boolean = await this.web3.eth.personal.unlockAccount(options.from, ethPassword, 3600); + this.log.debug(`Web3 Account unlock outcome: ${unlocked}`); + } catch (ex) { + throw new Error(`PluginLedgerConnectorQuorum#deployContract() failed to unlock account ${options.from}: ${ex.stack}`); + } + + const fromAddress = options.from; + const contract: Contract = this.instantiateContract(options.contractJsonArtifact, fromAddress); + this.log.debug(`Instantiated contract OK`); + + let nonce: number; + try { + this.log.debug(`Getting transaction count (nonce for account)`); + nonce = await this.web3.eth.getTransactionCount(fromAddress); + this.log.debug(`Transaction count (nonce) acquird OK: ${nonce}`); + } catch (ex) { + throw new Error(`Failed to obtain nonce: ${ex.stack}`); + } + + const deployOptions: DeployOptions = { + data: '0x' + options.contractJsonArtifact.bytecode, + }; + this.log.debug(`Calling contract deployment...`); + const deployTask: ContractSendMethod = contract.deploy(deployOptions); + this.log.debug(`Called deploy task OK with options: `, { deployOptions }); + + // try { + // this.log.debug(`Asking ledger for gas estimate...`); + // const gasEstimate = await deployTask.estimateGas({ gas: 5000000, }); + // this.log.debug(`Got GasEstimate=${gasEstimate}`); + // // const gas = gasEstimate * 3; // offer triple the gas estimate to be sure + // } catch (ex) { + // throw new Error(`PluginLedgerConnectorQuorum#deployContract() failed to get gas estimate: ${ex.stack}`); + // } + + const sendOptions: SendOptions = { + from: fromAddress, + gas: 1500000, + gasPrice: '0', + }; + + this.log.debug(`Calling send on deploy task...`); + const promiEventContract: PromiEvent = deployTask.send(sendOptions); + this.log.debug(`Called send OK with options: `, { sendOptions }); + + // promiEventContract + // .once('confirmation', (confNumber: number, receipt: TransactionReceipt) => { + // this.log.debug(`deployContract() - confirmation: `, { confNumber, receipt }); + // }) + // .once('error', (error: Error) => { + // this.log.error(`deployContract() - error: `, error); + // }) + // .once('receipt', (receipt: TransactionReceipt) => { + // this.log.debug(`deployContract() - receipt: `, { receipt }); + // }) + // .once('transactionHash', (receipt: string) => { + // this.log.debug(`deployContract() - transactionHash: `, { receipt }); + // }); + + try { + this.log.debug(`Starting await for contract deployment promise...`); + const deployedContract: Contract = await promiEventContract; + this.log.debug(`Deployed contract OK.`); + return deployedContract; + } catch (ex) { + const message = `PluginLedgerConnectorQuorum#deployContract() Failed to deploy contract: ${ex.stack}`; + throw new Error(message); + } } public async addPublicKey(publicKeyHex: string): Promise { diff --git a/packages/bif-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/deploy-contract-from-json.ts b/packages/bif-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/deploy-contract-from-json.ts index a9fc511865..1a1d78effe 100644 --- a/packages/bif-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/deploy-contract-from-json.ts +++ b/packages/bif-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/deploy-contract-from-json.ts @@ -1,31 +1,51 @@ // tslint:disable-next-line: no-var-requires const tap = require('tap'); import { PluginLedgerConnectorQuorum, PluginFactoryLedgerConnector } from '../../../../../main/typescript/public-api'; -import { QuorumTestLedger } from '@hyperledger-labs/bif-test-tooling'; +import { QuorumTestLedger, IQuorumGenesisOptions, IAccount } from '@hyperledger-labs/bif-test-tooling'; import HelloWorldContractJson from '../../../../solidity/hello-world-contract/HelloWorld.json'; +import { Logger, LoggerProvider } from '@hyperledger-labs/bif-common'; + +const log: Logger = LoggerProvider.getOrCreate({ label: 'test-deploy-contract-from-json', level: 'trace' }) tap.test('deploys contract via .json file', async (assert: any) => { - assert.plan(1); - const quorumTestLedger = new QuorumTestLedger(); + const quorumTestLedger = new QuorumTestLedger({ containerImageVersion: '1.0.0' }); await quorumTestLedger.start(); assert.tearDown(async () => { + log.debug(`Starting teardown...`); await quorumTestLedger.stop(); + log.debug(`Stopped container OK.`); await quorumTestLedger.destroy(); + log.debug(`Destroyed container OK.`); }); + // const rpcApiHttpHost: string = 'http://localhost:22000'; const rpcApiHttpHost = await quorumTestLedger.getRpcApiHttpHost(); + const quorumGenesisOptions: IQuorumGenesisOptions = await quorumTestLedger.getGenesisJsObject(); + assert.ok(quorumGenesisOptions); + assert.ok(quorumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys(quorumGenesisOptions.alloc).filter((address: string) => { + const anAccount: IAccount = quorumGenesisOptions.alloc[address]; + const balance: number = parseInt(anAccount.balance, 10); + return balance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; const factory = new PluginFactoryLedgerConnector(); const connector: PluginLedgerConnectorQuorum = await factory.create({ rpcApiHttpHost }); const options = { + from: firstHighNetWorthAccount, // 0xed9d02e382b34818e88b88a309c7fe71e65f419d from the gensis json alloc property contractJsonArtifact: HelloWorldContractJson, + // gas: 100000000000, + // gasPrice: 0, }; const out = await connector.deployContract(options); assert.ok(out); assert.end(); + log.debug('Assertion ended OK.'); }); diff --git a/packages/bif-test-tooling/src/main/typescript/public-api.ts b/packages/bif-test-tooling/src/main/typescript/public-api.ts index 0695ced12a..e6e12f607c 100755 --- a/packages/bif-test-tooling/src/main/typescript/public-api.ts +++ b/packages/bif-test-tooling/src/main/typescript/public-api.ts @@ -2,3 +2,4 @@ export { ITestLedger } from './i-test-ledger'; export { IKeyPair, isIKeyPair } from './i-key-pair'; export { BesuTestLedger, IBesuTestLedgerConstructorOptions, BESU_TEST_LEDGER_DEFAULT_OPTIONS, BESU_TEST_LEDGER_OPTIONS_JOI_SCHEMA } from './besu/besu-test-ledger'; export { QuorumTestLedger, IQuorumTestLedgerConstructorOptions, QUORUM_TEST_LEDGER_DEFAULT_OPTIONS, QUORUM_TEST_LEDGER_OPTIONS_JOI_SCHEMA } from './quorum/quorum-test-ledger'; +export * from './quorum/i-quorum-genesis-options'; diff --git a/packages/bif-test-tooling/src/main/typescript/quorum/i-quorum-genesis-options.ts b/packages/bif-test-tooling/src/main/typescript/quorum/i-quorum-genesis-options.ts new file mode 100644 index 0000000000..6b09c73684 --- /dev/null +++ b/packages/bif-test-tooling/src/main/typescript/quorum/i-quorum-genesis-options.ts @@ -0,0 +1,35 @@ + +export interface IAccount { + balance: string; +} + +export interface IAllocations { + [key: string]: IAccount; +} + +export interface IConfig { + homesteadBlock: number; + byzantiumBlock: number; + constantinopleBlock: number; + chainId: number; + eip150Block: number; + eip155Block: number; + eip150Hash: string; + eip158Block: number; + maxCodeSize: number; + isQuorum: boolean; +} + +export interface IQuorumGenesisOptions { + alloc: IAllocations; + coinbase: string; + config: IConfig; + difficulty: string; + extraData: string; + gasLimit: string; + mixhash: string; + nonce: string; + parentHash: string; + timestamp: string; +} + diff --git a/packages/bif-test-tooling/src/main/typescript/quorum/quorum-test-ledger.ts b/packages/bif-test-tooling/src/main/typescript/quorum/quorum-test-ledger.ts index 1af1e70f68..f2aeaab6e4 100644 --- a/packages/bif-test-tooling/src/main/typescript/quorum/quorum-test-ledger.ts +++ b/packages/bif-test-tooling/src/main/typescript/quorum/quorum-test-ledger.ts @@ -6,6 +6,7 @@ import { EventEmitter } from 'events'; import { ITestLedger } from "../i-test-ledger"; import { Streams } from '../common/streams'; import { IKeyPair } from '../i-key-pair'; +import { IQuorumGenesisOptions } from './i-quorum-genesis-options'; export interface IQuorumTestLedgerConstructorOptions { containerImageVersion?: string; @@ -91,6 +92,11 @@ export class QuorumTestLedger implements ITestLedger { return { publicKey, privateKey }; } + public async getGenesisJsObject(): Promise { + const quorumGenesisJson: string = await this.getFileContents('/genesis.json'); + return JSON.parse(quorumGenesisJson); + } + public async getTesseraKeyPair(): Promise { const publicKey = await this.getFileContents('/tm.pub'); const privateKey = await this.getFileContents('/tm.key'); @@ -110,12 +116,14 @@ export class QuorumTestLedger implements ITestLedger { await this.pullContainerImage(containerNameAndTag); return new Promise((resolve, reject) => { - const eventEmitter: EventEmitter = docker.run( containerNameAndTag, [], [], { + // Env: [ + // 'PRIVATE_CONFIG=ignore'// FIXME make it possible to have privacy configured programmatically for quorum + // ], ExposedPorts: { [`${this.rpcApiHttpPort}/tcp`]: {}, // quorum RPC - HTTP '8546/tcp': {}, // quorum RPC - WebSocket diff --git a/tools/ci.sh b/tools/ci.sh new file mode 100755 index 0000000000..4ddf9acd14 --- /dev/null +++ b/tools/ci.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +### +### Continous Integration Shell Script +### +### Designed to be re-entrant on a local dev machine as well, not just on a +### newly pulled up VM. +### +echo $BASH_VERSION + +STARTED_AT=`date +%s` +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +PROJECT_ROOT_DIR="$SCRIPT_DIR/.." + +function mainTask() +{ + set -euxo pipefail + + if ! [ -x "$(command -v lscpu)" ]; then + echo 'lscpu is not installed, skipping...' + else + lscpu + fi + + if ! [ -x "$(command -v lsmem)" ]; then + echo 'lsmem is not installed, skipping...' + else + lsmem + fi + + if ! [ -x "$(command -v smem)" ]; then + echo 'smem is not installed, skipping...' + else + smem --abbreviate --totals --system + fi + + # Travis does not have (nor need) nvm but CircleCI does have nvm and also + # need it big time because their default Node version is 6.x... + if [ "${CIRCLECI:-false}" = "true" ]; then + set +x + nvm install 10.19.0 + nvm alias default 10.19.0 + set -x + fi + + docker --version + docker-compose --version + node --version + npm --version + java -version + + ### COMMON + cd $PROJECT_ROOT_DIR + + npm install + npm run configure + npm run test + npm run test-integration + + ENDED_AT=`date +%s` + runtime=$((ENDED_AT-STARTED_AT)) + echo "$(date +%FT%T%z) [CI] SUCCESS - runtime=$runtime seconds." + exit 0 +} + +function onTaskFailure() +{ + set +eu # do not crash process upon individual command failures + + ENDED_AT=`date +%s` + runtime=$((ENDED_AT-STARTED_AT)) + echo "$(date +%FT%T%z) [CI] FAILURE - runtime=$runtime seconds." + exit 1 +} + +( + mainTask +) +if [ $? -ne 0 ]; then + onTaskFailure +fi