Skip to content

Commit 0eabce8

Browse files
committed
dedupe account/pool queries
1 parent 9b2dcaa commit 0eabce8

9 files changed

Lines changed: 81 additions & 67 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Full API documentation is available on [pkg.go.dev](https://pkg.go.dev/github.co
8383

8484
| Method | Returns | Description |
8585
|--------|---------|-------------|
86-
| `GetSyncStatus()` | `string` | Node sync status (SYNCED, BOOTSTRAP, etc.) |
86+
| `GetSyncStatus()` | `SyncStatus` | Node sync status (SYNCED, BOOTSTRAP, etc.) |
8787
| `GetDaemonStatus()` | `*DaemonStatus` | Comprehensive daemon status |
8888
| `GetNetworkID()` | `string` | Network identifier |
8989
| `GetAccount(publicKey, tokenID)` | `*AccountData` | Account balance, nonce, delegate |

client.go

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,14 @@ func (c *Client) request(query string, variables map[string]any, queryName strin
149149
// -- Queries --
150150

151151
// GetSyncStatus returns the node's sync status.
152-
// Returns one of: CONNECTING, LISTENING, OFFLINE, BOOTSTRAP, SYNCED, CATCHUP.
153-
func (c *Client) GetSyncStatus() (string, error) {
152+
// Returns one of the SyncStatus constants (SyncStatusSynced, SyncStatusBootstrap, ...).
153+
func (c *Client) GetSyncStatus() (SyncStatus, error) {
154154
data, err := c.request(querySyncStatus, nil, "get_sync_status")
155155
if err != nil {
156156
return "", err
157157
}
158158
var result struct {
159-
SyncStatus string `json:"syncStatus"`
159+
SyncStatus SyncStatus `json:"syncStatus"`
160160
}
161161
if err := json.Unmarshal(data, &result); err != nil {
162162
return "", err
@@ -172,12 +172,12 @@ func (c *Client) GetDaemonStatus() (*DaemonStatus, error) {
172172
}
173173
var result struct {
174174
DaemonStatus struct {
175-
SyncStatus string `json:"syncStatus"`
176-
BlockchainLength *int `json:"blockchainLength"`
177-
HighestBlockLengthReceived *int `json:"highestBlockLengthReceived"`
178-
UptimeSecs *int `json:"uptimeSecs"`
179-
StateHash string `json:"stateHash"`
180-
CommitID string `json:"commitId"`
175+
SyncStatus SyncStatus `json:"syncStatus"`
176+
BlockchainLength *int `json:"blockchainLength"`
177+
HighestBlockLengthReceived *int `json:"highestBlockLengthReceived"`
178+
UptimeSecs *int `json:"uptimeSecs"`
179+
StateHash string `json:"stateHash"`
180+
CommitID string `json:"commitId"`
181181
Peers []struct {
182182
PeerID string `json:"peerId"`
183183
Host string `json:"host"`
@@ -224,13 +224,15 @@ func (c *Client) GetNetworkID() (string, error) {
224224
// GetAccount returns account data for a public key.
225225
// Pass an empty tokenID to use the default MINA token.
226226
func (c *Client) GetAccount(publicKey, tokenID string) (*AccountData, error) {
227-
var data json.RawMessage
228-
var err error
227+
// tokenID is optional: a non-empty value scopes the query to that token,
228+
// while leaving it unset omits the $token variable so the daemon resolves
229+
// the default MINA token. A single query serves both cases.
230+
vars := map[string]any{"publicKey": publicKey}
229231
if tokenID != "" {
230-
data, err = c.request(queryGetAccountWithToken, map[string]any{"publicKey": publicKey, "token": tokenID}, "get_account")
231-
} else {
232-
data, err = c.request(queryGetAccount, map[string]any{"publicKey": publicKey}, "get_account")
232+
vars["token"] = tokenID
233233
}
234+
235+
data, err := c.request(queryGetAccount, vars, "get_account")
234236
if err != nil {
235237
return nil, err
236238
}
@@ -290,6 +292,7 @@ func (c *Client) GetAccount(publicKey, tokenID string) (*AccountData, error) {
290292
// GetBestChain returns blocks from the best chain.
291293
// Pass 0 for maxLength to use the daemon's default.
292294
func (c *Client) GetBestChain(maxLength int) ([]BlockInfo, error) {
295+
// maxLength <= 0 omits the argument, letting the daemon apply its default.
293296
var vars map[string]any
294297
if maxLength > 0 {
295298
vars = map[string]any{"maxLength": maxLength}
@@ -305,7 +308,7 @@ func (c *Client) GetBestChain(maxLength int) ([]BlockInfo, error) {
305308
StateHash string `json:"stateHash"`
306309
CommandTransactionCount int `json:"commandTransactionCount"`
307310
CreatorAccount struct {
308-
PublicKey any `json:"publicKey"`
311+
PublicKey string `json:"publicKey"`
309312
} `json:"creatorAccount"`
310313
ProtocolState struct {
311314
ConsensusState struct {
@@ -329,9 +332,11 @@ func (c *Client) GetBestChain(maxLength int) ([]BlockInfo, error) {
329332
slotGenesis, _ := strconv.Atoi(b.ProtocolState.ConsensusState.SlotSinceGenesis)
330333
slotFork, _ := strconv.Atoi(b.ProtocolState.ConsensusState.Slot)
331334

332-
creatorPK := "unknown"
333-
if v, ok := b.CreatorAccount.PublicKey.(string); ok {
334-
creatorPK = v
335+
// The daemon may omit the creator public key (e.g. for the genesis
336+
// block); fall back to a sentinel so callers get a stable value.
337+
creatorPK := b.CreatorAccount.PublicKey
338+
if creatorPK == "" {
339+
creatorPK = "unknown"
335340
}
336341

337342
blocks[i] = BlockInfo{
@@ -372,13 +377,15 @@ func (c *Client) GetPeers() ([]PeerInfo, error) {
372377
// GetPooledUserCommands returns pending user commands from the transaction pool.
373378
// Pass an empty publicKey to get all pending commands.
374379
func (c *Client) GetPooledUserCommands(publicKey string) ([]PooledUserCommand, error) {
375-
var data json.RawMessage
376-
var err error
380+
// publicKey is optional: a non-empty value filters to that sender, while
381+
// leaving it unset omits the $publicKey variable so the daemon returns
382+
// every pending command. A single query serves both cases.
383+
vars := map[string]any{}
377384
if publicKey != "" {
378-
data, err = c.request(queryPooledUserCommands, map[string]any{"publicKey": publicKey}, "get_pooled_user_commands")
379-
} else {
380-
data, err = c.request(queryPooledUserCommandsAll, nil, "get_pooled_user_commands")
385+
vars["publicKey"] = publicKey
381386
}
387+
388+
data, err := c.request(queryPooledUserCommands, vars, "get_pooled_user_commands")
382389
if err != nil {
383390
return nil, err
384391
}
@@ -423,6 +430,9 @@ func (c *Client) SendPayment(params SendPaymentParams) (*SendPaymentResult, erro
423430
"amount": params.Amount.NanominaString(),
424431
"fee": params.Fee.NanominaString(),
425432
}
433+
434+
// Memo and Nonce are optional: a zero memo and a nil nonce are simply
435+
// omitted, letting the daemon assign the next nonce.
426436
if params.Memo != "" {
427437
input["memo"] = params.Memo
428438
}
@@ -508,6 +518,8 @@ func (c *Client) SendDelegation(params SendDelegationParams) (*SendDelegationRes
508518
// Pass an empty string to disable the SNARK worker.
509519
// Returns the previous snark worker public key (empty if none).
510520
func (c *Client) SetSnarkWorker(publicKey string) (string, error) {
521+
// A non-empty publicKey sets the SNARK worker; an empty string sends a
522+
// null input, which unsets it.
511523
var input any
512524
if publicKey != "" {
513525
input = publicKey

client_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77
"net/http/httptest"
88
"testing"
9+
"time"
910
)
1011

1112
func gqlHandler(data any) http.HandlerFunc {
@@ -39,7 +40,7 @@ func TestGetSyncStatusSynced(t *testing.T) {
3940
if err != nil {
4041
t.Fatal(err)
4142
}
42-
if status != "SYNCED" {
43+
if status != SyncStatusSynced {
4344
t.Errorf("expected SYNCED, got %s", status)
4445
}
4546
}
@@ -53,7 +54,7 @@ func TestGetSyncStatusBootstrap(t *testing.T) {
5354
if err != nil {
5455
t.Fatal(err)
5556
}
56-
if status != "BOOTSTRAP" {
57+
if status != SyncStatusBootstrap {
5758
t.Errorf("expected BOOTSTRAP, got %s", status)
5859
}
5960
}
@@ -79,7 +80,7 @@ func TestGetDaemonStatus(t *testing.T) {
7980
if err != nil {
8081
t.Fatal(err)
8182
}
82-
if status.SyncStatus != "SYNCED" {
83+
if status.SyncStatus != SyncStatusSynced {
8384
t.Errorf("expected SYNCED, got %s", status.SyncStatus)
8485
}
8586
if status.BlockchainLength == nil || *status.BlockchainLength != 100 {
@@ -315,7 +316,7 @@ func TestConnectionErrorAfterRetries(t *testing.T) {
315316
WithGraphQLURI("http://127.0.0.1:1/graphql"),
316317
WithRetries(2),
317318
WithRetryDelay(0),
318-
WithTimeout(100*1000*1000), // 100ms in nanoseconds... use time.Duration
319+
WithTimeout(100*time.Millisecond),
319320
)
320321
defer client.Close()
321322

currency.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ func (c Currency) Nanomina() uint64 {
7171
}
7272

7373
// Mina returns the decimal string representation in whole MINA (e.g. "1.500000000").
74+
// The fractional part is always rendered with the full 9 nanomina digits so the
75+
// output round-trips exactly through CurrencyFromString.
7476
func (c Currency) Mina() string {
7577
s := strconv.FormatUint(c.nanomina, 10)
7678
if len(s) > 9 {
@@ -150,6 +152,10 @@ func (e *CurrencyUnderflowError) Error() string {
150152
return fmt.Sprintf("subtraction would result in negative: %s - %s", e.A, e.B)
151153
}
152154

155+
// parseDecimal converts a decimal MINA string (e.g. "1.5") into nanomina.
156+
// It is implemented by hand rather than via a float/decimal library to keep
157+
// the package dependency-free and to avoid binary-floating-point rounding of
158+
// nanomina amounts.
153159
func parseDecimal(s string) (uint64, error) {
154160
segments := strings.SplitN(s, ".", 3)
155161
switch len(segments) {

examples/basic_usage.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package main
66
import (
77
"fmt"
88
"log"
9+
"time"
910

1011
mina "github.com/MinaProtocol/mina-sdk-go"
1112
)
@@ -73,8 +74,8 @@ func connectToRemoteNode() {
7374
client := mina.NewClient(
7475
mina.WithGraphQLURI("http://my-mina-node:3085/graphql"),
7576
mina.WithRetries(5),
76-
mina.WithRetryDelay(10_000_000_000), // 10 seconds
77-
mina.WithTimeout(60_000_000_000), // 60 seconds
77+
mina.WithRetryDelay(10*time.Second),
78+
mina.WithTimeout(60*time.Second),
7879
)
7980
defer client.Close()
8081

integration_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ func TestIntegrationSyncStatus(t *testing.T) {
8181
if err != nil {
8282
t.Fatal(err)
8383
}
84-
validStatuses := map[string]bool{
85-
"CONNECTING": true, "LISTENING": true, "OFFLINE": true,
86-
"BOOTSTRAP": true, "SYNCED": true, "CATCHUP": true,
84+
validStatuses := map[mina.SyncStatus]bool{
85+
mina.SyncStatusConnecting: true, mina.SyncStatusListening: true, mina.SyncStatusOffline: true,
86+
mina.SyncStatusBootstrap: true, mina.SyncStatusSynced: true, mina.SyncStatusCatchup: true,
8787
}
8888
if !validStatuses[status] {
8989
t.Errorf("unexpected sync status: %s", status)

queries.go

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,11 @@ query {
3232
}
3333
`
3434

35+
// queryGetAccount fetches a single account. The token argument is optional:
36+
// $token is a nullable TokenId, and when no token variable is supplied the
37+
// daemon resolves the default MINA token.
3538
const queryGetAccount = `
36-
query ($publicKey: PublicKey!) {
37-
account(publicKey: $publicKey) {
38-
publicKey
39-
nonce
40-
delegate
41-
tokenId
42-
balance {
43-
total
44-
liquid
45-
locked
46-
}
47-
}
48-
}
49-
`
50-
51-
const queryGetAccountWithToken = `
52-
query ($publicKey: PublicKey!, $token: TokenId!) {
39+
query ($publicKey: PublicKey!, $token: TokenId) {
5340
account(publicKey: $publicKey, token: $token) {
5441
publicKey
5542
nonce
@@ -93,8 +80,11 @@ query {
9380
}
9481
`
9582

83+
// queryPooledUserCommands lists pending user commands. The publicKey argument
84+
// is optional: $publicKey is a nullable PublicKey, and when no variable is
85+
// supplied the daemon returns commands for every sender.
9686
const queryPooledUserCommands = `
97-
query ($publicKey: PublicKey!) {
87+
query ($publicKey: PublicKey) {
9888
pooledUserCommands(publicKey: $publicKey) {
9989
id
10090
hash
@@ -108,21 +98,6 @@ query ($publicKey: PublicKey!) {
10898
}
10999
`
110100

111-
const queryPooledUserCommandsAll = `
112-
query {
113-
pooledUserCommands {
114-
id
115-
hash
116-
kind
117-
nonce
118-
amount
119-
fee
120-
from
121-
to
122-
}
123-
}
124-
`
125-
126101
const mutationSendPayment = `
127102
mutation ($input: SendPaymentInput!) {
128103
sendPayment(input: $input) {

scripts/check_schema_drift.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
// go run scripts/check_schema_drift.go --endpoint http://127.0.0.1:8080/graphql --branch master --strict
1919
package main
2020

21+
// The schema diff is implemented in-package (rather than via a JSON-diff
22+
// dependency) so this `go:build ignore` tool stays dependency-free and the
23+
// output can be tuned to GraphQL-specific drift categories. Output is
24+
// human-readable by design; if a machine-readable format is needed later,
25+
// emit JSON behind a flag.
2126
import (
2227
"bytes"
2328
"encoding/json"

types.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@ package mina
22

33
import "encoding/json"
44

5+
// SyncStatus is a Mina node's synchronization status, as reported by the
6+
// daemon's syncStatus / daemonStatus.syncStatus fields.
7+
type SyncStatus string
8+
9+
// Known sync statuses returned by the Mina daemon.
10+
const (
11+
SyncStatusConnecting SyncStatus = "CONNECTING"
12+
SyncStatusListening SyncStatus = "LISTENING"
13+
SyncStatusOffline SyncStatus = "OFFLINE"
14+
SyncStatusBootstrap SyncStatus = "BOOTSTRAP"
15+
SyncStatusSynced SyncStatus = "SYNCED"
16+
SyncStatusCatchup SyncStatus = "CATCHUP"
17+
)
18+
519
// AccountBalance represents the balance of a Mina account.
620
type AccountBalance struct {
721
Total Currency
@@ -27,7 +41,7 @@ type PeerInfo struct {
2741

2842
// DaemonStatus represents the status of the Mina daemon.
2943
type DaemonStatus struct {
30-
SyncStatus string
44+
SyncStatus SyncStatus
3145
BlockchainLength *int
3246
HighestBlockLengthReceived *int
3347
UptimeSecs *int

0 commit comments

Comments
 (0)