5

I have an issue with processing new blocks through a WebSocket connection on a local node. Sometimes I receive multiple events with the same blockNumber but different blockHashes.

I understood from some StackExchange threads that one of these blocks are just not part of the canonical chain. However, my question is: How do I determine which one of them is on the canonical chain? What would be the exact steps I need to take when I open a WebSocket connection and receive a new block after subscribing to "newHeads" to ensure this block is canonical?

Example

This is a shortened log of an application that stores some transactions from the newest blocks into an SQL db.

Added block #7665190 | hash 0x9ba66766f3835d5be9191e0ad7c79b741707e78dcf1f2a8898b7a7c2141d357c
Added block #7665191 | hash 0xe550f98974b4b02fff709842f670e682fc90403ead36d6258927b38cd45a56bc
Added block #7665191 | hash 0x068f5648b0509303640a1aa9c10764c89f33482148fde4ef94ce0ab4f21c25d1
Added block #7665192 | hash 0x36c2f7e9a4542a2311c91e8cd1560994ca14841086be86d0f6dbb1be9eae9d46
Added block #7665193 | hash 0x0cd7d6e0dac1c3288e574b454d7e4dee9123c4e1e94b6e87307aae32e812f1f1

Is using a getBlockByNumber RPC call after receiving a new block number sufficient to determine that this is indeed the "right" block that I want to store transactions for in my DB?

I don't think so because if I would use getBlockByNumber on the first 7665191 I would still receive the transactions in that block. Then after using it on the subsequent 7665191 I would receive another set of transactions that might include a bunch of the previous ones. Thus introducing redundancy and possible mismatches between the canonical transactions hashes / addresses in my SQL db.

Second option I could think of would be to wait until I receive just a single blockNumber after (e.g. 7665192 in this case) and then check it's parentHash. If one of the 7665191's hash is equal to 7665192's parentHash I would take this one as the canonical block. However this just introduced a lot more questions:

  • What if I would receive duplicate blockNumbers multiple times in a row?
    • e.g. 7665191, 7665191, 7665192, 7665192, 7665193, 7665193 ...
  • What if the parentHash wouldn't be one of the previous duplicates?
    • e.g. parentHash of 7665192 wouldn't be equal to hash of any 7665191

To reiterate my question: What is the recommended way to ensure that I only store the real transactions from the blocks that you can find on bscscan.

2 Answers 2

5
+50

Basically, what you want is to have a confirmation that a given block was included in the canonical chain. I assume that you do not need very high security because based on your examples a confirmation of only one block would be enough for your need.

I think that expending your second option could be a solution.

Alternative 1 - Validate double blocks with parent hash

Let's say that you received two blocks with the number 1000.

  1. Wait until you receive a block that has number +1 than the block you want to confirm (block number 1001).
  2. Check the parentHash of block 1001 and see if it matches the hash of any of the blocks with number 1000. If you have a match, you know that this is the block to include.
  3. If you have no match at all (not sure if it is possible if you run a full node), then you should make a call to get the block that has a hash equal to the parentHash of block 1001. With web3js, you would use web3.eth.getBlock(parentHashOfBlock1001). You would then have the right block number 1000.

Alternative 2 - Include only on confirmation

Another solution would be to always wait one block before including the transactions of the previous block.

  1. When you receive a block header, do not include its transactions in your databse. Instead, get the block corresponding to the parentHash web3.eth.getBlock(parentHashOfCurrentBlock).
  2. Check if you have already included that parent block in your database. This could happen when you receive the same block number twice (both blocks should have the same parent even if one of them is not valid). If it was not included:
  3. Include the transactions of that parent block in your database.

You will always be one block late, but it should solve your issue most of the time.

Alternative 3 - Wait... then get block by number

Your node should take care of removing invalid blocks. If you wait a certain number of blocks and then get the past block by number, you should only receive the valid block.

1
  • I ended up creating a queue-like structure which also takes into account multiple duplicates and resolves them later when the parentHashes start to make sense. (i.e. it won't break even if the sequence of blocks would be: 1, 1, 1, 2, 2, 2, 2, 3, 4, ...). Thanks!
    – dvdblk
    Commented Jun 2, 2021 at 0:32
3

Proof-of-work chains like Ethereum are probabilistic. The "longest chain" rule helps nodes determine the canonical chain but much ambiguity exists at the head of the chain. Uncertainty diminishes exponentially with each new block on the longest known chain. The different blocks you see are candidates and it is not uncommon to have more than one since honest miners can find blocks without knowing another miner already did (network latency).

Uncertainty can be made low, but it is never zero. I would suggest you establish the confirmation threshold you require(e.g. 100, ~15 minutes). Knowing the highest block number observed, say 987654, get by block number 987554 (-100) and commit. For any given, confirmed transaction, this essentially a confirmation countdown as you reduce uncertainty to an acceptable level and it is often presented as such in user interfaces.

In case it isn't clear, there is really no need to pay attention to the noise at the head of the chain, but you will process the confirmed blocks or transactions as though the contents are nearly certain.

Hope it helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.