This commit is contained in:
0xmuralik
2022-10-12 13:04:44 +05:30
parent 11e16a01a8
commit 8d29064d08
16 changed files with 10420 additions and 15 deletions
+353
View File
@@ -0,0 +1,353 @@
# Cerc-io laconic gql
> Browser : http://localhost:9473 for gql
## Start server
```shell
./build/laconicd start --gql-playground --gql-server
```
Basic node status:
```graphql
{
getStatus {
version
node {
id
network
moniker
}
sync {
latest_block_height
catching_up
}
num_peers
peers {
is_outbound
remote_ip
}
disk_usage
}
}
```
Full node status:
```graphql
{
getStatus {
version
node {
id
network
moniker
}
sync {
latest_block_hash
latest_block_time
latest_block_height
catching_up
}
validator {
address
voting_power
proposer_priority
}
validators {
address
voting_power
proposer_priority
}
num_peers
peers {
node {
id
network
moniker
}
is_outbound
remote_ip
}
disk_usage
}
}
```
Get records by IDs.
```graphql
{
getRecordsByIds(ids: ["QmYDtNCKtTu6u6jaHaFAC5PWZXcj7fAmry6NoWwMaixFHz"]) {
id
names
bondId
createTime
expiryTime
owners
attributes {
key
value {
string
}
}
}
}
```
Query records.
```graphql
{
queryRecords(attributes: [{ key: "type", value: { string: "crn:bot" } }]) {
id
names
bondId
createTime
expiryTime
owners
attributes {
key
value {
string
}
}
}
}
```
Get account details:
```graphql
{
getAccounts(addresses: ["cosmos1wh8vvd0ymc5nt37h29z8kk2g2ays45ct2qu094"]) {
address
pubKey
number
sequence
balance {
type
quantity
}
}
}
```
Query bonds:
```graphql
{
queryBonds(
attributes: [
{
key: "owner"
value: { string: "cosmos1wh8vvd0ymc5nt37h29z8kk2g2ays45ct2qu094" }
}
]
) {
id
owner
balance {
type
quantity
}
}
}
```
Get bonds by IDs.
```graphql
{
getBondsByIds(
ids: [
"1c2b677cb2a27c88cc6bf8acf675c94b69051125b40c4fd073153b10f046dd87"
"c3f7a78c5042d2003880962ba31ff3b01fcf5942960e0bc3ca331f816346a440"
]
) {
id
owner
balance {
type
quantity
}
}
}
```
Query Bonds by Owner
```graphql
{
queryBondsByOwner(
ownerAddresses: ["ethm1mfdjngh5jvjs9lqtt9a7y2hlgw8v3syh3hsqzk"]
) {
owner
bonds {
id
owner
balance {
type
quantity
}
}
}
}
```
Query auctions by ids
```graphql
{
getAuctionsByIds(
ids: ["be98f2073c246194276554eefdb4c95b682a35a0f06fbe619a6da57c10c93e90"]
) {
id
ownerAddress
createTime
minimumBid {
type
quantity
}
commitFee {
type
quantity
}
commitsEndTime
revealFee {
type
quantity
}
revealsEndTime
winnerBid {
type
quantity
}
winnerPrice {
type
quantity
}
winnerAddress
bids {
bidderAddress
commitHash
commitTime
commitFee {
type
quantity
}
revealFee {
type
quantity
}
revealTime
bidAmount {
type
quantity
}
status
}
}
}
```
LookUp Authorities
```graphql
{
lookupAuthorities(names: []) {
ownerAddress
ownerAddress
height
bondId
status
expiryTime
auction {
id
ownerAddress
createTime
minimumBid {
type
quantity
}
commitFee {
type
quantity
}
commitsEndTime
revealFee {
type
quantity
}
revealsEndTime
winnerBid {
type
quantity
}
winnerPrice {
type
quantity
}
winnerAddress
bids {
bidderAddress
commitHash
commitTime
commitFee {
type
quantity
}
revealFee {
type
quantity
}
revealTime
bidAmount {
type
quantity
}
status
}
}
}
}
```
LookUp Names
```graphql
{
lookupNames(names: ["crn://hello/test"]) {
latest {
id
height
}
history {
id
height
}
}
}
```
Resolve Names
```graphql
{
resolveNames(names: ["asd"]) {
id
names
bondId
createTime
expiryTime
owners
attributes {
key
value {
string
}
}
}
}
```
+8781
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
# .gqlgen.yml example
#
# Refer to https://gqlgen.com/config/
# for detailed .gqlgen.yml documentation.
schema:
- cerc-io/laconicd/*.graphql
exec:
filename: generated.go
model:
filename: models_gen.go
resolver:
filename: resolver.go
type: Resolver
+160
View File
@@ -0,0 +1,160 @@
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
package gql
type Account struct {
Address string `json:"address"`
PubKey *string `json:"pubKey"`
Number string `json:"number"`
Sequence string `json:"sequence"`
Balance []*Coin `json:"balance"`
}
type Auction struct {
ID string `json:"id"`
Status string `json:"status"`
OwnerAddress string `json:"ownerAddress"`
CreateTime string `json:"createTime"`
CommitsEndTime string `json:"commitsEndTime"`
RevealsEndTime string `json:"revealsEndTime"`
CommitFee *Coin `json:"commitFee"`
RevealFee *Coin `json:"revealFee"`
MinimumBid *Coin `json:"minimumBid"`
WinnerAddress string `json:"winnerAddress"`
WinnerBid *Coin `json:"winnerBid"`
WinnerPrice *Coin `json:"winnerPrice"`
Bids []*AuctionBid `json:"bids"`
}
type AuctionBid struct {
BidderAddress string `json:"bidderAddress"`
Status string `json:"status"`
CommitHash string `json:"commitHash"`
CommitTime string `json:"commitTime"`
CommitFee *Coin `json:"commitFee"`
RevealTime string `json:"revealTime"`
RevealFee *Coin `json:"revealFee"`
BidAmount *Coin `json:"bidAmount"`
}
type AuthorityRecord struct {
OwnerAddress string `json:"ownerAddress"`
OwnerPublicKey string `json:"ownerPublicKey"`
Height string `json:"height"`
Status string `json:"status"`
BondID string `json:"bondId"`
ExpiryTime string `json:"expiryTime"`
Auction *Auction `json:"auction"`
}
type Bond struct {
ID string `json:"id"`
Owner string `json:"owner"`
Balance []*Coin `json:"balance"`
}
type Coin struct {
Type string `json:"type"`
Quantity string `json:"quantity"`
}
type KeyValue struct {
Key string `json:"key"`
Value *Value `json:"value"`
}
type KeyValueInput struct {
Key string `json:"key"`
Value *ValueInput `json:"value"`
}
type NameRecord struct {
Latest *NameRecordEntry `json:"latest"`
History []*NameRecordEntry `json:"history"`
}
type NameRecordEntry struct {
ID string `json:"id"`
Height string `json:"height"`
}
type NodeInfo struct {
ID string `json:"id"`
Network string `json:"network"`
Moniker string `json:"moniker"`
}
type OwnerBonds struct {
Owner string `json:"owner"`
Bonds []*Bond `json:"bonds"`
}
type PeerInfo struct {
Node *NodeInfo `json:"node"`
IsOutbound bool `json:"is_outbound"`
RemoteIP string `json:"remote_ip"`
}
type Record struct {
ID string `json:"id"`
Names []string `json:"names"`
BondID string `json:"bondId"`
CreateTime string `json:"createTime"`
ExpiryTime string `json:"expiryTime"`
Owners []string `json:"owners"`
Attributes []*KeyValue `json:"attributes"`
References []*Record `json:"references"`
}
type Reference struct {
ID string `json:"id"`
}
type ReferenceInput struct {
ID string `json:"id"`
}
type Status struct {
Version string `json:"version"`
Node *NodeInfo `json:"node"`
Sync *SyncInfo `json:"sync"`
Validator *ValidatorInfo `json:"validator"`
Validators []*ValidatorInfo `json:"validators"`
NumPeers string `json:"num_peers"`
Peers []*PeerInfo `json:"peers"`
DiskUsage string `json:"disk_usage"`
}
type SyncInfo struct {
LatestBlockHash string `json:"latest_block_hash"`
LatestBlockHeight string `json:"latest_block_height"`
LatestBlockTime string `json:"latest_block_time"`
CatchingUp bool `json:"catching_up"`
}
type ValidatorInfo struct {
Address string `json:"address"`
VotingPower string `json:"voting_power"`
ProposerPriority *string `json:"proposer_priority"`
}
type Value struct {
Null *bool `json:"null"`
Int *int `json:"int"`
Float *float64 `json:"float"`
String *string `json:"string"`
Boolean *bool `json:"boolean"`
JSON *string `json:"json"`
Reference *Reference `json:"reference"`
Values []*Value `json:"values"`
}
type ValueInput struct {
Null *bool `json:"null"`
Int *int `json:"int"`
Float *float64 `json:"float"`
String *string `json:"string"`
Boolean *bool `json:"boolean"`
Reference *ReferenceInput `json:"reference"`
Values []*ValueInput `json:"values"`
}
+348
View File
@@ -0,0 +1,348 @@
package gql
import (
"context"
"encoding/base64"
"strconv"
auctiontypes "github.com/cerc-io/laconicd/x/auction/types"
bondtypes "github.com/cerc-io/laconicd/x/bond/types"
nstypes "github.com/cerc-io/laconicd/x/nameservice/types"
"github.com/cosmos/cosmos-sdk/client"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
// DefaultLogNumLines is the number of log lines to tail by default.
const DefaultLogNumLines = 50
// MaxLogNumLines is the max number of log lines that can be tailed.
const MaxLogNumLines = 1000
type Resolver struct {
ctx client.Context
logFile string
}
// Query is the entry point to query execution.
func (r *Resolver) Query() QueryResolver {
return &queryResolver{r}
}
type queryResolver struct{ *Resolver }
func (q queryResolver) LookupAuthorities(ctx context.Context, names []string) ([]*AuthorityRecord, error) {
nsQueryClient := nstypes.NewQueryClient(q.ctx)
auctionQueryClient := auctiontypes.NewQueryClient(q.ctx)
var gqlResponse []*AuthorityRecord
for _, name := range names {
res, err := nsQueryClient.Whois(context.Background(), &nstypes.QueryWhoisRequest{Name: name})
if err != nil {
return nil, err
}
nameAuthority := res.GetNameAuthority()
gqlNameAuthorityRecord, err := GetGQLNameAuthorityRecord(&nameAuthority)
if err != nil {
return nil, err
}
if nameAuthority.AuctionId != "" {
auctionResp, err := auctionQueryClient.GetAuction(context.Background(), &auctiontypes.AuctionRequest{Id: nameAuthority.GetAuctionId()})
if err != nil {
return nil, err
}
bidsResp, err := auctionQueryClient.GetBids(context.Background(), &auctiontypes.BidsRequest{AuctionId: nameAuthority.GetAuctionId()})
if err != nil {
return nil, err
}
gqlAuctionRecord, err := GetGQLAuction(auctionResp.GetAuction(), bidsResp.GetBids())
if err != nil {
return nil, err
}
gqlNameAuthorityRecord.Auction = gqlAuctionRecord
}
gqlResponse = append(gqlResponse, gqlNameAuthorityRecord)
}
return gqlResponse, nil
}
func (q queryResolver) ResolveNames(ctx context.Context, names []string) ([]*Record, error) {
nsQueryClient := nstypes.NewQueryClient(q.ctx)
var gqlResponse []*Record
for _, name := range names {
res, err := nsQueryClient.ResolveCrn(context.Background(), &nstypes.QueryResolveCrn{Crn: name})
if err != nil {
// Return nil for record not found.
gqlResponse = append(gqlResponse, nil)
} else {
gqlRecord, err := getGQLRecord(context.Background(), q, *res.GetRecord())
if err != nil {
return nil, err
}
gqlResponse = append(gqlResponse, gqlRecord)
}
}
return gqlResponse, nil
}
func (q queryResolver) LookupNames(ctx context.Context, names []string) ([]*NameRecord, error) {
nsQueryClient := nstypes.NewQueryClient(q.ctx)
var gqlResponse []*NameRecord
for _, name := range names {
res, err := nsQueryClient.LookupCrn(context.Background(), &nstypes.QueryLookupCrn{Crn: name})
if err != nil {
// Return nil for name not found.
gqlResponse = append(gqlResponse, nil)
} else {
gqlRecord, err := getGQLNameRecord(res.GetName())
if err != nil {
return nil, err
}
gqlResponse = append(gqlResponse, gqlRecord)
}
}
return gqlResponse, nil
}
func (q queryResolver) QueryRecords(ctx context.Context, attributes []*KeyValueInput, all *bool) ([]*Record, error) {
nsQueryClient := nstypes.NewQueryClient(q.ctx)
res, err := nsQueryClient.ListRecords(
context.Background(),
&nstypes.QueryListRecordsRequest{
Attributes: parseRequestAttributes(attributes),
All: (all != nil && *all),
},
)
if err != nil {
return nil, err
}
records := res.GetRecords()
gqlResponse := make([]*Record, len(records))
for i, record := range records {
gqlRecord, err := getGQLRecord(context.Background(), q, record)
if err != nil {
return nil, err
}
gqlResponse[i] = gqlRecord
}
return gqlResponse, nil
}
func (q queryResolver) GetRecordsByIds(ctx context.Context, ids []string) ([]*Record, error) {
nsQueryClient := nstypes.NewQueryClient(q.ctx)
gqlResponse := make([]*Record, len(ids))
for i, id := range ids {
res, err := nsQueryClient.GetRecord(context.Background(), &nstypes.QueryRecordByIdRequest{Id: id})
if err != nil {
// Return nil for record not found.
gqlResponse[i] = nil
} else {
record, err := getGQLRecord(context.Background(), q, res.GetRecord())
if err != nil {
return nil, err
}
gqlResponse[i] = record
}
}
return gqlResponse, nil
}
func (q queryResolver) GetStatus(ctx context.Context) (*Status, error) {
nodeInfo, syncInfo, validatorInfo, err := getStatusInfo(q.ctx)
if err != nil {
return nil, err
}
numPeers, peers, err := getNetInfo(q.ctx)
if err != nil {
return nil, err
}
validatorSet, err := getValidatorSet(q.ctx)
if err != nil {
return nil, err
}
diskUsage, err := GetDiskUsage(NodeDataPath)
if err != nil {
return nil, err
}
return &Status{
Version: NameServiceVersion,
Node: nodeInfo,
Sync: syncInfo,
Validator: validatorInfo,
Validators: validatorSet,
NumPeers: numPeers,
Peers: peers,
DiskUsage: diskUsage,
}, nil
}
func (q queryResolver) GetAccounts(ctx context.Context, addresses []string) ([]*Account, error) {
accounts := make([]*Account, len(addresses))
for index, address := range addresses {
account, err := q.GetAccount(ctx, address)
if err != nil {
return nil, err
}
accounts[index] = account
}
return accounts, nil
}
func (q queryResolver) GetAccount(ctx context.Context, address string) (*Account, error) {
authQueryClient := authtypes.NewQueryClient(q.ctx)
accountResponse, err := authQueryClient.Account(ctx, &authtypes.QueryAccountRequest{Address: address})
if err != nil {
return nil, err
}
var account authtypes.AccountI
err = q.ctx.Codec.UnpackAny(accountResponse.GetAccount(), &account)
if err != nil {
return nil, err
}
var pubKey *string
if account.GetPubKey() != nil {
pubKeyStr := base64.StdEncoding.EncodeToString(account.GetPubKey().Bytes())
pubKey = &pubKeyStr
}
// Get the account balance
bankQueryClient := banktypes.NewQueryClient(q.ctx)
balance, err := bankQueryClient.AllBalances(ctx, &banktypes.QueryAllBalancesRequest{Address: address})
accNum := strconv.FormatUint(account.GetAccountNumber(), 10)
seq := strconv.FormatUint(account.GetSequence(), 10)
return &Account{
Address: address,
Number: accNum,
Sequence: seq,
PubKey: pubKey,
Balance: getGQLCoins(balance.GetBalances()),
}, nil
}
func (q queryResolver) GetBondsByIds(ctx context.Context, ids []string) ([]*Bond, error) {
bonds := make([]*Bond, len(ids))
for index, id := range ids {
bondObj, err := q.GetBond(ctx, id)
if err != nil {
return nil, err
}
bonds[index] = bondObj
}
return bonds, nil
}
func (q *queryResolver) GetBond(ctx context.Context, id string) (*Bond, error) {
bondQueryClient := bondtypes.NewQueryClient(q.ctx)
bondResp, err := bondQueryClient.GetBondById(context.Background(), &bondtypes.QueryGetBondByIdRequest{Id: id})
if err != nil {
return nil, err
}
bond := bondResp.GetBond()
if bond == nil {
return nil, nil
}
return getGQLBond(bondResp.GetBond())
}
func (q queryResolver) QueryBonds(ctx context.Context, attributes []*KeyValueInput) ([]*Bond, error) {
bondQueryClient := bondtypes.NewQueryClient(q.ctx)
bonds, err := bondQueryClient.Bonds(context.Background(), &bondtypes.QueryGetBondsRequest{})
if err != nil {
return nil, err
}
gqlResponse := make([]*Bond, len(bonds.GetBonds()))
for i, bondObj := range bonds.GetBonds() {
gqlBond, err := getGQLBond(bondObj)
if err != nil {
return nil, err
}
gqlResponse[i] = gqlBond
}
return gqlResponse, nil
}
// QueryBondsByOwner will return bonds by owner
func (q queryResolver) QueryBondsByOwner(ctx context.Context, ownerAddresses []string) ([]*OwnerBonds, error) {
ownerBonds := make([]*OwnerBonds, len(ownerAddresses))
for index, ownerAddress := range ownerAddresses {
bondsObj, err := q.GetBondsByOwner(ctx, ownerAddress)
if err != nil {
return nil, err
}
ownerBonds[index] = bondsObj
}
return ownerBonds, nil
}
func (q queryResolver) GetBondsByOwner(ctx context.Context, address string) (*OwnerBonds, error) {
bondQueryClient := bondtypes.NewQueryClient(q.ctx)
bondResp, err := bondQueryClient.GetBondsByOwner(context.Background(), &bondtypes.QueryGetBondsByOwnerRequest{Owner: address})
if err != nil {
return nil, err
}
ownerBonds := make([]*Bond, len(bondResp.GetBonds()))
for i, bond := range bondResp.GetBonds() {
bondObj, err := getGQLBond(&bond)
if err != nil {
return nil, err
}
ownerBonds[i] = bondObj
}
return &OwnerBonds{Bonds: ownerBonds, Owner: address}, nil
}
func (q queryResolver) GetAuctionsByIds(ctx context.Context, ids []string) ([]*Auction, error) {
auctionQueryClient := auctiontypes.NewQueryClient(q.ctx)
gqlAuctionResponse := make([]*Auction, len(ids))
for i, id := range ids {
auctionObj, err := auctionQueryClient.GetAuction(context.Background(), &auctiontypes.AuctionRequest{Id: id})
if err != nil {
return nil, err
}
bidsObj, err := auctionQueryClient.GetBids(context.Background(), &auctiontypes.BidsRequest{AuctionId: id})
if err != nil {
return nil, err
}
gqlAuction, err := GetGQLAuction(auctionObj.GetAuction(), bidsObj.GetBids())
if err != nil {
return nil, err
}
gqlAuctionResponse[i] = gqlAuction
}
return gqlAuctionResponse, nil
}
+45
View File
@@ -0,0 +1,45 @@
package gql
import (
"fmt"
"net/http"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/cosmos/cosmos-sdk/client"
"github.com/ethereum/go-ethereum/log"
"github.com/spf13/viper"
)
// Server configures and starts the GQL server.
func Server(ctx client.Context) {
if !viper.GetBool("gql-server") {
return
}
logFile := viper.GetString("log-file")
port := viper.GetString("gql-port")
srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: &Resolver{
ctx: ctx,
logFile: logFile,
}}))
http.Handle("/", playground.Handler("GraphQL playground", "/api"))
if viper.GetBool("gql-playground") {
apiBase := viper.GetString("gql-playground-api-base")
http.Handle("/webui", playground.Handler("GraphQL playground", apiBase+"/api"))
http.Handle("/console", playground.Handler("GraphQL playground", apiBase+"/graphql"))
}
http.Handle("/api", srv)
http.Handle("/graphql", srv)
log.Info("Connect to GraphQL playground", "url", fmt.Sprintf("http://localhost:%s", port))
err := http.ListenAndServe(":"+port, nil)
if err != nil {
panic(err)
}
}
+100
View File
@@ -0,0 +1,100 @@
package gql
import (
"context"
"os"
"os/exec"
"strconv"
"strings"
"github.com/cosmos/cosmos-sdk/client"
)
// NodeDataPath is the path to the laconicd data folder.
var NodeDataPath = os.ExpandEnv("$HOME/.laconicd/data")
func getStatusInfo(client client.Context) (*NodeInfo, *SyncInfo, *ValidatorInfo, error) {
nodeClient, err := client.GetNode()
if err != nil {
return nil, nil, nil, err
}
nodeStatus, err := nodeClient.Status(context.Background())
if err != nil {
return nil, nil, nil, err
}
return &NodeInfo{
ID: string(nodeStatus.NodeInfo.ID()),
Network: nodeStatus.NodeInfo.Network,
Moniker: nodeStatus.NodeInfo.Moniker,
}, &SyncInfo{
LatestBlockHash: nodeStatus.SyncInfo.LatestBlockHash.String(),
LatestBlockHeight: strconv.FormatInt(nodeStatus.SyncInfo.LatestBlockHeight, 10),
LatestBlockTime: nodeStatus.SyncInfo.LatestBlockTime.String(),
CatchingUp: nodeStatus.SyncInfo.CatchingUp,
}, &ValidatorInfo{
Address: nodeStatus.ValidatorInfo.Address.String(),
VotingPower: strconv.FormatInt(nodeStatus.ValidatorInfo.VotingPower, 10),
ProposerPriority: nil,
}, nil
}
func getNetInfo(client client.Context) (string, []*PeerInfo, error) {
nodeClient, err := client.GetNode()
if err != nil {
return "", nil, err
}
netInfo, err := nodeClient.NetInfo(context.Background())
if err != nil {
return "", nil, err
}
peersInfo := make([]*PeerInfo, netInfo.NPeers)
// TODO: find a way to get the peer information from nodeClient
for index, peer := range netInfo.Peers {
peersInfo[index] = &PeerInfo{
Node: &NodeInfo{
ID: string(peer.NodeInfo.ID()),
// Moniker: peer.Node.Moniker,
// Network: peer.Node.Network,
},
// IsOutbound: peer.IsOutbound,
// RemoteIP: peer.RemoteIP,
}
}
return strconv.FormatInt(int64(netInfo.NPeers), 10), peersInfo, nil
}
func getValidatorSet(client client.Context) ([]*ValidatorInfo, error) {
nodeClient, err := client.GetNode()
if err != nil {
return nil, err
}
res, err := nodeClient.Validators(context.Background(), nil, nil, nil)
if err != nil {
return nil, err
}
validatorSet := make([]*ValidatorInfo, len(res.Validators))
for index, validator := range res.Validators {
proposerPriority := strconv.FormatInt(validator.ProposerPriority, 10)
validatorSet[index] = &ValidatorInfo{
Address: validator.Address.String(),
VotingPower: strconv.FormatInt(validator.VotingPower, 10),
ProposerPriority: &proposerPriority,
}
}
return validatorSet, nil
}
// GetDiskUsage returns disk usage for the given path.
func GetDiskUsage(dirPath string) (string, error) {
out, err := exec.Command("du", "-sh", dirPath).Output()
if err != nil {
return "", err
}
return strings.Fields(string(out))[0], nil
}
+315
View File
@@ -0,0 +1,315 @@
package gql
import (
"context"
"encoding/json"
"reflect"
"strconv"
auctiontypes "github.com/cerc-io/laconicd/x/auction/types"
bondtypes "github.com/cerc-io/laconicd/x/bond/types"
nstypes "github.com/cerc-io/laconicd/x/nameservice/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// OwnerAttributeName denotes the owner attribute name for a bond.
const OwnerAttributeName = "owner"
// BondIDAttributeName denotes the record bond ID.
const BondIDAttributeName = "bondId"
// ExpiryTimeAttributeName denotes the record expiry time.
const ExpiryTimeAttributeName = "expiryTime"
func getGQLCoin(coin sdk.Coin) *Coin {
gqlCoin := Coin{
Type: coin.Denom,
Quantity: coin.Amount.BigInt().String(),
}
return &gqlCoin
}
func getGQLCoins(coins sdk.Coins) []*Coin {
gqlCoins := make([]*Coin, len(coins))
for index, coin := range coins {
gqlCoins[index] = getGQLCoin(coin)
}
return gqlCoins
}
func GetGQLNameAuthorityRecord(record *nstypes.NameAuthority) (*AuthorityRecord, error) {
if record == nil {
return nil, nil
}
return &AuthorityRecord{
OwnerAddress: record.OwnerAddress,
OwnerPublicKey: record.OwnerPublicKey,
Height: strconv.FormatUint(record.Height, 10),
Status: record.Status,
BondID: record.GetBondId(),
ExpiryTime: record.GetExpiryTime().String(),
}, nil
}
func getGQLRecord(ctx context.Context, resolver QueryResolver, record nstypes.Record) (*Record, error) {
// Nil record.
if record.Deleted {
return nil, nil
}
recordType := record.ToRecordType()
attributes, err := getAttributes(&recordType)
if err != nil {
return nil, err
}
references, err := getReferences(ctx, resolver, &recordType)
if err != nil {
return nil, err
}
return &Record{
ID: record.Id,
BondID: record.GetBondId(),
CreateTime: record.GetCreateTime(),
ExpiryTime: record.GetExpiryTime(),
Owners: record.GetOwners(),
Names: record.GetNames(),
Attributes: attributes,
References: references,
}, nil
}
func getGQLNameRecord(record *nstypes.NameRecord) (*NameRecord, error) {
if record == nil {
return nil, nil
}
records := make([]*NameRecordEntry, len(record.History))
for index, entry := range record.History {
records[index] = getNameRecordEntry(entry)
}
return &NameRecord{
Latest: getNameRecordEntry(record.Latest),
History: records,
}, nil
}
func getNameRecordEntry(record *nstypes.NameRecordEntry) *NameRecordEntry {
return &NameRecordEntry{
ID: record.Id,
Height: strconv.FormatUint(record.Height, 10),
}
}
func getGQLBond(bondObj *bondtypes.Bond) (*Bond, error) {
// Nil record.
if bondObj == nil {
return nil, nil
}
return &Bond{
ID: bondObj.Id,
Owner: bondObj.Owner,
Balance: getGQLCoins(bondObj.Balance),
}, nil
}
func matchBondOnAttributes(bondObj *bondtypes.Bond, attributes []*KeyValueInput) bool {
for _, attr := range attributes {
switch attr.Key {
case OwnerAttributeName:
{
if attr.Value.String == nil || bondObj.Owner != *attr.Value.String {
return false
}
}
default:
{
// Only attributes explicitly listed in the switch are queryable.
return false
}
}
}
return true
}
func getAuctionBid(bid *auctiontypes.Bid) *AuctionBid {
return &AuctionBid{
BidderAddress: bid.BidderAddress,
Status: bid.Status,
CommitHash: bid.CommitHash,
CommitTime: bid.GetCommitTime(),
RevealTime: bid.GetRevealTime(),
CommitFee: getGQLCoin(bid.CommitFee),
RevealFee: getGQLCoin(bid.RevealFee),
BidAmount: getGQLCoin(bid.BidAmount),
}
}
func GetGQLAuction(auction *auctiontypes.Auction, bids []*auctiontypes.Bid) (*Auction, error) {
if auction == nil {
return nil, nil
}
gqlAuction := Auction{
ID: auction.Id,
Status: auction.Status,
OwnerAddress: auction.OwnerAddress,
CreateTime: auction.GetCreateTime(),
CommitsEndTime: auction.GetCommitsEndTime(),
RevealsEndTime: auction.GetRevealsEndTime(),
CommitFee: getGQLCoin(auction.CommitFee),
RevealFee: getGQLCoin(auction.RevealFee),
MinimumBid: getGQLCoin(auction.MinimumBid),
WinnerAddress: auction.WinnerAddress,
WinnerBid: getGQLCoin(auction.WinningBid),
WinnerPrice: getGQLCoin(auction.WinningPrice),
}
auctionBids := make([]*AuctionBid, len(bids))
for index, entry := range bids {
auctionBids[index] = getAuctionBid(entry)
}
gqlAuction.Bids = auctionBids
return &gqlAuction, nil
}
func getReferences(ctx context.Context, resolver QueryResolver, r *nstypes.RecordType) ([]*Record, error) {
var ids []string
for _, value := range r.Attributes {
switch value.(type) {
case interface{}:
if obj, ok := value.(map[string]interface{}); ok {
if _, ok := obj["/"]; ok && len(obj) == 1 {
if _, ok := obj["/"].(string); ok {
ids = append(ids, obj["/"].(string))
}
}
}
}
}
return resolver.GetRecordsByIds(ctx, ids)
}
func getAttributes(r *nstypes.RecordType) ([]*KeyValue, error) {
return mapToKeyValuePairs(r.Attributes)
}
func mapToKeyValuePairs(attrs map[string]interface{}) ([]*KeyValue, error) {
var kvPairs []*KeyValue
trueVal := true
falseVal := false
for key, value := range attrs {
kvPair := &KeyValue{
Key: key,
Value: &Value{},
}
switch val := value.(type) {
case nil:
kvPair.Value.Null = &trueVal
case int:
kvPair.Value.Int = &val
case float64:
kvPair.Value.Float = &val
case string:
kvPair.Value.String = &val
case bool:
kvPair.Value.Boolean = &val
case interface{}:
if obj, ok := value.(map[string]interface{}); ok {
if _, ok := obj["/"]; ok && len(obj) == 1 {
if _, ok := obj["/"].(string); ok {
kvPair.Value.Reference = &Reference{
ID: obj["/"].(string),
}
}
} else {
bytes, err := json.Marshal(obj)
if err != nil {
return nil, err
}
jsonStr := string(bytes)
kvPair.Value.JSON = &jsonStr
}
}
}
if kvPair.Value.Null == nil {
kvPair.Value.Null = &falseVal
}
valueType := reflect.ValueOf(value)
if valueType.Kind() == reflect.Slice {
bytes, err := json.Marshal(value)
if err != nil {
return nil, err
}
jsonStr := string(bytes)
kvPair.Value.JSON = &jsonStr
}
kvPairs = append(kvPairs, kvPair)
}
return kvPairs, nil
}
func parseRequestAttributes(attrs []*KeyValueInput) []*nstypes.QueryListRecordsRequest_KeyValueInput {
kvPairs := []*nstypes.QueryListRecordsRequest_KeyValueInput{}
for _, value := range attrs {
kvPair := &nstypes.QueryListRecordsRequest_KeyValueInput{
Key: value.Key,
Value: &nstypes.QueryListRecordsRequest_ValueInput{},
}
if value.Value.String != nil {
kvPair.Value.String_ = *value.Value.String
kvPair.Value.Type = "string"
}
if value.Value.Int != nil {
kvPair.Value.Int = int64(*value.Value.Int)
kvPair.Value.Type = "int"
}
if value.Value.Float != nil {
kvPair.Value.Float = *value.Value.Float
kvPair.Value.Type = "float"
}
if value.Value.Boolean != nil {
kvPair.Value.Boolean = *value.Value.Boolean
kvPair.Value.Type = "boolean"
}
if value.Value.Reference != nil {
reference := &nstypes.QueryListRecordsRequest_ReferenceInput{
Id: value.Value.Reference.ID,
}
kvPair.Value.Reference = reference
kvPair.Value.Type = "reference"
}
// TODO: Handle arrays.
kvPairs = append(kvPairs, kvPair)
}
return kvPairs
}
+4
View File
@@ -0,0 +1,4 @@
package gql
// NameServiceVersion is the registry API version.
const NameServiceVersion = "0.3.0"
+238
View File
@@ -0,0 +1,238 @@
# Reference to another record.
type Reference {
id: String! # ID of linked record.
}
# Reference to another record.
input ReferenceInput {
id: String!
}
# Bonds contain funds that are used to pay rent on record registration and renewal.
type Bond {
id: String! # Primary key, auto-generated by the server.
owner: String! # Bond owner cosmos-sdk address.
balance: [Coin!] # Current balance for each coin type.
}
# OwnerBonds contains the bonds related the owner
type OwnerBonds {
owner: String!
bonds: [Bond!]
}
# Mutations require payment in coins (e.g. 100wire).
# Used by the wallet to get the account balance for display and mutations.
type Coin {
type: String! # e.g. 'WIRE'
quantity: String! # e.g. 1000000
}
# Represents an account on the blockchain.
# Mutations have to be signed by a particular account.
type Account {
address: String! # Blockchain address.
pubKey: String # Public key.
number: String! # Account number.
sequence: String! # Sequence number used to prevent replays.
balance: [Coin!] # Current balance for each coin type.
}
# Value of a given type.
type Value {
null: Boolean
int: Int
float: Float
string: String
boolean: Boolean
json: String
reference: Reference
values: [Value]
}
# Value of a given type used as input to queries.
input ValueInput {
null: Boolean
int: Int
float: Float
string: String
boolean: Boolean
reference: ReferenceInput
values: [ValueInput]
}
# Key/value pair.
type KeyValue {
key: String!
value: Value!
}
# Key/value pair for inputs.
input KeyValueInput {
key: String!
value: ValueInput!
}
# Status information about a node (https://docs.tendermint.com/master/rpc/#/Info/status).
type NodeInfo {
id: String! # Tendermint Node ID.
network: String! # Name of the network/blockchain.
moniker: String! # Name of the node.
}
# Node sync status.
type SyncInfo {
latest_block_hash: String!
latest_block_height: String!
latest_block_time: String!
catching_up: Boolean!
}
# Validator set info (https://docs.tendermint.com/master/rpc/#/Info/validators).
type ValidatorInfo {
address: String!
voting_power: String!
proposer_priority: String
}
# Network/peer info (https://docs.tendermint.com/master/rpc/#/Info/net_info).
type PeerInfo {
node: NodeInfo!
is_outbound: Boolean!
remote_ip: String!
}
# Vulcanize laconic status.
type Status {
version: String!
node: NodeInfo!
sync: SyncInfo!
validator: ValidatorInfo
validators: [ValidatorInfo]!
num_peers: String!
peers: [PeerInfo]
disk_usage: String!
}
# An auction bid.
type AuctionBid {
bidderAddress: String!
status: String!
commitHash: String!
commitTime: String!
commitFee: Coin!
revealTime: String!
revealFee: Coin!
bidAmount: Coin!
}
# A sealed-bid, 2nd price auction.
type Auction {
id: String! # Auction ID.
status: String! # Auction status (commit, reveal, expired).
ownerAddress: String! # Auction owner time.
createTime: String! # Create time.
commitsEndTime: String! # Commit phase end time.
revealsEndTime: String! # Reveal phase end time.
commitFee: Coin! # Fee required to bid/participate in the auction.
revealFee: Coin! # Reveal fee (paid back to bidders only if they unseal/reveal the bid).
minimumBid: Coin! # Minimum bid amount.
winnerAddress: String! # Winner address.
winnerBid: Coin! # The winning bid amount.
winnerPrice: Coin! # The price that the winner actually pays (2nd highest bid).
bids: [AuctionBid] # Bids make in the auction.
}
# Record defines the basic properties of an entity in the graph database.
type Record {
id: String! # Computed attribute: Multibase encoded content hash (https://github.com/multiformats/multibase).
names: [String!] # Names pointing to this CID (reverse lookup).
bondId: String! # Associated bond ID.
createTime: String! # Record create time.
expiryTime: String! # Record expiry time.
owners: [String!] # Addresses of record owners.
attributes: [KeyValue] # Record attributes.
references: [Record] # Record references.
}
# Name authority record.
type AuthorityRecord {
ownerAddress: String! # Owner address.
ownerPublicKey: String! # Owner public key.
height: String! # Height at which record was created.
status: String! # Status (active, auction, expired).
bondId: String! # Associated bond ID.
expiryTime: String! # Authority expiry time.
auction: Auction # Authority auction.
}
# Name record entry, created at a particular height.
type NameRecordEntry {
id: String! # Target record ID.
height: String! # Height at which record was created.
}
# Name record stores the latest and historical name -> record ID mappings.
type NameRecord {
latest: NameRecordEntry! # Latest mame record entry.
history: [NameRecordEntry] # Historical name record entries.
}
type Query {
#
# Status API.
#
getStatus: Status!
# Get blockchain accounts.
getAccounts(addresses: [String!]): [Account]
# Get bonds by IDs.
getBondsByIds(ids: [String!]): [Bond]
# Query bonds.
queryBonds(attributes: [KeyValueInput]): [Bond]
# Query bonds by owner.
queryBondsByOwner(ownerAddresses: [String!]): [OwnerBonds]
#
# GraphDB API.
#
# Get records by IDs.
getRecordsByIds(ids: [String!]): [Record]
# Query records.
queryRecords(
# Multiple attribute conditions are in a logical AND.
attributes: [KeyValueInput]
# Whether to query all records, not just named ones (false by default).
all: Boolean
): [Record]
#
# Naming API.
#
# Lookup authority information.
lookupAuthorities(names: [String!]): [AuthorityRecord]!
# Lookup name to record mapping information.
lookupNames(names: [String!]): [NameRecord]!
# Resolve names to records.
resolveNames(names: [String!]): [Record]!
#
# Auctions API.
#
# Get auctions by IDs.
getAuctionsByIds(ids: [String!]): [Auction]
}