Watched contract event creates 2 records with different values

Hi !

We have observed a very strange behaviour coming up from the RequestedBallBuy event from this transaction: https://mumbai.polygonscan.com/tx/0x1d6871b6f5171944d34a80f8c4753e9ac112d2847fa4c631bc018564907a5f07#eventlog

Here you can see the entries created from this event:

The first one holds the correct requestId value, meanwhile the second doesn’t. The first one createdAt is 30 Apr 2022 at 13:09:54 UTC meanwhile the second one is 30 Apr 2022 at 12:56:29 UTC (~15 minutes earlier). The first one is confirmed, the second one is not. This time difference matches an approximate duration of 100 blocks to be minted so a confirmation becomes confirmed on Polygon.

For context, we had a conversation at Unconfirmed events no longer sync on Mumbai (polygon testnet) that gives some insights about this. Since then, we have made our logic able to handle these different cases, however this time around it’s a bit different: the values on each record are different.

The transaction_index and log_index are also different. block_hash and block_number are the same, however the block_timestamp is different (the correct requestId was logged 5 seconds earlier than the wrong one). We are not sure what this means in terms of miners picking the transaction from the mempool and minting the block with a 5 seconds difference, is this chain reorganisation ?

For completeness, the source code that generates this requestId value (on-chain):

function _getRequestRandomId() private view returns (uint256) {
        return
            uint256(
                keccak256(
                    abi.encodePacked(
                        block.timestamp,
                        _msgSender(),
                        _requestIdNonce
                    )
                )
            ) % 1000000000;
    }

(where _requestIdNonce is a number starting from 0 and increments 1 by 1 for each request)

It’s obvious that not using the block.timestamp as a source of compute could potentially solve the issue, but then the _requestIdNonce might also be different if the second time the block was created the transactions order is not the same and the contract state does not match. This solution might be flawky, I’m all ears to listen for a better one that allows callers to have more than one request at a time and more than one request emitted in the same block.

Is this a behaviour that should be handled on our side ? How could we approach it ? If we are given wrong data and only 15 minutes later we receive the right one, I don’t see any way to prevent our logic to run with the first one.

In case helpful => Server URL: https://5o8m7xjcginv.usemoralis.com:2053/server

Thank you very much for your support :pray:t2:

It looks like something related to a reorg.

Can you validate the received data based on the request id? As in, if the request id isn’t the same as computed from block timestamp, msg sender and nonce then you could say that it is not valid.

You can recompute the keccak256 off-chain and check if it matches yes. However, I’m afraid both record will be valid, right ? For each of them, as they have lived in a different “reality” (chain context), they have computed a correct request id value.

Where is this logging with 5 seconds difference?

I mean the block_timestamp value, they are 5 seconds off of each other.

As we are discussing between the team in parallel to this conversation, we reached the conclusion that we could be using the requestIdNonce value directly instead of the computed requestId. However, we then read https://learnmeabitcoin.com/technical/chain-reorganisation and we didn’t know a transaction could be later mined in a different block.

I wonder how Moralis reacts to reorganization, maybe you can provide some light about this. We have identified two cases (are there more?):

  1. A transaction is mined in the same block number but at a different time in the new reference chain
  2. A transaction is mined in a different block number at a different time in the new reference chain.

How does this translate into the events Moralis will emit ?

Finally, we of course check for the request id validity and we do not allow duplicated behaviours, however we don’t see any way of differentiating false positives. We can’t just discard invalid requests ids, because maybe one of them is a valid request that has some issue. We can’t check if the request id was already used or not because it might come before, after, of 15 minutes later.

There can be a third case in theory when after the reorg the transaction fails because of a different chain state. It may not be the case for you.

Maybe that 15 minutes delay happened on testnet for a big reorg and maybe on polygon mainnet you will not get this type of huge delay.

Yes, right, that third case exists.

How will Moralis react to these different cases then ?

And for the 15 minutes, it actually doesn’t matter that much. Block frequency in testnet is obviously much slower (on mainnet 100 blocks take 3-4 minutes, on testnet we have observed it always takes at least 8-10 minutes with an average of ~13 minutes), but this doesn’t mean on the mainnet we won’t have huge delays. Reorganizations could be of very different heights, and in any case just a few seconds of difference is enough for our alerts to fire as the received request id is not found on on-chain state.

On Moralis side, when an event is found first time then is synced as unconfirmed, after it passes a number of blocks, like for example 100, then that event is also synced as confirmed. An upsert is used to update the row in both cases.

Yes, that’s what we have as a mental model so far. However, does it still apply in the same way when the chain is reorganized ? How does Moralis determine if it needs to update a record or create a new one ? It can’t just be event arguments.

After further online reading, reorganizations heights have historically been quite small and not frequent. I guess on testnet they are more likely to happen as the block frequence is heavily manipulated.

it looks like (log_index and transaction_hash) has to be unique

1 Like

Okay ! Thank you very much :+1:t2: