Разработка системы на модульном стеке (execution + DA + settlement)
Монолитный блокчейн делает всё сам: исполняет транзакции, обеспечивает доступность данных, финализирует состояние. Модульный подход разбивает эти функции между специализированными слоями. Результат — экосистема, где можно выбирать компоненты как модули: Celestia для DA, Ethereum для settlement, OP Stack или Arbitrum Orbit для execution. Это не теория — так построены десятки mainnet L2 и L3 в 2024–2025 году.
Типичный запрос: «хотим свой appchain с низкими комиссиями и кастомной логикой, но с безопасностью Ethereum». Это правильно сформулированная задача, и модульный стек — правильный ответ на неё.
Слои и их роли
Execution Layer
Исполняет транзакции, поддерживает состояние. Это ваш chain — со своими правилами, gas token, precompiles.
OP Stack (Optimism, Base, Zora) — самый зрелый фреймворк для Optimistic Rollup-based L2/L3. EVM-эквивалентность. op-geth + op-node + op-batcher + op-proposer.
Arbitrum Orbit — Arbitrum-based L2/L3. Поддерживает Stylus (WASM смарт-контракты на Rust/C++). Более гибкая кастомизация gas, permission models.
Polygon CDK — ZK-based chain development kit. zkEVM под капотом. Более сложно в эксплуатации, но ZK finality вместо fraud window.
Sovereign rollup через Rollkit — execution layer с любым execution environment, settlement в любой цепи (или без settlement вообще). Максимальная гибкость, минимальная зрелость.
Data Availability Layer
Блоки должны быть доступны для скачивания — иначе fraud proofs и state reconstruction невозможны. DA layer хранит данные транзакций (calldata или blobs).
Ethereum L1 (EIP-4844 blobs) — максимальная security, наивысшая стоимость. После EIP-4844: ~3–6 blobs per block, каждый blob ~128KB, стоимость blob gas отдельна от execution gas. Blobs удаляются через ~18 дней, но commitment (KZG) остаётся навсегда.
Celestia — специализированный DA layer. Data availability sampling (DAS): light nodes проверяют доступность через random sampling, не скачивая весь блок. Стоимость на порядки ниже Ethereum blobs при сопоставимых security guarantees для большинства use cases.
EigenDA — DA layer поверх Ethereum через EigenLayer restaking. Экономическая безопасность от рестейкнутого ETH. Значительно выше throughput чем Ethereum L1 при более высоких security гарантиях чем Celestia (на сегодня).
Avail — DA layer с data availability sampling, forkless upgrades. Хорошая альтернатива Celestia.
Settlement Layer
Финализация: определяет, что является каноническим состоянием rollup. Обрабатывает withdrawals, resolves disputes.
Для большинства проектов — Ethereum mainnet через L1 bridge contract. Альтернатива для L3 — использовать L2 как settlement layer (например, Arbitrum One как settlement для Orbit chain).
Практическая сборка: OP Stack + Celestia + Ethereum
Покажу конкретную конфигурацию, которую используют в production.
Компоненты
[Users/Apps]
↓
[op-geth (execution)] ← кастомный EVM, ваши precompiles
↓
[op-node (rollup node)] ← derive chain state, communicate with L1
↓
[op-batcher] → [Celestia] ← публикует batch данных в Celestia
↓
[op-proposer] → [L1 Settlement Contract] ← публикует state roots на Ethereum
Настройка Celestia DA
// Celestia client для отправки blobs
import (
"github.com/celestiaorg/celestia-node/api/rpc/client"
"github.com/celestiaorg/celestia-openrpc/types/blob"
)
type CelestiaDA struct {
client *client.Client
namespace blob.Namespace
}
func (c *CelestiaDA) Submit(ctx context.Context, data []byte) (uint64, error) {
b, err := blob.NewBlob(0, c.namespace, data)
if err != nil {
return 0, fmt.Errorf("create blob: %w", err)
}
height, err := c.client.Blob.Submit(ctx, []*blob.Blob{b}, blob.DefaultGasPrice())
if err != nil {
return 0, fmt.Errorf("submit blob: %w", err)
}
return height, nil
}
func (c *CelestiaDA) Retrieve(ctx context.Context, height uint64) ([]byte, error) {
blobs, err := c.client.Blob.GetAll(ctx, height, []blob.Namespace{c.namespace})
if err != nil {
return nil, err
}
if len(blobs) == 0 {
return nil, ErrBlobNotFound
}
return blobs[0].Data, nil
}
Namespace — 29-байтовый идентификатор вашего rollup в Celestia. Нужно зарезервировать уникальный namespace до запуска.
Конфигурация op-batcher для Celestia
OP Stack batcher нативно не поддерживает Celestia — нужен кастомный DA provider через AltDA (Alternative DA) интерфейс:
// Реализация AltDA provider для Celestia
type CelestiaAltDA struct {
da *CelestiaDA
}
func (c *CelestiaAltDA) GetInput(ctx context.Context, commitment []byte) ([]byte, error) {
// Decode commitment → Celestia block height + namespace
height, err := decodeCommitment(commitment)
if err != nil {
return nil, err
}
return c.da.Retrieve(ctx, height)
}
func (c *CelestiaAltDA) SetInput(ctx context.Context, data []byte) ([]byte, error) {
height, err := c.da.Submit(ctx, data)
if err != nil {
return nil, err
}
return encodeCommitment(height), nil
}
В конфигурации op-batcher:
[da]
type = "celestia"
rpc = "http://celestia-light-node:26658"
auth_token = "${CELESTIA_AUTH_TOKEN}"
namespace = "0x0000000000000000000000000000000000yournamespace"
L1 Settlement контракты
На Ethereum деплоятся два контракта:
OptimismPortal — входная/выходная точка для cross-domain сообщений и withdrawals. Handles challenge window для Optimistic.
L2OutputOracle — хранит state roots предложенные proposer'ом. Это то, от чего зависит finality withdrawals.
// Упрощённая схема interaction
contract L2OutputOracle {
struct OutputProposal {
bytes32 outputRoot; // keccak256(state_root, msg_passer_storage_root, latest_block_hash)
uint128 timestamp;
uint128 l2BlockNumber;
}
OutputProposal[] public l2Outputs;
address public proposer;
uint256 public constant FINALIZATION_PERIOD = 7 days;
function proposeL2Output(
bytes32 _outputRoot,
uint256 _l2BlockNumber,
bytes32 _l1BlockHash,
uint256 _l1BlockNumber
) external payable {
require(msg.sender == proposer, "Not proposer");
// Верификация что l1BlockHash соответствует _l1BlockNumber
require(blockhash(_l1BlockNumber) == _l1BlockHash, "Bad L1 block");
l2Outputs.push(OutputProposal({
outputRoot: _outputRoot,
timestamp: uint128(block.timestamp),
l2BlockNumber: uint128(_l2BlockNumber)
}));
}
}
Кастомизация execution layer
Custom precompiles
Precompiles — предкомпилированные контракты по фиксированным адресам с нативной реализацией. Например, добавить BLS12-381 операции или кастомный хэш-алгоритм:
// В op-geth: добавление custom precompile
var CustomPrecompiles = map[common.Address]vm.PrecompiledContract{
common.HexToAddress("0x0000000000000000000000000000000000000100"): &blsG1Add{},
common.HexToAddress("0x0000000000000000000000000000000000000101"): &customHashFunction{},
}
type blsG1Add struct{}
func (c *blsG1Add) RequiredGas(input []byte) uint64 {
return 500 // фиксированная стоимость
}
func (c *blsG1Add) Run(input []byte) ([]byte, error) {
if len(input) != 128 {
return nil, errors.New("invalid input length")
}
// BLS G1 point addition
p1 := new(bls12381.G1Affine)
p2 := new(bls12381.G1Affine)
p1.Unmarshal(input[:64])
p2.Unmarshal(input[64:])
result := new(bls12381.G1Affine).Add(p1, p2)
return result.Marshal(), nil
}
Gas token кастомизация
OP Stack поддерживает Custom Gas Token — native gas token, отличный от ETH. Это позволяет использовать ваш ERC-20 токен как gas.
Ограничение: custom gas token должен быть задеплоен на L1, иметь стандартный ERC-20 интерфейс, и не иметь transfer fees (rebasing/fee-on-transfer токены не поддерживаются).
Fee структура и sequencer revenue
User Transaction Fee = (base_fee + priority_fee) × gas_used [L2 execution cost]
+ L1 data fee [cost of DA]
Sequencer Revenue = collected fees - DA costs - L1 costs
При использовании Celestia вместо Ethereum для DA, L1 data fee снижается на 90–95% для большинства транзакций. Это главный экономический аргумент.
Мониторинг модульного стека
Мониторить нужно все три слоя независимо:
# Ключевые метрики
execution_layer:
- l2_block_time_seconds # должен быть стабильным
- sequencer_queue_depth # нагрузка на sequencer
- tx_pool_size # размер mempool
da_layer:
- celestia_blob_submission_latency_ms
- celestia_blob_submission_cost_utia # стоимость в TIA
- da_submission_failures_total
settlement_layer:
- l1_output_proposal_delay_blocks # proposer не отстаёт?
- l1_finalization_pending_count # withdrawals ожидающие финализации
- l1_gas_price_gwei # стоимость proposer transactions
Alerting: если DA submission начинает отставать от block production — это блокирующая проблема. Без DA блоки непроверяемы, а challengers не могут получить данные для fraud proof.
Декомпозиция по срокам
| Фаза | Содержание | Срок |
|---|---|---|
| Design | Выбор стека, namespace, токеномика, bridge design | 2–3 нед |
| Core setup | op-stack deployment, L1 contracts, genesis | 3–4 нед |
| DA integration | Celestia/EigenDA connector, batcher config | 2–3 нед |
| Testnet | Публичный тестнет, bridge testing, stress test | 3–4 нед |
| Security | Аудит bridge контрактов, fault proof testing | 4–6 нед |
| Mainnet | Деплой, sequencer ops, monitoring | 2–3 нед |
Критический путь — аудит bridge контрактов. Bridge — это где живут реальные деньги пользователей, и именно здесь большинство L2 находило критические уязвимости. Экономить на аудите bridge нельзя.
Итого: 16–23 недели от старта до mainnet. Команда: 2–3 backend инженера с опытом Go, 1 Solidity разработчик, DevOps/инфраструктурный инженер.







