Skip to main content

EVM Parallel funnel

This funnel processes the same primitives as the block funnel, but over extra companion networks. While there can only be a single instance of the block funnel, which is associated to the instance of the Paima L2 contract, an instance of this funnel gets instantiated for every extra network.

Support for parallel chains allows the game node to update based on events in different networks by actively monitoring them (instead of relying on a bridge). For example, this allows you to have your game settle user transactions to a cheap & faster chain ("main chain") while having NFTs for the game live on a separate layer that has higher liquidity and more dApps to compose with.

Conceptually

This funnel has the following steps:

  1. Fetch blocks & timestamps from the underlying funnel (the main chain you are syncing).
  2. Fetch the latest block from the parallel funnel.
  3. Fetch any dynamic primitive that needs to be registered
  4. Use eth_getLogs to get the primitives configured to this funnel in the block range.
  5. Merge the primitives fetched from the parallel funnel with the block from the underlying funnel.

Here is a visual representation of the flow:

Connecting to networks that produce blocks on-demand

Some networks only produce blocks if there are actually transactions on the network (otherwise, nothing happens) such as Arbitrum. This can be problematic for parallel funnels because it makes it hard to differentiate between a block that hasn't been fetched yet and the case where there simply is no block for that time interval.

The emulated block funnel can help mitigate this as it forces an empty block to be made if there hasn't been any block in a while

Connecting to networks with slow blocks

Many networks such as Ethereum L1 have relatively slow block times. This can be mitigated by relying on a delayed state.

Finding parallel chain blocks

We want to find blocks in a parallel chain that matches a timestamp range in the main chain. However, unfortunately there is no way to use eth_getLogs with a timestamp range, so the funnel actually has to find out the block height range.

We achieve this in 2 steps:

When booting up Paima Engine, there are 2 blocks we are interested in knowing:

  1. When the presync ends (the last block in the parallel chain that appears before the main chain starting block height)
  2. The earliest parallel chain block that whose events might be included in the next main chain block we have to sync

Syncing blocks one-by-one until we find these points would be too slow. To solve this, we instead use binary search to find and cache these blocks on boot.

Fig: A binary search looking for the block with a timestamp of 4

2) Fetching blocks in chunks

During presync, we fetch parallel chain events in batches defined by presyncStepSize.

After presync, we fetch parallel chain blocks in chunks of funnelBlockGroupSize. We keep doing this until we have a block with a timestamp greater than the latest one we've seen from the main chain. This allows us to know how to map the parallel chain block number of any event we find to the right block in the main chain.