Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Golang client for Stellar RPC #349

Merged
merged 11 commits into from
Jan 30, 2025
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
Shaptic marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading