Skip to content
This repository was archived by the owner on May 7, 2025. It is now read-only.
133 changes: 133 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package da

import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

pbda "github.com/rollkit/go-da/types/pb/da"
)

// Code defines error codes for JSON-RPC.
//
// They are reused for gRPC
type Code int

// gRPC checks for GRPCStatus method on errors to enable advanced error handling.

// Codes are used by JSON-RPC client and server
const (
CodeBlobNotFound Code = 32001
CodeBlobSizeOverLimit Code = 32002
CodeTxTimedOut Code = 32003
CodeTxAlreadyInMempool Code = 32004
CodeTxIncorrectAccountSequence Code = 32005
CodeTxTooLarge Code = 32006
CodeContextDeadline Code = 32007
CodeFutureHeight Code = 32008
)

// ErrBlobNotFound is used to indicate that the blob was not found.
type ErrBlobNotFound struct{}

func (e *ErrBlobNotFound) Error() string {
return "blob: not found"
}

// GRPCStatus returns the gRPC status with details for an ErrBlobNotFound error.
func (e *ErrBlobNotFound) GRPCStatus() *status.Status {
return getGRPCStatus(e, codes.NotFound, pbda.ErrorCode_ERROR_CODE_BLOB_NOT_FOUND)
}

// ErrBlobSizeOverLimit is used to indicate that the blob size is over limit.
type ErrBlobSizeOverLimit struct{}

func (e *ErrBlobSizeOverLimit) Error() string {
return "blob: over size limit"
}

// GRPCStatus returns the gRPC status with details for an ErrBlobSizeOverLimit error.
func (e *ErrBlobSizeOverLimit) GRPCStatus() *status.Status {
return getGRPCStatus(e, codes.ResourceExhausted, pbda.ErrorCode_ERROR_CODE_BLOB_SIZE_OVER_LIMIT)
}

// ErrTxTimedOut is the error message returned by the DA when mempool is congested.
type ErrTxTimedOut struct{}

func (e *ErrTxTimedOut) Error() string {
return "timed out waiting for tx to be included in a block"
}

// GRPCStatus returns the gRPC status with details for an ErrTxTimedOut error.
func (e *ErrTxTimedOut) GRPCStatus() *status.Status {
return getGRPCStatus(e, codes.DeadlineExceeded, pbda.ErrorCode_ERROR_CODE_TX_TIMED_OUT)
}

// ErrTxAlreadyInMempool is the error message returned by the DA when tx is already in mempool.
type ErrTxAlreadyInMempool struct{}

func (e *ErrTxAlreadyInMempool) Error() string {
return "tx already in mempool"
}

// GRPCStatus returns the gRPC status with details for an ErrTxAlreadyInMempool error.
func (e *ErrTxAlreadyInMempool) GRPCStatus() *status.Status {
return getGRPCStatus(e, codes.AlreadyExists, pbda.ErrorCode_ERROR_CODE_TX_ALREADY_IN_MEMPOOL)
}

// ErrTxIncorrectAccountSequence is the error message returned by the DA when tx has incorrect sequence.
type ErrTxIncorrectAccountSequence struct{}

func (e *ErrTxIncorrectAccountSequence) Error() string {
return "incorrect account sequence"
}

// GRPCStatus returns the gRPC status with details for an ErrTxIncorrectAccountSequence error.
func (e *ErrTxIncorrectAccountSequence) GRPCStatus() *status.Status {
return getGRPCStatus(e, codes.InvalidArgument, pbda.ErrorCode_ERROR_CODE_TX_INCORRECT_ACCOUNT_SEQUENCE)
}

// ErrTxTooLarge is the err message returned by the DA when tx size is too large.
type ErrTxTooLarge struct{}

func (e *ErrTxTooLarge) Error() string {
return "tx too large"
}

// GRPCStatus returns the gRPC status with details for an ErrTxTooLarge error.
func (e *ErrTxTooLarge) GRPCStatus() *status.Status {
return getGRPCStatus(e, codes.ResourceExhausted, pbda.ErrorCode_ERROR_CODE_TX_TOO_LARGE)
}

// ErrContextDeadline is the error message returned by the DA when context deadline exceeds.
type ErrContextDeadline struct{}

func (e *ErrContextDeadline) Error() string {
return "context deadline"
}

// GRPCStatus returns the gRPC status with details for an ErrContextDeadline error.
func (e *ErrContextDeadline) GRPCStatus() *status.Status {
return getGRPCStatus(e, codes.DeadlineExceeded, pbda.ErrorCode_ERROR_CODE_CONTEXT_DEADLINE)
}

// ErrFutureHeight is returned when requested height is from the future
type ErrFutureHeight struct{}

func (e *ErrFutureHeight) Error() string {
return "given height is from the future"
}

// GRPCStatus returns the gRPC status with details for an ErrFutureHeight error.
func (e *ErrFutureHeight) GRPCStatus() *status.Status {
return getGRPCStatus(e, codes.OutOfRange, pbda.ErrorCode_ERROR_CODE_FUTURE_HEIGHT)
}

// getGRPCStatus constructs a gRPC status with error details based on the provided error, gRPC code, and DA error code.
func getGRPCStatus(err error, grpcCode codes.Code, daCode pbda.ErrorCode) *status.Status {
base := status.New(grpcCode, err.Error())
detailed, err := base.WithDetails(&pbda.ErrorDetails{Code: daCode})
if err != nil {
return base
}
return detailed
}
16 changes: 16 additions & 0 deletions proto/da/da.proto
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,19 @@ message ValidateRequest {
message ValidateResponse {
repeated bool results = 1;
}

enum ErrorCode {
ERROR_CODE_UNSPECIFIED = 0;
ERROR_CODE_BLOB_NOT_FOUND = 32001;
ERROR_CODE_BLOB_SIZE_OVER_LIMIT = 32002;
ERROR_CODE_TX_TIMED_OUT = 32003;
ERROR_CODE_TX_ALREADY_IN_MEMPOOL = 32004;
ERROR_CODE_TX_INCORRECT_ACCOUNT_SEQUENCE = 32005;
ERROR_CODE_TX_TOO_LARGE = 32006;
ERROR_CODE_CONTEXT_DEADLINE = 32007;
ERROR_CODE_FUTURE_HEIGHT = 32008;
}

message ErrorDetails {
ErrorCode code = 1;
}
14 changes: 7 additions & 7 deletions proxy/grpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (c *Client) MaxBlobSize(ctx context.Context) (uint64, error) {
req := &pbda.MaxBlobSizeRequest{}
resp, err := c.client.MaxBlobSize(ctx, req)
if err != nil {
return 0, err
return 0, tryToMapError(err)
}
return resp.MaxBlobSize, nil
}
Expand All @@ -59,7 +59,7 @@ func (c *Client) Get(ctx context.Context, ids []da.ID, namespace da.Namespace) (
}
resp, err := c.client.Get(ctx, req)
if err != nil {
return nil, err
return nil, tryToMapError(err)
}

return blobsPB2DA(resp.Blobs), nil
Expand All @@ -70,7 +70,7 @@ func (c *Client) GetIDs(ctx context.Context, height uint64, namespace da.Namespa
req := &pbda.GetIdsRequest{Height: height, Namespace: &pbda.Namespace{Value: namespace}}
resp, err := c.client.GetIds(ctx, req)
if err != nil {
return nil, err
return nil, tryToMapError(err)
}

timestamp, err := types.TimestampFromProto(resp.Timestamp)
Expand Down Expand Up @@ -103,7 +103,7 @@ func (c *Client) Commit(ctx context.Context, blobs []da.Blob, namespace da.Names

resp, err := c.client.Commit(ctx, req)
if err != nil {
return nil, err
return nil, tryToMapError(err)
}

return commitsPB2DA(resp.Commitments), nil
Expand All @@ -119,7 +119,7 @@ func (c *Client) Submit(ctx context.Context, blobs []da.Blob, gasPrice float64,

resp, err := c.client.Submit(ctx, req)
if err != nil {
return nil, err
return nil, tryToMapError(err)
}

ids := make([]da.ID, len(resp.Ids))
Expand All @@ -141,7 +141,7 @@ func (c *Client) SubmitWithOptions(ctx context.Context, blobs []da.Blob, gasPric

resp, err := c.client.Submit(ctx, req)
if err != nil {
return nil, err
return nil, tryToMapError(err)
}

ids := make([]da.ID, len(resp.Ids))
Expand All @@ -160,5 +160,5 @@ func (c *Client) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, n
Namespace: &pbda.Namespace{Value: namespace},
}
resp, err := c.client.Validate(ctx, req)
return resp.Results, err
return resp.Results, tryToMapError(err)
}
53 changes: 53 additions & 0 deletions proxy/grpc/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package grpc

import (
"errors"

"google.golang.org/grpc/status"

"github.com/rollkit/go-da"
pbda "github.com/rollkit/go-da/types/pb/da"
)

func tryToMapError(err error) error {
if err == nil {
return nil
}

s, ok := status.FromError(err)
if ok {
details := s.Proto().Details
if len(details) == 1 {
var errorDetail pbda.ErrorDetails
unmarshalError := errorDetail.Unmarshal(details[0].Value)
if unmarshalError != nil {
return err
}
return errorForCode(errorDetail.Code)
}
}
return err
}

func errorForCode(code pbda.ErrorCode) error {
switch code {
case pbda.ErrorCode_ERROR_CODE_BLOB_NOT_FOUND:
return &da.ErrBlobNotFound{}
case pbda.ErrorCode_ERROR_CODE_BLOB_SIZE_OVER_LIMIT:
return &da.ErrBlobSizeOverLimit{}
case pbda.ErrorCode_ERROR_CODE_TX_TIMED_OUT:
return &da.ErrTxTimedOut{}
case pbda.ErrorCode_ERROR_CODE_TX_ALREADY_IN_MEMPOOL:
return &da.ErrTxAlreadyInMempool{}
case pbda.ErrorCode_ERROR_CODE_TX_INCORRECT_ACCOUNT_SEQUENCE:
return &da.ErrTxIncorrectAccountSequence{}
case pbda.ErrorCode_ERROR_CODE_TX_TOO_LARGE:
return &da.ErrTxTooLarge{}
case pbda.ErrorCode_ERROR_CODE_CONTEXT_DEADLINE:
return &da.ErrContextDeadline{}
case pbda.ErrorCode_ERROR_CODE_FUTURE_HEIGHT:
return &da.ErrFutureHeight{}
default:
return errors.New("unknown error code")
}
}
3 changes: 2 additions & 1 deletion proxy/jsonrpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ func NewClient(ctx context.Context, addr string, token string) (*Client, error)
func newClient(ctx context.Context, addr string, authHeader http.Header) (*Client, error) {
var multiCloser multiClientCloser
var client Client
errs := getKnownErrorsMapping()
for name, module := range moduleMap(&client) {
closer, err := jsonrpc.NewClient(ctx, addr, name, module, authHeader)
closer, err := jsonrpc.NewMergeClient(ctx, addr, name, []interface{}{module}, authHeader, jsonrpc.WithErrors(errs))
if err != nil {
return nil, err
}
Expand Down
21 changes: 21 additions & 0 deletions proxy/jsonrpc/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package jsonrpc

import (
"github.com/filecoin-project/go-jsonrpc"

"github.com/rollkit/go-da"
)

// getKnownErrorsMapping returns a mapping of known error codes to their corresponding error types.
func getKnownErrorsMapping() jsonrpc.Errors {
errs := jsonrpc.NewErrors()
errs.Register(jsonrpc.ErrorCode(da.CodeBlobNotFound), new(*da.ErrBlobNotFound))
errs.Register(jsonrpc.ErrorCode(da.CodeBlobSizeOverLimit), new(*da.ErrBlobSizeOverLimit))
errs.Register(jsonrpc.ErrorCode(da.CodeTxTimedOut), new(*da.ErrTxTimedOut))
errs.Register(jsonrpc.ErrorCode(da.CodeTxAlreadyInMempool), new(*da.ErrTxAlreadyInMempool))
errs.Register(jsonrpc.ErrorCode(da.CodeTxIncorrectAccountSequence), new(*da.ErrTxIncorrectAccountSequence))
errs.Register(jsonrpc.ErrorCode(da.CodeTxTooLarge), new(*da.ErrTxTooLarge))
errs.Register(jsonrpc.ErrorCode(da.CodeContextDeadline), new(*da.ErrContextDeadline))
errs.Register(jsonrpc.ErrorCode(da.CodeFutureHeight), new(*da.ErrFutureHeight))
return errs
}
2 changes: 1 addition & 1 deletion proxy/jsonrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (s *Server) RegisterService(namespace string, service interface{}, out inte

// NewServer accepts the host address port and the DA implementation to serve as a jsonrpc service
func NewServer(address, port string, DA da.DA) *Server {
rpc := jsonrpc.NewServer()
rpc := jsonrpc.NewServer(jsonrpc.WithServerErrors(getKnownErrorsMapping()))
srv := &Server{
rpc: rpc,
srv: &http.Server{
Expand Down
7 changes: 2 additions & 5 deletions test/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import (
// DefaultMaxBlobSize is the default max blob size
const DefaultMaxBlobSize = 64 * 64 * 482

// ErrTooHigh is returned when requested height is to high
var ErrTooHigh = errors.New("given height is from the future")

// DummyDA is a simple implementation of in-memory DA. Not production ready! Intended only for testing!
//
// Data is stored in a map, where key is a serialized sequence number. This key is returned as ID.
Expand Down Expand Up @@ -78,7 +75,7 @@ func (d *DummyDA) Get(ctx context.Context, ids []da.ID, _ da.Namespace) ([]da.Bl
}
}
if !found {
return nil, errors.New("no blob for given ID")
return nil, &da.ErrBlobNotFound{}
}
}
return blobs, nil
Expand All @@ -90,7 +87,7 @@ func (d *DummyDA) GetIDs(ctx context.Context, height uint64, _ da.Namespace) (*d
defer d.mu.Unlock()

if height > d.height {
return nil, ErrTooHigh
return nil, &da.ErrFutureHeight{}
}

kvps, ok := d.data[height]
Expand Down
6 changes: 4 additions & 2 deletions test/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ func BasicDATest(t *testing.T, d da.DA) {
// CheckErrors ensures that errors are handled properly by DA.
func CheckErrors(t *testing.T, d da.DA) {
ctx := context.TODO()
blob, err := d.Get(ctx, []da.ID{[]byte("invalid")}, testNamespace)
blob, err := d.Get(ctx, []da.ID{[]byte("invalid blob id")}, testNamespace)
assert.Error(t, err)
assert.ErrorIs(t, err, &da.ErrBlobNotFound{})
assert.Empty(t, blob)
}

Expand Down Expand Up @@ -140,7 +141,7 @@ func ConcurrentReadWriteTest(t *testing.T, d da.DA) {
for i := uint64(1); i <= 100; i++ {
_, err := d.GetIDs(ctx, i, []byte{})
if err != nil {
assert.Equal(t, err.Error(), ErrTooHigh.Error())
assert.ErrorIs(t, err, &da.ErrFutureHeight{})
}
}
}()
Expand All @@ -161,5 +162,6 @@ func HeightFromFutureTest(t *testing.T, d da.DA) {
ctx := context.TODO()
ret, err := d.GetIDs(ctx, 999999999, []byte{})
assert.Error(t, err)
assert.ErrorIs(t, err, &da.ErrFutureHeight{})
assert.Nil(t, ret)
}
Loading