From 1ae4c59676c589615af3913d5ae957de1ce68c57 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:35:46 -0500 Subject: [PATCH 01/21] Draft payload dispersal Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/codecs/blob_codec.go | 23 ++ api/clients/v2/mock/blob_verifier.go | 38 +++- api/clients/v2/payload_client_config.go | 43 ++++ api/clients/v2/payload_disperser.go | 227 +++++++++++++++++++ api/clients/v2/payload_disperser_config.go | 21 ++ api/clients/v2/payload_retriever.go | 25 +- api/clients/v2/payload_retriever_config.go | 36 +-- api/clients/v2/verification/blob_verifier.go | 30 +++ api/clients/v2/verification/eigenda_cert.go | 32 +++ 9 files changed, 419 insertions(+), 56 deletions(-) create mode 100644 api/clients/v2/payload_client_config.go create mode 100644 api/clients/v2/payload_disperser.go create mode 100644 api/clients/v2/payload_disperser_config.go diff --git a/api/clients/codecs/blob_codec.go b/api/clients/codecs/blob_codec.go index 5be5190261..ec3796a690 100644 --- a/api/clients/codecs/blob_codec.go +++ b/api/clients/codecs/blob_codec.go @@ -46,3 +46,26 @@ func GenericDecodeBlob(data []byte) ([]byte, error) { return data, nil } + +// CreateCodec creates a new BlobCodec based on the defined polynomial form of payloads, and the desired +// BlobEncodingVersion +func CreateCodec(payloadPolynomialForm PolynomialForm, version BlobEncodingVersion) (BlobCodec, error) { + lowLevelCodec, err := BlobEncodingVersionToCodec(version) + if err != nil { + return nil, fmt.Errorf("create low level codec: %w", err) + } + + switch payloadPolynomialForm { + case PolynomialFormCoeff: + // Data must NOT be IFFTed during blob construction, since the payload is already in PolynomialFormCoeff after + // being encoded. + return NewNoIFFTCodec(lowLevelCodec), nil + case PolynomialFormEval: + // Data MUST be IFFTed during blob construction, since the payload is in PolynomialFormEval after being encoded, + // but must be in PolynomialFormCoeff to produce a valid blob. + return NewIFFTCodec(lowLevelCodec), nil + default: + return nil, fmt.Errorf("unsupported polynomial form: %d", payloadPolynomialForm) + } + +} diff --git a/api/clients/v2/mock/blob_verifier.go b/api/clients/v2/mock/blob_verifier.go index 04894307ae..28bf1af88d 100644 --- a/api/clients/v2/mock/blob_verifier.go +++ b/api/clients/v2/mock/blob_verifier.go @@ -5,15 +5,49 @@ package mock import ( "context" - "github.com/Layr-Labs/eigenda/api/clients/v2/verification" + contractEigenDABlobVerifier "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDABlobVerifier" "github.com/stretchr/testify/mock" + + v2 "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2" + + "github.com/Layr-Labs/eigenda/api/clients/v2/verification" ) -// MockBlobVerifier is an autogenerated mock type for the MockBlobVerifier type +// MockBlobVerifier is an autogenerated mock type for the IBlobVerifier type type MockBlobVerifier struct { mock.Mock } +// GetNonSignerStakesAndSignature provides a mock function with given fields: ctx, signedBatch +func (_m *MockBlobVerifier) GetNonSignerStakesAndSignature(ctx context.Context, signedBatch *v2.SignedBatch) (*contractEigenDABlobVerifier.NonSignerStakesAndSignature, error) { + ret := _m.Called(ctx, signedBatch) + + if len(ret) == 0 { + panic("no return value specified for GetNonSignerStakesAndSignature") + } + + var r0 *contractEigenDABlobVerifier.NonSignerStakesAndSignature + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v2.SignedBatch) (*contractEigenDABlobVerifier.NonSignerStakesAndSignature, error)); ok { + return rf(ctx, signedBatch) + } + if rf, ok := ret.Get(0).(func(context.Context, *v2.SignedBatch) *contractEigenDABlobVerifier.NonSignerStakesAndSignature); ok { + r0 = rf(ctx, signedBatch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*contractEigenDABlobVerifier.NonSignerStakesAndSignature) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v2.SignedBatch) error); ok { + r1 = rf(ctx, signedBatch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // VerifyBlobV2 provides a mock function with given fields: ctx, eigenDACert func (_m *MockBlobVerifier) VerifyBlobV2(ctx context.Context, eigenDACert *verification.EigenDACert) error { ret := _m.Called(ctx, eigenDACert) diff --git a/api/clients/v2/payload_client_config.go b/api/clients/v2/payload_client_config.go new file mode 100644 index 0000000000..e69efde9b8 --- /dev/null +++ b/api/clients/v2/payload_client_config.go @@ -0,0 +1,43 @@ +package clients + +import ( + "time" + + "github.com/Layr-Labs/eigenda/api/clients/codecs" + "github.com/Layr-Labs/eigenda/core" + v2 "github.com/Layr-Labs/eigenda/core/v2" +) + +// PayloadClientConfig contains configuration values that are needed by both PayloadRetriever and PayloadDisperser +type PayloadClientConfig struct { + // The blob encoding version to use when writing and reading blobs + BlobEncodingVersion codecs.BlobEncodingVersion + + // The Ethereum RPC URL to use for querying the Ethereum blockchain. + EthRpcUrl string + + // The address of the EigenDABlobVerifier contract + EigenDABlobVerifierAddr string + + // PayloadPolynomialForm is the initial form of a Payload after being encoded. The configured form does not imply + // any restrictions on the contents of a payload: it merely dictates how payload data is treated after being + // encoded. + // + // Since blobs sent to the disperser must be in coefficient form, the initial form of the encoded payload dictates + // what data processing must be performed during blob construction. + // + // The chosen form also dictates how the KZG commitment made to the blob can be used. If the encoded payload starts + // in PolynomialFormEval (meaning the data WILL be IFFTed before computing the commitment) then it will be possible + // to open points on the KZG commitment to prove that the field elements correspond to the commitment. If the + // encoded payload starts in PolynomialFormCoeff (meaning the data will NOT be IFFTed before computing the + // commitment) then it will not be possible to create a commitment opening: the blob will need to be supplied in its + // entirety to perform a verification that any part of the data matches the KZG commitment. + PayloadPolynomialForm codecs.PolynomialForm + + // The timeout duration for contract calls + ContractCallTimeout time.Duration + + BlobVersion v2.BlobVersion + + Quorums []core.QuorumID +} diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go new file mode 100644 index 0000000000..2edd519ff1 --- /dev/null +++ b/api/clients/v2/payload_disperser.go @@ -0,0 +1,227 @@ +package clients + +import ( + "context" + "fmt" + "time" + + "github.com/Layr-Labs/eigenda/api/clients/codecs" + "github.com/Layr-Labs/eigenda/api/clients/v2/verification" + dispgrpc "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2" + "github.com/Layr-Labs/eigenda/common/geth" + core "github.com/Layr-Labs/eigenda/core/v2" + "github.com/Layr-Labs/eigenda/encoding" + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" +) + +// PayloadDisperser provides the ability to disperse payloads to EigenDA +// +// This struct is goroutine safe. +type PayloadDisperser struct { + log logging.Logger + config *PayloadDisperserConfig + codec codecs.BlobCodec + disperserClient DisperserClient + blobVerifier verification.IBlobVerifier +} + +// BuildPayloadDisperser builds a PayloadDisperser from config structs +func BuildPayloadDisperser( + log logging.Logger, + payloadDisperserConfig *PayloadDisperserConfig, + disperserClientConfig *DisperserClientConfig, +// signer to sign blob dispersal requests + signer core.BlobRequestSigner, +// prover is used to compute commitments to a new blob during the dispersal process +// +// IMPORTANT: it is permissible for the prover parameter to be nil, but operating with this configuration +// puts a trust assumption on the disperser. With a nil prover, the disperser is responsible for computing +// the commitments to a blob, and the PayloadDisperser doesn't have a mechanism to verify these commitments. +// +// TODO: In the future, an optimized method of commitment verification using fiat shamir transformation will +// be implemented. This feature will allow a PayloadDisperser to offload commitment generation onto the +// disperser, but the disperser's commitments will be verifiable without needing a full-fledged prover + prover encoding.Prover, + accountant *Accountant, + ethConfig geth.EthClientConfig, +) (*PayloadDisperser, error) { + + codec, err := codecs.CreateCodec( + payloadDisperserConfig.PayloadPolynomialForm, + payloadDisperserConfig.BlobEncodingVersion) + if err != nil { + return nil, fmt.Errorf("create codec: %w", err) + } + + disperserClient, err := NewDisperserClient(disperserClientConfig, signer, prover, accountant) + if err != nil { + return nil, fmt.Errorf("unable to create disperser client: %s", err) + } + + ethClient, err := geth.NewClient(ethConfig, gethcommon.Address{}, 0, log) + if err != nil { + return nil, fmt.Errorf("new eth client: %w", err) + } + + blobVerifier, err := verification.NewBlobVerifier(*ethClient, payloadDisperserConfig.EigenDABlobVerifierAddr) + if err != nil { + return nil, fmt.Errorf("new blob verifier: %w", err) + } + + return &PayloadDisperser{ + log: log, + config: payloadDisperserConfig, + codec: codec, + disperserClient: disperserClient, + blobVerifier: blobVerifier, + }, nil +} + +// SendPayload executes the dispersal of a payload, with these basic steps: +// +// 1. Encode payload into a blob +// 2. Disperse the blob +// 3. Continually poll the disperser with GetBlobStatus, until a terminal status is reached, or the polling timeout +// is reached +// 4. Construct an EigenDACert if dispersal is successful +// 5. Verify the constructed cert with a call to an ethereum contract +// 6. Return the valid cert +func (pd *PayloadDisperser) SendPayload( + ctx context.Context, + payload []byte, + salt uint32, +) (*verification.EigenDACert, error) { + + blobBytes, err := pd.codec.EncodeBlob(payload) + if err != nil { + return nil, fmt.Errorf("encode payload to blob: %w", err) + } + + timeoutCtx, cancel := context.WithTimeout(ctx, pd.config.DisperseBlobTimeout) + defer cancel() + blobStatus, blobKey, err := pd.disperserClient.DisperseBlob( + timeoutCtx, + blobBytes, + pd.config.BlobVersion, + pd.config.Quorums, + salt) + + if err != nil { + return nil, fmt.Errorf("disperse blob: %w", err) + } + + timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.BlobCertifiedTimeout) + defer cancel() + + blobStatusReply, err := pd.pollBlobStatus(timeoutCtx, blobKey, blobStatus.ToProfobuf()) + if err != nil { + return nil, fmt.Errorf("poll blob status: %w", err) + } + + timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.ContractCallTimeout) + defer cancel() + nonSignerStakesAndSignature, err := pd.blobVerifier.GetNonSignerStakesAndSignature( + timeoutCtx, blobStatusReply.GetSignedBatch()) + if err != nil { + return nil, fmt.Errorf("get non signer stake and signature: %w", err) + } + + eigenDACert, err := verification.BuildEigenDACert( + blobStatusReply.GetBlobInclusionInfo(), + blobStatusReply.GetSignedBatch().GetHeader(), + nonSignerStakesAndSignature) + + if err != nil { + return nil, fmt.Errorf("build eigen da cert: %w", err) + } + + err = pd.verifyCertWithTimeout(ctx, eigenDACert) + if err != nil { + return nil, fmt.Errorf("verify cert with timeout for blobKey %v: %w", blobKey, err) + } + + return eigenDACert, nil +} + +// Close is responsible for calling close on all internal clients. This method will do its best to close all internal +// clients, even if some closes fail. +// +// Any and all errors returned from closing internal clients will be joined and returned. +// +// This method should only be called once. +func (pd *PayloadDisperser) Close() error { + err := pd.disperserClient.Close() + if err != nil { + return fmt.Errorf("close disperser client: %w", err) + } + + return nil +} + +func (pd *PayloadDisperser) pollBlobStatus( + ctx context.Context, + blobKey core.BlobKey, + initialStatus dispgrpc.BlobStatus, +) (*dispgrpc.BlobStatusReply, error) { + + previousStatus := initialStatus + + ticker := time.NewTicker(pd.config.BlobStatusPollInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return nil, fmt.Errorf( + "timed out waiting for %v blob status, final status was %v: %w", + dispgrpc.BlobStatus_CERTIFIED, + previousStatus, + ctx.Err()) + case <-ticker.C: + // This call to the disperser doesn't have a dedicated timeout configured. + // If this call fails to return in a timely fashion, the timeout configured for the poll loop will trigger + blobStatusReply, err := pd.disperserClient.GetBlobStatus(ctx, blobKey) + + if err != nil { + pd.log.Warn("get blob status", "err", err, "blobKey", blobKey) + continue + } + + newStatus := blobStatusReply.Status + if newStatus != previousStatus { + pd.log.Info( + "Blob status changed", + "blob key", + blobKey, + "previous status", + previousStatus, + "new status", + newStatus) + previousStatus = newStatus + } + + switch newStatus { + case dispgrpc.BlobStatus_CERTIFIED: + return blobStatusReply, nil + case dispgrpc.BlobStatus_QUEUED, dispgrpc.BlobStatus_ENCODED: + continue + default: + return nil, fmt.Errorf("terminal dispersal failure for blobKey %v. blob status: %v", blobKey, newStatus) + } + } + } +} + +// verifyCertWithTimeout verifies an EigenDACert by making a call to VerifyBlobV2. +// +// This method times out after the duration configured in PayloadDisperserConfig.ContractCallTimeout +func (pd *PayloadDisperser) verifyCertWithTimeout( + ctx context.Context, + eigenDACert *verification.EigenDACert, +) error { + timeoutCtx, cancel := context.WithTimeout(ctx, pd.config.ContractCallTimeout) + defer cancel() + + return pd.blobVerifier.VerifyBlobV2(timeoutCtx, eigenDACert) +} diff --git a/api/clients/v2/payload_disperser_config.go b/api/clients/v2/payload_disperser_config.go new file mode 100644 index 0000000000..392d6a5c5c --- /dev/null +++ b/api/clients/v2/payload_disperser_config.go @@ -0,0 +1,21 @@ +package clients + +import "time" + +// PayloadDisperserConfig contains an embedded PayloadClientConfig, plus all additional configuration values needed +// by a PayloadDisperser +type PayloadDisperserConfig struct { + PayloadClientConfig + + // DisperseBlobTimeout is the duration after which the PayloadDisperser will time out, when trying to disperse a + // blob + DisperseBlobTimeout time.Duration + + // BlobCertifiedTimeout is the duration after which the PayloadDisperser will time out, while polling + // the disperser for blob status, waiting for BlobStatus_CERTIFIED + BlobCertifiedTimeout time.Duration + + // BlobStatusPollInterval is the tick rate for the PayloadDisperser to use, while polling the disperser with + // GetBlobStatus. + BlobStatusPollInterval time.Duration +} diff --git a/api/clients/v2/payload_retriever.go b/api/clients/v2/payload_retriever.go index 9eba64014a..7c7afe71ea 100644 --- a/api/clients/v2/payload_retriever.go +++ b/api/clients/v2/payload_retriever.go @@ -59,7 +59,7 @@ func BuildPayloadRetriever( return nil, fmt.Errorf("new blob verifier: %w", err) } - codec, err := createCodec(payloadRetrieverConfig) + codec, err := codecs.CreateCodec(payloadRetrieverConfig.PayloadPolynomialForm, payloadRetrieverConfig.BlobEncodingVersion) if err != nil { return nil, err } @@ -181,6 +181,8 @@ func (pr *PayloadRetriever) verifyBlobAgainstCert( kzgCommitment *encoding.G1Commitment, blobLength uint) error { + rand.Int63() + // 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, relayKey) @@ -258,27 +260,6 @@ func (pr *PayloadRetriever) Close() error { return nil } -// createCodec creates the codec based on client config values -func createCodec(config *PayloadRetrieverConfig) (codecs.BlobCodec, error) { - lowLevelCodec, err := codecs.BlobEncodingVersionToCodec(config.BlobEncodingVersion) - if err != nil { - return nil, fmt.Errorf("create low level codec: %w", err) - } - - switch config.PayloadPolynomialForm { - case codecs.PolynomialFormCoeff: - // Data must NOT be IFFTed during blob construction, since the payload is already in PolynomialFormCoeff after - // being encoded. - return codecs.NewNoIFFTCodec(lowLevelCodec), nil - case codecs.PolynomialFormEval: - // Data MUST be IFFTed during blob construction, since the payload is in PolynomialFormEval after being encoded, - // but must be in PolynomialFormCoeff to produce a valid blob. - return codecs.NewIFFTCodec(lowLevelCodec), nil - default: - return nil, fmt.Errorf("unsupported polynomial form: %d", config.PayloadPolynomialForm) - } -} - // blobCommitmentsBindingToInternal converts a blob commitment from an eigenDA cert into the internal // encoding.BlobCommitments type func blobCommitmentsBindingToInternal( diff --git a/api/clients/v2/payload_retriever_config.go b/api/clients/v2/payload_retriever_config.go index 670d68ce39..9e168662bd 100644 --- a/api/clients/v2/payload_retriever_config.go +++ b/api/clients/v2/payload_retriever_config.go @@ -1,40 +1,12 @@ package clients -import ( - "time" +import "time" - "github.com/Layr-Labs/eigenda/api/clients/codecs" -) - -// PayloadRetrieverConfig contains configuration values for a PayloadRetriever +// PayloadRetrieverConfig contains an embedded PayloadClientConfig, plus all additional configuration values needed +// by a PayloadRetriever type PayloadRetrieverConfig struct { - // The blob encoding version to use when writing and reading blobs - BlobEncodingVersion codecs.BlobEncodingVersion - - // The Ethereum RPC URL to use for querying the Ethereum blockchain. - EthRpcUrl string - - // The address of the EigenDABlobVerifier contract - EigenDABlobVerifierAddr string - - // PayloadPolynomialForm is the initial form of a Payload after being encoded. The configured form does not imply - // any restrictions on the contents of a payload: it merely dictates how payload data is treated after being - // encoded. - // - // Since blobs sent to the disperser must be in coefficient form, the initial form of the encoded payload dictates - // what data processing must be performed during blob construction. - // - // The chosen form also dictates how the KZG commitment made to the blob can be used. If the encoded payload starts - // in PolynomialFormEval (meaning the data WILL be IFFTed before computing the commitment) then it will be possible - // to open points on the KZG commitment to prove that the field elements correspond to the commitment. If the - // encoded payload starts in PolynomialFormCoeff (meaning the data will NOT be IFFTed before computing the - // commitment) then it will not be possible to create a commitment opening: the blob will need to be supplied in its - // entirety to perform a verification that any part of the data matches the KZG commitment. - PayloadPolynomialForm codecs.PolynomialForm + PayloadClientConfig // The timeout duration for relay calls to retrieve blobs. RelayTimeout time.Duration - - // The timeout duration for contract calls - ContractCallTimeout time.Duration } diff --git a/api/clients/v2/verification/blob_verifier.go b/api/clients/v2/verification/blob_verifier.go index 439886379f..aa1f60898b 100644 --- a/api/clients/v2/verification/blob_verifier.go +++ b/api/clients/v2/verification/blob_verifier.go @@ -20,8 +20,15 @@ type IBlobVerifier interface { ctx context.Context, eigenDACert *EigenDACert, ) error + + GetNonSignerStakesAndSignature( + ctx context.Context, + signedBatch *disperser.SignedBatch, + ) (*verifierBindings.NonSignerStakesAndSignature, error) } +var _ IBlobVerifier = &BlobVerifier{} + // BlobVerifier is responsible for making eth calls against the BlobVerifier contract to ensure cryptographic and // structural integrity of V2 certificates // @@ -102,3 +109,26 @@ func (v *BlobVerifier) VerifyBlobV2( return nil } + +// GetNonSignerStakesAndSignature calls the getNonSignerStakesAndSignature view function on the EigenDABlobVerifier +// contract, and returns the resulting NonSignerStakesAndSignature object. +func (v *BlobVerifier) GetNonSignerStakesAndSignature( + ctx context.Context, + signedBatch *disperser.SignedBatch, +) (*verifierBindings.NonSignerStakesAndSignature, error) { + + signedBatchBinding, err := SignedBatchProtoToBinding(signedBatch) + if err != nil { + return nil, fmt.Errorf("convert signed batch: %s", err) + } + + nonSignerStakesAndSignature, err := v.blobVerifierCaller.GetNonSignerStakesAndSignature( + &bind.CallOpts{Context: ctx}, + *signedBatchBinding) + + if err != nil { + return nil, fmt.Errorf("get non signer stakes and signature: %s", err) + } + + return &nonSignerStakesAndSignature, nil +} diff --git a/api/clients/v2/verification/eigenda_cert.go b/api/clients/v2/verification/eigenda_cert.go index 38bc3677f2..475766894d 100644 --- a/api/clients/v2/verification/eigenda_cert.go +++ b/api/clients/v2/verification/eigenda_cert.go @@ -1,6 +1,11 @@ package verification import ( + "fmt" + + disperser "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2" + + commonv2 "github.com/Layr-Labs/eigenda/api/grpc/common/v2" contractEigenDABlobVerifier "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDABlobVerifier" ) @@ -12,3 +17,30 @@ type EigenDACert struct { BatchHeader contractEigenDABlobVerifier.BatchHeaderV2 NonSignerStakesAndSignature contractEigenDABlobVerifier.NonSignerStakesAndSignature } + +// BuildEigenDACert creates a new EigenDACert from a BlobInclusionInfo, BatchHeader, and NonSignerStakesAndSignature +// +// For convenience, this function accepts arguments as protobufs where applicable, since that's the form the caller +// will have. +func BuildEigenDACert( + blobInclusionInfo *disperser.BlobInclusionInfo, + batchHeader *commonv2.BatchHeader, + nonSignerStakesAndSignature *contractEigenDABlobVerifier.NonSignerStakesAndSignature, +) (*EigenDACert, error) { + + bindingVerificationProof, err := VerificationProofProtoToBinding(blobInclusionInfo) + if err != nil { + return nil, fmt.Errorf("convert inclusion info to binding: %w", err) + } + + bindingBatchHeader, err := BatchHeaderProtoToBinding(batchHeader) + if err != nil { + return nil, fmt.Errorf("convert batch header to binding: %w", err) + } + + return &EigenDACert{ + BlobVerificationProof: *bindingVerificationProof, + BatchHeader: *bindingBatchHeader, + NonSignerStakesAndSignature: *nonSignerStakesAndSignature, + }, nil +} From e22151fa0dd50cce0945030f42454609dab2113b Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:06:35 -0500 Subject: [PATCH 02/21] Clean up Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_client_config.go | 4 +- api/clients/v2/payload_disperser.go | 92 +++++++++++-------------- api/clients/v2/payload_retriever.go | 2 +- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/api/clients/v2/payload_client_config.go b/api/clients/v2/payload_client_config.go index e69efde9b8..e5f9c791f9 100644 --- a/api/clients/v2/payload_client_config.go +++ b/api/clients/v2/payload_client_config.go @@ -16,8 +16,8 @@ type PayloadClientConfig struct { // The Ethereum RPC URL to use for querying the Ethereum blockchain. EthRpcUrl string - // The address of the EigenDABlobVerifier contract - EigenDABlobVerifierAddr string + // The address of the EigenDACertVerifier contract + EigenDACertVerifierAddr string // PayloadPolynomialForm is the initial form of a Payload after being encoded. The configured form does not imply // any restrictions on the contents of a payload: it merely dictates how payload data is treated after being diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 2edd519ff1..3265964cb5 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -19,29 +19,29 @@ import ( // // This struct is goroutine safe. type PayloadDisperser struct { - log logging.Logger + logger logging.Logger config *PayloadDisperserConfig codec codecs.BlobCodec disperserClient DisperserClient - blobVerifier verification.IBlobVerifier + certVerifier verification.IBlobVerifier } // BuildPayloadDisperser builds a PayloadDisperser from config structs func BuildPayloadDisperser( - log logging.Logger, + logger logging.Logger, payloadDisperserConfig *PayloadDisperserConfig, disperserClientConfig *DisperserClientConfig, -// signer to sign blob dispersal requests + // signer to sign blob dispersal requests signer core.BlobRequestSigner, -// prover is used to compute commitments to a new blob during the dispersal process -// -// IMPORTANT: it is permissible for the prover parameter to be nil, but operating with this configuration -// puts a trust assumption on the disperser. With a nil prover, the disperser is responsible for computing -// the commitments to a blob, and the PayloadDisperser doesn't have a mechanism to verify these commitments. -// -// TODO: In the future, an optimized method of commitment verification using fiat shamir transformation will -// be implemented. This feature will allow a PayloadDisperser to offload commitment generation onto the -// disperser, but the disperser's commitments will be verifiable without needing a full-fledged prover + // prover is used to compute commitments to a new blob during the dispersal process + // + // IMPORTANT: it is permissible for the prover parameter to be nil, but operating with this configuration + // puts a trust assumption on the disperser. With a nil prover, the disperser is responsible for computing + // the commitments to a blob, and the PayloadDisperser doesn't have a mechanism to verify these commitments. + // + // TODO: In the future, an optimized method of commitment verification using fiat shamir transformation will + // be implemented. This feature will allow a PayloadDisperser to offload commitment generation onto the + // disperser, but the disperser's commitments will be verifiable without needing a full-fledged prover prover encoding.Prover, accountant *Accountant, ethConfig geth.EthClientConfig, @@ -56,40 +56,43 @@ func BuildPayloadDisperser( disperserClient, err := NewDisperserClient(disperserClientConfig, signer, prover, accountant) if err != nil { - return nil, fmt.Errorf("unable to create disperser client: %s", err) + return nil, fmt.Errorf("new disperser client: %s", err) } - ethClient, err := geth.NewClient(ethConfig, gethcommon.Address{}, 0, log) + ethClient, err := geth.NewClient(ethConfig, gethcommon.Address{}, 0, logger) if err != nil { return nil, fmt.Errorf("new eth client: %w", err) } - blobVerifier, err := verification.NewBlobVerifier(*ethClient, payloadDisperserConfig.EigenDABlobVerifierAddr) + certVerifier, err := verification.NewBlobVerifier(*ethClient, payloadDisperserConfig.EigenDACertVerifierAddr) if err != nil { - return nil, fmt.Errorf("new blob verifier: %w", err) + return nil, fmt.Errorf("new cert verifier: %w", err) } return &PayloadDisperser{ - log: log, + logger: logger, config: payloadDisperserConfig, codec: codec, disperserClient: disperserClient, - blobVerifier: blobVerifier, + certVerifier: certVerifier, }, nil } -// SendPayload executes the dispersal of a payload, with these basic steps: +// SendPayload executes the dispersal of a payload, with these steps: // // 1. Encode payload into a blob // 2. Disperse the blob -// 3. Continually poll the disperser with GetBlobStatus, until a terminal status is reached, or the polling timeout -// is reached +// 3. Poll the disperser with GetBlobStatus until a terminal status is reached, or until the polling timeout is reached // 4. Construct an EigenDACert if dispersal is successful // 5. Verify the constructed cert with a call to an ethereum contract // 6. Return the valid cert func (pd *PayloadDisperser) SendPayload( ctx context.Context, + // payload is the raw data to be stored on eigenDA payload []byte, + // salt is added while constructing the blob header + // This salt should be utilized if a blob dispersal fails, in order to retry dispersing the same payload under a + // different blob key. salt uint32, ) (*verification.EigenDACert, error) { @@ -97,6 +100,7 @@ func (pd *PayloadDisperser) SendPayload( if err != nil { return nil, fmt.Errorf("encode payload to blob: %w", err) } + pd.logger.Debug("Payload encoded to blob") timeoutCtx, cancel := context.WithTimeout(ctx, pd.config.DisperseBlobTimeout) defer cancel() @@ -106,40 +110,44 @@ func (pd *PayloadDisperser) SendPayload( pd.config.BlobVersion, pd.config.Quorums, salt) - if err != nil { return nil, fmt.Errorf("disperse blob: %w", err) } + pd.logger.Debug("Successful DisperseBlob", "blobStatus", blobStatus, "blobKey", blobKey) timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.BlobCertifiedTimeout) defer cancel() - blobStatusReply, err := pd.pollBlobStatus(timeoutCtx, blobKey, blobStatus.ToProfobuf()) if err != nil { return nil, fmt.Errorf("poll blob status: %w", err) } + pd.logger.Debug("Blob status CERTIFIED", "blobKey", blobKey) timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.ContractCallTimeout) defer cancel() - nonSignerStakesAndSignature, err := pd.blobVerifier.GetNonSignerStakesAndSignature( + nonSignerStakesAndSignature, err := pd.certVerifier.GetNonSignerStakesAndSignature( timeoutCtx, blobStatusReply.GetSignedBatch()) if err != nil { return nil, fmt.Errorf("get non signer stake and signature: %w", err) } + pd.logger.Debug("Retrieved NonSignerStakesAndSignature", "blobKey", blobKey) eigenDACert, err := verification.BuildEigenDACert( blobStatusReply.GetBlobInclusionInfo(), blobStatusReply.GetSignedBatch().GetHeader(), nonSignerStakesAndSignature) - if err != nil { return nil, fmt.Errorf("build eigen da cert: %w", err) } + pd.logger.Debug("Constructed EigenDACert", "blobKey", blobKey) - err = pd.verifyCertWithTimeout(ctx, eigenDACert) + timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.ContractCallTimeout) + defer cancel() + err = pd.certVerifier.VerifyBlobV2(timeoutCtx, eigenDACert) if err != nil { - return nil, fmt.Errorf("verify cert with timeout for blobKey %v: %w", blobKey, err) + return nil, fmt.Errorf("verify cert for blobKey %v: %w", blobKey, err) } + pd.logger.Debug("EigenDACert verified", "blobKey", blobKey) return eigenDACert, nil } @@ -159,6 +167,7 @@ func (pd *PayloadDisperser) Close() error { return nil } +// pollBlobStatus polls the disperser for the status of a blob that has been dispersed func (pd *PayloadDisperser) pollBlobStatus( ctx context.Context, blobKey core.BlobKey, @@ -182,22 +191,18 @@ func (pd *PayloadDisperser) pollBlobStatus( // This call to the disperser doesn't have a dedicated timeout configured. // If this call fails to return in a timely fashion, the timeout configured for the poll loop will trigger blobStatusReply, err := pd.disperserClient.GetBlobStatus(ctx, blobKey) - if err != nil { - pd.log.Warn("get blob status", "err", err, "blobKey", blobKey) + pd.logger.Warn("get blob status", "err", err, "blobKey", blobKey) continue } newStatus := blobStatusReply.Status if newStatus != previousStatus { - pd.log.Info( + pd.logger.Debug( "Blob status changed", - "blob key", - blobKey, - "previous status", - previousStatus, - "new status", - newStatus) + "blob key", blobKey, + "previous status", previousStatus, + "new status", newStatus) previousStatus = newStatus } @@ -212,16 +217,3 @@ func (pd *PayloadDisperser) pollBlobStatus( } } } - -// verifyCertWithTimeout verifies an EigenDACert by making a call to VerifyBlobV2. -// -// This method times out after the duration configured in PayloadDisperserConfig.ContractCallTimeout -func (pd *PayloadDisperser) verifyCertWithTimeout( - ctx context.Context, - eigenDACert *verification.EigenDACert, -) error { - timeoutCtx, cancel := context.WithTimeout(ctx, pd.config.ContractCallTimeout) - defer cancel() - - return pd.blobVerifier.VerifyBlobV2(timeoutCtx, eigenDACert) -} diff --git a/api/clients/v2/payload_retriever.go b/api/clients/v2/payload_retriever.go index 7c7afe71ea..fbd2f56363 100644 --- a/api/clients/v2/payload_retriever.go +++ b/api/clients/v2/payload_retriever.go @@ -54,7 +54,7 @@ func BuildPayloadRetriever( return nil, fmt.Errorf("new eth client: %w", err) } - blobVerifier, err := verification.NewBlobVerifier(*ethClient, payloadRetrieverConfig.EigenDABlobVerifierAddr) + blobVerifier, err := verification.NewBlobVerifier(*ethClient, payloadRetrieverConfig.EigenDACertVerifierAddr) if err != nil { return nil, fmt.Errorf("new blob verifier: %w", err) } From ab591ba78ada4a1fffc3e6aff0afd572708f5a1a Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:16:49 -0500 Subject: [PATCH 03/21] Shorten name of config member Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_retriever.go | 32 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/api/clients/v2/payload_retriever.go b/api/clients/v2/payload_retriever.go index fbd2f56363..5fb3122f8a 100644 --- a/api/clients/v2/payload_retriever.go +++ b/api/clients/v2/payload_retriever.go @@ -28,12 +28,12 @@ type PayloadRetriever struct { // random doesn't need to be cryptographically secure, as it's only used to distribute load across relays. // Not all methods on Rand are guaranteed goroutine safe: if additional usages of random are added, they // must be evaluated for thread safety. - random *rand.Rand - payloadRetrieverConfig *PayloadRetrieverConfig - codec codecs.BlobCodec - relayClient RelayClient - g1Srs []bn254.G1Affine - blobVerifier verification.IBlobVerifier + random *rand.Rand + config *PayloadRetrieverConfig + codec codecs.BlobCodec + relayClient RelayClient + g1Srs []bn254.G1Affine + blobVerifier verification.IBlobVerifier } // BuildPayloadRetriever builds a PayloadRetriever from config structs. @@ -85,13 +85,13 @@ func NewPayloadRetriever( g1Srs []bn254.G1Affine) (*PayloadRetriever, error) { return &PayloadRetriever{ - log: log, - random: random, - payloadRetrieverConfig: payloadRetrieverConfig, - codec: codec, - relayClient: relayClient, - blobVerifier: blobVerifier, - g1Srs: g1Srs, + log: log, + random: random, + config: payloadRetrieverConfig, + codec: codec, + relayClient: relayClient, + blobVerifier: blobVerifier, + g1Srs: g1Srs, }, nil } @@ -181,8 +181,6 @@ func (pr *PayloadRetriever) verifyBlobAgainstCert( kzgCommitment *encoding.G1Commitment, blobLength uint) error { - rand.Int63() - // 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, relayKey) @@ -226,7 +224,7 @@ func (pr *PayloadRetriever) getBlobWithTimeout( relayKey core.RelayKey, blobKey core.BlobKey) ([]byte, error) { - timeoutCtx, cancel := context.WithTimeout(ctx, pr.payloadRetrieverConfig.RelayTimeout) + timeoutCtx, cancel := context.WithTimeout(ctx, pr.config.RelayTimeout) defer cancel() return pr.relayClient.GetBlob(timeoutCtx, relayKey, blobKey) @@ -239,7 +237,7 @@ func (pr *PayloadRetriever) verifyCertWithTimeout( ctx context.Context, eigenDACert *verification.EigenDACert, ) error { - timeoutCtx, cancel := context.WithTimeout(ctx, pr.payloadRetrieverConfig.ContractCallTimeout) + timeoutCtx, cancel := context.WithTimeout(ctx, pr.config.ContractCallTimeout) defer cancel() return pr.blobVerifier.VerifyBlobV2(timeoutCtx, eigenDACert) From bbb76ccdeae72481e1072137711e96d95aaf1a4d Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:32:33 -0500 Subject: [PATCH 04/21] Expand method doc Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 3265964cb5..a0262a54ad 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -168,6 +168,9 @@ func (pd *PayloadDisperser) Close() error { } // pollBlobStatus polls the disperser for the status of a blob that has been dispersed +// +// This method will only return a non-nil BlobStatusReply if the blob is reported to be CERTIFIED prior to the timeout. +// In all other cases, this method will return a nil BlobStatusReply, along with an error describing the failure. func (pd *PayloadDisperser) pollBlobStatus( ctx context.Context, blobKey core.BlobKey, From 501a2d3af13bdc33bc45343bd7a8d0cc1a0978b9 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:03:47 -0500 Subject: [PATCH 05/21] Clean up cert verifier changes Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/verification/cert_verifier.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/clients/v2/verification/cert_verifier.go b/api/clients/v2/verification/cert_verifier.go index 1e71ee94de..73b682d359 100644 --- a/api/clients/v2/verification/cert_verifier.go +++ b/api/clients/v2/verification/cert_verifier.go @@ -40,7 +40,7 @@ var _ ICertVerifier = &CertVerifier{} // NewCertVerifier constructs a CertVerifier func NewCertVerifier( - ethClient geth.EthClient, // the eth client, which should already be set up + ethClient geth.EthClient, // the eth client, which should already be set up certVerifierAddress string, // the hex address of the EigenDACertVerifier contract ) (*CertVerifier, error) { @@ -110,9 +110,9 @@ func (cv *CertVerifier) VerifyCertV2( return nil } -// GetNonSignerStakesAndSignature calls the getNonSignerStakesAndSignature view function on the EigenDABlobVerifier +// GetNonSignerStakesAndSignature calls the getNonSignerStakesAndSignature view function on the EigenDACertVerifier // contract, and returns the resulting NonSignerStakesAndSignature object. -func (v *CertVerifier) GetNonSignerStakesAndSignature( +func (cv *CertVerifier) GetNonSignerStakesAndSignature( ctx context.Context, signedBatch *disperser.SignedBatch, ) (*verifierBindings.NonSignerStakesAndSignature, error) { @@ -122,7 +122,7 @@ func (v *CertVerifier) GetNonSignerStakesAndSignature( return nil, fmt.Errorf("convert signed batch: %s", err) } - nonSignerStakesAndSignature, err := v.certVerifierCaller.GetNonSignerStakesAndSignature( + nonSignerStakesAndSignature, err := cv.certVerifierCaller.GetNonSignerStakesAndSignature( &bind.CallOpts{Context: ctx}, *signedBatchBinding) From 2f4f4a88062c2bf18798ea233432898be7381c46 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:20:34 -0500 Subject: [PATCH 06/21] Fix some nits Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_client_config.go | 2 +- api/clients/v2/payload_disperser.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/clients/v2/payload_client_config.go b/api/clients/v2/payload_client_config.go index e5f9c791f9..96c4902568 100644 --- a/api/clients/v2/payload_client_config.go +++ b/api/clients/v2/payload_client_config.go @@ -13,7 +13,7 @@ type PayloadClientConfig struct { // The blob encoding version to use when writing and reading blobs BlobEncodingVersion codecs.BlobEncodingVersion - // The Ethereum RPC URL to use for querying the Ethereum blockchain. + // The Ethereum RPC URL to use for querying an Ethereum network EthRpcUrl string // The address of the EigenDACertVerifier contract diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 798add2ec7..ab5d65eb4a 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -15,7 +15,7 @@ import ( gethcommon "github.com/ethereum/go-ethereum/common" ) -// PayloadDisperser provides the ability to disperse payloads to EigenDA +// PayloadDisperser provides the ability to disperse payloads to EigenDA via a Disperser grpc service. // // This struct is goroutine safe. type PayloadDisperser struct { @@ -84,7 +84,7 @@ func BuildPayloadDisperser( // 2. Disperse the blob // 3. Poll the disperser with GetBlobStatus until a terminal status is reached, or until the polling timeout is reached // 4. Construct an EigenDACert if dispersal is successful -// 5. Verify the constructed cert with a call to an ethereum contract +// 5. Verify the constructed cert with an eth_call to the EigenDACertVerifier contract // 6. Return the valid cert func (pd *PayloadDisperser) SendPayload( ctx context.Context, From ab2bf0584a7307fa167383c057f844d322b56a4f Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:30:42 -0500 Subject: [PATCH 07/21] Rename pollBlobStatus Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index ab5d65eb4a..6262b132b8 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -117,7 +117,7 @@ func (pd *PayloadDisperser) SendPayload( timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.BlobCertifiedTimeout) defer cancel() - blobStatusReply, err := pd.pollBlobStatus(timeoutCtx, blobKey, blobStatus.ToProfobuf()) + blobStatusReply, err := pd.pollBlobStatusUntilCertified(timeoutCtx, blobKey, blobStatus.ToProfobuf()) if err != nil { return nil, fmt.Errorf("poll blob status: %w", err) } @@ -167,11 +167,11 @@ func (pd *PayloadDisperser) Close() error { return nil } -// pollBlobStatus polls the disperser for the status of a blob that has been dispersed +// pollBlobStatusUntilCertified polls the disperser for the status of a blob that has been dispersed // // This method will only return a non-nil BlobStatusReply if the blob is reported to be CERTIFIED prior to the timeout. // In all other cases, this method will return a nil BlobStatusReply, along with an error describing the failure. -func (pd *PayloadDisperser) pollBlobStatus( +func (pd *PayloadDisperser) pollBlobStatusUntilCertified( ctx context.Context, blobKey core.BlobKey, initialStatus dispgrpc.BlobStatus, From a5bc6241d0b595081a5efd320a98255f10644910 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:40:24 -0500 Subject: [PATCH 08/21] Create utility method to build eigen DA cert Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 49 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 6262b132b8..1c1df27804 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -119,27 +119,15 @@ func (pd *PayloadDisperser) SendPayload( defer cancel() blobStatusReply, err := pd.pollBlobStatusUntilCertified(timeoutCtx, blobKey, blobStatus.ToProfobuf()) if err != nil { - return nil, fmt.Errorf("poll blob status: %w", err) + return nil, fmt.Errorf("poll blob status until certified: %w", err) } pd.logger.Debug("Blob status CERTIFIED", "blobKey", blobKey) - timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.ContractCallTimeout) - defer cancel() - nonSignerStakesAndSignature, err := pd.certVerifier.GetNonSignerStakesAndSignature( - timeoutCtx, blobStatusReply.GetSignedBatch()) - if err != nil { - return nil, fmt.Errorf("get non signer stake and signature: %w", err) - } - pd.logger.Debug("Retrieved NonSignerStakesAndSignature", "blobKey", blobKey) - - eigenDACert, err := verification.BuildEigenDACert( - blobStatusReply.GetBlobInclusionInfo(), - blobStatusReply.GetSignedBatch().GetHeader(), - nonSignerStakesAndSignature) + eigenDACert, err := pd.buildEigenDACert(ctx, blobKey, blobStatusReply) if err != nil { - return nil, fmt.Errorf("build eigen da cert: %w", err) + // error returned from method is sufficiently descriptive + return nil, err } - pd.logger.Debug("Constructed EigenDACert", "blobKey", blobKey) timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.ContractCallTimeout) defer cancel() @@ -220,3 +208,32 @@ func (pd *PayloadDisperser) pollBlobStatusUntilCertified( } } } + +// buildEigenDACert makes a call to the getNonSignerStakesAndSignature view function on the EigenDACertVerifier +// contract, and then assembles an EigenDACert +func (pd *PayloadDisperser) buildEigenDACert( + ctx context.Context, + blobKey core.BlobKey, + blobStatusReply *dispgrpc.BlobStatusReply, +) (*verification.EigenDACert, error) { + + timeoutCtx, cancel := context.WithTimeout(ctx, pd.config.ContractCallTimeout) + defer cancel() + nonSignerStakesAndSignature, err := pd.certVerifier.GetNonSignerStakesAndSignature( + timeoutCtx, blobStatusReply.GetSignedBatch()) + if err != nil { + return nil, fmt.Errorf("get non signer stake and signature: %w", err) + } + pd.logger.Debug("Retrieved NonSignerStakesAndSignature", "blobKey", blobKey) + + eigenDACert, err := verification.BuildEigenDACert( + blobStatusReply.GetBlobInclusionInfo(), + blobStatusReply.GetSignedBatch().GetHeader(), + nonSignerStakesAndSignature) + if err != nil { + return nil, fmt.Errorf("build eigen da cert: %w", err) + } + pd.logger.Debug("Constructed EigenDACert", "blobKey", blobKey) + + return eigenDACert, nil +} From 887e5b9278af001cb65043721c2208b6ac62a8ed Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:43:52 -0500 Subject: [PATCH 09/21] Combine config files Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- .../{payload_client_config.go => config.go} | 29 +++++++++++++++++++ api/clients/v2/payload_disperser_config.go | 21 -------------- api/clients/v2/payload_retriever_config.go | 12 -------- 3 files changed, 29 insertions(+), 33 deletions(-) rename api/clients/v2/{payload_client_config.go => config.go} (64%) delete mode 100644 api/clients/v2/payload_disperser_config.go delete mode 100644 api/clients/v2/payload_retriever_config.go diff --git a/api/clients/v2/payload_client_config.go b/api/clients/v2/config.go similarity index 64% rename from api/clients/v2/payload_client_config.go rename to api/clients/v2/config.go index 96c4902568..0cb992a57c 100644 --- a/api/clients/v2/payload_client_config.go +++ b/api/clients/v2/config.go @@ -41,3 +41,32 @@ type PayloadClientConfig struct { Quorums []core.QuorumID } + +// PayloadRetrieverConfig contains an embedded PayloadClientConfig, plus all additional configuration values needed +// by a PayloadRetriever +type PayloadRetrieverConfig struct { + PayloadClientConfig + + // The timeout duration for relay calls to retrieve blobs. + RelayTimeout time.Duration +} + +// PayloadDisperserConfig contains an embedded PayloadClientConfig, plus all additional configuration values needed +// by a PayloadDisperser +type PayloadDisperserConfig struct { + PayloadClientConfig + + // DisperseBlobTimeout is the duration after which the PayloadDisperser will time out, when trying to disperse a + // blob + DisperseBlobTimeout time.Duration + + // BlobCertifiedTimeout is the duration after which the PayloadDisperser will time out, while polling + // the disperser for blob status, waiting for BlobStatus_CERTIFIED + BlobCertifiedTimeout time.Duration + + // BlobStatusPollInterval is the tick rate for the PayloadDisperser to use, while polling the disperser with + // GetBlobStatus. + BlobStatusPollInterval time.Duration +} + + diff --git a/api/clients/v2/payload_disperser_config.go b/api/clients/v2/payload_disperser_config.go deleted file mode 100644 index 392d6a5c5c..0000000000 --- a/api/clients/v2/payload_disperser_config.go +++ /dev/null @@ -1,21 +0,0 @@ -package clients - -import "time" - -// PayloadDisperserConfig contains an embedded PayloadClientConfig, plus all additional configuration values needed -// by a PayloadDisperser -type PayloadDisperserConfig struct { - PayloadClientConfig - - // DisperseBlobTimeout is the duration after which the PayloadDisperser will time out, when trying to disperse a - // blob - DisperseBlobTimeout time.Duration - - // BlobCertifiedTimeout is the duration after which the PayloadDisperser will time out, while polling - // the disperser for blob status, waiting for BlobStatus_CERTIFIED - BlobCertifiedTimeout time.Duration - - // BlobStatusPollInterval is the tick rate for the PayloadDisperser to use, while polling the disperser with - // GetBlobStatus. - BlobStatusPollInterval time.Duration -} diff --git a/api/clients/v2/payload_retriever_config.go b/api/clients/v2/payload_retriever_config.go deleted file mode 100644 index 9e168662bd..0000000000 --- a/api/clients/v2/payload_retriever_config.go +++ /dev/null @@ -1,12 +0,0 @@ -package clients - -import "time" - -// PayloadRetrieverConfig contains an embedded PayloadClientConfig, plus all additional configuration values needed -// by a PayloadRetriever -type PayloadRetrieverConfig struct { - PayloadClientConfig - - // The timeout duration for relay calls to retrieve blobs. - RelayTimeout time.Duration -} From 32862cf30144ebc70c582a22385c95408b3d1785 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:50:25 -0500 Subject: [PATCH 10/21] Change eigenDA cert constructor signature Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 5 +---- api/clients/v2/verification/eigenda_cert.go | 13 ++++--------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 1c1df27804..e40a838022 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -226,10 +226,7 @@ func (pd *PayloadDisperser) buildEigenDACert( } pd.logger.Debug("Retrieved NonSignerStakesAndSignature", "blobKey", blobKey) - eigenDACert, err := verification.BuildEigenDACert( - blobStatusReply.GetBlobInclusionInfo(), - blobStatusReply.GetSignedBatch().GetHeader(), - nonSignerStakesAndSignature) + eigenDACert, err := verification.BuildEigenDACert(blobStatusReply, nonSignerStakesAndSignature) if err != nil { return nil, fmt.Errorf("build eigen da cert: %w", err) } diff --git a/api/clients/v2/verification/eigenda_cert.go b/api/clients/v2/verification/eigenda_cert.go index 131af1aa6f..4de160b99c 100644 --- a/api/clients/v2/verification/eigenda_cert.go +++ b/api/clients/v2/verification/eigenda_cert.go @@ -5,7 +5,6 @@ import ( disperser "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2" - commonv2 "github.com/Layr-Labs/eigenda/api/grpc/common/v2" contractEigenDABlobVerifier "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDABlobVerifier" ) @@ -18,22 +17,18 @@ type EigenDACert struct { NonSignerStakesAndSignature contractEigenDABlobVerifier.NonSignerStakesAndSignature } -// BuildEigenDACert creates a new EigenDACert from a BlobInclusionInfo, BatchHeader, and NonSignerStakesAndSignature -// -// For convenience, this function accepts arguments as protobufs where applicable, since that's the form the caller -// will have. +// BuildEigenDACert creates a new EigenDACert from a BlobStatusReply, and NonSignerStakesAndSignature func BuildEigenDACert( - blobInclusionInfo *disperser.BlobInclusionInfo, - batchHeader *commonv2.BatchHeader, + blobStatusReply *disperser.BlobStatusReply, nonSignerStakesAndSignature *contractEigenDABlobVerifier.NonSignerStakesAndSignature, ) (*EigenDACert, error) { - bindingInclusionInfo, err := InclusionInfoProtoToBinding(blobInclusionInfo) + bindingInclusionInfo, err := InclusionInfoProtoToBinding(blobStatusReply.GetBlobInclusionInfo()) if err != nil { return nil, fmt.Errorf("convert inclusion info to binding: %w", err) } - bindingBatchHeader, err := BatchHeaderProtoToBinding(batchHeader) + bindingBatchHeader, err := BatchHeaderProtoToBinding(blobStatusReply.GetSignedBatch().GetHeader()) if err != nil { return nil, fmt.Errorf("convert batch header to binding: %w", err) } From c08d16f8e7b62275763a018dd9fc153284303f86 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:03:27 -0500 Subject: [PATCH 11/21] Address more PR comments Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 2 +- api/clients/v2/verification/cert_verifier.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index e40a838022..b0fdebab7b 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -92,7 +92,7 @@ func (pd *PayloadDisperser) SendPayload( payload []byte, // salt is added while constructing the blob header // This salt should be utilized if a blob dispersal fails, in order to retry dispersing the same payload under a - // different blob key. + // different blob key, when using reserved bandwidth payments. salt uint32, ) (*verification.EigenDACert, error) { diff --git a/api/clients/v2/verification/cert_verifier.go b/api/clients/v2/verification/cert_verifier.go index 73b682d359..5695b77928 100644 --- a/api/clients/v2/verification/cert_verifier.go +++ b/api/clients/v2/verification/cert_verifier.go @@ -40,7 +40,7 @@ var _ ICertVerifier = &CertVerifier{} // NewCertVerifier constructs a CertVerifier func NewCertVerifier( - ethClient geth.EthClient, // the eth client, which should already be set up + ethClient geth.EthClient, // the eth client, which should already be set up certVerifierAddress string, // the hex address of the EigenDACertVerifier contract ) (*CertVerifier, error) { @@ -119,7 +119,7 @@ func (cv *CertVerifier) GetNonSignerStakesAndSignature( signedBatchBinding, err := SignedBatchProtoToBinding(signedBatch) if err != nil { - return nil, fmt.Errorf("convert signed batch: %s", err) + return nil, fmt.Errorf("convert signed batch: %w", err) } nonSignerStakesAndSignature, err := cv.certVerifierCaller.GetNonSignerStakesAndSignature( @@ -127,7 +127,7 @@ func (cv *CertVerifier) GetNonSignerStakesAndSignature( *signedBatchBinding) if err != nil { - return nil, fmt.Errorf("get non signer stakes and signature: %s", err) + return nil, fmt.Errorf("get non signer stakes and signature: %w", err) } return &nonSignerStakesAndSignature, nil From 01c819961c2b25a17a85a9fc4e13707a2f2a4e74 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:07:40 -0500 Subject: [PATCH 12/21] Make configs not be pointers Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index b0fdebab7b..b3088395d2 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -20,7 +20,7 @@ import ( // This struct is goroutine safe. type PayloadDisperser struct { logger logging.Logger - config *PayloadDisperserConfig + config PayloadDisperserConfig codec codecs.BlobCodec disperserClient DisperserClient certVerifier verification.ICertVerifier @@ -29,8 +29,8 @@ type PayloadDisperser struct { // BuildPayloadDisperser builds a PayloadDisperser from config structs func BuildPayloadDisperser( logger logging.Logger, - payloadDisperserConfig *PayloadDisperserConfig, - disperserClientConfig *DisperserClientConfig, + payloadDisperserConfig PayloadDisperserConfig, + disperserClientConfig DisperserClientConfig, // signer to sign blob dispersal requests signer core.BlobRequestSigner, // prover is used to compute commitments to a new blob during the dispersal process @@ -54,7 +54,7 @@ func BuildPayloadDisperser( return nil, fmt.Errorf("create codec: %w", err) } - disperserClient, err := NewDisperserClient(disperserClientConfig, signer, prover, accountant) + disperserClient, err := NewDisperserClient(&disperserClientConfig, signer, prover, accountant) if err != nil { return nil, fmt.Errorf("new disperser client: %s", err) } From 5150c5fef18c466a9ce8ab1a82fbb2120e30059d Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:40:40 -0500 Subject: [PATCH 13/21] Log enum names instead of values Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index b3088395d2..cec4ba7c3a 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -113,7 +113,7 @@ func (pd *PayloadDisperser) SendPayload( if err != nil { return nil, fmt.Errorf("disperse blob: %w", err) } - pd.logger.Debug("Successful DisperseBlob", "blobStatus", blobStatus, "blobKey", blobKey) + pd.logger.Debug("Successful DisperseBlob", "blobStatus", blobStatus.String(), "blobKey", blobKey) timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.BlobCertifiedTimeout) defer cancel() @@ -175,8 +175,8 @@ func (pd *PayloadDisperser) pollBlobStatusUntilCertified( case <-ctx.Done(): return nil, fmt.Errorf( "timed out waiting for %v blob status, final status was %v: %w", - dispgrpc.BlobStatus_CERTIFIED, - previousStatus, + dispgrpc.BlobStatus_CERTIFIED.Descriptor(), + previousStatus.Descriptor(), ctx.Err()) case <-ticker.C: // This call to the disperser doesn't have a dedicated timeout configured. @@ -192,8 +192,8 @@ func (pd *PayloadDisperser) pollBlobStatusUntilCertified( pd.logger.Debug( "Blob status changed", "blob key", blobKey, - "previous status", previousStatus, - "new status", newStatus) + "previous status", previousStatus.Descriptor(), + "new status", newStatus.Descriptor()) previousStatus = newStatus } @@ -203,7 +203,10 @@ func (pd *PayloadDisperser) pollBlobStatusUntilCertified( case dispgrpc.BlobStatus_QUEUED, dispgrpc.BlobStatus_ENCODED: continue default: - return nil, fmt.Errorf("terminal dispersal failure for blobKey %v. blob status: %v", blobKey, newStatus) + return nil, fmt.Errorf( + "terminal dispersal failure for blobKey %v. blob status: %v", + blobKey, + newStatus.Descriptor()) } } } From 70c0b92985bf53d5b8cef3542c2ee945d598f96e Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:03:12 -0500 Subject: [PATCH 14/21] Pass in eth client pointer to verifier Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 2 +- api/clients/v2/payload_retriever.go | 2 +- api/clients/v2/verification/cert_verifier.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index cec4ba7c3a..9b95292238 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -64,7 +64,7 @@ func BuildPayloadDisperser( return nil, fmt.Errorf("new eth client: %w", err) } - certVerifier, err := verification.NewCertVerifier(*ethClient, payloadDisperserConfig.EigenDACertVerifierAddr) + certVerifier, err := verification.NewCertVerifier(ethClient, payloadDisperserConfig.EigenDACertVerifierAddr) if err != nil { return nil, fmt.Errorf("new cert verifier: %w", err) } diff --git a/api/clients/v2/payload_retriever.go b/api/clients/v2/payload_retriever.go index d8ec7db23b..1c7909dcee 100644 --- a/api/clients/v2/payload_retriever.go +++ b/api/clients/v2/payload_retriever.go @@ -54,7 +54,7 @@ func BuildPayloadRetriever( return nil, fmt.Errorf("new eth client: %w", err) } - certVerifier, err := verification.NewCertVerifier(*ethClient, payloadRetrieverConfig.EigenDACertVerifierAddr) + certVerifier, err := verification.NewCertVerifier(ethClient, payloadRetrieverConfig.EigenDACertVerifierAddr) if err != nil { return nil, fmt.Errorf("new cert verifier: %w", err) } diff --git a/api/clients/v2/verification/cert_verifier.go b/api/clients/v2/verification/cert_verifier.go index 5695b77928..e5b5248222 100644 --- a/api/clients/v2/verification/cert_verifier.go +++ b/api/clients/v2/verification/cert_verifier.go @@ -40,7 +40,7 @@ var _ ICertVerifier = &CertVerifier{} // NewCertVerifier constructs a CertVerifier func NewCertVerifier( - ethClient geth.EthClient, // the eth client, which should already be set up + ethClient *geth.EthClient, // the eth client, which should already be set up certVerifierAddress string, // the hex address of the EigenDACertVerifier contract ) (*CertVerifier, error) { From 790266209257c5636548005d4bffa2dcee2fe549 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:42:40 -0500 Subject: [PATCH 15/21] Add doc for BlobVersion Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/config.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/api/clients/v2/config.go b/api/clients/v2/config.go index 0cb992a57c..0cfa741308 100644 --- a/api/clients/v2/config.go +++ b/api/clients/v2/config.go @@ -37,9 +37,11 @@ type PayloadClientConfig struct { // The timeout duration for contract calls ContractCallTimeout time.Duration + // The BlobVersion to use when creating new blobs, or interpreting blob bytes. + // + // BlobVersion needs to point to a version defined in the threshold registry contract. + // https://github.com/Layr-Labs/eigenda/blob/3ed9ef6ed3eb72c46ce3050eb84af28f0afdfae2/contracts/src/interfaces/IEigenDAThresholdRegistry.sol#L6 BlobVersion v2.BlobVersion - - Quorums []core.QuorumID } // PayloadRetrieverConfig contains an embedded PayloadClientConfig, plus all additional configuration values needed @@ -67,6 +69,6 @@ type PayloadDisperserConfig struct { // BlobStatusPollInterval is the tick rate for the PayloadDisperser to use, while polling the disperser with // GetBlobStatus. BlobStatusPollInterval time.Duration -} - + Quorums []core.QuorumID +} From 0c3662487afe3238d2ba91a25a0da67f8ad5911b Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:03:37 -0500 Subject: [PATCH 16/21] Add doc for Quorums Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/config.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/clients/v2/config.go b/api/clients/v2/config.go index 0cfa741308..2f4003668a 100644 --- a/api/clients/v2/config.go +++ b/api/clients/v2/config.go @@ -70,5 +70,12 @@ type PayloadDisperserConfig struct { // GetBlobStatus. BlobStatusPollInterval time.Duration + // Quorums is the set of quorums that need to have a threshold of signatures for an EigenDA cert to successfully + // verify. + // + // TODO: Clients are currently charged for QuorumIDs 0 and 1 regardless of whether or not they are included in this + // array. A decision still needs to be made for how we want to handle this. Should this field be called + // `CustomQuorums`, and we simply append any values contained onto [0, 1]? Or should we require users to include 0 + // and 1 here, and throw an error if they don't? Quorums []core.QuorumID } From a311f9517b5c6fbc594ea45914e2017084669e57 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:57:05 -0500 Subject: [PATCH 17/21] Log blob key hex instead of bytes Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 18 +++++++++--------- api/clients/v2/payload_retriever.go | 20 ++++++++++++-------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 9b95292238..2150b05834 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -113,7 +113,7 @@ func (pd *PayloadDisperser) SendPayload( if err != nil { return nil, fmt.Errorf("disperse blob: %w", err) } - pd.logger.Debug("Successful DisperseBlob", "blobStatus", blobStatus.String(), "blobKey", blobKey) + pd.logger.Debug("Successful DisperseBlob", "blobStatus", blobStatus.String(), "blobKey", blobKey.Hex()) timeoutCtx, cancel = context.WithTimeout(ctx, pd.config.BlobCertifiedTimeout) defer cancel() @@ -121,7 +121,7 @@ func (pd *PayloadDisperser) SendPayload( if err != nil { return nil, fmt.Errorf("poll blob status until certified: %w", err) } - pd.logger.Debug("Blob status CERTIFIED", "blobKey", blobKey) + pd.logger.Debug("Blob status CERTIFIED", "blobKey", blobKey.Hex()) eigenDACert, err := pd.buildEigenDACert(ctx, blobKey, blobStatusReply) if err != nil { @@ -133,9 +133,9 @@ func (pd *PayloadDisperser) SendPayload( defer cancel() err = pd.certVerifier.VerifyCertV2(timeoutCtx, eigenDACert) if err != nil { - return nil, fmt.Errorf("verify cert for blobKey %v: %w", blobKey, err) + return nil, fmt.Errorf("verify cert for blobKey %v: %w", blobKey.Hex(), err) } - pd.logger.Debug("EigenDACert verified", "blobKey", blobKey) + pd.logger.Debug("EigenDACert verified", "blobKey", blobKey.Hex()) return eigenDACert, nil } @@ -183,7 +183,7 @@ func (pd *PayloadDisperser) pollBlobStatusUntilCertified( // If this call fails to return in a timely fashion, the timeout configured for the poll loop will trigger blobStatusReply, err := pd.disperserClient.GetBlobStatus(ctx, blobKey) if err != nil { - pd.logger.Warn("get blob status", "err", err, "blobKey", blobKey) + pd.logger.Warn("get blob status", "err", err, "blobKey", blobKey.Hex()) continue } @@ -191,7 +191,7 @@ func (pd *PayloadDisperser) pollBlobStatusUntilCertified( if newStatus != previousStatus { pd.logger.Debug( "Blob status changed", - "blob key", blobKey, + "blob key", blobKey.Hex(), "previous status", previousStatus.Descriptor(), "new status", newStatus.Descriptor()) previousStatus = newStatus @@ -205,7 +205,7 @@ func (pd *PayloadDisperser) pollBlobStatusUntilCertified( default: return nil, fmt.Errorf( "terminal dispersal failure for blobKey %v. blob status: %v", - blobKey, + blobKey.Hex(), newStatus.Descriptor()) } } @@ -227,13 +227,13 @@ func (pd *PayloadDisperser) buildEigenDACert( if err != nil { return nil, fmt.Errorf("get non signer stake and signature: %w", err) } - pd.logger.Debug("Retrieved NonSignerStakesAndSignature", "blobKey", blobKey) + pd.logger.Debug("Retrieved NonSignerStakesAndSignature", "blobKey", blobKey.Hex()) eigenDACert, err := verification.BuildEigenDACert(blobStatusReply, nonSignerStakesAndSignature) if err != nil { return nil, fmt.Errorf("build eigen da cert: %w", err) } - pd.logger.Debug("Constructed EigenDACert", "blobKey", blobKey) + pd.logger.Debug("Constructed EigenDACert", "blobKey", blobKey.Hex()) return eigenDACert, nil } diff --git a/api/clients/v2/payload_retriever.go b/api/clients/v2/payload_retriever.go index 1c7909dcee..110aa4bb1f 100644 --- a/api/clients/v2/payload_retriever.go +++ b/api/clients/v2/payload_retriever.go @@ -107,7 +107,7 @@ func (pr *PayloadRetriever) GetPayload( err := pr.verifyCertWithTimeout(ctx, eigenDACert) if err != nil { - return nil, fmt.Errorf("verify cert with timeout for blobKey %v: %w", blobKey, err) + return nil, fmt.Errorf("verify cert with timeout for blobKey %v: %w", blobKey.Hex(), err) } relayKeys := eigenDACert.BlobInclusionInfo.BlobCertificate.RelayKeys @@ -136,7 +136,11 @@ func (pr *PayloadRetriever) GetPayload( blob, err := pr.getBlobWithTimeout(ctx, relayKey, blobKey) // if GetBlob returned an error, try calling a different relay if err != nil { - pr.log.Warn("blob couldn't be retrieved from relay", "blobKey", blobKey, "relayKey", relayKey, "error", err) + pr.log.Warn( + "blob couldn't be retrieved from relay", + "blobKey", blobKey.Hex(), + "relayKey", relayKey, + "error", err) continue } @@ -156,14 +160,14 @@ func (pr *PayloadRetriever) GetPayload( but could potentially indicate a maliciously generated blob certificate. It should not be possible for an honestly generated certificate to verify for an invalid blob!`, - "blobKey", blobKey, "relayKey", relayKey, "eigenDACert", eigenDACert, "error", err) + "blobKey", blobKey.Hex(), "relayKey", relayKey, "eigenDACert", eigenDACert, "error", err) return nil, fmt.Errorf("decode blob: %w", err) } return payload, nil } - return nil, fmt.Errorf("unable to retrieve blob %v from any relay. relay count: %d", blobKey, relayKeyCount) + 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. @@ -183,7 +187,7 @@ func (pr *PayloadRetriever) verifyBlobAgainstCert( // 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, relayKey) + 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 @@ -192,13 +196,13 @@ func (pr *PayloadRetriever) verifyBlobAgainstCert( if err != nil { return fmt.Errorf( "generate and compare commitment for blob %v received from relay %v: %w", - blobKey, + blobKey.Hex(), relayKey, err) } if !valid { - return fmt.Errorf("commitment for blob %v is invalid for bytes received from relay %v", blobKey, relayKey) + 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 @@ -209,7 +213,7 @@ func (pr *PayloadRetriever) verifyBlobAgainstCert( 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, + blobKey.Hex(), len(blob), relayKey, blobLength*encoding.BYTES_PER_SYMBOL) From f4a6f804daec74338cd6d9e242270c4fc2c185f1 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:08:16 -0500 Subject: [PATCH 18/21] Simplify existing half builder/half constructor Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 44 +++++------------------------ 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 2150b05834..2dfa654b4c 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -8,11 +8,8 @@ import ( "github.com/Layr-Labs/eigenda/api/clients/codecs" "github.com/Layr-Labs/eigenda/api/clients/v2/verification" dispgrpc "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2" - "github.com/Layr-Labs/eigenda/common/geth" core "github.com/Layr-Labs/eigenda/core/v2" - "github.com/Layr-Labs/eigenda/encoding" "github.com/Layr-Labs/eigensdk-go/logging" - gethcommon "github.com/ethereum/go-ethereum/common" ) // PayloadDisperser provides the ability to disperse payloads to EigenDA via a Disperser grpc service. @@ -26,49 +23,22 @@ type PayloadDisperser struct { certVerifier verification.ICertVerifier } -// BuildPayloadDisperser builds a PayloadDisperser from config structs -func BuildPayloadDisperser( +// NewPayloadDisperser creates a PayloadDisperser from subcomponents that have already been constructed and initialized. +func NewPayloadDisperser( logger logging.Logger, payloadDisperserConfig PayloadDisperserConfig, - disperserClientConfig DisperserClientConfig, - // signer to sign blob dispersal requests - signer core.BlobRequestSigner, - // prover is used to compute commitments to a new blob during the dispersal process - // - // IMPORTANT: it is permissible for the prover parameter to be nil, but operating with this configuration - // puts a trust assumption on the disperser. With a nil prover, the disperser is responsible for computing + codec codecs.BlobCodec, + // IMPORTANT: it is permissible for the disperserClient to be configured without a prover, but operating with this + // configuration puts a trust assumption on the disperser. With a nil prover, the disperser is responsible for computing // the commitments to a blob, and the PayloadDisperser doesn't have a mechanism to verify these commitments. // // TODO: In the future, an optimized method of commitment verification using fiat shamir transformation will // be implemented. This feature will allow a PayloadDisperser to offload commitment generation onto the // disperser, but the disperser's commitments will be verifiable without needing a full-fledged prover - prover encoding.Prover, - accountant *Accountant, - ethConfig geth.EthClientConfig, + disperserClient DisperserClient, + certVerifier verification.ICertVerifier, ) (*PayloadDisperser, error) { - codec, err := codecs.CreateCodec( - payloadDisperserConfig.PayloadPolynomialForm, - payloadDisperserConfig.BlobEncodingVersion) - if err != nil { - return nil, fmt.Errorf("create codec: %w", err) - } - - disperserClient, err := NewDisperserClient(&disperserClientConfig, signer, prover, accountant) - if err != nil { - return nil, fmt.Errorf("new disperser client: %s", err) - } - - ethClient, err := geth.NewClient(ethConfig, gethcommon.Address{}, 0, logger) - if err != nil { - return nil, fmt.Errorf("new eth client: %w", err) - } - - certVerifier, err := verification.NewCertVerifier(ethClient, payloadDisperserConfig.EigenDACertVerifierAddr) - if err != nil { - return nil, fmt.Errorf("new cert verifier: %w", err) - } - return &PayloadDisperser{ logger: logger, config: payloadDisperserConfig, From 959e4564cfb4705fa38d4f88e9e753a3d23bbecf Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:37:53 -0500 Subject: [PATCH 19/21] Add config utilities Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/config.go | 114 ++++++++++++++++++ api/clients/v2/payload_disperser.go | 5 + api/clients/v2/payload_retriever.go | 11 +- api/clients/v2/test/payload_retriever_test.go | 9 +- 4 files changed, 135 insertions(+), 4 deletions(-) diff --git a/api/clients/v2/config.go b/api/clients/v2/config.go index 2f4003668a..169aec00a2 100644 --- a/api/clients/v2/config.go +++ b/api/clients/v2/config.go @@ -1,6 +1,7 @@ package clients import ( + "fmt" "time" "github.com/Layr-Labs/eigenda/api/clients/codecs" @@ -79,3 +80,116 @@ type PayloadDisperserConfig struct { // and 1 here, and throw an error if they don't? Quorums []core.QuorumID } + +// GetDefaultPayloadClientConfig creates a PayloadClientConfig with default values +// +// NOTE: EthRpcUrl and EigenDACertVerifierAddr do not have defined defaults. These must always be specifically configured. +func getDefaultPayloadClientConfig() *PayloadClientConfig { + return &PayloadClientConfig{ + BlobEncodingVersion: codecs.DefaultBlobEncoding, + PayloadPolynomialForm: codecs.PolynomialFormEval, + ContractCallTimeout: 5 * time.Second, + BlobVersion: 0, + } +} + +// checkAndSetDefaults checks an existing config struct and performs the following actions: +// +// 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. +func (cc *PayloadClientConfig) checkAndSetDefaults() error { + // BlobEncodingVersion may be 0, so don't do anything + + if cc.EthRpcUrl == "" { + return fmt.Errorf("EthRpcUrl is required") + } + + if cc.EigenDACertVerifierAddr == "" { + return fmt.Errorf("EigenDACertVerifierAddr is required") + } + + // Nothing to do for PayloadPolynomialForm + + defaultConfig := getDefaultPayloadClientConfig() + + if cc.ContractCallTimeout == 0 { + cc.ContractCallTimeout = defaultConfig.ContractCallTimeout + } + + // BlobVersion may be 0, so don't do anything + + return nil +} + +// GetDefaultPayloadRetrieverConfig creates a PayloadRetrieverConfig with default values +// +// NOTE: EthRpcUrl and EigenDACertVerifierAddr do not have defined defaults. These must always be specifically configured. +func GetDefaultPayloadRetrieverConfig() *PayloadRetrieverConfig { + return &PayloadRetrieverConfig{ + PayloadClientConfig: *getDefaultPayloadClientConfig(), + RelayTimeout: 5 * time.Second, + } +} + +// checkAndSetDefaults checks an existing config struct and performs the following actions: +// +// 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. +func (rc *PayloadRetrieverConfig) checkAndSetDefaults() error { + err := rc.PayloadClientConfig.checkAndSetDefaults() + if err != nil { + return err + } + + defaultConfig := GetDefaultPayloadRetrieverConfig() + if rc.RelayTimeout == 0 { + rc.RelayTimeout = defaultConfig.RelayTimeout + } + + return nil +} + +// GetDefaultPayloadDisperserConfig creates a PayloadDisperserConfig with default values +// +// NOTE: EthRpcUrl and EigenDACertVerifierAddr do not have defined defaults. These must always be specifically configured. +func GetDefaultPayloadDisperserConfig() *PayloadDisperserConfig { + return &PayloadDisperserConfig{ + PayloadClientConfig: *getDefaultPayloadClientConfig(), + DisperseBlobTimeout: 5 * time.Second, + BlobCertifiedTimeout: 10 * time.Second, + BlobStatusPollInterval: 1 * time.Second, + Quorums: []core.QuorumID{0, 1}, + } +} + +// checkAndSetDefaults checks an existing config struct and performs the following actions: +// +// 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. +func (dc *PayloadDisperserConfig) checkAndSetDefaults() error { + err := dc.PayloadClientConfig.checkAndSetDefaults() + if err != nil { + return err + } + + defaultConfig := GetDefaultPayloadDisperserConfig() + + if dc.DisperseBlobTimeout == 0 { + dc.DisperseBlobTimeout = defaultConfig.DisperseBlobTimeout + } + + if dc.BlobCertifiedTimeout == 0 { + dc.BlobCertifiedTimeout = defaultConfig.BlobCertifiedTimeout + } + + if dc.BlobStatusPollInterval == 0 { + dc.BlobStatusPollInterval = defaultConfig.BlobStatusPollInterval + } + + // Quorums may be empty, so don't do anything + + return nil +} diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 2dfa654b4c..6d90459a43 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -39,6 +39,11 @@ func NewPayloadDisperser( certVerifier verification.ICertVerifier, ) (*PayloadDisperser, error) { + err := payloadDisperserConfig.checkAndSetDefaults() + if err != nil { + return nil, fmt.Errorf("check and set PayloadDisperserConfig defaults: %w", err) + } + return &PayloadDisperser{ logger: logger, config: payloadDisperserConfig, diff --git a/api/clients/v2/payload_retriever.go b/api/clients/v2/payload_retriever.go index 110aa4bb1f..8ad9681d81 100644 --- a/api/clients/v2/payload_retriever.go +++ b/api/clients/v2/payload_retriever.go @@ -29,7 +29,7 @@ type PayloadRetriever struct { // Not all methods on Rand are guaranteed goroutine safe: if additional usages of random are added, they // must be evaluated for thread safety. random *rand.Rand - config *PayloadRetrieverConfig + config PayloadRetrieverConfig codec codecs.BlobCodec relayClient RelayClient g1Srs []bn254.G1Affine @@ -39,7 +39,7 @@ type PayloadRetriever struct { // BuildPayloadRetriever builds a PayloadRetriever from config structs. func BuildPayloadRetriever( log logging.Logger, - payloadRetrieverConfig *PayloadRetrieverConfig, + payloadRetrieverConfig PayloadRetrieverConfig, ethConfig geth.EthClientConfig, relayClientConfig *RelayClientConfig, g1Srs []bn254.G1Affine) (*PayloadRetriever, error) { @@ -78,12 +78,17 @@ func BuildPayloadRetriever( func NewPayloadRetriever( log logging.Logger, random *rand.Rand, - payloadRetrieverConfig *PayloadRetrieverConfig, + payloadRetrieverConfig PayloadRetrieverConfig, relayClient RelayClient, certVerifier verification.ICertVerifier, codec codecs.BlobCodec, g1Srs []bn254.G1Affine) (*PayloadRetriever, error) { + err := payloadRetrieverConfig.checkAndSetDefaults() + if err != nil { + return nil, fmt.Errorf("check and set PayloadRetrieverConfig config: %w", err) + } + return &PayloadRetriever{ log: log, random: random, diff --git a/api/clients/v2/test/payload_retriever_test.go b/api/clients/v2/test/payload_retriever_test.go index f56159298a..c69e731552 100644 --- a/api/clients/v2/test/payload_retriever_test.go +++ b/api/clients/v2/test/payload_retriever_test.go @@ -45,7 +45,14 @@ func buildPayloadRetrieverTester(t *testing.T) PayloadRetrieverTester { logger, err := common.NewLogger(common.DefaultLoggerConfig()) require.NoError(t, err) - clientConfig := &clients.PayloadRetrieverConfig{ + // the constructor checks that these values aren't empty. we don't need them, though, since we're using mocks + payloadClientConfig := clients.PayloadClientConfig{ + EthRpcUrl: "x", + EigenDACertVerifierAddr: "y", + } + + clientConfig := clients.PayloadRetrieverConfig{ + PayloadClientConfig: payloadClientConfig, RelayTimeout: 50 * time.Millisecond, } From 8ec68d31584585e1a9a2ca4d165fc055fb816fde Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:56:25 -0500 Subject: [PATCH 20/21] Add TODO Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_disperser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/clients/v2/payload_disperser.go b/api/clients/v2/payload_disperser.go index 6d90459a43..b66da2e7be 100644 --- a/api/clients/v2/payload_disperser.go +++ b/api/clients/v2/payload_disperser.go @@ -172,6 +172,7 @@ func (pd *PayloadDisperser) pollBlobStatusUntilCertified( previousStatus = newStatus } + // TODO: we'll need to add more in-depth response status processing to derive failover errors switch newStatus { case dispgrpc.BlobStatus_CERTIFIED: return blobStatusReply, nil From 8c755e6ea691d796f7e6b6cb393f2a8cf8a03aa3 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:51:28 -0500 Subject: [PATCH 21/21] Tweak Quorums TODO Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/clients/v2/config.go b/api/clients/v2/config.go index 169aec00a2..5580af57d4 100644 --- a/api/clients/v2/config.go +++ b/api/clients/v2/config.go @@ -75,9 +75,9 @@ type PayloadDisperserConfig struct { // verify. // // TODO: Clients are currently charged for QuorumIDs 0 and 1 regardless of whether or not they are included in this - // array. A decision still needs to be made for how we want to handle this. Should this field be called - // `CustomQuorums`, and we simply append any values contained onto [0, 1]? Or should we require users to include 0 - // and 1 here, and throw an error if they don't? + // array. Therefore, if 0 and 1 aren't included in this array, you are missing out on security that your are paying + // for. The strategy for how to handle this field in the context of rollups is still in flux: this comment should + // be revisited and revised as necessary. Quorums []core.QuorumID }