Global counter

Let's define an important variable and how to use it

Why it exists

If x and y are two distinct executions that happened on the same blockchain, we want to be able to say that either x occurred before y or that y occurred before x. We want that for all x and y, i.e. a strong order, sometimes called a strict total order.

Note: You could use block_number, but that will fail if you have multiple records within a given block. You could use (block_number, transaction_index), but that will fail if you have multiple records within the same transaction. Go down this rabbit hole far enough, and you'll rebuild your own version of our global counter!

When should you use it

Two main use cases:

  1. Sort a table with execution records by their order: order by global_counter (descending, if you want the most recent first).
  2. Get state at a particular point in execution history (including current): find the execution records with the highest global counter up to that point in execution history.

How to use it

Include global_counter as a uint256 in your schemas. In your EVM lambdas, populate it with simGlobalCounter(). Note that the global counter is stable within each instance of a hook execution.

Apache Pinot still has limited math support for big_decimal. We're told this will be improved soon. In the meantime, if performing math on global_counter is failing for you, you may consider casting it to a double first (cast(global_counter as double), but note that this reduces precision and therefore can yield inaccurate event ordering.

What it is

You probably shouldn't read this, but if you want the gory details... The global_counter is 192-bits long and consists of:

  1. version (8 bits): the version of the global counter
  2. block_number (32 bits)
  3. reorg_incarnation (24 bits): Indicates how many re-orgs have happened already.
  4. block_epilogue (8 bits): Consensus events that happen after the last transaction of the block. These might be present even if no transactions were included in a block. It is deliberately stored in bits which are higher than the txn_index to denote that events that have these bits set happened after any transaction in that block.
  5. txn_index (24 bits)
  6. block_prologue (8 bits): Consensus events that happen before the first transaction of the block
  7. txn_state (8 bits): The phase we are in, relative to the transaction itself: before, within, epilogue, after
  8. txn_state_counter (8 bits): What event are we in, within the txn_state, i.e, bytecode execution
  9. shadow_pc (40 bits): Only relevant if we are in bytecode execution phase. Represents the number of opcodes executed
  10. evt_counter (16 bits) Extra event counter to distinguish between multiple events that we may emit as a result of the execution of the same opcode. One extreme example of it would be to emit an event every time a stack item pops off. This can definitely happen multiple times when executing the same opcode, although we've never actually came up with a good use case for this :).
  11. padding (16 bits) Zeroes padding the counter to 192 bits. Our big-int library likes numbers that divide by 32.