diff --git a/api/clients/config.go b/api/clients/config.go new file mode 100644 index 0000000000..fb7ba81bfd --- /dev/null +++ b/api/clients/config.go @@ -0,0 +1,51 @@ +package clients + +import ( + "fmt" + "time" +) + +type EigenDAClientConfig struct { + // RPC is the HTTP provider URL for the Data Availability node. + RPC string + + // The total amount of time that the client will spend waiting for EigenDA to confirm a blob + StatusQueryTimeout time.Duration + + // The amount of time to wait between status queries of a newly dispersed blob + StatusQueryRetryInterval time.Duration + + // The total amount of time that the client will waiting for a response from the EigenDA disperser + ResponseTimeout time.Duration + + // The quorum IDs to write blobs to using this client. Should not include default quorums 0 or 1. + CustomQuorumIDs []uint + + // Signer private key in hex encoded format. This key should not be associated with an Ethereum address holding any funds. + SignerPrivateKeyHex string + + // Whether to disable TLS for an insecure connection when connecting to a local EigenDA disperser instance. + DisableTLS bool + + // The blob encoding version to use when writing blobs from the high level interface. + PutBlobEncodingVersion BlobEncodingVersion +} + +func (c *EigenDAClientConfig) CheckAndSetDefaults() error { + if c.StatusQueryRetryInterval == 0 { + c.StatusQueryRetryInterval = 5 * time.Second + } + if c.StatusQueryTimeout == 0 { + c.StatusQueryTimeout = 25 * time.Minute + } + if c.ResponseTimeout == 0 { + c.ResponseTimeout = 30 * time.Second + } + if len(c.SignerPrivateKeyHex) != 64 { + return fmt.Errorf("EigenDAClientConfig.SignerPrivateKeyHex should be 64 hex characters long, should not have 0x prefix") + } + if len(c.RPC) == 0 { + return fmt.Errorf("EigenDAClientConfig.RPC not set") + } + return nil +} diff --git a/clients/disperser_client.go b/api/clients/disperser_client.go similarity index 90% rename from clients/disperser_client.go rename to api/clients/disperser_client.go index fa930bdeca..50eb03cbc4 100644 --- a/clients/disperser_client.go +++ b/api/clients/disperser_client.go @@ -37,6 +37,7 @@ type DisperserClient interface { DisperseBlob(ctx context.Context, data []byte, customQuorums []uint8) (*disperser.BlobStatus, []byte, error) DisperseBlobAuthenticated(ctx context.Context, data []byte, customQuorums []uint8) (*disperser.BlobStatus, []byte, error) GetBlobStatus(ctx context.Context, key []byte) (*disperser_rpc.BlobStatusReply, error) + RetrieveBlob(ctx context.Context, batchHeaderHash []byte, blobIndex uint32) ([]byte, error) } type disperserClient struct { @@ -222,3 +223,23 @@ func (c *disperserClient) GetBlobStatus(ctx context.Context, requestID []byte) ( return reply, nil } + +func (c *disperserClient) RetrieveBlob(ctx context.Context, batchHeaderHash []byte, blobIndex uint32) ([]byte, error) { + addr := fmt.Sprintf("%v:%v", c.config.Hostname, c.config.Port) + dialOptions := c.getDialOptions() + conn, err := grpc.Dial(addr, dialOptions...) + if err != nil { + return nil, err + } + disperserClient := disperser_rpc.NewDisperserClient(conn) + ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*60) + defer cancel() + reply, err := disperserClient.RetrieveBlob(ctxTimeout, &disperser_rpc.RetrieveBlobRequest{ + BatchHeaderHash: batchHeaderHash, + BlobIndex: blobIndex, + }) + if err != nil { + return nil, err + } + return reply.Data, nil +} diff --git a/api/clients/eigenda_client.go b/api/clients/eigenda_client.go new file mode 100644 index 0000000000..7978a9b78f --- /dev/null +++ b/api/clients/eigenda_client.go @@ -0,0 +1,175 @@ +package clients + +import ( + "context" + "encoding/base64" + "encoding/hex" + "fmt" + "net" + "time" + + grpcdisperser "github.com/Layr-Labs/eigenda/api/grpc/disperser" + "github.com/Layr-Labs/eigenda/core/auth" + "github.com/Layr-Labs/eigenda/disperser" + "github.com/ethereum/go-ethereum/log" +) + +type IEigenDAClient interface { + GetBlob(ctx context.Context, BatchHeaderHash []byte, BlobIndex uint32) ([]byte, error) + PutBlob(ctx context.Context, txData []byte) (*grpcdisperser.BlobInfo, error) +} + +type EigenDAClient struct { + Config EigenDAClientConfig + Log log.Logger + Client DisperserClient + PutCodec BlobCodec +} + +var _ IEigenDAClient = EigenDAClient{} + +func NewEigenDAClient(log log.Logger, config EigenDAClientConfig) (*EigenDAClient, error) { + err := config.CheckAndSetDefaults() + if err != nil { + return nil, err + } + + host, port, err := net.SplitHostPort(config.RPC) + if err != nil { + return nil, fmt.Errorf("failed to parse EigenDA RPC: %w", err) + } + + signer := auth.NewLocalBlobRequestSigner(config.SignerPrivateKeyHex) + llConfig := NewConfig(host, port, config.ResponseTimeout, !config.DisableTLS) + llClient := NewDisperserClient(llConfig, signer) + + codec, err := BlobEncodingVersionToCodec(config.PutBlobEncodingVersion) + if err != nil { + return nil, fmt.Errorf("error initializing EigenDA client: %w", err) + } + + return &EigenDAClient{ + Log: log, + Config: config, + Client: llClient, + PutCodec: codec, + }, nil +} + +func (m EigenDAClient) GetBlob(ctx context.Context, BatchHeaderHash []byte, BlobIndex uint32) ([]byte, error) { + data, err := m.Client.RetrieveBlob(ctx, BatchHeaderHash, BlobIndex) + if err != nil { + return nil, err + } + + if len(data) == 0 { + return nil, fmt.Errorf("blob has length zero") + } + + version := BlobEncodingVersion(data[0]) + codec, err := BlobEncodingVersionToCodec(version) + if err != nil { + return nil, fmt.Errorf("error getting blob: %w", err) + } + + rawData, err := codec.DecodeBlob(data) + if err != nil { + return nil, fmt.Errorf("error getting blob: %w", err) + } + + return rawData, nil +} + +func (m EigenDAClient) PutBlob(ctx context.Context, data []byte) (*grpcdisperser.BlobInfo, error) { + resultChan, errorChan := m.PutBlobAsync(ctx, data) + select { // no timeout here because we depend on the configured timeout in PutBlobAsync + case result := <-resultChan: + return result, nil + case err := <-errorChan: + return nil, err + } +} + +func (m EigenDAClient) PutBlobAsync(ctx context.Context, data []byte) (resultChan chan *grpcdisperser.BlobInfo, errChan chan error) { + resultChan = make(chan *grpcdisperser.BlobInfo, 1) + errChan = make(chan error, 1) + go m.putBlob(ctx, data, resultChan, errChan) + return +} + +func (m EigenDAClient) putBlob(ctx context.Context, rawData []byte, resultChan chan *grpcdisperser.BlobInfo, errChan chan error) { + m.Log.Info("Attempting to disperse blob to EigenDA") + + // encode blob + if m.PutCodec == nil { + errChan <- fmt.Errorf("PutCodec cannot be nil") + return + } + data := m.PutCodec.EncodeBlob(rawData) + + customQuorumNumbers := make([]uint8, len(m.Config.CustomQuorumIDs)) + for i, e := range m.Config.CustomQuorumIDs { + customQuorumNumbers[i] = uint8(e) + } + + // disperse blob + blobStatus, requestID, err := m.Client.DisperseBlobAuthenticated(ctx, data, customQuorumNumbers) + if err != nil { + errChan <- fmt.Errorf("error initializing DisperseBlobAuthenticated() client: %w", err) + return + } + + // process response + if *blobStatus == disperser.Failed { + m.Log.Error("Unable to disperse blob to EigenDA, aborting", "err", err) + errChan <- fmt.Errorf("reply status is %d", blobStatus) + return + } + + base64RequestID := base64.StdEncoding.EncodeToString(requestID) + m.Log.Info("Blob dispersed to EigenDA, now waiting for confirmation", "requestID", base64RequestID) + + ticker := time.NewTicker(m.Config.StatusQueryRetryInterval) + defer ticker.Stop() + + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, m.Config.StatusQueryTimeout) + defer cancel() + + for { + select { + case <-ctx.Done(): + errChan <- fmt.Errorf("timed out waiting for EigenDA blob to confirm blob with request id=%s: %w", base64RequestID, ctx.Err()) + return + case <-ticker.C: + statusRes, err := m.Client.GetBlobStatus(ctx, requestID) + if err != nil { + m.Log.Error("Unable to retrieve blob dispersal status, will retry", "requestID", base64RequestID, "err", err) + continue + } + + switch statusRes.Status { + case grpcdisperser.BlobStatus_PROCESSING, grpcdisperser.BlobStatus_DISPERSING: + m.Log.Info("Blob submitted, waiting for dispersal from EigenDA", "requestID", base64RequestID) + case grpcdisperser.BlobStatus_FAILED: + m.Log.Error("EigenDA blob dispersal failed in processing", "requestID", base64RequestID, "err", err) + errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing, requestID=%s: %w", base64RequestID, err) + return + case grpcdisperser.BlobStatus_INSUFFICIENT_SIGNATURES: + m.Log.Error("EigenDA blob dispersal failed in processing with insufficient signatures", "requestID", base64RequestID, "err", err) + errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing with insufficient signatures, requestID=%s: %w", base64RequestID, err) + return + case grpcdisperser.BlobStatus_CONFIRMED: + m.Log.Info("EigenDA blob confirmed, waiting for finalization", "requestID", base64RequestID) + case grpcdisperser.BlobStatus_FINALIZED: + batchHeaderHashHex := fmt.Sprintf("0x%s", hex.EncodeToString(statusRes.Info.BlobVerificationProof.BatchMetadata.BatchHeaderHash)) + m.Log.Info("Successfully dispersed blob to EigenDA", "requestID", base64RequestID, "batchHeaderHash", batchHeaderHashHex) + resultChan <- statusRes.Info + return + default: + errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing with reply status %d", statusRes.Status) + return + } + } + } +} diff --git a/api/clients/eigenda_client_test.go b/api/clients/eigenda_client_test.go new file mode 100644 index 0000000000..865083f9ff --- /dev/null +++ b/api/clients/eigenda_client_test.go @@ -0,0 +1,360 @@ +package clients_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/Layr-Labs/eigenda/api/clients" + clientsmock "github.com/Layr-Labs/eigenda/api/clients/mock" + "github.com/Layr-Labs/eigenda/api/grpc/common" + grpcdisperser "github.com/Layr-Labs/eigenda/api/grpc/disperser" + "github.com/Layr-Labs/eigenda/disperser" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestPutRetrieveBlobSuccess(t *testing.T) { + disperserClient := clientsmock.NewMockDisperserClient() + expectedBlobStatus := disperser.Processing + (disperserClient.On("DisperseBlobAuthenticated", mock.Anything, mock.Anything, mock.Anything). + Return(&expectedBlobStatus, []byte("mock-request-id"), nil)) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_DISPERSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_CONFIRMED}, nil).Once()) + finalizedBlobInfo := &grpcdisperser.BlobInfo{ + BlobHeader: &grpcdisperser.BlobHeader{ + Commitment: &common.G1Commitment{X: []byte{0x00, 0x00, 0x00, 0x00}, Y: []byte{0x01, 0x00, 0x00, 0x00}}, + BlobQuorumParams: []*grpcdisperser.BlobQuorumParam{ + { + QuorumNumber: 0, + }, + { + QuorumNumber: 1, + }, + }, + }, + BlobVerificationProof: &grpcdisperser.BlobVerificationProof{ + BlobIndex: 100, + BatchMetadata: &grpcdisperser.BatchMetadata{ + BatchHeaderHash: []byte("mock-batch-header-hash"), + BatchHeader: &grpcdisperser.BatchHeader{ + ReferenceBlockNumber: 200, + }, + }, + }, + } + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_FINALIZED, Info: finalizedBlobInfo}, nil).Once()) + (disperserClient.On("RetrieveBlob", mock.Anything, mock.Anything, mock.Anything). + Return(nil, nil).Once()) // pass nil in as the return blob to tell the mock to return the corresponding blob + logger := log.NewLogger(log.DiscardHandler()) + eigendaClient := clients.EigenDAClient{ + Log: logger, + Config: clients.EigenDAClientConfig{ + RPC: "localhost:51001", + StatusQueryTimeout: 10 * time.Minute, + StatusQueryRetryInterval: 50 * time.Millisecond, + ResponseTimeout: 10 * time.Second, + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d", + DisableTLS: false, + PutBlobEncodingVersion: clients.DefaultBlobEncoding, + }, + Client: disperserClient, + PutCodec: clients.DefaultBlobEncodingCodec{}, + } + expectedBlob := []byte("dc49e7df326cfb2e7da5cf68f263e1898443ec2e862350606e7dfbda55ad10b5d61ed1d54baf6ae7a86279c1b4fa9c49a7de721dacb211264c1f5df31bade51c") + blobInfo, err := eigendaClient.PutBlob(context.Background(), expectedBlob) + require.NoError(t, err) + require.NotNil(t, blobInfo) + assert.Equal(t, finalizedBlobInfo, blobInfo) + + resultBlob, err := eigendaClient.GetBlob(context.Background(), []byte("mock-batch-header-hash"), 100) + require.NoError(t, err) + require.Equal(t, expectedBlob, resultBlob) +} + +func TestPutBlobFailDispersal(t *testing.T) { + disperserClient := clientsmock.NewMockDisperserClient() + (disperserClient.On("DisperseBlobAuthenticated", mock.Anything, mock.Anything, mock.Anything). + Return(nil, nil, fmt.Errorf("error dispersing"))) + logger := log.NewLogger(log.DiscardHandler()) + eigendaClient := clients.EigenDAClient{ + Log: logger, + Config: clients.EigenDAClientConfig{ + RPC: "localhost:51001", + StatusQueryTimeout: 10 * time.Minute, + StatusQueryRetryInterval: 50 * time.Millisecond, + ResponseTimeout: 10 * time.Second, + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d", + DisableTLS: false, + PutBlobEncodingVersion: clients.DefaultBlobEncoding, + }, + Client: disperserClient, + PutCodec: clients.DefaultBlobEncodingCodec{}, + } + blobInfo, err := eigendaClient.PutBlob(context.Background(), []byte("hello")) + require.Error(t, err) + require.Nil(t, blobInfo) +} + +func TestPutBlobFailureInsufficentSignatures(t *testing.T) { + disperserClient := clientsmock.NewMockDisperserClient() + expectedBlobStatus := disperser.Processing + (disperserClient.On("DisperseBlobAuthenticated", mock.Anything, mock.Anything, mock.Anything). + Return(&expectedBlobStatus, []byte("mock-request-id"), nil)) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_DISPERSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_INSUFFICIENT_SIGNATURES}, nil).Once()) + logger := log.NewLogger(log.DiscardHandler()) + eigendaClient := clients.EigenDAClient{ + Log: logger, + Config: clients.EigenDAClientConfig{ + RPC: "localhost:51001", + StatusQueryTimeout: 10 * time.Minute, + StatusQueryRetryInterval: 50 * time.Millisecond, + ResponseTimeout: 10 * time.Second, + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d", + DisableTLS: false, + PutBlobEncodingVersion: clients.DefaultBlobEncoding, + }, + Client: disperserClient, + PutCodec: clients.DefaultBlobEncodingCodec{}, + } + blobInfo, err := eigendaClient.PutBlob(context.Background(), []byte("hello")) + require.Error(t, err) + require.Nil(t, blobInfo) +} + +func TestPutBlobFailureGeneral(t *testing.T) { + disperserClient := clientsmock.NewMockDisperserClient() + expectedBlobStatus := disperser.Processing + (disperserClient.On("DisperseBlobAuthenticated", mock.Anything, mock.Anything, mock.Anything). + Return(&expectedBlobStatus, []byte("mock-request-id"), nil)) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_DISPERSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_FAILED}, nil).Once()) + logger := log.NewLogger(log.DiscardHandler()) + eigendaClient := clients.EigenDAClient{ + Log: logger, + Config: clients.EigenDAClientConfig{ + RPC: "localhost:51001", + StatusQueryTimeout: 10 * time.Minute, + StatusQueryRetryInterval: 50 * time.Millisecond, + ResponseTimeout: 10 * time.Second, + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d", + DisableTLS: false, + PutBlobEncodingVersion: clients.DefaultBlobEncoding, + }, + Client: disperserClient, + PutCodec: clients.DefaultBlobEncodingCodec{}, + } + blobInfo, err := eigendaClient.PutBlob(context.Background(), []byte("hello")) + require.Error(t, err) + require.Nil(t, blobInfo) +} + +func TestPutBlobFailureUnknown(t *testing.T) { + disperserClient := clientsmock.NewMockDisperserClient() + expectedBlobStatus := disperser.Processing + (disperserClient.On("DisperseBlobAuthenticated", mock.Anything, mock.Anything, mock.Anything). + Return(&expectedBlobStatus, []byte("mock-request-id"), nil)) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_DISPERSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_UNKNOWN}, nil).Once()) + logger := log.NewLogger(log.DiscardHandler()) + eigendaClient := clients.EigenDAClient{ + Log: logger, + Config: clients.EigenDAClientConfig{ + RPC: "localhost:51001", + StatusQueryTimeout: 10 * time.Minute, + StatusQueryRetryInterval: 50 * time.Millisecond, + ResponseTimeout: 10 * time.Second, + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d", + DisableTLS: false, + PutBlobEncodingVersion: clients.DefaultBlobEncoding, + }, + Client: disperserClient, + PutCodec: clients.DefaultBlobEncodingCodec{}, + } + blobInfo, err := eigendaClient.PutBlob(context.Background(), []byte("hello")) + require.Error(t, err) + require.Nil(t, blobInfo) +} + +func TestPutBlobFinalizationTimeout(t *testing.T) { + disperserClient := clientsmock.NewMockDisperserClient() + expectedBlobStatus := disperser.Processing + (disperserClient.On("DisperseBlobAuthenticated", mock.Anything, mock.Anything, mock.Anything). + Return(&expectedBlobStatus, []byte("mock-request-id"), nil)) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + logger := log.NewLogger(log.DiscardHandler()) + eigendaClient := clients.EigenDAClient{ + Log: logger, + Config: clients.EigenDAClientConfig{ + RPC: "localhost:51001", + StatusQueryTimeout: 200 * time.Millisecond, + StatusQueryRetryInterval: 51 * time.Millisecond, + ResponseTimeout: 10 * time.Second, + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d", + DisableTLS: false, + PutBlobEncodingVersion: clients.DefaultBlobEncoding, + }, + Client: disperserClient, + PutCodec: clients.DefaultBlobEncodingCodec{}, + } + blobInfo, err := eigendaClient.PutBlob(context.Background(), []byte("hello")) + require.Error(t, err) + require.Nil(t, blobInfo) +} + +func TestPutBlobIndividualRequestTimeout(t *testing.T) { + disperserClient := clientsmock.NewMockDisperserClient() + expectedBlobStatus := disperser.Processing + (disperserClient.On("DisperseBlobAuthenticated", mock.Anything, mock.Anything, mock.Anything). + Return(&expectedBlobStatus, []byte("mock-request-id"), nil)) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + time.Sleep(100 * time.Millisecond) // Simulate a 100ms delay, which should fail the request + }). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_DISPERSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_CONFIRMED}, nil).Once()) + finalizedBlobInfo := &grpcdisperser.BlobInfo{ + BlobHeader: &grpcdisperser.BlobHeader{ + Commitment: &common.G1Commitment{X: []byte{0x00, 0x00, 0x00, 0x00}, Y: []byte{0x01, 0x00, 0x00, 0x00}}, + BlobQuorumParams: []*grpcdisperser.BlobQuorumParam{ + { + QuorumNumber: 0, + }, + { + QuorumNumber: 1, + }, + }, + }, + BlobVerificationProof: &grpcdisperser.BlobVerificationProof{ + BlobIndex: 100, + BatchMetadata: &grpcdisperser.BatchMetadata{ + BatchHeaderHash: []byte("mock-batch-header-hash"), + BatchHeader: &grpcdisperser.BatchHeader{ + ReferenceBlockNumber: 200, + }, + }, + }, + } + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_FINALIZED, Info: finalizedBlobInfo}, nil).Once()) + logger := log.NewLogger(log.DiscardHandler()) + eigendaClient := clients.EigenDAClient{ + Log: logger, + Config: clients.EigenDAClientConfig{ + RPC: "localhost:51001", + StatusQueryTimeout: 10 * time.Minute, + StatusQueryRetryInterval: 50 * time.Millisecond, + ResponseTimeout: 50 * time.Millisecond, // very low timeout + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d", + DisableTLS: false, + PutBlobEncodingVersion: clients.DefaultBlobEncoding, + }, + Client: disperserClient, + PutCodec: clients.DefaultBlobEncodingCodec{}, + } + blobInfo, err := eigendaClient.PutBlob(context.Background(), []byte("hello")) + + // despite initial timeout it should succeed + require.NoError(t, err) + require.NotNil(t, blobInfo) + assert.Equal(t, finalizedBlobInfo, blobInfo) +} + +func TestPutBlobTotalTimeout(t *testing.T) { + disperserClient := clientsmock.NewMockDisperserClient() + expectedBlobStatus := disperser.Processing + (disperserClient.On("DisperseBlobAuthenticated", mock.Anything, mock.Anything, mock.Anything). + Return(&expectedBlobStatus, []byte("mock-request-id"), nil)) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + time.Sleep(100 * time.Millisecond) // Simulate a 100ms delay, which should fail the request + }). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_PROCESSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_DISPERSING}, nil).Once()) + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_CONFIRMED}, nil).Once()) + finalizedBlobInfo := &grpcdisperser.BlobInfo{ + BlobHeader: &grpcdisperser.BlobHeader{ + Commitment: &common.G1Commitment{X: []byte{0x00, 0x00, 0x00, 0x00}, Y: []byte{0x01, 0x00, 0x00, 0x00}}, + BlobQuorumParams: []*grpcdisperser.BlobQuorumParam{ + { + QuorumNumber: 0, + }, + { + QuorumNumber: 1, + }, + }, + }, + BlobVerificationProof: &grpcdisperser.BlobVerificationProof{ + BlobIndex: 100, + BatchMetadata: &grpcdisperser.BatchMetadata{ + BatchHeaderHash: []byte("mock-batch-header-hash"), + BatchHeader: &grpcdisperser.BatchHeader{ + ReferenceBlockNumber: 200, + }, + }, + }, + } + (disperserClient.On("GetBlobStatus", mock.Anything, mock.Anything). + Return(&grpcdisperser.BlobStatusReply{Status: grpcdisperser.BlobStatus_FINALIZED, Info: finalizedBlobInfo}, nil).Once()) + logger := log.NewLogger(log.DiscardHandler()) + eigendaClient := clients.EigenDAClient{ + Log: logger, + Config: clients.EigenDAClientConfig{ + RPC: "localhost:51001", + StatusQueryTimeout: 100 * time.Millisecond, // low total timeout + StatusQueryRetryInterval: 50 * time.Millisecond, + ResponseTimeout: 10 * time.Minute, + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "75f9e29cac7f5774d106adb355ef294987ce39b7863b75bb3f2ea42ca160926d", + DisableTLS: false, + PutBlobEncodingVersion: clients.DefaultBlobEncoding, + }, + Client: disperserClient, + PutCodec: clients.DefaultBlobEncodingCodec{}, + } + blobInfo, err := eigendaClient.PutBlob(context.Background(), []byte("hello")) + + // should timeout even though it would have finalized eventually + require.Error(t, err) + require.Nil(t, blobInfo) +} diff --git a/api/clients/eigenda_client_testnet_test.go b/api/clients/eigenda_client_testnet_test.go new file mode 100644 index 0000000000..ff6474005b --- /dev/null +++ b/api/clients/eigenda_client_testnet_test.go @@ -0,0 +1,42 @@ +package clients_test + +import ( + "context" + "flag" + "os" + "testing" + "time" + + "github.com/Layr-Labs/eigenda/api/clients" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/assert" +) + +var runTestnetIntegrationTests bool + +func init() { + flag.BoolVar(&runTestnetIntegrationTests, "testnet-integration", false, "Run testnet-based integration tests") +} + +func TestClientUsingTestnet(t *testing.T) { + if !runTestnetIntegrationTests { + t.Skip("Skipping testnet integration test") + } + logger := log.NewLogger(log.NewTerminalHandler(os.Stderr, true)) + client, err := clients.NewEigenDAClient(logger, clients.EigenDAClientConfig{ + RPC: "disperser-holesky.eigenda.xyz:443", + StatusQueryTimeout: 25 * time.Minute, + StatusQueryRetryInterval: 5 * time.Second, + CustomQuorumIDs: []uint{}, + SignerPrivateKeyHex: "2d23e142a9e86a9175b9dfa213f20ea01f6c1731e09fa6edf895f70fe279cbb1", + }) + data := "hello world!" + assert.NoError(t, err) + blobInfo, err := client.PutBlob(context.Background(), []byte(data)) + assert.NoError(t, err) + batchHeaderHash := blobInfo.BlobVerificationProof.BatchMetadata.BatchHeaderHash + blobIndex := blobInfo.BlobVerificationProof.BlobIndex + blob, err := client.GetBlob(context.Background(), batchHeaderHash, blobIndex) + assert.NoError(t, err) + assert.Equal(t, data, string(blob)) +} diff --git a/api/clients/encodings.go b/api/clients/encodings.go new file mode 100644 index 0000000000..d1399fe819 --- /dev/null +++ b/api/clients/encodings.go @@ -0,0 +1,84 @@ +package clients + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/Layr-Labs/eigenda/encoding/utils/codec" +) + +type BlobEncodingVersion byte + +const ( + // This minimal blob encoding includes a version byte, a length varuint, and 31 byte field element mapping. It does not include IFFT padding + IFFT. + DefaultBlobEncoding BlobEncodingVersion = 0x00 +) + +type BlobCodec interface { + DecodeBlob(encodedData []byte) ([]byte, error) + EncodeBlob(rawData []byte) []byte +} + +func BlobEncodingVersionToCodec(version BlobEncodingVersion) (BlobCodec, error) { + switch version { + case DefaultBlobEncoding: + return DefaultBlobEncodingCodec{}, nil + default: + return nil, fmt.Errorf("unsupported blob encoding version: %x", version) + } +} + +type DefaultBlobEncodingCodec struct{} + +var _ BlobCodec = DefaultBlobEncodingCodec{} + +func (v DefaultBlobEncodingCodec) EncodeBlob(rawData []byte) []byte { + // encode current blob encoding version byte + encodedData := make([]byte, 0, 1+8+len(rawData)) + + // append version byte + encodedData = append(encodedData, byte(DefaultBlobEncoding)) + + // encode data length + encodedData = append(encodedData, ConvertIntToVarUInt(len(rawData))...) + + // append raw data + encodedData = append(encodedData, rawData...) + + // encode modulo bn254 + encodedData = codec.ConvertByPaddingEmptyByte(encodedData) + + return encodedData +} + +func (v DefaultBlobEncodingCodec) DecodeBlob(encodedData []byte) ([]byte, error) { + // decode modulo bn254 + decodedData := codec.RemoveEmptyByteFromPaddedBytes(encodedData) + + // Return exact data with buffer removed + reader := bytes.NewReader(decodedData) + + // read version byte, we will not use it for now since there is only one version + _, err := reader.ReadByte() + if err != nil { + return nil, fmt.Errorf("failed to read version byte") + } + + // read length uvarint + length, err := binary.ReadUvarint(reader) + if err != nil { + return nil, fmt.Errorf("failed to decode length uvarint prefix") + } + + rawData := make([]byte, length) + n, err := reader.Read(rawData) + if err != nil { + return nil, fmt.Errorf("failed to copy unpadded data into final buffer, length: %d, bytes read: %d", length, n) + } + if uint64(n) != length { + return nil, fmt.Errorf("data length does not match length prefix") + } + + return rawData, nil +} diff --git a/api/clients/encodings_test.go b/api/clients/encodings_test.go new file mode 100644 index 0000000000..8b15aef8ef --- /dev/null +++ b/api/clients/encodings_test.go @@ -0,0 +1,50 @@ +package clients + +import ( + "bytes" + "crypto/rand" + "math/big" + "testing" +) + +// Helper function to generate a random byte slice of a given length +func randomByteSlice(length int64) []byte { + b := make([]byte, length) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + return b +} + +// TestDefaultBlobEncodingCodec tests the encoding and decoding of random byte streams +func TestDefaultBlobEncodingCodec(t *testing.T) { + // Create an instance of the DefaultBlobEncodingCodec + codec := DefaultBlobEncodingCodec{} + + // Number of test iterations + const iterations = 100 + + for i := 0; i < iterations; i++ { + // Generate a random length for the byte slice + length, err := rand.Int(rand.Reader, big.NewInt(1024)) // Random length between 0 and 1023 + if err != nil { + panic(err) + } + originalData := randomByteSlice(length.Int64() + 1) // ensure it's not length 0 + + // Encode the original data + encodedData := codec.EncodeBlob(originalData) + + // Decode the encoded data + decodedData, err := codec.DecodeBlob(encodedData) + if err != nil { + t.Fatalf("Iteration %d: failed to decode blob: %v", i, err) + } + + // Compare the original data with the decoded data + if !bytes.Equal(originalData, decodedData) { + t.Fatalf("Iteration %d: original and decoded data do not match\nOriginal: %v\nDecoded: %v", i, originalData, decodedData) + } + } +} diff --git a/clients/mock/disperser_client.go b/api/clients/mock/disperser_client.go similarity index 51% rename from clients/mock/disperser_client.go rename to api/clients/mock/disperser_client.go index 2f29b8e808..0488af63cb 100644 --- a/clients/mock/disperser_client.go +++ b/api/clients/mock/disperser_client.go @@ -2,37 +2,61 @@ package mock import ( "context" + "encoding/base64" + "fmt" + "github.com/Layr-Labs/eigenda/api/clients" disperser_rpc "github.com/Layr-Labs/eigenda/api/grpc/disperser" - "github.com/Layr-Labs/eigenda/clients" "github.com/Layr-Labs/eigenda/disperser" + "github.com/Layr-Labs/eigenda/encoding/rs" "github.com/stretchr/testify/mock" ) +type BlobKey struct { + BatchHeaderHash []byte + BlobIndex uint32 +} + type MockDisperserClient struct { mock.Mock + mockRequestIDStore map[string][]byte + mockRetrievalStore map[string][]byte } var _ clients.DisperserClient = (*MockDisperserClient)(nil) func NewMockDisperserClient() *MockDisperserClient { - return &MockDisperserClient{} + return &MockDisperserClient{ + mockRequestIDStore: make(map[string][]byte), + mockRetrievalStore: make(map[string][]byte), + } } func (c *MockDisperserClient) DisperseBlobAuthenticated(ctx context.Context, data []byte, quorums []uint8) (*disperser.BlobStatus, []byte, error) { + // do data validation for the benefit of high-level client tests + _, err := rs.ToFrArray(data) + if err != nil { + return nil, nil, err + } + args := c.Called(data, quorums) var status *disperser.BlobStatus if args.Get(0) != nil { status = (args.Get(0)).(*disperser.BlobStatus) } + var key []byte if args.Get(1) != nil { key = (args.Get(1)).([]byte) } - var err error + if args.Get(2) != nil { err = (args.Get(2)).(error) } + + keyStr := base64.StdEncoding.EncodeToString(key) + c.mockRequestIDStore[keyStr] = data + return status, key, err } @@ -50,6 +74,10 @@ func (c *MockDisperserClient) DisperseBlob(ctx context.Context, data []byte, quo if args.Get(2) != nil { err = (args.Get(2)).(error) } + + keyStr := base64.StdEncoding.EncodeToString(key) + c.mockRequestIDStore[keyStr] = data + return status, key, err } @@ -58,10 +86,33 @@ func (c *MockDisperserClient) GetBlobStatus(ctx context.Context, key []byte) (*d var reply *disperser_rpc.BlobStatusReply if args.Get(0) != nil { reply = (args.Get(0)).(*disperser_rpc.BlobStatusReply) + if reply.Status == disperser_rpc.BlobStatus_FINALIZED { + retrievalKey := fmt.Sprintf("%s-%d", base64.StdEncoding.EncodeToString(reply.Info.BlobVerificationProof.BatchMetadata.BatchHeaderHash), reply.Info.BlobVerificationProof.BlobIndex) + requestIDKey := base64.StdEncoding.EncodeToString(key) + c.mockRetrievalStore[retrievalKey] = c.mockRequestIDStore[requestIDKey] + } } var err error if args.Get(1) != nil { err = (args.Get(1)).(error) } + return reply, err } + +func (c *MockDisperserClient) RetrieveBlob(ctx context.Context, batchHeaderHash []byte, blobIndex uint32) ([]byte, error) { + args := c.Called(batchHeaderHash, blobIndex) + var blob []byte + if args.Get(0) != nil { + blob = (args.Get(0)).([]byte) + } else { + keyStr := fmt.Sprintf("%s-%d", base64.StdEncoding.EncodeToString(batchHeaderHash), blobIndex) + blob = c.mockRetrievalStore[keyStr] + } + + var err error + if args.Get(1) != nil { + err = (args.Get(1)).(error) + } + return blob, err +} diff --git a/clients/mock/node_client.go b/api/clients/mock/node_client.go similarity index 96% rename from clients/mock/node_client.go rename to api/clients/mock/node_client.go index 27d4e8fdbb..6fe9d8b4ba 100644 --- a/clients/mock/node_client.go +++ b/api/clients/mock/node_client.go @@ -3,7 +3,7 @@ package mock import ( "context" - "github.com/Layr-Labs/eigenda/clients" + "github.com/Layr-Labs/eigenda/api/clients" "github.com/Layr-Labs/eigenda/core" "github.com/stretchr/testify/mock" "github.com/wealdtech/go-merkletree" diff --git a/clients/mock/retrieval_client.go b/api/clients/mock/retrieval_client.go similarity index 94% rename from clients/mock/retrieval_client.go rename to api/clients/mock/retrieval_client.go index 63df957451..3a0e3ccf49 100644 --- a/clients/mock/retrieval_client.go +++ b/api/clients/mock/retrieval_client.go @@ -3,7 +3,7 @@ package mock import ( "context" - "github.com/Layr-Labs/eigenda/clients" + "github.com/Layr-Labs/eigenda/api/clients" "github.com/Layr-Labs/eigenda/core" "github.com/stretchr/testify/mock" ) diff --git a/clients/node_client.go b/api/clients/node_client.go similarity index 100% rename from clients/node_client.go rename to api/clients/node_client.go diff --git a/clients/retrieval_client.go b/api/clients/retrieval_client.go similarity index 100% rename from clients/retrieval_client.go rename to api/clients/retrieval_client.go diff --git a/clients/tests/retrieval_client_test.go b/api/clients/retrieval_client_test.go similarity index 98% rename from clients/tests/retrieval_client_test.go rename to api/clients/retrieval_client_test.go index a2ecd29abc..505cc9aee3 100644 --- a/clients/tests/retrieval_client_test.go +++ b/api/clients/retrieval_client_test.go @@ -1,4 +1,4 @@ -package retriever_test +package clients_test import ( "bytes" @@ -6,8 +6,8 @@ import ( "runtime" "testing" - "github.com/Layr-Labs/eigenda/clients" - clientsmock "github.com/Layr-Labs/eigenda/clients/mock" + "github.com/Layr-Labs/eigenda/api/clients" + clientsmock "github.com/Layr-Labs/eigenda/api/clients/mock" "github.com/Layr-Labs/eigenda/core" coreindexer "github.com/Layr-Labs/eigenda/core/indexer" coremock "github.com/Layr-Labs/eigenda/core/mock" diff --git a/api/clients/utils.go b/api/clients/utils.go new file mode 100644 index 0000000000..1ff9d64a1c --- /dev/null +++ b/api/clients/utils.go @@ -0,0 +1,9 @@ +package clients + +import "encoding/binary" + +func ConvertIntToVarUInt(v int) []byte { + buf := make([]byte, binary.MaxVarintLen64) + n := binary.PutUvarint(buf, uint64(v)) + return buf[:n] +} diff --git a/api/go.mod b/api/go.mod deleted file mode 100644 index dd91aee454..0000000000 --- a/api/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/Layr-Labs/eigenda/api - -go 1.21 - -toolchain go1.21.1 - -require ( - google.golang.org/grpc v1.58.3 - google.golang.org/protobuf v1.31.0 -) - -require ( - github.com/golang/protobuf v1.5.3 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect -) diff --git a/api/go.sum b/api/go.sum deleted file mode 100644 index 10acb4d7f8..0000000000 --- a/api/go.sum +++ /dev/null @@ -1,21 +0,0 @@ -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/core/auth/auth_test.go b/core/auth/auth_test.go index 947979f50c..80f318f2cf 100644 --- a/core/auth/auth_test.go +++ b/core/auth/auth_test.go @@ -17,7 +17,7 @@ func TestAuthentication(t *testing.T) { // Make the signer privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - signer := auth.NewSigner(privateKeyHex) + signer := auth.NewLocalBlobRequestSigner(privateKeyHex) testHeader := core.BlobAuthHeader{ BlobCommitments: encoding.BlobCommitments{}, @@ -44,7 +44,7 @@ func TestAuthenticationFail(t *testing.T) { // Make the signer privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - signer := auth.NewSigner(privateKeyHex) + signer := auth.NewLocalBlobRequestSigner(privateKeyHex) testHeader := core.BlobAuthHeader{ BlobCommitments: encoding.BlobCommitments{}, @@ -54,7 +54,7 @@ func TestAuthenticationFail(t *testing.T) { } privateKeyHex = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded" - signer = auth.NewSigner(privateKeyHex) + signer = auth.NewLocalBlobRequestSigner(privateKeyHex) // Sign the header signature, err := signer.SignBlobRequest(testHeader) diff --git a/core/auth/signer.go b/core/auth/signer.go index 69baec49cf..86632f3798 100644 --- a/core/auth/signer.go +++ b/core/auth/signer.go @@ -12,11 +12,13 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -type signer struct { +type LocalBlobRequestSigner struct { PrivateKey *ecdsa.PrivateKey } -func NewSigner(privateKeyHex string) core.BlobRequestSigner { +var _ core.BlobRequestSigner = &LocalBlobRequestSigner{} + +func NewLocalBlobRequestSigner(privateKeyHex string) *LocalBlobRequestSigner { privateKeyBytes := common.FromHex(privateKeyHex) privateKey, err := crypto.ToECDSA(privateKeyBytes) @@ -24,12 +26,12 @@ func NewSigner(privateKeyHex string) core.BlobRequestSigner { log.Fatalf("Failed to parse private key: %v", err) } - return &signer{ + return &LocalBlobRequestSigner{ PrivateKey: privateKey, } } -func (s *signer) SignBlobRequest(header core.BlobAuthHeader) ([]byte, error) { +func (s *LocalBlobRequestSigner) SignBlobRequest(header core.BlobAuthHeader) ([]byte, error) { // Message you want to sign buf := make([]byte, 4) @@ -45,7 +47,7 @@ func (s *signer) SignBlobRequest(header core.BlobAuthHeader) ([]byte, error) { return sig, nil } -func (s *signer) GetAccountID() string { +func (s *LocalBlobRequestSigner) GetAccountID() string { publicKeyBytes := crypto.FromECDSAPub(&s.PrivateKey.PublicKey) return hexutil.Encode(publicKeyBytes) diff --git a/disperser/apiserver/ratelimit_test.go b/disperser/apiserver/ratelimit_test.go index 8a7941d663..69b3f99093 100644 --- a/disperser/apiserver/ratelimit_test.go +++ b/disperser/apiserver/ratelimit_test.go @@ -105,7 +105,7 @@ func TestAuthRatelimit(t *testing.T) { // Use an unauthenticated signer privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeb" - signer := auth.NewSigner(privateKeyHex) + signer := auth.NewLocalBlobRequestSigner(privateKeyHex) errorChan := make(chan error, 10) @@ -130,7 +130,7 @@ func TestAuthRatelimit(t *testing.T) { // Use an authenticated signer privateKeyHex = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded" - signer = auth.NewSigner(privateKeyHex) + signer = auth.NewLocalBlobRequestSigner(privateKeyHex) // This should succeed because the account throughput limit is 100 KiB/s for quorum 0 simulateClient(t, signer, "4.4.4.4", data50KiB, []uint32{0}, 0, errorChan, false) diff --git a/disperser/apiserver/server_test.go b/disperser/apiserver/server_test.go index 50d857346e..d86beae396 100644 --- a/disperser/apiserver/server_test.go +++ b/disperser/apiserver/server_test.go @@ -83,7 +83,7 @@ func TestDisperseBlobAuth(t *testing.T) { // Use an unauthenticated signer privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeb" - signer := auth.NewSigner(privateKeyHex) + signer := auth.NewLocalBlobRequestSigner(privateKeyHex) errorChan := make(chan error, 10) @@ -105,7 +105,7 @@ func TestDisperseBlobAuthTimeout(t *testing.T) { // Use an unauthenticated signer privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeb" - signer := auth.NewSigner(privateKeyHex) + signer := auth.NewLocalBlobRequestSigner(privateKeyHex) errorChan := make(chan error, 10) diff --git a/go.mod b/go.mod index 6db71b7018..ed50d26428 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.21 toolchain go1.21.1 require ( - github.com/Layr-Labs/eigenda/api v0.0.0 github.com/Layr-Labs/eigensdk-go v0.1.7-0.20240507215523-7e4891d5099a github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/credentials v1.17.11 @@ -211,5 +210,3 @@ require ( gopkg.in/yaml.v3 v3.0.1 rsc.io/tmplfunc v0.0.3 // indirect ) - -replace github.com/Layr-Labs/eigenda/api => ./api diff --git a/inabox/tests/integration_suite_test.go b/inabox/tests/integration_suite_test.go index bb13e23e28..c3ec09703f 100644 --- a/inabox/tests/integration_suite_test.go +++ b/inabox/tests/integration_suite_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/Layr-Labs/eigenda/clients" + "github.com/Layr-Labs/eigenda/api/clients" "github.com/Layr-Labs/eigenda/common" "github.com/Layr-Labs/eigenda/common/geth" rollupbindings "github.com/Layr-Labs/eigenda/contracts/bindings/MockRollup" diff --git a/inabox/tests/integration_test.go b/inabox/tests/integration_test.go index 7f6064a255..4bfdfa22f3 100644 --- a/inabox/tests/integration_test.go +++ b/inabox/tests/integration_test.go @@ -7,8 +7,8 @@ import ( "math/big" "time" + "github.com/Layr-Labs/eigenda/api/clients" disperserpb "github.com/Layr-Labs/eigenda/api/grpc/disperser" - "github.com/Layr-Labs/eigenda/clients" rollupbindings "github.com/Layr-Labs/eigenda/contracts/bindings/MockRollup" "github.com/Layr-Labs/eigenda/core/auth" "github.com/Layr-Labs/eigenda/disperser" @@ -34,7 +34,7 @@ var _ = Describe("Inabox Integration", func() { Expect(err).To(BeNil()) privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded" - signer := auth.NewSigner(privateKeyHex) + signer := auth.NewLocalBlobRequestSigner(privateKeyHex) disp := clients.NewDisperserClient(&clients.Config{ Hostname: "localhost", diff --git a/inabox/tests/ratelimit_test.go b/inabox/tests/ratelimit_test.go index 3d233c1b55..47a93decb7 100644 --- a/inabox/tests/ratelimit_test.go +++ b/inabox/tests/ratelimit_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" + "github.com/Layr-Labs/eigenda/api/clients" grpcdisperser "github.com/Layr-Labs/eigenda/api/grpc/disperser" "github.com/Layr-Labs/eigenda/api/grpc/retriever" - "github.com/Layr-Labs/eigenda/clients" "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/disperser" "github.com/Layr-Labs/eigenda/inabox/deploy" diff --git a/retriever/cmd/Dockerfile b/retriever/cmd/Dockerfile index 21cf9e1a44..425b13df43 100644 --- a/retriever/cmd/Dockerfile +++ b/retriever/cmd/Dockerfile @@ -9,7 +9,6 @@ COPY core /app/core COPY api /app/api COPY contracts /app/contracts COPY disperser /app/disperser -COPY clients /app/clients COPY node /app/node COPY operators/churner /app/operators/churner COPY indexer /app/indexer diff --git a/retriever/cmd/main.go b/retriever/cmd/main.go index 5d23d815f7..dda8967ace 100644 --- a/retriever/cmd/main.go +++ b/retriever/cmd/main.go @@ -7,8 +7,8 @@ import ( "net" "os" + "github.com/Layr-Labs/eigenda/api/clients" pb "github.com/Layr-Labs/eigenda/api/grpc/retriever" - "github.com/Layr-Labs/eigenda/clients" "github.com/Layr-Labs/eigenda/common" "github.com/Layr-Labs/eigenda/common/geth" "github.com/Layr-Labs/eigenda/common/healthcheck" diff --git a/retriever/server.go b/retriever/server.go index a641fee6f4..190c9fa177 100644 --- a/retriever/server.go +++ b/retriever/server.go @@ -4,8 +4,8 @@ import ( "context" "errors" + "github.com/Layr-Labs/eigenda/api/clients" pb "github.com/Layr-Labs/eigenda/api/grpc/retriever" - "github.com/Layr-Labs/eigenda/clients" "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/retriever/eth" "github.com/Layr-Labs/eigensdk-go/logging" diff --git a/retriever/server_test.go b/retriever/server_test.go index 227bbed83e..08fed4ebcd 100644 --- a/retriever/server_test.go +++ b/retriever/server_test.go @@ -6,8 +6,8 @@ import ( "runtime" "testing" + clientsmock "github.com/Layr-Labs/eigenda/api/clients/mock" pb "github.com/Layr-Labs/eigenda/api/grpc/retriever" - clientsmock "github.com/Layr-Labs/eigenda/clients/mock" binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" "github.com/Layr-Labs/eigenda/core" coremock "github.com/Layr-Labs/eigenda/core/mock" diff --git a/synthetic-test-client.Dockerfile b/synthetic-test-client.Dockerfile index dbefcd0f63..c1a28641e1 100644 --- a/synthetic-test-client.Dockerfile +++ b/synthetic-test-client.Dockerfile @@ -7,7 +7,6 @@ COPY ./test/synthetic-test /app COPY go.mod /app COPY go.sum /app COPY api /app/api -COPY clients /app/clients COPY node /app/node COPY common /app/common COPY operators/churner /app/operators/churner diff --git a/test/integration_test.go b/test/integration_test.go index 3003560f12..fc5c8c4661 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -22,7 +22,7 @@ import ( "github.com/Layr-Labs/eigenda/encoding/utils/codec" "github.com/consensys/gnark-crypto/ecc/bn254/fp" - clientsmock "github.com/Layr-Labs/eigenda/clients/mock" + clientsmock "github.com/Layr-Labs/eigenda/api/clients/mock" "github.com/Layr-Labs/eigenda/disperser/apiserver" dispatcher "github.com/Layr-Labs/eigenda/disperser/batcher/grpc" "github.com/Layr-Labs/eigenda/disperser/encoder" diff --git a/test/synthetic-test/synthetic_client_test.go b/test/synthetic-test/synthetic_client_test.go index a309f85511..ab868c557a 100644 --- a/test/synthetic-test/synthetic_client_test.go +++ b/test/synthetic-test/synthetic_client_test.go @@ -21,9 +21,9 @@ import ( "github.com/google/uuid" "github.com/shurcooL/graphql" + "github.com/Layr-Labs/eigenda/api/clients" disperser_rpc "github.com/Layr-Labs/eigenda/api/grpc/disperser" retriever_rpc "github.com/Layr-Labs/eigenda/api/grpc/retriever" - "github.com/Layr-Labs/eigenda/clients" common "github.com/Layr-Labs/eigenda/common" "github.com/Layr-Labs/eigenda/common/geth" rollupbindings "github.com/Layr-Labs/eigenda/contracts/bindings/MockRollup" diff --git a/tools/traffic/config.go b/tools/traffic/config.go index f0f797338d..2e04bf65a4 100644 --- a/tools/traffic/config.go +++ b/tools/traffic/config.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "github.com/Layr-Labs/eigenda/clients" + "github.com/Layr-Labs/eigenda/api/clients" "github.com/Layr-Labs/eigenda/common" "github.com/Layr-Labs/eigenda/tools/traffic/flags" "github.com/urfave/cli" diff --git a/tools/traffic/generator.go b/tools/traffic/generator.go index 6650fc6afd..868d736bf1 100644 --- a/tools/traffic/generator.go +++ b/tools/traffic/generator.go @@ -10,7 +10,7 @@ import ( "syscall" "time" - "github.com/Layr-Labs/eigenda/clients" + "github.com/Layr-Labs/eigenda/api/clients" "github.com/Layr-Labs/eigenda/common" "github.com/Layr-Labs/eigenda/encoding/utils/codec" "github.com/Layr-Labs/eigensdk-go/logging" diff --git a/tools/traffic/generator_test.go b/tools/traffic/generator_test.go index 34e72443a7..67b4d7422c 100644 --- a/tools/traffic/generator_test.go +++ b/tools/traffic/generator_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/Layr-Labs/eigenda/clients" - clientsmock "github.com/Layr-Labs/eigenda/clients/mock" + "github.com/Layr-Labs/eigenda/api/clients" + clientsmock "github.com/Layr-Labs/eigenda/api/clients/mock" "github.com/Layr-Labs/eigenda/disperser" "github.com/Layr-Labs/eigenda/tools/traffic" "github.com/Layr-Labs/eigensdk-go/logging"