Skip to content

Commit

Permalink
Merge branch 'master' into pedro/transactions_call_conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
MakisChristou authored Dec 17, 2024
2 parents eb47f49 + 1a8ab07 commit be4777c
Show file tree
Hide file tree
Showing 35 changed files with 1,061 additions and 320 deletions.
12 changes: 11 additions & 1 deletion .github/workflows/publish-docker-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,20 @@ jobs:
platforms: ${{ github.event_name != 'pull_request' && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
push: ${{ github.event_name != 'pull_request' }}
load: ${{ github.event_name == 'pull_request' }}
provenance: false
provenance: ${{ github.event_name != 'pull_request' }}
sbom: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

- name: Update Docker Hub
# official documentation docker: https://docs.docker.com/build/ci/github-actions/update-dockerhub-desc/
if: ${{ inputs.environment == 'docker-publish' && github.event_name != 'pull_request' }}
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ github.repository }}

- name: Scan for vulnerabilities
uses: crazy-max/ghaction-container-scan@v3
if: ${{ github.event_name == 'pull_request' || github.ref_name == 'master' }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ jobs:
uses: actions/checkout@v4
with:
repository: vechain/thor-e2e-tests
# https://github.com/vechain/thor-e2e-tests/tree/8b72bedff11c9e8873d88b6e2dba356d43b56779
ref: 8b72bedff11c9e8873d88b6e2dba356d43b56779
# https://github.com/vechain/thor-e2e-tests/tree/956b34bcf5b0b072cd7c8fd2e546a9beb66a866a
ref: 956b34bcf5b0b072cd7c8fd2e546a9beb66a866a

- name: Download artifact
uses: actions/download-artifact@v4
Expand Down
62 changes: 0 additions & 62 deletions api/admin.go

This file was deleted.

32 changes: 32 additions & 0 deletions api/admin/admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2024 The VeChainThor developers

// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>

package admin

import (
"log/slog"
"net/http"
"sync/atomic"

"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/vechain/thor/v2/api/admin/apilogs"
"github.com/vechain/thor/v2/api/admin/loglevel"

healthAPI "github.com/vechain/thor/v2/api/admin/health"
)

func New(logLevel *slog.LevelVar, health *healthAPI.Health, apiLogsToggle *atomic.Bool) http.HandlerFunc {
router := mux.NewRouter()
subRouter := router.PathPrefix("/admin").Subrouter()

loglevel.New(logLevel).Mount(subRouter, "/loglevel")
healthAPI.NewAPI(health).Mount(subRouter, "/health")
apilogs.New(apiLogsToggle).Mount(subRouter, "/apilogs")

handler := handlers.CompressHandler(router)

return handler.ServeHTTP
}
70 changes: 70 additions & 0 deletions api/admin/apilogs/api_logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) 2024 The VeChainThor developers
//
// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>

package apilogs

import (
"net/http"
"sync"
"sync/atomic"

"github.com/gorilla/mux"
"github.com/vechain/thor/v2/api/utils"
"github.com/vechain/thor/v2/log"
)

type APILogs struct {
enabled *atomic.Bool
mu sync.Mutex
}

type Status struct {
Enabled bool `json:"enabled"`
}

func New(enabled *atomic.Bool) *APILogs {
return &APILogs{
enabled: enabled,
}
}

func (a *APILogs) Mount(root *mux.Router, pathPrefix string) {
sub := root.PathPrefix(pathPrefix).Subrouter()
sub.Path("").
Methods(http.MethodGet).
Name("get-api-logs-enabled").
HandlerFunc(utils.WrapHandlerFunc(a.areAPILogsEnabled))

sub.Path("").
Methods(http.MethodPost).
Name("post-api-logs-enabled").
HandlerFunc(utils.WrapHandlerFunc(a.setAPILogsEnabled))
}

func (a *APILogs) areAPILogsEnabled(w http.ResponseWriter, _ *http.Request) error {
a.mu.Lock()
defer a.mu.Unlock()

return utils.WriteJSON(w, Status{
Enabled: a.enabled.Load(),
})
}

func (a *APILogs) setAPILogsEnabled(w http.ResponseWriter, r *http.Request) error {
a.mu.Lock()
defer a.mu.Unlock()

var req Status
if err := utils.ParseJSON(r.Body, &req); err != nil {
return utils.BadRequest(err)
}
a.enabled.Store(req.Enabled)

log.Info("api logs updated", "pkg", "apilogs", "enabled", req.Enabled)

return utils.WriteJSON(w, Status{
Enabled: a.enabled.Load(),
})
}
91 changes: 91 additions & 0 deletions api/admin/apilogs/api_logs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) 2024 The VeChainThor developers
//
// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>

package apilogs

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"

"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
)

type TestCase struct {
name string
method string
expectedHTTP int
startValue bool
expectedEndValue bool
requestBody bool
}

func marshalBody(tt TestCase, t *testing.T) []byte {
var reqBody []byte
var err error
if tt.method == "POST" {
reqBody, err = json.Marshal(Status{Enabled: tt.requestBody})
if err != nil {
t.Fatalf("could not marshal request body: %v", err)
}
}
return reqBody
}

func TestLogLevelHandler(t *testing.T) {
tests := []TestCase{
{
name: "Valid POST input - set logs to enabled",
method: "POST",
expectedHTTP: http.StatusOK,
startValue: false,
requestBody: true,
expectedEndValue: true,
},
{
name: "Valid POST input - set logs to disabled",
method: "POST",
expectedHTTP: http.StatusOK,
startValue: true,
requestBody: false,
expectedEndValue: false,
},
{
name: "GET request - get current level INFO",
method: "GET",
expectedHTTP: http.StatusOK,
startValue: true,
expectedEndValue: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logLevel := atomic.Bool{}
logLevel.Store(tt.startValue)

reqBodyBytes := marshalBody(tt, t)

req, err := http.NewRequest(tt.method, "/admin/apilogs", bytes.NewBuffer(reqBodyBytes))
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
router := mux.NewRouter()
New(&logLevel).Mount(router, "/admin/apilogs")
router.ServeHTTP(rr, req)

assert.Equal(t, tt.expectedHTTP, rr.Code)
responseBody := Status{}
assert.NoError(t, json.Unmarshal(rr.Body.Bytes(), &responseBody))
assert.Equal(t, tt.expectedEndValue, responseBody.Enabled)
})
}
}
84 changes: 84 additions & 0 deletions api/admin/health/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) 2024 The VeChainThor developers

// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>

package health

import (
"time"

"github.com/vechain/thor/v2/chain"
"github.com/vechain/thor/v2/comm"
"github.com/vechain/thor/v2/thor"
)

type BlockIngestion struct {
ID *thor.Bytes32 `json:"id"`
Timestamp *time.Time `json:"timestamp"`
}

type Status struct {
Healthy bool `json:"healthy"`
BestBlockTime *time.Time `json:"bestBlockTime"`
PeerCount int `json:"peerCount"`
IsNetworkProgressing bool `json:"isNetworkProgressing"`
}

type Health struct {
repo *chain.Repository
p2p *comm.Communicator
}

const (
defaultBlockTolerance = time.Duration(2*thor.BlockInterval) * time.Second // 2 blocks tolerance
defaultMinPeerCount = 2
)

func New(repo *chain.Repository, p2p *comm.Communicator) *Health {
return &Health{
repo: repo,
p2p: p2p,
}
}

// isNetworkProgressing checks if the network is producing new blocks within the allowed interval.
func (h *Health) isNetworkProgressing(now time.Time, bestBlockTimestamp time.Time, blockTolerance time.Duration) bool {
return now.Sub(bestBlockTimestamp) <= blockTolerance
}

// isNodeConnectedP2P checks if the node is connected to peers
func (h *Health) isNodeConnectedP2P(peerCount int, minPeerCount int) bool {
return peerCount >= minPeerCount
}

func (h *Health) Status(blockTolerance time.Duration, minPeerCount int) (*Status, error) {
// Fetch the best block details
bestBlock := h.repo.BestBlockSummary()
bestBlockTimestamp := time.Unix(int64(bestBlock.Header.Timestamp()), 0)

// Fetch the current connected peers
var connectedPeerCount int
if h.p2p == nil {
connectedPeerCount = minPeerCount // ignore peers in solo mode
} else {
connectedPeerCount = h.p2p.PeerCount()
}

now := time.Now()

// Perform the checks
networkProgressing := h.isNetworkProgressing(now, bestBlockTimestamp, blockTolerance)
nodeConnected := h.isNodeConnectedP2P(connectedPeerCount, minPeerCount)

// Calculate overall health status
healthy := networkProgressing && nodeConnected

// Return the current status
return &Status{
Healthy: healthy,
BestBlockTime: &bestBlockTimestamp,
IsNetworkProgressing: networkProgressing,
PeerCount: connectedPeerCount,
}, nil
}
Loading

0 comments on commit be4777c

Please sign in to comment.