Skip to main content

Parallel funnels

Merging state from multiple chains

When running a game, there is always a (single) primary EVM network which is synchronized by the block funnel. This is the network that has the Paima L2 contract deployed, and it's the one that provides the inputs for the game. In addition to this, there are funnel types that are used for synchronizing additional networks in parallel:

Conceptually these funnels are independent from each other, so there can be as many as needed, but they all depend on the block funnel.

parallel evmfunnelcarpfunnelminafunnel

The reason for this is that the engine makes it appear as if events happened in a single network by merging the events from the parallel chains into the blocks from the block funnel. This provides a single interface where adding an extension in an extra network is no different than adding an extension in a single network.

Determinism

Merging of data is done in a way to ensure that the state transition is deterministic.

For example,
if on the main chain we have blocks with timestamps 3 and 5,
and on the parallel chain we have blocks with timestamps 4 and 5,
the events in the parallel chain will look as if they happened in the main chain's block that has timestamp 5. And this is always the same regardless of the time of the sync, since the chains are always processed in tandem. Note that it is possible that there are no blocks to merge at a certain point (if no events we're monitoring occur in these blocks).

Visually:

Order of state transition function

As a consequence of the merging procedure, it's possible for the state transition function to get called in the same block by the effect of multiple primitives from different chains. In this case the order is deterministic and is given by the following rules:

  1. The events of the main chain funnel will be first.
  2. The order of events from parallel funnels depend on the order the networks are defined in the configuration file.
  3. Events coming from the same funnel will be contiguous.
  4. When merging multiple blocks from the same network into a single main block, the order is that of the block number.
  5. For events coming from the same funnel and in the same block, the order is given by the order of the extensions configuration file.

It's worth noting that as a consequence of the third item, it's possible for the order to not coincide with the order of the timestamps across different parallel chains.

To illustrate this, let's see follow the order of events for block 2 (main chain) in the following scenario:

Parallel chain 2Parallel chain 125113242212123541stf order

The final order of STF calls would be:

  1. Transactions in main chain, block 2.
  2. Transactions in parallel chain 1, block 1.
  3. Transactions in parallel chain 1, block 2.
  4. Transactions in parallel chain 2, block 1.
  5. Transactions in parallel chain 2, block 2.

In this case the state transition function will see some events with an original timestamp of 4 (in parallel chain 1) before the events with an original timestamp of 3 (in parallel chain 2), but the timestamp it's only used to deterministically decide which blocks to merge, and not to sort the transactions.

Finalizing blocks

We cannot go back in time to add information to old blocks that have already been parsed by the game's state machine. Therefore, it means that a block can only be considered finalized and ready to be given to the state machine once we're certain we have all the information for all chains being monitored. The only way for us to know that we have all the information for a block is if the latest confirmed timestamp of all networks we're monitoring is more recent or equal ( depending on whether the network can have multiple blocks with the same timestamp), than the timestamp of the main chain block timestamp we're looking at.

Note: this behavior helps protect apps built with Paima from getting in a bad state. If an RPC you're connecting to for a network gets stuck, it should stop block production for the entire application. Otherwise, it can break determinism because your node (that is missing events from the parallel chain RPC that is stuck) will see a different block history from somebody else running a node connecting to a properly functioning RPC for that parallel chain.

Delayed state

When getting information from a chain with probabilistic finality, it's necessary to have a setup that can avoid rollbacks, since there is no way in for the engine to handle that otherwise. Because of this, the parallel funnels have the ability to run in a delayed state. This involves two different variables:

confirmationDepth: number
delay: number

Confirmation depth

confirmationDepth is a parameter that is based on blocks. A confirmationDepth of 0 would be proper for a network with instant finality. Increasing the argument depends on the underlying network, and the amount of confidence desired.

Delay

If the delay setting is used, the parallel chain is delayed by this amount of seconds. This is equivalent to either subtracting the delay for the timestamps of the main network before merging (without changing the result), or to adding the delay to the parallel network timestamps. In that case the merge process changes in the following way (with a delay of 60 seconds).

6163Delay shifted 6567- 60 sParallel chainMain chainReal time

This has the effect that no events newer than current time - delay will be processed by the funnel (relative to the main chain's latest synced block).

Guidelines

In general both of these are needed for the funnel to properly function, and a good guideline is to set delay to a value greater or equal than confirmationDepth * block_production_speed. Having a greater value adds latency for the engine to react to events, but decreases the chances of the funnel needing to wait for block production in order to get a confirmed block (see finalizing blocks). This is particularly problematic because a single network may stall all of the other ones.

Considering the above, setting up a confirmationDepth without delay it's not really advised, since the funnel will stall with every block. On the other hand, it's possible to set a delay without a confirmationDepth, but if the underlying network stops producing blocks for some reason, it may lead to eventually finalizing a block that it's not stable, unless the underlying network has some extra guarantees based exclusively on timings.

Performance implications

Leveraging parallel funnels has multiple performance implications. In some cases the performance impact can be mitigated by emulated blocks, but we will cover all performance implications in this section

Networking overhead

From Paima's perspective, a block can only be considered complete and ready to send to the game's state machine after it's fetched all necessary information. That means that fetching blocks is only as fast as your slowest connection when fetching the latest block.

If you're connecting to a public node for a network and receiving data from that node is slow (ex: geographically far from you, running on cheap hardware, etc.), the delay in fetching data from that node will delay the final block creation process and cause slowdowns in your game node.