28: Add simple ObjectCache and cached SolanaBlockChain decorator
This commit is contained in:
@@ -2,7 +2,10 @@ package com.r35157.jupiterperpsalarm.impl.ref;
|
||||
|
||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
||||
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
|
||||
import com.r35157.libs.objcache.ObjectCache;
|
||||
import com.r35157.libs.objcache.impl.ref.ObjectCacheImpl;
|
||||
import com.r35157.libs.solana.SolanaBlockChain;
|
||||
import com.r35157.libs.solana.impl.cached.CachedSolanaBlockChain;
|
||||
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -46,8 +49,14 @@ public final class JupiterPerpsAlarmImpl {
|
||||
alarmConfiguration.variables()
|
||||
);
|
||||
|
||||
System.out.print("Initializing dependecies... ");
|
||||
SolanaBlockChain solanaBlockChain = new SolanaBlockChainImpl();
|
||||
System.out.print("Initializing dependencies... ");
|
||||
ObjectCache objectCache = new ObjectCacheImpl();
|
||||
SolanaBlockChain realSolanaBlockChain = new SolanaBlockChainImpl();
|
||||
SolanaBlockChain solanaBlockChain = new CachedSolanaBlockChain(
|
||||
realSolanaBlockChain,
|
||||
objectCache
|
||||
);
|
||||
|
||||
JupiterPerpsService jupiterPerpsService = new AnchorIdlJupiterPerpsServiceImpl(solanaBlockChain);
|
||||
JupiterPerpsEntryPriceVariableRefresher entryPriceVariableRefresher =
|
||||
new JupiterPerpsEntryPriceVariableRefresher(alarmConfiguration.variables(), jupiterPerpsService);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.r35157.libs.objcache;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ObjectCache {
|
||||
|
||||
void put(
|
||||
ObjectCacheKey key,
|
||||
Object object,
|
||||
long ttlMillis
|
||||
);
|
||||
|
||||
@Nullable
|
||||
Object get(
|
||||
ObjectCacheKey key
|
||||
);
|
||||
|
||||
void remove(
|
||||
ObjectCacheKey key
|
||||
);
|
||||
|
||||
void clear();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.r35157.libs.objcache;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ObjectCacheKey {
|
||||
|
||||
public ObjectCacheKey(
|
||||
String owner,
|
||||
String operation,
|
||||
Object... parameters
|
||||
) {
|
||||
this.owner = Objects.requireNonNull(owner);
|
||||
this.operation = Objects.requireNonNull(operation);
|
||||
this.parameters = List.copyOf(List.of(parameters));
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public String getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public List<Object> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(object instanceof ObjectCacheKey other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return owner.equals(other.owner)
|
||||
&& operation.equals(other.operation)
|
||||
&& parameters.equals(other.parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(owner, operation, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ObjectCacheKey{" +
|
||||
"owner='" + owner + '\'' +
|
||||
", operation='" + operation + '\'' +
|
||||
", parameters=" + parameters +
|
||||
'}';
|
||||
}
|
||||
|
||||
private final String owner;
|
||||
private final String operation;
|
||||
private final List<Object> parameters;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.r35157.libs.objcache.impl.ref;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ObjectCacheEntry {
|
||||
|
||||
public ObjectCacheEntry(
|
||||
Object object,
|
||||
long insertedAtMillis,
|
||||
long ttlMillis
|
||||
) {
|
||||
this.object = Objects.requireNonNull(object);
|
||||
this.insertedAtMillis = insertedAtMillis;
|
||||
this.ttlMillis = ttlMillis;
|
||||
}
|
||||
|
||||
public Object getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
public long getAgeMillis() {
|
||||
return System.currentTimeMillis() - insertedAtMillis;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return getAgeMillis() >= ttlMillis;
|
||||
}
|
||||
|
||||
private final Object object;
|
||||
private final long insertedAtMillis;
|
||||
private final long ttlMillis;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.r35157.libs.objcache.impl.ref;
|
||||
|
||||
import com.r35157.libs.objcache.ObjectCache;
|
||||
import com.r35157.libs.objcache.ObjectCacheKey;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public final class ObjectCacheImpl implements ObjectCache {
|
||||
|
||||
@Override
|
||||
public void put(
|
||||
ObjectCacheKey key,
|
||||
Object object,
|
||||
long ttlMillis
|
||||
) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(object);
|
||||
|
||||
if (ttlMillis < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"ttlMillis must be zero or greater"
|
||||
);
|
||||
}
|
||||
|
||||
objects.put(
|
||||
key,
|
||||
new ObjectCacheEntry(
|
||||
object,
|
||||
System.currentTimeMillis(),
|
||||
ttlMillis
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object get(
|
||||
ObjectCacheKey key
|
||||
) {
|
||||
Objects.requireNonNull(key);
|
||||
|
||||
ObjectCacheEntry entry = objects.get(key);
|
||||
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entry.isExpired()) {
|
||||
objects.remove(key, entry);
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(
|
||||
ObjectCacheKey key
|
||||
) {
|
||||
Objects.requireNonNull(key);
|
||||
objects.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
objects.clear();
|
||||
}
|
||||
|
||||
private final ConcurrentMap<
|
||||
ObjectCacheKey,
|
||||
ObjectCacheEntry
|
||||
> objects = new ConcurrentHashMap<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package com.r35157.libs.solana.impl.cached;
|
||||
|
||||
import com.r35157.libs.objcache.ObjectCache;
|
||||
import com.r35157.libs.objcache.ObjectCacheKey;
|
||||
import com.r35157.libs.solana.SPLTokenHolding;
|
||||
import com.r35157.libs.solana.SPLTokenSupply;
|
||||
import com.r35157.libs.solana.SolanaAccountInfo;
|
||||
import com.r35157.libs.solana.SolanaBlockChain;
|
||||
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
|
||||
import com.r35157.libs.solana.SolanaProgramAddressSeed;
|
||||
import com.r35157.libs.solana.valuetypes.SolanaProgramDerivedAddress;
|
||||
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public final class CachedSolanaBlockChain implements SolanaBlockChain {
|
||||
|
||||
public CachedSolanaBlockChain(
|
||||
SolanaBlockChain delegate,
|
||||
ObjectCache objectCache
|
||||
) {
|
||||
this.delegate = Objects.requireNonNull(delegate);
|
||||
this.objectCache = Objects.requireNonNull(objectCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ΩSolanaAmountΩ getBalanceInSolana(
|
||||
ΩSolanaAddressΩ address
|
||||
) throws IOException, InterruptedException {
|
||||
return delegate.getBalanceInSolana(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ΩlamportsΩ getBalanceInLamport(
|
||||
ΩSolanaAddressΩ address
|
||||
) throws IOException, InterruptedException {
|
||||
return delegate.getBalanceInLamport(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ΩSPLMintAddressΩ, SPLTokenHolding> getSPLTokenHoldings(
|
||||
ΩSolanaAddressΩ ownerAddress,
|
||||
SolanaSPLTokenProgram splProgramId
|
||||
) throws IOException, InterruptedException {
|
||||
long t0 = System.currentTimeMillis();
|
||||
Map<ΩSPLMintAddressΩ, SPLTokenHolding> result = delegate.getSPLTokenHoldings(
|
||||
ownerAddress,
|
||||
splProgramId
|
||||
);
|
||||
System.out.println("**F** LOOKUP TIME: " + (System.currentTimeMillis() - t0) + "ms");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ΩSolanaNFTAddressΩ> getSolanaNFTCandidateAddresses(
|
||||
ΩSolanaAddressΩ ownerAddress,
|
||||
SolanaSPLTokenProgram splProgram
|
||||
) throws IOException, InterruptedException {
|
||||
long t0 = System.currentTimeMillis();
|
||||
Set<ΩSolanaNFTAddressΩ> result = delegate.getSolanaNFTCandidateAddresses(
|
||||
ownerAddress,
|
||||
splProgram
|
||||
);
|
||||
System.out.println("@@@E@@@ LOOKUP TIME: " + (System.currentTimeMillis() - t0) + "ms");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SolanaProgramDerivedAddress findProgramAddress(
|
||||
ΩSolanaProgramIdΩ programId,
|
||||
List<SolanaProgramAddressSeed> seeds
|
||||
) {
|
||||
long t0 = System.currentTimeMillis();
|
||||
SolanaProgramDerivedAddress addr = delegate.findProgramAddress(
|
||||
programId,
|
||||
seeds
|
||||
);
|
||||
System.out.println("@@@D@@@ LOOKUP TIME: " + (System.currentTimeMillis() - t0) + "ms");
|
||||
return addr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SolanaAccountInfo getAccountInfo(
|
||||
ΩSolanaAddressΩ accountAddress
|
||||
) throws IOException, InterruptedException {
|
||||
long t0 = System.currentTimeMillis();
|
||||
SolanaAccountInfo result;
|
||||
|
||||
ObjectCacheKey key = new ObjectCacheKey(
|
||||
SolanaBlockChain.class.getName(),
|
||||
"getAccountInfo",
|
||||
accountAddress
|
||||
);
|
||||
|
||||
Object cachedObject = objectCache.get(key);
|
||||
|
||||
result = (cachedObject != null)
|
||||
? castAccountInfo(cachedObject)
|
||||
: loadAccountInfo(
|
||||
key,
|
||||
accountAddress
|
||||
);
|
||||
|
||||
System.out.println("@@@C@@@ LOOKUP TIME: " + (System.currentTimeMillis() - t0) + "ms");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ΩSolanaAddressΩ encodeSolanaAddress(
|
||||
byte[] addressBytes
|
||||
) {
|
||||
return delegate.encodeSolanaAddress(addressBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SPLTokenSupply getSPLTokenSupply(
|
||||
ΩSPLMintAddressΩ mintAddress,
|
||||
SolanaSPLTokenProgram splProgram
|
||||
) throws IOException, InterruptedException {
|
||||
long t0 = System.currentTimeMillis();
|
||||
SPLTokenSupply supply = delegate.getSPLTokenSupply(
|
||||
mintAddress,
|
||||
splProgram
|
||||
);
|
||||
System.out.println("@@@B@@@ LOOKUP TIME: " + (System.currentTimeMillis() - t0) + "ms");
|
||||
return supply;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SolanaAccountInfo> getProgramAccounts(
|
||||
ΩSolanaProgramIdΩ programId,
|
||||
Set<SolanaProgramAccountMemcmpFilter> filters
|
||||
) throws IOException, InterruptedException {
|
||||
long t0 = System.currentTimeMillis();
|
||||
Set<SolanaAccountInfo> result;
|
||||
Set<SolanaProgramAccountMemcmpFilter> copiedFilters =
|
||||
Set.copyOf(filters);
|
||||
|
||||
ObjectCacheKey key = new ObjectCacheKey(
|
||||
SolanaBlockChain.class.getName(),
|
||||
"getProgramAccounts",
|
||||
programId,
|
||||
copiedFilters
|
||||
);
|
||||
|
||||
Object cachedObject = objectCache.get(key);
|
||||
|
||||
result = (cachedObject != null)
|
||||
? castProgramAccounts(cachedObject)
|
||||
: loadProgramAccounts(
|
||||
key,
|
||||
programId,
|
||||
copiedFilters
|
||||
);
|
||||
|
||||
System.out.println("@@@A@@@ LOOKUP TIME: " + (System.currentTimeMillis() - t0) + "ms");
|
||||
return result;
|
||||
}
|
||||
|
||||
private synchronized Set<SolanaAccountInfo> loadProgramAccounts(
|
||||
ObjectCacheKey key,
|
||||
ΩSolanaProgramIdΩ programId,
|
||||
Set<SolanaProgramAccountMemcmpFilter> filters
|
||||
) throws IOException, InterruptedException {
|
||||
Object cachedObject = objectCache.get(key);
|
||||
|
||||
if (cachedObject != null) {
|
||||
return castProgramAccounts(cachedObject);
|
||||
}
|
||||
|
||||
Set<SolanaAccountInfo> programAccounts = Set.copyOf(
|
||||
delegate.getProgramAccounts(
|
||||
programId,
|
||||
filters
|
||||
)
|
||||
);
|
||||
|
||||
objectCache.put(
|
||||
key,
|
||||
programAccounts,
|
||||
PROGRAM_ACCOUNTS_TTL_MILLIS
|
||||
);
|
||||
|
||||
return programAccounts;
|
||||
}
|
||||
|
||||
private synchronized SolanaAccountInfo loadAccountInfo(
|
||||
ObjectCacheKey key,
|
||||
ΩSolanaAddressΩ accountAddress
|
||||
) throws IOException, InterruptedException {
|
||||
Object cachedObject = objectCache.get(key);
|
||||
|
||||
if (cachedObject != null) {
|
||||
return castAccountInfo(cachedObject);
|
||||
}
|
||||
|
||||
SolanaAccountInfo accountInfo = delegate.getAccountInfo(accountAddress);
|
||||
|
||||
objectCache.put(
|
||||
key,
|
||||
accountInfo,
|
||||
ACCOUNT_INFO_TTL_MILLIS
|
||||
);
|
||||
|
||||
return accountInfo;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Set<SolanaAccountInfo> castProgramAccounts(
|
||||
Object object
|
||||
) {
|
||||
return (Set<SolanaAccountInfo>) object;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private SolanaAccountInfo castAccountInfo(
|
||||
Object object
|
||||
) {
|
||||
return (SolanaAccountInfo) object;
|
||||
}
|
||||
|
||||
private static final long ACCOUNT_INFO_TTL_MILLIS = 60_000;
|
||||
private static final long PROGRAM_ACCOUNTS_TTL_MILLIS = 60_000;
|
||||
|
||||
private final SolanaBlockChain delegate;
|
||||
private final ObjectCache objectCache;
|
||||
}
|
||||
Reference in New Issue
Block a user