[Ethereum] Could event watch() traverse from latest block number(tail) to head

eventsweb3.pyweb3js

In general, I assume events traverse from smaller to larger block number.

Example:

contractDeployedBlockNumber = 10000
fromBlock: contractDeployedBlockNumber, toBlock: 'latest'

[Q] Could event watch() traverse from 'latest' block number through 0th block number (or smaller block number)? this is basically reverse traverse operation in order to catch the latest event first and break, this will help to retrieve the latest emitted event as fast as possible.


Smart contract's function keeps a registered users' information and updates it using registerUser() function.

contract Example {
   mapping(address => uint) userLatestEmittedBlocNum;
   function registerUser(string memory userEmail,
                         string memory name,
                         string memory githubUserName) public
       returns (bool success)
   {
       userLatestEmittedBlocNum[msg.sender] = block.number;
       emit LogUser(msg.sender, userEmail, name, githubUserName);
       return true;
   }

   function getUserLatestEmittedBlocNum(address userAddress) public view
    returns(uint)
  {
      if (userLatestEmittedBlocNum[userAddress] != 0)
          return (userLatestEmittedBlocNum[userAddress]);
   }
   event LogUser(address userAddress, string userEmail, string name, string githubUserName);
 }

I can store the block number where the user's information is updated under userLatestEmittedBlocNum[msg.sender] and retrieve the emitted log directly from that block number, but this will cost additional storage and gas usage.

blockReadFrom = Example.functions. getUserLatestEmittedBlocNum(userAddress).call()
my_filter = Example.eventFilter('LogUser',{'fromBlock':int(blockReadFrom),'toBlock':int(blockReadFrom) + 1})

As a results we need to retrieve the latest event for each users for the up-to-date information.

It may be inefficient to iterate from smaller block number to latest block number (that will also get the previous updated events as well), and span range might be very large. Instead, if we could able to scan from latest block number to small block number and break the iteration when we catch the event, it might be faster to find the latest emitted log.

On a smart contract, some information is logged and over time it may updated, hence the latest event is valid. So I want to retrieve the latest event on my smart contract, without start scanning from the much smaller block number, which is time consuming and inefficient. So basically starting from 'latest' block number to smaller block, and break this iteration if I catch the event. Otherwise if I start from smaller block number, no matter what, I have to iterate till the latest block number.


Goal: fromBlock: latest, toBlock: 0

blockNum = 0;
var contract = myContract.LogJob({}, { fromBlock: blockNum, toBlock: 'latest' });
var i = 0;
contract.watch(function (error, result) {
    console.log(i++ + ": " + JSON.stringify(result));
});

Best Answer

I'll try, because bounties. :-)

Technically, no, but you could create a similar effect.

I say "no" because the blockchain is a well-ordered set of blocks each containing a well-ordered set of transactions. By extension, the blockchain is a well-ordered set of transactions starting with the first transaction in or after block 0.

Here's the kicker.

It's meant to be reviewed in order owing to the fact that a blocks after the genesis block can't be independently verified without reference to the preceeding block. In other words, to know that you are looking at the authentic 50th block (by consensus), you must have knowledge of block 49. If you start at the end you will, generally, either recursively apply logic all the way back to block 0, or place your trust in something other than your own independent assessment.

Also, you can never know you have the latest block. The most you can ever know is you have the latest block you know about (and there is probably an even newer one on the way). What, would be first on a list of events starting with the newest first? It wouldn't last very long.

To accomplish something similar to your use-case seems to imply listening to all known events and continuously inserting newer "most recent" blocks at the beginning of a list. Or (probably more efficient) append the newest to the end of your own list and then read it back in reverse when you need to. This would probably be in relation to an offchain concern (UI, cache, something else ...) - maybe "point in time" snapshots or live updates as new "most recent" log entries are revealed.

It's perfectly fair to maintain your own off-chain data sources for performance and other reasons. You can still get the benefits of blockchain when users/clients can verify the facts if they want to by confirming facts against their own copy of the chain. This is approximately what is happening when you use online block explorers and other apps that need more performance than a typical node would be capable of. You can sort that sort of thing any way you like.

Hope it helps.

UPDATE

There is a pattern to efficiently do this if you design the contract to support it. It uses very simple breadcrumbs:

pragma solidity 0.5.1;

contract ReverseOrderEvents {

    uint lastEventBlock;

    event LogEvent(address sender, uint previousEvent);

    function doSomething() public {
        emit LogEvent(msg.sender, lastEventBlock);
        lastEventBlock = block.number;
    }
}

On the client side, when lastEvent != 0 then jump to that block and "listen" for events of interest. This event will include a pointer to the block that contains the previous event. Rinse and repeat. When you hit 0 there are no previous events.

The technique is simple, and you could use separate pointers for separate logs, separate users, etc., as needed so clients can find the most important recent information quickly.

This allows a client to start with the most recent and crawl back as far as interested. The pattern can be used in stateless designs where the data is in the event log and possibly only the most recent entry is important.

Just in case it isn't clear, watch() doesn't go in reverse. To use this, you would watch() one block only (which would be fast), find what you need, and then watch() the next single block of interest, following the reverse order clues you laid down for yourself.

Related Topic