From d6e5d8e097898c468145d18987a01b1e65651ba0 Mon Sep 17 00:00:00 2001 From: S3bb1 Date: Fri, 10 Jan 2020 13:38:31 +0100 Subject: [PATCH 01/24] fix ipfs timings - [CORE-816] --- src/dfs/ipfs.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dfs/ipfs.ts b/src/dfs/ipfs.ts index a8d5e26..1f7d740 100644 --- a/src/dfs/ipfs.ts +++ b/src/dfs/ipfs.ts @@ -204,8 +204,9 @@ export class Ipfs extends Logger implements DfsInterface { } } + let wait; const timeout = new Promise((resolve, reject) => { - const wait = setTimeout(() => { + wait = setTimeout(() => { clearTimeout(wait); reject(new Error(`error while getting ipfs hash ${ipfsHash}: rejected after ${IPFS_TIMEOUT}ms`)); @@ -213,6 +214,7 @@ export class Ipfs extends Logger implements DfsInterface { }); const getRemoteHash = runFunctionAsPromise(this.remoteNode.files, 'cat', ipfsHash) .then((buffer: any) => { + clearTimeout(wait); const ret = buffer.toString('binary'); if (this.cache) { this.cache.add(ipfsHash, buffer); @@ -223,6 +225,7 @@ export class Ipfs extends Logger implements DfsInterface { return ret; }) .catch(() => { + clearTimeout(wait); this.log(`error while getting ipfs hash ${ipfsHash}`); }); return Promise.race([ From e1338c0dc2cbd55453015a225d980381f838c0fa Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Fri, 17 Jan 2020 10:02:05 +0100 Subject: [PATCH 02/24] use typescript version `3.7.4` - [CORE-690] --- VERSIONS.md | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index f8f000e..a46157a 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -4,6 +4,7 @@ ### Features ### Fixes +- use typescript version `3.7.4` ### Deprecations diff --git a/package.json b/package.json index c468102..a16c672 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "nyc": "^13.1.0", "request": "^2.83.0", "ts-node": "^8.3.0", - "typescript": "^3.5.3" + "typescript": "^3.7.4" }, "homepage": "https://dbcp.online/", "husky": { @@ -76,4 +76,4 @@ }, "types": "./dist/index.d.ts", "version": "1.10.0" -} +} \ No newline at end of file From bbec6a260f157f6db1586b1c09f4ead722f5b051 Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Fri, 17 Jan 2020 10:03:55 +0100 Subject: [PATCH 03/24] use typescript version `3.7.4` - [CORE-690] --- VERSIONS.md | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index f8f000e..a46157a 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -4,6 +4,7 @@ ### Features ### Fixes +- use typescript version `3.7.4` ### Deprecations diff --git a/package.json b/package.json index c468102..a16c672 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "nyc": "^13.1.0", "request": "^2.83.0", "ts-node": "^8.3.0", - "typescript": "^3.5.3" + "typescript": "^3.7.4" }, "homepage": "https://dbcp.online/", "husky": { @@ -76,4 +76,4 @@ }, "types": "./dist/index.d.ts", "version": "1.10.0" -} +} \ No newline at end of file From deec23d75b4006a469822ba6954982c353b31726 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 21 Jan 2020 13:38:59 +0100 Subject: [PATCH 04/24] add getGasPrice to SignerInterface -[CORE-896] --- src/contracts/signer-interface.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/contracts/signer-interface.ts b/src/contracts/signer-interface.ts index dc8cc4f..8e35e67 100644 --- a/src/contracts/signer-interface.ts +++ b/src/contracts/signer-interface.ts @@ -69,4 +69,12 @@ export interface SignerInterface { * @return {Promise; + + /** + * get gas price (either from config or from api.eth.web3.eth.gasPrice (gas price median of last + * blocks) or api.config.eth.gasPrice; unset config value or set it to falsy for median gas price + * + * @return {Promise} hex string with gas price + */ + getGasPrice(): Promise; } From d6affc2450b37c9be8abb64644290e843ca82355 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 21 Jan 2020 13:47:19 +0100 Subject: [PATCH 05/24] add GasPrice function -[CORE-896] --- src/contracts/executor.spec.ts | 4 ++++ src/contracts/signer-external.ts | 10 ++++++++++ src/contracts/signer-internal.ts | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/contracts/executor.spec.ts b/src/contracts/executor.spec.ts index 0831311..072ae65 100644 --- a/src/contracts/executor.spec.ts +++ b/src/contracts/executor.spec.ts @@ -84,6 +84,10 @@ class MockedSigner implements SignerInterface { public async signMessage(): Promise { throw new Error('not implemented'); } + + public async getGasPrice(): Promise { + throw new Error('not implemented'); + } } diff --git a/src/contracts/signer-external.ts b/src/contracts/signer-external.ts index 938ea9b..cc5be13 100644 --- a/src/contracts/signer-external.ts +++ b/src/contracts/signer-external.ts @@ -30,6 +30,16 @@ export class SignerExternal implements SignerInterface { handleTxResult('not implemented'); } + /** + * get gas price (either from config or from api.eth.web3.eth.gasPrice (gas price median of last + * blocks) or api.config.eth.gasPrice; unset config value or set it to falsy for median gas price + * + * @return {Promise} hex string with gas price + */ + getGasPrice(): Promise { + throw new Error('not implemented'); + } + public signAndExecuteTransaction( contract, functionName, functionArguments, options, handleTxResult, ) { diff --git a/src/contracts/signer-internal.ts b/src/contracts/signer-internal.ts index 9155630..343242b 100644 --- a/src/contracts/signer-internal.ts +++ b/src/contracts/signer-internal.ts @@ -138,9 +138,9 @@ export class SignerInternal extends Logger implements SignerInterface { * get gas price (either from config or from api.eth.web3.eth.gasPrice (gas price median of last * blocks) or api.config.eth.gasPrice; unset config value or set it to falsy for median gas price * - * @return hex string with gas price + * @return {Promise} hex string with gas price */ - getGasPrice() { + getGasPrice(): Promise { let chain; if (this.config.gasPrice) { chain = Promise.resolve(this.config.gasPrice); From 29db88feaff7f3436ae100785a8c728c75009e9d Mon Sep 17 00:00:00 2001 From: wulfraem Date: Tue, 21 Jan 2020 13:47:36 +0100 Subject: [PATCH 06/24] update `AccountStore` to throw if private key is missing - [CORE-901] --- VERSIONS.md | 1 + src/account-store.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index a46157a..ff0b753 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -5,6 +5,7 @@ ### Fixes - use typescript version `3.7.4` +- update `AccountStore` to throw if private key is missing ### Deprecations diff --git a/src/account-store.ts b/src/account-store.ts index 81c03b9..9587262 100644 --- a/src/account-store.ts +++ b/src/account-store.ts @@ -59,7 +59,11 @@ export class AccountStore extends Logger implements KeyStoreInterface { * @param {string} accountId eth accountId * @return {Promise} private key for this account */ - getPrivateKey(accountId: string): Promise { - return Promise.resolve(this.accounts[accountId]); + async getPrivateKey(accountId: string): Promise { + const privateKey = this.accounts[accountId]; + if (!privateKey) { + throw new Error(`missing private key for accountId "${accountId}"`); + } + return privateKey; } } From 646afb40b74faab8125abb6a4d9652479c6db5be Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 21 Jan 2020 13:59:35 +0100 Subject: [PATCH 07/24] update eslint-plugin -[CORE-896] --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a16c672..9b1a0ee 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@types/chai-as-promised": "^7.1.0", "@types/chai-spies": "^1.0.1", "@types/mocha": "^5.2.7", - "@typescript-eslint/eslint-plugin": "^2.12.0", + "@typescript-eslint/eslint-plugin": "^2.16.0", "@typescript-eslint/parser": "^2.11.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", @@ -76,4 +76,4 @@ }, "types": "./dist/index.d.ts", "version": "1.10.0" -} \ No newline at end of file +} From bf734baa774805b59c1682f7e245335e4673f9b2 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 21 Jan 2020 16:27:33 +0100 Subject: [PATCH 08/24] edit visibility scope of functions -[CORE-896] --- src/contracts/signer-internal.ts | 71 ++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/contracts/signer-internal.ts b/src/contracts/signer-internal.ts index 343242b..5df116c 100644 --- a/src/contracts/signer-internal.ts +++ b/src/contracts/signer-internal.ts @@ -97,11 +97,11 @@ export class SignerInternal extends Logger implements SignerInterface { /** * @brief retrieve private key for given account * - * @param accountId eth account ID + * @param {string} accountId eth account ID * * @return Promise that resolves to {string} private key of given account */ - getPrivateKey(accountId: string) { + public async getPrivateKey(accountId: string) { return this.accountStore.getPrivateKey(accountId); } @@ -120,11 +120,11 @@ export class SignerInternal extends Logger implements SignerInterface { /** * patch '0x' prefix to input if not already added, also casts numbers to hex string * - * @param input input to prefix with '0x' + * @param {string} input to prefix with '0x' * * @return patched input */ - ensureHashWithPrefix(input: string | number) { + public ensureHashWithPrefix(input: string | number) { if (typeof input === 'number') { return `0x${input.toString(16)}`; } @@ -140,7 +140,7 @@ export class SignerInternal extends Logger implements SignerInterface { * * @return {Promise} hex string with gas price */ - getGasPrice(): Promise { + public async getGasPrice(): Promise { let chain; if (this.config.gasPrice) { chain = Promise.resolve(this.config.gasPrice); @@ -160,14 +160,14 @@ export class SignerInternal extends Logger implements SignerInterface { } /** - * gets nonce for current user, looks into actions submitted by current user in current block for - * this as well + * gets nonce for current user, looks into actions submitted by current user + * in current block for this as well * - * @param accountId Ethereum account ID + * @param {string} accountId Ethereum account ID * - * @return nonce of given user + * @return {Promise} nonce of given user */ - getNonce(accountId: string) { + public async getNonce(accountId: string) { return this.web3.eth .getTransactionCount(accountId) .then((count) => { @@ -182,12 +182,12 @@ export class SignerInternal extends Logger implements SignerInterface { * Should be called to encode constructor params (taken from * https://github.com/ethereum/web3.js/blob/develop/lib/web3/contract.js) * - * @param abi The abi - * @param params The parameters + * @param {any[]} abi The abi + * @param {any[]} params The parameters * * @return encoded params */ - encodeConstructorParams(abi: any[], params: any[]) { + public encodeConstructorParams(abi: any[], params: any[]) { if (params.length) { return abi .filter((json) => json.type === 'constructor' && json.inputs.length === params.length) @@ -259,8 +259,13 @@ export class SignerInternal extends Logger implements SignerInterface { return receipt; } - - signAndExecuteSend(options, handleTxResult) { + /** + * { Signs and send the signed transaction } + * + * @param {any} options The options + * @param {Function} handleTxResult The handle transmit result + */ + public async signAndExecuteSend(options: any, handleTxResult: Function) { this.log('will sign tx for eth for transaction', 'debug'); Promise .all([ @@ -297,18 +302,20 @@ export class SignerInternal extends Logger implements SignerInterface { } /** - * create, sing and submit a contract transaction with private key of options.from - * - * @param contract contract instance from api.eth.loadContract(...) - * @param functionName function name - * @param functionArguments arguments for contract creation, pass empty Array if no - * arguments - * @param options transaction arguments, having at least .from and .gas - * @param handleTxResult callback(error, result) + * Create, sign and submit a contract transaction. * - * @return Promise, resolved when done or resolves to event result if event given + * @param {any} contract contract instance from api.eth.loadContract(...) + * @param {string} functionName function name + * @param {any[]} functionArguments arguments for contract creation, pass empty Array if + * no arguments + * @param {any} options transaction arguments, having at least .from and + * .gas + * @param {Function} handleTxResult callback(error, result) + * @return {Promise} resolved when done */ - signAndExecuteTransaction(contract, functionName, functionArguments, options, handleTxResult) { + public async signAndExecuteTransaction(contract: any, functionName: string, + functionArguments: any[], + options: any, handleTxResult: Function) { this.log(`will sign tx for function "${functionName}"`, 'debug'); Promise .all([ @@ -353,14 +360,16 @@ export class SignerInternal extends Logger implements SignerInterface { * creates a contract by contstructing creation transaction and signing it with private key of * options.from * - * @param contractName contract name - * @param functionArguments arguments for contract creation, pass empty Array if no - * arguments - * @param options transaction arguments, having at least .from and .gas + * @param {any} contractName contract name + * @param {any[]} functionArguments arguments for contract creation, pass empty + * Array if no arguments + * @param {any} options transaction arguments, having at + * least .from and .gas * - * @return Promise contract address + * @return {Promise} contract address */ - createContract(contractName: string, functionArguments: any[], options: any): Promise { + public async createContract(contractName: string, functionArguments: any[], options: any): + Promise { this.log('will sign tx for contract creation', 'debug'); const compiledContract = this.contractLoader.getCompiledContract(contractName); if (!compiledContract) { From 4238b7b1a8ef3e6ef9a8eedd27bc8f49bd95e8ac Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 21 Jan 2020 16:44:30 +0100 Subject: [PATCH 09/24] refactor code with public functions first and alphabetically sorted -[CORE-896] --- src/contracts/signer-internal.ts | 296 +++++++++++++++---------------- 1 file changed, 147 insertions(+), 149 deletions(-) diff --git a/src/contracts/signer-internal.ts b/src/contracts/signer-internal.ts index 5df116c..e00681a 100644 --- a/src/contracts/signer-internal.ts +++ b/src/contracts/signer-internal.ts @@ -95,26 +95,88 @@ export class SignerInternal extends Logger implements SignerInterface { } /** - * @brief retrieve private key for given account + * creates a contract by contstructing creation transaction and signing it with private key of + * options.from * - * @param {string} accountId eth account ID + * @param {any} contractName contract name + * @param {any[]} functionArguments arguments for contract creation, pass empty + * Array if no arguments + * @param {any} options transaction arguments, having at + * least .from and .gas * - * @return Promise that resolves to {string} private key of given account + * @return {Promise} contract address */ - public async getPrivateKey(accountId: string) { - return this.accountStore.getPrivateKey(accountId); + public async createContract(contractName: string, functionArguments: any[], options: any): + Promise { + this.log('will sign tx for contract creation', 'debug'); + const compiledContract = this.contractLoader.getCompiledContract(contractName); + if (!compiledContract) { + throw new Error(`cannot find contract description for contract "${contractName}"`); + } + if (!compiledContract.bytecode) { + throw new Error(`trying to create an instance of abstract contract "${contractName}"`); + } + + return Promise + .all([ + this.getPrivateKey(options.from), + typeof options.gasPrice !== 'undefined' ? options.gasPrice : this.getGasPrice(), + this.getNonce(options.from), + ]) + .then(async ([privateKey, gasPrice, nonce]: [string, number, number]) => { + const abi = JSON.parse(compiledContract.interface); + const txParams = { + nonce, + gasPrice, + gasLimit: this.ensureHashWithPrefix(options.gas), + value: options.value || 0, + data: this.ensureHashWithPrefix( + `${compiledContract.bytecode}` + + `${this.encodeConstructorParams(abi, functionArguments)}`, + ), + chainId: NaN, + }; + + const txObject = new Transaction(txParams); + const privateKeyBuffer = Buffer.from(privateKey, 'hex'); + txObject.sign(privateKeyBuffer); + const signedTx = this.ensureHashWithPrefix(txObject.serialize().toString('hex')); + + // submit via sendRawTransaction + const receipt = await this.sendSignedTransaction(signedTx); + + if (options.gas === receipt.gasUsed) { + throw new Error('all gas used up'); + } else { + this.log(`contract creation of "${contractName}" used ${receipt.gasUsed} gas`); + return new this.web3.eth.Contract(abi, receipt.contractAddress); + } + }) + .catch((ex) => { + const msg = `could not sign contract creation of "${contractName}"; "${(ex.message || ex)}"`; + this.log(msg, 'error'); + throw ex; + }); } /** - * get public key for given account + * Should be called to encode constructor params (taken from + * https://github.com/ethereum/web3.js/blob/develop/lib/web3/contract.js) * - * @param {string} accountId account to get public key for + * @param {any[]} abi The abi + * @param {any[]} params The parameters + * + * @return encoded params */ - public async getPublicKey(accountId: string): Promise { - const ecdh = crypto.createECDH('secp256k1'); - ecdh.setPrivateKey(await this.getPrivateKey(accountId), 'hex'); - - return ecdh.getPublicKey().toString('hex'); + public encodeConstructorParams(abi: any[], params: any[]) { + if (params.length) { + return abi + .filter((json) => json.type === 'constructor' && json.inputs.length === params.length) + .map((json) => json.inputs.map((input) => input.type)) + .map((types) => coder.encodeParameters(types, params)) + .map((encodedParams) => encodedParams.replace(/^0x/, ''))[0] || ''; + } + return ''; } /** @@ -167,7 +229,7 @@ export class SignerInternal extends Logger implements SignerInterface { * * @return {Promise} nonce of given user */ - public async getNonce(accountId: string) { + public async getNonce(accountId: string): Promise { return this.web3.eth .getTransactionCount(accountId) .then((count) => { @@ -179,90 +241,32 @@ export class SignerInternal extends Logger implements SignerInterface { } /** - * Should be called to encode constructor params (taken from - * https://github.com/ethereum/web3.js/blob/develop/lib/web3/contract.js) + * @brief retrieve private key for given account * - * @param {any[]} abi The abi - * @param {any[]} params The parameters + * @param {string} accountId eth account ID * - * @return encoded params + * @return Promise that resolves to {string} private key of given account */ - public encodeConstructorParams(abi: any[], params: any[]) { - if (params.length) { - return abi - .filter((json) => json.type === 'constructor' && json.inputs.length === params.length) - .map((json) => json.inputs.map((input) => input.type)) - .map((types) => coder.encodeParameters(types, params)) - .map((encodedParams) => encodedParams.replace(/^0x/, ''))[0] || ''; - } - return ''; + public async getPrivateKey(accountId: string) { + return this.accountStore.getPrivateKey(accountId); } - /** - * Wraps `web3.eth.sendSignedTransaction` function to handle missing events in chains. In this - * case, load receipt for received transactionHashes manually and wait until blockHash is set. + * get public key for given account * - * @param {Web3} web3 web3 instance - * @param {any} signedTx signed transaction object + * @param {string} accountId account to get public key for */ - private async sendSignedTransaction(signedTx: any): Promise { - let receipt: any; - let txHash: string; - let resolved = false; - - // send the signed transaction and try to recieve an receipt - await new Promise((resolve, reject) => { - // Load last transaction receipt for the current txHash, if no valid receipt could be loaded - // before, resolve the callback function, else the original receipt event was received before - // and we never should resolve the callback function. - const checkReceipt = async () => { - if (!receipt || !receipt.blockHash) { - // load the receipt for the transaction hash - const newReceipt = await this.web3.eth.getTransactionReceipt(txHash); - - // if no receipt event was fired before, use the newly loaded receipt - if (!receipt || !receipt.blockHash) { - receipt = newReceipt; - } - - // if it's still not a valid receipt, wait for block header - if (!receipt || !receipt.blockHash) { - // trigger the reload directly - this.pendingTransactions[txHash] = this.pendingTransactions[txHash] || []; - this.pendingTransactions[txHash].push(() => checkReceipt()); - } else if (!resolved) { - resolved = true; - resolve(); - } - } else if (!resolved) { - resolved = true; - resolve(); - } - }; - - this.web3.eth - .sendSignedTransaction(signedTx) - .on('transactionHash', (transactionHash) => { - txHash = transactionHash; - checkReceipt(); - }) - .on('receipt', (newReceipt) => { - if (!receipt || !receipt.blockHash) { - receipt = newReceipt; - checkReceipt(); - } - }) - .on('error', (error) => reject(error)); - }); + public async getPublicKey(accountId: string): Promise { + const ecdh = crypto.createECDH('secp256k1'); + ecdh.setPrivateKey(await this.getPrivateKey(accountId), 'hex'); - return receipt; + return ecdh.getPublicKey().toString('hex'); } /** * { Signs and send the signed transaction } * - * @param {any} options The options + * @param {any} options The options * @param {Function} handleTxResult The handle transmit result */ public async signAndExecuteSend(options: any, handleTxResult: Function) { @@ -356,72 +360,6 @@ export class SignerInternal extends Logger implements SignerInterface { }); } - /** - * creates a contract by contstructing creation transaction and signing it with private key of - * options.from - * - * @param {any} contractName contract name - * @param {any[]} functionArguments arguments for contract creation, pass empty - * Array if no arguments - * @param {any} options transaction arguments, having at - * least .from and .gas - * - * @return {Promise} contract address - */ - public async createContract(contractName: string, functionArguments: any[], options: any): - Promise { - this.log('will sign tx for contract creation', 'debug'); - const compiledContract = this.contractLoader.getCompiledContract(contractName); - if (!compiledContract) { - throw new Error(`cannot find contract description for contract "${contractName}"`); - } - if (!compiledContract.bytecode) { - throw new Error(`trying to create an instance of abstract contract "${contractName}"`); - } - - return Promise - .all([ - this.getPrivateKey(options.from), - typeof options.gasPrice !== 'undefined' ? options.gasPrice : this.getGasPrice(), - this.getNonce(options.from), - ]) - .then(async ([privateKey, gasPrice, nonce]: [string, number, number]) => { - const abi = JSON.parse(compiledContract.interface); - const txParams = { - nonce, - gasPrice, - gasLimit: this.ensureHashWithPrefix(options.gas), - value: options.value || 0, - data: this.ensureHashWithPrefix( - `${compiledContract.bytecode}` - + `${this.encodeConstructorParams(abi, functionArguments)}`, - ), - chainId: NaN, - }; - - const txObject = new Transaction(txParams); - const privateKeyBuffer = Buffer.from(privateKey, 'hex'); - txObject.sign(privateKeyBuffer); - const signedTx = this.ensureHashWithPrefix(txObject.serialize().toString('hex')); - - // submit via sendRawTransaction - const receipt = await this.sendSignedTransaction(signedTx); - - if (options.gas === receipt.gasUsed) { - throw new Error('all gas used up'); - } else { - this.log(`contract creation of "${contractName}" used ${receipt.gasUsed} gas`); - return new this.web3.eth.Contract(abi, receipt.contractAddress); - } - }) - .catch((ex) => { - const msg = `could not sign contract creation of "${contractName}"; "${(ex.message || ex)}"`; - this.log(msg, 'error'); - throw ex; - }); - } - - /** * sign given message with accounts private key * @@ -435,4 +373,64 @@ export class SignerInternal extends Logger implements SignerInterface { return signature; } + + /** + * Wraps `web3.eth.sendSignedTransaction` function to handle missing events in chains. In this + * case, load receipt for received transactionHashes manually and wait until blockHash is set. + * + * @param {Web3} web3 web3 instance + * @param {any} signedTx signed transaction object + */ + private async sendSignedTransaction(signedTx: any): Promise { + let receipt: any; + let txHash: string; + let resolved = false; + + // send the signed transaction and try to recieve an receipt + await new Promise((resolve, reject) => { + // Load last transaction receipt for the current txHash, if no valid receipt could be loaded + // before, resolve the callback function, else the original receipt event was received before + // and we never should resolve the callback function. + const checkReceipt = async () => { + if (!receipt || !receipt.blockHash) { + // load the receipt for the transaction hash + const newReceipt = await this.web3.eth.getTransactionReceipt(txHash); + + // if no receipt event was fired before, use the newly loaded receipt + if (!receipt || !receipt.blockHash) { + receipt = newReceipt; + } + + // if it's still not a valid receipt, wait for block header + if (!receipt || !receipt.blockHash) { + // trigger the reload directly + this.pendingTransactions[txHash] = this.pendingTransactions[txHash] || []; + this.pendingTransactions[txHash].push(() => checkReceipt()); + } else if (!resolved) { + resolved = true; + resolve(); + } + } else if (!resolved) { + resolved = true; + resolve(); + } + }; + + this.web3.eth + .sendSignedTransaction(signedTx) + .on('transactionHash', (transactionHash) => { + txHash = transactionHash; + checkReceipt(); + }) + .on('receipt', (newReceipt) => { + if (!receipt || !receipt.blockHash) { + receipt = newReceipt; + checkReceipt(); + } + }) + .on('error', (error) => reject(error)); + }); + + return receipt; + } } From 31121512874525995456377083caaef3350740c4 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 21 Jan 2020 16:47:08 +0100 Subject: [PATCH 10/24] refactor code -[CODE-896] --- src/contracts/signer-interface.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/contracts/signer-interface.ts b/src/contracts/signer-interface.ts index 8e35e67..a0f7ac6 100644 --- a/src/contracts/signer-interface.ts +++ b/src/contracts/signer-interface.ts @@ -26,6 +26,14 @@ export interface SignerInterface { */ createContract(contractName: string, functionArguments: any[], options: any): Promise; + /** + * get gas price (either from config or from api.eth.web3.eth.gasPrice (gas price median of last + * blocks) or api.config.eth.gasPrice; unset config value or set it to falsy for median gas price + * + * @return {Promise} hex string with gas price + */ + getGasPrice(): Promise; + /** * get public key for given account * @@ -69,12 +77,4 @@ export interface SignerInterface { * @return {Promise; - - /** - * get gas price (either from config or from api.eth.web3.eth.gasPrice (gas price median of last - * blocks) or api.config.eth.gasPrice; unset config value or set it to falsy for median gas price - * - * @return {Promise} hex string with gas price - */ - getGasPrice(): Promise; } From b5617d74fb2b031ad6053e0151fbaf0b2bf87b80 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 21 Jan 2020 16:53:30 +0100 Subject: [PATCH 11/24] edit visibility and refactor code -[CORE-896] --- src/contracts/signer-external.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/contracts/signer-external.ts b/src/contracts/signer-external.ts index cc5be13..089927e 100644 --- a/src/contracts/signer-external.ts +++ b/src/contracts/signer-external.ts @@ -22,6 +22,10 @@ export class SignerExternal implements SignerInterface { throw new Error('not implemented'); } + public async getGasPrice(): Promise { + throw new Error('not implemented'); + } + public async getPublicKey(): Promise { throw new Error('not implemented'); } @@ -30,16 +34,6 @@ export class SignerExternal implements SignerInterface { handleTxResult('not implemented'); } - /** - * get gas price (either from config or from api.eth.web3.eth.gasPrice (gas price median of last - * blocks) or api.config.eth.gasPrice; unset config value or set it to falsy for median gas price - * - * @return {Promise} hex string with gas price - */ - getGasPrice(): Promise { - throw new Error('not implemented'); - } - public signAndExecuteTransaction( contract, functionName, functionArguments, options, handleTxResult, ) { From 0618dc9b66aec9e5477271d741f7c9e6338c4cab Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Mon, 27 Jan 2020 09:22:04 +0100 Subject: [PATCH 12/24] update signer-internal docu -[CORE-896] --- docs/blockchain/signer-internal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/blockchain/signer-internal.rst b/docs/blockchain/signer-internal.rst index 76e8536..cce7a59 100644 --- a/docs/blockchain/signer-internal.rst +++ b/docs/blockchain/signer-internal.rst @@ -134,7 +134,7 @@ Example ------------------------------------------------------------------------------ -.. _signerInternal_getGasPricex: +.. _signerInternal_getGasPrice: getGasPrice =================== From 885d9ed8e31cfdc8d35a6e8f71efa032ac2d2958 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Mon, 27 Jan 2020 09:22:37 +0100 Subject: [PATCH 13/24] update Versions.md -[CORE-896] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index a46157a..aba572b 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -2,6 +2,7 @@ ## Next Version ### Features +- add `getGasPrice` to `SignerInterface` ### Fixes - use typescript version `3.7.4` From b07fb531ebb347af50a828cc21be6e967835daea Mon Sep 17 00:00:00 2001 From: wulfraem Date: Wed, 29 Jan 2020 13:57:19 +0100 Subject: [PATCH 14/24] update `eslint` related modules' versions - to align them with other projects - [CORE-901] --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9b1a0ee..75fc5c6 100644 --- a/package.json +++ b/package.json @@ -24,14 +24,14 @@ "@types/chai-spies": "^1.0.1", "@types/mocha": "^5.2.7", "@typescript-eslint/eslint-plugin": "^2.16.0", - "@typescript-eslint/parser": "^2.11.0", + "@typescript-eslint/parser": "^2.16.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "chai-spies": "^1.0.0", "common-shakeify": "^0.6.2", "dirty-chai": "^2.0.1", - "eslint": "^6.1.0", - "eslint-config-airbnb": "^18.0.1", + "eslint": "^6.8.0", + "eslint-config-airbnb-base": "^14.0.0", "eslint-config-airbnb-typescript": "^6.3.1", "eslint-plugin-chai-friendly": "^0.5.0", "eslint-plugin-import": "^2.19.1", From df3fb970512bfb1972284f883ed06558f92e6215 Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Tue, 4 Feb 2020 11:15:07 +0100 Subject: [PATCH 15/24] check `dataSchema` for incorrect ajv schema when setting description - [CORE-944] --- VERSIONS.md | 1 + docs/common/validator.rst | 38 ++++++++++++++++++++++++++++++++++---- src/description.spec.ts | 10 ++++++++++ src/description.ts | 18 +++++++++++++++++- src/validator.spec.ts | 11 +++++++++++ src/validator.ts | 15 +++++++++++++-- 6 files changed, 86 insertions(+), 7 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index 0e56a56..05c6e5a 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -7,6 +7,7 @@ ### Fixes - use typescript version `3.7.4` - update `AccountStore` to throw if private key is missing +- check `dataSchema` for incorrect ajv schema when setting description ### Deprecations diff --git a/docs/common/validator.rst b/docs/common/validator.rst index 479ce39..2912349 100644 --- a/docs/common/validator.rst +++ b/docs/common/validator.rst @@ -54,10 +54,37 @@ Example .. code-block:: typescript const nameResolver = new Validator({ - schema - }); + schema + }); +-------------------------------------------------------------------------------- + +.. _validator_isSchemaCorrect: + +isSchemaCorrect +=================== + +.. code-block:: javascript + + Validator.isSchemaCorrect(schema); + +Checks if the given ajv schema is correct and returns an array of ajv errors, when the schema is invalid. + + + +---------- +Parameters +---------- + +#. ``schema`` - ``any``: schema to be validated + +------- +Returns +------- + +``bool`` | |source ajvError|_: true if data is valid, array of object if validation is failed + -------------------------------------------------------------------------------- @@ -84,7 +111,7 @@ Parameters Returns ------- -``bool | strings[]``: true if data is valid, array of object if validation is failed +``bool`` | |source ajvError|_: true if data is valid, array of object if validation is failed ------------------------------------------------------------------------------ @@ -113,4 +140,7 @@ Returns .. _source logLevel: ../common/logger.html#loglevel .. |source logLogInterface| replace:: ``LogLogInterface`` -.. _source logLogInterface: ../common/logger.html#logloginterface \ No newline at end of file +.. _source logLogInterface: ../common/logger.html#logloginterface + +.. |source ajvError| replace:: ``AjvError`` +.. _source ajvError: https://github.com/epoberezkin/ajv/blob/master/lib/compile/error_classes.js \ No newline at end of file diff --git a/src/description.spec.ts b/src/description.spec.ts index 0ba0c57..c1b5306 100644 --- a/src/description.spec.ts +++ b/src/description.spec.ts @@ -129,6 +129,16 @@ describe('Description handler', function test() { accounts[0], ); await expect(promise).to.be.rejected; + + // invalid dataSchema + descriptionEnvelope = { public: { ...sampleDescription } }; + descriptionEnvelope.public.dataSchema = { type: 'text' }; + promise = description.setDescriptionToContract( + contract.options.address, + descriptionEnvelope, + accounts[0], + ); + await expect(promise).to.be.rejected; }); it('should be able to hold versions history', async () => { diff --git a/src/description.ts b/src/description.ts index c316c99..4e32215 100644 --- a/src/description.ts +++ b/src/description.ts @@ -353,6 +353,22 @@ export class Description extends Logger { schema: descriptionSchemas[combinedDescription.dbcpVersion], }); this.log(`validating DBCP definition with schema version ${combinedDescription.dbcpVersion}`, 'info'); - return validator.validate(combinedDescription); + // early return, when description it self is incorrect + const descValidation = validator.validate(combinedDescription); + if (descValidation !== true) { + return descValidation; + } + + // if a dataSchema was attached to the description, check it's integrity, so no wrong types or + // configuration values are passed. + if (combinedDescription.dataSchema) { + const schemaValidation = Validator.isSchemaCorrect(combinedDescription.dataSchema); + + if (schemaValidation !== true) { + return schemaValidation; + } + } + + return descValidation; } } diff --git a/src/validator.spec.ts b/src/validator.spec.ts index 77f9517..9a2de8b 100644 --- a/src/validator.spec.ts +++ b/src/validator.spec.ts @@ -78,4 +78,15 @@ describe('Validation helper', () => { expect(e).to.be.an('error'); } }); + + it('can get errors of a schema', () => { + const testSchema = { + $id: 'testSchema', + type: 'text', + }; + + const result = Validator.isSchemaCorrect(testSchema); + expect(Array.isArray(result)).to.be.true; + expect(result[0].message).to.be.eq('should be equal to one of the allowed values'); + }); }); diff --git a/src/validator.ts b/src/validator.ts index 3e0a1e8..ddd2755 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -36,6 +36,17 @@ export class Validator extends Logger { ajv: any; + /** + * Determines whether the specified schema is a correct DBCP schema. + * + * @param {any} schema DBCP schema definition + */ + static isSchemaCorrect(schema): boolean|Array { + const ajv = new Ajv({ allErrors: true }); + const isValid = ajv.validateSchema(schema); + return isValid ? true : ajv.errors; + } + constructor(options) { super(options); this.schema = options.schema; @@ -49,7 +60,7 @@ export class Validator extends Logger { * @param {any} data to be validated data * @returns {any} true if data is valid, array of object if validation is failed */ - validate(data: any) { + validate(data: any): boolean|Array { const validationResult = this.validator(data); if (!validationResult) { return this.validator.errors; @@ -62,7 +73,7 @@ export class Validator extends Logger { * * @returns {string} all previous validation errors concatenated as readable string */ - getErrorsAsText() { + getErrorsAsText(): string { return this.ajv.errorsText(this.validator.errors); } } From dc2e43e6c0ee9b267cfa9a8689294914355de7ad Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Wed, 5 Feb 2020 11:28:58 +0100 Subject: [PATCH 16/24] add description dataSchema shorthand ajv property support - [CORE-944] --- src/description.spec.ts | 27 ++++++++++++++++++++++++--- src/description.ts | 22 +++++++++++++++++++--- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/description.spec.ts b/src/description.spec.ts index c1b5306..0ad21e6 100644 --- a/src/description.spec.ts +++ b/src/description.spec.ts @@ -85,7 +85,7 @@ describe('Description handler', function test() { ); }); - it('should reject invalid description', async () => { + it.only('should reject invalid description', async () => { const contract = await executor.createContract('Described', [], { from: accounts[0], gas: 1000000 }); let descriptionEnvelope; let promise; @@ -130,9 +130,30 @@ describe('Description handler', function test() { ); await expect(promise).to.be.rejected; - // invalid dataSchema + // invalid ajv dataSchema descriptionEnvelope = { public: { ...sampleDescription } }; - descriptionEnvelope.public.dataSchema = { type: 'text' }; + descriptionEnvelope.public.dataSchema = { + properties: { + test: { + type: 'text', + }, + }, + type: 'object', + }; + promise = description.setDescriptionToContract( + contract.options.address, + descriptionEnvelope, + accounts[0], + ); + await expect(promise).to.be.rejected; + + // invalid shorthand ajv dataSchema for multiple ajv schemas + descriptionEnvelope = { public: { ...sampleDescription } }; + descriptionEnvelope.public.dataSchema = { + type: { + type: 'text', + }, + }; promise = description.setDescriptionToContract( contract.options.address, descriptionEnvelope, diff --git a/src/description.ts b/src/description.ts index 4e32215..401e57e 100644 --- a/src/description.ts +++ b/src/description.ts @@ -362,10 +362,26 @@ export class Description extends Logger { // if a dataSchema was attached to the description, check it's integrity, so no wrong types or // configuration values are passed. if (combinedDescription.dataSchema) { - const schemaValidation = Validator.isSchemaCorrect(combinedDescription.dataSchema); + const schema = combinedDescription.dataSchema; + + // if type is speecified as string on top level, check the full dataSchema (represents normal + // ajv definition) + if (typeof schema.type === 'string') { + const schemaValidation = Validator.isSchemaCorrect(schema); + if (schemaValidation !== true) { + return schemaValidation; + } + } - if (schemaValidation !== true) { - return schemaValidation; + // Else, shorthand dataschema properties are define, so do not check full dataSchema. Iterate + // through all sub properties to check for entry schema validity, so each entry can use any + // name without ajv reservd property restrictions. + const entries = Object.keys(schema); + for (let i = 0; i < entries.length; i += 1) { + const schemaValidation = Validator.isSchemaCorrect(schema[entries[i]]); + if (schemaValidation !== true) { + return schemaValidation; + } } } From ff32622541ac47de2a8001ff701f2d6d32a72565 Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Wed, 5 Feb 2020 11:38:59 +0100 Subject: [PATCH 17/24] remove it.only - [CORE-944] --- src/description.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/description.spec.ts b/src/description.spec.ts index 0ad21e6..c671cf8 100644 --- a/src/description.spec.ts +++ b/src/description.spec.ts @@ -85,7 +85,7 @@ describe('Description handler', function test() { ); }); - it.only('should reject invalid description', async () => { + it('should reject invalid description', async () => { const contract = await executor.createContract('Described', [], { from: accounts[0], gas: 1000000 }); let descriptionEnvelope; let promise; From 59604914d0ae7fba2f02fdcd1deac331178ed53c Mon Sep 17 00:00:00 2001 From: Tschuck Date: Wed, 5 Feb 2020 14:54:44 +0100 Subject: [PATCH 18/24] Update src/description.ts Co-Authored-By: Sebastian Dechant --- src/description.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/description.ts b/src/description.ts index 401e57e..c146e6b 100644 --- a/src/description.ts +++ b/src/description.ts @@ -364,7 +364,7 @@ export class Description extends Logger { if (combinedDescription.dataSchema) { const schema = combinedDescription.dataSchema; - // if type is speecified as string on top level, check the full dataSchema (represents normal + // if type is specified as string on top level, check the full dataSchema (represents normal // ajv definition) if (typeof schema.type === 'string') { const schemaValidation = Validator.isSchemaCorrect(schema); From a0bad144c6222154565312019794ea60410eb3a1 Mon Sep 17 00:00:00 2001 From: Tschuck Date: Wed, 5 Feb 2020 14:54:54 +0100 Subject: [PATCH 19/24] Update src/description.ts Co-Authored-By: Sebastian Dechant --- src/description.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/description.ts b/src/description.ts index c146e6b..e9b0694 100644 --- a/src/description.ts +++ b/src/description.ts @@ -373,7 +373,7 @@ export class Description extends Logger { } } - // Else, shorthand dataschema properties are define, so do not check full dataSchema. Iterate + // else, shorthand dataSchema properties are defined. So do not check full dataSchema, instead iterate // through all sub properties to check for entry schema validity, so each entry can use any // name without ajv reservd property restrictions. const entries = Object.keys(schema); From 0c0a90ab9864e92f978864840ae0f08e0438a699 Mon Sep 17 00:00:00 2001 From: Tschuck Date: Wed, 5 Feb 2020 14:55:02 +0100 Subject: [PATCH 20/24] Update src/description.ts Co-Authored-By: Sebastian Dechant --- src/description.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/description.ts b/src/description.ts index e9b0694..1c17235 100644 --- a/src/description.ts +++ b/src/description.ts @@ -375,7 +375,7 @@ export class Description extends Logger { // else, shorthand dataSchema properties are defined. So do not check full dataSchema, instead iterate // through all sub properties to check for entry schema validity, so each entry can use any - // name without ajv reservd property restrictions. + // name without ajv reserved property restrictions. const entries = Object.keys(schema); for (let i = 0; i < entries.length; i += 1) { const schemaValidation = Validator.isSchemaCorrect(schema[entries[i]]); From 4d4266acac8483f18cd01f2930d22784a2f6bc6b Mon Sep 17 00:00:00 2001 From: Tschuck Date: Wed, 5 Feb 2020 14:55:12 +0100 Subject: [PATCH 21/24] Update src/description.ts Co-Authored-By: Sebastian Dechant --- src/description.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/description.ts b/src/description.ts index 1c17235..365a2c4 100644 --- a/src/description.ts +++ b/src/description.ts @@ -374,7 +374,7 @@ export class Description extends Logger { } // else, shorthand dataSchema properties are defined. So do not check full dataSchema, instead iterate - // through all sub properties to check for entry schema validity, so each entry can use any + // through all sub properties to check for entry schema validity. So that each entry can use any // name without ajv reserved property restrictions. const entries = Object.keys(schema); for (let i = 0; i < entries.length; i += 1) { From be7490f32319ae5987a3db759cbd7581460bf688 Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Wed, 5 Feb 2020 14:58:44 +0100 Subject: [PATCH 22/24] fix max length exceeding comment - [CORE-944] --- src/description.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/description.ts b/src/description.ts index 365a2c4..812a4d2 100644 --- a/src/description.ts +++ b/src/description.ts @@ -373,9 +373,9 @@ export class Description extends Logger { } } - // else, shorthand dataSchema properties are defined. So do not check full dataSchema, instead iterate - // through all sub properties to check for entry schema validity. So that each entry can use any - // name without ajv reserved property restrictions. + // else, shorthand dataSchema properties are defined. So do not check full dataSchema, instead + // iterate through all sub properties to check for entry schema validity. So that each entry + // can use any name without ajv reserved property restrictions. const entries = Object.keys(schema); for (let i = 0; i < entries.length; i += 1) { const schemaValidation = Validator.isSchemaCorrect(schema[entries[i]]); From 5c5672f6e3a4d561549129a94e6ac05dd0102554 Mon Sep 17 00:00:00 2001 From: Tschuck Date: Wed, 5 Feb 2020 14:59:14 +0100 Subject: [PATCH 23/24] Update src/description.ts Co-Authored-By: wulfraem --- src/description.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/description.ts b/src/description.ts index 812a4d2..592c52d 100644 --- a/src/description.ts +++ b/src/description.ts @@ -359,7 +359,7 @@ export class Description extends Logger { return descValidation; } - // if a dataSchema was attached to the description, check it's integrity, so no wrong types or + // if a dataSchema was attached to the description, check its integrity, so no wrong types or // configuration values are passed. if (combinedDescription.dataSchema) { const schema = combinedDescription.dataSchema; From 96936bef9fdeae41827dad43a880c19ee53eddbe Mon Sep 17 00:00:00 2001 From: wulfraem Date: Fri, 7 Feb 2020 11:24:21 +0100 Subject: [PATCH 24/24] release v1.11.0 - [CORE-951] --- VERSIONS.md | 10 ++++++++-- package.json | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index 05c6e5a..1c49475 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -2,6 +2,14 @@ ## Next Version ### Features + +### Fixes + +### Deprecations + + +## Version 1.11.0 +### Features - add `getGasPrice` to `SignerInterface` ### Fixes @@ -9,8 +17,6 @@ - update `AccountStore` to throw if private key is missing - check `dataSchema` for incorrect ajv schema when setting description -### Deprecations - ## Version 1.10.0 ### Features diff --git a/package.json b/package.json index aea9606..b1439c8 100644 --- a/package.json +++ b/package.json @@ -75,5 +75,5 @@ "testunitcoverage": "npm run build && nyc -r lcov -e .ts -x \"**/*.spec.ts\" -x \"lib\" mocha --exit -r ts-node/register $TESTSPECS && nyc report --reporter=json > coverage/coverage.json" }, "types": "./dist/index.d.ts", - "version": "1.10.0" + "version": "1.11.0" } \ No newline at end of file