From da90d813f9d36a7173e571b398b8f42ff972ad13 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Wed, 24 Jan 2024 15:05:27 -0800 Subject: [PATCH 1/4] guardian prover improvements for getting most recent signed block, as well as saving and getting startups --- .../healthchecker/healthchecker.go | 6 + ...cent_signed_block_by_guardian_prover_id.go | 39 ++++ ...signed_block_by_guardian_prover_id_test.go | 66 +++++++ ...st_recent_startup_by_guardian_prover_id.go | 41 ++++ ...cent_startup_by_guardian_prover_id_test.go | 65 +++++++ .../get_startups_by_guardian_prover_id.go | 40 ++++ ...get_startups_by_guardian_prover_id_test.go | 65 +++++++ .../http/post_startup.go | 76 ++++++++ .../http/routes.go | 6 + .../http/server.go | 3 + .../http/server_test.go | 1 + .../1666651001_create_startups_table.sql | 17 ++ .../mock/signed_block_repo.go | 22 +++ .../mock/startup_repo.go | 64 +++++++ .../repo/signed_block.go | 20 ++ .../repo/startup.go | 74 ++++++++ .../repo/startup_test.go | 175 ++++++++++++++++++ .../signed_block.go | 3 +- .../guardian-prover-health-check/startup.go | 39 ++++ 19 files changed, 821 insertions(+), 1 deletion(-) create mode 100644 packages/guardian-prover-health-check/http/get_most_recent_signed_block_by_guardian_prover_id.go create mode 100644 packages/guardian-prover-health-check/http/get_most_recent_signed_block_by_guardian_prover_id_test.go create mode 100644 packages/guardian-prover-health-check/http/get_most_recent_startup_by_guardian_prover_id.go create mode 100644 packages/guardian-prover-health-check/http/get_most_recent_startup_by_guardian_prover_id_test.go create mode 100644 packages/guardian-prover-health-check/http/get_startups_by_guardian_prover_id.go create mode 100644 packages/guardian-prover-health-check/http/get_startups_by_guardian_prover_id_test.go create mode 100644 packages/guardian-prover-health-check/http/post_startup.go create mode 100644 packages/guardian-prover-health-check/migrations/1666651001_create_startups_table.sql create mode 100644 packages/guardian-prover-health-check/mock/startup_repo.go create mode 100644 packages/guardian-prover-health-check/repo/startup.go create mode 100644 packages/guardian-prover-health-check/repo/startup_test.go create mode 100644 packages/guardian-prover-health-check/startup.go diff --git a/packages/guardian-prover-health-check/healthchecker/healthchecker.go b/packages/guardian-prover-health-check/healthchecker/healthchecker.go index e5279c9ebf2..461ad4a5b7e 100644 --- a/packages/guardian-prover-health-check/healthchecker/healthchecker.go +++ b/packages/guardian-prover-health-check/healthchecker/healthchecker.go @@ -69,6 +69,11 @@ func InitFromConfig(ctx context.Context, h *HealthChecker, cfg *Config) (err err return err } + startupRepo, err := repo.NewStartupRepository(db) + if err != nil { + return err + } + l1EthClient, err := ethclient.Dial(cfg.L1RPCUrl) if err != nil { return err @@ -126,6 +131,7 @@ func InitFromConfig(ctx context.Context, h *HealthChecker, cfg *Config) (err err EthClient: l2EthClient, HealthCheckRepo: healthCheckRepo, SignedBlockRepo: signedBlockRepo, + StartupRepo: startupRepo, GuardianProvers: guardianProvers, }) diff --git a/packages/guardian-prover-health-check/http/get_most_recent_signed_block_by_guardian_prover_id.go b/packages/guardian-prover-health-check/http/get_most_recent_signed_block_by_guardian_prover_id.go new file mode 100644 index 00000000000..98633dc3728 --- /dev/null +++ b/packages/guardian-prover-health-check/http/get_most_recent_signed_block_by_guardian_prover_id.go @@ -0,0 +1,39 @@ +package http + +import ( + "net/http" + "strconv" + + echo "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" +) + +// GetMostRecentSignedBlockByGuardianProverID +// +// returns signed block data by each guardian prover. +// +// @Summary Get most recent signed block by guardian prover ID +// @ID get-most-recent-signed-block-by-guardian-prover-ID +// @Accept json +// @Produce json +// @Success 200 {object} block +// @Router /signedBlocks/:id[get] + +func (srv *Server) GetMostRecentSignedBlockByGuardianProverID(c echo.Context) error { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + log.Error("Failed to convert id to integer", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err) + } + + signedBlock, err := srv.signedBlockRepo.GetMostRecentByGuardianProverID( + id, + ) + + if err != nil { + log.Error("Failed to most recent block by guardian prover ID", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err) + } + + return c.JSON(http.StatusOK, signedBlock) +} diff --git a/packages/guardian-prover-health-check/http/get_most_recent_signed_block_by_guardian_prover_id_test.go b/packages/guardian-prover-health-check/http/get_most_recent_signed_block_by_guardian_prover_id_test.go new file mode 100644 index 00000000000..df9352962f4 --- /dev/null +++ b/packages/guardian-prover-health-check/http/get_most_recent_signed_block_by_guardian_prover_id_test.go @@ -0,0 +1,66 @@ +package http + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cyberhorsey/webutils/testutils" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + guardianproverhealthcheck "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check" +) + +func Test_GetMostRecentSignedBlockByGuardianProverID(t *testing.T) { + srv := newTestServer("") + + for i := 0; i < 10; i++ { + err := srv.signedBlockRepo.Save(guardianproverhealthcheck.SaveSignedBlockOpts{ + GuardianProverID: 1, + RecoveredAddress: "0x123", + BlockID: uint64(i), + BlockHash: "0x123", + Signature: "0x123", + }) + + assert.Nil(t, err) + } + + tests := []struct { + name string + id string + wantStatus int + wantBodyRegexpMatches []string + }{ + { + "success", + "1", + http.StatusOK, + // nolint: lll + []string{`{"guardianProverID":1,"blockID":9,"blockHash":"0x123","signature":"0x123","recoveredAddress":"0x123","createdAt":"0001-01-01T00:00:00Z"}`}, + }, + { + "success", + "9839483294", + http.StatusInternalServerError, + []string{``}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := testutils.NewUnauthenticatedRequest( + echo.GET, + fmt.Sprintf("/signedBlock/%v", tt.id), + nil, + ) + + rec := httptest.NewRecorder() + + srv.ServeHTTP(rec, req) + + testutils.AssertStatusAndBody(t, rec, tt.wantStatus, tt.wantBodyRegexpMatches) + }) + } +} diff --git a/packages/guardian-prover-health-check/http/get_most_recent_startup_by_guardian_prover_id.go b/packages/guardian-prover-health-check/http/get_most_recent_startup_by_guardian_prover_id.go new file mode 100644 index 00000000000..aceb43c99f4 --- /dev/null +++ b/packages/guardian-prover-health-check/http/get_most_recent_startup_by_guardian_prover_id.go @@ -0,0 +1,41 @@ +package http + +import ( + "errors" + "net/http" + "strconv" + + echo "github.com/labstack/echo/v4" +) + +// GetMostRecentStartupByGuardianProverID +// +// returns the startup +// +// @Summary GetMostRecentStartupByGuardianProverID +// @ID get-most-recent-startup-by-guardian-prover-id +// @Accept json +// @Produce json +// @Success 200 {object} guardianproverhealthcheck.Startup +// @Router /mostRecentStartup/:id [get] + +func (srv *Server) GetMostRecentStartupByGuardianProverID( + c echo.Context, +) error { + idParam := c.Param("id") + if idParam == "" { + return c.JSON(http.StatusBadRequest, errors.New("no id provided")) + } + + id, err := strconv.Atoi(idParam) + if err != nil { + return c.JSON(http.StatusBadRequest, err) + } + + startup, err := srv.startupRepo.GetMostRecentByGuardianProverID(c.Request().Context(), id) + if err != nil { + return c.JSON(http.StatusBadRequest, err) + } + + return c.JSON(http.StatusOK, startup) +} diff --git a/packages/guardian-prover-health-check/http/get_most_recent_startup_by_guardian_prover_id_test.go b/packages/guardian-prover-health-check/http/get_most_recent_startup_by_guardian_prover_id_test.go new file mode 100644 index 00000000000..554393198f0 --- /dev/null +++ b/packages/guardian-prover-health-check/http/get_most_recent_startup_by_guardian_prover_id_test.go @@ -0,0 +1,65 @@ +package http + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cyberhorsey/webutils/testutils" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + guardianproverhealthcheck "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check" +) + +func Test_GetMostRecentStartupByGuardianProverID(t *testing.T) { + srv := newTestServer("") + + for i := 0; i < 5; i++ { + err := srv.startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ + GuardianProverID: 1, + GuardianProverAddress: "0x123", + Revision: "asdf", + Version: "v1.0.0", + }) + + assert.Nil(t, err) + } + + tests := []struct { + name string + id string + wantStatus int + wantBodyRegexpMatches []string + }{ + { + "success", + "1", + http.StatusOK, + // nolint: lll + []string{`{"guardianProverID":1,"guardianProverAddress":"0x123","revision":"asdf","version":"v1.0.0","createdAt":"0001-01-01T00:00:00Z"}`}, + }, + { + "doesntExist", + "9839483294", + http.StatusBadRequest, + []string{``}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := testutils.NewUnauthenticatedRequest( + echo.GET, + fmt.Sprintf("/mostRecentStartup/%v", tt.id), + nil, + ) + + rec := httptest.NewRecorder() + + srv.ServeHTTP(rec, req) + + testutils.AssertStatusAndBody(t, rec, tt.wantStatus, tt.wantBodyRegexpMatches) + }) + } +} diff --git a/packages/guardian-prover-health-check/http/get_startups_by_guardian_prover_id.go b/packages/guardian-prover-health-check/http/get_startups_by_guardian_prover_id.go new file mode 100644 index 00000000000..669423be78f --- /dev/null +++ b/packages/guardian-prover-health-check/http/get_startups_by_guardian_prover_id.go @@ -0,0 +1,40 @@ +package http + +import ( + "errors" + "net/http" + "strconv" + + echo "github.com/labstack/echo/v4" +) + +// GetStartupsByGuardianProverID +// +// returns a paginated list of startups by guardian prover iD +// +// @Summary Get startups by guardian prover ID +// @ID get-startups-by-guardian-prover-id +// @Accept json +// @Produce json +// @Success 200 {object} paginate.Page +// @Router /startups [get] +// @Param id string true "guardian prover ID with which to query" + +func (srv *Server) GetStartupsByGuardianProverID(c echo.Context) error { + idParam := c.Param("id") + if idParam == "" { + return c.JSON(http.StatusBadRequest, errors.New("no id provided")) + } + + id, err := strconv.Atoi(idParam) + if err != nil { + return c.JSON(http.StatusBadRequest, err) + } + + page, err := srv.startupRepo.GetByGuardianProverID(c.Request().Context(), c.Request(), id) + if err != nil { + return c.JSON(http.StatusBadRequest, err) + } + + return c.JSON(http.StatusOK, page) +} diff --git a/packages/guardian-prover-health-check/http/get_startups_by_guardian_prover_id_test.go b/packages/guardian-prover-health-check/http/get_startups_by_guardian_prover_id_test.go new file mode 100644 index 00000000000..4ae1b1b5e3b --- /dev/null +++ b/packages/guardian-prover-health-check/http/get_startups_by_guardian_prover_id_test.go @@ -0,0 +1,65 @@ +package http + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cyberhorsey/webutils/testutils" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + guardianproverhealthcheck "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check" +) + +func Test_GetStartupsByGuardianProverID(t *testing.T) { + srv := newTestServer("") + + err := srv.startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ + GuardianProverID: 1, + GuardianProverAddress: "0x123", + Revision: "asdf", + Version: "v1.0.0", + }) + + assert.Nil(t, err) + + assert.Equal(t, nil, err) + + tests := []struct { + name string + id string + wantStatus int + wantBodyRegexpMatches []string + }{ + { + "success", + "1", + http.StatusOK, + // nolint: lll + []string{`{"items":\[{"guardianProverID":1,"guardianProverAddress":"0x123","revision":"asdf","version":"v1.0.0","createdAt":"0001-01-01T00:00:00Z"}\],"page":0,"size":0,"max_page":0,"total_pages":0,"total":0,"last":false,"first":false,"visible":0}`}, + }, + { + "successDoesntExist", + "9839483294", + http.StatusOK, + []string{``}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := testutils.NewUnauthenticatedRequest( + echo.GET, + fmt.Sprintf("/startups/%v", tt.id), + nil, + ) + + rec := httptest.NewRecorder() + + srv.ServeHTTP(rec, req) + + testutils.AssertStatusAndBody(t, rec, tt.wantStatus, tt.wantBodyRegexpMatches) + }) + } +} diff --git a/packages/guardian-prover-health-check/http/post_startup.go b/packages/guardian-prover-health-check/http/post_startup.go new file mode 100644 index 00000000000..a928df0815a --- /dev/null +++ b/packages/guardian-prover-health-check/http/post_startup.go @@ -0,0 +1,76 @@ +package http + +import ( + "log/slog" + "net/http" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + echo "github.com/labstack/echo/v4" + guardianproverhealthcheck "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check" +) + +type startupReq struct { + ProverAddress string `json:"prover"` + Version string `json:"version"` + Revision string `json:"revision"` + Signature string `json:"signature"` +} + +// PostStartup +// +// post a health check from a guardian prover +// +// @Summary Post startup +// @ID post-startup +// @Accept json +// @Produce json +// @Success 200 null +// @Router /startup [post] + +func (srv *Server) PostStartup(c echo.Context) error { + req := &startupReq{} + + // bind incoming request + if err := c.Bind(req); err != nil { + return c.JSON(http.StatusBadRequest, err) + } + + msg := crypto.Keccak256Hash( + common.HexToAddress(req.ProverAddress).Bytes(), + []byte(req.Revision), + []byte(req.Version), + ).Bytes() + + recoveredGuardianProver, err := guardianproverhealthcheck.SignatureToGuardianProver( + msg, + req.Signature, + srv.guardianProvers, + ) + + // if not, we want to return an error + if err != nil { + return c.JSON(http.StatusBadRequest, err) + } + + // otherwise, we can store it in the database. + // expected address and recovered address will be the same until we have an auth + // mechanism which will allow us to store health checks that ecrecover to an unexpected + // address. + if err := srv.startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ + GuardianProverID: recoveredGuardianProver.ID.Uint64(), + Version: req.Version, + Revision: req.Revision, + GuardianProverAddress: req.ProverAddress, + }); err != nil { + return c.JSON(http.StatusBadRequest, err) + } + + slog.Info("successful startup", + "guardianProver", recoveredGuardianProver.Address.Hex(), + "revision", req.Revision, + "version", req.Version, + ) + + return c.JSON(http.StatusOK, nil) +} diff --git a/packages/guardian-prover-health-check/http/routes.go b/packages/guardian-prover-health-check/http/routes.go index 02eed805e95..495351df2dd 100644 --- a/packages/guardian-prover-health-check/http/routes.go +++ b/packages/guardian-prover-health-check/http/routes.go @@ -14,7 +14,13 @@ func (srv *Server) configureRoutes() { srv.echo.GET("/signedBlocks", srv.GetSignedBlocks) + srv.echo.GET("/signedBlock/:id", srv.GetMostRecentSignedBlockByGuardianProverID) + srv.echo.POST("/signedBlock", srv.PostSignedBlock) srv.echo.POST("/healthCheck", srv.PostHealthCheck) + + srv.echo.GET("/startups/:id", srv.GetStartupsByGuardianProverID) + + srv.echo.GET("/mostRecentStartup/:id", srv.GetMostRecentStartupByGuardianProverID) } diff --git a/packages/guardian-prover-health-check/http/server.go b/packages/guardian-prover-health-check/http/server.go index a09f7e06bf3..36c6bbb455b 100644 --- a/packages/guardian-prover-health-check/http/server.go +++ b/packages/guardian-prover-health-check/http/server.go @@ -29,6 +29,7 @@ type Server struct { ethClient *ethclient.Client healthCheckRepo guardianproverhealthcheck.HealthCheckRepository signedBlockRepo guardianproverhealthcheck.SignedBlockRepository + startupRepo guardianproverhealthcheck.StartupRepository guardianProvers []guardianproverhealthcheck.GuardianProver } @@ -37,6 +38,7 @@ type NewServerOpts struct { EthClient *ethclient.Client HealthCheckRepo guardianproverhealthcheck.HealthCheckRepository SignedBlockRepo guardianproverhealthcheck.SignedBlockRepository + StartupRepo guardianproverhealthcheck.StartupRepository CorsOrigins []string GuardianProvers []guardianproverhealthcheck.GuardianProver } @@ -48,6 +50,7 @@ func NewServer(opts NewServerOpts) (*Server, error) { healthCheckRepo: opts.HealthCheckRepo, guardianProvers: opts.GuardianProvers, signedBlockRepo: opts.SignedBlockRepo, + startupRepo: opts.StartupRepo, } corsOrigins := opts.CorsOrigins diff --git a/packages/guardian-prover-health-check/http/server_test.go b/packages/guardian-prover-health-check/http/server_test.go index e6ad7585344..c9e12cba2e5 100644 --- a/packages/guardian-prover-health-check/http/server_test.go +++ b/packages/guardian-prover-health-check/http/server_test.go @@ -20,6 +20,7 @@ func newTestServer(url string) *Server { echo: echo.New(), healthCheckRepo: mock.NewHealthCheckRepository(), signedBlockRepo: mock.NewSignedBlockRepository(), + startupRepo: mock.NewStartupRepository(), guardianProvers: make([]guardianproverhealthcheck.GuardianProver, 0), } diff --git a/packages/guardian-prover-health-check/migrations/1666651001_create_startups_table.sql b/packages/guardian-prover-health-check/migrations/1666651001_create_startups_table.sql new file mode 100644 index 00000000000..c4327b5b2cc --- /dev/null +++ b/packages/guardian-prover-health-check/migrations/1666651001_create_startups_table.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS startup ( + id int NOT NULL PRIMARY KEY AUTO_INCREMENT, + guardian_prover_id int NOT NULL, + guardian_prover_address VARCHAR(42) NOT NULL, + revision VARCHAR(255) NOT NULL, + version VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- +goose StatementEnd +-- +goose Down +-- +goose StatementBegin +DROP TABLE startup; +-- +goose StatementEnd \ No newline at end of file diff --git a/packages/guardian-prover-health-check/mock/signed_block_repo.go b/packages/guardian-prover-health-check/mock/signed_block_repo.go index 1d942f58a0e..a19cdd292d7 100644 --- a/packages/guardian-prover-health-check/mock/signed_block_repo.go +++ b/packages/guardian-prover-health-check/mock/signed_block_repo.go @@ -1,6 +1,8 @@ package mock import ( + "errors" + guardianproverhealthcheck "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check" ) @@ -40,3 +42,23 @@ func (r *SignedBlockRepo) GetByStartingBlockID( return sb, nil } + +func (r *SignedBlockRepo) GetMostRecentByGuardianProverID(id int) (*guardianproverhealthcheck.SignedBlock, error) { + var b *guardianproverhealthcheck.SignedBlock + + for k, v := range r.signedBlocks { + if v.GuardianProverID == uint64(id) { + if k == 0 { + b = v + } else if v.BlockID > b.BlockID { + b = v + } + } + } + + if b == nil { + return nil, errors.New("no signed blocks by this guardian prover") + } + + return b, nil +} diff --git a/packages/guardian-prover-health-check/mock/startup_repo.go b/packages/guardian-prover-health-check/mock/startup_repo.go new file mode 100644 index 00000000000..12c1eb9df70 --- /dev/null +++ b/packages/guardian-prover-health-check/mock/startup_repo.go @@ -0,0 +1,64 @@ +package mock + +import ( + "context" + "errors" + "net/http" + + "github.com/morkid/paginate" + guardianproverhealthcheck "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check" +) + +type StartupRepo struct { + startups []*guardianproverhealthcheck.Startup +} + +func NewStartupRepository() *StartupRepo { + return &StartupRepo{ + startups: make([]*guardianproverhealthcheck.Startup, 0), + } +} +func (h *StartupRepo) GetByGuardianProverID( + ctx context.Context, + req *http.Request, + id int, +) (paginate.Page, error) { + return paginate.Page{ + Items: h.startups, + }, nil +} + +func (r *StartupRepo) GetMostRecentByGuardianProverID( + ctx context.Context, + id int, +) (*guardianproverhealthcheck.Startup, error) { + var s *guardianproverhealthcheck.Startup + + for k, v := range r.startups { + if v.GuardianProverID == uint64(id) { + if k == 0 { + s = v + } else if v.CreatedAt.Compare(s.CreatedAt) == 1 { + s = v + } + } + } + + if s == nil { + return nil, errors.New("no signed blocks by this guardian prover") + } + + return s, nil +} + +func (h *StartupRepo) Save(opts guardianproverhealthcheck.SaveStartupOpts) error { + h.startups = append(h.startups, &guardianproverhealthcheck.Startup{ + GuardianProverID: opts.GuardianProverID, + GuardianProverAddress: opts.GuardianProverAddress, + Revision: opts.Revision, + Version: opts.Version, + }, + ) + + return nil +} diff --git a/packages/guardian-prover-health-check/repo/signed_block.go b/packages/guardian-prover-health-check/repo/signed_block.go index acab3ca4252..8cd0a25c66f 100644 --- a/packages/guardian-prover-health-check/repo/signed_block.go +++ b/packages/guardian-prover-health-check/repo/signed_block.go @@ -49,3 +49,23 @@ func (r *SignedBlockRepository) GetByStartingBlockID( return sb, nil } + +func (r *SignedBlockRepository) GetMostRecentByGuardianProverID(id int) ( + *guardianproverhealthcheck.SignedBlock, + error) { + q := `SELECT * + FROM signed_blocks + WHERE block_id = ( + SELECT MAX(block_id) + FROM signed_blocks + WHERE guardian_prover_id = ? + ) AND guardian_prover_id = ?;` + + var b *guardianproverhealthcheck.SignedBlock + + if err := r.startQuery().Raw(q, id, id).Scan(&b).Error; err != nil { + return nil, err + } + + return b, nil +} diff --git a/packages/guardian-prover-health-check/repo/startup.go b/packages/guardian-prover-health-check/repo/startup.go new file mode 100644 index 00000000000..9a1d141ed7e --- /dev/null +++ b/packages/guardian-prover-health-check/repo/startup.go @@ -0,0 +1,74 @@ +package repo + +import ( + "context" + "net/http" + + "github.com/morkid/paginate" + guardianproverhealthcheck "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check" + "gorm.io/gorm" +) + +type StartupRepository struct { + db DB +} + +func NewStartupRepository(db DB) (*StartupRepository, error) { + if db == nil { + return nil, ErrNoDB + } + + return &StartupRepository{ + db: db, + }, nil +} + +func (r *StartupRepository) startQuery() *gorm.DB { + return r.db.GormDB().Table("startups") +} + +func (r *StartupRepository) GetByGuardianProverID( + ctx context.Context, + req *http.Request, + id int, +) (paginate.Page, error) { + pg := paginate.New(&paginate.Config{ + DefaultSize: 100, + }) + + reqCtx := pg.With(r.startQuery().Order("created_at desc"). + Where("guardian_prover_id = ?", id)) + + page := reqCtx.Request(req).Response(&[]guardianproverhealthcheck.Startup{}) + + return page, nil +} + +func (r *StartupRepository) GetMostRecentByGuardianProverID( + ctx context.Context, + id int, +) (*guardianproverhealthcheck.Startup, error) { + s := &guardianproverhealthcheck.Startup{} + + if err := r.startQuery().Order("created_at desc"). + Where("guardian_prover_id = ?", id).Limit(1). + Scan(s).Error; err != nil { + return nil, err + } + + return s, nil +} + +func (r *StartupRepository) Save(opts guardianproverhealthcheck.SaveStartupOpts) error { + b := &guardianproverhealthcheck.Startup{ + GuardianProverAddress: opts.GuardianProverAddress, + GuardianProverID: opts.GuardianProverID, + Revision: opts.Revision, + Version: opts.Version, + } + if err := r.startQuery().Create(b).Error; err != nil { + return err + } + + return nil +} diff --git a/packages/guardian-prover-health-check/repo/startup_test.go b/packages/guardian-prover-health-check/repo/startup_test.go new file mode 100644 index 00000000000..ac8d32b7eab --- /dev/null +++ b/packages/guardian-prover-health-check/repo/startup_test.go @@ -0,0 +1,175 @@ +package repo + +import ( + "context" + "net/http" + "testing" + + guardianproverhealthcheck "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check" + "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check/db" + "gopkg.in/go-playground/assert.v1" +) + +func Test_NewStartupRepo(t *testing.T) { + tests := []struct { + name string + db DB + wantErr error + }{ + { + "success", + &db.DB{}, + nil, + }, + { + "noDb", + nil, + ErrNoDB, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := NewStartupRepository(tt.db) + assert.Equal(t, tt.wantErr, err) + }) + } +} + +func TestIntegration_Startup_Save(t *testing.T) { + db, close, err := testMysql(t) + assert.Equal(t, nil, err) + + defer close() + + startupRepo, err := NewStartupRepository(db) + assert.Equal(t, nil, err) + tests := []struct { + name string + opts guardianproverhealthcheck.SaveStartupOpts + wantErr error + }{ + { + "success", + guardianproverhealthcheck.SaveStartupOpts{ + GuardianProverID: 1, + GuardianProverAddress: "0x123", + Revision: "asdf", + Version: "v1.0.0", + }, + nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err = startupRepo.Save(tt.opts) + assert.Equal(t, tt.wantErr, err) + }) + } +} + +func TestIntegration_Startup_GetByGuardianProverID(t *testing.T) { + db, close, err := testMysql(t) + assert.Equal(t, nil, err) + + defer close() + + startupRepo, err := NewStartupRepository(db) + assert.Equal(t, nil, err) + + err = startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ + GuardianProverID: 1, + GuardianProverAddress: "0x123", + Revision: "asdf", + Version: "v1.0.0", + }) + + assert.Equal(t, nil, err) + + err = startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ + GuardianProverID: 1, + GuardianProverAddress: "0x123", + Revision: "zxxc", + Version: "v1.0.1", + }) + + assert.Equal(t, nil, err) + + tests := []struct { + name string + id int + wantErr error + }{ + { + "success", + 1, + nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, "/signedBlock", nil) + assert.Equal(t, nil, err) + + page, err := startupRepo.GetByGuardianProverID(context.Background(), req, tt.id) + assert.Equal(t, nil, err) + + assert.Equal(t, page.Total, int64(2)) + }) + } +} + +func TestIntegration_Startup_GetMostRecentByGuardianProverID(t *testing.T) { + db, close, err := testMysql(t) + assert.Equal(t, nil, err) + + defer close() + + startupRepo, err := NewStartupRepository(db) + assert.Equal(t, nil, err) + + latestRevision := "kskdjak" + latestVersion := "v9.0.0" + + err = startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ + GuardianProverID: 1, + GuardianProverAddress: "0x123", + Revision: "asdf", + Version: "v1.0.0", + }) + + assert.Equal(t, nil, err) + + err = startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ + GuardianProverID: 1, + GuardianProverAddress: "0x123", + Revision: latestRevision, + Version: latestVersion, + }) + + assert.Equal(t, nil, err) + + tests := []struct { + name string + id int + wantErr error + }{ + { + "success", + 1, + nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + startup, err := startupRepo.GetMostRecentByGuardianProverID(context.Background(), tt.id) + assert.Equal(t, tt.wantErr, err) + + assert.Equal(t, latestRevision, startup.Revision) + assert.Equal(t, latestVersion, startup.Version) + }) + } +} diff --git a/packages/guardian-prover-health-check/signed_block.go b/packages/guardian-prover-health-check/signed_block.go index 537ba61457e..d8138506ed5 100644 --- a/packages/guardian-prover-health-check/signed_block.go +++ b/packages/guardian-prover-health-check/signed_block.go @@ -10,7 +10,7 @@ type SignedBlock struct { BlockHash string `json:"blockHash"` Signature string `json:"signature"` RecoveredAddress string `json:"recoveredAddress"` - CreatedAt time.Time `jsom:"createdAt"` + CreatedAt time.Time `json:"createdAt"` } type SaveSignedBlockOpts struct { @@ -30,4 +30,5 @@ type GetSignedBlocksByStartingBlockIDOpts struct { type SignedBlockRepository interface { Save(opts SaveSignedBlockOpts) error GetByStartingBlockID(opts GetSignedBlocksByStartingBlockIDOpts) ([]*SignedBlock, error) + GetMostRecentByGuardianProverID(id int) (*SignedBlock, error) } diff --git a/packages/guardian-prover-health-check/startup.go b/packages/guardian-prover-health-check/startup.go new file mode 100644 index 00000000000..33ea79dd01b --- /dev/null +++ b/packages/guardian-prover-health-check/startup.go @@ -0,0 +1,39 @@ +package guardianproverhealthcheck + +import ( + "context" + "net/http" + "time" + + "github.com/morkid/paginate" +) + +// Startup represents an individual startup from a guardian prover, every time +// one boots up, we should receive a startup and save it in the database. +type Startup struct { + GuardianProverID uint64 `json:"guardianProverID"` + GuardianProverAddress string `json:"guardianProverAddress"` + Revision string `json:"revision"` + Version string `json:"version"` + CreatedAt time.Time `json:"createdAt"` +} + +type SaveStartupOpts struct { + GuardianProverID uint64 + GuardianProverAddress string + Revision string + Version string +} + +type StartupRepository interface { + GetByGuardianProverID( + ctx context.Context, + req *http.Request, + id int, + ) (paginate.Page, error) + GetMostRecentByGuardianProverID( + ctx context.Context, + id int, + ) (*Startup, error) + Save(opts SaveStartupOpts) error +} From d23a21437374cd201432e7cd8e7a1a7e5299967b Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Wed, 24 Jan 2024 15:20:40 -0800 Subject: [PATCH 2/4] rename mig --- ...te_startups_table.sql => 1666651002_create_startups_table.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/guardian-prover-health-check/migrations/{1666651001_create_startups_table.sql => 1666651002_create_startups_table.sql} (100%) diff --git a/packages/guardian-prover-health-check/migrations/1666651001_create_startups_table.sql b/packages/guardian-prover-health-check/migrations/1666651002_create_startups_table.sql similarity index 100% rename from packages/guardian-prover-health-check/migrations/1666651001_create_startups_table.sql rename to packages/guardian-prover-health-check/migrations/1666651002_create_startups_table.sql From 111e27952010d3158ee0ba8315d0361b0aa99071 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Wed, 24 Jan 2024 15:28:21 -0800 Subject: [PATCH 3/4] rename table --- packages/guardian-prover-health-check/cmd/utils/subcommand.go | 2 +- packages/guardian-prover-health-check/metrics/metrics_test.go | 2 +- .../migrations/1666651002_create_startups_table.sql | 4 ++-- packages/guardian-prover-health-check/repo/containers_test.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/guardian-prover-health-check/cmd/utils/subcommand.go b/packages/guardian-prover-health-check/cmd/utils/subcommand.go index 7ceb08e2b20..490020ad42f 100644 --- a/packages/guardian-prover-health-check/cmd/utils/subcommand.go +++ b/packages/guardian-prover-health-check/cmd/utils/subcommand.go @@ -28,7 +28,7 @@ func SubcommandAction(app SubcommandApplication) cli.ActionFunc { return err } - slog.Info("Starting Taiko relayer application", "name", app.Name()) + slog.Info("Starting Taiko guardian prover health check application", "name", app.Name()) if err := app.Start(); err != nil { slog.Error("Starting application error", "name", app.Name(), "error", err) diff --git a/packages/guardian-prover-health-check/metrics/metrics_test.go b/packages/guardian-prover-health-check/metrics/metrics_test.go index 5c4775e2e99..a0bf95d3a96 100644 --- a/packages/guardian-prover-health-check/metrics/metrics_test.go +++ b/packages/guardian-prover-health-check/metrics/metrics_test.go @@ -9,7 +9,7 @@ import ( "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" - "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" + "github.com/taikoxyz/taiko-mono/packages/guardian-prover-health-check/cmd/flags" "github.com/urfave/cli/v2" ) diff --git a/packages/guardian-prover-health-check/migrations/1666651002_create_startups_table.sql b/packages/guardian-prover-health-check/migrations/1666651002_create_startups_table.sql index c4327b5b2cc..22f3ebdf9b9 100644 --- a/packages/guardian-prover-health-check/migrations/1666651002_create_startups_table.sql +++ b/packages/guardian-prover-health-check/migrations/1666651002_create_startups_table.sql @@ -1,6 +1,6 @@ -- +goose Up -- +goose StatementBegin -CREATE TABLE IF NOT EXISTS startup ( +CREATE TABLE IF NOT EXISTS startups ( id int NOT NULL PRIMARY KEY AUTO_INCREMENT, guardian_prover_id int NOT NULL, guardian_prover_address VARCHAR(42) NOT NULL, @@ -13,5 +13,5 @@ CREATE TABLE IF NOT EXISTS startup ( -- +goose StatementEnd -- +goose Down -- +goose StatementBegin -DROP TABLE startup; +DROP TABLE startups; -- +goose StatementEnd \ No newline at end of file diff --git a/packages/guardian-prover-health-check/repo/containers_test.go b/packages/guardian-prover-health-check/repo/containers_test.go index ead1b0b685b..935d18fa92c 100644 --- a/packages/guardian-prover-health-check/repo/containers_test.go +++ b/packages/guardian-prover-health-check/repo/containers_test.go @@ -15,7 +15,7 @@ import ( ) var ( - dbName = "relayer" + dbName = "guardianproverhealthcheck" dbUsername = "root" dbPassword = "password" ) From 0cc5f3f2c4ec9a47d1f3c3d7230645a101b93d19 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Wed, 24 Jan 2024 15:36:53 -0800 Subject: [PATCH 4/4] rm --- .../repo/startup_test.go | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/packages/guardian-prover-health-check/repo/startup_test.go b/packages/guardian-prover-health-check/repo/startup_test.go index ac8d32b7eab..fefe4213710 100644 --- a/packages/guardian-prover-health-check/repo/startup_test.go +++ b/packages/guardian-prover-health-check/repo/startup_test.go @@ -120,56 +120,3 @@ func TestIntegration_Startup_GetByGuardianProverID(t *testing.T) { }) } } - -func TestIntegration_Startup_GetMostRecentByGuardianProverID(t *testing.T) { - db, close, err := testMysql(t) - assert.Equal(t, nil, err) - - defer close() - - startupRepo, err := NewStartupRepository(db) - assert.Equal(t, nil, err) - - latestRevision := "kskdjak" - latestVersion := "v9.0.0" - - err = startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ - GuardianProverID: 1, - GuardianProverAddress: "0x123", - Revision: "asdf", - Version: "v1.0.0", - }) - - assert.Equal(t, nil, err) - - err = startupRepo.Save(guardianproverhealthcheck.SaveStartupOpts{ - GuardianProverID: 1, - GuardianProverAddress: "0x123", - Revision: latestRevision, - Version: latestVersion, - }) - - assert.Equal(t, nil, err) - - tests := []struct { - name string - id int - wantErr error - }{ - { - "success", - 1, - nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - startup, err := startupRepo.GetMostRecentByGuardianProverID(context.Background(), tt.id) - assert.Equal(t, tt.wantErr, err) - - assert.Equal(t, latestRevision, startup.Revision) - assert.Equal(t, latestVersion, startup.Version) - }) - } -}