Skip to content

Commit 55c706c

Browse files
Shaptic2opremio
andauthored
getLedgerEntries: optionally use high-performance Core server (#353)
* Replace getLedgerEntries DB queries with Core fetches * Add infrastructure for testing the new Core http query server * Sort entries in response according to request order * Only test the query server from protocol 23 onwards * Enable debug printouts for integration tests * Make sure all ports are allocated at once to minimize clashes --------- Co-authored-by: Alfonso Acosta <[email protected]>
1 parent cd2ef19 commit 55c706c

15 files changed

+427
-140
lines changed

.github/workflows/stellar-rpc.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ jobs:
104104
STELLAR_RPC_INTEGRATION_TESTS_ENABLED: true
105105
STELLAR_RPC_INTEGRATION_TESTS_CORE_MAX_SUPPORTED_PROTOCOL: ${{ matrix.protocol-version }}
106106
STELLAR_RPC_INTEGRATION_TESTS_CAPTIVE_CORE_BIN: /usr/bin/stellar-core
107-
PROTOCOL_21_CORE_DEBIAN_PKG_VERSION: 22.0.0-2138.721fd0a65.focal
108-
PROTOCOL_21_CORE_DOCKER_IMG: stellar/stellar-core:22.0.0-2138.721fd0a65.focal
109-
PROTOCOL_22_CORE_DEBIAN_PKG_VERSION: 22.0.0-2138.721fd0a65.focal
110-
PROTOCOL_22_CORE_DOCKER_IMG: stellar/stellar-core:22.0.0-2138.721fd0a65.focal
107+
PROTOCOL_21_CORE_DEBIAN_PKG_VERSION: 22.1.0-2194.0241e79f7.focal
108+
PROTOCOL_21_CORE_DOCKER_IMG: stellar/stellar-core:22.1.0-2194.0241e79f7.focal
109+
PROTOCOL_22_CORE_DEBIAN_PKG_VERSION: 22.1.1-2251.ac9f21ac7.focal~do~not~use~in~prd
110+
PROTOCOL_22_CORE_DOCKER_IMG: stellar/unsafe-stellar-core:22.1.1-2251.ac9f21ac7.focal-do-not-use-in-prd
111111

112112
steps:
113113
- uses: actions/checkout@v4

cmd/stellar-rpc/internal/config/main.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ type Config struct {
1414

1515
Strict bool
1616

17-
StellarCoreURL string
18-
CaptiveCoreStoragePath string
19-
StellarCoreBinaryPath string
20-
CaptiveCoreConfigPath string
21-
CaptiveCoreHTTPPort uint
17+
StellarCoreURL string
18+
CaptiveCoreStoragePath string
19+
StellarCoreBinaryPath string
20+
CaptiveCoreConfigPath string
21+
CaptiveCoreHTTPPort uint16
22+
CaptiveCoreHTTPQueryPort uint16
23+
CaptiveCoreHTTPQueryThreadPoolSize uint16
24+
CaptiveCoreHTTPQuerySnapshotLedgers uint16
2225

2326
Endpoint string
2427
AdminEndpoint string

cmd/stellar-rpc/internal/config/options.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const (
2020
OneDayOfLedgers = 17280
2121
SevenDayOfLedgers = OneDayOfLedgers * 7
2222

23-
defaultHTTPEndpoint = "localhost:8000"
23+
defaultHTTPEndpoint = "localhost:8000"
24+
defaultCaptiveCoreHTTPPort = 11626 // regular queries like /info
2425
)
2526

2627
// TODO: refactor and remove the linter exceptions
@@ -84,7 +85,25 @@ func (cfg *Config) options() Options {
8485
Name: "stellar-captive-core-http-port",
8586
Usage: "HTTP port for Captive Core to listen on (0 disables the HTTP server)",
8687
ConfigKey: &cfg.CaptiveCoreHTTPPort,
87-
DefaultValue: uint(11626),
88+
DefaultValue: uint16(defaultCaptiveCoreHTTPPort),
89+
},
90+
{
91+
Name: "stellar-captive-core-http-query-port",
92+
Usage: "HTTP port for Captive Core to listen on for high-performance queries like /getledgerentry (0 disables the HTTP server, must not conflict with CAPTIVE_CORE_HTTP_PORT)",
93+
ConfigKey: &cfg.CaptiveCoreHTTPQueryPort,
94+
DefaultValue: uint16(0), // Disabled by default, although it normally uses 11628
95+
},
96+
{
97+
Name: "stellar-captive-core-http-query-thread-pool-size",
98+
Usage: "Number of threads to use by Captive Core's high-performance query server",
99+
ConfigKey: &cfg.CaptiveCoreHTTPQueryThreadPoolSize,
100+
DefaultValue: uint16(runtime.NumCPU()), //nolint:gosec
101+
},
102+
{
103+
Name: "stellar-captive-core-http-query-snapshot-ledgers",
104+
Usage: "Size of ledger history in Captive Core's high-performance query server (don't touch unless you know what you are doing)",
105+
ConfigKey: &cfg.CaptiveCoreHTTPQuerySnapshotLedgers,
106+
DefaultValue: uint16(4),
88107
},
89108
{
90109
Name: "log-level",

cmd/stellar-rpc/internal/config/test.soroban.rpc.config

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ STELLAR_CORE_BINARY_PATH="/usr/bin/stellar-core"
88
HISTORY_ARCHIVE_URLS=["http://localhost:1570"]
99
DB_PATH="/opt/stellar/stellar-rpc/rpc_db.sqlite"
1010
STELLAR_CAPTIVE_CORE_HTTP_PORT=0
11+
STELLAR_CAPTIVE_CORE_HTTP_QUERY_PORT=11628
1112
CHECKPOINT_FREQUENCY=64

cmd/stellar-rpc/internal/config/toml_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ func TestRoundTrip(t *testing.T) {
113113
*v = "test"
114114
case *uint:
115115
*v = 42
116+
case *uint16:
117+
*v = 22
116118
case *uint32:
117119
*v = 32
118120
case *time.Duration:

cmd/stellar-rpc/internal/daemon/daemon.go

+36-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package daemon
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"net"
78
"net/http"
89
"net/http/pprof"
@@ -49,6 +50,7 @@ const (
4950
type Daemon struct {
5051
core *ledgerbackend.CaptiveStellarCore
5152
coreClient *CoreClientWithMetrics
53+
coreQueryingClient interfaces.FastCoreClient
5254
ingestService *ingest.Service
5355
db *db.DB
5456
jsonRPCHandler *internal.Handler
@@ -120,15 +122,27 @@ func (d *Daemon) Close() error {
120122

121123
// newCaptiveCore creates a new captive core backend instance and returns it.
122124
func newCaptiveCore(cfg *config.Config, logger *supportlog.Entry) (*ledgerbackend.CaptiveStellarCore, error) {
125+
var queryServerParams *ledgerbackend.HTTPQueryServerParams
126+
if cfg.CaptiveCoreHTTPQueryPort != 0 {
127+
// Only try to enable the server if the port passed is non-zero
128+
queryServerParams = &ledgerbackend.HTTPQueryServerParams{
129+
Port: cfg.CaptiveCoreHTTPQueryPort,
130+
ThreadPoolSize: cfg.CaptiveCoreHTTPQueryThreadPoolSize,
131+
SnapshotLedgers: cfg.CaptiveCoreHTTPQuerySnapshotLedgers,
132+
}
133+
}
134+
135+
httpPort := uint(cfg.CaptiveCoreHTTPPort)
123136
captiveCoreTomlParams := ledgerbackend.CaptiveCoreTomlParams{
124-
HTTPPort: &cfg.CaptiveCoreHTTPPort,
137+
HTTPPort: &httpPort,
125138
HistoryArchiveURLs: cfg.HistoryArchiveURLs,
126139
NetworkPassphrase: cfg.NetworkPassphrase,
127140
Strict: true,
128141
UseDB: true,
129142
EnforceSorobanDiagnosticEvents: true,
130143
EnforceSorobanTransactionMetaExtV1: true,
131144
CoreBinaryPath: cfg.StellarCoreBinaryPath,
145+
HTTPQueryServerParams: queryServerParams,
132146
}
133147
captiveCoreToml, err := ledgerbackend.NewCaptiveCoreTomlFromFile(cfg.CaptiveCoreConfigPath, captiveCoreTomlParams)
134148
if err != nil {
@@ -156,12 +170,13 @@ func MustNew(cfg *config.Config, logger *supportlog.Entry) *Daemon {
156170
metricsRegistry := prometheus.NewRegistry()
157171

158172
daemon := &Daemon{
159-
logger: logger,
160-
core: core,
161-
db: mustOpenDatabase(cfg, logger, metricsRegistry),
162-
done: make(chan struct{}),
163-
metricsRegistry: metricsRegistry,
164-
coreClient: newCoreClientWithMetrics(createStellarCoreClient(cfg), metricsRegistry),
173+
logger: logger,
174+
core: core,
175+
db: mustOpenDatabase(cfg, logger, metricsRegistry),
176+
done: make(chan struct{}),
177+
metricsRegistry: metricsRegistry,
178+
coreClient: newCoreClientWithMetrics(createStellarCoreClient(cfg), metricsRegistry),
179+
coreQueryingClient: createHighperfStellarCoreClient(cfg),
165180
}
166181

167182
feewindows := daemon.mustInitializeStorage(cfg)
@@ -235,6 +250,17 @@ func createStellarCoreClient(cfg *config.Config) stellarcore.Client {
235250
}
236251
}
237252

253+
func createHighperfStellarCoreClient(cfg *config.Config) interfaces.FastCoreClient {
254+
// It doesn't make sense to create a client if the local server is not enabled
255+
if cfg.CaptiveCoreHTTPQueryPort == 0 {
256+
return nil
257+
}
258+
return &stellarcore.Client{
259+
URL: fmt.Sprintf("http://localhost:%d", cfg.CaptiveCoreHTTPQueryPort),
260+
HTTP: &http.Client{Timeout: cfg.CoreRequestTimeout},
261+
}
262+
}
263+
238264
func createIngestService(cfg *config.Config, logger *supportlog.Entry, daemon *Daemon,
239265
feewindows *feewindow.FeeWindows, historyArchive *historyarchive.ArchiveInterface,
240266
) *ingest.Service {
@@ -486,3 +512,6 @@ func (d *Daemon) Run() {
486512
return
487513
}
488514
}
515+
516+
// Ensure the daemon conforms to the interface
517+
var _ interfaces.Daemon = (*Daemon)(nil)

cmd/stellar-rpc/internal/daemon/interfaces/interfaces.go

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/stellar/go/ingest/ledgerbackend"
99
proto "github.com/stellar/go/protocols/stellarcore"
10+
"github.com/stellar/go/xdr"
1011
)
1112

1213
// Daemon defines the interface that the Daemon would be implementing.
@@ -16,10 +17,15 @@ type Daemon interface {
1617
MetricsRegistry() *prometheus.Registry
1718
MetricsNamespace() string
1819
CoreClient() CoreClient
20+
FastCoreClient() FastCoreClient
1921
GetCore() *ledgerbackend.CaptiveStellarCore
2022
}
2123

2224
type CoreClient interface {
2325
Info(ctx context.Context) (*proto.InfoResponse, error)
2426
SubmitTransaction(ctx context.Context, txBase64 string) (*proto.TXResponse, error)
2527
}
28+
29+
type FastCoreClient interface {
30+
GetLedgerEntries(ctx context.Context, ledgerSeq uint32, keys ...xdr.LedgerKey) (proto.GetLedgerEntryResponse, error)
31+
}

cmd/stellar-rpc/internal/daemon/interfaces/noOpDaemon.go

+11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/stellar/go/ingest/ledgerbackend"
99
proto "github.com/stellar/go/protocols/stellarcore"
10+
"github.com/stellar/go/xdr"
1011
)
1112

1213
// TODO: deprecate and rename to stellar_rpc
@@ -41,6 +42,10 @@ func (d *NoOpDaemon) CoreClient() CoreClient {
4142
return d.coreClient
4243
}
4344

45+
func (d *NoOpDaemon) FastCoreClient() FastCoreClient {
46+
return d.coreClient
47+
}
48+
4449
func (d *NoOpDaemon) GetCore() *ledgerbackend.CaptiveStellarCore {
4550
return d.core
4651
}
@@ -54,3 +59,9 @@ func (s noOpCoreClient) Info(context.Context) (*proto.InfoResponse, error) {
5459
func (s noOpCoreClient) SubmitTransaction(context.Context, string) (*proto.TXResponse, error) {
5560
return &proto.TXResponse{Status: proto.PreflightStatusOk}, nil
5661
}
62+
63+
func (s noOpCoreClient) GetLedgerEntries(context.Context,
64+
uint32, ...xdr.LedgerKey,
65+
) (proto.GetLedgerEntryResponse, error) {
66+
return proto.GetLedgerEntryResponse{}, nil
67+
}

cmd/stellar-rpc/internal/daemon/metrics.go

+4
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ func (d *Daemon) CoreClient() interfaces.CoreClient {
111111
return d.coreClient
112112
}
113113

114+
func (d *Daemon) FastCoreClient() interfaces.FastCoreClient {
115+
return d.coreQueryingClient
116+
}
117+
114118
func (d *Daemon) GetCore() *ledgerbackend.CaptiveStellarCore {
115119
return d.core
116120
}

cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go

+37-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,18 @@ import (
1515
)
1616

1717
func TestGetLedgerEntriesNotFound(t *testing.T) {
18-
test := infrastructure.NewTest(t, nil)
18+
t.Run("WithCore", func(t *testing.T) {
19+
testGetLedgerEntriesNotFound(t, true)
20+
})
21+
t.Run("WithoutCore", func(t *testing.T) {
22+
testGetLedgerEntriesNotFound(t, false)
23+
})
24+
}
25+
26+
func testGetLedgerEntriesNotFound(t *testing.T, useCore bool) {
27+
test := infrastructure.NewTest(t, &infrastructure.TestConfig{
28+
EnableCoreHTTPQueryServer: useCore,
29+
})
1930
client := test.GetRPCLient()
2031

2132
hash := xdr.Hash{0xa, 0xb}
@@ -48,7 +59,18 @@ func TestGetLedgerEntriesNotFound(t *testing.T) {
4859
}
4960

5061
func TestGetLedgerEntriesInvalidParams(t *testing.T) {
51-
test := infrastructure.NewTest(t, nil)
62+
t.Run("WithCore", func(t *testing.T) {
63+
testGetLedgerEntriesInvalidParams(t, true)
64+
})
65+
t.Run("WithoutCore", func(t *testing.T) {
66+
testGetLedgerEntriesInvalidParams(t, false)
67+
})
68+
}
69+
70+
func testGetLedgerEntriesInvalidParams(t *testing.T, useCore bool) {
71+
test := infrastructure.NewTest(t, &infrastructure.TestConfig{
72+
EnableCoreHTTPQueryServer: useCore,
73+
})
5274

5375
client := test.GetRPCLient()
5476

@@ -66,7 +88,18 @@ func TestGetLedgerEntriesInvalidParams(t *testing.T) {
6688
}
6789

6890
func TestGetLedgerEntriesSucceeds(t *testing.T) {
69-
test := infrastructure.NewTest(t, nil)
91+
t.Run("WithCore", func(t *testing.T) {
92+
testGetLedgerEntriesSucceeds(t, true)
93+
})
94+
t.Run("WithoutCore", func(t *testing.T) {
95+
testGetLedgerEntriesSucceeds(t, false)
96+
})
97+
}
98+
99+
func testGetLedgerEntriesSucceeds(t *testing.T, useCore bool) {
100+
test := infrastructure.NewTest(t, &infrastructure.TestConfig{
101+
EnableCoreHTTPQueryServer: useCore,
102+
})
70103
_, contractID, contractHash := test.CreateHelloWorldContract()
71104

72105
contractCodeKeyB64, err := xdr.MarshalBase64(xdr.LedgerKey{
@@ -117,7 +150,7 @@ func TestGetLedgerEntriesSucceeds(t *testing.T) {
117150
require.Equal(t, xdr.LedgerEntryTypeContractCode, firstEntry.Type)
118151
require.Equal(t, infrastructure.GetHelloWorldContract(), firstEntry.MustContractCode().Code)
119152

120-
require.Greater(t, result.Entries[1].LastModifiedLedger, uint32(0))
153+
require.Positive(t, result.Entries[1].LastModifiedLedger)
121154
require.LessOrEqual(t, result.Entries[1].LastModifiedLedger, result.LatestLedger)
122155
require.NotNil(t, result.Entries[1].LiveUntilLedgerSeq)
123156
require.Greater(t, *result.Entries[1].LiveUntilLedgerSeq, result.LatestLedger)

cmd/stellar-rpc/internal/integrationtest/infrastructure/docker/captive-core-integration-tests.cfg.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ PUBLIC_KEY="GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS"
2121

2222
# should be "core" when running RPC in a container or "localhost:port" when running RPC in the host
2323
ADDRESS="${CORE_HOST_PORT}"
24-
QUALITY="MEDIUM"
24+
QUALITY="MEDIUM"

cmd/stellar-rpc/internal/integrationtest/infrastructure/docker/docker-compose.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ services:
1313
# Note: Please keep the image pinned to an immutable tag matching the Captive Core version.
1414
# This avoids implicit updates which break compatibility between
1515
# the Core container and captive core.
16-
image: ${CORE_IMAGE:-stellar/stellar-core:22.0.0-2138.721fd0a65.focal}
16+
image: ${CORE_IMAGE:-stellar/unsafe-stellar-core:22.1.1-2251.ac9f21ac7.focal-do-not-use-in-prd}
17+
1718
depends_on:
1819
- core-postgres
1920
environment:
@@ -23,6 +24,8 @@ services:
2324
- "127.0.0.1:0:11625"
2425
# http
2526
- "127.0.0.1:0:11626"
27+
# high-perf http
28+
- "127.0.0.1:0:11628"
2629
# history archive
2730
- "127.0.0.1:0:1570"
2831
entrypoint: /usr/bin/env

0 commit comments

Comments
 (0)