From c800df8d6dd781d78464f588a212ad7ef62ca0da Mon Sep 17 00:00:00 2001 From: Fabian Vogelsteller Date: Tue, 28 Feb 2017 20:11:58 +0100 Subject: [PATCH] decode receipts logs on contract events --- docs/web3-eth-contract.rst | 2 +- gulpfile.js | 43 +++--- packages/web3-core-helpers/src/formatters.js | 2 +- packages/web3-core-method/src/index.js | 25 +++- packages/web3-eth-contract/src/index.js | 23 ++- packages/web3-providers-http/src/index.js | 9 -- packages/web3-providers-ipc/src/index.js | 19 +-- packages/web3-providers-ws/src/index.js | 33 +++-- packages/web3-utils/src/index.js | 8 +- test/contract.js | 142 +++++++++++++++++++ test/httpprovider.js | 8 -- test/ipcprovider.js | 46 +++--- 12 files changed, 248 insertions(+), 112 deletions(-) diff --git a/docs/web3-eth-contract.rst b/docs/web3-eth-contract.rst index 58b9a4ec4be..2aa8d538dcf 100644 --- a/docs/web3-eth-contract.rst +++ b/docs/web3-eth-contract.rst @@ -488,7 +488,7 @@ The **callback** will return the 32 bytes transaction hash. - ``"transactionHash"`` returns ``String``: is fired right after the transaction is send and a transaction hash is available. - ``"receipt"`` returns ``Object``: is fired when the transaction receipt is available. -- ``"confirmation"`` returns ``Number``, ``Object``: is fired for every confirmation up to the 12th confirmation. Receives the confirmation number as the first and the receipt as the second argument. Fired from confirmation 0 on, which is the block where its minded. +- ``"confirmation"`` returns ``Number``, ``Object``: is fired for every confirmation up to the 24th confirmation. Receives the confirmation number as the first and the receipt as the second argument. Fired from confirmation 0 on, which is the block where its minded. - ``"error"`` returns ``Error``: is fired if an error occurs during sending. If a out of gas error, the second parameter is the receipt. diff --git a/gulpfile.js b/gulpfile.js index b6c410e5b34..d8a03dcccc0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -33,43 +33,38 @@ var packages = [{ src: './packages/web3-core-requestManager/src/index.js' },{ fileName: 'web3-providers-ipc', - expose: 'IpcProvider', + expose: 'Web3IpcProvider', src: './packages/web3-providers-ipc/src/index.js' },{ fileName: 'web3-providers-http', - expose: 'HttpProvider', + expose: 'Web3HttpProvider', src: './packages/web3-providers-http/src/index.js', ignore: ['xmlhttprequest'] },{ fileName: 'web3-providers-ws', - expose: 'WsProvider', + expose: 'Web3WsProvider', src: './packages/web3-providers-ws/src/index.js' },{ fileName: 'web3-eth', expose: 'Eth', src: './packages/web3-eth/src/index.js' +},{ + fileName: 'web3-personal', + expose: 'Personal', + src: './packages/web3-personal/src/index.js' +},{ + fileName: 'web3-shh', + expose: 'Shh', + src: './packages/web3-shh/src/index.js' +},{ + fileName: 'web3-bzz', + expose: 'Bzz', + src: './packages/web3-bzz/src/index.js' +},{ + fileName: 'web3-eth-iban', + expose: 'Iban', + src: './packages/web3-eth-iban/src/index.js' }]; -// ,{ -// fileName: 'web3-eth', -// expose: 'Eth', -// src: './packages/web3-eth/src/index.js' -// },{ -// fileName: 'web3-personal', -// expose: 'Personal', -// src: './packages/web3-personal/src/index.js' -// },{ -// fileName: 'web3-shh', -// expose: 'Shh', -// src: './packages/web3-shh/src/index.js' -// },{ -// fileName: 'web3-bzz', -// expose: 'Bzz', -// src: './packages/web3-bzz/src/index.js' -// },{ -// fileName: 'web3-eth-iban', -// expose: 'Iban', -// src: './packages/web3-eth-iban/src/index.js' -// }]; var browserifyOptions = { debug: true, diff --git a/packages/web3-core-helpers/src/formatters.js b/packages/web3-core-helpers/src/formatters.js index 8ce52dc59f3..e971f989a04 100644 --- a/packages/web3-core-helpers/src/formatters.js +++ b/packages/web3-core-helpers/src/formatters.js @@ -262,7 +262,7 @@ var outputLogFormatter = function(log) { typeof log.logIndex === 'string') { var shaId = utils.sha3(log.blockHash.replace('0x','') + log.transactionHash.replace('0x','') + log.logIndex.replace('0x','')); log.id = 'log_'+ shaId.replace('0x','').substr(0,8); - } else { + } else if(!log.id) { log.id = null; } diff --git a/packages/web3-core-method/src/index.js b/packages/web3-core-method/src/index.js index 86f73636633..e152b84ac75 100644 --- a/packages/web3-core-method/src/index.js +++ b/packages/web3-core-method/src/index.js @@ -29,7 +29,7 @@ var utils = require('web3-utils'); var promiEvent = require('web3-core-promiEvent'); var TIMEOUTBLOCK = 50; -var CONFIRMATIONBLOCKS = 12; +var CONFIRMATIONBLOCKS = 24; var Method = function Method(options) { @@ -158,7 +158,7 @@ Method.prototype.attachToObject = function (obj) { } }; -Method.prototype._confirmTransaction = function (defer, result, payload) { +Method.prototype._confirmTransaction = function (defer, result, payload, extraFormatters) { var method = this, promiseResolved = false, canUnsubscribe = true, @@ -183,13 +183,19 @@ Method.prototype._confirmTransaction = function (defer, result, payload) { promiseResolved = true; utils._fireError(receiptError, defer.eventEmitter, defer.reject); }) - // if CONFIRMATION listener exists check for confirmations + // if CONFIRMATION listener exists check for confirmations, by setting canUnsubscribe = false .then(function(receipt) { if (!receipt) { - throw new Error('Receipt is null'); + throw new Error('Receipt is "null"'); } + // apply extra formatters + if (extraFormatters && extraFormatters.receiptFormatter) { + receipt = extraFormatters.receiptFormatter(receipt); + } + + // check if confirmation listener exists if (defer.eventEmitter.listeners('confirmation').length > 0) { defer.eventEmitter.emit('confirmation', confirmationCount, receipt); @@ -252,7 +258,10 @@ Method.prototype._confirmTransaction = function (defer, result, payload) { defer.resolve(receipt); } else { - utils._fireError([new Error('Transaction ran out of gas. Please provide more gas.'), receipt], defer.eventEmitter, defer.reject); + if(receipt) { + receipt = JSON.stringify(receipt, null, 2); + } + utils._fireError(new Error("Transaction ran out of gas. Please provide more gas:\n"+ receipt), defer.eventEmitter, defer.reject); } if (canUnsubscribe) { @@ -285,12 +294,14 @@ Method.prototype._confirmTransaction = function (defer, result, payload) { Method.prototype.buildCall = function() { var method = this, - isSendTx = (method.call === 'eth_sendTransaction' || method.call === 'eth_sendRawTransaction'); + call = (_.isString(method.call)) ? method.call.toLowerCase() : Method.call, + isSendTx = (call === 'eth_sendtransaction' || call === 'eth_sendrawtransaction'); // actual send function var send = function () { + var extraFromatters = this; var defer = promiEvent(!isSendTx), payload = method.toPayload(Array.prototype.slice.call(arguments)); @@ -326,7 +337,7 @@ Method.prototype.buildCall = function() { defer.eventEmitter.emit('transactionHash', result); - method._confirmTransaction(defer, result, payload); + method._confirmTransaction(defer, result, payload, extraFromatters); } diff --git a/packages/web3-eth-contract/src/index.js b/packages/web3-eth-contract/src/index.js index daac539f11e..62942cd0573 100644 --- a/packages/web3-eth-contract/src/index.js +++ b/packages/web3-eth-contract/src/index.js @@ -466,7 +466,6 @@ Contract.prototype.deploy = function(options, callback){ }; - /** * Gets the event signature and outputformatters * @@ -771,7 +770,27 @@ Contract.prototype._executeMethod = function _executeMethod(){ return utils._fireError(new Error('Can not send value to non-payable contract method or constructor'), defer.eventEmitter, defer.reject, args.callback); } - return this._parent._eth.sendTransaction(args.options, args.callback); + + // make sure receipt logs are decoded + var extraFormatters = { + receiptFormatter: function (receipt) { + if (_.isArray(receipt.logs)) { + + // decode logs + receipt.events = _.map(receipt.logs, function(log) { + return _this._parent._decodeEventABI.call({ + name: 'ALLEVENTS', + jsonInterface: _this._parent.options.jsonInterface + }, log); + }); + + delete receipt.logs; + } + return receipt; + } + }; + + return this._parent._eth.sendTransaction.apply(extraFormatters, [args.options, args.callback]); } diff --git a/packages/web3-providers-http/src/index.js b/packages/web3-providers-http/src/index.js index 7c701d9e0cd..2a047969250 100644 --- a/packages/web3-providers-http/src/index.js +++ b/packages/web3-providers-http/src/index.js @@ -78,14 +78,5 @@ HttpProvider.prototype.send = function (payload, callback) { } }; -/** - * Synchronously tries to make Http request - * - * @method isConnected - * @return {Boolean} returns true if request haven't failed. Otherwise false - */ -HttpProvider.prototype.isConnected = function() { - return this.connected; -}; module.exports = HttpProvider; diff --git a/packages/web3-providers-ipc/src/index.js b/packages/web3-providers-ipc/src/index.js index 6cc9ce09775..da99af8cd69 100644 --- a/packages/web3-providers-ipc/src/index.js +++ b/packages/web3-providers-ipc/src/index.js @@ -138,21 +138,14 @@ IpcProvider.prototype.reconnect = function() { this.connection.connect({path: this.path}); }; -/** -Check if the current connection is still valid. - -@method isConnected -*/ -IpcProvider.prototype.isConnected = function() { - var _this = this; - - // try reconnect, when connection is gone - if(!_this.connection.writable) - _this.connection.connect({path: _this.path}); - return !!this.connection.writable; -}; +/** + Sends the request + @method send + @param {Object} payload example: {id: 1, jsonrpc: '2.0', 'method': 'eth_someMethod', params: []} + @param {Function} callback the callback to call + */ IpcProvider.prototype.send = function (payload, callback) { // try reconnect, when connection is gone if(!this.connection.writable) diff --git a/packages/web3-providers-ws/src/index.js b/packages/web3-providers-ws/src/index.js index 8c9035be687..70b4419821c 100644 --- a/packages/web3-providers-ws/src/index.js +++ b/packages/web3-providers-ws/src/index.js @@ -190,23 +190,6 @@ WebsocketProvider.prototype._timeout = function() { } }; -// TODO add reconnect - - -/** - Check if the current connection is still valid. - - @method isConnected - */ -WebsocketProvider.prototype.isConnected = function() { - // try reconnect, when connection is gone - // if(!_this.connection.writable) - // _this.connection.connect({path: _this.path}); - - console.log(this.connection); - - // return !!this.connection.writable; -}; WebsocketProvider.prototype.send = function (payload, callback) { // try reconnect, when connection is gone @@ -239,6 +222,14 @@ WebsocketProvider.prototype.on = function (type, callback) { this.connection.onopen = callback; break; + case 'end': + this.connection.onclose = callback; + break; + + case 'error': + this.connection.onerror = callback; + break; + // default: // this.connection.on(type, callback); // break; @@ -291,6 +282,14 @@ WebsocketProvider.prototype.removeAllListeners = function (type) { this.connection.onopen = null; break; + case 'end': + this.connection.onclose = null; + break; + + case 'error': + this.connection.onerror = null; + break; + default: // this.connection.removeAllListeners(type); break; diff --git a/packages/web3-utils/src/index.js b/packages/web3-utils/src/index.js index 0a3548ed813..59cb10b521f 100644 --- a/packages/web3-utils/src/index.js +++ b/packages/web3-utils/src/index.js @@ -51,12 +51,6 @@ var sha3 = function (value) { * @return {Object} the emitter */ var _fireError = function (error, emitter, reject, callback) { - var additionalData; - - if (isArray(error)) { - error = error[0]; - additionalData = error[1]; - } if (isFunction(callback)) { callback(error); @@ -73,7 +67,7 @@ var _fireError = function (error, emitter, reject, callback) { } if(emitter && isFunction(emitter.emit)) { - emitter.emit('error', error, additionalData); + emitter.emit('error', error); emitter.removeAllListeners(); } diff --git a/test/contract.js b/test/contract.js index a7482f539f6..6c86b06e4e7 100644 --- a/test/contract.js +++ b/test/contract.js @@ -1296,6 +1296,148 @@ describe('contract', function () { }); }); + it('should sendTransaction and check for receipts with formatted logs', function (done) { + var provider = new FakeHttpProvider(); + var eth = new Eth(provider); + var signature = sha3('mySend(address,uint256)').slice(0, 10); + + provider.injectValidation(function (payload) { + assert.equal(payload.method, 'eth_sendTransaction'); + assert.deepEqual(payload.params, [{ + data: signature +'000000000000000000000000'+ addressLowercase.replace('0x','') +'000000000000000000000000000000000000000000000000000000000000000a', + from: address2, + to: addressLowercase + }]); + }); + provider.injectResult('0x1234000000000000000000000000000000000000000000000000000000056789'); + + provider.injectValidation(function (payload) { + assert.equal(payload.method, 'eth_subscribe'); + assert.deepEqual(payload.params, ['newHeads']); + }); + provider.injectResult('0x1234567'); + + // fake newBlock + provider.injectNotification({ + method: 'eth_subscription', + params: { + subscription: '0x1234567', + result: { + blockNumber: '0x10' + } + } + }); + + provider.injectValidation(function (payload) { + assert.equal(payload.method, 'eth_getTransactionReceipt'); + assert.deepEqual(payload.params, ['0x1234000000000000000000000000000000000000000000000000000000056789']); + }); + provider.injectResult({ + contractAddress: null, + cumulativeGasUsed: '0xa', + transactionIndex: '0x3', + transactionHash: '0x1234', + blockNumber: '0xa', + logs: [{ + address: address, + topics: [ + sha3('Unchanged(uint256,address,uint256)'), + '0x0000000000000000000000000000000000000000000000000000000000000002', + '0x000000000000000000000000'+ addressLowercase.replace('0x','') + ], + blockNumber: '0xa', + transactionHash: '0x1234', + blockHash: '0x1345', + logIndex: '0x4', + data: '0x0000000000000000000000000000000000000000000000000000000000000005' + },{ + address: address, + topics: [ + sha3('Changed(address,uint256,uint256,uint256)'), + '0x000000000000000000000000'+ addressLowercase.replace('0x',''), + '0x0000000000000000000000000000000000000000000000000000000000000001' + ], + blockNumber: '0xa', + transactionHash: '0x1234', + blockHash: '0x1345', + logIndex: '0x4', + data: '0x0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000008' + }] + }); + provider.injectValidation(function (payload) { + assert.equal(payload.method, 'eth_unsubscribe'); + assert.deepEqual(payload.params, ['0x1234567']); + }); + provider.injectResult('0x321'); + + + var contract = new eth.Contract(abi, address); + + contract.methods.mySend(address, 10).send({from: address2}) + .on('receipt', function (receipt) { + // console.log(receipt); + // console.log(receipt.events[0].raw); + // console.log(receipt.events[1].raw); + + // wont throw if it errors ?! + assert.deepEqual(receipt, { + contractAddress: null, + cumulativeGasUsed: 10, + transactionIndex: 3, + transactionHash: '0x1234', + blockNumber: 10, + gasUsed: 0, + events: + [ { address: address, + blockNumber: 10, + transactionHash: '0x1234', + blockHash: '0x1345', + logIndex: 4, + id: 'log_9ff24cb4', + transactionIndex: 0, + returnValues: { + value: '2', + addressFrom: address, + t1: '5' + }, + event: 'Unchanged', + raw: { + topics: [ '0xf359668f205d0b5cfdc20d11353e05f633f83322e96f15486cbb007d210d66e5', + '0x0000000000000000000000000000000000000000000000000000000000000002', + '0x000000000000000000000000'+ addressLowercase.replace('0x','') ], + data: '0x0000000000000000000000000000000000000000000000000000000000000005', + } + }, { + address: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe', + blockNumber: 10, + transactionHash: '0x1234', + blockHash: '0x1345', + logIndex: 4, + id: 'log_9ff24cb4', + transactionIndex: 0, + returnValues: { + from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe', + amount: '1', + t1: '1', + t2: '8' + }, + event: 'Changed', + raw: { + topics: [ '0x792991ed5ba9322deaef76cff5051ce4bedaaa4d097585970f9ad8f09f54e651', + '0x000000000000000000000000'+ addressLowercase.replace('0x',''), + '0x0000000000000000000000000000000000000000000000000000000000000001' ], + data: '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008', + } + }] + }); + + done(); + }); + + + }); + it('should sendTransaction to contract function', function () { var provider = new FakeHttpProvider(); var eth = new Eth(provider); diff --git a/test/httpprovider.js b/test/httpprovider.js index adcca2d9368..96c4496387c 100644 --- a/test/httpprovider.js +++ b/test/httpprovider.js @@ -22,12 +22,4 @@ describe('web3-providers-http', function () { }); }); }); - - describe('isConnected', function () { - it('should return a boolean', function () { - var provider = new HttpProvider(); - - assert.isBoolean(provider.isConnected()); - }); - }); }); diff --git a/test/ipcprovider.js b/test/ipcprovider.js index 1d9e21b4dc8..93551b6f75c 100644 --- a/test/ipcprovider.js +++ b/test/ipcprovider.js @@ -26,28 +26,28 @@ describe('web3-providers-ipc', function () { // }); // }); - describe('isConnected', function () { - it('should return a boolean', function () { - var provider = new IpcProvider('', net); - - assert.isBoolean(provider.isConnected()); - }); - - it('should return false', function () { - var provider = new IpcProvider('', net); - - provider.connection.writable = false; - - assert.isFalse(provider.isConnected()); - }); - - it('should return true, when a net handle is set', function () { - var provider = new IpcProvider('', net); - - provider.connection.writable = true; - - assert.isTrue(provider.isConnected()); - }); - }); + // describe('isConnected', function () { + // it('should return a boolean', function () { + // var provider = new IpcProvider('', net); + // + // assert.isBoolean(provider.isConnected()); + // }); + // + // it('should return false', function () { + // var provider = new IpcProvider('', net); + // + // provider.connection.writable = false; + // + // assert.isFalse(provider.isConnected()); + // }); + // + // it('should return true, when a net handle is set', function () { + // var provider = new IpcProvider('', net); + // + // provider.connection.writable = true; + // + // assert.isTrue(provider.isConnected()); + // }); + // }); });