2023-04-24 08:02:19 -07:00
|
|
|
package aptos
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-resty/resty/v2"
|
2023-06-30 07:25:09 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/metrics"
|
2023-04-24 08:02:19 -07:00
|
|
|
"go.uber.org/ratelimit"
|
|
|
|
)
|
|
|
|
|
|
|
|
var ErrTooManyRequests = fmt.Errorf("too many requests")
|
|
|
|
|
2023-06-30 07:25:09 -07:00
|
|
|
const clientName = "aptos"
|
|
|
|
|
2023-04-24 08:02:19 -07:00
|
|
|
// AptosSDK is a client for the Aptos API.
|
|
|
|
type AptosSDK struct {
|
2023-06-30 07:25:09 -07:00
|
|
|
client *resty.Client
|
|
|
|
rl ratelimit.Limiter
|
|
|
|
metrics metrics.Metrics
|
2023-04-24 08:02:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
type GetLatestBlock struct {
|
|
|
|
BlockHeight string `json:"block_height"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Payload struct {
|
|
|
|
Function string `json:"function"`
|
|
|
|
TypeArguments []string `json:"type_arguments"`
|
|
|
|
Arguments []any `json:"arguments"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Transaction struct {
|
|
|
|
Version string `json:"version"`
|
|
|
|
Hash string `json:"hash"`
|
|
|
|
Payload Payload `json:"payload,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type GetBlockResult struct {
|
|
|
|
BlockHeight string `json:"block_height"`
|
|
|
|
BlockHash string `json:"block_hash"`
|
|
|
|
BlockTimestamp string `json:"block_timestamp"`
|
|
|
|
Transactions []Transaction `json:"transactions"`
|
|
|
|
}
|
|
|
|
|
2023-06-06 14:04:34 -07:00
|
|
|
type GetTransactionResult struct {
|
|
|
|
Version string `json:"version"`
|
|
|
|
Hash string `json:"hash"`
|
|
|
|
StateChangeHash string `json:"state_change_hash"`
|
|
|
|
EventRootHash string `json:"event_root_hash"`
|
|
|
|
StateCheckpointHash any `json:"state_checkpoint_hash"`
|
|
|
|
GasUsed string `json:"gas_used"`
|
|
|
|
Success bool `json:"success"`
|
|
|
|
VMStatus string `json:"vm_status"`
|
|
|
|
}
|
|
|
|
|
2023-04-24 08:02:19 -07:00
|
|
|
func (r *GetBlockResult) GetBlockTime() (*time.Time, error) {
|
|
|
|
t, err := strconv.ParseUint(r.BlockTimestamp, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tm := time.UnixMicro(int64(t))
|
|
|
|
return &tm, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewAptosSDK creates a new AptosSDK.
|
2023-06-30 07:25:09 -07:00
|
|
|
func NewAptosSDK(url string, rl ratelimit.Limiter, metrics metrics.Metrics) *AptosSDK {
|
2023-04-24 08:02:19 -07:00
|
|
|
return &AptosSDK{
|
2023-06-30 07:25:09 -07:00
|
|
|
rl: rl,
|
|
|
|
client: resty.New().SetBaseURL(url),
|
|
|
|
metrics: metrics,
|
2023-04-24 08:02:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *AptosSDK) GetLatestBlock(ctx context.Context) (uint64, error) {
|
|
|
|
s.rl.Take()
|
|
|
|
resp, err := s.client.R().
|
|
|
|
SetContext(ctx).
|
|
|
|
SetResult(&GetLatestBlock{}).
|
|
|
|
Get("v1")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2023-06-30 07:25:09 -07:00
|
|
|
s.metrics.IncRpcRequest(clientName, "get-latest-block", resp.StatusCode())
|
|
|
|
|
2023-04-24 08:02:19 -07:00
|
|
|
if resp.IsError() {
|
|
|
|
return 0, fmt.Errorf("status code: %s. %s", resp.Status(), string(resp.Body()))
|
|
|
|
}
|
|
|
|
|
|
|
|
result := resp.Result().(*GetLatestBlock)
|
|
|
|
if result == nil {
|
|
|
|
return 0, fmt.Errorf("empty response")
|
|
|
|
}
|
|
|
|
if result.BlockHeight == "" {
|
|
|
|
return 0, fmt.Errorf("empty block height")
|
|
|
|
}
|
2023-07-14 10:06:20 -07:00
|
|
|
return strconv.ParseUint(result.BlockHeight, 10, 64)
|
2023-04-24 08:02:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *AptosSDK) GetBlock(ctx context.Context, block uint64) (*GetBlockResult, error) {
|
|
|
|
s.rl.Take()
|
|
|
|
resp, err := s.client.R().
|
|
|
|
SetContext(ctx).
|
|
|
|
SetResult(&GetBlockResult{}).
|
|
|
|
SetQueryParam("with_transactions", "true").
|
|
|
|
Get(fmt.Sprintf("v1/blocks/by_height/%d", block))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-30 07:25:09 -07:00
|
|
|
|
|
|
|
s.metrics.IncRpcRequest(clientName, "get-block", resp.StatusCode())
|
|
|
|
|
2023-04-24 08:02:19 -07:00
|
|
|
if resp.IsError() {
|
|
|
|
if resp.StatusCode() == http.StatusTooManyRequests {
|
|
|
|
return nil, ErrTooManyRequests
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("status code: %s. %s", resp.Status(), string(resp.Body()))
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp.Result().(*GetBlockResult), nil
|
|
|
|
}
|
2023-06-06 14:04:34 -07:00
|
|
|
|
|
|
|
func (s *AptosSDK) GetTransaction(ctx context.Context, version string) (*GetTransactionResult, error) {
|
|
|
|
s.rl.Take()
|
|
|
|
resp, err := s.client.R().
|
|
|
|
SetContext(ctx).
|
|
|
|
SetResult(&GetTransactionResult{}).
|
|
|
|
SetQueryParam("with_transactions", "true").
|
|
|
|
Get(fmt.Sprintf("v1/transactions/by_version/%s", version))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-30 07:25:09 -07:00
|
|
|
s.metrics.IncRpcRequest(clientName, "get-transaction", resp.StatusCode())
|
|
|
|
|
2023-06-06 14:04:34 -07:00
|
|
|
if resp.IsError() {
|
|
|
|
if resp.StatusCode() == http.StatusTooManyRequests {
|
|
|
|
return nil, ErrTooManyRequests
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("status code: %s. %s", resp.Status(), string(resp.Body()))
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp.Result().(*GetTransactionResult), nil
|
|
|
|
}
|