diff --git a/tools/points-service/api.go b/tools/points-service/api.go index af302f543..a1846ab19 100644 --- a/tools/points-service/api.go +++ b/tools/points-service/api.go @@ -38,6 +38,7 @@ func (p *PointsAPI) StartAPIServer(ctx context.Context, addr string) error { r.HandleFunc("/admin/add_manual_entry", p.AddManualPointsEntry).Methods("POST") r.HandleFunc("/admin/add_manual_opt_out", p.AddManualOptOut).Methods("POST") + r.HandleFunc("/admin/update_addr", p.UpdateAddrForManualValRecord).Methods("PUT") srv := &http.Server{ Addr: addr, @@ -144,6 +145,37 @@ func (p *PointsAPI) AddManualOptOut(w http.ResponseWriter, r *http.Request) { writeJSON(w, resp, http.StatusOK) } +func (p *PointsAPI) UpdateAddrForManualValRecord(w http.ResponseWriter, r *http.Request) { + if err := p.checkAuth(r.Header.Get("Authorization")); err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + + var req struct { + Pubkey string `json:"pubkey"` + OldAddr string `json:"old_addr"` + NewAddr string `json:"new_addr"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + if req.Pubkey == "" || req.OldAddr == "" || req.NewAddr == "" { + http.Error(w, "Missing or invalid required fields", http.StatusBadRequest) + return + } + + if err := updateAddrForManualValRecord(p.db, p.logger, req.Pubkey, req.OldAddr, req.NewAddr); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resp := map[string]string{"status": "success"} + writeJSON(w, resp, http.StatusOK) +} + func (p *PointsAPI) HealthCheck(w http.ResponseWriter, r *http.Request) { if !p.ps.IsPointsRoutineRunning() { http.Error(w, "Points routine not running", http.StatusServiceUnavailable) diff --git a/tools/points-service/main.go b/tools/points-service/main.go index b8ed779f5..1beae0dae 100644 --- a/tools/points-service/main.go +++ b/tools/points-service/main.go @@ -504,7 +504,12 @@ func insertManualValRecord(db *sql.DB, return nil } -func insertOptOut(db *sql.DB, logger *slog.Logger, pubkey, adder, eventType string, outBlock uint64) { +func insertOptOut( + db *sql.DB, + logger *slog.Logger, + pubkey, adder, eventType string, + outBlock uint64, +) { _, err := db.Exec(` UPDATE validator_records @@ -543,6 +548,45 @@ func insertManualOptOut(db *sql.DB, logger *slog.Logger, pubkey, adder string, o return nil } +func updateAddrForManualValRecord( + db *sql.DB, + logger *slog.Logger, + pubkey string, + oldAddr string, + newAddr string, +) error { + var count int + err := db.QueryRow(` + SELECT COUNT(*) + FROM validator_records + WHERE pubkey = ? AND adder = ? AND registry_type IS NULL AND event_type IS NULL + `, pubkey, oldAddr).Scan(&count) + if err != nil { + return fmt.Errorf("failed to query validator_records: %w", err) + } + if count == 0 { + return fmt.Errorf("no manual validator record found for pubkey %s with addr %s", pubkey, oldAddr) + } + + res, err := db.Exec(` + UPDATE validator_records + SET adder = ? + WHERE pubkey = ? AND adder = ? AND registry_type IS NULL AND event_type IS NULL + `, newAddr, pubkey, oldAddr) + if err != nil { + return fmt.Errorf("failed to update adder for manual val record: %w", err) + } + + rows, _ := res.RowsAffected() + logger.Info("updated adder for manual validator record", + "pubkey", pubkey, + "old_addr", oldAddr, + "new_adder", newAddr, + "rows_affected", rows) + + return nil +} + func getMsgSenderFromTxnHash(ethClient *ethclient.Client, txHash common.Hash) (common.Address, error) { tx, _, err := ethClient.TransactionByHash(context.Background(), txHash) if err != nil { diff --git a/tools/points-service/points_test.go b/tools/points-service/points_test.go index f89c05398..2695310b1 100644 --- a/tools/points-service/points_test.go +++ b/tools/points-service/points_test.go @@ -119,9 +119,13 @@ func TestManualPointsEntry(t *testing.T) { } logger := slog.New(slog.NewTextHandler(io.Discard, nil)) - insertOptIn(db, logger, "0x123", "0x456", "vanilla", "staked", 100) + vanillaStakedPubkey := "0x123" + vanillaStakedAddr := "0x456" + insertOptIn(db, logger, vanillaStakedPubkey, vanillaStakedAddr, "vanilla", "staked", 100) - if err := insertManualValRecord(db, "0x12345", "0x45678", 90); err != nil { + manuallyInsertedPubkey := "0x12345" + manuallyInsertedAddr := "0x45678" + if err := insertManualValRecord(db, manuallyInsertedPubkey, manuallyInsertedAddr, 90); err != nil { t.Fatalf("failed to insert manual val record: %v", err) } @@ -181,4 +185,68 @@ func TestManualPointsEntry(t *testing.T) { if optedOutBlockAfter2.String != "4001" { t.Errorf("expected opted_out_block to be 4001, got %s", optedOutBlockAfter2.String) } + + newAddr := "0x2222222277777777" + + err = updateAddrForManualValRecord(db, logger, vanillaStakedPubkey, vanillaStakedAddr, newAddr) + if err == nil { + t.Fatalf("expected error for updating addr of non-manually inserted record") + } + + wrongOldAddr := "0x3333333377777777" + err = updateAddrForManualValRecord(db, logger, manuallyInsertedPubkey, wrongOldAddr, newAddr) + if err == nil { + t.Fatalf("expected error for updating addr of manually inserted record with wrong old addr") + } + + wrongPubkey := "0xpkpkpk" + err = updateAddrForManualValRecord(db, logger, wrongPubkey, manuallyInsertedAddr, newAddr) + if err == nil { + t.Fatalf("expected error for updating addr of manually inserted record with wrong pubkey") + } + + err = updateAddrForManualValRecord(db, logger, manuallyInsertedPubkey, manuallyInsertedAddr, newAddr) + if err != nil { + t.Fatalf("expected no error for updating addr of manually inserted record, got %v", err) + } + + var countAfterUpdate int + err = db.QueryRow("SELECT COUNT(*) FROM validator_records").Scan(&countAfterUpdate) + if err != nil { + t.Fatalf("failed to query count: %v", err) + } + if countAfterUpdate != 2 { + t.Errorf("expected 2 records, got %d", countAfterUpdate) + } + + var addrAfterUpdate sql.NullString + err = db.QueryRow("SELECT adder FROM validator_records WHERE pubkey = '0x12345'").Scan(&addrAfterUpdate) + if err != nil { + t.Fatalf("failed to query adder: %v", err) + } + if addrAfterUpdate.String != newAddr { + t.Errorf("expected adder to be %s, got %s", newAddr, addrAfterUpdate.String) + } + + // Confirm we can change it a second time + newAddrAgain := "0xagain" + + err = updateAddrForManualValRecord(db, logger, manuallyInsertedPubkey, manuallyInsertedAddr, newAddrAgain) + if err == nil { + t.Fatalf("expected error for updating addr of manually inserted record with same old addr as last time") + } + + err = updateAddrForManualValRecord(db, logger, manuallyInsertedPubkey, newAddr, newAddrAgain) + if err != nil { + t.Fatalf("expected no error for updating addr of manually inserted record, got %v", err) + } + + var addrAfterSecondUpdate sql.NullString + err = db.QueryRow("SELECT adder FROM validator_records WHERE pubkey = '0x12345'").Scan(&addrAfterSecondUpdate) + if err != nil { + t.Fatalf("failed to query adder: %v", err) + } + if addrAfterSecondUpdate.String != newAddrAgain { + t.Errorf("expected adder to be %s, got %s", newAddrAgain, addrAfterSecondUpdate.String) + } }