Closed VladoDemcak closed 4 years ago
@frozeman - Could you help me with this please?
I found your commit from 2016 :D where the part with deleting fromBlock
from params in subscription has been added. I still dont have clue why it's here. When I comment it out the subscription for events takes fromBlock
correctly. otherwise it takes only the latest block.
Here is the commit: https://github.com/ethereum/web3.js/commit/23f428b09627095658d09d306455082641424aac
@VladoDemcak Thanks for opening and investigating - will look at this today along with #3379.
@VladoDemcak I've tried to build a reproduction for this in #3391 but it looks like everything works...
The case replicates what you've described here, (using Geth stable (1.9), mining at 2 second intervals):
So if current block is 10 and there was an event in block 9 it returns only this event but doesnt return event from block 8,7,6...0.
The Travis CI output for it is here.
Does that example +/- model your request correctly?
i also have this issue of the fromBlock
being ignored when calling contract.events.Transfer
(weirdly enough it's on the "Transfer" event of an ERC20 contract)
in my case i'm connected to the Portis provider on Rinkeby, but i'm passing a filter
parameter (maybe the one difference with your test @cgewecke ?)
@cgewecke Thanks for your effort. Is your test case isolated from the others? Could you please retest the issue with steps on kovan network I am describing below?
I've tested it on kovan network and it doesn't return any events from the past when I create subscription for Transfer
events.
I deployed my custom SimpleToken
on address 0xaA8f1B72Ac1c9769D25a9251A982fb4a6c296Bf1
Check the txns for the token:
There are several transactions in multiple blocks eg: 1699289, 16992925, 16993448 ... (at least 6 blocks)
I have found very interesting behavior please see below:
Steps to reproduce the issue:
create .js
file with content of example.js
from below
Change INFURA_KEY to your ProjectID. How to register project id is described below:
2.1 Go to: https://infura.io/register
2.2 Create new account
2.3 "CREATE NEW PROJECT", give it a nam, copy PROJECT_ID and replace INFURA_KEY with your PROJECT_ID in example.js
Run the example.js
-> it should print :
listening...
and nothing else
Now the funny part. Uncomment the code under // uncomment code below
in example.js
for getting results from the past. It doesn't print anything it's just simple web3 call
Run the code again -> - it prints all events fromBlock -> event events from the PAST! and the fromBlock
here works!!!
example.js:
const Web3 = require('web3');
const abi = [
{
"inputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "subtractedValue",
"type": "uint256"
}
],
"name": "decreaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "addedValue",
"type": "uint256"
}
],
"name": "increaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]
const web3 = new Web3("wss://kovan.infura.io/ws/v3/INFURA_KEY");
const run = async () => {
const contract = new web3.eth.Contract(abi, '0xaA8f1B72Ac1c9769D25a9251A982fb4a6c296Bf1');
// uncomment code below
// await contract.getPastEvents('Transfer', {
// fromBlock: 0,
// toBlock: 'latest'
// });
contract.events.Transfer({fromBlock: 0})
.on('data', (event) => console.log(event));
}
run()
.then(() => {
console.log("listening...")
});
I observe this problem on:
@VladoDemcak Are you able to provide a super simple example using just a node.js script and ganache (or as a mocha test)? Something similar to the tests we have in the test suite here? #3391 also worked for ganache on my end...
Infura's websockets are known to be quite fragile. There are lots of dropped connections and sometimes swallowed errors. (A PR is open at #3190 to try to remedy this in several ways).
Tbh, if you're interested in storing events for a deployed contract via Infura the most robust solution is likely to set up an interval poll, and run getPastEvents
every so often with an http provider.
@cgewecke This is super weird.
I will try to create some super simple example but everything is in const run = async () => {
in my example from above comment.
Could you please verify/answer following:
contract.getPastEvents('Transfer'
or something else before subscribing to contract.events.Transfer({fromBlock: 0})
?I found it works when contract.getPastEvents('Transfer'
is executed before subscribing.
@VladoDemcak I haven't run a transfer
specific example because the issue you've raised is about fromBlock
which should work the same for any event.
One way forward might be to express this problem in terms of Web3's E2E tests which try to be as minimal as possible...then work backwards to your specific case and identify the differences.
You should be able to clone Web3, install and run the E2E client tests using the commands listed in the main package.json
's scripts
For example:
npm run test:e2e:ganache
will execute the existing events tests on a ganache-cli instance. @cgewecke Thanks.
yes I saw the events tests
and I am trying to run it locally.
my current suspicion is that in events tests
the Basic
SmartContract is being deployed via web3 and tests hold the instance
instance = await basic.deploy().send({from: accounts[0]});
In my case I deploy my contract via truffle and the instance is created based on abi and contract_address
.
const contract = new web3.eth.Contract(abi, '0xaA8f1B72Ac1c9769D25a9251A982fb4a6c296Bf1');
@cgewecke
I've prepared repo where you should be able to replicate the problem. Please follow instructions in README. https://github.com/VladoDemcak/eth-subscribe-debug I also added screenshots of 3 terminals where you can see what was going on.
I think your tests work because there is the instance of contract which is used also for subscribing. And this instance has some attributes (or is in different state) which is necessary for subscription.
Important thing is that I observe the issue with fromBlock
when I run subscription in independent node (the same case for Infura example I posted above).
The interesting part is that when I execute getPastEvents
or send the Event before subscribing for Events in the same node
, it works for every blockchain I tested on!!!
It looks like there is a hidden implementation in getPastEvents
(or in sending event) which is adding some attribute to contract or establishes a connection and this affects behavior of "only subscription" approach.
On this link you can see 3 JSON.stringify(contract)
outputs I have taken from listen.js
in my repo example.
There are several differences between the contract objects. E.g working contracts (with prefix "working" in this gist ) are different in fields like: base64nonce
, headers.sec-websocket-accept
, maskBytes.data[]
sooo nothing special.
BUT when I compare non-working contract with working contract there are differences in fields:
_provider.connected: false
in non-working contract and _provider.connected: true
in working contract.
Next .. _requestManager.provider.connected: false
in non-working example but true
in working example. Again _readyState: 0
in non-working _readyState: 1
in working example .... and more ...
I will try to find more .... But please if you know how to fix this (e.g with some attribute or something) please let me know.
==========
even when i execute:
await web3.eth.getAccounts()
or
await web3.eth.getBlockNumber()
before calling subscription with fromBlock
it works.
i also have this issue of the
fromBlock
being ignored when callingcontract.events.Transfer
(weirdly enough it's on the "Transfer" event of an ERC20 contract)in my case i'm connected to the Portis provider on Rinkeby, but i'm passing a
filter
parameter (maybe the one difference with your test @cgewecke ?)
@teawaterwire Can you share your code?
await web3.eth.getAccounts()
Ai!! That's really weird. Thanks for making the repo, will take a look tomorrow and see if anything jumps out.
@cgewecke were you able to replicate the issue on your side?
@VladoDemcak Yes I was, thanks for making such a great reproduction.
Still investigating and your suggestion about whether the socket is connected seems like it would make sense.
However, I'm seeing the initial request for eth_getLogs
here execute correctly in the "non-working" example. It just returns fewer logs in ganache, and no logs on geth unless a previous call has been made.
In sum, you cannot initiate an event subscription as the first communication you have with the client and get all the past logs as expected.
Very mysterious, especially the different responses from the client...smh.
In sum, you cannot initiate an event subscription as the first communication you have with the client and get all the past logs as expected.
Exactly! Thanks for re-testing.
If you will find any solution let me know and I'll test it. Meanwhile I will try to do something as well.
@VladoDemcak When I wrap the delete
block statement you initially flagged as a problem in a 150ms timeout (here)
...it works.
It must be a race condition caused by latency setting up the initial Websocket connection. By the time the first request gets made, the param's been deleted.
@cgewecke Thanks for this in-depth analysis, great 😄, will close here.
@holgerd77 why did you close the issue? Are you guys going to fix it somewhere?
@VladoDemcak Sorry, I might have misread the last comment from @cgewecke, will re-open for now.
@holgerd77 @cgewecke Guys, I honestly appreciate all your effort you put into this. Do you have any plans to fix it any time soon?
I am asking if we should stay with the getAccounts() / getBlockNumber() / getGasPrice()
workaround before creating an event subscription or you have a solution for this problem and we will wait for that fix?
@VladoDemcak I will open a PR with a fix today, but it may be a few weeks before Web3 is published again. Would probably use the work-around for the time being, sorry.
@cgewecke @holgerd77 if i may ask I would have one question which is not exactly related to the issue but cannot find any answer.
When I create subscription for e.g contract Transfer
event like below.
For Example.
contract.events.Transfer({
fromBlock: 0,
transactionConfirmationBlocks: 5 <--- something like this line - according to doc I know there isn't something like this for subscription
})
Can I get the event ONLY after 5 confirmations? Is there any way how to achieve this functionality without creating custom queue for received events and computing number of confirmation blocks?
Maybe I am facing the same issue as described here: https://github.com/ethereum/web3.js/issues/3379.
Current behaviour When I am subscribed I get only events from last block. Eg if there are events in block 0,1,2,3 and I set
fromBlock
: 0 and the current block is 3 I get only events from block 3 and not from 0.Expected behaviour Once I am subscribed and I specify
fromBlock
: 0, I should get all events from block 0, 1 ... latest.I've created the post on stackoverflow as well so more details about the problem I am facing is described here.
I am using
WebsocketProvider
andquorum-v2.4.0
andweb3js 1.2.6
. So I am connected like this:When I create subscription for my contract it looks like below. I specify
fromBlock
because I want to get all events from block 0 when I am creating subscription.However it doesn't return events from block 0 even though there are some events. It returns only the events from last block. So if current block is 10 and there was an event in block 9 it returns only this event but doesnt return event from block 8,7,6...0.
After some time of investigating the issue I have found there is a weird part of the implementation in subscription.js where
fromBlock
is removed withdelete
keyword. Check the snipped fromsubscription.js
below.Since this code deletes the
fromBlock
from params it is missing in WebsocketProvider.prototype.send.Because of this delete part the payload in send function in
WebsocketProvider
doesnt call RPC withfromBlock
parameter (request is withoutfromBlock
) and I assume it is considered aslatest
so it doesn't return events from past:After deleting
fromBlock
param the request looks like this:When I comment out the part where
fromBlock
is being deleted the payload is:and this second example correctly returns the events from block 0 not only the new.