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 }