Skip to content

Commit

Permalink
feat(api balance endpoint): add spoke balance API endpoint
Browse files Browse the repository at this point in the history
Signed-off-by: james-a-morris <jaamorris@cs.stonybrook.edu>
  • Loading branch information
james-a-morris committed Oct 25, 2024
1 parent 58fda7f commit d371499
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 6 deletions.
11 changes: 8 additions & 3 deletions packages/indexer-api/src/controllers/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ export class BalancesController {
};

public getSpokePoolBalance = (
req: Request,
{ query }: Request,
res: Response,
next: NextFunction,
) => {
req.query && s.assert(req.query, SpokePoolBalanceParams);
res.json([]);
if (!s.is(query, SpokePoolBalanceParams)) {
return res.status(400).json({ error: "Invalid query" });
}
this.service
.spokePoolBalance(query)
.then((result) => res.json(result))
.catch((err) => next(err));
};
}
15 changes: 13 additions & 2 deletions packages/indexer-api/src/dtos/balances.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ export const HubPoolBalanceQueryParams = s.object({
// query spokepools by chainId, must specify
export const SpokePoolBalanceParams = s.object({
chainId: s.number(),
l1Token: s.optional(s.string()),
// unsure why we have timestamp, implies we are storign history of balances? this is in the spec.
timestamp: s.number(),
// unsure why specified as l2Token in spec, don't we have spoke pool on L1?
l2Token: s.optional(s.number()),
});

export type SpokePoolBalanceResultElement = {
lastExecutedRunningBalance: string;
pendingRunningBalance: string | null;
pendingNetSendAmount: string | null;
currentRunningBalance: string;
currentNetSendAmount: string;
};

export type SpokePoolBalanceResults = {
[chainId: string]: SpokePoolBalanceResultElement;
};
85 changes: 84 additions & 1 deletion packages/indexer-api/src/services/balances.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import assert from "assert";
import Redis from "ioredis";
import * as Indexer from "@repo/indexer";
import {
SpokePoolBalanceResultElement,
SpokePoolBalanceResults,
} from "../dtos/balances.dto";

export class BalancesService {
hubBalancesCache: Indexer.redis.hubBalancesCache.HubPoolBalanceCache;
constructor(private redis: Redis) {
currentBundleLeavesCache: Indexer.redis.bundleLeavesCache.BundleLeavesCache;
proposedBundleLeavesCache: Indexer.redis.bundleLeavesCache.BundleLeavesCache;
constructor(redis: Redis) {
this.hubBalancesCache =
new Indexer.redis.hubBalancesCache.HubPoolBalanceCache({
redis,
prefix: "hubBalanceCache",
});
this.currentBundleLeavesCache =
new Indexer.redis.bundleLeavesCache.BundleLeavesCache({
redis,
prefix: "currentBundleCache",
});
this.proposedBundleLeavesCache =
new Indexer.redis.bundleLeavesCache.BundleLeavesCache({
redis,
prefix: "proposedBundleCache",
});
}
async hubPoolBalance(params?: {
l1Token?: string;
Expand All @@ -22,4 +38,71 @@ export class BalancesService {
return this.hubBalancesCache.getAllL1Tokens();
}
}

async spokePoolBalance(params: {
chainId: number;
l1Token?: string;
}): Promise<SpokePoolBalanceResults> {
const { l1Token, chainId } = params;
const bundleLeaves = await resolveAllL1Tokens(
l1Token,
chainId,
this.proposedBundleLeavesCache,
this.currentBundleLeavesCache,
);
return bundleLeaves.reduce(
(acc, bundleLeaf) => ({
...acc,
[bundleLeaf.chainId]: {
lastExecutedRunningBalance: bundleLeaf.lastExecutedRunningBalance,
pendingRunningBalance: bundleLeaf.pendingRunningBalance,
pendingNetSendAmount: bundleLeaf.pendingNetSendAmount,
currentRunningBalance: bundleLeaf.currentRunningBalance,
currentNetSendAmount: bundleLeaf.currentNetSendAmount,
},
}),
{} as SpokePoolBalanceResults,
);
}
}

function combineBundleLeaf(
proposedBundleLeaf?: Indexer.redis.bundleLeavesCache.BundleLeaf,
currentBundleLeaf?: Indexer.redis.bundleLeavesCache.BundleLeaf,
): SpokePoolBalanceResultElement & { chainId: number } {
assert(currentBundleLeaf, "currentBundleLeaf is required");
return {
chainId: currentBundleLeaf.chainId,
lastExecutedRunningBalance: currentBundleLeaf.lastExecutedRunningBalance,
pendingRunningBalance: proposedBundleLeaf?.runningBalance ?? null,
pendingNetSendAmount: proposedBundleLeaf?.netSendAmount ?? null,
currentRunningBalance: currentBundleLeaf.runningBalance,
currentNetSendAmount: currentBundleLeaf.netSendAmount,
};
}

async function resolveAllL1Tokens(
l1Token: string | undefined,
chainId: number,
proposedCache: Indexer.redis.bundleLeavesCache.BundleLeavesCache,
currentCache: Indexer.redis.bundleLeavesCache.BundleLeavesCache,
): Promise<(SpokePoolBalanceResultElement & { chainId: number })[]> {
if (l1Token) {
const currentL1Cache = await currentCache.get(chainId, l1Token);
const proposedL1Cache = await proposedCache.get(chainId, l1Token);
return [combineBundleLeaf(proposedL1Cache, currentL1Cache)];
} else {
const currentL1Cache = await currentCache.getByChainId(chainId);
return Promise.all(
currentL1Cache
.filter((v) => v !== undefined)
.map(async (currentCacheValue) => {
const proposedCacheValue = await proposedCache.get(
currentCacheValue.chainId,
currentCacheValue.l1Token,
);
return combineBundleLeaf(proposedCacheValue, currentCacheValue);
}),
);
}
}
1 change: 1 addition & 0 deletions packages/indexer/src/redis/bundleLeavesCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const BundleLeaf = s.object({
l1Token: s.string(),
netSendAmount: s.string(),
runningBalance: s.string(),
lastExecutedRunningBalance: s.string(),
});
export type BundleLeaf = s.Infer<typeof BundleLeaf>;
export type BundleLeaves = BundleLeaf[];
Expand Down
23 changes: 23 additions & 0 deletions packages/indexer/src/services/BundleBuilderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,22 @@ export class BundleBuilderService extends BaseIndexer {
// and not any specific proposal
convertProposalRangeResultToProposalRange(ranges),
);

// first clear the cache to prepare for update
await this.currentBundleCache.clear();
// Persist this to Redis
await Promise.all(
resultsToPersist.flatMap((leaf) => {
const lastExecutedRunningBalance =
lastExecutedBundle.proposal.bundleEvaluationBlockNumbers[
lastExecutedBundle.proposal.chainIds.findIndex(
(chainId) => leaf.chainId === chainId,
)
];
assert(
lastExecutedRunningBalance,
"Last executed running balance not found",
);
assert(
leaf.l1Tokens.length == leaf.netSendAmounts.length,
"Net send amount count does not match token counts",
Expand All @@ -264,6 +275,7 @@ export class BundleBuilderService extends BaseIndexer {
l1Token,
netSendAmount: leaf.netSendAmounts[tokenIndex]!,
runningBalance: leaf.runningBalances[tokenIndex]!,
lastExecutedRunningBalance: String(lastExecutedRunningBalance),
});
});
}),
Expand Down Expand Up @@ -309,6 +321,16 @@ export class BundleBuilderService extends BaseIndexer {
// Persist this to Redis
await Promise.all(
resultsToPersist.flatMap((leaf) => {
const lastExecutedRunningBalance =
lastExecutedBundle.proposal.bundleEvaluationBlockNumbers[
lastExecutedBundle.proposal.chainIds.findIndex(
(chainId) => leaf.chainId === chainId,
)
];
assert(
lastExecutedRunningBalance,
"Last executed running balance not found",
);
assert(
leaf.l1Tokens.length == leaf.netSendAmounts.length,
"Net send amount count does not match token counts",
Expand All @@ -323,6 +345,7 @@ export class BundleBuilderService extends BaseIndexer {
l1Token,
netSendAmount: leaf.netSendAmounts[tokenIndex]!,
runningBalance: leaf.runningBalances[tokenIndex]!,
lastExecutedRunningBalance: String(lastExecutedRunningBalance),
});
});
}),
Expand Down

0 comments on commit d371499

Please sign in to comment.