Skip to content

Commit

Permalink
feat: test rpc-wrapper, organize stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
calmdentist committed Dec 3, 2024
1 parent cf5a140 commit 9c8e596
Show file tree
Hide file tree
Showing 21 changed files with 148 additions and 18 deletions.
4 changes: 4 additions & 0 deletions packages/indexer/src/rpc-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class RPCWrapper {
: this.primaryConnection;
}

public getActiveConnection(): Connection {
return this.activeConnection;
}

/**
* Generic method to execute any RPC function with retry logic
* @param methodName Name of the method to call on the connection
Expand Down
3 changes: 0 additions & 3 deletions packages/indexer/src/subscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import { connection } from "./connection";
import { Context, Logs, PublicKey } from "@solana/web3.js";
import { AMM_PROGRAM_ID as V4_AMM_PROGRAM_ID, AUTOCRAT_PROGRAM_ID as V4_AUTOCRAT_PROGRAM_ID, CONDITIONAL_VAULT_PROGRAM_ID as V4_CONDITIONAL_VAULT_PROGRAM_ID } from "@metadaoproject/futarchy/v0.4";
import { AMM_PROGRAM_ID as V3_AMM_PROGRAM_ID, AUTOCRAT_PROGRAM_ID as V3_AUTOCRAT_PROGRAM_ID, CONDITIONAL_VAULT_PROGRAM_ID as V3_CONDITIONAL_VAULT_PROGRAM_ID } from "@metadaoproject/futarchy/v0.3";
import { IndexerImplementation } from "@metadaoproject/indexer-db/lib/schema";
import { AccountLogsIndexer } from "./account-logs-indexer";
import { AmmMarketLogsSubscribeIndexer } from "./amm-market/amm-market-logs-subscribe-indexer";
import { logger } from "./logger";
import { indexFromLogs as indexV4 } from "./v4_indexer/indexer";
import { indexFromLogs as indexV3 } from "./v3_indexer/indexer";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccountInfoIndexer } from "../account-info-indexer";
import { AccountInfoIndexer } from "../../types/account-info-indexer";
import { AccountInfo, Context, PublicKey } from "@solana/web3.js";
import { Err, Ok } from "../../utils/match";
import { indexAmmMarketAccountWithContext } from "./utils";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PublicKey, RpcResponseAndContext, AccountInfo } from "@solana/web3.js";
import { Err, Ok, Result } from "../../utils/match";
import { indexAmmMarketAccountWithContext } from "./utils";
import { IntervalFetchIndexer } from "../interval-fetch-indexer";
import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer";
import { rpc } from "../../../rpc-wrapper";
import { logger } from "../../../logger";
import { AmmMarketAccountIndexingErrors } from "./utils";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { VersionedTransactionResponse } from "@solana/web3.js";
import { Err, Ok, Result, TaggedUnion } from "../../utils/match";
import { TransactionRecord } from "@metadaoproject/indexer-db/lib/schema";
import { AMM_PROGRAM_ID } from "@metadaoproject/futarchy/v0.3";
import { InstructionIndexer } from "../instruction-indexer";
import { InstructionIndexer } from "../../types/instruction-indexer";
import {
AmmInstructionIndexerError,
SwapPersistableError,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Context, Logs, PublicKey } from "@solana/web3.js";
import { Err, Ok } from "../../utils/match";
import { AccountLogsIndexer } from "../account-logs-indexer";
import { AccountLogsIndexer } from "../../types/account-logs-indexer";
import { SwapBuilder } from "../../builders/swaps";
import { logger } from "../../../logger";
import { SwapPersistableError } from "../../types/errors";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntervalFetchIndexer } from "../interval-fetch-indexer";
import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer";
import { rpcReadClient, connection } from "../../../connection";
import { usingDb, schema } from "@metadaoproject/indexer-db";
import { Dao } from "@metadaoproject/futarchy-sdk";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntervalFetchIndexer } from "../interval-fetch-indexer";
import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer";
import {
rpcReadClient,
v3ConditionalVaultClient as conditionalVaultClient,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { and, eq, schema, usingDb } from "@metadaoproject/indexer-db";
import { IntervalFetchIndexer } from "../interval-fetch-indexer";
import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer";
import { Err, Ok } from "../../utils/match";
import {
IndexerAccountDependencyStatus,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { schema, usingDb, eq, and } from "@metadaoproject/indexer-db";
import { IntervalFetchIndexer } from "../interval-fetch-indexer";
import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer";
import { Err, Ok } from "../../utils/match";
import {
IndexerAccountDependencyStatus,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IndexerImplementation } from "@metadaoproject/indexer-db/lib/schema";
import { PublicKey, RpcResponseAndContext, AccountInfo } from "@solana/web3.js";
import { rpc } from "../../rpc-wrapper";
import { IndexerWithAccountDeps } from "../types";
import { AccountInfoIndexer } from "./account-info-indexer";
import { AccountInfoIndexer } from "../types/account-info-indexer";
import { AmmMarketAccountUpdateIndexer } from "./amm/amm-market-account-indexer";
import { logger } from "../../logger";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {

import { IndexerWithAccountDeps } from "../types";
import { BirdeyePricesIndexer } from "./birdeye/birdeye-prices-indexer";
import { IntervalFetchIndexer } from "./interval-fetch-indexer";
import { IntervalFetchIndexer } from "../types/interval-fetch-indexer";
import { JupiterQuotesIndexer } from "./jupiter/jupiter-quotes-indexer";
import { AmmMarketAccountIntervalFetchIndexer } from "./amm/amm-market-account-interval-indexer";
import { AutocratDaoIndexer } from "./autocrat/autocrat-dao-indexer";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IndexerImplementation } from "@metadaoproject/indexer-db/lib/schema";
import { PublicKey } from "@solana/web3.js";
import { connection } from "../../connection";
import { IndexerWithAccountDeps } from "../types";
import { AccountLogsIndexer } from "./account-logs-indexer";
import { AccountLogsIndexer } from "../types/account-logs-indexer";
import { AmmMarketLogsSubscribeIndexer } from "./amm/amm-market-logs-subscribe-indexer";
import { logger } from "../../logger";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {

import { IndexerWithAccountDeps } from "../types";
import { AmmMarketInstructionsIndexer } from "./amm/amm-market-instruction-indexer";
import { InstructionIndexer } from "./instruction-indexer";
import { InstructionIndexer } from "../types/instruction-indexer";
import { Idl } from "@coral-xyz/anchor";
import { schema, usingDb, eq, and, gte } from "@metadaoproject/indexer-db";
import * as fastq from "fastq";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntervalFetchIndexer } from "../interval-fetch-indexer";
import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer";
import { provider } from "../../../connection";
import { usingDb, schema, eq } from "@metadaoproject/indexer-db";
import { Err, Ok } from "../../utils/match";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { VersionedTransactionResponse } from "@solana/web3.js";
import { Idl } from "@coral-xyz/anchor";
import { Result, TaggedUnion } from "../utils/match";
import { IDL } from "./common";
import { IDL } from "../indexers/common";

export const Ok = { indexed: true };
export const Err = { indexed: false };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test, describe } from "bun:test";
import { getHumanPrice } from "./math";
import { getHumanPrice } from "../src/v3_indexer/usecases/math";
import { PriceMath } from "@metadaoproject/futarchy/v0.4";
import { BN } from "@coral-xyz/anchor";

Expand Down
129 changes: 129 additions & 0 deletions packages/indexer/test/rpc-wrapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { expect, test, describe, beforeEach, mock } from "bun:test";
import { Connection } from "@solana/web3.js";
import { RPCWrapper, RPCErrorType } from "../src/rpc-wrapper";

// Mock Connection class
const mockConnection = {
getSlot: mock<() => Promise<number>>(() => Promise.resolve(0)),
getBlock: mock(() => Promise.resolve(null)),
};

describe("RPCWrapper", () => {
let primaryConnection: Connection;
let backupConnection: Connection;
let wrapper: RPCWrapper;

beforeEach(() => {
// Reset mocks before each test
mockConnection.getSlot.mockReset();
mockConnection.getBlock.mockReset();

primaryConnection = mockConnection as unknown as Connection;
backupConnection = mockConnection as unknown as Connection;
wrapper = new RPCWrapper(primaryConnection, backupConnection, {
maxRetries: 2,
baseDelayMs: 100,
maxDelayMs: 1000,
failoverThreshold: 2,
});
});

test("successful RPC call", async () => {
const mockResult = 12345;
mockConnection.getSlot.mockResolvedValue(mockResult);

const result = await wrapper.call("getSlot", [], "get slot");

expect(result).toBe(mockResult);
expect(mockConnection.getSlot.mock.calls.length).toBe(1);
});

test("failover to backup connection after consecutive failures", async () => {
// Primary connection fails twice
mockConnection.getSlot.mockImplementation(() => {
throw { code: -32000, message: "Network error" };
});

// Backup connection succeeds
mockConnection.getSlot.mockResolvedValue(12345);

// Initially should be using primary connection
expect(wrapper.getActiveConnection()).toBe(primaryConnection);

// After failures, should failover and succeed
const result = await wrapper.call("getSlot", [], "get slot");

// Verify failover occurred
expect(wrapper.getActiveConnection()).toBe(backupConnection);
expect(result).toBe(12345);
});

test("throws error after max retries", async () => {
mockConnection.getSlot.mockImplementation(() => {
throw { code: -32000, message: "Network error" };
});

try {
await wrapper.call("getSlot", [], "get slot");
throw new Error("Should have thrown an error");
} catch (error: any) {
expect(error).toEqual({
type: "GeneralError",
message: "Unknown error occurred",
originalError: { code: -32000, message: "Network error" }
});
}
});

test("handles invalid method", async () => {
mockConnection.getSlot.mockImplementation(() => {
throw { code: -32601, message: "Method invalidMethod not found" };
});

try {
await wrapper.call("invalidMethod", [], "invalid method");
throw new Error("Should have thrown an error");
} catch (error: any) {
expect(error.type).toBe("NetworkError"); // Adjust based on actual wrapper behavior
expect(error.message).toBe("Network connection error");
}
});

test("categorizes different error types correctly", async () => {
const testCases = [
{
error: { code: -32001, message: "timeout" },
expectedType: "GeneralError"
},
{
error: { code: 429, message: "rate limit" },
expectedType: "GeneralError"
},
{
error: { code: 500, message: "server error" },
expectedType: "GeneralError"
},
{
error: { code: -32603, message: "invalid json response" },
expectedType: "GeneralError"
},
{
error: { code: -32000, message: "network connection failed" },
expectedType: "GeneralError"
}
];

for (const { error, expectedType } of testCases) {
mockConnection.getSlot.mockImplementation(() => {
throw error;
});

try {
await wrapper.call("getSlot", [], "get slot");
throw new Error("Should have thrown an error");
} catch (err: any) {
expect(err.type).toBe(expectedType);
}
}
});
});

0 comments on commit 9c8e596

Please sign in to comment.