Skip to content

op-batcher: Assess da-type choice when submitting a channel #11609

@sebastianst

Description

@sebastianst

Context

Since #11219 the batcher can dynamically switch between calldata and blobs, depending on the economically best option at the time of starting a new channel. This works reasonably well for high-throughput chains, where the delay between starting and submitting a channel is usually only a few minutes. But it is still not optimal, as the most economical option might have changed since building up the channel. Furthermore, for low-throughput chains, this behavior is useless, as a da-type decision delay of multiple hours is ineffective. And lastly, the decision is currently made under the assumption that the channel will be full to its size limit. But the channel may not be full, e.g. using less frames (=blobs) with a half-empty last frame because of a short max-channel-duration setting that closed it (especially relevant on testnets).

Change Description

To address these shortcomings, the batcher should perform a second/the check at the time that it would start submitting a channel (i.e., when starting to post the first batcher transaction for that channel (almost always a channel fits into a single batcher transaction, unless the first block added to it doesn't fit into the target-num-frames frames)), and possibly rebuild the channel using different da-type settings.

The goal of this improvement is to always select the best da-type for a channel at the time of tx submission, and make the dynamic da-type selection feature useable for low-throughput chains.

Implementation Hints

It probably makes most sense to move the channel config retrieval via the ChannelConfigProvider from the point at which a new channel is created (

cfg := s.cfgProvider.ChannelConfig()
) to the point at which the first frame of any channel is submitted (places where we return non-empty txData inside channelManager.TxData()), and then reuse that config for the following channel. This has two advantages.

  1. This adds the check for the correct channel config at the point when submission happens. Most of the time this check will confirm the current configuration.
  2. The channel config of the previous channel is the best estimator for the correct channel config of the following channel, when the exact data content isn't known yet.

Note that a special case is a fresh batcher restart, at which point an estimated configuration has to be retrieved once before the first channel. But this could happen in the batcher's initialization path.

Channel Rebuilding

If the check doesn't confirm the channel config that's currently in use, the channel should be rebuilt using the new config. One good option to accomplish this may be to reinsert the blocks into the L2 blocks queue inside the channelManager, so that the existing code path is then used to build the channel with the new channel config. This has the advantage that e.g. if switching from calldata to blobs, the channel would suddenly have more space, so it wouldn't be immediately submitted and just continue to fill up regularly.

ChannelConfigProvider

It's important to note that the current ChannelConfigProvider has a single method to retrieve the next estimated channel config, and the implementation of the dynamic provider always assumes full channels. This is not optimal for the proposed change, as we will know the exact channel data at the point of submission, so we can make an exact fee calculation. To enable this, I suggest we add a second method to the ChannelConfigProvider to make its interface look similar to the following:

type ChannelConfigProvider interface {
	ChannelConfigFull() ChannelConfig // note: renamed old method that estimates full channels, would be used only at startup
	ChannelConfig(data []byte) ChannelConfig // new method, takes exact data
}

The new method can take as a parameter the exact data and use core.IntrinsicGas to exactly calculate the calldata gas in the dynamic provider, instead of this manual calculation:

// It would be nicer to use core.IntrinsicGas, but we don't have the actual data at hand
calldataBytes := dec.calldataConfig.MaxFrameSize + 1 // + 1 version byte
calldataGas := big.NewInt(int64(calldataBytes*randomByteCalldataGas + params.TxGas))

Otherwise, the existing cost comparison code at DynamicEthChannelConfig.ChannelConfig() should be as much reused between the two new methods as possible.

Channel Config Immutability

One important rule that should be observed is to not change the DA-type and channel configuration once the first frame of a channel started being submitted. This is important a) because otherwise we might end up trying to replace a transaction with a different tx type, which is not allowed and would get the batcher into a blocked tx recovery mode and b) in view of the upcoming Holocene features regarding Strict Batch Ordering would invalidate future batcher transactions that might already be in the tx-pool because of parallel transaction sending.

Hardening the batcher's behavior during restarts should be done as a separate task, possibly introducing some form of persistence of in-flight transactions.

Metadata

Metadata

Assignees

Labels

A-op-batcherArea: op-batcherT-protocolTeam: changes to node components (op-node, op-reth, etc.) implemented by go/rust/etc. devs

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions