diff --git a/packages/web3-core-subscriptions/src/subscription.js b/packages/web3-core-subscriptions/src/subscription.js index dc44cf4c613..d0dfbe408f7 100644 --- a/packages/web3-core-subscriptions/src/subscription.js +++ b/packages/web3-core-subscriptions/src/subscription.js @@ -232,9 +232,13 @@ Subscription.prototype.subscribe = function() { // get past logs, if fromBlock is available if(payload.params[0] === 'logs' && _.isObject(payload.params[1]) && payload.params[1].hasOwnProperty('fromBlock') && isFinite(payload.params[1].fromBlock)) { // send the subscription request + + // copy the params to avoid race-condition with deletion below this block + var blockParams = Object.assign({}, payload.params[1]); + this.options.requestManager.send({ method: 'eth_getLogs', - params: [payload.params[1]] + params: [blockParams] }, function (err, logs) { if(!err) { logs.forEach(function(log){ diff --git a/test/e2e.contract.events.js b/test/e2e.contract.events.js index 15e87acd604..4ca2d64d780 100644 --- a/test/e2e.contract.events.js +++ b/test/e2e.contract.events.js @@ -13,6 +13,7 @@ describe('contract.events [ @E2E ]', function() { var accounts; var basic; var instance; + var port; var basicOptions = { data: Basic.bytecode, @@ -21,7 +22,7 @@ describe('contract.events [ @E2E ]', function() { }; beforeEach(async function(){ - var port = utils.getWebsocketPort(); + port = utils.getWebsocketPort(); web3 = new Web3('ws://localhost:' + port); accounts = await web3.eth.getAccounts(); @@ -133,6 +134,53 @@ describe('contract.events [ @E2E ]', function() { }); }); + // Regression test for a race-condition where a fresh web3 instance + // subscribing to past events would have its call parameters deleted while it + // made initial Websocket handshake and return an incorrect response. + it('can immediately listen for events in the past', async function(){ + this.timeout(15000); + + const first = await instance + .methods + .firesEvent(accounts[0], 1) + .send({from: accounts[0]}); + + const second = await instance + .methods + .firesEvent(accounts[0], 1) + .send({from: accounts[0]}); + + // Go forward one block... + await utils.mine(web3, accounts[0]); + const latestBlock = await web3.eth.getBlockNumber(); + + assert(first.blockNumber < latestBlock); + assert(second.blockNumber < latestBlock); + + // Re-instantiate web3 & instance to simulate + // subscribing to past events as first request + web3 = new Web3('ws://localhost:' + port); + const newInstance = new web3.eth.Contract(Basic.abi, instance.options.address); + + let counter = 0; + await new Promise(async resolve => { + newInstance + .events + .BasicEvent({ + fromBlock: 0 + }) + .on('data', function(event) { + counter++; + assert(event.blockNumber < latestBlock); + + if (counter === 2){ + this.removeAllListeners(); + resolve(); + } + }); + }); + }); + it('hears events when subscribed to "logs" (emitter)', function(){ return new Promise(async function(resolve, reject){