Skip to content

Commit be7702a

Browse files
authored
Add Golang client for Stellar RPC (#349)
1 parent 44db0ee commit be7702a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2217
-1868
lines changed

client/main.go

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/creachadair/jrpc2"
8+
"github.com/creachadair/jrpc2/jhttp"
9+
10+
"github.com/stellar/stellar-rpc/protocol"
11+
)
12+
13+
type Client struct {
14+
url string
15+
cli *jrpc2.Client
16+
httpClient *http.Client
17+
}
18+
19+
func NewClient(url string, httpClient *http.Client) *Client {
20+
c := &Client{url: url, httpClient: httpClient}
21+
c.refreshClient()
22+
return c
23+
}
24+
25+
func (c *Client) Close() error {
26+
return c.cli.Close()
27+
}
28+
29+
func (c *Client) refreshClient() {
30+
if c.cli != nil {
31+
c.cli.Close()
32+
}
33+
var opts *jhttp.ChannelOptions
34+
if c.httpClient != nil {
35+
opts = &jhttp.ChannelOptions{
36+
Client: c.httpClient,
37+
}
38+
}
39+
ch := jhttp.NewChannel(c.url, opts)
40+
c.cli = jrpc2.NewClient(ch, nil)
41+
}
42+
43+
func (c *Client) callResult(ctx context.Context, method string, params, result any) error {
44+
err := c.cli.CallResult(ctx, method, params, result)
45+
if err != nil {
46+
// This is needed because of https://github.com/creachadair/jrpc2/issues/118
47+
c.refreshClient()
48+
}
49+
return err
50+
}
51+
52+
func (c *Client) GetEvents(ctx context.Context,
53+
request protocol.GetEventsRequest,
54+
) (protocol.GetEventsResponse, error) {
55+
var result protocol.GetEventsResponse
56+
err := c.callResult(ctx, protocol.GetEventsMethodName, request, &result)
57+
if err != nil {
58+
return protocol.GetEventsResponse{}, err
59+
}
60+
return result, nil
61+
}
62+
63+
func (c *Client) GetFeeStats(ctx context.Context) (protocol.GetFeeStatsResponse, error) {
64+
var result protocol.GetFeeStatsResponse
65+
err := c.callResult(ctx, protocol.GetFeeStatsMethodName, nil, &result)
66+
if err != nil {
67+
return protocol.GetFeeStatsResponse{}, err
68+
}
69+
return result, nil
70+
}
71+
72+
func (c *Client) GetHealth(ctx context.Context) (protocol.GetHealthResponse, error) {
73+
var result protocol.GetHealthResponse
74+
err := c.callResult(ctx, protocol.GetHealthMethodName, nil, &result)
75+
if err != nil {
76+
return protocol.GetHealthResponse{}, err
77+
}
78+
return result, nil
79+
}
80+
81+
func (c *Client) GetLatestLedger(ctx context.Context) (protocol.GetLatestLedgerResponse, error) {
82+
var result protocol.GetLatestLedgerResponse
83+
err := c.callResult(ctx, protocol.GetLatestLedgerMethodName, nil, &result)
84+
if err != nil {
85+
return protocol.GetLatestLedgerResponse{}, err
86+
}
87+
return result, nil
88+
}
89+
90+
func (c *Client) GetLedgerEntries(ctx context.Context,
91+
request protocol.GetLedgerEntriesRequest,
92+
) (protocol.GetLedgerEntriesResponse, error) {
93+
var result protocol.GetLedgerEntriesResponse
94+
err := c.callResult(ctx, protocol.GetLedgerEntriesMethodName, request, &result)
95+
if err != nil {
96+
return protocol.GetLedgerEntriesResponse{}, err
97+
}
98+
return result, nil
99+
}
100+
101+
func (c *Client) GetLedgers(ctx context.Context,
102+
request protocol.GetLedgersRequest,
103+
) (protocol.GetLedgersResponse, error) {
104+
var result protocol.GetLedgersResponse
105+
err := c.callResult(ctx, protocol.GetLedgersMethodName, request, &result)
106+
if err != nil {
107+
return protocol.GetLedgersResponse{}, err
108+
}
109+
return result, nil
110+
}
111+
112+
func (c *Client) GetNetwork(ctx context.Context,
113+
) (protocol.GetNetworkResponse, error) {
114+
// phony
115+
var request protocol.GetNetworkRequest
116+
var result protocol.GetNetworkResponse
117+
err := c.callResult(ctx, protocol.GetNetworkMethodName, request, &result)
118+
if err != nil {
119+
return protocol.GetNetworkResponse{}, err
120+
}
121+
return result, nil
122+
}
123+
124+
func (c *Client) GetTransaction(ctx context.Context,
125+
request protocol.GetTransactionRequest,
126+
) (protocol.GetTransactionResponse, error) {
127+
var result protocol.GetTransactionResponse
128+
err := c.callResult(ctx, protocol.GetTransactionMethodName, request, &result)
129+
if err != nil {
130+
return protocol.GetTransactionResponse{}, err
131+
}
132+
return result, nil
133+
}
134+
135+
func (c *Client) GetTransactions(ctx context.Context,
136+
request protocol.GetTransactionsRequest,
137+
) (protocol.GetTransactionsResponse, error) {
138+
var result protocol.GetTransactionsResponse
139+
err := c.callResult(ctx, protocol.GetTransactionsMethodName, request, &result)
140+
if err != nil {
141+
return protocol.GetTransactionsResponse{}, err
142+
}
143+
return result, nil
144+
}
145+
146+
func (c *Client) GetVersionInfo(ctx context.Context) (protocol.GetVersionInfoResponse, error) {
147+
var result protocol.GetVersionInfoResponse
148+
err := c.callResult(ctx, protocol.GetVersionInfoMethodName, nil, &result)
149+
if err != nil {
150+
return protocol.GetVersionInfoResponse{}, err
151+
}
152+
return result, nil
153+
}
154+
155+
func (c *Client) SendTransaction(ctx context.Context,
156+
request protocol.SendTransactionRequest,
157+
) (protocol.SendTransactionResponse, error) {
158+
var result protocol.SendTransactionResponse
159+
err := c.callResult(ctx, protocol.SendTransactionMethodName, request, &result)
160+
if err != nil {
161+
return protocol.SendTransactionResponse{}, err
162+
}
163+
return result, nil
164+
}
165+
166+
func (c *Client) SimulateTransaction(ctx context.Context,
167+
request protocol.SimulateTransactionRequest,
168+
) (protocol.SimulateTransactionResponse, error) {
169+
var result protocol.SimulateTransactionResponse
170+
err := c.callResult(ctx, protocol.SimulateTransactionMethodName, request, &result)
171+
if err != nil {
172+
return protocol.SimulateTransactionResponse{}, err
173+
}
174+
return result, nil
175+
}

cmd/stellar-rpc/internal/db/event.go

+11-12
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ import (
1414
"github.com/stellar/go/support/db"
1515
"github.com/stellar/go/support/log"
1616
"github.com/stellar/go/xdr"
17+
18+
"github.com/stellar/stellar-rpc/protocol"
1719
)
1820

1921
const (
2022
eventTableName = "events"
2123
firstLedger = uint32(2)
22-
MinTopicCount = 1
23-
MaxTopicCount = 4
2424
)
2525

2626
type NestedTopicArray [][][]byte
@@ -34,7 +34,7 @@ type EventWriter interface {
3434
type EventReader interface {
3535
GetEvents(
3636
ctx context.Context,
37-
cursorRange CursorRange,
37+
cursorRange protocol.CursorRange,
3838
contractIDs [][]byte,
3939
topics NestedTopicArray,
4040
eventTypes []int,
@@ -53,7 +53,6 @@ func NewEventReader(log *log.Entry, db db.SessionInterface, passphrase string) E
5353
return &eventHandler{log: log, db: db, passphrase: passphrase}
5454
}
5555

56-
//nolint:gocognit,cyclop,funlen
5756
func (eventHandler *eventHandler) InsertEvents(lcm xdr.LedgerCloseMeta) error {
5857
txCount := lcm.CountTransactions()
5958

@@ -115,8 +114,8 @@ func (eventHandler *eventHandler) InsertEvents(lcm xdr.LedgerCloseMeta) error {
115114
if e.Event.ContractId != nil {
116115
contractID = e.Event.ContractId[:]
117116
}
118-
119-
id := Cursor{Ledger: lcm.LedgerSequence(), Tx: tx.Index, Op: 0, Event: uint32(index)}.String()
117+
index32 := uint32(index) //nolint:gosec
118+
id := protocol.Cursor{Ledger: lcm.LedgerSequence(), Tx: tx.Index, Op: 0, Event: index32}.String()
120119
eventBlob, err := e.MarshalBinary()
121120
if err != nil {
122121
return err
@@ -128,8 +127,8 @@ func (eventHandler *eventHandler) InsertEvents(lcm xdr.LedgerCloseMeta) error {
128127
}
129128

130129
// Encode the topics
131-
topicList := make([][]byte, MaxTopicCount)
132-
for index := 0; index < len(v0.Topics) && index < MaxTopicCount; index++ {
130+
topicList := make([][]byte, protocol.MaxTopicCount)
131+
for index := 0; index < len(v0.Topics) && index < protocol.MaxTopicCount; index++ {
133132
segment := v0.Topics[index]
134133
seg, err := segment.MarshalBinary()
135134
if err != nil {
@@ -160,7 +159,7 @@ func (eventHandler *eventHandler) InsertEvents(lcm xdr.LedgerCloseMeta) error {
160159

161160
type ScanFunction func(
162161
event xdr.DiagnosticEvent,
163-
cursor Cursor,
162+
cursor protocol.Cursor,
164163
ledgerCloseTimestamp int64,
165164
txHash *xdr.Hash,
166165
) bool
@@ -171,7 +170,7 @@ func (eventHandler *eventHandler) trimEvents(latestLedgerSeq uint32, retentionWi
171170
return nil
172171
}
173172
cutoff := latestLedgerSeq + 1 - retentionWindow
174-
id := Cursor{Ledger: cutoff}.String()
173+
id := protocol.Cursor{Ledger: cutoff}.String()
175174

176175
_, err := sq.StatementBuilder.
177176
RunWith(eventHandler.stmtCache).
@@ -189,7 +188,7 @@ func (eventHandler *eventHandler) trimEvents(latestLedgerSeq uint32, retentionWi
189188
//nolint:funlen,cyclop
190189
func (eventHandler *eventHandler) GetEvents(
191190
ctx context.Context,
192-
cursorRange CursorRange,
191+
cursorRange protocol.CursorRange,
193192
contractIDs [][]byte,
194193
topics NestedTopicArray,
195194
eventTypes []int,
@@ -268,7 +267,7 @@ func (eventHandler *eventHandler) GetEvents(
268267

269268
id, eventData, ledgerCloseTime := row.eventCursorID, row.eventData, row.ledgerCloseTime
270269
transactionHash := row.transactionHash
271-
cur, err := ParseCursor(id)
270+
cur, err := protocol.ParseCursor(id)
272271
if err != nil {
273272
return errors.Join(err, errors.New("failed to parse cursor"))
274273
}

cmd/stellar-rpc/internal/db/event_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/stellar/go/xdr"
1515

1616
"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/daemon/interfaces"
17+
"github.com/stellar/stellar-rpc/protocol"
1718
)
1819

1920
func transactionMetaWithEvents(events ...xdr.ContractEvent) xdr.TransactionMeta {
@@ -170,9 +171,9 @@ func TestInsertEvents(t *testing.T) {
170171
require.NoError(t, err)
171172

172173
eventReader := NewEventReader(log, db, passphrase)
173-
start := Cursor{Ledger: 1}
174-
end := Cursor{Ledger: 100}
175-
cursorRange := CursorRange{Start: start, End: end}
174+
start := protocol.Cursor{Ledger: 1}
175+
end := protocol.Cursor{Ledger: 100}
176+
cursorRange := protocol.CursorRange{Start: start, End: end}
176177

177178
err = eventReader.GetEvents(ctx, cursorRange, nil, nil, nil, nil)
178179
require.NoError(t, err)

cmd/stellar-rpc/internal/db/transaction_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/stellar/go/xdr"
1616

1717
"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/daemon/interfaces"
18+
"github.com/stellar/stellar-rpc/protocol"
1819
)
1920

2021
func TestTransactionNotFound(t *testing.T) {
@@ -95,9 +96,9 @@ func TestTransactionFound(t *testing.T) {
9596
require.ErrorIs(t, err, ErrNoTransaction)
9697

9798
eventReader := NewEventReader(log, db, passphrase)
98-
start := Cursor{Ledger: 1}
99-
end := Cursor{Ledger: 1000}
100-
cursorRange := CursorRange{Start: start, End: end}
99+
start := protocol.Cursor{Ledger: 1}
100+
end := protocol.Cursor{Ledger: 1000}
101+
cursorRange := protocol.CursorRange{Start: start, End: end}
101102

102103
err = eventReader.GetEvents(ctx, cursorRange, nil, nil, nil, nil)
103104
require.NoError(t, err)

cmd/stellar-rpc/internal/integrationtest/get_fee_stats_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/stellar/go/xdr"
1212

1313
"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/integrationtest/infrastructure"
14-
"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/methods"
14+
"github.com/stellar/stellar-rpc/protocol"
1515
)
1616

1717
func TestGetFeeStats(t *testing.T) {
@@ -37,12 +37,12 @@ func TestGetFeeStats(t *testing.T) {
3737
require.NoError(t, xdr.SafeUnmarshalBase64(classicTxResponse.ResultXDR, &classicTxResult))
3838
classicFee := uint64(classicTxResult.FeeCharged)
3939

40-
var result methods.GetFeeStatsResult
41-
if err := test.GetRPCLient().CallResult(context.Background(), "getFeeStats", nil, &result); err != nil {
40+
result, err := test.GetRPCLient().GetFeeStats(context.Background())
41+
if err != nil {
4242
t.Fatalf("rpc call failed: %v", err)
4343
}
44-
expectedResult := methods.GetFeeStatsResult{
45-
SorobanInclusionFee: methods.FeeDistribution{
44+
expectedResult := protocol.GetFeeStatsResponse{
45+
SorobanInclusionFee: protocol.FeeDistribution{
4646
Max: sorobanInclusionFee,
4747
Min: sorobanInclusionFee,
4848
Mode: sorobanInclusionFee,
@@ -60,7 +60,7 @@ func TestGetFeeStats(t *testing.T) {
6060
TransactionCount: 1,
6161
LedgerCount: result.SorobanInclusionFee.LedgerCount,
6262
},
63-
InclusionFee: methods.FeeDistribution{
63+
InclusionFee: protocol.FeeDistribution{
6464
Max: classicFee,
6565
Min: classicFee,
6666
Mode: classicFee,

0 commit comments

Comments
 (0)