Skip to content

Commit 8b8e5b1

Browse files
Feature: json RPC like at node (#424)
* Feature: json RPC like at node * Fixes
1 parent 203efc2 commit 8b8e5b1

File tree

19 files changed

+793
-1
lines changed

19 files changed

+793
-1
lines changed

.github/workflows/build.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,49 @@ jobs:
204204
cache-to: type=gha,mode=max
205205
tags: ${{ steps.meta-celestials.outputs.tags }}
206206
labels: ${{ steps.meta-celestials.outputs.labels }}
207+
208+
# Json Rpc
209+
build_jsonrpc:
210+
name: Build Json RPC
211+
runs-on: ubuntu-latest
212+
env:
213+
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
214+
DOCKER_REGISTRY: ghcr.io
215+
DOCKER_IMAGE_BASE: ${{ github.repository }}
216+
steps:
217+
- name: Check out the repo
218+
uses: actions/checkout@v4
219+
220+
- name: Set up Docker Buildx
221+
uses: docker/setup-buildx-action@v3
222+
223+
- name: Log in to the registry
224+
uses: docker/login-action@v3
225+
with:
226+
registry: ${{ env.DOCKER_REGISTRY }}
227+
username: ${{ github.actor }}
228+
password: ${{ secrets.GITHUB_TOKEN }}
229+
230+
- name: Json RPC validate build configuration
231+
uses: docker/build-push-action@v6
232+
with:
233+
context: .
234+
call: check
235+
file: build/jsonrpc/Dockerfile
236+
237+
- name: Json RPC image tags & labels
238+
id: meta-jsonrpc
239+
uses: docker/metadata-action@v5
240+
with:
241+
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_BASE }}-jsonrpc
242+
243+
- name: Json RPC image build & push
244+
uses: docker/build-push-action@v6
245+
with:
246+
context: .
247+
file: build/jsonrpc/Dockerfile
248+
push: true
249+
cache-from: type=gha
250+
cache-to: type=gha,mode=max
251+
tags: ${{ steps.meta-jsonrpc.outputs.tags }}
252+
labels: ${{ steps.meta-jsonrpc.outputs.labels }}

.vscode/launch.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@
4848
"../../configs/dipdup.yml",
4949
],
5050
"envFile": "${workspaceFolder}/.env"
51+
}, {
52+
"name": "Launch JSON RPC",
53+
"type": "go",
54+
"request": "launch",
55+
"mode": "debug",
56+
"program": "${workspaceFolder}/cmd/jsonrpc",
57+
"args": [
58+
"-c",
59+
"../../configs/dipdup.yml",
60+
],
61+
"envFile": "${workspaceFolder}/.env"
5162
},
5263
]
5364
}

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ api:
1313
private_api:
1414
cd cmd/private_api && go run . -c ../../configs/dipdup.yml
1515

16+
jsonrpc:
17+
cd cmd/jsonrpc && go run . -c ../../configs/dipdup.yml
18+
1619
celestials:
1720
cd cmd/celestials && go run . -c ../../configs/dipdup.yml
1821

build/jsonrpc/Dockerfile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# ---------------------------------------------------------------------
2+
# The first stage container, for building the application
3+
# ---------------------------------------------------------------------
4+
FROM golang:1.24.4-alpine AS builder
5+
6+
ENV CGO_ENABLED=0
7+
ENV GO111MODULE=on
8+
ENV GOOS=linux
9+
10+
RUN apk --no-cache add ca-certificates
11+
12+
RUN mkdir -p $GOPATH/src/github.com/celenium-io/celestia-indexer/
13+
14+
COPY ./go.* $GOPATH/src/github.com/celenium-io/celestia-indexer/
15+
WORKDIR $GOPATH/src/github.com/celenium-io/celestia-indexer
16+
RUN go mod download
17+
18+
COPY cmd/jsonrpc cmd/jsonrpc
19+
COPY internal internal
20+
COPY pkg pkg
21+
22+
WORKDIR $GOPATH/src/github.com/celenium-io/celestia-indexer/cmd/jsonrpc/
23+
RUN go build -a -installsuffix cgo -o /go/bin/jsonrpc .
24+
25+
# ---------------------------------------------------------------------
26+
# The second stage container, for running the application
27+
# ---------------------------------------------------------------------
28+
FROM scratch
29+
30+
WORKDIR /app/jsonrpc
31+
32+
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
33+
COPY --from=builder /go/bin/jsonrpc /go/bin/jsonrpc
34+
COPY ./configs/dipdup.yml ./
35+
COPY database database
36+
37+
ENTRYPOINT ["/go/bin/jsonrpc", "-c", "dipdup.yml"]

cmd/api/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ func initEcho(cfg ApiConfig, env string) *echo.Echo {
210210
e.Use(middleware.DecompressWithConfig(middleware.DecompressConfig{
211211
Skipper: websocketSkipper,
212212
}))
213-
e.Use(middleware.BodyLimit("2M"))
213+
e.Use(middleware.BodyLimit("9M"))
214214
e.Use(middleware.CSRFWithConfig(
215215
middleware.CSRFConfig{
216216
Skipper: func(c echo.Context) bool {

cmd/jsonrpc/config.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-FileCopyrightText: 2025 PK Lab AG <celenium@baking-bad.org>
2+
// SPDX-License-Identifier: MIT
3+
4+
package main
5+
6+
import (
7+
indexerConfig "github.com/celenium-io/celestia-indexer/pkg/indexer/config"
8+
"github.com/dipdup-net/go-lib/config"
9+
)
10+
11+
type Config struct {
12+
*config.Config `yaml:",inline"`
13+
LogLevel string `validate:"omitempty,oneof=debug trace info warn error fatal panic" yaml:"log_level"`
14+
JsonRpcConfig JsonRpcConfig `validate:"required" yaml:"jsonrpc"`
15+
Indexer indexerConfig.Indexer `validate:"required" yaml:"indexer"`
16+
Environment string `validate:"omitempty,oneof=development production" yaml:"environment"`
17+
}
18+
19+
type JsonRpcConfig struct {
20+
Bind string `validate:"required,hostname_port" yaml:"bind"`
21+
RateLimit float64 `validate:"omitempty,min=0" yaml:"rate_limit"`
22+
Prometheus bool `validate:"omitempty" yaml:"prometheus"`
23+
RequestTimeout int `validate:"omitempty,min=1" yaml:"request_timeout"`
24+
BlobReceiver string `validate:"required" yaml:"blob_receiver"`
25+
SentryDsn string `validate:"omitempty" yaml:"sentry_dsn"`
26+
}

cmd/jsonrpc/handler.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// SPDX-FileCopyrightText: 2025 PK Lab AG <celenium@baking-bad.org>
2+
// SPDX-License-Identifier: MIT
3+
4+
package main
5+
6+
import (
7+
"context"
8+
"encoding/base64"
9+
"errors"
10+
"slices"
11+
"sync"
12+
13+
"github.com/celenium-io/celestia-indexer/internal/storage"
14+
"github.com/celenium-io/celestia-indexer/pkg/node"
15+
nodeTypes "github.com/celenium-io/celestia-indexer/pkg/node/types"
16+
"github.com/celenium-io/celestia-indexer/pkg/types"
17+
)
18+
19+
type BlobHandler struct {
20+
receiver node.DalApi
21+
logs storage.IBlobLog
22+
namespaces storage.INamespace
23+
}
24+
25+
func NewBlobHandler(
26+
receiver node.DalApi,
27+
logs storage.IBlobLog,
28+
namespaces storage.INamespace,
29+
) *BlobHandler {
30+
return &BlobHandler{
31+
receiver: receiver,
32+
logs: logs,
33+
namespaces: namespaces,
34+
}
35+
}
36+
37+
func (h *BlobHandler) Get(ctx context.Context, height types.Level, namespace string, commitment string) (nodeTypes.Blob, error) {
38+
return h.receiver.Blob(ctx, height, namespace, commitment)
39+
}
40+
41+
func (h *BlobHandler) GetAll(ctx context.Context, height types.Level, namespaces []string) ([]nodeTypes.Blob, error) {
42+
var (
43+
resultBlobs = make([][]nodeTypes.Blob, len(namespaces))
44+
resultErr = make([]error, len(namespaces))
45+
wg = new(sync.WaitGroup)
46+
)
47+
for i, namespace := range namespaces {
48+
wg.Add(1)
49+
go func(i int, namespace string) {
50+
defer wg.Done()
51+
52+
hash, err := base64.StdEncoding.DecodeString(namespace)
53+
if err != nil {
54+
resultErr[i] = err
55+
return
56+
}
57+
ns, err := h.namespaces.ByNamespaceIdAndVersion(ctx, hash[1:], hash[0])
58+
if err != nil {
59+
resultErr[i] = err
60+
return
61+
}
62+
63+
blobs, err := h.logs.ByNamespace(ctx, ns.Id, storage.BlobLogFilters{
64+
Limit: 100,
65+
Height: height,
66+
})
67+
if err != nil {
68+
resultErr[i] = err
69+
return
70+
}
71+
if len(blobs) > 0 {
72+
res := make([]nodeTypes.Blob, 0)
73+
for i := range blobs {
74+
b, err := h.receiver.Blob(ctx, height, namespace, blobs[i].Commitment)
75+
if err != nil {
76+
resultErr[i] = err
77+
return
78+
}
79+
res = append(res, b)
80+
}
81+
resultBlobs[i] = res
82+
}
83+
}(i, namespace)
84+
}
85+
wg.Wait()
86+
87+
blobs := slices.Concat(resultBlobs...)
88+
err := errors.Join(resultErr...)
89+
return blobs, err
90+
}

cmd/jsonrpc/handler_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-FileCopyrightText: 2025 PK Lab AG <celenium@baking-bad.org>
2+
// SPDX-License-Identifier: MIT
3+
4+
package main
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/celenium-io/celestia-indexer/internal/storage"
11+
"github.com/celenium-io/celestia-indexer/internal/storage/mock"
12+
testsuite "github.com/celenium-io/celestia-indexer/internal/test_suite"
13+
nodeMock "github.com/celenium-io/celestia-indexer/pkg/node/mock"
14+
"github.com/celenium-io/celestia-indexer/pkg/node/types"
15+
pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types"
16+
"github.com/labstack/echo/v4"
17+
"github.com/stretchr/testify/suite"
18+
"go.uber.org/mock/gomock"
19+
)
20+
21+
// HandlerTestSuite -
22+
type HandlerTestSuite struct {
23+
suite.Suite
24+
ns *mock.MockINamespace
25+
logs *mock.MockIBlobLog
26+
blobReceiver *nodeMock.MockDalApi
27+
echo *echo.Echo
28+
handler *BlobHandler
29+
ctrl *gomock.Controller
30+
}
31+
32+
// SetupSuite -
33+
func (s *HandlerTestSuite) SetupSuite() {
34+
s.echo = echo.New()
35+
s.ctrl = gomock.NewController(s.T())
36+
s.ns = mock.NewMockINamespace(s.ctrl)
37+
s.logs = mock.NewMockIBlobLog(s.ctrl)
38+
s.blobReceiver = nodeMock.NewMockDalApi(s.ctrl)
39+
s.handler = NewBlobHandler(s.blobReceiver, s.logs, s.ns)
40+
}
41+
42+
// TearDownSuite -
43+
func (s *HandlerTestSuite) TearDownSuite() {
44+
s.ctrl.Finish()
45+
s.Require().NoError(s.echo.Shutdown(context.Background()))
46+
}
47+
48+
func TestSuiteHandler_Run(t *testing.T) {
49+
suite.Run(t, new(HandlerTestSuite))
50+
}
51+
52+
func (s *HandlerTestSuite) TestGet() {
53+
data := testsuite.RandomText(100)
54+
s.blobReceiver.EXPECT().
55+
Blob(gomock.Any(), pkgTypes.Level(6357101), "AAAAAAAAAAAAAAAAAAAAAAAAAMod4SqXFNgavI8=", "TekzHEK3JoBFapZdQFUoPwtuzUgpEd6OWyhGGzFTv3s=").
56+
Return(types.Blob{
57+
Namespace: "AAAAAAAAAAAAAAAAAAAAAAAAAMod4SqXFNgavI8=",
58+
Commitment: "TekzHEK3JoBFapZdQFUoPwtuzUgpEd6OWyhGGzFTv3s=",
59+
ShareVersion: 0,
60+
Data: data,
61+
}, nil).
62+
Times(1)
63+
64+
response, err := s.handler.Get(s.T().Context(), 6357101, "AAAAAAAAAAAAAAAAAAAAAAAAAMod4SqXFNgavI8=", "TekzHEK3JoBFapZdQFUoPwtuzUgpEd6OWyhGGzFTv3s=")
65+
s.Require().NoError(err)
66+
s.Require().EqualValues("AAAAAAAAAAAAAAAAAAAAAAAAAMod4SqXFNgavI8=", response.Namespace)
67+
s.Require().EqualValues("TekzHEK3JoBFapZdQFUoPwtuzUgpEd6OWyhGGzFTv3s=", response.Commitment)
68+
s.Require().EqualValues(data, response.Data)
69+
s.Require().EqualValues(0, response.ShareVersion)
70+
}
71+
72+
func (s *HandlerTestSuite) TestGetAll() {
73+
data := testsuite.RandomText(100)
74+
75+
s.ns.EXPECT().
76+
ByNamespaceIdAndVersion(gomock.Any(), gomock.Any(), byte(0)).
77+
Return(storage.Namespace{
78+
Id: 1,
79+
}, nil).
80+
Times(1)
81+
82+
s.logs.EXPECT().
83+
ByNamespace(gomock.Any(), uint64(1), storage.BlobLogFilters{
84+
Limit: 100,
85+
Height: 6357101,
86+
}).
87+
Return([]storage.BlobLog{
88+
{
89+
Commitment: "TekzHEK3JoBFapZdQFUoPwtuzUgpEd6OWyhGGzFTv3s=",
90+
},
91+
}, nil).
92+
Times(1)
93+
94+
s.blobReceiver.EXPECT().
95+
Blob(gomock.Any(), pkgTypes.Level(6357101), "AAAAAAAAAAAAAAAAAAAAAAAAAMod4SqXFNgavI8=", "TekzHEK3JoBFapZdQFUoPwtuzUgpEd6OWyhGGzFTv3s=").
96+
Return(types.Blob{
97+
Namespace: "AAAAAAAAAAAAAAAAAAAAAAAAAMod4SqXFNgavI8=",
98+
Commitment: "TekzHEK3JoBFapZdQFUoPwtuzUgpEd6OWyhGGzFTv3s=",
99+
ShareVersion: 0,
100+
Data: data,
101+
}, nil).
102+
Times(1)
103+
104+
response, err := s.handler.GetAll(s.T().Context(), 6357101, []string{"AAAAAAAAAAAAAAAAAAAAAAAAAMod4SqXFNgavI8="})
105+
s.Require().NoError(err)
106+
s.Require().Len(response, 1)
107+
108+
s.Require().EqualValues("AAAAAAAAAAAAAAAAAAAAAAAAAMod4SqXFNgavI8=", response[0].Namespace)
109+
s.Require().EqualValues("TekzHEK3JoBFapZdQFUoPwtuzUgpEd6OWyhGGzFTv3s=", response[0].Commitment)
110+
s.Require().EqualValues(data, response[0].Data)
111+
s.Require().EqualValues(0, response[0].ShareVersion)
112+
}

0 commit comments

Comments
 (0)