Skip to content

Commit 7658aa3

Browse files
committed
feat: add search for address, transaction, block, epoch and token
1 parent 35e2e41 commit 7658aa3

File tree

5 files changed

+221
-2
lines changed

5 files changed

+221
-2
lines changed

backend/pkg/api/data_access/dummy.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,3 +900,23 @@ func (*DummyService) GetValidatorDashboardValidatorsOfList(ctx context.Context,
900900
func (d *DummyService) GetEthpool(ctx context.Context, day time.Time, validators []t.VDBValidator) ([]t.EthpoolData, error) {
901901
return getDummyData[[]t.EthpoolData](ctx)
902902
}
903+
904+
func (d *DummyService) GetSearchAddress(ctx context.Context, chainId uint64, address []byte) (*t.SearchAddress, error) {
905+
return getDummyStruct[t.SearchAddress](ctx)
906+
}
907+
908+
func (d *DummyService) GetSearchTransaction(ctx context.Context, chainId uint64, transactionHash []byte) (*t.SearchTransaction, error) {
909+
return getDummyStruct[t.SearchTransaction](ctx)
910+
}
911+
912+
func (d *DummyService) GetSearchBlock(ctx context.Context, chainId uint64, blockNumber uint64) (*t.SearchBlock, error) {
913+
return getDummyStruct[t.SearchBlock](ctx)
914+
}
915+
916+
func (d *DummyService) GetSearchEpoch(ctx context.Context, chainId uint64, epoch uint64) (*t.SearchEpoch, error) {
917+
return getDummyStruct[t.SearchEpoch](ctx)
918+
}
919+
920+
func (d *DummyService) GetSearchToken(ctx context.Context, chainId uint64, address []byte) (*t.SearchToken, error) {
921+
return getDummyStruct[t.SearchToken](ctx)
922+
}

backend/pkg/api/data_access/search.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package dataaccess
22

33
import (
44
"context"
5+
"database/sql"
6+
"fmt"
57
"strings"
68

9+
"github.com/doug-martin/goqu/v9"
710
"github.com/ethereum/go-ethereum/common/hexutil"
811
t "github.com/gobitfly/beaconchain/pkg/api/types"
912
"github.com/gobitfly/beaconchain/pkg/commons/db"
13+
"github.com/pkg/errors"
1014
)
1115

1216
type SearchRepository interface {
@@ -18,6 +22,11 @@ type SearchRepository interface {
1822
GetSearchValidatorsByWithdrawalEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByWithdrawalCredential, error)
1923
GetSearchValidatorsByGraffiti(ctx context.Context, chainId uint64, graffiti string) (*t.SearchValidatorsByGraffiti, error)
2024
GetSearchValidatorsByGraffitiHex(ctx context.Context, chainId uint64, graffiti []byte) (*t.SearchValidatorsByGraffiti, error)
25+
GetSearchAddress(ctx context.Context, chainId uint64, address []byte) (*t.SearchAddress, error)
26+
GetSearchTransaction(ctx context.Context, chainId uint64, transactionHash []byte) (*t.SearchTransaction, error)
27+
GetSearchBlock(ctx context.Context, chainId uint64, blockNumber uint64) (*t.SearchBlock, error)
28+
GetSearchEpoch(ctx context.Context, chainId uint64, epoch uint64) (*t.SearchEpoch, error)
29+
GetSearchToken(ctx context.Context, chainId uint64, address []byte) (*t.SearchToken, error)
2130
}
2231

2332
func (d *DataAccessService) GetSearchValidatorByIndex(ctx context.Context, chainId, index uint64) (*t.SearchValidator, error) {
@@ -131,3 +140,90 @@ func (d *DataAccessService) GetSearchValidatorsByGraffitiHex(ctx context.Context
131140
}
132141
return ret, nil
133142
}
143+
144+
func (d *DataAccessService) GetSearchAddress(ctx context.Context, chainId uint64, address []byte) (*t.SearchAddress, error) {
145+
eth1AddressSearchItem, err := d.bigtable.SearchForAddress(address, 1)
146+
if err != nil {
147+
return nil, fmt.Errorf("failed to search for address %s: %w", hexutil.Encode(address), err)
148+
}
149+
if len(eth1AddressSearchItem) == 0 || eth1AddressSearchItem[0] == nil {
150+
return nil, ErrNotFound
151+
}
152+
foundAddress := *eth1AddressSearchItem[0]
153+
return &t.SearchAddress{
154+
Address: t.Address{
155+
Hash: t.Hash("0x" + foundAddress.Address),
156+
Label: foundAddress.Name,
157+
IsContract: foundAddress.Token != "",
158+
},
159+
}, nil
160+
}
161+
162+
func (d *DataAccessService) GetSearchTransaction(ctx context.Context, chainId uint64, transactionHash []byte) (*t.SearchTransaction, error) {
163+
tx, err := db.BigtableClient.GetIndexedEth1Transaction(transactionHash)
164+
if err != nil {
165+
return nil, fmt.Errorf("failed to search for transaction %s: %w", hexutil.Encode(transactionHash), err)
166+
}
167+
if tx == nil {
168+
return nil, ErrNotFound
169+
}
170+
return &t.SearchTransaction{
171+
TransactionHash: t.Hash(hexutil.Encode(tx.Hash)),
172+
}, nil
173+
}
174+
175+
func (d *DataAccessService) GetSearchBlock(ctx context.Context, chainId uint64, blockNumber uint64) (*t.SearchBlock, error) {
176+
block, err := db.BigtableClient.GetBlockFromBlocksTable(blockNumber)
177+
if err != nil {
178+
if err == db.ErrBlockNotFound {
179+
return nil, ErrNotFound
180+
}
181+
return nil, fmt.Errorf("failed to search for block %d: %w", blockNumber, err)
182+
}
183+
if block == nil {
184+
return nil, fmt.Errorf("nil block returned for block number %d", blockNumber)
185+
}
186+
return &t.SearchBlock{
187+
BlockNumber: block.Number,
188+
}, nil
189+
}
190+
191+
func (d *DataAccessService) GetSearchEpoch(ctx context.Context, chainId uint64, epoch uint64) (*t.SearchEpoch, error) {
192+
ds := goqu.Dialect("postgres").
193+
From("epochs").
194+
Select(goqu.I("epoch")).
195+
Where(goqu.I("epoch").Eq(epoch))
196+
197+
foundEpoch, err := runQuery[uint64](ctx, d.readerDb, ds)
198+
if err != nil {
199+
if errors.Is(err, sql.ErrNoRows) {
200+
return nil, ErrNotFound
201+
}
202+
return nil, err
203+
}
204+
return &t.SearchEpoch{
205+
Epoch: foundEpoch,
206+
}, nil
207+
}
208+
209+
func (d *DataAccessService) GetSearchToken(ctx context.Context, chainId uint64, address []byte) (*t.SearchToken, error) {
210+
eth1AddressSearchItem, err := d.bigtable.SearchForAddress(address, 1)
211+
if err != nil {
212+
return nil, fmt.Errorf("failed to search for address %s: %w", hexutil.Encode(address), err)
213+
}
214+
if len(eth1AddressSearchItem) == 0 || eth1AddressSearchItem[0] == nil {
215+
return nil, ErrNotFound
216+
}
217+
foundAddress := *eth1AddressSearchItem[0]
218+
if foundAddress.Token == "" {
219+
return nil, ErrNotFound
220+
}
221+
return &t.SearchToken{
222+
Address: t.Address{
223+
Hash: t.Hash("0x" + foundAddress.Address),
224+
IsContract: true,
225+
Label: foundAddress.Name,
226+
},
227+
Token: foundAddress.Token,
228+
}, nil
229+
}

backend/pkg/api/handlers/search.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ const (
3131
validatorsByWithdrawalEns searchTypeKey = "validators_by_withdrawal_ens_name"
3232
validatorsByGraffiti searchTypeKey = "validators_by_graffiti"
3333
validatorsByGraffitiHex searchTypeKey = "validators_by_graffiti_hex"
34+
35+
addressKey searchTypeKey = "address"
36+
transactionKey searchTypeKey = "transaction"
37+
blockKey searchTypeKey = "block"
38+
epochKey searchTypeKey = "epoch"
39+
tokenKey searchTypeKey = "token"
3440
)
3541

3642
type searchType struct {
@@ -95,6 +101,31 @@ func init() {
95101
responseType: string(validatorsByGraffiti),
96102
handlerFunc: handleSearchValidatorsByGraffitiHex,
97103
},
104+
addressKey: {
105+
regex: types.ReEthereumAddress,
106+
responseType: string(addressKey),
107+
handlerFunc: handleSearchAddress,
108+
},
109+
transactionKey: {
110+
regex: types.ReTransactionHash,
111+
responseType: string(transactionKey),
112+
handlerFunc: handleSearchTransaction,
113+
},
114+
blockKey: {
115+
regex: types.ReInteger,
116+
responseType: string(blockKey),
117+
handlerFunc: handleSearchBlock,
118+
},
119+
epochKey: {
120+
regex: types.ReInteger,
121+
responseType: string(epochKey),
122+
handlerFunc: handleSearchEpoch,
123+
},
124+
tokenKey: {
125+
regex: types.ReEthereumAddress,
126+
responseType: string(tokenKey),
127+
handlerFunc: handleSearchToken,
128+
},
98129
}
99130
}
100131

@@ -286,6 +317,51 @@ func handleSearchValidatorsByGraffitiHex(ctx context.Context, h *HandlerService,
286317
return asSearchResult(validatorsByGraffitiHex, chainId, result, err)
287318
}
288319

320+
func handleSearchAddress(ctx context.Context, h *HandlerService, input string, chainId uint64) (*types.SearchResult, error) {
321+
address, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
322+
if err != nil {
323+
return nil, err
324+
}
325+
result, err := h.daService.GetSearchAddress(ctx, chainId, address)
326+
return asSearchResult(addressKey, chainId, result, err)
327+
}
328+
329+
func handleSearchTransaction(ctx context.Context, h *HandlerService, input string, chainId uint64) (*types.SearchResult, error) {
330+
transactionHash, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
331+
if err != nil {
332+
return nil, err
333+
}
334+
result, err := h.daService.GetSearchTransaction(ctx, chainId, transactionHash)
335+
return asSearchResult(transactionKey, chainId, result, err)
336+
}
337+
338+
func handleSearchBlock(ctx context.Context, h *HandlerService, input string, chainId uint64) (*types.SearchResult, error) {
339+
blockNumber, err := strconv.ParseUint(input, 10, 64)
340+
if err != nil {
341+
return nil, err
342+
}
343+
result, err := h.daService.GetSearchBlock(ctx, chainId, blockNumber)
344+
return asSearchResult(blockKey, chainId, result, err)
345+
}
346+
347+
func handleSearchEpoch(ctx context.Context, h *HandlerService, input string, chainId uint64) (*types.SearchResult, error) {
348+
epoch, err := strconv.ParseUint(input, 10, 64)
349+
if err != nil {
350+
return nil, err
351+
}
352+
result, err := h.daService.GetSearchEpoch(ctx, chainId, epoch)
353+
return asSearchResult(epochKey, chainId, result, err)
354+
}
355+
356+
func handleSearchToken(ctx context.Context, h *HandlerService, input string, chainId uint64) (*types.SearchResult, error) {
357+
tokenAddress, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
358+
if err != nil {
359+
return nil, err
360+
}
361+
result, err := h.daService.GetSearchToken(ctx, chainId, tokenAddress)
362+
return asSearchResult(tokenKey, chainId, result, err)
363+
}
364+
289365
// --------------------------------------
290366
// Input Validation
291367

backend/pkg/api/types/regexes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var (
1111
ReValidatorList = regexp.MustCompile(`^(0x[0-9a-fA-F]{96}|[0-9]+)(,\s*(0x[0-9a-fA-F]{96}|[0-9]+)\s*)+$`)
1212
ReEthereumAddress = regexp.MustCompile(`^(0x)?[0-9a-fA-F]{40}$`)
1313
ReWithdrawalCredential = regexp.MustCompile(`^(0x)?0[012][0-9a-fA-F]{62}$`)
14-
ReTransactionHash = regexp.MustCompile(`^0x[0-9a-fA-F]{62}$`)
14+
ReTransactionHash = regexp.MustCompile(`^0x[0-9a-fA-F]{64}$`)
1515
ReEnsName = regexp.MustCompile(`^.+\.eth$`)
1616
ReGraffiti = regexp.MustCompile(`^.{2,32}$`) // at least 2 characters, so that queries won't time out
1717
ReGraffitiHex = regexp.MustCompile(`^(0x)?([0-9a-fA-F]{2}){32}$`)

backend/pkg/api/types/search.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ package types
22

33
// search types to be used between the data access layer and the api layer, shouldn't be exported to typescript
44

5+
type PostSearchRequest struct {
6+
Input string `json:"input"`
7+
Networks []interface{} `json:"networks,omitempty" tstype:"(number | string)[]"`
8+
Types []string `json:"types,omitempty"`
9+
}
10+
511
type SearchValidator struct {
612
Index uint64 `json:"index"`
713
PublicKey string `json:"public_key"`
@@ -29,12 +35,33 @@ type SearchValidatorsByGraffiti struct {
2935
Count uint64 `json:"count"`
3036
}
3137

38+
type SearchAddress struct {
39+
Address Address `json:"address"`
40+
}
41+
42+
type SearchTransaction struct {
43+
TransactionHash Hash `json:"transaction_hash"`
44+
}
45+
46+
type SearchBlock struct {
47+
BlockNumber uint64 `json:"block_number"`
48+
}
49+
50+
type SearchEpoch struct {
51+
Epoch uint64 `json:"epoch"`
52+
}
53+
54+
type SearchToken struct {
55+
Address Address `json:"address"`
56+
Token string `json:"token" tstype:"erc20 | erc721 | erc1155"`
57+
}
58+
3259
type SearchResult struct {
3360
Type string `json:"type"`
3461
ChainId uint64 `json:"chain_id"`
3562
Value interface{} `json:"value"`
3663
}
3764

3865
type InternalPostSearchResponse struct {
39-
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 })[]"`
66+
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 } | { type: 'address'; chain_id: number; value: SearchAddress } | { type: 'transaction'; chain_id: number; value: SearchTransaction } | { type: 'block'; chain_id: number; value: SearchBlock } | { type: 'epoch'; chain_id: number; value: SearchEpoch } | { type: 'token'; chain_id: number; value: SearchToken })[]"`
4067
}

0 commit comments

Comments
 (0)