Разработка кастомного уровня Data Availability
Data Availability — это проблема, которую большинство разработчиков блокчейна недооценивают до тех пор, пока не сталкиваются с ней в production. Классическая формулировка: как легкий клиент может убедиться, что блок-продюсер действительно опубликовал все данные транзакций, не скачивая блок целиком? Если данные не доступны — невозможно верифицировать fraud proofs, невозможно восстановить state, невозможно построить rollup поверх этого слоя. DA — это фундамент доверия ко всей системе.
Зачем кастомный DA-слой
Существующие решения — Celestia, EigenDA, Avail, Ethereum danksharding (EIP-4844 + full danksharding) — покрывают большинство потребностей. Кастомный DA нужен в специфических случаях:
- Требования к производительности выходят за пределы публичных DA-сетей (Celestia: ~8MB/block, EigenDA: до нескольких MB/s per operator set)
- Приватные данные: публичные DA-слои публикуют данные открыто. Для enterprise/permissioned приложений нужна другая модель
- Специализированная схема кодирования: под конкретные типы данных (например, ZK-proof батчи со специфической структурой)
- Суверенность: нежелание зависеть от третьей сети и её токеномики
- Специфические гарантии finality: отличные от существующих решений
Теоретическая основа: Data Availability Sampling
Ключевая инновация современных DA-слоев — DAS (Data Availability Sampling). Вместо того чтобы скачивать весь блок, легкий клиент случайно семплирует небольшое количество чанков. Если достаточное число семплов доступно — с высокой вероятностью весь блок доступен.
Это работает благодаря erasure coding: данные блока кодируются с избыточностью 2x (например, 1MB данных → 2MB закодированных чанков). Если хотя бы 50% чанков доступны, исходные данные можно восстановить. Поэтому для того чтобы скрыть данные, блок-продюсер должен скрыть >50% всех чанков — а это легко обнаруживается при DAS.
Reed-Solomon vs. 2D erasure coding
Простой Reed-Solomon работает в одном измерении. Celestia использует 2D Reed-Solomon: данные организуются в матрицу и кодируются по строкам и столбцам. Это позволяет локализовать fraud proofs — доказать что конкретная строка или столбец некорректно закодированы, не скачивая весь блок.
Исходные данные (k×k матрица):
[d00 d01 ... d0k]
[d10 d11 ... d1k]
...
После 2D RS кодирования (2k×2k):
[d00 d01 ... d0k | p00 p01 ... p0k] ← parity по строкам
[d10 d11 ... d1k | p10 p11 ... p1k]
...
[p00 p01 ... p0k | pp00 pp01 ... ] ← parity по столбцам + диагонали
KZG Polynomial Commitments
Для эффективных DA-proofs необходим механизм, позволяющий доказать что конкретный чанк является частью конкретного блока без скачивания всего блока. KZG commitments (Kate-Zaverucha-Goldberg, EIP-4844) — оптимальное решение:
- Данные блока интерпретируются как полином
p(x)степениn-1 - Commitment
C = [p(τ)]₁— точка на BLS12-381 elliptic curve (48 байт) - Proof для конкретного значения
p(z) = y— ещё одна точка (48 байт) - Верификация: pairing check
e(C - [y]₁, [1]₂) = e(π, [τ - z]₂)
Размер proof — константный (48 байт) независимо от размера данных. Верификация — одна pairing операция (~2ms на современном железе).
Для кастомного DA-слоя реализация KZG commitments выглядит так:
use ark_bls12_381::{Bls12_381, Fr, G1Projective};
use ark_poly::{univariate::DensePolynomial, UVPolynomial};
use ark_poly_commit::kzg10::{KZG10, Powers, VerifierKey};
pub struct DALayer {
powers: Powers<Bls12_381>,
vk: VerifierKey<Bls12_381>,
}
impl DALayer {
pub fn commit_blob(&self, data: &[u8]) -> (Commitment, Vec<Fr>) {
let scalars = bytes_to_field_elements(data);
let poly = DensePolynomial::from_coefficients_vec(scalars.clone());
let (commitment, _) = KZG10::commit(&self.powers, &poly, None, None).unwrap();
(commitment, scalars)
}
pub fn generate_proof(&self, data: &[Fr], index: usize) -> Proof {
let poly = DensePolynomial::from_coefficients_vec(data.to_vec());
let point = Fr::from(index as u64);
KZG10::open(&self.powers, &poly, point, &Fr::zero()).unwrap()
}
pub fn verify_chunk(
&self,
commitment: &Commitment,
index: usize,
value: Fr,
proof: &Proof,
) -> bool {
let point = Fr::from(index as u64);
KZG10::check(&self.vk, commitment, point, value, proof).unwrap()
}
}
Сетевой уровень: P2P и хранение
Распределение данных
Данные должны быть распределены между достаточным числом нод так, чтобы отказ любого меньшинства нод не привёл к недоступности данных. Типичная схема:
- Producer (sequencer/block producer) разрезает блок на чанки, создаёт commitments
- Чанки распределяются по DA nodes через P2P gossip (libp2p рекомендуется)
- Каждый DA node хранит подмножество чанков и attestует их доступность
- Light clients делают DAS, выбирая случайные ноды для семплирования
Аттестации доступности
DA nodes публикуют signed аттестации: "я храню чанки X для блока Y". Эти аттестации агрегируются для формирования Data Availability Certificate (DAC). В приватных DA-слоях (enterprise) достаточно порогового количества подписей от доверенных комитетов. В публичных — нужен более сложный механизм.
pub struct DAAttestation {
pub block_hash: [u8; 32],
pub block_height: u64,
pub chunk_indices: Vec<u32>, // какие чанки хранит эта нода
pub timestamp: u64,
pub signature: BLSSignature, // BLS для агрегации
}
pub struct DAC {
pub block_hash: [u8; 32],
pub aggregate_signature: BLSAggregatedSignature,
pub signer_bitfield: BitVec, // какие члены комитета подписали
pub threshold_met: bool,
}
BLS-подписи выбраны не случайно: BLS поддерживает signature aggregation — n подписей агрегируются в одну, что критически важно для эффективности при больших комитетах.
Интеграция с rollup
DA-слой — это не standalone продукт, он всегда интегрируется с rollup или другой системой. Rollup sequencer должен:
- После формирования батча — отправить данные в DA-слой
- Дождаться DAC (Data Availability Certificate)
- Включить хэш DAC в L1 state root commitment
L1 контракт rollup верифицирует наличие корректного DAC перед принятием state root:
contract RollupWithCustomDA {
IDALayerVerifier public daVerifier;
struct BatchCommitment {
bytes32 stateRoot;
bytes32 dataCommitment; // KZG commitment на данные батча
bytes dacCertificate; // агрегированная BLS подпись комитета
}
function submitBatch(BatchCommitment calldata batch) external {
// верифицируем что DA комитет аттестовал доступность данных
require(
daVerifier.verifyDAC(
batch.dataCommitment,
batch.dacCertificate,
REQUIRED_THRESHOLD // например 2/3 комитета
),
"DA not certified"
);
// принимаем state root только если данные доступны
pendingRoots[currentBatchId] = batch.stateRoot;
emit BatchSubmitted(currentBatchId, batch.stateRoot, batch.dataCommitment);
currentBatchId++;
}
}
Сравнение подходов
| Подход | Throughput | Гарантии безопасности | Сложность |
|---|---|---|---|
| Ethereum calldata | ~375KB/block | Full Ethereum security | Низкая |
| EIP-4844 blobs | ~768KB/block | Full Ethereum security | Низкая |
| Celestia | ~8MB/block | DAS + fraud proofs | Средняя |
| EigenDA | До десятков MB/s | EigenLayer restaking | Средняя |
| Custom DA (committee) | Зависит от конфигурации | Доверенный комитет | Высокая |
| Custom DA (DAS+KZG) | Масштабируется | Cryptographic guarantees | Очень высокая |
Кастомный DA с DAS и KZG — максимально сложное решение. Для большинства проектов Celestia или EigenDA обеспечат необходимую пропускную способность с проверенной безопасностью. Кастомный DA оправдан когда специфические требования к приватности, производительности или суверенности не могут быть удовлетворены существующими решениями.







