Skip to content

Commit

Permalink
Not getting success callback/ receipt with walletconnect with web3.js…
Browse files Browse the repository at this point in the history
… 1.3.3 - Closes #3891 (#4304)

* 🐛 Fix transaction receipt polling technique

* ✅ Add unit test for the delay scenario

* 🔧 Add configuration option "blockHeaderTimeout"

* 📝 Add docs for the new configuration

* ✅ Add unit test for the configuration

* 🔖 Update change log file

* ✅ Fix failing unit test

* Update comment

I'm just going to commit this since it's a comment change, and so I can provide my approval without it going stale

* 🎨 Update default value for blockHeaderTimeout to 10 seconds

Co-authored-by: Wyatt Barnes <wyatt@writerof.software>
  • Loading branch information
nazarhussain and spacesailor24 committed Sep 22, 2021
1 parent 93070a7 commit 40974e9
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ Released with 1.0.0-beta.37 code base.
### Fixed

- Unable to send legacy transaction if network supported EIP-1559 (#4277)
- Fixed bug in sending transaction with providers not support "newBlockHeaders" event (#3891)

### Changed

Expand All @@ -445,6 +446,8 @@ Released with 1.0.0-beta.37 code base.
- lerna from 3.22.1 to 4.0.0 (#4231)
- Dropped build tests in CI for Node v8 and v10, and added support for Node v14
- Change default value for `maxPriorityFeePerGas` from `1 Gwei` to `2.5 Gwei` (#4284)
- Fixed bug in signTransaction (#4295)
- Introduced new configuration "blockHeaderTimeout" for waiting of block headers for transaction receipt (#3891)
- Fixed bug in signTransaction (#4295)

## [Unreleased]
Expand Down
21 changes: 21 additions & 0 deletions docs/web3-eth-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,27 @@ Returns

------------------------------------------------------------------------------

.. _eth-contract-blockHeaderTimeout:

blockHeaderTimeout
=====================

.. code-block:: javascript
web3.eth.Contract.blockHeaderTimeout
contract.blockHeaderTimeout // on contract instance
The ``blockHeaderTimeout`` is used over socket-based connections. This option defines the amount seconds it should wait for "newBlockHeaders" event before falling back to polling to fetch transaction receipt.


-------
Returns
-------

``number``: The current value of blockHeaderTimeout (default: 10 seconds)

------------------------------------------------------------------------------

.. _eth-contract-module-transactionconfirmationblocks:

transactionConfirmationBlocks
Expand Down
34 changes: 34 additions & 0 deletions docs/web3-eth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,40 @@ Example
web3.eth.transactionBlockTimeout = 100;
------------------------------------------------------------------------------

.. _eth-contract-blockHeaderTimeout:

blockHeaderTimeout
=====================

.. code-block:: javascript
web3.eth.Contract.blockHeaderTimeout
contract.blockHeaderTimeout // on contract instance
The ``blockHeaderTimeout`` is used over socket-based connections. This option defines the amount seconds it should wait for "newBlockHeaders" event before falling back to polling to fetch transaction receipt.


-------
Returns
-------

``number``: The current value of blockHeaderTimeout (default: 10 seconds)

-------
Example
-------

.. code-block:: javascript
web3.eth.blockHeaderTimeout;
> 5
// set the transaction confirmations blocks
web3.eth.blockHeaderTimeout = 10;
------------------------------------------------------------------------------

.. _web3-module-transactionconfirmationblocks:
Expand Down
36 changes: 25 additions & 11 deletions packages/web3-core-method/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var Method = function Method(options) {
this.transactionBlockTimeout = options.transactionBlockTimeout || 50;
this.transactionConfirmationBlocks = options.transactionConfirmationBlocks || 24;
this.transactionPollingTimeout = options.transactionPollingTimeout || 750;
this.blockHeaderTimeout = options.blockHeaderTimeout || 10; // 10 seconds
this.defaultCommon = options.defaultCommon;
this.defaultChain = options.defaultChain;
this.defaultHardfork = options.defaultHardfork;
Expand Down Expand Up @@ -547,22 +548,35 @@ Method.prototype._confirmTransaction = function (defer, result, payload) {

// start watching for confirmation depending on the support features of the provider
var startWatching = function (existingReceipt) {
let blockHeaderArrived = false;

const startInterval = () => {
intervalId = setInterval(checkConfirmation.bind(null, existingReceipt, true), 1000);
};

if (!this.requestManager.provider.on) {
startInterval();
} else {
_ethereumCall.subscribe('newBlockHeaders', function (err, blockHeader, sub) {
if (err || !blockHeader) {
// fall back to polling
startInterval();
} else {
checkConfirmation(existingReceipt, false, err, blockHeader, sub);
}
});
// If provider do not support event subscription use polling
if(!this.requestManager.provider.on) {
return startInterval();
}

// Subscribe to new block headers to look for tx receipt
_ethereumCall.subscribe('newBlockHeaders', function (err, blockHeader, sub) {
blockHeaderArrived = true;

if (err || !blockHeader) {
// fall back to polling
return startInterval();
}

checkConfirmation(existingReceipt, false, err, blockHeader, sub);
});

// Fallback to polling if tx receipt didn't arrived in "blockHeaderTimeout" [10 seconds]
setTimeout(() => {
if(!blockHeaderArrived) {
startInterval();
}
}, this.blockHeaderTimeout * 1000);
}.bind(this);


Expand Down
13 changes: 13 additions & 0 deletions packages/web3-eth-contract/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ var Contract = function Contract(jsonInterface, address, options) {
},
enumerable: true
});
Object.defineProperty(this, 'blockHeaderTimeout', {
get: function () {
if (_this.options.blockHeaderTimeout === 0) {
return _this.options.blockHeaderTimeout;
}

return _this.options.blockHeaderTimeout || this.constructor.blockHeaderTimeout;
},
set: function (val) {
_this.options.blockHeaderTimeout = val;
},
enumerable: true
});
Object.defineProperty(this, 'defaultAccount', {
get: function () {
return defaultAccount;
Expand Down
19 changes: 19 additions & 0 deletions packages/web3-eth/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ var Eth = function Eth() {
var transactionBlockTimeout = 50;
var transactionConfirmationBlocks = 24;
var transactionPollingTimeout = 750;
var blockHeaderTimeout = 10; // 10 seconds
var maxListenersWarningThreshold = 100;
var defaultChain, defaultHardfork, defaultCommon;

Expand Down Expand Up @@ -222,6 +223,23 @@ var Eth = function Eth() {
},
enumerable: true
});
Object.defineProperty(this, 'blockHeaderTimeout', {
get: function () {
return blockHeaderTimeout;
},
set: function (val) {
blockHeaderTimeout = val;

// also set on the Contract object
_this.Contract.blockHeaderTimeout = blockHeaderTimeout;

// update defaultBlock
methods.forEach(function(method) {
method.blockHeaderTimeout = blockHeaderTimeout;
});
},
enumerable: true
});
Object.defineProperty(this, 'defaultAccount', {
get: function () {
return defaultAccount;
Expand Down Expand Up @@ -333,6 +351,7 @@ var Eth = function Eth() {
this.Contract.transactionBlockTimeout = this.transactionBlockTimeout;
this.Contract.transactionConfirmationBlocks = this.transactionConfirmationBlocks;
this.Contract.transactionPollingTimeout = this.transactionPollingTimeout;
this.Contract.blockHeaderTimeout = this.blockHeaderTimeout;
this.Contract.handleRevert = this.handleRevert;
this.Contract._requestManager = this._requestManager;
this.Contract._ethAccounts = this.accounts;
Expand Down
25 changes: 25 additions & 0 deletions test/eth.blockHeaderTimeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
var chai = require('chai');
var assert = chai.assert;
var Eth = require('../packages/web3-eth');

var eth = new Eth();

var setValue = 123;

describe('web3.eth', function () {
describe('blockHeaderTimeout', function () {
it('should check if blockHeaderTimeout is set to proper value', function () {
assert.equal(eth.blockHeaderTimeout, 10);
assert.equal(eth.Contract.blockHeaderTimeout, 10);
assert.equal(eth.getCode.method.blockHeaderTimeout, 10);
});
it('should set blockHeaderTimeout for all sub packages is set to proper value, if Eth package is changed', function () {
eth.blockHeaderTimeout = setValue;

assert.equal(eth.blockHeaderTimeout, setValue);
assert.equal(eth.Contract.blockHeaderTimeout, setValue);
assert.equal(eth.getCode.method.blockHeaderTimeout, setValue);
});
});
});

61 changes: 61 additions & 0 deletions test/method.buildCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,67 @@ describe('lib/web3/method', function () {
gasPrice: '23435234234'
})
});

it('should fallback to polling if provider support `on` but `newBlockHeaders` does not arrive in `blockHeaderTimeout` seconds', function (done) {
const provider = new FakeHttpProvider();
// provider with method 'on' but no subscription capabilities should use polling
provider.on = (...args) => {}
const eth = new Eth(provider);

const method = new Method({
name: 'sendTransaction',
call: 'eth_sendTransaction',
params: 1,
inputFormatter: [formatters.inputTransactionFormatter]
});
method.setRequestManager(eth._requestManager, eth);
method.blockHeaderTimeout = 1;

// generate send function
const send = method.buildCall();

// add results
provider.injectValidation(function (payload) {
assert.equal(payload.method, 'eth_sendTransaction');
assert.deepEqual(payload.params, [{
from: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
to: '0x11f4d0a3c12e86b4b5f39b213f7e19d048276dae',
value: '0xa',
gasPrice: "0x574d94bba"
}]);
});
provider.injectResult('0x1234567453543456321456321'); // tx hash

provider.injectValidation(function (payload) {
assert.equal(payload.method, 'eth_getTransactionReceipt');
assert.deepEqual(payload.params, ['0x1234567453543456321456321']);
});
provider.injectResult(null);

provider.injectValidation(function (payload) {
// here is the check.
// first will try subscribing with `eth_subscribe`.
assert.equal(payload.method, 'eth_subscribe');
assert.deepEqual(payload.params, ['newHeads']);
});
provider.injectResult(null);

// after failing with `eth_subscribe`,
// it should start polling with `eth_getTransactionReceipt`
provider.injectValidation(function (payload) {
assert.equal(payload.method, 'eth_getTransactionReceipt');
assert.deepEqual(payload.params, ['0x1234567453543456321456321']);
done();
});
provider.injectResult(null);

send({
from: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
to: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
value: '0xa',
gasPrice: '23435234234'
})
});
});
});

0 comments on commit 40974e9

Please sign in to comment.