From f698e5576bcda5c0e2163b86310e423f314deb01 Mon Sep 17 00:00:00 2001 From: remoterami <142154971+remoterami@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:34:31 +0100 Subject: [PATCH] feat(vdb): added validator search by hex graffiti --- backend/pkg/api/api_test.go | 11 +++++--- backend/pkg/api/data_access/dummy.go | 12 +++++--- backend/pkg/api/data_access/search.go | 29 ++++++++++++++++---- backend/pkg/api/handlers/input_validation.go | 5 ++-- backend/pkg/api/handlers/search.go | 16 +++++++++++ backend/pkg/api/types/search.go | 5 ++-- frontend/types/api/search.ts | 5 ++-- 7 files changed, 64 insertions(+), 19 deletions(-) diff --git a/backend/pkg/api/api_test.go b/backend/pkg/api/api_test.go index 11a4c4e07..accf3d87e 100644 --- a/backend/pkg/api/api_test.go +++ b/backend/pkg/api/api_test.go @@ -293,7 +293,8 @@ func TestInternalSearchHandler(t *testing.T) { "validators_by_withdrawal_credential", "validator_by_index", "validator_by_public_key", - "validators_by_graffiti" + "validators_by_graffiti", + "validators_by_graffiti_hex" ] }`)).Expect().Status(http.StatusOK).JSON().Decode(&resp) @@ -320,7 +321,8 @@ func TestInternalSearchHandler(t *testing.T) { "validators_by_withdrawal_credential", "validator_by_index", "validator_by_public_key", - "validators_by_graffiti" + "validators_by_graffiti", + "validators_by_graffiti_hex" ] }`)).Expect().Status(http.StatusOK).JSON().Decode(&resp) @@ -346,12 +348,13 @@ func TestInternalSearchHandler(t *testing.T) { "validators_by_withdrawal_credential", "validator_by_index", "validator_by_public_key", - "validators_by_graffiti" + "validators_by_graffiti", + "validators_by_graffiti_hex" ] }`)).Expect().Status(http.StatusOK).JSON().Decode(&resp) assert.NotEqual(t, 0, len(resp.Data), "response data should not be empty") - validatorsByWithdrawalAddress, ok := resp.Data[0].Value.(api_types.SearchValidatorsByWithdrwalCredential) + validatorsByWithdrawalAddress, ok := resp.Data[0].Value.(api_types.SearchValidatorsByWithdrawalCredential) assert.True(t, ok, "response data should be of type SearchValidator") assert.Greater(t, validatorsByWithdrawalAddress.Count, uint64(0), "returned number of validators should be greater than 0") } diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go index a2ed4265b..76e4a94a4 100644 --- a/backend/pkg/api/data_access/dummy.go +++ b/backend/pkg/api/data_access/dummy.go @@ -518,18 +518,22 @@ func (d *DummyService) GetSearchValidatorsByDepositEnsName(ctx context.Context, return getDummyStruct[t.SearchValidatorsByDepositAddress](ctx) } -func (d *DummyService) GetSearchValidatorsByWithdrawalCredential(ctx context.Context, chainId uint64, credential []byte) (*t.SearchValidatorsByWithdrwalCredential, error) { - return getDummyStruct[t.SearchValidatorsByWithdrwalCredential](ctx) +func (d *DummyService) GetSearchValidatorsByWithdrawalCredential(ctx context.Context, chainId uint64, credential []byte) (*t.SearchValidatorsByWithdrawalCredential, error) { + return getDummyStruct[t.SearchValidatorsByWithdrawalCredential](ctx) } -func (d *DummyService) GetSearchValidatorsByWithdrawalEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByWithdrwalCredential, error) { - return getDummyStruct[t.SearchValidatorsByWithdrwalCredential](ctx) +func (d *DummyService) GetSearchValidatorsByWithdrawalEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByWithdrawalCredential, error) { + return getDummyStruct[t.SearchValidatorsByWithdrawalCredential](ctx) } func (d *DummyService) GetSearchValidatorsByGraffiti(ctx context.Context, chainId uint64, graffiti string) (*t.SearchValidatorsByGraffiti, error) { return getDummyStruct[t.SearchValidatorsByGraffiti](ctx) } +func (d *DummyService) GetSearchValidatorsByGraffitiHex(ctx context.Context, chainId uint64, graffiti []byte) (*t.SearchValidatorsByGraffiti, error) { + return getDummyStruct[t.SearchValidatorsByGraffiti](ctx) +} + func (d *DummyService) GetUserValidatorDashboardCount(ctx context.Context, userId uint64, active bool) (uint64, error) { return getDummyData[uint64](ctx) } diff --git a/backend/pkg/api/data_access/search.go b/backend/pkg/api/data_access/search.go index d177e0161..c1a29329f 100644 --- a/backend/pkg/api/data_access/search.go +++ b/backend/pkg/api/data_access/search.go @@ -2,6 +2,7 @@ package dataaccess import ( "context" + "strings" "github.com/ethereum/go-ethereum/common/hexutil" t "github.com/gobitfly/beaconchain/pkg/api/types" @@ -13,9 +14,10 @@ type SearchRepository interface { GetSearchValidatorByPublicKey(ctx context.Context, chainId uint64, publicKey []byte) (*t.SearchValidator, error) GetSearchValidatorsByDepositAddress(ctx context.Context, chainId uint64, address []byte) (*t.SearchValidatorsByDepositAddress, error) GetSearchValidatorsByDepositEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByDepositAddress, error) - GetSearchValidatorsByWithdrawalCredential(ctx context.Context, chainId uint64, credential []byte) (*t.SearchValidatorsByWithdrwalCredential, error) - GetSearchValidatorsByWithdrawalEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByWithdrwalCredential, error) + GetSearchValidatorsByWithdrawalCredential(ctx context.Context, chainId uint64, credential []byte) (*t.SearchValidatorsByWithdrawalCredential, error) + GetSearchValidatorsByWithdrawalEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByWithdrawalCredential, error) GetSearchValidatorsByGraffiti(ctx context.Context, chainId uint64, graffiti string) (*t.SearchValidatorsByGraffiti, error) + GetSearchValidatorsByGraffitiHex(ctx context.Context, chainId uint64, graffiti []byte) (*t.SearchValidatorsByGraffiti, error) } func (d *DataAccessService) GetSearchValidatorByIndex(ctx context.Context, chainId, index uint64) (*t.SearchValidator, error) { @@ -75,9 +77,9 @@ func (d *DataAccessService) GetSearchValidatorsByDepositEnsName(ctx context.Cont return nil, ErrNotFound } -func (d *DataAccessService) GetSearchValidatorsByWithdrawalCredential(ctx context.Context, chainId uint64, credential []byte) (*t.SearchValidatorsByWithdrwalCredential, error) { +func (d *DataAccessService) GetSearchValidatorsByWithdrawalCredential(ctx context.Context, chainId uint64, credential []byte) (*t.SearchValidatorsByWithdrawalCredential, error) { // TODO: implement handling of chainid - ret := &t.SearchValidatorsByWithdrwalCredential{ + ret := &t.SearchValidatorsByWithdrawalCredential{ WithdrawalCredential: hexutil.Encode(credential), } err := db.ReaderDb.GetContext(ctx, &ret.Count, "select count(validatorindex) from validators where withdrawalcredentials = $1;", credential) @@ -90,7 +92,7 @@ func (d *DataAccessService) GetSearchValidatorsByWithdrawalCredential(ctx contex return ret, nil } -func (d *DataAccessService) GetSearchValidatorsByWithdrawalEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByWithdrwalCredential, error) { +func (d *DataAccessService) GetSearchValidatorsByWithdrawalEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByWithdrawalCredential, error) { // TODO: implement handling of chainid // TODO: finalize ens implementation first return nil, ErrNotFound @@ -100,6 +102,7 @@ func (d *DataAccessService) GetSearchValidatorsByGraffiti(ctx context.Context, c // TODO: implement handling of chainid ret := &t.SearchValidatorsByGraffiti{ Graffiti: graffiti, + Hex: hexutil.Encode([]byte(graffiti)), } err := db.ReaderDb.GetContext(ctx, &ret.Count, "select count(distinct proposer) from blocks where graffiti_text = $1;", graffiti) if err != nil { @@ -110,3 +113,19 @@ func (d *DataAccessService) GetSearchValidatorsByGraffiti(ctx context.Context, c } return ret, nil } + +func (d *DataAccessService) GetSearchValidatorsByGraffitiHex(ctx context.Context, chainId uint64, graffiti []byte) (*t.SearchValidatorsByGraffiti, error) { + // TODO: implement handling of chainid + ret := &t.SearchValidatorsByGraffiti{ + Graffiti: strings.TrimRight(string(graffiti), "\u0000"), + Hex: hexutil.Encode(graffiti), + } + err := db.ReaderDb.GetContext(ctx, &ret.Count, "select count(distinct proposer) from blocks where graffiti = $1;", graffiti) + if err != nil { + return nil, err + } + if ret.Count == 0 { + return nil, ErrNotFound + } + return ret, nil +} diff --git a/backend/pkg/api/handlers/input_validation.go b/backend/pkg/api/handlers/input_validation.go index df3d53f1d..f03373a90 100644 --- a/backend/pkg/api/handlers/input_validation.go +++ b/backend/pkg/api/handlers/input_validation.go @@ -34,8 +34,9 @@ var ( reEthereumAddress = regexp.MustCompile(`^(0x)?[0-9a-fA-F]{40}$`) reWithdrawalCredential = regexp.MustCompile(`^(0x0[01])?[0-9a-fA-F]{62}$`) reEnsName = regexp.MustCompile(`^.+\.eth$`) - reGraffiti = regexp.MustCompile(`^.{2,}$`) // at least 2 characters, so that queries won't time out - reCursor = regexp.MustCompile(`^[A-Za-z0-9-_]+$`) // has to be base64 + reGraffiti = regexp.MustCompile(`^.{2,}$`) // at least 2 characters, so that queries won't time out + reGraffitiHex = regexp.MustCompile(`^(0x)?([0-9a-fA-F]{2}){2,}$`) // at least 2 bytes, so that queries won't time out + reCursor = regexp.MustCompile(`^[A-Za-z0-9-_]+$`) // has to be base64 reEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") rePassword = regexp.MustCompile(`^.{5,}$`) reEmailUserToken = regexp.MustCompile(`^[a-z0-9]{40}$`) diff --git a/backend/pkg/api/handlers/search.go b/backend/pkg/api/handlers/search.go index e1a9f28c6..3720f5e7d 100644 --- a/backend/pkg/api/handlers/search.go +++ b/backend/pkg/api/handlers/search.go @@ -30,6 +30,7 @@ const ( validatorsByWithdrawalAddress searchTypeKey = "validators_by_withdrawal_address" validatorsByWithdrawalEns searchTypeKey = "validators_by_withdrawal_ens_name" validatorsByGraffiti searchTypeKey = "validators_by_graffiti" + validatorsByGraffitiHex searchTypeKey = "validators_by_graffiti_hex" ) // source of truth for all possible search types and their regex @@ -70,6 +71,10 @@ var searchTypeMap = map[searchTypeKey]searchType{ regex: reGraffiti, responseType: string(validatorsByGraffiti), }, + validatorsByGraffitiHex: { + regex: reGraffitiHex, + responseType: string(validatorsByGraffiti), + }, } type searchType struct { @@ -173,6 +178,8 @@ func (h *HandlerService) handleSearchType(ctx context.Context, input string, sea return h.handleSearchValidatorsByWithdrawalEnsName(ctx, input, chainId) case validatorsByGraffiti: return h.handleSearchValidatorsByGraffiti(ctx, input, chainId) + case validatorsByGraffitiHex: + return h.handleSearchValidatorsByGraffitiHex(ctx, input, chainId) default: return nil, errors.New("invalid search type") } @@ -271,6 +278,15 @@ func (h *HandlerService) handleSearchValidatorsByGraffiti(ctx context.Context, i return asSearchResult(validatorsByGraffiti, chainId, result, err) } +func (h *HandlerService) handleSearchValidatorsByGraffitiHex(ctx context.Context, input string, chainId uint64) (*types.SearchResult, error) { + graffitiHex, err := hex.DecodeString(strings.TrimPrefix(input, "0x")) + if err != nil { + return nil, err + } + result, err := h.daService.GetSearchValidatorsByGraffitiHex(ctx, chainId, graffitiHex) + return asSearchResult(validatorsByGraffitiHex, chainId, result, err) +} + // -------------------------------------- // Input Validation diff --git a/backend/pkg/api/types/search.go b/backend/pkg/api/types/search.go index 202b1607e..c57b68cce 100644 --- a/backend/pkg/api/types/search.go +++ b/backend/pkg/api/types/search.go @@ -17,7 +17,7 @@ type SearchValidatorsByDepositAddress struct { Count uint64 `json:"count"` } -type SearchValidatorsByWithdrwalCredential struct { +type SearchValidatorsByWithdrawalCredential struct { EnsName string `json:"ens_name,omitempty"` WithdrawalCredential string `json:"withdrawal_credential"` Count uint64 `json:"count"` @@ -25,6 +25,7 @@ type SearchValidatorsByWithdrwalCredential struct { type SearchValidatorsByGraffiti struct { Graffiti string `json:"graffiti"` + Hex string `json:"hex"` Count uint64 `json:"count"` } @@ -35,5 +36,5 @@ type SearchResult struct { } type InternalPostSearchResponse struct { - Data []SearchResult `json:"data" tstype:"({ type: 'validator'; chain_id: number; value: SearchValidator } | { type: 'validator_list'; chain_id: number; value: SearchValidatorList } | { type: 'validators_by_deposit_address'; chain_id: number; value: SearchValidatorsByDepositAddress } | { type: 'validators_by_withdrawal_credential'; chain_id: number; value: SearchValidatorsByWithdrwalCredential } | { type: 'validators_by_graffiti'; chain_id: number; value: SearchValidatorsByGraffiti })[]"` + Data []SearchResult `json:"data" tstype:"({ type: 'validator'; chain_id: number; value: SearchValidator } | { type: 'validator_list'; chain_id: number; value: SearchValidatorList } | { type: 'validators_by_deposit_address'; chain_id: number; value: SearchValidatorsByDepositAddress } | { type: 'validators_by_withdrawal_credential'; chain_id: number; value: SearchValidatorsByWithdrawalCredential } | { type: 'validators_by_graffiti'; chain_id: number; value: SearchValidatorsByGraffiti })[]"` } diff --git a/frontend/types/api/search.ts b/frontend/types/api/search.ts index e5f2eb870..4c1beaca9 100644 --- a/frontend/types/api/search.ts +++ b/frontend/types/api/search.ts @@ -16,13 +16,14 @@ export interface SearchValidatorsByDepositAddress { deposit_address: string; count: number /* uint64 */; } -export interface SearchValidatorsByWithdrwalCredential { +export interface SearchValidatorsByWithdrawalCredential { ens_name?: string; withdrawal_credential: string; count: number /* uint64 */; } export interface SearchValidatorsByGraffiti { graffiti: string; + hex: string; count: number /* uint64 */; } export interface SearchResult { @@ -31,5 +32,5 @@ export interface SearchResult { value: any; } export interface InternalPostSearchResponse { - data: ({ type: 'validator'; chain_id: number; value: SearchValidator } | { type: 'validator_list'; chain_id: number; value: SearchValidatorList } | { type: 'validators_by_deposit_address'; chain_id: number; value: SearchValidatorsByDepositAddress } | { type: 'validators_by_withdrawal_credential'; chain_id: number; value: SearchValidatorsByWithdrwalCredential } | { type: 'validators_by_graffiti'; chain_id: number; value: SearchValidatorsByGraffiti })[]; + data: ({ type: 'validator'; chain_id: number; value: SearchValidator } | { type: 'validator_list'; chain_id: number; value: SearchValidatorList } | { type: 'validators_by_deposit_address'; chain_id: number; value: SearchValidatorsByDepositAddress } | { type: 'validators_by_withdrawal_credential'; chain_id: number; value: SearchValidatorsByWithdrawalCredential } | { type: 'validators_by_graffiti'; chain_id: number; value: SearchValidatorsByGraffiti })[]; }