Skip to content

Commit 9529eea

Browse files
Code reorganization (#10)
* Code reorganization * renami dir * update dockerfiles * add custom air.toml config * fix typo * fix typo
1 parent 07508b5 commit 9529eea

26 files changed

+424
-363
lines changed

.github/workflows/go_test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ jobs:
2323
- name: Build
2424
run: |
2525
cd ./listener
26-
go build -a -installsuffix cgo -o bin/listener ./src
26+
go build -a -installsuffix cgo -o bin/listener ./cmd/listener

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.env
2-
./listener/src/tmp/*
2+
./listener/tmp/*
3+
./listener/bin/*

docker-compose.dev.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ services:
99
context: listener
1010
dockerfile: Dockerfile.dev
1111
volumes:
12-
- ./listener/src:/app/src
12+
- ./listener/cmd:/app/cmd
13+
- ./listener/internal:/app/internal
1314

1415
mongo:
1516
extends:

listener/.air.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Sample .air.toml configuration
2+
root = "."
3+
tmp_dir = "tmp"
4+
5+
[build]
6+
cmd = "go build -o ./tmp/listener ./cmd/listener/main.go"
7+
bin = "tmp/listener"
8+
full_bin = "tmp/listener"
9+
# Include other configurations as necessary
10+
11+
[log]
12+
time_format = "15:04:05"
13+
14+
[restart]
15+
delay = 1000 # Milliseconds delay before restarting on file changes

listener/Dockerfile

+4-6
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@ COPY go.mod .
99
COPY go.sum .
1010
RUN go mod download
1111

12-
# Copy the source code into the container.
13-
COPY src/ ./src/
14-
15-
# Set the Current Working Directory inside the container to the src directory.
16-
WORKDIR /app/src
12+
## The code is in the current dir under internal and cmd Copy them and set the required workdir
13+
COPY internal/ ./internal/
14+
COPY cmd/ ./cmd/
1715

1816
# Build the application, outputting the executable to /bin directory.
19-
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /bin/listener main.go
17+
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /bin/listener ./cmd/listener/main.go
2018

2119
# Use a Docker multi-stage build to create a lean production image.
2220
FROM debian:bookworm-slim

listener/Dockerfile.dev

+4-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ RUN go install github.com/cosmtrek/air@latest
1313
# Copy the Air configuration file (if you have one) into the container.
1414
# If you don't have a custom .air.toml, you can skip this step,
1515
# and Air will use its default configuration.
16-
# COPY .air.toml . (Uncomment if you have a custom Air config)
16+
COPY .air.toml .
1717

1818
# Copy go module files.
1919
COPY go.mod .
@@ -26,10 +26,8 @@ RUN go mod download
2626

2727
# Expect source code to be mounted at this directory rather than copied
2828
# This is the change for development mode.
29-
VOLUME ["/app/src"]
30-
31-
# Set the Current Working Directory inside the container to the src directory.
32-
WORKDIR /app/src
29+
VOLUME ["/app/cmd"]
30+
VOLUME ["/app/internal"]
3331

3432
# Command to run the application using Air for live reloading.
35-
CMD ["air"]
33+
CMD ["air", "-c", ".air.toml"]

listener/src/main.go listener/cmd/listener/main.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package main
22

33
import (
4-
"github.com/dappnode/validator-monitoring/listener/src/api"
5-
"github.com/dappnode/validator-monitoring/listener/src/config"
6-
"github.com/dappnode/validator-monitoring/listener/src/logger"
4+
"github.com/dappnode/validator-monitoring/listener/internal/api"
5+
"github.com/dappnode/validator-monitoring/listener/internal/config"
6+
"github.com/dappnode/validator-monitoring/listener/internal/logger"
77
)
88

99
func main() {

listener/internal/api/api.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package api
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/dappnode/validator-monitoring/listener/internal/api/routes"
7+
"github.com/dappnode/validator-monitoring/listener/internal/logger"
8+
"github.com/dappnode/validator-monitoring/listener/internal/mongodb"
9+
)
10+
11+
type httpApi struct {
12+
server *http.Server
13+
port string
14+
dbUri string
15+
}
16+
17+
// create a new api instance
18+
func NewApi(port string, mongoDbUri string) *httpApi {
19+
return &httpApi{
20+
port: port,
21+
dbUri: mongoDbUri,
22+
}
23+
}
24+
25+
// start the api
26+
func (s *httpApi) Start() {
27+
// if somehow s.server is not nil, it means the server is already running, this should never happen
28+
if s.server != nil {
29+
logger.Fatal("HTTP server already started")
30+
}
31+
32+
logger.Info("Server is running on port " + s.port)
33+
var err error
34+
35+
// connect to the MongoDB server
36+
dbClient, err := mongodb.ConnectMongoDB(s.dbUri)
37+
if err != nil {
38+
logger.Fatal("Failed to connect to MongoDB: " + err.Error())
39+
}
40+
41+
// get the collection
42+
dbCollection := dbClient.Database("validatorMonitoring").Collection("signatures")
43+
if dbCollection == nil {
44+
logger.Fatal("Failed to connect to MongoDB collection")
45+
}
46+
47+
// setup the http api
48+
s.server = &http.Server{
49+
Addr: ":" + s.port,
50+
Handler: routes.SetupRouter(dbCollection),
51+
}
52+
53+
// start the api
54+
if err := s.server.ListenAndServe(); err != nil {
55+
logger.Fatal("Failed to start server: " + err.Error())
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package handlers
2+
3+
import "net/http"
4+
5+
func GetHealthCheck(w http.ResponseWriter, r *http.Request) {
6+
respondOK(w, "Server is running")
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package handlers
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"net/http"
7+
8+
"github.com/dappnode/validator-monitoring/listener/internal/api/types"
9+
"github.com/dappnode/validator-monitoring/listener/internal/api/validation"
10+
"github.com/dappnode/validator-monitoring/listener/internal/logger"
11+
"go.mongodb.org/mongo-driver/bson"
12+
"go.mongodb.org/mongo-driver/mongo"
13+
)
14+
15+
type signatureRequest struct {
16+
Payload string `json:"payload"`
17+
Signature string `json:"signature"`
18+
Network string `json:"network"`
19+
Label string `json:"label"`
20+
}
21+
22+
func PostNewSignature(w http.ResponseWriter, r *http.Request, dbCollection *mongo.Collection) {
23+
logger.Debug("Received new POST '/newSignature' request")
24+
25+
var sigs []signatureRequest
26+
err := json.NewDecoder(r.Body).Decode(&sigs)
27+
if err != nil {
28+
logger.Error("Failed to decode request body: " + err.Error())
29+
respondError(w, http.StatusBadRequest, "Invalid request format")
30+
return
31+
}
32+
33+
var validPubkeys []string // Needed to store valid pubkeys for bulk validation later
34+
35+
// For each element of the request slice, we validate the element format and decode its payload
36+
for _, req := range sigs {
37+
if req.Network == "" || req.Label == "" || req.Signature == "" || req.Payload == "" {
38+
logger.Debug("Skipping invalid signature from request, missing fields")
39+
continue // Skipping invalid elements
40+
}
41+
if !validation.ValidateSignature(req.Signature) {
42+
logger.Debug("Skipping invalid signature from request, invalid signature format: " + req.Signature)
43+
continue // Skipping invalid signatures
44+
}
45+
decodedBytes, err := base64.StdEncoding.DecodeString(req.Payload)
46+
if err != nil {
47+
logger.Error("Failed to decode BASE64 payload from request: " + err.Error())
48+
continue // Skipping payloads that can't be decoded from BASE64
49+
}
50+
var decodedPayload types.DecodedPayload
51+
if err := json.Unmarshal(decodedBytes, &decodedPayload); err != nil {
52+
logger.Error("Failed to decode JSON payload from request: " + err.Error())
53+
continue // Skipping payloads that can't be decoded from JSON
54+
}
55+
// If the payload is valid, we append the pubkey to the validPubkeys slice. Else, we skip it
56+
if decodedPayload.Platform == "dappnode" && decodedPayload.Timestamp != "" && decodedPayload.Pubkey != "" {
57+
validPubkeys = append(validPubkeys, decodedPayload.Pubkey) // Collecting valid pubkeys
58+
} else {
59+
logger.Debug("Skipping invalid signature from request, invalid payload format")
60+
}
61+
}
62+
63+
// Make a single API call to validate pubkeys in bulk
64+
validatedPubkeys := validation.ValidatePubkeysWithConsensusClient(validPubkeys)
65+
if len(validatedPubkeys) == 0 {
66+
respondError(w, http.StatusInternalServerError, "Failed to validate pubkeys with consensus client")
67+
return
68+
}
69+
70+
// Now, iterate over the originally valid requests, check if the pubkey was validated, then verify signature and insert into DB
71+
// This means going over the requests again! TODO: find a better way?
72+
for _, req := range sigs {
73+
decodedBytes, _ := base64.StdEncoding.DecodeString(req.Payload)
74+
var decodedPayload types.DecodedPayload
75+
json.Unmarshal(decodedBytes, &decodedPayload)
76+
77+
// Only try to validate message signatures if the pubkey is validated
78+
if _, exists := validatedPubkeys[decodedPayload.Pubkey]; exists {
79+
// If the pubkey is validated, we can proceed to validate the signature
80+
if validation.ValidateSignatureAgainstPayload(req.Signature, decodedPayload) {
81+
// Insert into MongoDB
82+
_, err := dbCollection.InsertOne(r.Context(), bson.M{
83+
"platform": decodedPayload.Platform,
84+
"timestamp": decodedPayload.Timestamp,
85+
"pubkey": decodedPayload.Pubkey,
86+
"signature": req.Signature,
87+
"network": req.Network,
88+
"label": req.Label,
89+
})
90+
if err != nil {
91+
continue // TODO: Log error or handle as needed
92+
} else {
93+
logger.Info("New Signature " + req.Signature + " inserted into MongoDB")
94+
}
95+
}
96+
}
97+
}
98+
99+
respondOK(w, "Finished processing signatures")
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package handlers
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
7+
"go.mongodb.org/mongo-driver/bson"
8+
"go.mongodb.org/mongo-driver/mongo"
9+
)
10+
11+
type signaturesRequest struct {
12+
Pubkey string `json:"pubkey"`
13+
Network string `json:"network"`
14+
}
15+
16+
func PostSignaturesByValidator(w http.ResponseWriter, r *http.Request, dbCollection *mongo.Collection) {
17+
var req signaturesRequest
18+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
19+
respondError(w, http.StatusBadRequest, "Invalid request body")
20+
return
21+
}
22+
23+
filter := bson.M{"pubkey": req.Pubkey, "network": req.Network}
24+
cursor, err := dbCollection.Find(r.Context(), filter)
25+
if err != nil {
26+
respondError(w, http.StatusInternalServerError, "Error fetching signatures from MongoDB")
27+
return
28+
}
29+
defer cursor.Close(r.Context())
30+
31+
var signatures []bson.M
32+
if err = cursor.All(r.Context(), &signatures); err != nil {
33+
respondError(w, http.StatusInternalServerError, "Error reading signatures from cursor")
34+
return
35+
}
36+
37+
respondOK(w, signatures)
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package handlers
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
7+
"go.mongodb.org/mongo-driver/bson"
8+
"go.mongodb.org/mongo-driver/mongo"
9+
"go.mongodb.org/mongo-driver/mongo/options"
10+
)
11+
12+
// Endpoint that returns an aggregation of all signatures for a given pubkey and network in this format:
13+
// [
14+
//
15+
// {
16+
// "label": "example_label",
17+
// "network": "stader",
18+
// "pubkey": "0xb48c495c19082d892f38227bced89f7199f4e9b642bf94c7f2f1ccf29c0e6a6f54d653002513aa7cdb56c88368797ec",
19+
// "signatures": [
20+
// {
21+
// "platform": "dappnode",
22+
// "signature": "0xa8b00e7746a523346c5165dfa80ffafe52317c6fe6cdcfabd41886f9c8209b829266c5000597142b58dddbcc9c23cfd81315180acf18bb000db50d08293bc539e06a7c751d3d9dec89fb441b3ba6aefdeeff9cfed72fb41171173f22e2993e74",
23+
// "timestamp": "185921877"
24+
// },
25+
// {
26+
// "platform": "dappnode",
27+
// "signature": "0xa8b00e7746a523346c5165dfa80ffafe52317c6fe6cdcfabd41886f9c8209b829266c5000597142b58dddbcc9c23cfd81315180acf18bb000db50d08293bc539e06a7c751d3d9dec89fb441b3ba6aefdeeff9cfed72fb41171173f22e2993e74",
28+
// "timestamp": "185921877"
29+
// }
30+
// ]
31+
// }
32+
//
33+
// ]
34+
func PostSignaturesByValidatorAggr(w http.ResponseWriter, r *http.Request, dbCollection *mongo.Collection) {
35+
var req signaturesRequest
36+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
37+
respondError(w, http.StatusBadRequest, "Invalid request body")
38+
return
39+
}
40+
41+
// Define the aggregation pipeline
42+
// We should probably add pubkey to each signatures array element, so a 3rd party can easily verify the signature?
43+
pipeline := []bson.M{
44+
{
45+
"$match": bson.M{
46+
"pubkey": req.Pubkey,
47+
"network": req.Network,
48+
},
49+
},
50+
{
51+
"$group": bson.M{
52+
"_id": bson.M{"pubkey": "$pubkey", "network": "$network", "label": "$label"},
53+
"signatures": bson.M{
54+
"$push": bson.M{
55+
"signature": "$signature",
56+
"timestamp": "$timestamp",
57+
"platform": "$platform",
58+
},
59+
},
60+
},
61+
},
62+
{
63+
"$project": bson.M{
64+
"_id": 0,
65+
"pubkey": "$_id.pubkey",
66+
"network": "$_id.network",
67+
"label": "$_id.label",
68+
"signatures": 1,
69+
},
70+
},
71+
}
72+
73+
cursor, err := dbCollection.Aggregate(r.Context(), pipeline, options.Aggregate())
74+
if err != nil {
75+
respondError(w, http.StatusInternalServerError, "Error aggregating signatures from MongoDB")
76+
return
77+
}
78+
defer cursor.Close(r.Context())
79+
80+
var results []bson.M
81+
if err := cursor.All(r.Context(), &results); err != nil {
82+
respondError(w, http.StatusInternalServerError, "Error reading aggregation results")
83+
return
84+
}
85+
86+
// Respond with the aggregation results
87+
respondOK(w, results)
88+
}

0 commit comments

Comments
 (0)