Skip to content

Commit 871af8f

Browse files
committed
Add property-based configuration parsing and parsing tests for each.
1 parent 5a668a0 commit 871af8f

19 files changed

Lines changed: 1232 additions & 253 deletions

gradle/sava.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ developerName="Jim"
55
developerId="jpe7s"
66
developerEmail="james@glam.systems"
77
javaVersion=25
8-
solanaBOMVersion=25.14.3
8+
solanaBOMVersion=25.16.0

services/build.gradle.kts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ testModuleInfo {
33
runtimeOnly("org.junit.jupiter.engine")
44
}
55

6-
dependencies {
7-
project(":sdk")
8-
}
9-
106
//dependencyAnalysis {
117
// issues {
128
// onAny {
139
// severity("ignore")
1410
// }
1511
// }
1612
//}
13+
14+
dependencies {
15+
project(":sdk")
16+
17+
// project(":ravina:ravina-core")
18+
// project(":ravina:ravina-solana")
19+
}

services/src/main/java/systems/glam/services/config/AccountFetcherConfig.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package systems.glam.services.config;
22

3+
import software.sava.services.core.config.PropertiesParser;
34
import software.sava.services.core.config.ServiceConfigUtil;
45
import systems.comodal.jsoniter.FieldBufferPredicate;
56
import systems.comodal.jsoniter.JsonIterator;
67

78
import java.time.Duration;
9+
import java.util.Properties;
810

911
import static systems.comodal.jsoniter.JsonIterator.fieldEquals;
1012

@@ -14,20 +16,39 @@ public static AccountFetcherConfig createDefault() {
1416
return new AccountFetcherConfig(Duration.ofSeconds(5), false);
1517
}
1618

19+
public static AccountFetcherConfig parseConfig(final Properties properties) {
20+
return parseConfig("", properties);
21+
}
22+
23+
public static AccountFetcherConfig parseConfig(final String prefix, final Properties properties) {
24+
final var parser = new AccountFetcherConfig.Parser();
25+
parser.parseProperties(prefix, properties);
26+
return parser.createConfig();
27+
}
28+
1729
public static AccountFetcherConfig parseConfig(final JsonIterator ji) {
1830
final var parser = new AccountFetcherConfig.Parser();
1931
ji.testObject(parser);
2032
return parser.createConfig();
2133
}
2234

23-
private static final class Parser implements FieldBufferPredicate {
35+
private static final class Parser extends PropertiesParser implements FieldBufferPredicate {
2436

2537
private Duration fetchDelay;
2638
private boolean reactive;
2739

2840
private Parser() {
2941
}
3042

43+
private void parseProperties(final String prefix, final Properties properties) {
44+
final var p = propertyPrefix(prefix);
45+
final var fetchDelay = parseDuration(properties, p, "fetchDelay");
46+
if (fetchDelay != null) {
47+
this.fetchDelay = fetchDelay;
48+
}
49+
this.reactive = parseBoolean(properties, p, "reactive", this.reactive);
50+
}
51+
3152
private AccountFetcherConfig createConfig() {
3253
return new AccountFetcherConfig(
3354
fetchDelay == null ? Duration.ofSeconds(5) : fetchDelay,

services/src/main/java/systems/glam/services/config/BaseDelegateServiceConfig.java

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
import java.util.function.Consumer;
5555

5656
import static java.util.Objects.requireNonNullElse;
57+
import static software.sava.services.core.config.PropertiesParser.getProperty;
58+
import static software.sava.services.core.config.PropertiesParser.propertyPrefix;
5759
import static software.sava.services.solana.config.ChainItemFormatter.parseFormatter;
5860
import static software.sava.services.solana.load_balance.LoadBalanceUtil.createRPCLoadBalancer;
5961
import static systems.comodal.jsoniter.JsonIterator.fieldEquals;
@@ -253,6 +255,172 @@ protected ConfigParser(final ExecutorService taskExecutor, final HttpClient http
253255
this.httpClient = httpClient;
254256
}
255257

258+
protected void parseProperties(final String prefix, final Properties properties) {
259+
final var p = propertyPrefix(prefix);
260+
261+
final var glamStateKeyStr = getProperty(properties, p, "glamStateKey");
262+
if (glamStateKeyStr != null) {
263+
this.glamStateKey = PublicKey.fromBase58Encoded(glamStateKeyStr);
264+
}
265+
266+
final var signingServicePrefix = p + "signingService.";
267+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(signingServicePrefix))) {
268+
this.signingServiceConfig = SigningServiceConfig.parseConfig(
269+
taskExecutor, signingServicePrefix, DEFAULT_NETWORK_BACKOFF, properties
270+
);
271+
}
272+
273+
final var serviceBackoffPrefix = p + "serviceBackoff.";
274+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(serviceBackoffPrefix))) {
275+
final var backoffConfig = BackoffConfig.parse(serviceBackoffPrefix, properties);
276+
this.serviceBackoff = backoffConfig.createBackoff();
277+
}
278+
279+
final var formatterPrefix = p + "formatter.";
280+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(formatterPrefix))) {
281+
this.formatter = ChainItemFormatter.parseConfig(formatterPrefix, properties);
282+
}
283+
284+
final var notificationHooksPrefix = p + "notificationHooks.";
285+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(notificationHooksPrefix))) {
286+
final var webHookConfigs = WebHookConfig.parseConfigs(
287+
notificationHooksPrefix,
288+
properties,
289+
null,
290+
CapacityConfig.createSimpleConfig(
291+
Duration.ofSeconds(13),
292+
2,
293+
Duration.ofSeconds(1)
294+
),
295+
DEFAULT_NETWORK_BACKOFF
296+
);
297+
this.notifyClient = createNotifyClient(webHookConfigs);
298+
}
299+
300+
final var cacheDirectoryStr = getProperty(properties, p, "cacheDirectory");
301+
if (cacheDirectoryStr != null) {
302+
this.cacheDirectory = Path.of(cacheDirectoryStr);
303+
}
304+
305+
final var tableCachePrefix = p + "tableCache.";
306+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(tableCachePrefix))) {
307+
this.tableCacheConfig = TableCacheConfig.parseConfig(tableCachePrefix, properties);
308+
}
309+
310+
final var rpcCallWeightsPrefix = p + "rpcCallWeights.";
311+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(rpcCallWeightsPrefix))) {
312+
this.callWeights = CallWeights.parseConfig(rpcCallWeightsPrefix, properties);
313+
}
314+
315+
final var rpcPrefix = p + "rpc.";
316+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(rpcPrefix))) {
317+
final var loadBalancerConfig = LoadBalancerConfig.parse(
318+
rpcPrefix,
319+
properties,
320+
CapacityConfig.createSimpleConfig(
321+
Duration.ofSeconds(13),
322+
10,
323+
Duration.ofSeconds(1)
324+
),
325+
DEFAULT_NETWORK_BACKOFF
326+
);
327+
this.defaultRPCBackoff = loadBalancerConfig.defaultBackoff();
328+
this.rpcClients = createRPCLoadBalancer(loadBalancerConfig, httpClient);
329+
}
330+
331+
final var sendRPCPrefix = p + "sendRPC.";
332+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(sendRPCPrefix))) {
333+
final var loadBalancerConfig = LoadBalancerConfig.parse(
334+
sendRPCPrefix,
335+
properties,
336+
CapacityConfig.createSimpleConfig(
337+
Duration.ofSeconds(5),
338+
1,
339+
Duration.ofSeconds(1)
340+
),
341+
defaultRPCBackoff
342+
);
343+
this.sendClients = createRPCLoadBalancer(loadBalancerConfig, httpClient);
344+
}
345+
346+
final var websocketPrefix = p + "websocket.";
347+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(websocketPrefix))) {
348+
this.websocketConfig = RemoteResourceConfig.parseConfig(websocketPrefix, properties, null, DEFAULT_NETWORK_BACKOFF);
349+
}
350+
351+
final var epochServicePrefix = p + "epochService.";
352+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(epochServicePrefix))) {
353+
this.epochServiceConfig = EpochServiceConfig.parseConfig(epochServicePrefix, properties);
354+
}
355+
356+
final var txMonitorPrefix = p + "txMonitor.";
357+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(txMonitorPrefix))) {
358+
this.txMonitorConfig = TxMonitorConfig.parseConfig(txMonitorPrefix, properties);
359+
}
360+
361+
final var accountFetcherPrefix = p + "accountFetcher.";
362+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(accountFetcherPrefix))) {
363+
this.accountFetcherConfig = AccountFetcherConfig.parseConfig(accountFetcherPrefix, properties);
364+
}
365+
366+
final var defensivePollingPrefix = p + "defensivePolling.";
367+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(defensivePollingPrefix))) {
368+
this.defensivePollingConfig = DefensivePollingConfig.parseConfig(defensivePollingPrefix, properties);
369+
}
370+
371+
final var heliusPrefix = p + "helius.";
372+
if (properties.stringPropertyNames().stream().anyMatch(k -> k.startsWith(heliusPrefix))) {
373+
final var heliusConfig = HeliusConfig.parse(heliusPrefix, properties);
374+
final var heliusClient = heliusConfig.createClient(httpClient);
375+
final var balancedItem = BalancedItem.createItem(
376+
new HeliusFeeProvider(heliusClient),
377+
heliusConfig.capacityMonitor(),
378+
requireNonNullElse(heliusConfig.backoff(), DEFAULT_NETWORK_BACKOFF)
379+
);
380+
this.feeProviders = LoadBalancer.createBalancer(balancedItem);
381+
}
382+
383+
final var maxSOLPriorityFeeStr = getProperty(properties, p, "maxSOLPriorityFee");
384+
if (maxSOLPriorityFeeStr != null) {
385+
this.maxSOLPriorityFee = new BigDecimal(maxSOLPriorityFeeStr);
386+
}
387+
388+
final var warnFeePayerBalanceStr = getProperty(properties, p, "warnFeePayerBalance");
389+
if (warnFeePayerBalanceStr != null) {
390+
this.warnFeePayerBalance = new BigDecimal(warnFeePayerBalanceStr);
391+
}
392+
393+
final var minFeePayerBalanceStr = getProperty(properties, p, "minFeePayerBalance");
394+
if (minFeePayerBalanceStr != null) {
395+
this.minFeePayerBalance = new BigDecimal(minFeePayerBalanceStr);
396+
}
397+
398+
final var minCheckStateDelayStr = getProperty(properties, p, "minCheckStateDelay");
399+
if (minCheckStateDelayStr != null) {
400+
this.minCheckStateDelay = ServiceConfigUtil.parseDuration(minCheckStateDelayStr);
401+
}
402+
403+
final var maxCheckStateDelayStr = getProperty(properties, p, "maxCheckStateDelay");
404+
if (maxCheckStateDelayStr != null) {
405+
this.maxCheckStateDelay = ServiceConfigUtil.parseDuration(maxCheckStateDelayStr);
406+
}
407+
408+
final var defaultCuBudgetMultiplierStr = getProperty(properties, p, "defaultCuBudgetMultiplier");
409+
if (defaultCuBudgetMultiplierStr != null) {
410+
this.defaultCuBudgetMultiplier = Double.parseDouble(defaultCuBudgetMultiplierStr);
411+
}
412+
413+
final var maxTransactionRetriesStr = getProperty(properties, p, "maxTransactionRetries");
414+
if (maxTransactionRetriesStr != null) {
415+
this.maxTransactionRetries = Integer.parseInt(maxTransactionRetriesStr);
416+
}
417+
418+
final var hikariPropertiesFilesStr = getProperty(properties, p, "hikariPropertiesFiles");
419+
if (hikariPropertiesFilesStr != null) {
420+
this.hikariPropertiesFiles = List.of(hikariPropertiesFilesStr.split(","));
421+
}
422+
}
423+
256424
private NotifyClient createNotifyClient(final List<WebHookConfig> webHookConfigs) {
257425
final var webHookClients = webHookConfigs.stream()
258426
.map(webHookConfig -> webHookConfig.createCaller(httpClient))

services/src/main/java/systems/glam/services/config/DefensivePollingConfig.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package systems.glam.services.config;
22

3+
import software.sava.services.core.config.PropertiesParser;
34
import software.sava.services.core.config.ServiceConfigUtil;
45
import systems.comodal.jsoniter.FieldBufferPredicate;
56
import systems.comodal.jsoniter.JsonIterator;
67

78
import java.time.Duration;
9+
import java.util.Properties;
810

911
import static systems.comodal.jsoniter.JsonIterator.fieldEquals;
1012

@@ -16,6 +18,16 @@ public record DefensivePollingConfig(Duration globalConfig,
1618

1719
private static final Duration EIGHT_HOURS = Duration.ofHours(8);
1820

21+
public static DefensivePollingConfig parseConfig(final Properties properties) {
22+
return parseConfig("", properties);
23+
}
24+
25+
public static DefensivePollingConfig parseConfig(final String prefix, final Properties properties) {
26+
final var parser = new DefensivePollingConfig.Parser();
27+
parser.parseProperties(prefix, properties);
28+
return parser.createConfig();
29+
}
30+
1931
public static DefensivePollingConfig parseConfig(final JsonIterator ji) {
2032
final var parser = new DefensivePollingConfig.Parser();
2133
ji.testObject(parser);
@@ -32,7 +44,7 @@ public static DefensivePollingConfig createDefaultConfig() {
3244
);
3345
}
3446

35-
private static final class Parser implements FieldBufferPredicate {
47+
private static final class Parser extends PropertiesParser implements FieldBufferPredicate {
3648

3749
private Duration globalConfig;
3850
private Duration glamStateAccounts;
@@ -43,6 +55,30 @@ private static final class Parser implements FieldBufferPredicate {
4355
private Parser() {
4456
}
4557

58+
private void parseProperties(final String prefix, final Properties properties) {
59+
final var p = propertyPrefix(prefix);
60+
final var globalConfig = PropertiesParser.parseDuration(properties, p, "globalConfig");
61+
if (globalConfig != null) {
62+
this.globalConfig = globalConfig;
63+
}
64+
final var glamStateAccounts = PropertiesParser.parseDuration(properties, p, "glamStateAccounts");
65+
if (glamStateAccounts != null) {
66+
this.glamStateAccounts = glamStateAccounts;
67+
}
68+
final var integTables = PropertiesParser.parseDuration(properties, p, "integTables");
69+
if (integTables != null) {
70+
this.integTables = integTables;
71+
}
72+
final var stakePools = PropertiesParser.parseDuration(properties, p, "stakePools");
73+
if (stakePools != null) {
74+
this.stakePools = stakePools;
75+
}
76+
final var kaminoScope = PropertiesParser.parseDuration(properties, p, "kaminoScope");
77+
if (kaminoScope != null) {
78+
this.kaminoScope = kaminoScope;
79+
}
80+
}
81+
4682
private DefensivePollingConfig createConfig() {
4783
if (globalConfig == null) {
4884
globalConfig = Duration.ofMinutes(1);

services/src/main/java/systems/glam/services/fulfillment/config/FulfillmentServiceConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
import java.net.http.HttpClient;
1010
import java.nio.file.Files;
1111
import java.nio.file.Path;
12+
import java.util.Properties;
1213
import java.util.concurrent.ExecutorService;
1314

15+
import static software.sava.services.core.config.PropertiesParser.getProperty;
16+
import static software.sava.services.core.config.PropertiesParser.propertyPrefix;
1417
import static systems.comodal.jsoniter.JsonIterator.fieldEquals;
1518

1619
public record FulfillmentServiceConfig(DelegateServiceConfig delegateServiceConfig, boolean softRedeem) {
@@ -35,6 +38,16 @@ protected Parser(final ExecutorService taskExecutor, final HttpClient httpClient
3538
super(taskExecutor, httpClient);
3639
}
3740

41+
@Override
42+
protected void parseProperties(final String prefix, final Properties properties) {
43+
super.parseProperties(prefix, properties);
44+
final var p = propertyPrefix(prefix);
45+
final var softRedeemStr = getProperty(properties, p, "softRedeem");
46+
if (softRedeemStr != null) {
47+
this.softRedeem = Boolean.parseBoolean(softRedeemStr);
48+
}
49+
}
50+
3851
public FulfillmentServiceConfig createFulfillmentConfig() {
3952
final var delegateServiceConfig = createBaseConfig();
4053

services/src/main/java/systems/glam/services/integrations/kamino/KaminoCacheImpl.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,16 +406,14 @@ private void handleVaultStateChange(final AccountInfo<byte[]> accountInfo) {
406406
return;
407407
}
408408

409-
final var reserveKeys = KaminoVaultContext.parseReserveKeys(data);
410-
final var vaultLookupTable = PublicKey.readPubKey(data, VaultState.VAULT_LOOKUP_TABLE_OFFSET);
411409

412410
final KaminoVaultContext kaminoVaultContext;
413411
if (previous == null) {
414412
kaminoVaultContext = KaminoVaultContext.createContext(
415-
slot, accountInfo.pubKey(), data, reserveKeys, vaultLookupTable
413+
slot, data, accountInfo.pubKey(), sharesMint
416414
);
417415
} else {
418-
kaminoVaultContext = previous.withReserves(slot, reserveKeys, vaultLookupTable);
416+
kaminoVaultContext = previous.createIfChanged(slot, data);
419417
if (kaminoVaultContext == previous) {
420418
return;
421419
}

0 commit comments

Comments
 (0)