Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add signature array processing #9

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 65 additions & 54 deletions listener/src/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,71 +17,82 @@ func (s *httpApi) handleRoot(w http.ResponseWriter, r *http.Request) {

func (s *httpApi) handleSignature(w http.ResponseWriter, r *http.Request) {
logger.Debug("Received new POST '/signature' request")
var req types.SignatureRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
logger.Error("Failed to decode request body" + err.Error())
s.respondError(w, http.StatusBadRequest, "Invalid request")
return
}

// validate that the request has the required fields and that the signature is valid
// we expect network and label to be strings, signature to be a 0x prefixed hex string of 194 characters
// and payload to be a 0x prefixed hex string.
if req.Network == "" || req.Label == "" || req.Signature == "" || req.Payload == "" {
logger.Debug("Invalid request, missing fields. Request had the following fields: " + req.Network + " " + req.Label + " " + req.Signature + " " + req.Payload)
s.respondError(w, http.StatusBadRequest, "Invalid request, missing fields")
return
}

// validate the signature
if !validateSignature(req.Signature) {
logger.Debug("Invalid signature. It should be a 0x prefixed hex string of 194 characters. Request had the following signature: " + req.Signature)
s.respondError(w, http.StatusBadRequest, "Invalid signature")
return
}

// Decode BASE64 payload
decodedBytes, err := base64.StdEncoding.DecodeString(req.Payload)
var sigs []types.SignatureRequest
err := json.NewDecoder(r.Body).Decode(&sigs)
if err != nil {
logger.Error("Failed to decode BASE64 payload" + err.Error())
s.respondError(w, http.StatusBadRequest, "Invalid BASE64 payload")
logger.Error("Failed to decode request body: " + err.Error())
s.respondError(w, http.StatusBadRequest, "Invalid request format")
return
}
var decodedPayload types.DecodedPayload
err = json.Unmarshal(decodedBytes, &decodedPayload)
if err != nil {
logger.Error("Failed to decode JSON payload" + err.Error())
s.respondError(w, http.StatusBadRequest, "Invalid payload format")
return

var validPubkeys []string // Needed to store valid pubkeys for bulk validation later

// For each element of the request slice, we validate the element format and decode its payload
for _, req := range sigs {
if req.Network == "" || req.Label == "" || req.Signature == "" || req.Payload == "" {
logger.Debug("Skipping invalid signature from request, missing fields")
continue // Skipping invalid elements
}
if !validateSignature(req.Signature) {
logger.Debug("Skipping invalid signature from request, invalid signature format: " + req.Signature)
continue // Skipping invalid signatures
}
decodedBytes, err := base64.StdEncoding.DecodeString(req.Payload)
if err != nil {
logger.Error("Failed to decode BASE64 payload from request: " + err.Error())
continue // Skipping payloads that can't be decoded from BASE64
}
var decodedPayload types.DecodedPayload
if err := json.Unmarshal(decodedBytes, &decodedPayload); err != nil {
logger.Error("Failed to decode JSON payload from request: " + err.Error())
continue // Skipping payloads that can't be decoded from JSON
}
// If the payload is valid, we append the pubkey to the validPubkeys slice. Else, we skip it
if decodedPayload.Platform == "dappnode" && decodedPayload.Timestamp != "" && decodedPayload.Pubkey != "" {
validPubkeys = append(validPubkeys, decodedPayload.Pubkey) // Collecting valid pubkeys
} else {
logger.Debug("Skipping invalid signature from request, invalid payload format")
}
}

// Validate the decoded payload (maybe we should be more strict here)
if decodedPayload.Platform != "dappnode" || decodedPayload.Timestamp == "" || decodedPayload.Pubkey == "" {
logger.Debug("Invalid payload content. Request had the following payload: " + decodedPayload.Platform + " " + decodedPayload.Timestamp + " " + decodedPayload.Pubkey)
s.respondError(w, http.StatusBadRequest, "Payload content is invalid")
// Make a single API call to validate pubkeys in bulk
validatedPubkeys := validatePubkeysWithConsensusClient(validPubkeys)
if len(validatedPubkeys) == 0 {
s.respondError(w, http.StatusInternalServerError, "Failed to validate pubkeys with consensus client")
return
}

logger.Debug("Request's payload decoded successfully. Inserting decoded message into MongoDB")

// Insert into MongoDB
_, err = s.dbCollection.InsertOne(r.Context(), bson.M{
"platform": decodedPayload.Platform,
"timestamp": decodedPayload.Timestamp,
"pubkey": decodedPayload.Pubkey,
"signature": req.Signature,
"network": req.Network,
"label": req.Label,
})
if err != nil {
logger.Error("Failed to insert message into MongoDB" + err.Error())
s.respondError(w, http.StatusInternalServerError, "Failed to insert payload into MongoDB")
return
// Now, iterate over the originally valid requests, check if the pubkey was validated, then verify signature and insert into DB
// This means going over the requests again! TODO: find a better way?
for _, req := range sigs {
decodedBytes, _ := base64.StdEncoding.DecodeString(req.Payload)
var decodedPayload types.DecodedPayload
json.Unmarshal(decodedBytes, &decodedPayload)

// Only try to validate message signatures if the pubkey is validated
if _, exists := validatedPubkeys[decodedPayload.Pubkey]; exists {
// If the pubkey is validated, we can proceed to validate the signature
if validateSignatureAgainstPayload(req.Signature, decodedPayload) {
// Insert into MongoDB
_, err := s.dbCollection.InsertOne(r.Context(), bson.M{
"platform": decodedPayload.Platform,
"timestamp": decodedPayload.Timestamp,
"pubkey": decodedPayload.Pubkey,
"signature": req.Signature,
"network": req.Network,
"label": req.Label,
})
if err != nil {
continue // TODO: Log error or handle as needed
} else {
logger.Info("New Signature " + req.Signature + " inserted into MongoDB")
}
}
}
}
logger.Info("A new message with pubkey " + decodedPayload.Pubkey + " was decoded and inserted into MongoDB successfully")
s.respondOK(w, "Message validated and inserted into MongoDB")

s.respondOK(w, "Finished processing signatures")
}

func (s *httpApi) handleGetSignatures(w http.ResponseWriter, r *http.Request) {
Expand Down
21 changes: 20 additions & 1 deletion listener/src/api/utils.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
package api

import "github.com/dappnode/validator-monitoring/listener/src/types"

// A valid signature is a 0x prefixed hex string of 194 characters (including the prefix)
func validateSignature(signature string) bool {

// validate the signature
if len(signature) != 194 || signature[:2] != "0x" {
return false
}
return true
}

// validatePubkeysWithConsensusClient simulates making a bulk request to a consensus client for validating pubkeys.
// It can return a map of validated pubkeys that exist as validators.
func validatePubkeysWithConsensusClient(pubkeys []string) map[string]bool {
validatedPubkeys := make(map[string]bool)
// make api call: GET /eth/v1/beacon/states/{state_id}/validators?id=validator_pubkey1,validator_pubkey2,validator_pubkey3

for _, pubkey := range pubkeys {
validatedPubkeys[pubkey] = true // Assuming all given pubkeys are valid
}
return validatedPubkeys
}

// Dummy implementation of validateSignatureAgainstPayload
func validateSignatureAgainstPayload(signature string, payload types.DecodedPayload) bool {
// signature validation logic here
return true
}
Loading