Skip to content

Commit

Permalink
Implement validator payload retrieval (#1194)
Browse files Browse the repository at this point in the history
Signed-off-by: litt3 <[email protected]>
  • Loading branch information
litt3 authored Feb 6, 2025
1 parent c2f781d commit 3ad96b1
Show file tree
Hide file tree
Showing 16 changed files with 488 additions and 138 deletions.
94 changes: 79 additions & 15 deletions api/clients/v2/config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package clients

import (
"fmt"
"errors"
"time"

"github.com/Layr-Labs/eigenda/api/clients/codecs"
Expand Down Expand Up @@ -61,6 +61,25 @@ type RelayPayloadRetrieverConfig struct {
RelayTimeout time.Duration
}

// ValidatorPayloadRetrieverConfig contains an embedded PayloadClientConfig, plus all additional configuration values
// needed by a ValidatorPayloadRetriever
type ValidatorPayloadRetrieverConfig struct {
PayloadClientConfig

// The timeout duration for retrieving chunks from a given quorum, and reassembling the chunks into a blob.
// Once this timeout triggers, the retriever will give up on the quorum, and retry with the next quorum (if one exists)
RetrievalTimeout time.Duration

// The address of the BlsOperatorStateRetriever contract
BlsOperatorStateRetrieverAddr string

// The address of the EigenDAServiceManager contract
EigenDAServiceManagerAddr string

// The maximum number of simultaneous connections to use when fetching chunks during validator retrieval
MaxConnectionCount uint
}

// PayloadDisperserConfig contains an embedded PayloadClientConfig, plus all additional configuration values needed
// by a PayloadDisperser
type PayloadDisperserConfig struct {
Expand Down Expand Up @@ -104,20 +123,20 @@ func getDefaultPayloadClientConfig() *PayloadClientConfig {
}
}

// checkAndSetDefaults checks an existing config struct and performs the following actions:
// checkAndSetDefaults checks an existing config struct. It performs one of the following actions for any contained 0 values:
//
// 1. If a config value is 0, and a 0 value makes sense, do nothing.
// 2. If a config value is 0, but a 0 value doesn't make sense and a default value is defined, then set it to the default.
// 3. If a config value is 0, but a 0 value doesn't make sense and a default value isn't defined, return an error.
// 1. If 0 is an acceptable value for the field, do nothing.
// 2. If 0 is NOT an acceptable value for the field, and a default value is defined, then set it to the default.
// 3. If 0 is NOT an acceptable value for the field, and a default value is NOT defined, return an error.
func (cc *PayloadClientConfig) checkAndSetDefaults() error {
// BlobEncodingVersion may be 0, so don't do anything

if cc.EthRpcUrl == "" {
return fmt.Errorf("EthRpcUrl is required")
return errors.New("EthRpcUrl is required")
}

if cc.EigenDACertVerifierAddr == "" {
return fmt.Errorf("EigenDACertVerifierAddr is required")
return errors.New("EigenDACertVerifierAddr is required")
}

// Nothing to do for PayloadPolynomialForm
Expand Down Expand Up @@ -145,11 +164,11 @@ func GetDefaultRelayPayloadRetrieverConfig() *RelayPayloadRetrieverConfig {
}
}

// checkAndSetDefaults checks an existing config struct and performs the following actions:
// checkAndSetDefaults checks an existing config struct. It performs one of the following actions for any contained 0 values:
//
// 1. If a config value is 0, and a 0 value makes sense, do nothing.
// 2. If a config value is 0, but a 0 value doesn't make sense and a default value is defined, then set it to the default.
// 3. If a config value is 0, but a 0 value doesn't make sense and a default value isn't defined, return an error.
// 1. If 0 is an acceptable value for the field, do nothing.
// 2. If 0 is NOT an acceptable value for the field, and a default value is defined, then set it to the default.
// 3. If 0 is NOT an acceptable value for the field, and a default value is NOT defined, return an error.
func (rc *RelayPayloadRetrieverConfig) checkAndSetDefaults() error {
err := rc.PayloadClientConfig.checkAndSetDefaults()
if err != nil {
Expand All @@ -164,6 +183,51 @@ func (rc *RelayPayloadRetrieverConfig) checkAndSetDefaults() error {
return nil
}

// GetDefaultValidatorPayloadRetrieverConfig creates a ValidatorPayloadRetrieverConfig with default values
//
// NOTE: The following fields do not have defined defaults and must always be specifically configured:
// - EthRpcUrl
// - EigenDACertVerifierAddr
// - BlsOperatorStateRetrieverAddr
// - EigenDAServiceManagerAddr
func GetDefaultValidatorPayloadRetrieverConfig() *ValidatorPayloadRetrieverConfig {
return &ValidatorPayloadRetrieverConfig{
PayloadClientConfig: *getDefaultPayloadClientConfig(),
RetrievalTimeout: 30 * time.Second,
MaxConnectionCount: 100,
}
}

// checkAndSetDefaults checks an existing config struct. It performs one of the following actions for any contained 0 values:
//
// 1. If 0 is an acceptable value for the field, do nothing.
// 2. If 0 is NOT an acceptable value for the field, and a default value is defined, then set it to the default.
// 3. If 0 is NOT an acceptable value for the field, and a default value is NOT defined, return an error.
func (rc *ValidatorPayloadRetrieverConfig) checkAndSetDefaults() error {
err := rc.PayloadClientConfig.checkAndSetDefaults()
if err != nil {
return err
}

if rc.BlsOperatorStateRetrieverAddr == "" {
return errors.New("BlsOperatorStateRetrieverAddr is required")
}

if rc.EigenDAServiceManagerAddr == "" {
return errors.New("EigenDAServiceManagerAddr is required")
}

defaultConfig := GetDefaultValidatorPayloadRetrieverConfig()
if rc.RetrievalTimeout == 0 {
rc.RetrievalTimeout = defaultConfig.RetrievalTimeout
}
if rc.MaxConnectionCount == 0 {
rc.MaxConnectionCount = defaultConfig.MaxConnectionCount
}

return nil
}

// GetDefaultPayloadDisperserConfig creates a PayloadDisperserConfig with default values
//
// NOTE: EthRpcUrl and EigenDACertVerifierAddr do not have defined defaults. These must always be specifically configured.
Expand All @@ -177,11 +241,11 @@ func GetDefaultPayloadDisperserConfig() *PayloadDisperserConfig {
}
}

// checkAndSetDefaults checks an existing config struct and performs the following actions:
// checkAndSetDefaults checks an existing config struct. It performs one of the following actions for any contained 0 values:
//
// 1. If a config value is 0, and a 0 value makes sense, do nothing.
// 2. If a config value is 0, but a 0 value doesn't make sense and a default value is defined, then set it to the default.
// 3. If a config value is 0, but a 0 value doesn't make sense and a default value isn't defined, return an error.
// 1. If 0 is an acceptable value for the field, do nothing.
// 2. If 0 is NOT an acceptable value for the field, and a default value is defined, then set it to the default.
// 3. If 0 is NOT an acceptable value for the field, and a default value is NOT defined, return an error.
func (dc *PayloadDisperserConfig) checkAndSetDefaults() error {
err := dc.PayloadClientConfig.checkAndSetDefaults()
if err != nil {
Expand Down
54 changes: 44 additions & 10 deletions api/clients/v2/mock/retrieval_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 17 additions & 75 deletions api/clients/v2/relay_payload_retriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import (

"github.com/Layr-Labs/eigenda/api/clients/codecs"
"github.com/Layr-Labs/eigenda/api/clients/v2/verification"
verifiercontract "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDACertVerifier"
core "github.com/Layr-Labs/eigenda/core/v2"
"github.com/Layr-Labs/eigenda/encoding"
"github.com/Layr-Labs/eigensdk-go/logging"
"github.com/consensys/gnark-crypto/ecc/bn254"
)
Expand Down Expand Up @@ -114,7 +112,7 @@ func (pr *RelayPayloadRetriever) GetPayload(
return nil, errors.New("relay key count is zero")
}

blobCommitments, err := blobCommitmentsBindingToInternal(
blobCommitments, err := verification.BlobCommitmentsBindingToInternal(
&eigenDACert.BlobInclusionInfo.BlobCertificate.BlobHeader.Commitment)

if err != nil {
Expand Down Expand Up @@ -142,11 +140,23 @@ func (pr *RelayPayloadRetriever) GetPayload(
continue
}

err = pr.verifyBlobAgainstCert(blobKey, relayKey, blob, blobCommitments.Commitment, blobCommitments.Length)
err = verification.CheckBlobLength(blob, blobCommitments.Length)
if err != nil {
pr.log.Warn("check blob length", "blobKey", blobKey.Hex(), "relayKey", relayKey, "error", err)
continue
}

// An honest relay should never send a blob which doesn't verify against the cert
valid, err := verification.GenerateAndCompareBlobCommitment(pr.g1Srs, blob, blobCommitments.Commitment)
if err != nil {
pr.log.Warn("verify blob from relay against cert: %w", err)
pr.log.Warn(
"generate and compare blob commitment",
"blobKey", blobKey.Hex(), "relayKey", relayKey, "error", err)
continue
}
if !valid {
pr.log.Warn(
"generated commitment doesn't match cert commitment",
"blobKey", blobKey.Hex(), "relayKey", relayKey)
continue
}

Expand All @@ -168,59 +178,7 @@ func (pr *RelayPayloadRetriever) GetPayload(
return nil, fmt.Errorf("unable to retrieve blob %v from any relay. relay count: %d", blobKey.Hex(), relayKeyCount)
}

// verifyBlobAgainstCert verifies the blob received from a relay against the certificate.
//
// The following verifications are performed in this method:
// 1. Verify that the blob isn't empty
// 2. Verify the blob against the cert's kzg commitment
// 3. Verify that the blob length is less than or equal to the cert's blob length
//
// If all verifications succeed, the method returns nil. Otherwise, it returns an error.
func (pr *RelayPayloadRetriever) verifyBlobAgainstCert(
blobKey *core.BlobKey,
relayKey core.RelayKey,
blob []byte,
kzgCommitment *encoding.G1Commitment,
blobLength uint) error {

// An honest relay should never send an empty blob
if len(blob) == 0 {
return fmt.Errorf("blob %v received from relay %v had length 0", blobKey.Hex(), relayKey)
}

// TODO: in the future, this will be optimized to use fiat shamir transformation for verification, rather than
// regenerating the commitment: https://github.com/Layr-Labs/eigenda/issues/1037
valid, err := verification.GenerateAndCompareBlobCommitment(pr.g1Srs, blob, kzgCommitment)
if err != nil {
return fmt.Errorf(
"generate and compare commitment for blob %v received from relay %v: %w",
blobKey.Hex(),
relayKey,
err)
}

if !valid {
return fmt.Errorf("commitment for blob %v is invalid for bytes received from relay %v", blobKey.Hex(), relayKey)
}

// Checking that the length returned by the relay is <= the length claimed in the BlobCommitments is sufficient
// here: it isn't necessary to verify the length proof itself, since this will have been done by DA nodes prior to
// signing for availability.
//
// Note that the length in the commitment is the length of the blob in symbols
if uint(len(blob)) > blobLength*encoding.BYTES_PER_SYMBOL {
return fmt.Errorf(
"length for blob %v (%d bytes) received from relay %v is greater than claimed blob length (%d bytes)",
blobKey.Hex(),
len(blob),
relayKey,
blobLength*encoding.BYTES_PER_SYMBOL)
}

return nil
}

// getBlobWithTimeout attempts to get a blob from a given relay, and times out based on config.RelayTimeout
// getBlobWithTimeout attempts to get a blob from a given relay, and times out based on config.FetchTimeout
func (pr *RelayPayloadRetriever) getBlobWithTimeout(
ctx context.Context,
relayKey core.RelayKey,
Expand All @@ -246,19 +204,3 @@ func (pr *RelayPayloadRetriever) Close() error {

return nil
}

// blobCommitmentsBindingToInternal converts a blob commitment from an eigenDA cert into the internal
// encoding.BlobCommitments type
func blobCommitmentsBindingToInternal(
blobCommitmentBinding *verifiercontract.BlobCommitment,
) (*encoding.BlobCommitments, error) {

blobCommitment, err := encoding.BlobCommitmentsFromProtobuf(
verification.BlobCommitmentBindingToProto(blobCommitmentBinding))

if err != nil {
return nil, fmt.Errorf("blob commitments from protobuf: %w", err)
}

return blobCommitment, nil
}
Loading

0 comments on commit 3ad96b1

Please sign in to comment.