mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
355 lines
12 KiB
Go
355 lines
12 KiB
Go
// Package query provides query functionality for reading blockchain state.
|
|
package query
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
"github.com/cosmos/cosmos-sdk/types/query"
|
|
authTypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
|
|
"github.com/sonr-io/sonr/client/config"
|
|
"github.com/sonr-io/sonr/client/errors"
|
|
)
|
|
|
|
// QueryClient provides an interface for querying blockchain state.
|
|
type QueryClient interface {
|
|
// Chain information
|
|
ChainInfo(ctx context.Context) (*ChainInfo, error)
|
|
NodeInfo(ctx context.Context) (*NodeInfo, error)
|
|
|
|
// Account queries
|
|
Account(ctx context.Context, address string) (*authTypes.QueryAccountResponse, error)
|
|
Accounts(ctx context.Context, pagination *PageRequest) (*authTypes.QueryAccountsResponse, error)
|
|
|
|
// Balance queries
|
|
Balance(ctx context.Context, address, denom string) (*banktypes.QueryBalanceResponse, error)
|
|
AllBalances(ctx context.Context, address string, pagination *PageRequest) (*banktypes.QueryAllBalancesResponse, error)
|
|
TotalSupply(ctx context.Context, pagination *PageRequest) (*banktypes.QueryTotalSupplyResponse, error)
|
|
SupplyOf(ctx context.Context, denom string) (*banktypes.QuerySupplyOfResponse, error)
|
|
|
|
// Staking queries
|
|
Validators(ctx context.Context, status string, pagination *PageRequest) (*stakingtypes.QueryValidatorsResponse, error)
|
|
Validator(ctx context.Context, validatorAddr string) (*stakingtypes.QueryValidatorResponse, error)
|
|
Delegations(ctx context.Context, delegatorAddr string, pagination *PageRequest) (*stakingtypes.QueryDelegatorDelegationsResponse, error)
|
|
Delegation(ctx context.Context, delegatorAddr, validatorAddr string) (*stakingtypes.QueryDelegationResponse, error)
|
|
|
|
// Transaction queries
|
|
Tx(ctx context.Context, hash string) (*TxResponse, error)
|
|
TxsByEvents(ctx context.Context, events []string, pagination *PageRequest) (*TxSearchResponse, error)
|
|
|
|
// Module-specific queries will be handled by module clients
|
|
}
|
|
|
|
// ChainInfo contains basic information about the blockchain.
|
|
type ChainInfo struct {
|
|
ChainID string `json:"chain_id"`
|
|
BlockHeight int64 `json:"block_height"`
|
|
BlockTime string `json:"block_time"`
|
|
NodeVersion string `json:"node_version"`
|
|
ApplicationVersion string `json:"application_version"`
|
|
}
|
|
|
|
// NodeInfo contains information about the connected node.
|
|
type NodeInfo struct {
|
|
NodeID string `json:"node_id"`
|
|
Network string `json:"network"`
|
|
Version string `json:"version"`
|
|
ListenAddr string `json:"listen_addr"`
|
|
Moniker string `json:"moniker"`
|
|
}
|
|
|
|
// TxResponse represents a transaction response.
|
|
type TxResponse struct {
|
|
Hash string `json:"hash"`
|
|
Height int64 `json:"height"`
|
|
Code uint32 `json:"code"`
|
|
Log string `json:"log"`
|
|
GasWanted int64 `json:"gas_wanted"`
|
|
GasUsed int64 `json:"gas_used"`
|
|
Events []Event `json:"events"`
|
|
}
|
|
|
|
// Event represents a transaction event.
|
|
type Event struct {
|
|
Type string `json:"type"`
|
|
Attributes []Attribute `json:"attributes"`
|
|
}
|
|
|
|
// Attribute represents an event attribute.
|
|
type Attribute struct {
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
// TxSearchResponse represents a transaction search response.
|
|
type TxSearchResponse struct {
|
|
Txs []*TxResponse `json:"txs"`
|
|
TotalCount int64 `json:"total_count"`
|
|
}
|
|
|
|
// PageRequest represents pagination parameters.
|
|
type PageRequest struct {
|
|
Key []byte `json:"key,omitempty"`
|
|
Offset uint64 `json:"offset,omitempty"`
|
|
Limit uint64 `json:"limit,omitempty"`
|
|
CountTotal bool `json:"count_total,omitempty"`
|
|
Reverse bool `json:"reverse,omitempty"`
|
|
}
|
|
|
|
// queryClient implements QueryClient.
|
|
type queryClient struct {
|
|
grpcConn *grpc.ClientConn
|
|
config *config.NetworkConfig
|
|
|
|
// Cosmos SDK service clients
|
|
authQueryClient authTypes.QueryClient
|
|
bankQueryClient banktypes.QueryClient
|
|
stakingQueryClient stakingtypes.QueryClient
|
|
}
|
|
|
|
// NewQueryClient creates a new query client.
|
|
func NewQueryClient(grpcConn *grpc.ClientConn, cfg *config.NetworkConfig) (QueryClient, error) {
|
|
if grpcConn == nil {
|
|
return nil, fmt.Errorf("gRPC connection is required")
|
|
}
|
|
|
|
if cfg == nil {
|
|
return nil, fmt.Errorf("network configuration is required")
|
|
}
|
|
|
|
return &queryClient{
|
|
grpcConn: grpcConn,
|
|
config: cfg,
|
|
authQueryClient: authTypes.NewQueryClient(grpcConn),
|
|
bankQueryClient: banktypes.NewQueryClient(grpcConn),
|
|
stakingQueryClient: stakingtypes.NewQueryClient(grpcConn),
|
|
}, nil
|
|
}
|
|
|
|
// ChainInfo retrieves basic chain information.
|
|
func (qc *queryClient) ChainInfo(ctx context.Context) (*ChainInfo, error) {
|
|
// TODO: Implement proper chain info query using Tendermint RPC client
|
|
// Should query /status endpoint for current block height and time
|
|
// Get node version and application version from /abci_info
|
|
// Return comprehensive chain information with real-time data
|
|
return &ChainInfo{
|
|
ChainID: qc.config.ChainID,
|
|
NodeVersion: "unknown",
|
|
ApplicationVersion: "unknown",
|
|
}, nil
|
|
}
|
|
|
|
// NodeInfo retrieves node information.
|
|
func (qc *queryClient) NodeInfo(ctx context.Context) (*NodeInfo, error) {
|
|
// TODO: Implement proper node info query using Tendermint RPC client
|
|
// Should query /status endpoint for node ID and network info
|
|
// Get listen address and moniker from node status
|
|
// Include peer count and sync status information
|
|
return &NodeInfo{
|
|
NodeID: "unknown",
|
|
Network: qc.config.ChainID,
|
|
Version: "unknown",
|
|
}, nil
|
|
}
|
|
|
|
// Account retrieves account information by address.
|
|
func (qc *queryClient) Account(ctx context.Context, address string) (*authTypes.QueryAccountResponse, error) {
|
|
req := &authTypes.QueryAccountRequest{Address: address}
|
|
|
|
resp, err := qc.authQueryClient.Account(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query account %s", address)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Accounts retrieves all accounts with pagination.
|
|
func (qc *queryClient) Accounts(ctx context.Context, pagination *PageRequest) (*authTypes.QueryAccountsResponse, error) {
|
|
req := &authTypes.QueryAccountsRequest{}
|
|
|
|
if pagination != nil {
|
|
req.Pagination = &query.PageRequest{
|
|
Key: pagination.Key,
|
|
Offset: pagination.Offset,
|
|
Limit: pagination.Limit,
|
|
CountTotal: pagination.CountTotal,
|
|
Reverse: pagination.Reverse,
|
|
}
|
|
}
|
|
|
|
resp, err := qc.authQueryClient.Accounts(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query accounts")
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Balance retrieves the balance of a specific denomination for an address.
|
|
func (qc *queryClient) Balance(ctx context.Context, address, denom string) (*banktypes.QueryBalanceResponse, error) {
|
|
req := &banktypes.QueryBalanceRequest{
|
|
Address: address,
|
|
Denom: denom,
|
|
}
|
|
|
|
resp, err := qc.bankQueryClient.Balance(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query balance for %s", address)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// AllBalances retrieves all balances for an address.
|
|
func (qc *queryClient) AllBalances(ctx context.Context, address string, pagination *PageRequest) (*banktypes.QueryAllBalancesResponse, error) {
|
|
req := &banktypes.QueryAllBalancesRequest{Address: address}
|
|
|
|
if pagination != nil {
|
|
req.Pagination = &query.PageRequest{
|
|
Key: pagination.Key,
|
|
Offset: pagination.Offset,
|
|
Limit: pagination.Limit,
|
|
CountTotal: pagination.CountTotal,
|
|
Reverse: pagination.Reverse,
|
|
}
|
|
}
|
|
|
|
resp, err := qc.bankQueryClient.AllBalances(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query all balances for %s", address)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// TotalSupply retrieves the total supply of all denominations.
|
|
func (qc *queryClient) TotalSupply(ctx context.Context, pagination *PageRequest) (*banktypes.QueryTotalSupplyResponse, error) {
|
|
req := &banktypes.QueryTotalSupplyRequest{}
|
|
|
|
if pagination != nil {
|
|
req.Pagination = &query.PageRequest{
|
|
Key: pagination.Key,
|
|
Offset: pagination.Offset,
|
|
Limit: pagination.Limit,
|
|
CountTotal: pagination.CountTotal,
|
|
Reverse: pagination.Reverse,
|
|
}
|
|
}
|
|
|
|
resp, err := qc.bankQueryClient.TotalSupply(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query total supply")
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// SupplyOf retrieves the supply of a specific denomination.
|
|
func (qc *queryClient) SupplyOf(ctx context.Context, denom string) (*banktypes.QuerySupplyOfResponse, error) {
|
|
req := &banktypes.QuerySupplyOfRequest{Denom: denom}
|
|
|
|
resp, err := qc.bankQueryClient.SupplyOf(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query supply of %s", denom)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Validators retrieves validators with optional status filter.
|
|
func (qc *queryClient) Validators(ctx context.Context, status string, pagination *PageRequest) (*stakingtypes.QueryValidatorsResponse, error) {
|
|
req := &stakingtypes.QueryValidatorsRequest{Status: status}
|
|
|
|
if pagination != nil {
|
|
req.Pagination = &query.PageRequest{
|
|
Key: pagination.Key,
|
|
Offset: pagination.Offset,
|
|
Limit: pagination.Limit,
|
|
CountTotal: pagination.CountTotal,
|
|
Reverse: pagination.Reverse,
|
|
}
|
|
}
|
|
|
|
resp, err := qc.stakingQueryClient.Validators(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query validators")
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Validator retrieves a specific validator by address.
|
|
func (qc *queryClient) Validator(ctx context.Context, validatorAddr string) (*stakingtypes.QueryValidatorResponse, error) {
|
|
req := &stakingtypes.QueryValidatorRequest{ValidatorAddr: validatorAddr}
|
|
|
|
resp, err := qc.stakingQueryClient.Validator(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query validator %s", validatorAddr)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Delegations retrieves all delegations for a delegator.
|
|
func (qc *queryClient) Delegations(ctx context.Context, delegatorAddr string, pagination *PageRequest) (*stakingtypes.QueryDelegatorDelegationsResponse, error) {
|
|
req := &stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: delegatorAddr}
|
|
|
|
if pagination != nil {
|
|
req.Pagination = &query.PageRequest{
|
|
Key: pagination.Key,
|
|
Offset: pagination.Offset,
|
|
Limit: pagination.Limit,
|
|
CountTotal: pagination.CountTotal,
|
|
Reverse: pagination.Reverse,
|
|
}
|
|
}
|
|
|
|
resp, err := qc.stakingQueryClient.DelegatorDelegations(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query delegations for %s", delegatorAddr)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Delegation retrieves a specific delegation.
|
|
func (qc *queryClient) Delegation(ctx context.Context, delegatorAddr, validatorAddr string) (*stakingtypes.QueryDelegationResponse, error) {
|
|
req := &stakingtypes.QueryDelegationRequest{
|
|
DelegatorAddr: delegatorAddr,
|
|
ValidatorAddr: validatorAddr,
|
|
}
|
|
|
|
resp, err := qc.stakingQueryClient.Delegation(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query delegation from %s to %s", delegatorAddr, validatorAddr)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Tx retrieves a transaction by hash.
|
|
func (qc *queryClient) Tx(ctx context.Context, hash string) (*TxResponse, error) {
|
|
// TODO: Implement transaction query using Tendermint RPC client
|
|
// Should query /tx endpoint with transaction hash
|
|
// Parse transaction result and decode events
|
|
// Return formatted transaction response with gas usage
|
|
// Handle transaction not found errors gracefully
|
|
return nil, fmt.Errorf("transaction queries not yet implemented")
|
|
}
|
|
|
|
// TxsByEvents retrieves transactions by events.
|
|
func (qc *queryClient) TxsByEvents(ctx context.Context, events []string, pagination *PageRequest) (*TxSearchResponse, error) {
|
|
// TODO: Implement transaction search using Tendermint RPC client
|
|
// Should query /tx_search endpoint with event filters
|
|
// Support pagination with page and per_page parameters
|
|
// Parse and format transaction results with event data
|
|
// Handle complex event queries with AND/OR logic
|
|
return nil, fmt.Errorf("transaction search not yet implemented")
|
|
}
|