Skip to content

Commit

Permalink
Add additional details to getLatestLedger response
Browse files Browse the repository at this point in the history
  • Loading branch information
aditya1702 committed Dec 12, 2024
1 parent d3a0f4e commit 9cc175a
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 103 deletions.
88 changes: 63 additions & 25 deletions cmd/stellar-rpc/internal/methods/get_latest_ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,83 @@ package methods

import (
"context"
"encoding/base64"
"fmt"

"github.com/creachadair/jrpc2"

"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/db"
)

type GetLatestLedgerRequest struct {
Format string `json:"xdrFormat,omitempty"`
}

type GetLatestLedgerResponse struct {
// Hash of the latest ledger as a hex-encoded string
Hash string `json:"id"`
// Stellar Core protocol version associated with the ledger.
ProtocolVersion uint32 `json:"protocolVersion"`
// Sequence number of the latest ledger.
Sequence uint32 `json:"sequence"`
LedgerInfo
}

// NewGetLatestLedgerHandler returns a JSON RPC handler to retrieve the latest ledger entry from Stellar core.
func NewGetLatestLedgerHandler(ledgerEntryReader db.LedgerEntryReader, ledgerReader db.LedgerReader) jrpc2.Handler {
return NewHandler(func(ctx context.Context) (GetLatestLedgerResponse, error) {
latestSequence, err := ledgerEntryReader.GetLatestLedgerSequence(ctx)
if err != nil {
return GetLatestLedgerResponse{}, &jrpc2.Error{
Code: jrpc2.InternalError,
Message: "could not get latest ledger sequence",
}
type latestLedgerHandler struct {
ledgerEntryReader db.LedgerEntryReader
ledgerReader db.LedgerReader
}

func (h latestLedgerHandler) getLatestLedger(ctx context.Context, request GetLatestLedgerRequest) (GetLatestLedgerResponse, error) {
latestSequence, err := h.ledgerEntryReader.GetLatestLedgerSequence(ctx)
if err != nil {
return GetLatestLedgerResponse{}, &jrpc2.Error{
Code: jrpc2.InternalError,
Message: "could not get latest ledger sequence",
}
}

latestLedger, found, err := ledgerReader.GetLedger(ctx, latestSequence)
if (err != nil) || (!found) {
return GetLatestLedgerResponse{}, &jrpc2.Error{
Code: jrpc2.InternalError,
Message: "could not get latest ledger",
}
latestLedger, found, err := h.ledgerReader.GetLedger(ctx, latestSequence)
if (err != nil) || (!found) {
return GetLatestLedgerResponse{}, &jrpc2.Error{
Code: jrpc2.InternalError,
Message: "could not get latest ledger",
}
}

response := GetLatestLedgerResponse{
ProtocolVersion: latestLedger.ProtocolVersion(),
}
response.Hash = latestLedger.LedgerHash().HexString()
response.Sequence = latestSequence
response.LedgerCloseTime = latestLedger.LedgerCloseTime()

response := GetLatestLedgerResponse{
Hash: latestLedger.LedgerHash().HexString(),
ProtocolVersion: latestLedger.ProtocolVersion(),
Sequence: latestSequence,
// Format the data according to the requested format (JSON or XDR)
switch request.Format {
case FormatJSON:
var convErr error
response.LedgerMetadataJSON, response.LedgerHeaderJSON, convErr = ledgerToJSON(&latestLedger)
if convErr != nil {
return GetLatestLedgerResponse{}, convErr
}
default:
closeMetaB, err := latestLedger.MarshalBinary()
if err != nil {
return GetLatestLedgerResponse{}, fmt.Errorf("error marshaling ledger close meta: %w", err)
}
return response, nil
})

headerB, err := latestLedger.LedgerHeaderHistoryEntry().MarshalBinary()
if err != nil {
return GetLatestLedgerResponse{}, fmt.Errorf("error marshaling ledger header: %w", err)
}

response.LedgerMetadata = base64.StdEncoding.EncodeToString(closeMetaB)
response.LedgerHeader = base64.StdEncoding.EncodeToString(headerB)
}

return response, nil
}

// NewGetLatestLedgerHandler returns a JSON RPC handler to retrieve the latest ledger entry from Stellar core.
func NewGetLatestLedgerHandler(ledgerEntryReader db.LedgerEntryReader, ledgerReader db.LedgerReader) jrpc2.Handler {
return NewHandler((&latestLedgerHandler{
ledgerEntryReader: ledgerEntryReader,
ledgerReader: ledgerReader,
}).getLatestLedger)
}
122 changes: 44 additions & 78 deletions cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,102 +2,68 @@ package methods

import (
"context"
"errors"
"encoding/json"
"testing"

"github.com/creachadair/jrpc2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/stellar/go/xdr"

"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/db"
"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/ledgerbucketwindow"
)

const (
expectedLatestLedgerSequence uint32 = 960
expectedLatestLedgerProtocolVersion uint32 = 20
expectedLatestLedgerHashBytes byte = 42
)

type ConstantLedgerEntryReader struct{}

type ConstantLedgerEntryReaderTx struct{}

type ConstantLedgerReader struct{}

func (ledgerReader *ConstantLedgerReader) GetLedgerRange(_ context.Context) (ledgerbucketwindow.LedgerRange, error) {
return ledgerbucketwindow.LedgerRange{}, nil
}

func (ledgerReader *ConstantLedgerReader) NewTx(_ context.Context) (db.LedgerReaderTx, error) {
return nil, errors.New("mock NewTx error")
}

func (entryReader *ConstantLedgerEntryReader) GetLatestLedgerSequence(_ context.Context) (uint32, error) {
return expectedLatestLedgerSequence, nil
}

func (entryReader *ConstantLedgerEntryReader) NewTx(_ context.Context, _ bool) (db.LedgerEntryReadTx, error) {
return ConstantLedgerEntryReaderTx{}, nil
}

func (entryReaderTx ConstantLedgerEntryReaderTx) GetLatestLedgerSequence() (uint32, error) {
return expectedLatestLedgerSequence, nil
var expectedResponse = GetLatestLedgerResponse{
ProtocolVersion: uint32(0),
LedgerInfo: LedgerInfo{
Hash: "0000000000000000000000000000000000000000000000000000000000000000",
Sequence: 5,
LedgerCloseTime: 225,
LedgerHeader: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", //nolint:lll
LedgerMetadata: "AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAACAAABAIAAAAAAAAAAPww0v5OtDZlx0EzMkPcFURyDiq2XNKSi+w16A/x/6JoAAAABAAAAAP///6EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE/FqKJRRfCYUt0glxgPMqrA9VV2uKo7gsQDTTGLdLSYgAAAAAAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", //nolint:lll
},
}

func (entryReaderTx ConstantLedgerEntryReaderTx) GetLedgerEntries(_ ...xdr.LedgerKey) ([]db.LedgerKeyAndEntry, error) {
return nil, nil
}

func (entryReaderTx ConstantLedgerEntryReaderTx) Done() error {
return nil
}
func TestGetLatestLedger_DefaultRequest(t *testing.T) {
testDB := setupTestDB(t, 5)
request := GetLatestLedgerRequest{
Format: FormatBase64,
}

func (ledgerReader *ConstantLedgerReader) GetLedger(_ context.Context,
sequence uint32,
) (xdr.LedgerCloseMeta, bool, error) {
return createLedger(sequence, expectedLatestLedgerProtocolVersion, expectedLatestLedgerHashBytes), true, nil
}
handler := latestLedgerHandler{
ledgerReader: db.NewLedgerReader(testDB),
ledgerEntryReader: db.NewLedgerEntryReader(testDB),
}

func (ledgerReader *ConstantLedgerReader) StreamAllLedgers(_ context.Context, _ db.StreamLedgerFn) error {
return nil
resp, err := handler.getLatestLedger(context.Background(), request)
require.NoError(t, err)
require.Equal(t, expectedResponse, resp)
}

func (ledgerReader *ConstantLedgerReader) StreamLedgerRange(
_ context.Context,
_ uint32,
_ uint32,
_ db.StreamLedgerFn,
) error {
return nil
}
func TestGetLatestLedger_JSONFormat(t *testing.T) {
testDB := setupTestDB(t, 5)
request := GetLatestLedgerRequest{
Format: FormatJSON,
}

func createLedger(ledgerSequence uint32, protocolVersion uint32, hash byte) xdr.LedgerCloseMeta {
return xdr.LedgerCloseMeta{
V: 1,
V1: &xdr.LedgerCloseMetaV1{
LedgerHeader: xdr.LedgerHeaderHistoryEntry{
Hash: xdr.Hash{hash},
Header: xdr.LedgerHeader{
LedgerSeq: xdr.Uint32(ledgerSequence),
LedgerVersion: xdr.Uint32(protocolVersion),
},
},
},
handler := latestLedgerHandler{
ledgerReader: db.NewLedgerReader(testDB),
ledgerEntryReader: db.NewLedgerEntryReader(testDB),
}
}

func TestGetLatestLedger(t *testing.T) {
getLatestLedgerHandler := NewGetLatestLedgerHandler(&ConstantLedgerEntryReader{}, &ConstantLedgerReader{})
latestLedgerRespI, err := getLatestLedgerHandler(context.Background(), &jrpc2.Request{})
latestLedgerResp := latestLedgerRespI.(GetLatestLedgerResponse)
resp, err := handler.getLatestLedger(context.Background(), request)
require.NoError(t, err)

expectedLatestLedgerHashStr := xdr.Hash{expectedLatestLedgerHashBytes}.HexString()
assert.Equal(t, expectedLatestLedgerHashStr, latestLedgerResp.Hash)
assert.NotEmpty(t, resp.LedgerHeaderJSON)
assert.Empty(t, resp.LedgerHeader)
assert.NotEmpty(t, resp.LedgerMetadataJSON)
assert.Empty(t, resp.LedgerMetadata)

assert.Equal(t, expectedLatestLedgerProtocolVersion, latestLedgerResp.ProtocolVersion)
assert.Equal(t, expectedLatestLedgerSequence, latestLedgerResp.Sequence)
var headerJSON map[string]interface{}
err = json.Unmarshal(resp.LedgerHeaderJSON, &headerJSON)
require.NoError(t, err)
assert.NotEmpty(t, headerJSON)

var metaJSON map[string]interface{}
err = json.Unmarshal(resp.LedgerMetadataJSON, &metaJSON)
require.NoError(t, err)
assert.NotEmpty(t, metaJSON)
}

0 comments on commit 9cc175a

Please sign in to comment.