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 block encoding param to block index api #1752

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 4 additions & 4 deletions api/indexer/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ type Client struct {
// Use a separate type that only decodes the block bytes because we cannot decode block JSON
// due to Actions/Auth interfaces included in the block's transactions.
type GetBlockClientResponse struct {
BlockBytes codec.Bytes `json:"blockBytes"`
BlockBytes codec.Bytes `json:"block"`
}

func (c *Client) GetBlock(ctx context.Context, blkID ids.ID, parser chain.Parser) (*chain.ExecutedBlock, error) {
resp := GetBlockClientResponse{}
err := c.requester.SendRequest(
ctx,
"getBlock",
&GetBlockRequest{BlockID: blkID},
&GetBlockRequest{BlockID: blkID, Encoding: Hex},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not provide the Encoding through variable, I think this client will continue to always use Hex.

&resp,
)
if err != nil {
Expand All @@ -54,7 +54,7 @@ func (c *Client) GetBlockByHeight(ctx context.Context, height uint64, parser cha
err := c.requester.SendRequest(
ctx,
"getBlockByHeight",
&GetBlockByHeightRequest{Height: height},
&GetBlockByHeightRequest{Height: height, Encoding: Hex},
&resp,
)
if err != nil {
Expand All @@ -68,7 +68,7 @@ func (c *Client) GetLatestBlock(ctx context.Context, parser chain.Parser) (*chai
err := c.requester.SendRequest(
ctx,
"getLatestBlock",
nil,
&GetLatestBlockRequest{Encoding: Hex},
&resp,
)
if err != nil {
Expand Down
82 changes: 66 additions & 16 deletions api/indexer/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import (
const Endpoint = "/indexer"

var (
ErrTxNotFound = errors.New("tx not found")
ErrTxNotFound = errors.New("tx not found")
ErrInvalidEncodingParameter = errors.New("invalid encoding parameter")
ErrUnsupportedEncoding = errors.New("unsupported encoding")

_ api.HandlerFactory[api.VM] = (*apiFactory)(nil)
)
Expand All @@ -46,59 +48,83 @@ func (f *apiFactory) New(vm api.VM) (api.Handler, error) {
}

type GetBlockRequest struct {
BlockID ids.ID `json:"blockID"`
BlockID ids.ID `json:"blockID"`
Encoding Encoding `json:"encoding"`
}

type GetBlockByHeightRequest struct {
Height uint64 `json:"height"`
Height uint64 `json:"height"`
Encoding Encoding `json:"encoding"`
}

type GetLatestBlockRequest struct {
Encoding Encoding `json:"encoding"`
}

type GetBlockResponse struct {
Block *chain.ExecutedBlock `json:"block"`
BlockBytes codec.Bytes `json:"blockBytes"`
Block any `json:"block"`
}

func (g *GetBlockResponse) setResponse(block *chain.ExecutedBlock) error {
g.Block = block
blockBytes, err := block.Marshal()
if err != nil {
return err
func (g *GetBlockResponse) setResponse(block *chain.ExecutedBlock, encoding Encoding) error {
switch encoding {
case JSON:
g.Block = block
case Hex:
blockBytes, err := block.Marshal()
if err != nil {
return err
}
g.Block = codec.Bytes(blockBytes)
default:
return ErrUnsupportedEncoding
}
g.BlockBytes = blockBytes
return nil
}

func (s *Server) GetBlock(req *http.Request, args *GetBlockRequest, reply *GetBlockResponse) error {
_, span := s.tracer.Start(req.Context(), "Indexer.GetBlock")
defer span.End()

if err := args.Encoding.Validate(); err != nil {
return err
}

block, err := s.indexer.GetBlock(args.BlockID)
if err != nil {
return err
}
return reply.setResponse(block)

return reply.setResponse(block, args.Encoding)
}

func (s *Server) GetBlockByHeight(req *http.Request, args *GetBlockByHeightRequest, reply *GetBlockResponse) error {
_, span := s.tracer.Start(req.Context(), "Indexer.GetBlockByHeight")
defer span.End()

if err := args.Encoding.Validate(); err != nil {
return err
}

block, err := s.indexer.GetBlockByHeight(args.Height)
if err != nil {
return err
}
return reply.setResponse(block)
return reply.setResponse(block, args.Encoding)
}

func (s *Server) GetLatestBlock(req *http.Request, _ *struct{}, reply *GetBlockResponse) error {
func (s *Server) GetLatestBlock(req *http.Request, args *GetLatestBlockRequest, reply *GetBlockResponse) error {
_, span := s.tracer.Start(req.Context(), "Indexer.GetLatestBlock")
defer span.End()

if err := args.Encoding.Validate(); err != nil {
return err
}

block, err := s.indexer.GetLatestBlock()
if err != nil {
return err
}
return reply.setResponse(block)
return reply.setResponse(block, args.Encoding)
}

type GetTxRequest struct {
Expand All @@ -116,7 +142,7 @@ type GetTxResponse struct {

type Server struct {
tracer trace.Tracer
indexer *Indexer
indexer AbstractIndexer
}

func (s *Server) GetTx(req *http.Request, args *GetTxRequest, reply *GetTxResponse) error {
Expand All @@ -143,3 +169,27 @@ func (s *Server) GetTx(req *http.Request, args *GetTxRequest, reply *GetTxRespon
reply.ErrorStr = errorStr
return nil
}

type Encoding string

var (
JSON Encoding = "json"
Hex Encoding = "hex"
)

func (e *Encoding) Validate() error {
if *e == "" {
*e = JSON
}
if *e != JSON && *e != Hex {
return ErrInvalidEncodingParameter
}
return nil
}

type AbstractIndexer interface {
GetBlock(blkID ids.ID) (*chain.ExecutedBlock, error)
GetBlockByHeight(height uint64) (*chain.ExecutedBlock, error)
GetLatestBlock() (*chain.ExecutedBlock, error)
GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimensions, uint64, [][]byte, string, error)
}
116 changes: 116 additions & 0 deletions api/indexer/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package indexer

import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/trace"
"github.com/stretchr/testify/require"

"github.com/ava-labs/hypersdk/api"
"github.com/ava-labs/hypersdk/chain"
"github.com/ava-labs/hypersdk/chain/chaintest"
"github.com/ava-labs/hypersdk/fees"
)

func TestEncodingValidate(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

tests := []struct {
input Encoding
exp Encoding
expErr error
}{
{
input: JSON,
exp: JSON,
},
{
input: Hex,
exp: Hex,
},
{
input: Encoding(""),
exp: JSON,
},
{
input: Encoding("another"),
expErr: ErrInvalidEncodingParameter,
},
}

for _, test := range tests {
err := test.input.Validate()
if test.expErr != nil {
require.EqualError(t, ErrInvalidEncodingParameter, err.Error())
} else {
require.Equal(t, test.exp, test.input)
}
}
}

func TestBlockRequests(t *testing.T) {
require := require.New(t)
blocks := chaintest.GenerateEmptyExecutedBlocks(require, ids.GenerateTestID(), 0, 0, 0, 1)
server, client := newIndexerHTTPServerAndClient(require, blocks[0])
defer server.Close()

res, err := client.GetBlock(context.Background(), blocks[0].BlockID, chaintest.NewEmptyParser())
require.NoError(err)
require.Equal(blocks[0].BlockID, res.BlockID)

res, err = client.GetBlockByHeight(context.Background(), blocks[0].Block.Hght, chaintest.NewEmptyParser())
require.NoError(err)
require.Equal(blocks[0].BlockID, res.BlockID)

res, err = client.GetLatestBlock(context.Background(), chaintest.NewEmptyParser())
require.NoError(err)
require.Equal(blocks[0].BlockID, res.BlockID)
}

func newIndexerHTTPServerAndClient(require *require.Assertions, block *chain.ExecutedBlock) (*httptest.Server, *Client) {
rpcServer := &Server{
tracer: trace.Noop,
indexer: &mockIndexer{
execuredBlock: block,
},
}
handler, err := api.NewJSONRPCHandler(Name, rpcServer)
require.NoError(err)
mux := http.NewServeMux()
mux.Handle(Endpoint, handler)
testServer := httptest.NewServer(mux)
client := NewClient(testServer.URL)
return testServer, client
}

type mockIndexer struct {
execuredBlock *chain.ExecutedBlock
}

func (m *mockIndexer) GetBlock(blockID ids.ID) (*chain.ExecutedBlock, error) {
if blockID == m.execuredBlock.BlockID {
return m.execuredBlock, nil
}
return nil, errors.New("not found")
}

func (m *mockIndexer) GetBlockByHeight(height uint64) (*chain.ExecutedBlock, error) {
if height == m.execuredBlock.Block.Hght {
return m.execuredBlock, nil
}
return nil, errors.New("not found")
}

func (m *mockIndexer) GetLatestBlock() (*chain.ExecutedBlock, error) {
return m.execuredBlock, nil
}

func (*mockIndexer) GetTransaction(_ ids.ID) (bool, int64, bool, fees.Dimensions, uint64, [][]byte, string, error) {
panic("unimplemented")
}