diff --git a/Cargo.lock b/Cargo.lock index 628f97995b6..9bef1e3f926 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5477,7 +5477,28 @@ dependencies = [ "petgraph", "prettyplease", "prost 0.12.6", - "prost-types", + "prost-types 0.12.6", + "regex", + "syn 2.0.77", + "tempfile", +] + +[[package]] +name = "prost-build" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.3", + "prost-types 0.13.3", "regex", "syn 2.0.77", "tempfile", @@ -5520,7 +5541,7 @@ dependencies = [ "miette", "once_cell", "prost 0.12.6", - "prost-types", + "prost-types 0.12.6", "serde", "serde-value", ] @@ -5534,6 +5555,15 @@ dependencies = [ "prost 0.12.6", ] +[[package]] +name = "prost-types" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" +dependencies = [ + "prost 0.13.3", +] + [[package]] name = "protox" version = "0.5.1" @@ -5544,7 +5574,7 @@ dependencies = [ "miette", "prost 0.12.6", "prost-reflect", - "prost-types", + "prost-types 0.12.6", "protox-parse", "thiserror", ] @@ -5557,7 +5587,7 @@ checksum = "7b4581f441c58863525a3e6bec7b8de98188cf75239a56c725a3e7288450a33f" dependencies = [ "logos", "miette", - "prost-types", + "prost-types 0.12.6", "thiserror", ] @@ -6186,6 +6216,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -8243,8 +8286,11 @@ dependencies = [ "percent-encoding", "pin-project", "prost 0.13.3", + "rustls-native-certs 0.8.0", + "rustls-pemfile 2.1.3", "socket2", "tokio", + "tokio-rustls 0.26.0", "tokio-stream", "tower 0.4.13", "tower-layer", @@ -8252,6 +8298,40 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2 1.0.86", + "prost-build 0.13.3", + "prost-types 0.13.3", + "quote 1.0.37", + "syn 2.0.77", +] + +[[package]] +name = "tonic-web" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5299dd20801ad736dccb4a5ea0da7376e59cd98f213bf1c3d478cf53f4834b58" +dependencies = [ + "base64 0.22.1", + "bytes", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "pin-project", + "tokio-stream", + "tonic", + "tower-http", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.4.13" @@ -10089,7 +10169,11 @@ version = "0.1.0" dependencies = [ "anyhow", "axum", + "prost 0.13.3", "tokio", + "tonic", + "tonic-build", + "tonic-web", "tracing", ] @@ -10894,7 +10978,7 @@ dependencies = [ "heck 0.5.0", "prettyplease", "proc-macro2 1.0.86", - "prost-build", + "prost-build 0.12.6", "prost-reflect", "protox", "quote 1.0.37", diff --git a/core/node/eigenda_proxy/Cargo.toml b/core/node/eigenda_proxy/Cargo.toml index 91cf46c3f9c..44f2a000c5f 100644 --- a/core/node/eigenda_proxy/Cargo.toml +++ b/core/node/eigenda_proxy/Cargo.toml @@ -14,3 +14,11 @@ anyhow.workspace = true axum.workspace = true tokio.workspace = true tracing.workspace = true +# we can't use the workspace version of prost because +# the tonic dependency requires a hugher version. +prost = "0.13.1" +tonic = { version = "0.12.1", features = ["tls", "channel", "tls-roots"]} +tonic-web = "0.12.1" + +[build-dependencies] +tonic-build = { version = "0.12.1", features = ["prost"] } diff --git a/core/node/eigenda_proxy/build.rs b/core/node/eigenda_proxy/build.rs new file mode 100644 index 00000000000..4789292b469 --- /dev/null +++ b/core/node/eigenda_proxy/build.rs @@ -0,0 +1,7 @@ +fn main() { + tonic_build::configure() + .build_server(false) + .out_dir("src/") + .compile_protos(&["proto/disperser/disperser.proto"], &["proto"]) + .unwrap(); +} diff --git a/core/node/eigenda_proxy/proto/common/common.proto b/core/node/eigenda_proxy/proto/common/common.proto new file mode 100644 index 00000000000..343222f88ca --- /dev/null +++ b/core/node/eigenda_proxy/proto/common/common.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; +package common; +option go_package = "github.com/Layr-Labs/eigenda/api/grpc/common"; + +message G1Commitment { + // The X coordinate of the KZG commitment. This is the raw byte representation of the field element. + bytes x = 1; + // The Y coordinate of the KZG commitment. This is the raw byte representation of the field element. + bytes y = 2; +} diff --git a/core/node/eigenda_proxy/proto/disperser/disperser.proto b/core/node/eigenda_proxy/proto/disperser/disperser.proto new file mode 100644 index 00000000000..d83aaf3124a --- /dev/null +++ b/core/node/eigenda_proxy/proto/disperser/disperser.proto @@ -0,0 +1,252 @@ +syntax = "proto3"; +package disperser; +import "common/common.proto"; +option go_package = "github.com/Layr-Labs/eigenda/api/grpc/disperser"; + +// Disperser defines the public APIs for dispersing blobs. +service Disperser { + // This API accepts blob to disperse from clients. + // This executes the dispersal async, i.e. it returns once the request + // is accepted. The client could use GetBlobStatus() API to poll the the + // processing status of the blob. + rpc DisperseBlob(DisperseBlobRequest) returns (DisperseBlobReply) {} + + + // DisperseBlobAuthenticated is similar to DisperseBlob, except that it requires the + // client to authenticate itself via the AuthenticationData message. The protoco is as follows: + // 1. The client sends a DisperseBlobAuthenticated request with the DisperseBlobRequest message + // 2. The Disperser sends back a BlobAuthHeader message containing information for the client to + // verify and sign. + // 3. The client verifies the BlobAuthHeader and sends back the signed BlobAuthHeader in an + // AuthenticationData message. + // 4. The Disperser verifies the signature and returns a DisperseBlobReply message. + rpc DisperseBlobAuthenticated(stream AuthenticatedRequest) returns (stream AuthenticatedReply); + + // This API is meant to be polled for the blob status. + rpc GetBlobStatus(BlobStatusRequest) returns (BlobStatusReply) {} + + // This retrieves the requested blob from the Disperser's backend. + // This is a more efficient way to retrieve blobs than directly retrieving + // from the DA Nodes (see detail about this approach in + // api/proto/retriever/retriever.proto). + // The blob should have been initially dispersed via this Disperser service + // for this API to work. + rpc RetrieveBlob(RetrieveBlobRequest) returns (RetrieveBlobReply) {} +} + +// Requests and Responses + +// Authenicated Message Types + +message AuthenticatedRequest { + oneof payload { + DisperseBlobRequest disperse_request = 1; + AuthenticationData authentication_data = 2; + } +} + +message AuthenticatedReply { + oneof payload { + BlobAuthHeader blob_auth_header = 1; + DisperseBlobReply disperse_reply = 2; + } +} + +// BlobAuthHeader contains information about the blob for the client to verify and sign. +// - Once payments are enabled, the BlobAuthHeader will contain the KZG commitment to the blob, which the client + // will verify and sign. Having the client verify the KZG commitment instead of calculating it avoids +// the need for the client to have the KZG structured reference string (SRS), which can be large. +// The signed KZG commitment prevents the disperser from sending a different blob to the DA Nodes +// than the one the client sent. +// - In the meantime, the BlobAuthHeader contains a simple challenge parameter is used to prevent +// replay attacks in the event that a signature is leaked. +message BlobAuthHeader { + uint32 challenge_parameter = 1; +} + +// AuthenticationData contains the signature of the BlobAuthHeader. +message AuthenticationData { + bytes authentication_data = 1; +} + +message DisperseBlobRequest { + // The data to be dispersed. + // The size of data must be <= 2MiB. Every 32 bytes of data chunk is interpreted as an integer in big endian format + // where the lower address has more significant bits. The integer must stay in the valid range to be interpreted + // as a field element on the bn254 curve. The valid range is + // 0 <= x < 21888242871839275222246405745257275088548364400416034343698204186575808495617 + // containing slightly less than 254 bits and more than 253 bits. If any one of the 32 bytes chunk is outside the range, + // the whole request is deemed as invalid, and rejected. + bytes data = 1; + // The quorums to which the blob will be sent, in addition to the required quorums which are configured + // on the EigenDA smart contract. If required quorums are included here, an error will be returned. + // The disperser will ensure that the encoded blobs for each quorum are all processed + // within the same batch. + repeated uint32 custom_quorum_numbers = 2; + + // The account ID of the client. This should be a hex-encoded string of the ECSDA public key + // corresponding to the key used by the client to sign the BlobAuthHeader. + string account_id = 3; +} + +message DisperseBlobReply { + // The status of the blob associated with the request_id. + BlobStatus result = 1; + // The request ID generated by the disperser. + // Once a request is accepted (although not processed), a unique request ID will be + // generated. + // Two different DisperseBlobRequests (determined by the hash of the DisperseBlobRequest) + // will have different IDs, and the same DisperseBlobRequest sent repeatedly at different + // times will also have different IDs. + // The client should use this ID to query the processing status of the request (via + // the GetBlobStatus API). + bytes request_id = 2; +} + +// BlobStatusRequest is used to query the status of a blob. +message BlobStatusRequest { + bytes request_id = 1; +} + +message BlobStatusReply { + // The status of the blob. + BlobStatus status = 1; + // The blob info needed for clients to confirm the blob against the EigenDA contracts. + BlobInfo info = 2; +} + +// RetrieveBlobRequest contains parameters to retrieve the blob. +message RetrieveBlobRequest { + bytes batch_header_hash = 1; + uint32 blob_index = 2; +} + +// RetrieveBlobReply contains the retrieved blob data +message RetrieveBlobReply { + bytes data = 1; +} + +// Data Types + +// BlobStatus represents the status of a blob. +// The status of a blob is updated as the blob is processed by the disperser. +// The status of a blob can be queried by the client using the GetBlobStatus API. +// Intermediate states are states that the blob can be in while being processed, and it can be updated to a differet state: +// - PROCESSING +// - DISPERSING +// - CONFIRMED +// Terminal states are states that will not be updated to a different state: +// - FAILED +// - FINALIZED +// - INSUFFICIENT_SIGNATURES +enum BlobStatus { + UNKNOWN = 0; + + // PROCESSING means that the blob is currently being processed by the disperser + PROCESSING = 1; + // CONFIRMED means that the blob has been dispersed to DA Nodes and the dispersed + // batch containing the blob has been confirmed onchain + CONFIRMED = 2; + + // FAILED means that the blob has failed permanently (for reasons other than insufficient + // signatures, which is a separate state) + FAILED = 3; + // FINALIZED means that the block containing the blob's confirmation transaction has been finalized on Ethereum + FINALIZED = 4; + // INSUFFICIENT_SIGNATURES means that the confirmation threshold for the blob was not met + // for at least one quorum. + INSUFFICIENT_SIGNATURES = 5; + + // DISPERSING means that the blob is currently being dispersed to DA Nodes and being confirmed onchain + DISPERSING = 6; +} + +// Types below correspond to the types necessary to verify a blob +// https://github.com/Layr-Labs/eigenda/blob/master/contracts/src/libraries/EigenDABlobUtils.sol#L29 + +// BlobInfo contains information needed to confirm the blob against the EigenDA contracts +message BlobInfo { + BlobHeader blob_header = 1; + BlobVerificationProof blob_verification_proof = 2; +} + +message BlobHeader { + // KZG commitment of the blob. + common.G1Commitment commitment = 1; + // The length of the blob in symbols (each symbol is 32 bytes). + uint32 data_length = 2; + // The params of the quorums that this blob participates in. + repeated BlobQuorumParam blob_quorum_params = 3; +} + +message BlobQuorumParam { + // The ID of the quorum. + uint32 quorum_number = 1; + // The max percentage of stake within the quorum that can be held by or delegated + // to adversarial operators. Currently, this and the next parameter are standardized + // across the quorum using values read from the EigenDA contracts. + uint32 adversary_threshold_percentage = 2; + // The min percentage of stake that must attest in order to consider + // the dispersal is successful. + uint32 confirmation_threshold_percentage = 3; + // The length of each chunk. + uint32 chunk_length = 4; +} + +message BlobVerificationProof { + // batch_id is an incremental ID assigned to a batch by EigenDAServiceManager + uint32 batch_id = 1; + // The index of the blob in the batch (which is logically an ordered list of blobs). + uint32 blob_index = 2; + BatchMetadata batch_metadata = 3; + // inclusion_proof is a merkle proof for a blob header's inclusion in a batch + bytes inclusion_proof = 4; + // indexes of quorums in BatchHeader.quorum_numbers that match the quorums in BlobHeader.blob_quorum_params + // Ex. BlobHeader.blob_quorum_params = [ + // { + // quorum_number = 0, + // ... + // }, + // { + // quorum_number = 3, + // ... + // }, + // { + // quorum_number = 5, + // ... + // }, + // ] + // BatchHeader.quorum_numbers = [0, 5, 3] => 0x000503 + // Then, quorum_indexes = [0, 2, 1] => 0x000201 + bytes quorum_indexes = 5; +} + +message BatchMetadata { + BatchHeader batch_header = 1; + // The hash of all public keys of the operators that did not sign the batch. + bytes signatory_record_hash = 2; + // The fee payment paid by users for dispersing this batch. It's the bytes + // representation of a big.Int value. + bytes fee = 3; + // The Ethereum block number at which the batch is confirmed onchain. + uint32 confirmation_block_number = 4; + // This is the hash of the ReducedBatchHeader defined onchain, see: + // https://github.com/Layr-Labs/eigenda/blob/master/contracts/src/interfaces/IEigenDAServiceManager.sol#L43 + // The is the message that the operators will sign their signatures on. + bytes batch_header_hash = 5; +} + +message BatchHeader { + // The root of the merkle tree with the hashes of blob headers as leaves. + bytes batch_root = 1; + // All quorums associated with blobs in this batch. Sorted in ascending order. + // Ex. [0, 2, 1] => 0x000102 + bytes quorum_numbers = 2; + // The percentage of stake that has signed for this batch. + // The quorum_signed_percentages[i] is percentage for the quorum_numbers[i]. + bytes quorum_signed_percentages = 3; + // The Ethereum block number at which the batch was created. + // The Disperser will encode and disperse the blobs based on the onchain info + // (e.g. operator stakes) at this block number. + uint32 reference_block_number = 4; +} diff --git a/core/node/eigenda_proxy/src/common.rs b/core/node/eigenda_proxy/src/common.rs new file mode 100644 index 00000000000..43e6f128dd6 --- /dev/null +++ b/core/node/eigenda_proxy/src/common.rs @@ -0,0 +1,10 @@ +// This file is @generated by prost-build. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct G1Commitment { + /// The X coordinate of the KZG commitment. This is the raw byte representation of the field element. + #[prost(bytes = "vec", tag = "1")] + pub x: ::prost::alloc::vec::Vec, + /// The Y coordinate of the KZG commitment. This is the raw byte representation of the field element. + #[prost(bytes = "vec", tag = "2")] + pub y: ::prost::alloc::vec::Vec, +} diff --git a/core/node/eigenda_proxy/src/disperser.rs b/core/node/eigenda_proxy/src/disperser.rs new file mode 100644 index 00000000000..2e2800828ff --- /dev/null +++ b/core/node/eigenda_proxy/src/disperser.rs @@ -0,0 +1,501 @@ +// This file is @generated by prost-build. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AuthenticatedRequest { + #[prost(oneof = "authenticated_request::Payload", tags = "1, 2")] + pub payload: ::core::option::Option, +} +/// Nested message and enum types in `AuthenticatedRequest`. +pub mod authenticated_request { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Payload { + #[prost(message, tag = "1")] + DisperseRequest(super::DisperseBlobRequest), + #[prost(message, tag = "2")] + AuthenticationData(super::AuthenticationData), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AuthenticatedReply { + #[prost(oneof = "authenticated_reply::Payload", tags = "1, 2")] + pub payload: ::core::option::Option, +} +/// Nested message and enum types in `AuthenticatedReply`. +pub mod authenticated_reply { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Payload { + #[prost(message, tag = "1")] + BlobAuthHeader(super::BlobAuthHeader), + #[prost(message, tag = "2")] + DisperseReply(super::DisperseBlobReply), + } +} +/// BlobAuthHeader contains information about the blob for the client to verify and sign. +/// - Once payments are enabled, the BlobAuthHeader will contain the KZG commitment to the blob, which the client +/// will verify and sign. Having the client verify the KZG commitment instead of calculating it avoids +/// the need for the client to have the KZG structured reference string (SRS), which can be large. +/// The signed KZG commitment prevents the disperser from sending a different blob to the DA Nodes +/// than the one the client sent. +/// - In the meantime, the BlobAuthHeader contains a simple challenge parameter is used to prevent +/// replay attacks in the event that a signature is leaked. +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct BlobAuthHeader { + #[prost(uint32, tag = "1")] + pub challenge_parameter: u32, +} +/// AuthenticationData contains the signature of the BlobAuthHeader. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AuthenticationData { + #[prost(bytes = "vec", tag = "1")] + pub authentication_data: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DisperseBlobRequest { + /// The data to be dispersed. + /// The size of data must be <= 2MiB. Every 32 bytes of data chunk is interpreted as an integer in big endian format + /// where the lower address has more significant bits. The integer must stay in the valid range to be interpreted + /// as a field element on the bn254 curve. The valid range is + /// 0 <= x < 21888242871839275222246405745257275088548364400416034343698204186575808495617 + /// containing slightly less than 254 bits and more than 253 bits. If any one of the 32 bytes chunk is outside the range, + /// the whole request is deemed as invalid, and rejected. + #[prost(bytes = "vec", tag = "1")] + pub data: ::prost::alloc::vec::Vec, + /// The quorums to which the blob will be sent, in addition to the required quorums which are configured + /// on the EigenDA smart contract. If required quorums are included here, an error will be returned. + /// The disperser will ensure that the encoded blobs for each quorum are all processed + /// within the same batch. + #[prost(uint32, repeated, tag = "2")] + pub custom_quorum_numbers: ::prost::alloc::vec::Vec, + /// The account ID of the client. This should be a hex-encoded string of the ECSDA public key + /// corresponding to the key used by the client to sign the BlobAuthHeader. + #[prost(string, tag = "3")] + pub account_id: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DisperseBlobReply { + /// The status of the blob associated with the request_id. + #[prost(enumeration = "BlobStatus", tag = "1")] + pub result: i32, + /// The request ID generated by the disperser. + /// Once a request is accepted (although not processed), a unique request ID will be + /// generated. + /// Two different DisperseBlobRequests (determined by the hash of the DisperseBlobRequest) + /// will have different IDs, and the same DisperseBlobRequest sent repeatedly at different + /// times will also have different IDs. + /// The client should use this ID to query the processing status of the request (via + /// the GetBlobStatus API). + #[prost(bytes = "vec", tag = "2")] + pub request_id: ::prost::alloc::vec::Vec, +} +/// BlobStatusRequest is used to query the status of a blob. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlobStatusRequest { + #[prost(bytes = "vec", tag = "1")] + pub request_id: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlobStatusReply { + /// The status of the blob. + #[prost(enumeration = "BlobStatus", tag = "1")] + pub status: i32, + /// The blob info needed for clients to confirm the blob against the EigenDA contracts. + #[prost(message, optional, tag = "2")] + pub info: ::core::option::Option, +} +/// RetrieveBlobRequest contains parameters to retrieve the blob. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RetrieveBlobRequest { + #[prost(bytes = "vec", tag = "1")] + pub batch_header_hash: ::prost::alloc::vec::Vec, + #[prost(uint32, tag = "2")] + pub blob_index: u32, +} +/// RetrieveBlobReply contains the retrieved blob data +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RetrieveBlobReply { + #[prost(bytes = "vec", tag = "1")] + pub data: ::prost::alloc::vec::Vec, +} +/// BlobInfo contains information needed to confirm the blob against the EigenDA contracts +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlobInfo { + #[prost(message, optional, tag = "1")] + pub blob_header: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub blob_verification_proof: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlobHeader { + /// KZG commitment of the blob. + #[prost(message, optional, tag = "1")] + pub commitment: ::core::option::Option, + /// The length of the blob in symbols (each symbol is 32 bytes). + #[prost(uint32, tag = "2")] + pub data_length: u32, + /// The params of the quorums that this blob participates in. + #[prost(message, repeated, tag = "3")] + pub blob_quorum_params: ::prost::alloc::vec::Vec, +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct BlobQuorumParam { + /// The ID of the quorum. + #[prost(uint32, tag = "1")] + pub quorum_number: u32, + /// The max percentage of stake within the quorum that can be held by or delegated + /// to adversarial operators. Currently, this and the next parameter are standardized + /// across the quorum using values read from the EigenDA contracts. + #[prost(uint32, tag = "2")] + pub adversary_threshold_percentage: u32, + /// The min percentage of stake that must attest in order to consider + /// the dispersal is successful. + #[prost(uint32, tag = "3")] + pub confirmation_threshold_percentage: u32, + /// The length of each chunk. + #[prost(uint32, tag = "4")] + pub chunk_length: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlobVerificationProof { + /// batch_id is an incremental ID assigned to a batch by EigenDAServiceManager + #[prost(uint32, tag = "1")] + pub batch_id: u32, + /// The index of the blob in the batch (which is logically an ordered list of blobs). + #[prost(uint32, tag = "2")] + pub blob_index: u32, + #[prost(message, optional, tag = "3")] + pub batch_metadata: ::core::option::Option, + /// inclusion_proof is a merkle proof for a blob header's inclusion in a batch + #[prost(bytes = "vec", tag = "4")] + pub inclusion_proof: ::prost::alloc::vec::Vec, + /// indexes of quorums in BatchHeader.quorum_numbers that match the quorums in BlobHeader.blob_quorum_params + /// Ex. BlobHeader.blob_quorum_params = [ + /// { + /// quorum_number = 0, + /// ... + /// }, + /// { + /// quorum_number = 3, + /// ... + /// }, + /// { + /// quorum_number = 5, + /// ... + /// }, + /// ] + /// BatchHeader.quorum_numbers = \[0, 5, 3\] => 0x000503 + /// Then, quorum_indexes = \[0, 2, 1\] => 0x000201 + #[prost(bytes = "vec", tag = "5")] + pub quorum_indexes: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BatchMetadata { + #[prost(message, optional, tag = "1")] + pub batch_header: ::core::option::Option, + /// The hash of all public keys of the operators that did not sign the batch. + #[prost(bytes = "vec", tag = "2")] + pub signatory_record_hash: ::prost::alloc::vec::Vec, + /// The fee payment paid by users for dispersing this batch. It's the bytes + /// representation of a big.Int value. + #[prost(bytes = "vec", tag = "3")] + pub fee: ::prost::alloc::vec::Vec, + /// The Ethereum block number at which the batch is confirmed onchain. + #[prost(uint32, tag = "4")] + pub confirmation_block_number: u32, + /// This is the hash of the ReducedBatchHeader defined onchain, see: + /// + /// The is the message that the operators will sign their signatures on. + #[prost(bytes = "vec", tag = "5")] + pub batch_header_hash: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BatchHeader { + /// The root of the merkle tree with the hashes of blob headers as leaves. + #[prost(bytes = "vec", tag = "1")] + pub batch_root: ::prost::alloc::vec::Vec, + /// All quorums associated with blobs in this batch. Sorted in ascending order. + /// Ex. \[0, 2, 1\] => 0x000102 + #[prost(bytes = "vec", tag = "2")] + pub quorum_numbers: ::prost::alloc::vec::Vec, + /// The percentage of stake that has signed for this batch. + /// The quorum_signed_percentages\[i\] is percentage for the quorum_numbers\[i\]. + #[prost(bytes = "vec", tag = "3")] + pub quorum_signed_percentages: ::prost::alloc::vec::Vec, + /// The Ethereum block number at which the batch was created. + /// The Disperser will encode and disperse the blobs based on the onchain info + /// (e.g. operator stakes) at this block number. + #[prost(uint32, tag = "4")] + pub reference_block_number: u32, +} +/// BlobStatus represents the status of a blob. +/// The status of a blob is updated as the blob is processed by the disperser. +/// The status of a blob can be queried by the client using the GetBlobStatus API. +/// Intermediate states are states that the blob can be in while being processed, and it can be updated to a differet state: +/// - PROCESSING +/// - DISPERSING +/// - CONFIRMED +/// Terminal states are states that will not be updated to a different state: +/// - FAILED +/// - FINALIZED +/// - INSUFFICIENT_SIGNATURES +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum BlobStatus { + Unknown = 0, + /// PROCESSING means that the blob is currently being processed by the disperser + Processing = 1, + /// CONFIRMED means that the blob has been dispersed to DA Nodes and the dispersed + /// batch containing the blob has been confirmed onchain + Confirmed = 2, + /// FAILED means that the blob has failed permanently (for reasons other than insufficient + /// signatures, which is a separate state) + Failed = 3, + /// FINALIZED means that the block containing the blob's confirmation transaction has been finalized on Ethereum + Finalized = 4, + /// INSUFFICIENT_SIGNATURES means that the confirmation threshold for the blob was not met + /// for at least one quorum. + InsufficientSignatures = 5, + /// DISPERSING means that the blob is currently being dispersed to DA Nodes and being confirmed onchain + Dispersing = 6, +} +impl BlobStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unknown => "UNKNOWN", + Self::Processing => "PROCESSING", + Self::Confirmed => "CONFIRMED", + Self::Failed => "FAILED", + Self::Finalized => "FINALIZED", + Self::InsufficientSignatures => "INSUFFICIENT_SIGNATURES", + Self::Dispersing => "DISPERSING", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "UNKNOWN" => Some(Self::Unknown), + "PROCESSING" => Some(Self::Processing), + "CONFIRMED" => Some(Self::Confirmed), + "FAILED" => Some(Self::Failed), + "FINALIZED" => Some(Self::Finalized), + "INSUFFICIENT_SIGNATURES" => Some(Self::InsufficientSignatures), + "DISPERSING" => Some(Self::Dispersing), + _ => None, + } + } +} +/// Generated client implementations. +pub mod disperser_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + /// Disperser defines the public APIs for dispersing blobs. + #[derive(Debug, Clone)] + pub struct DisperserClient { + inner: tonic::client::Grpc, + } + impl DisperserClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl DisperserClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> DisperserClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + DisperserClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + /// This API accepts blob to disperse from clients. + /// This executes the dispersal async, i.e. it returns once the request + /// is accepted. The client could use GetBlobStatus() API to poll the the + /// processing status of the blob. + pub async fn disperse_blob( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/disperser.Disperser/DisperseBlob", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("disperser.Disperser", "DisperseBlob")); + self.inner.unary(req, path, codec).await + } + /// DisperseBlobAuthenticated is similar to DisperseBlob, except that it requires the + /// client to authenticate itself via the AuthenticationData message. The protoco is as follows: + /// 1. The client sends a DisperseBlobAuthenticated request with the DisperseBlobRequest message + /// 2. The Disperser sends back a BlobAuthHeader message containing information for the client to + /// verify and sign. + /// 3. The client verifies the BlobAuthHeader and sends back the signed BlobAuthHeader in an + /// AuthenticationData message. + /// 4. The Disperser verifies the signature and returns a DisperseBlobReply message. + pub async fn disperse_blob_authenticated( + &mut self, + request: impl tonic::IntoStreamingRequest< + Message = super::AuthenticatedRequest, + >, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/disperser.Disperser/DisperseBlobAuthenticated", + ); + let mut req = request.into_streaming_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("disperser.Disperser", "DisperseBlobAuthenticated"), + ); + self.inner.streaming(req, path, codec).await + } + /// This API is meant to be polled for the blob status. + pub async fn get_blob_status( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/disperser.Disperser/GetBlobStatus", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("disperser.Disperser", "GetBlobStatus")); + self.inner.unary(req, path, codec).await + } + /// This retrieves the requested blob from the Disperser's backend. + /// This is a more efficient way to retrieve blobs than directly retrieving + /// from the DA Nodes (see detail about this approach in + /// api/proto/retriever/retriever.proto). + /// The blob should have been initially dispersed via this Disperser service + /// for this API to work. + pub async fn retrieve_blob( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/disperser.Disperser/RetrieveBlob", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("disperser.Disperser", "RetrieveBlob")); + self.inner.unary(req, path, codec).await + } + } +} diff --git a/core/node/eigenda_proxy/src/lib.rs b/core/node/eigenda_proxy/src/lib.rs index da8403d085c..535087fa454 100644 --- a/core/node/eigenda_proxy/src/lib.rs +++ b/core/node/eigenda_proxy/src/lib.rs @@ -1,5 +1,7 @@ -use std::net::SocketAddr; +mod common; +mod disperser; +use std::net::SocketAddr; use anyhow::Context as _; use axum::{ routing::{get, put},