Skip to content

Commit

Permalink
Add Golang client for Stellar RPC (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
2opremio authored Jan 30, 2025
1 parent 44db0ee commit be7702a
Show file tree
Hide file tree
Showing 54 changed files with 2,217 additions and 1,868 deletions.
175 changes: 175 additions & 0 deletions client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package client

import (
"context"
"net/http"

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/jhttp"

"github.com/stellar/stellar-rpc/protocol"
)

type Client struct {
url string
cli *jrpc2.Client
httpClient *http.Client
}

func NewClient(url string, httpClient *http.Client) *Client {
c := &Client{url: url, httpClient: httpClient}
c.refreshClient()
return c
}

func (c *Client) Close() error {
return c.cli.Close()
}

func (c *Client) refreshClient() {
if c.cli != nil {
c.cli.Close()
}
var opts *jhttp.ChannelOptions
if c.httpClient != nil {
opts = &jhttp.ChannelOptions{
Client: c.httpClient,
}
}
ch := jhttp.NewChannel(c.url, opts)
c.cli = jrpc2.NewClient(ch, nil)
}

func (c *Client) callResult(ctx context.Context, method string, params, result any) error {
err := c.cli.CallResult(ctx, method, params, result)
if err != nil {
// This is needed because of https://github.com/creachadair/jrpc2/issues/118
c.refreshClient()
}
return err
}

func (c *Client) GetEvents(ctx context.Context,
request protocol.GetEventsRequest,
) (protocol.GetEventsResponse, error) {
var result protocol.GetEventsResponse
err := c.callResult(ctx, protocol.GetEventsMethodName, request, &result)
if err != nil {
return protocol.GetEventsResponse{}, err
}
return result, nil
}

func (c *Client) GetFeeStats(ctx context.Context) (protocol.GetFeeStatsResponse, error) {
var result protocol.GetFeeStatsResponse
err := c.callResult(ctx, protocol.GetFeeStatsMethodName, nil, &result)
if err != nil {
return protocol.GetFeeStatsResponse{}, err
}
return result, nil
}

func (c *Client) GetHealth(ctx context.Context) (protocol.GetHealthResponse, error) {
var result protocol.GetHealthResponse
err := c.callResult(ctx, protocol.GetHealthMethodName, nil, &result)
if err != nil {
return protocol.GetHealthResponse{}, err
}
return result, nil
}

func (c *Client) GetLatestLedger(ctx context.Context) (protocol.GetLatestLedgerResponse, error) {
var result protocol.GetLatestLedgerResponse
err := c.callResult(ctx, protocol.GetLatestLedgerMethodName, nil, &result)
if err != nil {
return protocol.GetLatestLedgerResponse{}, err
}
return result, nil
}

func (c *Client) GetLedgerEntries(ctx context.Context,
request protocol.GetLedgerEntriesRequest,
) (protocol.GetLedgerEntriesResponse, error) {
var result protocol.GetLedgerEntriesResponse
err := c.callResult(ctx, protocol.GetLedgerEntriesMethodName, request, &result)
if err != nil {
return protocol.GetLedgerEntriesResponse{}, err
}
return result, nil
}

func (c *Client) GetLedgers(ctx context.Context,
request protocol.GetLedgersRequest,
) (protocol.GetLedgersResponse, error) {
var result protocol.GetLedgersResponse
err := c.callResult(ctx, protocol.GetLedgersMethodName, request, &result)
if err != nil {
return protocol.GetLedgersResponse{}, err
}
return result, nil
}

func (c *Client) GetNetwork(ctx context.Context,
) (protocol.GetNetworkResponse, error) {
// phony
var request protocol.GetNetworkRequest
var result protocol.GetNetworkResponse
err := c.callResult(ctx, protocol.GetNetworkMethodName, request, &result)
if err != nil {
return protocol.GetNetworkResponse{}, err
}
return result, nil
}

func (c *Client) GetTransaction(ctx context.Context,
request protocol.GetTransactionRequest,
) (protocol.GetTransactionResponse, error) {
var result protocol.GetTransactionResponse
err := c.callResult(ctx, protocol.GetTransactionMethodName, request, &result)
if err != nil {
return protocol.GetTransactionResponse{}, err
}
return result, nil
}

func (c *Client) GetTransactions(ctx context.Context,
request protocol.GetTransactionsRequest,
) (protocol.GetTransactionsResponse, error) {
var result protocol.GetTransactionsResponse
err := c.callResult(ctx, protocol.GetTransactionsMethodName, request, &result)
if err != nil {
return protocol.GetTransactionsResponse{}, err
}
return result, nil
}

func (c *Client) GetVersionInfo(ctx context.Context) (protocol.GetVersionInfoResponse, error) {
var result protocol.GetVersionInfoResponse
err := c.callResult(ctx, protocol.GetVersionInfoMethodName, nil, &result)
if err != nil {
return protocol.GetVersionInfoResponse{}, err
}
return result, nil
}

func (c *Client) SendTransaction(ctx context.Context,
request protocol.SendTransactionRequest,
) (protocol.SendTransactionResponse, error) {
var result protocol.SendTransactionResponse
err := c.callResult(ctx, protocol.SendTransactionMethodName, request, &result)
if err != nil {
return protocol.SendTransactionResponse{}, err
}
return result, nil
}

func (c *Client) SimulateTransaction(ctx context.Context,
request protocol.SimulateTransactionRequest,
) (protocol.SimulateTransactionResponse, error) {
var result protocol.SimulateTransactionResponse
err := c.callResult(ctx, protocol.SimulateTransactionMethodName, request, &result)
if err != nil {
return protocol.SimulateTransactionResponse{}, err
}
return result, nil
}
23 changes: 11 additions & 12 deletions cmd/stellar-rpc/internal/db/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import (
"github.com/stellar/go/support/db"
"github.com/stellar/go/support/log"
"github.com/stellar/go/xdr"

"github.com/stellar/stellar-rpc/protocol"
)

const (
eventTableName = "events"
firstLedger = uint32(2)
MinTopicCount = 1
MaxTopicCount = 4
)

type NestedTopicArray [][][]byte
Expand All @@ -34,7 +34,7 @@ type EventWriter interface {
type EventReader interface {
GetEvents(
ctx context.Context,
cursorRange CursorRange,
cursorRange protocol.CursorRange,
contractIDs [][]byte,
topics NestedTopicArray,
eventTypes []int,
Expand All @@ -53,7 +53,6 @@ func NewEventReader(log *log.Entry, db db.SessionInterface, passphrase string) E
return &eventHandler{log: log, db: db, passphrase: passphrase}
}

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

Expand Down Expand Up @@ -115,8 +114,8 @@ func (eventHandler *eventHandler) InsertEvents(lcm xdr.LedgerCloseMeta) error {
if e.Event.ContractId != nil {
contractID = e.Event.ContractId[:]
}

id := Cursor{Ledger: lcm.LedgerSequence(), Tx: tx.Index, Op: 0, Event: uint32(index)}.String()
index32 := uint32(index) //nolint:gosec
id := protocol.Cursor{Ledger: lcm.LedgerSequence(), Tx: tx.Index, Op: 0, Event: index32}.String()
eventBlob, err := e.MarshalBinary()
if err != nil {
return err
Expand All @@ -128,8 +127,8 @@ func (eventHandler *eventHandler) InsertEvents(lcm xdr.LedgerCloseMeta) error {
}

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

type ScanFunction func(
event xdr.DiagnosticEvent,
cursor Cursor,
cursor protocol.Cursor,
ledgerCloseTimestamp int64,
txHash *xdr.Hash,
) bool
Expand All @@ -171,7 +170,7 @@ func (eventHandler *eventHandler) trimEvents(latestLedgerSeq uint32, retentionWi
return nil
}
cutoff := latestLedgerSeq + 1 - retentionWindow
id := Cursor{Ledger: cutoff}.String()
id := protocol.Cursor{Ledger: cutoff}.String()

_, err := sq.StatementBuilder.
RunWith(eventHandler.stmtCache).
Expand All @@ -189,7 +188,7 @@ func (eventHandler *eventHandler) trimEvents(latestLedgerSeq uint32, retentionWi
//nolint:funlen,cyclop
func (eventHandler *eventHandler) GetEvents(
ctx context.Context,
cursorRange CursorRange,
cursorRange protocol.CursorRange,
contractIDs [][]byte,
topics NestedTopicArray,
eventTypes []int,
Expand Down Expand Up @@ -268,7 +267,7 @@ func (eventHandler *eventHandler) GetEvents(

id, eventData, ledgerCloseTime := row.eventCursorID, row.eventData, row.ledgerCloseTime
transactionHash := row.transactionHash
cur, err := ParseCursor(id)
cur, err := protocol.ParseCursor(id)
if err != nil {
return errors.Join(err, errors.New("failed to parse cursor"))
}
Expand Down
7 changes: 4 additions & 3 deletions cmd/stellar-rpc/internal/db/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/stellar/go/xdr"

"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/daemon/interfaces"
"github.com/stellar/stellar-rpc/protocol"
)

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

eventReader := NewEventReader(log, db, passphrase)
start := Cursor{Ledger: 1}
end := Cursor{Ledger: 100}
cursorRange := CursorRange{Start: start, End: end}
start := protocol.Cursor{Ledger: 1}
end := protocol.Cursor{Ledger: 100}
cursorRange := protocol.CursorRange{Start: start, End: end}

err = eventReader.GetEvents(ctx, cursorRange, nil, nil, nil, nil)
require.NoError(t, err)
Expand Down
7 changes: 4 additions & 3 deletions cmd/stellar-rpc/internal/db/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/stellar/go/xdr"

"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/daemon/interfaces"
"github.com/stellar/stellar-rpc/protocol"
)

func TestTransactionNotFound(t *testing.T) {
Expand Down Expand Up @@ -95,9 +96,9 @@ func TestTransactionFound(t *testing.T) {
require.ErrorIs(t, err, ErrNoTransaction)

eventReader := NewEventReader(log, db, passphrase)
start := Cursor{Ledger: 1}
end := Cursor{Ledger: 1000}
cursorRange := CursorRange{Start: start, End: end}
start := protocol.Cursor{Ledger: 1}
end := protocol.Cursor{Ledger: 1000}
cursorRange := protocol.CursorRange{Start: start, End: end}

err = eventReader.GetEvents(ctx, cursorRange, nil, nil, nil, nil)
require.NoError(t, err)
Expand Down
12 changes: 6 additions & 6 deletions cmd/stellar-rpc/internal/integrationtest/get_fee_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/stellar/go/xdr"

"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/integrationtest/infrastructure"
"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/methods"
"github.com/stellar/stellar-rpc/protocol"
)

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

var result methods.GetFeeStatsResult
if err := test.GetRPCLient().CallResult(context.Background(), "getFeeStats", nil, &result); err != nil {
result, err := test.GetRPCLient().GetFeeStats(context.Background())
if err != nil {
t.Fatalf("rpc call failed: %v", err)
}
expectedResult := methods.GetFeeStatsResult{
SorobanInclusionFee: methods.FeeDistribution{
expectedResult := protocol.GetFeeStatsResponse{
SorobanInclusionFee: protocol.FeeDistribution{
Max: sorobanInclusionFee,
Min: sorobanInclusionFee,
Mode: sorobanInclusionFee,
Expand All @@ -60,7 +60,7 @@ func TestGetFeeStats(t *testing.T) {
TransactionCount: 1,
LedgerCount: result.SorobanInclusionFee.LedgerCount,
},
InclusionFee: methods.FeeDistribution{
InclusionFee: protocol.FeeDistribution{
Max: classicFee,
Min: classicFee,
Mode: classicFee,
Expand Down
Loading

0 comments on commit be7702a

Please sign in to comment.