2023-11-28 09:54:07 -08:00
|
|
|
import { Circuit, Ratelimit, RatelimitError, Retry, RetryMode } from "mollitia";
|
2023-11-28 06:29:59 -08:00
|
|
|
import { solana } from "../../../domain/entities";
|
|
|
|
import { SolanaSlotRepository } from "../../../domain/repositories";
|
|
|
|
import { Fallible, SolanaFailure, ErrorType } from "../../../domain/errors";
|
2023-11-28 09:54:07 -08:00
|
|
|
import winston from "../../../infrastructure/log";
|
2023-11-28 13:29:25 -08:00
|
|
|
import { DynamicStrategy } from "../strategies/DynamicStrategy";
|
2023-11-28 06:29:59 -08:00
|
|
|
|
2023-11-28 13:29:25 -08:00
|
|
|
const CHAIN = "solana";
|
|
|
|
const NAME = "solana-slotRepo";
|
|
|
|
|
|
|
|
export class RateLimitedSolanaSlotRepository implements SolanaSlotRepository, DynamicStrategy {
|
2023-11-28 06:29:59 -08:00
|
|
|
delegate: SolanaSlotRepository;
|
|
|
|
breaker: Circuit;
|
2023-11-28 09:54:07 -08:00
|
|
|
logger: winston.Logger = winston.child({ module: "RateLimitedSolanaSlotRepository" });
|
2023-11-28 06:29:59 -08:00
|
|
|
|
|
|
|
constructor(delegate: SolanaSlotRepository, opts: Options = { period: 10_000, limit: 50 }) {
|
|
|
|
this.delegate = delegate;
|
|
|
|
this.breaker = new Circuit({
|
|
|
|
options: {
|
2023-11-28 09:54:07 -08:00
|
|
|
modules: [
|
|
|
|
new Ratelimit({ limitPeriod: opts.period, limitForPeriod: opts.limit }),
|
|
|
|
new Retry({
|
|
|
|
attempts: 1,
|
|
|
|
interval: 10_000,
|
|
|
|
fastFirst: false,
|
|
|
|
mode: RetryMode.LINEAR,
|
|
|
|
factor: 1,
|
|
|
|
onRejection: (err: Error | any) => {
|
|
|
|
if (err.message?.startsWith("429 Too Many Requests")) {
|
|
|
|
this.logger.warn("Got 429 from solana RPC node. Retrying in 10 secs...");
|
|
|
|
return 10_000; // Wait 10 secs if we get a 429
|
|
|
|
} else {
|
|
|
|
return false; // Dont retry, let the caller handle it
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
],
|
2023-11-28 06:29:59 -08:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-11-28 13:29:25 -08:00
|
|
|
apply(chain: string): boolean {
|
|
|
|
return chain === CHAIN;
|
|
|
|
}
|
|
|
|
|
|
|
|
getName(): string {
|
|
|
|
return NAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
createInstance(): RateLimitedSolanaSlotRepository {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2023-11-28 06:29:59 -08:00
|
|
|
getLatestSlot(commitment: string): Promise<number> {
|
|
|
|
return this.breaker.fn(() => this.delegate.getLatestSlot(commitment)).execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
async getBlock(slot: number, finality?: string): Promise<Fallible<solana.Block, SolanaFailure>> {
|
|
|
|
try {
|
|
|
|
const result: Fallible<solana.Block, SolanaFailure> = await this.breaker
|
|
|
|
.fn(() => this.delegate.getBlock(slot, finality))
|
|
|
|
.execute();
|
2023-11-28 09:54:07 -08:00
|
|
|
|
|
|
|
if (!result.isOk()) {
|
|
|
|
throw result.getError();
|
|
|
|
}
|
|
|
|
|
2023-11-28 06:29:59 -08:00
|
|
|
return result;
|
2023-11-28 09:54:07 -08:00
|
|
|
} catch (err: SolanaFailure | any) {
|
|
|
|
// this needs more handling due to delegate.getBlock returning a Fallible with a SolanaFailure
|
2023-11-28 06:29:59 -08:00
|
|
|
if (err instanceof RatelimitError) {
|
|
|
|
return Fallible.error(new SolanaFailure(0, err.message, ErrorType.Ratelimit));
|
|
|
|
}
|
|
|
|
|
2023-11-28 09:54:07 -08:00
|
|
|
if (err instanceof SolanaFailure) {
|
|
|
|
return Fallible.error(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Fallible.error(new SolanaFailure(err, err?.message ?? "unknown error"));
|
2023-11-28 06:29:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getSignaturesForAddress(
|
|
|
|
address: string,
|
|
|
|
beforeSig: string,
|
|
|
|
afterSig: string,
|
|
|
|
limit: number
|
|
|
|
): Promise<solana.ConfirmedSignatureInfo[]> {
|
|
|
|
return this.breaker
|
|
|
|
.fn(() => this.delegate.getSignaturesForAddress(address, beforeSig, afterSig, limit))
|
|
|
|
.execute(address, beforeSig, afterSig, limit);
|
|
|
|
}
|
|
|
|
|
|
|
|
getTransactions(sigs: solana.ConfirmedSignatureInfo[]): Promise<solana.Transaction[]> {
|
|
|
|
return this.breaker.fn(() => this.delegate.getTransactions(sigs)).execute(sigs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Options = {
|
|
|
|
period: number;
|
|
|
|
limit: number;
|
|
|
|
};
|