Skip to content

Commit

Permalink
added request function for batch processing to contract methods
Browse files Browse the repository at this point in the history
  • Loading branch information
frozeman committed Sep 13, 2016
1 parent 446c03d commit 5962333
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 75 deletions.
159 changes: 102 additions & 57 deletions lib/web3/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var Contract = function(jsonInterface, address, options) {


// set address
// TODO should be able to set no address at start??
Object.defineProperty(this, 'address', {
set: function(value){
if(utils.isAddress(value))
Expand Down Expand Up @@ -674,39 +675,58 @@ Contract.prototype._createTxObject = function _createTxObject(){
txObject = {};

txObject.call = this.parent._executeMethod.bind(txObject, 'call');
txObject.call.request = this.parent._executeMethod.bind(txObject, 'call', true); // to make batch requests

txObject.send = this.parent._executeMethod.bind(txObject, 'send');
txObject.send.request = this.parent._executeMethod.bind(txObject, 'send', true); // to make batch requests

txObject.estimate = this.parent._executeMethod.bind(txObject, 'estimate');
txObject.encodeABI = this.parent._encodeMethodABI.bind(txObject);
txObject.arguments = arguments;

txObject._method = this.method;
txObject._parent = this.parent;


return txObject;
};

/**
* Executes a call, transact or estimateGas on a contract function
*
* @method _executeMethod
* @param {Object} options
* @param {String} type the type this execute function should execute
* @param {Boolean} makeRequest if true, it simply returns the request parameters, rather than executing it
*/
Contract.prototype._executeMethod = function _executeMethod(type){
var _this = this,
args = Array.prototype.slice.call(arguments),
defer = eventifiedPromise(),
options = options || {},
defaultBlock = null,
callback = null;

type = args.shift();

// get the callback
if(utils.isFunction(args[args.length - 1])) {
callback = args.pop();
}

// get block number to use for call
if(type === 'call' && args[args.length - 1] !== true && (utils.isString(args[args.length - 1]) || isFinite(args[args.length - 1]))) {
defaultBlock = args.pop();
}

// get the options
options = (utils.isObject(args[args.length - 1]))
? args.pop()
: {};

// get the makeRequest argument
makeRequest = (args[args.length - 1] === true)? args.pop() : false;


options.from = options.from || this._parent.options.from;
options.data = this.encodeABI();
// TODO remove once we switched everywhere to gasLimit
Expand All @@ -728,79 +748,104 @@ Contract.prototype._executeMethod = function _executeMethod(type){
return this._parent._fireError(new Error('Couldn\'t find a matching contract method, or the number of parameters is wrong.'), defer.promise, defer.reject, callback);
}

// create the callback method
var methodReturnCallback = function(err, returnValue) {
returnValue = (type === 'estimate' || type === 'send') ? returnValue : _this._parent._decodeMethodReturn(_this._method.outputTypes, returnValue);
// simple return request
if(makeRequest) {


if (err) {
return _this._parent._fireError(err, defer.promise, defer.reject, callback);
var payload = {
params: [formatters.inputCallFormatter(options), formatters.inputDefaultBlockNumberFormatter(defaultBlock)],
callback: callback
};

if(type === 'call') {
payload.method = 'eth_call';
payload.format = _this._parent._decodeMethodReturn.bind(null, _this._method.outputTypes);
} else {
payload.method = 'eth_sendTransaction';
}

if(callback) {
callback(null, returnValue);
}
return payload;

// send immediate returnValue
defer.promise.emit('data', returnValue);

if(type === 'send') {

// fire "mined" event and resolve after
_this._parent._web3.eth.subscribe('newBlocks', {}, function (err, block, sub) {
if(!err) {

_this._parent._web3.eth.getTransactionReceipt(returnValue, function (err, receipt) {
if(!err) {
if(receipt) {
sub.unsubscribe();

if(!receipt.outOfGas) {
defer.promise.emit('mined', receipt);
defer.resolve(receipt);
defer.promise.removeAllListeners();

} else {
return _this._parent._fireError(new Error('Transaction ran out of gas.'), defer.promise, defer.reject);
}
}
} else {
sub.unsubscribe();
return _this._parent._fireError(err, defer.promise, defer.reject);
}
});
} else {

// create the callback method
var methodReturnCallback = function(err, returnValue) {

if(type === 'call')
returnValue : _this._parent._decodeMethodReturn(_this._method.outputTypes, returnValue);

} else {
sub.unsubscribe();
return _this._parent._fireError(err, defer.promise, defer.reject);
}
});

if (err) {
return _this._parent._fireError(err, defer.promise, defer.reject, callback);
} else {
// remove all listeners on the end, as no event will ever fire again
defer.resolve(returnValue);
defer.promise.removeAllListeners();

if(callback) {
callback(null, returnValue);
}

// send immediate returnValue
defer.promise.emit('data', returnValue);

if(type === 'send') {

// fire "mined" event and resolve after
_this._parent._web3.eth.subscribe('newBlocks', {}, function (err, block, sub) {
if(!err) {

_this._parent._web3.eth.getTransactionReceipt(returnValue, function (err, receipt) {
if(!err) {
if(receipt) {
sub.unsubscribe();

if(!receipt.outOfGas) {
defer.promise.emit('mined', receipt);
defer.resolve(receipt);
defer.promise.removeAllListeners();

} else {
return _this._parent._fireError(new Error('Transaction ran out of gas.'), defer.promise, defer.reject);
}
}
} else {
sub.unsubscribe();
return _this._parent._fireError(err, defer.promise, defer.reject);
}
});


} else {
sub.unsubscribe();
return _this._parent._fireError(err, defer.promise, defer.reject);
}
});

} else {
// remove all listeners on the end, as no event will ever fire again
defer.resolve(returnValue);
defer.promise.removeAllListeners();
}
}
}
};
};


switch (type) {
case 'estimate':
switch (type) {
case 'estimate':

this._parent._web3.eth.estimateGas(options, methodReturnCallback);
return this._parent._web3.eth.estimateGas(options, methodReturnCallback);

break;
case 'call':
break;
case 'call':

this._parent._web3.eth.call(options, methodReturnCallback);
return this._parent._web3.eth.call(options, defaultBlock, methodReturnCallback);

break;
case 'send':
break;
case 'send':

this._parent._web3.eth.sendTransaction(options, methodReturnCallback);
return this._parent._web3.eth.sendTransaction(options, methodReturnCallback);

break;
}

break;
}

return defer.promise;
Expand Down
6 changes: 5 additions & 1 deletion lib/web3/formatters.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ var isPredefinedBlockNumber = function (blockNumber) {
};

var inputDefaultBlockNumberFormatter = function (blockNumber) {
if (blockNumber === undefined) {
if (!blockNumber) {
return config.defaultBlock;
}
return inputBlockNumberFormatter(blockNumber);
Expand Down Expand Up @@ -95,6 +95,10 @@ var inputCallFormatter = function (options){
var inputTransactionFormatter = function (options){

options.from = options.from || config.defaultAccount;

if(!options.from)
throw new Error('The send transactions "from" field must be defined!');

options.from = inputAddressFormatter(options.from);

if (options.to) { // it might be contract creation
Expand Down
38 changes: 21 additions & 17 deletions test/batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var bn = require('bignumber.js');
describe('lib/web3/batch', function () {
describe('execute', function () {
it('should execute batch request', function (done) {

var provider = new FakeHttpProvider();
web3.setProvider(provider);
web3.reset();
Expand Down Expand Up @@ -39,14 +39,14 @@ describe('lib/web3/batch', function () {
assert.deepEqual(second.params, ['0x0000000000000000000000000000000000000005', 'latest']);
});

var batch = web3.createBatch();
var batch = web3.createBatch();
batch.add(web3.eth.getBalance.request('0x0000000000000000000000000000000000000000', 'latest', callback));
batch.add(web3.eth.getBalance.request('0x0000000000000000000000000000000000000005', 'latest', callback2));
batch.execute();
});

it('should execute batch request for async properties', function (done) {

var provider = new FakeHttpProvider();
web3.setProvider(provider);
web3.reset();
Expand Down Expand Up @@ -77,20 +77,20 @@ describe('lib/web3/batch', function () {
assert.deepEqual(second.params, []);
});

var batch = web3.createBatch();
var batch = web3.createBatch();
batch.add(web3.eth.getAccounts.request(callback));
batch.add(web3.net.getPeerCount.request(callback2));
batch.execute();
});

it('should execute batch request with contract', function (done) {

var provider = new FakeHttpProvider();
web3.setProvider(provider);
web3.reset();

var abi = [{
"name": "balance(address)",
"name": "balance",
"type": "function",
"inputs": [{
"name": "who",
Expand All @@ -103,7 +103,7 @@ describe('lib/web3/batch', function () {
}]
}];


var address = '0x1000000000000000000000000000000000000001';
var result = '0x126';
var result2 = '0x0000000000000000000000000000000000000000000000000000000000000123';
Expand All @@ -121,33 +121,37 @@ describe('lib/web3/batch', function () {
};

provider.injectValidation(function (payload) {

var first = payload[0];
var second = payload[1];

assert.equal(first.method, 'eth_getBalance');
assert.deepEqual(first.params, ['0x0000000000000000000000000000000000000000', 'latest']);
assert.deepEqual(first.params, ['0x0000000000000000000000000000000000000022', 'latest']);
assert.equal(second.method, 'eth_call');
assert.deepEqual(second.params, [{
'to': '0x1000000000000000000000000000000000000001',
'data': '0xe3d670d70000000000000000000000001000000000000000000000000000000000000001'
}]);
},
'latest' // default block
]);
});

var batch = web3.createBatch();
batch.add(web3.eth.getBalance.request('0x0000000000000000000000000000000000000000', 'latest', callback));
batch.add(web3.eth.contract(abi).at(address).balance.request(address, callback2));

var batch = web3.createBatch();
batch.add(web3.eth.getBalance.request('0x0000000000000000000000000000000000000022', 'latest', callback));
batch.add(new web3.eth.contract(abi, address).balance(address).call.request(callback2));
provider.injectBatchResults([result, result2]);
batch.execute();
});

it('should execute batch requests and receive errors', function (done) {

var provider = new FakeHttpProvider();
web3.setProvider(provider);
web3.reset();

var abi = [{
"name": "balance(address)",
"name": "balance",
"type": "function",
"inputs": [{
"name": "who",
Expand All @@ -160,7 +164,7 @@ describe('lib/web3/batch', function () {
}]
}];


var address = '0x1000000000000000000000000000000000000001';
var result = 'Something went wrong';
var result2 = 'Something went wrong 2';
Expand Down Expand Up @@ -191,7 +195,7 @@ describe('lib/web3/batch', function () {
}]);
});

var batch = web3.createBatch();
var batch = web3.createBatch();
batch.add(web3.eth.getBalance.request('0x0000000000000000000000000000000000000000', 'latest', callback));
batch.add(web3.eth.contract(abi).at(address).balance.request(address, callback2));
provider.injectBatchResults([result, result2], true); // injects error
Expand Down

0 comments on commit 5962333

Please sign in to comment.