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 additional details to getLatestLedger response #337

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 65 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,85 @@ 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)
}
Loading