Lifecycle Phases
Secure interaction between a rollup and EigenDA is composed of three distinct system flows:
- Dispersal: Submitting payload data to the DA network
- Retrieval: Fetching payload data from the DA network
- Verification: Ensuring the integrity and quorum-based certification of data availability. Where and how verification is performed is often contingent on how an integration is implemented; e.g:
- Pessimistic Verification where a
DACert
is checked as pre-inclusion check for a sequencer inbox - Optimistic Verification where a
DACert
is only verified in a worst-case challenge
- Pessimistic Verification where a
Secure Dispersal
Diagram
System Flow
-
EigenDA Client takes a raw payload bytes and converts it into a blob.
-
Using
latest_block_number
(lbn) number fetched from ETH RPC node, EigenDA Client calls the router to get theEigenDACertVerifier
contract address most likely (if usingEigenDACertVerifierRouter
) to be committed to by thereference_block_number
(rbn) returned by the EigenDA disperser. -
Using the
verifier
, EigenDA Client fetches therequired_quorums
and embeds them into theBlobHeader
as part of the disperser request. -
The EigenDA Client submits the payload blob request to the EigenDA disperser via
DisperseBlob
endpoint and polls for aBlobStatusReply
(BSR). -
While querying the disperser's
GetBlobStatus
endpoint, EigenDA Client periodically checks against the confirmation threshold as it’s updated in real-time by the disperser using the rbn returned in theBlobStatusReply
for fetching thresholds. (ref) -
Once confirmation thresholds are fulfilled, EigenDA Client calls the
verifier
'scertVersion()
method to get thecert_version
and casts theDACert
into a structured ABI binding type using thecert_version
to dictate which certificate representation to use. (ref) -
EigenDA Client then passes ABI encoded cert bytes via a call to the
verifier
'scheckDACert
function which performs onchain cert verification logic and returns a uintverification_status_code
-
Using the
verification_status_code
, EigenDA Client determines whether to return the certificate (CertV2Lib.StatusCode.SUCCESS
) to the Rollup Batcher or retry a subsequent dispersal attempt
Payload to Blob Encoding
This phase occurs inside the eigenda-proxy, because the proxy acts as the “bridge” between the Rollup Domain and Data Availability Domain (see lifecycle diagram).
A payload
consists of an arbitrary byte array. The DisperseBlob endpoint accepts a blob
, which needs to be an encoded bn254 field element array.
Disperser polling
The DisperseBlob
method takes a blob
and blob_header
as input. Under the hood, the disperser performs the following steps:
- Batching: The blob is aggregated into a Merkle tree along with other blobs.
- Reed-Solomon Encoding: The blob is erasure-coded into chunks for fault tolerance.
- Dispersal to Validators: The chunks are distributed to EigenDA validator nodes based on the required quorum settings.
- Signature Collection: The disperser collects BLS signatures from participating validators.
- Status Reporting: A
BlobStatusReply
is returned to the client to reflect progress or terminal status.
The disperser batches blobs for a few seconds before dispersing them to nodes, so an entire dispersal process can exceed 10 seconds. For this reason, the API has been designed asynchronously with 2 relevant methods.
// Async call which queues up the blob for processing and immediately returns.
rpc DisperseBlob(DisperseBlobRequest) returns (DisperseBlobReply) {}
// Polled for the blob status updates, until a terminal status is received
rpc GetBlobStatus(BlobStatusRequest) returns (BlobStatusReply) {}
// Intermediate states: QUEUED, ENCODED, GATHERING_SIGNATURES
// Terminal states: UNKNOWN, COMPLETE, FAILED
enum BlobStatus {
UNKNOWN = 0; // functionally equivalent to FAILED but for unknown unknown bugs
QUEUED = 1; // Initial state after a DisperseBlob call returns
ENCODED = 2; // Reed-Solomon encoded into chunks ready to be dispersed to DA Nodes
GATHERING_SIGNATURES = 3; // blob chunks are actively being transmitted to validators
COMPLETE = 4; // blob has been dispersed and attested by DA nodes
FAILED = 5;
}
After a successful DisperseBlob RPC call, the disperser returns BlobStatus.QUEUED
. To retrieve a valid BlobStatusResponse
, the GetBlobStatus RPC endpoint should be polled until a terminal status is reached.
If BlobStatus.GATHERING_SIGNATURES
is returned, the signed_batch
and blob_verification_info
fields will be present in the BlobStatusReply
. These can be used to construct a DACert
, which may be verified immediately against the configured threshold parameters stored in the EigenDACertVerifier
contract. If the verification passes, the certificate can be accepted early. If verification fails, polling should continue.
Once BlobStatus.COMPLETE
is returned, it indicates that the disperser has stopped collecting additional signatures, typically due to reaching a timeout or encountering an issue. While the signed_batch
and blob_verification_info
fields will be populated and can be used to construct a DACert
, the DACert
could still be invalid if an insufficient amount of signatures were collected in-regards to the threshold parameters.
Any other terminal status indicates failure, and a new blob dispersal will need to be made.
Failover to Native Rollup DA
Proxy can be configured to retry BlobStatus.UNKNOWN
, BlobStatus.FAILED
, & BlobStatus.COMPLETE
(if threshold check failed) dispersal n
times, after which it returns to the rollup a 503
HTTP status code which rollup batchers can use to failover to EthDA or native rollup DA offerings (e.g, arbitrum anytrust). See here for more info on the OP implementation and here for Arbitrum.
BlobStatusReply → Cert
Implementation Note: While not mandated by the EigenDA spec, clients must currently reconstruct the
DACert
from fields in theBlobStatusReply
, as the disperser does not return a cert directly. The transformation is visualized in the Ultra High Res Diagram.
In the updated implementation, a CertBuilder
constructs the DA Cert through direct communication with the OperatorStateRetriever
contract, which provides the necessary information about operator stake states. This approach ensures accurate on-chain data for certificate verification. The following pseudocode demonstrates this process:
class DACert:
batch_header: any
blob_verification_proof: any
nonsigner_stake_sigs: any
cert_version: uint8
signedQuorumNumbers: bytes
def get_da_cert(blob_header_hash, operator_state_retriever, cert_version_uint8) -> DACert:
"""
DA Cert construction pseudocode with OperatorStateRetriever
@param blob_header_hash: key used for referencing blob status from disperser
@param operator_state_retriever: ABI contract binding for retrieving operator state data
@param cert_version_uint8: uint8 version of the certificate format to use
@return DACert: EigenDA certificate used by rollup
"""
# Call the disperser for the info needed to construct the cert
blob_status_reply = disperser_client.get_blob_status(blob_header_hash)
# Validate the blob_header received, since it uniquely identifies
# an EigenDA dispersal.
blob_header_hash_from_reply = blob_status_reply.blob_verification_info.blob_certificate.blob_header.Hash()
if blob_header_hash \!= blob_header_hash_from_reply:
throw/raise/panic
# Extract first 2 cert fields from blob status reply
batch_header = blob_status_reply.signed_batch.batch_header
blob_verification_proof = blob_status_reply.blob_verification_info
# Get the reference block number from the batch header
reference_block_number = batch_header.reference_block_number
# Get quorum IDs from the blob header
quorum_numbers = blob_verification_info.blob_certificate.blob_header.quorum_numbers
# Retrieve operator state data directly from the OperatorStateRetriever contract
operator_states = operator_state_retriever.getOperatorState(
reference_block_number,
quorum_numbers,
blob_status_reply.signed_batch.signatures
)
# Construct NonSignerStakesAndSignature using the operator state data
nonsigner_stake_sigs = construct_nonsigner_stakes_and_signature(
operator_states,
blob_status_reply.signed_batch.signatures
)
signed_quorum_numbers = blob_status_reply.signed_batch.quorum_numbers
return DACert(batch_header, blob_verification_proof, nonsigner_stake_sigs, cert_version_uint8, signed_quorum_numbers)
Secure Retrieval
System Diagram
System Flow
-
A Rollup Node queries Proxy’s
/get
endpoint to fetch batch contents associated with an encoded DA commitment. -
Proxy decodes the
cert_version
for the DA commitment and uses an internal mapping ofcert_version
⇒cert_abi_struct
to deserialize into the structured binding cert type. -
Proxy submits ABI encoded cert bytes to
EigenDACertVerifier
read call via thecheckDAcert
method, which returns averification_status_code
. -
Proxy interprets the
verification_status_code
to understand how to acknowledge the certificate's validity. If the verification fails, Proxy returns an HTTP 418 I'm a teapot status code, indicating to a secure rollup that it should disregard the certificate and treat it as an empty batch in its derivation pipeline. -
Assuming a valid certificate, Proxy attempts to query EigenDA retrieval paths for the underlying blob contents.
-
Once fetched, Proxy verifies the blob's KZG commitments to ensure tamper resistance (i.e., confirming that what's returned from EigenDA matches what was committed to during dispersal).
-
Proxy decodes the underlying blob into a
payload
type, which is returned to the Rollup Node.
Retrieval Paths
There are two main blob retrieval paths:
- decentralized retrieval: retrieve erasure coded chunks from Validators and recreate the
blob
from them. - centralized retrieval: the same Relay API that Validators use to download chunks, can also be used to retrieve full blobs.
EigenDA V2 has a new Relay API for retrieving blobs from the disperser. The GetBlob
method takes a blob_key
as input, which is a synonym for blob_header_hash
. Note that BlobCertificate
(different from DACert
!) contains an array of relay_keys
, which are the relays that can serve that specific blob. A relay’s URL can be retrieved from the relayKeyToUrl function on the EigenDARelayRegistry.sol contract.
Decoding
Decoding performs the exact reverse operations that Encoding did.