github.com/MetalBlockchain/metalgo@v1.11.9/indexer/client.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package indexer
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"github.com/MetalBlockchain/metalgo/ids"
    11  	"github.com/MetalBlockchain/metalgo/utils/formatting"
    12  	"github.com/MetalBlockchain/metalgo/utils/json"
    13  	"github.com/MetalBlockchain/metalgo/utils/rpc"
    14  )
    15  
    16  var _ Client = (*client)(nil)
    17  
    18  // Client interface for Avalanche Indexer API Endpoint
    19  type Client interface {
    20  	// GetContainerRange returns the transactions at index [startIndex], [startIndex+1], ... , [startIndex+n-1]
    21  	// If [n] == 0, returns an empty response (i.e. null).
    22  	// If [startIndex] > the last accepted index, returns an error (unless the above apply.)
    23  	// If we run out of transactions, returns the ones fetched before running out.
    24  	GetContainerRange(ctx context.Context, startIndex uint64, numToFetch int, options ...rpc.Option) ([]Container, error)
    25  	// Get a container by its index
    26  	GetContainerByIndex(ctx context.Context, index uint64, options ...rpc.Option) (Container, error)
    27  	// Get the most recently accepted container and its index
    28  	GetLastAccepted(context.Context, ...rpc.Option) (Container, uint64, error)
    29  	// Returns 1 less than the number of containers accepted on this chain
    30  	GetIndex(ctx context.Context, containerID ids.ID, options ...rpc.Option) (uint64, error)
    31  	// Returns true if the given container is accepted
    32  	IsAccepted(ctx context.Context, containerID ids.ID, options ...rpc.Option) (bool, error)
    33  	// Get a container and its index by its ID
    34  	GetContainerByID(ctx context.Context, containerID ids.ID, options ...rpc.Option) (Container, uint64, error)
    35  }
    36  
    37  // Client implementation for Avalanche Indexer API Endpoint
    38  type client struct {
    39  	requester rpc.EndpointRequester
    40  }
    41  
    42  // NewClient creates a client that can interact with an index via HTTP API
    43  // calls.
    44  // [uri] is the path to make API calls to.
    45  // For example:
    46  //   - http://1.2.3.4:9650/ext/index/C/block
    47  //   - http://1.2.3.4:9650/ext/index/X/tx
    48  func NewClient(uri string) Client {
    49  	return &client{
    50  		requester: rpc.NewEndpointRequester(uri),
    51  	}
    52  }
    53  
    54  func (c *client) GetContainerRange(ctx context.Context, startIndex uint64, numToFetch int, options ...rpc.Option) ([]Container, error) {
    55  	var fcs GetContainerRangeResponse
    56  	err := c.requester.SendRequest(ctx, "index.getContainerRange", &GetContainerRangeArgs{
    57  		StartIndex: json.Uint64(startIndex),
    58  		NumToFetch: json.Uint64(numToFetch),
    59  		Encoding:   formatting.Hex,
    60  	}, &fcs, options...)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	response := make([]Container, len(fcs.Containers))
    66  	for i, resp := range fcs.Containers {
    67  		containerBytes, err := formatting.Decode(resp.Encoding, resp.Bytes)
    68  		if err != nil {
    69  			return nil, fmt.Errorf("couldn't decode container %s: %w", resp.ID, err)
    70  		}
    71  		response[i] = Container{
    72  			ID:        resp.ID,
    73  			Timestamp: resp.Timestamp.Unix(),
    74  			Bytes:     containerBytes,
    75  		}
    76  	}
    77  	return response, nil
    78  }
    79  
    80  func (c *client) GetContainerByIndex(ctx context.Context, index uint64, options ...rpc.Option) (Container, error) {
    81  	var fc FormattedContainer
    82  	err := c.requester.SendRequest(ctx, "index.getContainerByIndex", &GetContainerByIndexArgs{
    83  		Index:    json.Uint64(index),
    84  		Encoding: formatting.Hex,
    85  	}, &fc, options...)
    86  	if err != nil {
    87  		return Container{}, err
    88  	}
    89  
    90  	containerBytes, err := formatting.Decode(fc.Encoding, fc.Bytes)
    91  	if err != nil {
    92  		return Container{}, fmt.Errorf("couldn't decode container %s: %w", fc.ID, err)
    93  	}
    94  	return Container{
    95  		ID:        fc.ID,
    96  		Timestamp: fc.Timestamp.Unix(),
    97  		Bytes:     containerBytes,
    98  	}, nil
    99  }
   100  
   101  func (c *client) GetLastAccepted(ctx context.Context, options ...rpc.Option) (Container, uint64, error) {
   102  	var fc FormattedContainer
   103  	err := c.requester.SendRequest(ctx, "index.getLastAccepted", &GetLastAcceptedArgs{
   104  		Encoding: formatting.Hex,
   105  	}, &fc, options...)
   106  	if err != nil {
   107  		return Container{}, 0, err
   108  	}
   109  
   110  	containerBytes, err := formatting.Decode(fc.Encoding, fc.Bytes)
   111  	if err != nil {
   112  		return Container{}, 0, fmt.Errorf("couldn't decode container %s: %w", fc.ID, err)
   113  	}
   114  	return Container{
   115  		ID:        fc.ID,
   116  		Timestamp: fc.Timestamp.Unix(),
   117  		Bytes:     containerBytes,
   118  	}, uint64(fc.Index), nil
   119  }
   120  
   121  func (c *client) GetIndex(ctx context.Context, id ids.ID, options ...rpc.Option) (uint64, error) {
   122  	var index GetIndexResponse
   123  	err := c.requester.SendRequest(ctx, "index.getIndex", &GetIndexArgs{
   124  		ID: id,
   125  	}, &index, options...)
   126  	return uint64(index.Index), err
   127  }
   128  
   129  func (c *client) IsAccepted(ctx context.Context, id ids.ID, options ...rpc.Option) (bool, error) {
   130  	var res IsAcceptedResponse
   131  	err := c.requester.SendRequest(ctx, "index.isAccepted", &IsAcceptedArgs{
   132  		ID: id,
   133  	}, &res, options...)
   134  	return res.IsAccepted, err
   135  }
   136  
   137  func (c *client) GetContainerByID(ctx context.Context, id ids.ID, options ...rpc.Option) (Container, uint64, error) {
   138  	var fc FormattedContainer
   139  	err := c.requester.SendRequest(ctx, "index.getContainerByID", &GetContainerByIDArgs{
   140  		ID:       id,
   141  		Encoding: formatting.Hex,
   142  	}, &fc, options...)
   143  	if err != nil {
   144  		return Container{}, 0, err
   145  	}
   146  
   147  	containerBytes, err := formatting.Decode(fc.Encoding, fc.Bytes)
   148  	if err != nil {
   149  		return Container{}, 0, fmt.Errorf("couldn't decode container %s: %w", fc.ID, err)
   150  	}
   151  	return Container{
   152  		ID:        fc.ID,
   153  		Timestamp: fc.Timestamp.Unix(),
   154  		Bytes:     containerBytes,
   155  	}, uint64(fc.Index), nil
   156  }