Skip to content

Commit

Permalink
Merge pull request #3 from machinefi/ioid-with-http
Browse files Browse the repository at this point in the history
add ioid & device query http api
  • Loading branch information
huangzhiran authored Aug 28, 2024
2 parents e591e22 + 16ae7d8 commit 85cc7ec
Showing 19 changed files with 1,935 additions and 20 deletions.
17 changes: 10 additions & 7 deletions cmd/sequencer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
FROM --platform=linux/amd64 golang:1.22 AS builder
FROM --platform=linux/amd64 golang:1.22-alpine AS builder

Check warning on line 1 in cmd/sequencer/Dockerfile

GitHub Actions / build_image (ubuntu-latest)

FROM --platform flag should not use a constant value

FromPlatformFlagConstDisallowed: FROM --platform flag should not use constant value "linux/amd64" More info: https://docs.docker.com/go/dockerfile/rule/from-platform-flag-const-disallowed/

ENV GO111MODULE=on

RUN apk update && apk upgrade && apk add --no-cache ca-certificates tzdata musl-dev gcc && update-ca-certificates

WORKDIR /go/src
COPY ./ ./

RUN cd ./cmd/sequencer && make build
RUN cd ./cmd/sequencer && CGO_ENABLED=1 CGO_LDFLAGS='-L./lib/linux-x86_64 -lioConnectCore' go build -ldflags "-s -w -extldflags '-static'" -o pebble-sequencer

FROM --platform=linux/amd64 alpine:3.20 AS runtime

Check warning on line 12 in cmd/sequencer/Dockerfile

GitHub Actions / build_image (ubuntu-latest)

FROM --platform flag should not use a constant value

FromPlatformFlagConstDisallowed: FROM --platform flag should not use constant value "linux/amd64" More info: https://docs.docker.com/go/dockerfile/rule/from-platform-flag-const-disallowed/

ENV LANG en_US.UTF-8

Check warning on line 14 in cmd/sequencer/Dockerfile

GitHub Actions / build_image (ubuntu-latest)

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

FROM --platform=linux/amd64 scratch AS runtime
RUN apk add --no-cache ca-certificates tzdata

COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2
COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /go/src/cmd/sequencer/pebble-sequencer /go/bin/pebble-sequencer
EXPOSE 80
EXPOSE 9000

WORKDIR /go/bin
ENTRYPOINT ["/go/bin/pebble-sequencer"]
185 changes: 185 additions & 0 deletions cmd/sequencer/api/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package api

import (
"context"
"log/slog"
"net/http"
"strings"

"github.com/gin-gonic/gin"
"github.com/machinefi/ioconnect-go/pkg/ioconnect"
"github.com/pkg/errors"

"github.com/machinefi/sprout-pebble-sequencer/cmd/sequencer/apitypes"
"github.com/machinefi/sprout-pebble-sequencer/cmd/sequencer/clients"
"github.com/machinefi/sprout-pebble-sequencer/pkg/models"
"github.com/machinefi/sprout-pebble-sequencer/pkg/modules/event"
)

type httpServer struct {
ctx context.Context
engine *gin.Engine
jwk *ioconnect.JWK
clients *clients.Manager
}

func NewHttpServer(ctx context.Context, jwk *ioconnect.JWK, clientMgr *clients.Manager) *httpServer {
s := &httpServer{
ctx: ctx,
engine: gin.Default(),
jwk: jwk,
clients: clientMgr,
}

slog.Debug("jwk information",
"did:io", jwk.DID(),
"did:io#key", jwk.KID(),
"ka did:io", jwk.KeyAgreementDID(),
"ka did:io#key", jwk.KeyAgreementKID(),
"doc", jwk.Doc(),
)

s.engine.POST("/issue_vc", s.issueJWTCredential)
s.engine.POST("/device/:imei/confirm", s.verifyToken, s.confirmDevice)
s.engine.GET("/device/:imei/query", s.verifyToken, s.queryDeviceState)
s.engine.GET("/didDoc", s.didDoc)

return s
}

// this func will block caller
func (s *httpServer) Run(address string) error {
if err := s.engine.Run(address); err != nil {
return errors.Wrap(err, "failed to start http server")
}
return nil
}

// verifyToken make sure the client token is issued by sequencer
func (s *httpServer) verifyToken(c *gin.Context) {
tok := c.GetHeader("Authorization")
if tok == "" {
tok = c.Query("authorization")
}

if tok == "" {
return
}

tok = strings.TrimSpace(strings.Replace(tok, "Bearer", " ", 1))

clientID, err := s.jwk.VerifyToken(tok)
if err != nil {
c.JSON(http.StatusUnauthorized, apitypes.NewErrRsp(errors.Wrap(err, "invalid credential token")))
return
}
client := s.clients.ClientByIoID(clientID)
if client == nil {
c.JSON(http.StatusUnauthorized, apitypes.NewErrRsp(errors.New("invalid credential token")))
return
}

ctx := clients.WithClientID(c.Request.Context(), client)
c.Request = c.Request.WithContext(ctx)
}

func (s *httpServer) confirmDevice(c *gin.Context) {
//imei := c.Param("imei")

}

func (s *httpServer) queryDeviceState(c *gin.Context) {
imei := c.Param("imei")

dev := &models.Device{ID: imei}
if err := event.FetchByPrimary(s.ctx, dev); err != nil {
c.JSON(http.StatusInternalServerError, apitypes.NewErrRsp(err))
return
}
if dev.Status == models.CREATED {
c.JSON(http.StatusBadRequest, apitypes.NewErrRsp(errors.Errorf("device %s is not propsaled", dev.ID)))
return
}
var (
firmware string
uri string
version string
)
if parts := strings.Split(dev.RealFirmware, " "); len(parts) == 2 {
app := &models.App{ID: parts[0]}
err := event.FetchByPrimary(s.ctx, app)
if err == nil {
firmware = app.ID
uri = app.Uri
version = app.Version
}
}

// meta := contexts.AppMeta().MustFrom(ctx)
//pubType := "pub_DeviceQueryRsp"
pubData := &struct {
Status int32 `json:"status"`
Proposer string `json:"proposer"`
Firmware string `json:"firmware,omitempty"`
URI string `json:"uri,omitempty"`
Version string `json:"version,omitempty"`
ServerMeta string `json:"server_meta,omitempty"`
}{
Status: dev.Status,
Proposer: dev.Proposer,
Firmware: firmware,
URI: uri,
Version: version,
// ServerMeta: meta.String(),
}

// if client != nil {
// slog.Info("encrypt response task query", "response", response)
// cipher, err := s.jwk.EncryptJSON(response, client.KeyAgreementKID())
// if err != nil {
// c.JSON(http.StatusInternalServerError, apitypes.NewErrRsp(errors.Wrap(err, "failed to encrypt response when query task")))
// return
// }
// c.Data(http.StatusOK, "application/octet-stream", cipher)
// return
// }

c.JSON(http.StatusOK, pubData)
}

func (s *httpServer) didDoc(c *gin.Context) {
if s.jwk == nil {
c.JSON(http.StatusNotAcceptable, apitypes.NewErrRsp(errors.New("jwk is not config")))
return
}
c.JSON(http.StatusOK, s.jwk.Doc())
}

func (s *httpServer) issueJWTCredential(c *gin.Context) {
req := new(apitypes.IssueTokenReq)
if err := c.ShouldBindJSON(req); err != nil {
c.JSON(http.StatusBadRequest, apitypes.NewErrRsp(err))
return
}

client := s.clients.ClientByIoID(req.ClientID)
if client == nil {
c.String(http.StatusForbidden, errors.Errorf("client is not register to ioRegistry").Error())
return
}

token, err := s.jwk.SignToken(req.ClientID)
if err != nil {
c.String(http.StatusInternalServerError, errors.Wrap(err, "failed to sign token").Error())
return
}
slog.Info("token signed", "token", token)

cipher, err := s.jwk.Encrypt([]byte(token), client.KeyAgreementKID())
if err != nil {
c.String(http.StatusInternalServerError, errors.Wrap(err, "failed to encrypt").Error())
return
}

c.Data(http.StatusOK, "application/json", cipher)
}
57 changes: 57 additions & 0 deletions cmd/sequencer/apitypes/apitypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package apitypes

import "time"

type ErrRsp struct {
Error string `json:"error,omitempty"`
}

func NewErrRsp(err error) *ErrRsp {
return &ErrRsp{Error: err.Error()}
}

type HandleMessageReq struct {
ProjectID uint64 `json:"projectID" binding:"required"`
ProjectVersion string `json:"projectVersion" binding:"required"`
Data string `json:"data" binding:"required"`
}

type HandleMessageRsp struct {
MessageID string `json:"messageID"`
}

type LivenessRsp struct {
Status string `json:"status"`
}

type StateLog struct {
State string `json:"state"`
Time time.Time `json:"time"`
Comment string `json:"comment"`
Result string `json:"result"`
}

type QueryTaskStateLogRsp struct {
TaskID uint64 `json:"taskID"`
ProjectID uint64 `json:"projectID"`
States []*StateLog `json:"states"`
}

type QueryMessageStateLogRsp struct {
MessageID string `json:"messageID"`
States []*StateLog `json:"states"`
}

type CoordinatorConfigRsp struct {
ProjectContractAddress string `json:"projectContractAddress"`
OperatorETHAddress string `json:"OperatorETHAddress,omitempty"`
OperatorSolanaAddress string `json:"operatorSolanaAddress,omitempty"`
}

type IssueTokenReq struct {
ClientID string `json:"clientID"`
}

type IssueTokenRsp struct {
Token string `json:"token"`
}
14 changes: 14 additions & 0 deletions cmd/sequencer/apitypes/apitypes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package apitypes

import (
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)

func TestNewErrRsp(t *testing.T) {
r := require.New(t)
rsp := NewErrRsp(errors.New(t.Name()))
r.Contains(rsp.Error, t.Name())
}
21 changes: 21 additions & 0 deletions cmd/sequencer/clients/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package clients

import (
"github.com/machinefi/ioconnect-go/pkg/ioconnect"
)

type Client struct {
jwk *ioconnect.JWK
}

func (c *Client) KeyAgreementKID() string {
return c.jwk.KeyAgreementKID()
}

func (c *Client) DID() string {
return c.jwk.DID()
}

func (c *Client) Doc() *ioconnect.Doc {
return c.jwk.Doc()
}
17 changes: 17 additions & 0 deletions cmd/sequencer/clients/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package clients

import "context"

type clientIDCtxKey struct{}

func WithClientID(parent context.Context, client *Client) context.Context {
if parent == nil {
panic("with client id context, nil context")
}
return context.WithValue(parent, clientIDCtxKey{}, client)
}

func ClientIDFrom(ctx context.Context) *Client {
v, _ := ctx.Value(clientIDCtxKey{}).(*Client)
return v
}
37 changes: 37 additions & 0 deletions cmd/sequencer/clients/contracts/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.DEFAULT_GOAL := update

.PHONY: update
update: fetch generate_abi

.PHONY: fetch
fetch:
@echo "#### clone ioID-contracts main branch..."
@if [ -d "ioid" ]; then \
cd ioid && git pull --quiet origin main; \
else \
git clone -b main --quiet git@github.com:machinefi/ioID-contracts.git ioid; \
fi
@echo DONE

.PHONY: generate_abi
generate_abi: generate_abi_ioid generate_abi_project_device

.PHONY: generate_abi_ioid
generate_abi_ioid:
@echo "#### generate abis of ioID from latest contracts..."
@cd ioid && yarn install > /dev/null 2>&1 && yarn hardhat compile > /dev/null 2>&1
@cat ioid/artifacts/contracts/ioIDRegistry.sol/ioIDRegistry.json | jq .abi > ioIDRegistry.json
@echo DONE

.PHONY: generate_abi_project_device
generate_abi_project_device:
@echo "#### generate abis of ProjectDevice from latest contracts..."
@cd ../../smartcontracts && yarn install > /dev/null 2>&1 && yarn hardhat compile > /dev/null 2>&1
@cat ../../smartcontracts/artifacts/contracts/examples/ProjectDevice.sol/ProjectDevice.json | jq .abi > ProjectDevice.json
@cat ../../smartcontracts/artifacts/contracts/W3bstreamProject.sol/W3bstreamProject.json | jq .abi > W3bstreamProject.json
@echo DONE

.PHONY: clean
clean:
@rm -rf ioid

Loading

0 comments on commit 85cc7ec

Please sign in to comment.