diff --git a/cmd/stellar-rpc/internal/methods/get_latest_ledger.go b/cmd/stellar-rpc/internal/methods/get_latest_ledger.go index db69ee18..1aabe4f7 100644 --- a/cmd/stellar-rpc/internal/methods/get_latest_ledger.go +++ b/cmd/stellar-rpc/internal/methods/get_latest_ledger.go @@ -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) } diff --git a/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go b/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go index a2284fb7..1338d0f9 100644 --- a/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go +++ b/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go @@ -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) }