From 8e6f2383da10ce7d014476de93c70406ae5901b3 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Thu, 27 Sep 2018 19:01:20 +0200 Subject: [PATCH] Sparrow dom/tranaction hash (#546) * add transaction hash event to contract call * simpify api * add transactionHash and confirmationCallback to deploy function as well * handle null calllbacks * add possible probleme explanation * remove unneeded options * adding comments and abstracting constant * add test for transactionHash and onComplete callbacks * handle possible race condition in test --- src/contractInterface/users/resolver.js | 4 +- src/contractInterface/users/v00_adapter.js | 11 +++--- src/resources/users.js | 8 +++- src/services/contract-service.js | 43 +++++++++++++++++++++- test/resource_users.test.js | 31 ++++++++++++++++ 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/contractInterface/users/resolver.js b/src/contractInterface/users/resolver.js index 0a5058e6..f8bc4d28 100644 --- a/src/contractInterface/users/resolver.js +++ b/src/contractInterface/users/resolver.js @@ -11,8 +11,8 @@ class UsersResolver { this.currentAdapter = this.adapters[this.currentVersion] } - async set({ profile, attestations = [] }) { - return this.currentAdapter.set({ profile, attestations }) + async set({ profile, attestations = [], options = {} }) { + return this.currentAdapter.set({ profile, attestations, options }) } async get(address) { diff --git a/src/contractInterface/users/v00_adapter.js b/src/contractInterface/users/v00_adapter.js index 6238947e..a7425ae8 100644 --- a/src/contractInterface/users/v00_adapter.js +++ b/src/contractInterface/users/v00_adapter.js @@ -20,13 +20,13 @@ class V00_UsersAdapter { this.contractName = 'V00_UserRegistry' } - async set({ profile, attestations = [] }) { + async set({ profile, attestations = [], options = {}}) { if (profile) { const selfAttestation = await this.profileAttestation(profile) attestations.push(selfAttestation) } const newAttestations = await this.newAttestations(attestations) - return await this.addAttestations(newAttestations) + return await this.addAttestations(newAttestations, options) } async get(address) { @@ -91,7 +91,7 @@ class V00_UsersAdapter { }) } - async addAttestations(attestations) { + async addAttestations(attestations, options) { const account = await this.contractService.currentAccount() const userRegistry = await this.contractService.deployed( this.contractService.contracts[this.contractName] @@ -123,7 +123,7 @@ class V00_UsersAdapter { 'ClaimHolderRegistered', 'addClaims', [topics, issuers, sigs, data, dataOffsets], - { from: account, gas: 400000, contractAddress: identityAddress } + { ...options, from: account, gas: 400000, contractAddress: identityAddress } ) } else { // create identity with presigned claims @@ -138,7 +138,8 @@ class V00_UsersAdapter { data, dataOffsets ], - { from: account, gas } + { from: account, gas }, + options ) } } else if (!identityAddress) { diff --git a/src/resources/users.js b/src/resources/users.js index cae8ac4b..694e3fa8 100644 --- a/src/resources/users.js +++ b/src/resources/users.js @@ -5,8 +5,12 @@ class Users { this.resolver = new UsersResolver({ contractService, ipfsService }) } - async set({ profile, attestations = [] }) { - return this.resolver.set({ profile, attestations }) + /* possible options values: + * - confirmationCallback(confirmationCount, transactionReceipt) -> called repeatedly after a transaction is mined + * - transactionHashCallback(hash) -> called immediately when the transaction hash is received + */ + async set({ profile, attestations = [], options = {}}) { + return this.resolver.set({ profile, attestations, options }) } async get(address) { diff --git a/src/services/contract-service.js b/src/services/contract-service.js index f2ff3524..3d30daca 100644 --- a/src/services/contract-service.js +++ b/src/services/contract-service.js @@ -13,6 +13,7 @@ import bs58 from 'bs58' import Web3 from 'web3' const emptyAddress = '0x0000000000000000000000000000000000000000' +const NUMBER_CONFIRMATIONS_TO_REPORT = 20 const SUPPORTED_ERC20 = [ { symbol: 'OGN', decimals: 18, contractName: 'OriginToken' } ] @@ -192,7 +193,7 @@ class ContractService { return withLibraryAddresses } - async deploy(contract, args, options) { + async deploy(contract, args, options, { confirmationCallback, transactionHashCallback } = {} ) { const bytecode = await this.getBytecode(contract) const deployed = await this.deployed(contract) const txReceipt = await new Promise((resolve, reject) => { @@ -205,16 +206,53 @@ class ContractService { .on('receipt', receipt => { resolve(receipt) }) + //.on('confirmation', confirmationCallback) + //.on('transactionHash', transactionHashCallback) + // Workaround for "confirmationCallback" not being triggered with web3 version:1.0.0-beta.34 + .on('transactionHash', (hash) => { + if (transactionHashCallback) + transactionHashCallback(hash) + if (confirmationCallback) + this.checkForDeploymentCompletion(hash, confirmationCallback) + }) .on('error', err => reject(err)) }) return txReceipt } + /* confirmation callback does not get triggered in current version of web3 version:1.0.0-beta.34 + * so this function perpetually (until 20 confirmations) checks for presence of deployed contract. + * + * This could also be a problem in Ethereum node: https://github.com/ethereum/web3.js/issues/1255 + */ + async checkForDeploymentCompletion(hash, confirmationCallback) { + const transactionInfo = await this.web3.eth.getTransaction(hash) + + // transaction not mined + if (transactionInfo.blockNumber === null){ + setTimeout(() => { + this.checkForDeploymentCompletion(hash, confirmationCallback) + }, 1500) + } else { + const currentBlockNumber = await this.web3.eth.getBlockNumber() + const confirmations = currentBlockNumber - transactionInfo.blockNumber + confirmationCallback(confirmations, { + transactionHash: transactionInfo.hash + }) + // do checks until NUMBER_CONFIRMATIONS_TO_REPORT block confirmations + if (confirmations < NUMBER_CONFIRMATIONS_TO_REPORT) { + setTimeout(() => { + this.checkForDeploymentCompletion(hash, confirmationCallback) + }, 1500) + } + } + } + async call( contractName, functionName, args = [], - { contractAddress, from, gas, value, confirmationCallback, additionalGas = 0 } = {} + { contractAddress, from, gas, value, confirmationCallback, transactionHashCallback, additionalGas = 0 } = {} ) { const contractDefinition = this.contracts[contractName] if (typeof contractDefinition === 'undefined') { @@ -239,6 +277,7 @@ class ContractService { .send(opts) .on('receipt', resolve) .on('confirmation', confirmationCallback) + .on('transactionHash', transactionHashCallback) .on('error', reject) }) const block = await this.web3.eth.getBlock(transactionReceipt.blockNumber) diff --git a/test/resource_users.test.js b/test/resource_users.test.js index 08210c9e..9de7cf80 100644 --- a/test/resource_users.test.js +++ b/test/resource_users.test.js @@ -1,4 +1,5 @@ import chai from 'chai' +import chaiString from 'chai-string' import chaiAsPromised from 'chai-as-promised' import Web3 from 'web3' @@ -9,6 +10,7 @@ import ContractService from '../src/services/contract-service' import IpfsService from '../src/services/ipfs-service' chai.use(chaiAsPromised) +chai.use(chaiString) const expect = chai.expect const issuerPrivatekey = @@ -220,6 +222,35 @@ describe('User Resource', function() { const badProfile = { profile: { bad: 'profile' } } return expect(users.set(badProfile)).to.eventually.be.rejectedWith(Error) }) + + it('should be able to receive transactionHash and onComplete callbacks', (done) => { + let transactionHash + let doneCalled = false + + users.set({ + profile: { + firstName: 'Wonder', + lastName: 'Woman', + }, + options: { + transactionHashCallback: (hash) => { + transactionHash = hash + }, + confirmationCallback: (confirmationCount, transactionReceipt) => { + expect(confirmationCount).is.a('number') + expect(transactionReceipt).is.a('object') + // transactionHashCallback should always execute before confirmationCallback + expect(transactionHash).to.startsWith('0x') + + // prevent done being called multiple times + if (!doneCalled){ + doneCalled = true + done() + } + } + } + }) + }) }) describe('get', () => {