wormhole-explorer/contract-watcher/internal/terra/client.go

192 lines
4.3 KiB
Go

package terra
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"time"
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/metrics"
"go.uber.org/ratelimit"
)
const clientName = "terra"
// TerraSDK is a client for the Terra blockchain.
type TerraSDK struct {
url string
client *http.Client
rl ratelimit.Limiter
metrics metrics.Metrics
}
// TerraTrx is a transaction on the Terra blockchain.
type TerraTrx struct {
}
// NewTerraSDK creates a new TerraSDK.
func NewTerraSDK(url string, rl ratelimit.Limiter, metrics metrics.Metrics) *TerraSDK {
return &TerraSDK{
url: url,
rl: rl,
client: &http.Client{},
metrics: metrics,
}
}
type LastBlockResponse struct {
Block struct {
Header struct {
Height string `json:"height"`
} `json:"header"`
} `json:"block"`
}
// GetLastBlock returns the last block height.
func (t *TerraSDK) GetLastBlock(ctx context.Context) (int64, error) {
lastBlockURL := fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/blocks/latest", t.url)
req, err := http.NewRequest(http.MethodGet, lastBlockURL, nil)
if err != nil {
return 0, err
}
req.Header.Add("Content-Type", "application/json")
t.rl.Take()
res, err := t.client.Do(req)
if err != nil {
return 0, err
}
defer res.Body.Close()
t.metrics.IncRpcRequest(clientName, "get-last-block", res.StatusCode)
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return 0, err
}
var response LastBlockResponse
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
lastBlockHeight, err := strconv.ParseInt(response.Block.Header.Height, 10, 64)
if err != nil {
return 0, err
}
return lastBlockHeight, nil
}
type TxByBlockResponse struct {
Limit int `json:"limit"`
NextOffset *int `json:"next"`
Txs []Tx `json:"txs"`
}
type Tx struct {
ID int `json:"id"`
Tx any `json:"tx"`
Logs any `json:"logs"`
Code int `json:"code"`
Height string `json:"height"`
Txhash string `json:"txhash"`
RawLog string `json:"raw_log"`
Timestamp *time.Time `json:"timestamp"`
}
type WormholeTerraTx struct {
Type string `json:"type"`
Value struct {
Fee struct {
Gas string `json:"gas"`
Amount []struct {
Denom string `json:"denom"`
Amount string `json:"amount"`
} `json:"amount"`
} `json:"fee"`
Msg []struct {
Type string `json:"type"`
Value struct {
Coins []any `json:"coins"`
Sender string `json:"sender"`
Contract string `json:"contract"`
ExecuteMsg struct {
SubmitVaa struct {
Data []byte `json:"data"`
} `json:"submit_vaa"`
} `json:"execute_msg"`
} `json:"value"`
} `json:"msg"`
Memo string `json:"memo"`
Signatures []struct {
PubKey struct {
Type string `json:"type"`
Value string `json:"value"`
} `json:"pub_key"`
Signature string `json:"signature"`
} `json:"signatures"`
TimeoutHeight string `json:"timeout_height"`
} `json:"value"`
}
type WormholeTerraTxLog struct {
Log struct {
Tax string `json:"tax"`
} `json:"log"`
Events []struct {
Type string `json:"type"`
Attributes []struct {
Key string `json:"key"`
Value string `json:"value"`
} `json:"attributes"`
} `json:"events"`
MsgIndex int `json:"msg_index"`
}
// GetTransactionsByBlockHeight returns the transactions for a given block height.
func (t *TerraSDK) GetTransactionsByBlockHeight(ctx context.Context, height int64, offset *int) (*TxByBlockResponse, error) {
transactionsByBlockURL := fmt.Sprintf("%s/v1/txs", t.url)
req, err := http.NewRequest(http.MethodGet, transactionsByBlockURL, nil)
if err != nil {
return nil, err
}
values := req.URL.Query()
values.Add("block", strconv.FormatInt(height, 10))
values.Add("limit", "100")
if offset != nil {
values.Add("offset", strconv.Itoa(*offset))
}
req.URL.RawQuery = values.Encode()
req.Header.Add("Content-Type", "application/json")
t.rl.Take()
res, err := t.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
t.metrics.IncRpcRequest(clientName, "get-transactions-by-block", res.StatusCode)
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var response TxByBlockResponse
err = json.Unmarshal(body, &response)
if err != nil {
return nil, err
}
return &response, nil
}