Skip to content

Commit

Permalink
add signature array processing (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
Marketen authored Apr 10, 2024
1 parent 9e2551c commit 07508b5
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 55 deletions.
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
}

0 comments on commit 07508b5

Please sign in to comment.