Skip to content

improve: refactor event config search #988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/arch/evm/SpokeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,20 +290,20 @@ export async function findFillEvent(
if (!blockNumber) return undefined;

// We can hardcode this to 0 to instruct paginatedEventQuery to make a single request for the same block number.
const maxBlockLookBack = 0;
const [fromBlock, toBlock] = [blockNumber, blockNumber];
const maxLookBack = 0;
const [from, to] = [blockNumber, blockNumber];

const query = (
await Promise.all([
paginatedEventQuery(
spokePool,
spokePool.filters.FilledRelay(null, null, null, null, null, relayData.originChainId, relayData.depositId),
{ fromBlock, toBlock, maxBlockLookBack }
{ from, to, maxLookBack }
),
paginatedEventQuery(
spokePool,
spokePool.filters.FilledV3Relay(null, null, null, null, null, relayData.originChainId, relayData.depositId),
{ fromBlock, toBlock, maxBlockLookBack }
{ from, to, maxLookBack }
),
])
).flat();
Expand Down
54 changes: 5 additions & 49 deletions src/arch/svm/SpokeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ export function getTimeAt(_spokePool: unknown, _blockNumber: number): Promise<nu
}

/**
* Retrieves the chain time at a particular block.
* Retrieves the chain time at a particular slot.
* @note This should be the same as getTimeAt() but can differ in test. These two functions should be consolidated.
* @returns The chain time at the specified block tag.
* @returns The chain time at the specified slot.
*/
export async function getTimestampForBlock(provider: Provider, blockNumber: number): Promise<number> {
const block = await provider.getBlock(BigInt(blockNumber)).send();
export async function getTimestampForSlot(provider: Provider, slotNumber: number): Promise<number> {
const block = await provider.getBlock(BigInt(slotNumber)).send();
let timestamp: number;
if (!block?.blockTime) {
console.error(`Unable to resolve svm block ${blockNumber}`);
console.error(`Unable to resolve svm block ${slotNumber}`);
timestamp = 0; // @todo: How to handle this?
} else {
timestamp = Number(block.blockTime); // Unix timestamps fit within number.
Expand Down Expand Up @@ -67,50 +67,6 @@ export function getDepositIdAtBlock(_contract: unknown, _blockTag: number): Prom
throw new Error("getDepositIdAtBlock: not implemented");
}

/**
* xxx todo
*/
export async function getSlotForBlock(
provider: Provider,
blockNumber: bigint,
lowSlot: bigint,
_highSlot?: bigint
): Promise<bigint | undefined> {
// @todo: Factor getBlock out to SlotFinder ??
const getBlockNumber = async (slot: bigint): Promise<bigint> => {
const block = await provider
.getBlock(slot, { transactionDetails: "none", maxSupportedTransactionVersion: 0 })
.send();
return block?.blockHeight ?? BigInt(0); // @xxx Handle undefined here!
};

let highSlot = _highSlot ?? (await provider.getSlot().send());
const [blockLow = 0, blockHigh = 1_000_000_000] = await Promise.all([
getBlockNumber(lowSlot),
getBlockNumber(highSlot),
]);

if (blockLow > blockNumber || blockHigh < blockNumber) {
return undefined; // blockNumber did not occur within the specified block range.
}

// Find the lowest slot number where blockHeight is greater than the requested blockNumber.
do {
const midSlot = (highSlot + lowSlot) / BigInt(2);
const midBlock = await getBlockNumber(midSlot);

if (midBlock < blockNumber) {
lowSlot = midSlot + BigInt(1);
} else if (midBlock > blockNumber) {
highSlot = midSlot + BigInt(1); // blockNumber occurred at or earlier than midBlock.
} else {
return midSlot;
}
} while (lowSlot <= highSlot);

return undefined;
}

export function findDepositBlock(
_spokePool: unknown,
depositId: BigNumber,
Expand Down
32 changes: 9 additions & 23 deletions src/arch/svm/eventsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import web3, {
import { bs58 } from "../../utils";
import { EventData, EventName, EventWithData } from "./types";
import { decodeEvent, isDevnet } from "./utils";
import { getSlotForBlock } from "./SpokeUtils";

// Utility type to extract the return type for the JSON encoding overload. We only care about the overload where the
// configuration parameter (C) has the optional property 'encoding' set to 'json'.
Expand Down Expand Up @@ -65,51 +64,38 @@ export class SvmSpokeEventsClient {
* Queries events for the SvmSpoke program filtered by event name.
*
* @param eventName - The name of the event to filter by.
* @param fromBlock - Optional starting block.
* @param toBlock - Optional ending block.
* @param fromSlot - Optional starting slot.
* @param toSlot - Optional ending slot.
* @param options - Options for fetching signatures.
* @returns A promise that resolves to an array of events matching the eventName.
*/
public async queryEvents<T extends EventData>(
eventName: EventName,
fromBlock?: bigint,
toBlock?: bigint,
fromSlot?: bigint,
toSlot?: bigint,
options: GetSignaturesForAddressConfig = { limit: 1000, commitment: "confirmed" }
): Promise<EventWithData<T>[]> {
const events = await this.queryAllEvents(fromBlock, toBlock, options);
const events = await this.queryAllEvents(fromSlot, toSlot, options);
return events.filter((event) => event.name === eventName) as EventWithData<T>[];
}

/**
* Queries all events for a specific program.
*
* @param fromBlock - Optional starting block.
* @param toBlock - Optional ending block.
* @param fromSlot - Optional starting slot.
* @param toSlot - Optional ending slot.
* @param options - Options for fetching signatures.
* @returns A promise that resolves to an array of all events with additional metadata.
*/
private async queryAllEvents(
fromBlock?: bigint,
toBlock?: bigint,
fromSlot?: bigint,
toSlot?: bigint,
options: GetSignaturesForAddressConfig = { limit: 1000, commitment: "confirmed" }
): Promise<EventWithData<EventData>[]> {
const allSignatures: GetSignaturesForAddressTransaction[] = [];
let hasMoreSignatures = true;
let currentOptions = options;

let fromSlot: bigint | undefined;
let toSlot: bigint | undefined;

if (fromBlock) {
const slot = await getSlotForBlock(this.rpc, fromBlock, BigInt(0));
fromSlot = slot;
}

if (toBlock) {
const slot = await getSlotForBlock(this.rpc, toBlock, BigInt(0));
toSlot = slot;
}

while (hasMoreSignatures) {
const signatures: GetSignaturesForAddressApiResponse = await this.rpc
.getSignaturesForAddress(this.svmSpokeAddress, currentOptions)
Expand Down
14 changes: 7 additions & 7 deletions src/clients/AcrossConfigStoreClient/AcrossConfigStoreClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ export class AcrossConfigStoreClient extends BaseAbstractClient {
constructor(
readonly logger: winston.Logger,
readonly configStore: Contract,
eventSearchConfig: MakeOptional<EventSearchConfig, "toBlock"> = { fromBlock: 0, maxBlockLookBack: 0 },
eventSearchConfig: MakeOptional<EventSearchConfig, "to"> = { from: 0, maxLookBack: 0 },
readonly configStoreVersion: number
) {
super(eventSearchConfig);
this.firstBlockToSearch = eventSearchConfig.fromBlock;
this.latestBlockSearched = 0;
this.firstHeightToSearch = eventSearchConfig.from;
this.latestHeightSearched = 0;
}

getRateModelForBlockNumber(
Expand Down Expand Up @@ -351,7 +351,7 @@ export class AcrossConfigStoreClient extends BaseAbstractClient {
return {
success: true,
chainId,
searchEndBlock: searchConfig.toBlock,
searchEndBlock: searchConfig.to,
events: {
updatedTokenConfigEvents,
updatedGlobalConfigEvents,
Expand Down Expand Up @@ -548,9 +548,9 @@ export class AcrossConfigStoreClient extends BaseAbstractClient {
}

this.hasLatestConfigStoreVersion = this.hasValidConfigStoreVersionForTimestamp();
this.latestBlockSearched = result.searchEndBlock;
this.firstBlockToSearch = result.searchEndBlock + 1; // Next iteration should start off from where this one ended.
this.eventSearchConfig.toBlock = undefined; // Caller can re-set on subsequent updates if necessary
this.latestHeightSearched = result.searchEndBlock;
this.firstHeightToSearch = result.searchEndBlock + 1; // Next iteration should start off from where this one ended.
this.eventSearchConfig.to = undefined; // Caller can re-set on subsequent updates if necessary
this.chainId = this.chainId ?? chainId; // Update on the first run only.
this.isUpdated = true;

Expand Down
26 changes: 13 additions & 13 deletions src/clients/BaseAbstractClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ export function isUpdateFailureReason(x: EventSearchConfig | UpdateFailureReason
*/
export abstract class BaseAbstractClient {
protected _isUpdated: boolean;
public firstBlockToSearch = 0;
public latestBlockSearched = 0;
public firstHeightToSearch = 0;
public latestHeightSearched = 0;

/**
* Creates a new client.
* @param cachingMechanism The caching mechanism to use for this client. If not provided, the client will not rely on an external cache.
*/
constructor(
readonly eventSearchConfig: MakeOptional<EventSearchConfig, "toBlock"> = { fromBlock: 0, maxBlockLookBack: 0 },
readonly eventSearchConfig: MakeOptional<EventSearchConfig, "to"> = { from: 0, maxLookBack: 0 },
protected cachingMechanism?: CachingMechanismInterface
) {
this._isUpdated = false;
Expand Down Expand Up @@ -62,25 +62,25 @@ export abstract class BaseAbstractClient {
public async updateSearchConfig(
provider: providers.Provider | SVMProvider
): Promise<EventSearchConfig | UpdateFailureReason> {
const fromBlock = this.firstBlockToSearch;
let { toBlock } = this.eventSearchConfig;
if (isDefined(toBlock)) {
if (fromBlock > toBlock) {
throw new Error(`Invalid event search config fromBlock (${fromBlock}) > toBlock (${toBlock})`);
const from = this.firstHeightToSearch;
let { to } = this.eventSearchConfig;
if (isDefined(to)) {
if (from > to) {
throw new Error(`Invalid event search config from (${from}) > to (${to})`);
}
} else {
if (provider instanceof providers.Provider) {
toBlock = await provider.getBlockNumber();
to = await provider.getBlockNumber();
} else {
toBlock = Number(await provider.getBlockHeight({ commitment: "confirmed" }).send());
to = Number(await provider.getSlot({ commitment: "confirmed" }).send());
}
if (toBlock < fromBlock) {
if (to < from) {
return UpdateFailureReason.AlreadyUpdated;
}
}

const { maxBlockLookBack } = this.eventSearchConfig;
return { fromBlock, toBlock, maxBlockLookBack };
const { maxLookBack } = this.eventSearchConfig;
return { from, to, maxLookBack };
}

/**
Expand Down
18 changes: 9 additions & 9 deletions src/clients/BundleDataClient/BundleDataClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export class BundleDataClient {
}

const bundle = this.clients.hubPoolClient.getLatestFullyExecutedRootBundle(
this.clients.hubPoolClient.latestBlockSearched
this.clients.hubPoolClient.latestHeightSearched
);
if (bundle !== undefined) {
refunds.push(await this.getPendingRefundsFromBundle(bundle));
Expand All @@ -317,7 +317,7 @@ export class BundleDataClient {
async getPendingRefundsFromBundle(bundle: ProposedRootBundle): Promise<CombinedRefunds> {
const nextBundleMainnetStartBlock = this.clients.hubPoolClient.getNextBundleStartBlockNumber(
this.chainIdListForBundleEvaluationBlockNumbers,
this.clients.hubPoolClient.latestBlockSearched,
this.clients.hubPoolClient.latestHeightSearched,
this.clients.hubPoolClient.chainId
);
const chainIds = this.clients.configStoreClient.getChainIdIndicesForBlock(nextBundleMainnetStartBlock);
Expand Down Expand Up @@ -447,7 +447,7 @@ export class BundleDataClient {
const hubPoolClient = this.clients.hubPoolClient;
const nextBundleMainnetStartBlock = hubPoolClient.getNextBundleStartBlockNumber(
this.chainIdListForBundleEvaluationBlockNumbers,
hubPoolClient.latestBlockSearched,
hubPoolClient.latestHeightSearched,
hubPoolClient.chainId
);
const chainIds = this.clients.configStoreClient.getChainIdIndicesForBlock(nextBundleMainnetStartBlock);
Expand All @@ -460,8 +460,8 @@ export class BundleDataClient {
this.spokePoolClients,
getEndBlockBuffers(chainIds, this.blockRangeEndBlockBuffer),
this.clients,
this.clients.hubPoolClient.latestBlockSearched,
this.clients.configStoreClient.getEnabledChains(this.clients.hubPoolClient.latestBlockSearched)
this.clients.hubPoolClient.latestHeightSearched,
this.clients.configStoreClient.getEnabledChains(this.clients.hubPoolClient.latestHeightSearched)
);
// Return block ranges for blocks after _pendingBlockRanges and up to widestBlockRanges.
// If a chain is disabled or doesn't have a spoke pool client, return a range of 0
Expand Down Expand Up @@ -728,7 +728,7 @@ export class BundleDataClient {
// hasn't queried. This is because this function will usually be called
// in production with block ranges that were validated by
// DataworkerUtils.blockRangesAreInvalidForSpokeClients.
Math.min(queryBlock, spokePoolClients[deposit.destinationChainId].latestBlockSearched)
Math.min(queryBlock, spokePoolClients[deposit.destinationChainId].latestHeightSearched)
);
};

Expand Down Expand Up @@ -1542,7 +1542,7 @@ export class BundleDataClient {
spokePoolClient.spokePool,
deposit,
spokePoolClient.deploymentBlock,
spokePoolClient.latestBlockSearched
spokePoolClient.latestHeightSearched
);
}

Expand Down Expand Up @@ -1570,13 +1570,13 @@ export class BundleDataClient {
// contain blocks where the spoke pool client hasn't queried. This is because this function
// will usually be called in production with block ranges that were validated by
// DataworkerUtils.blockRangesAreInvalidForSpokeClients.
const startBlockForChain = Math.min(_startBlockForChain, spokePoolClient.latestBlockSearched);
const startBlockForChain = Math.min(_startBlockForChain, spokePoolClient.latestHeightSearched);
// @dev Add 1 to the bundle end block. The thinking here is that there can be a gap between
// block timestamps in subsequent blocks. The bundle data client assumes that fill deadlines expire
// in exactly one bundle, therefore we must make sure that the bundle block timestamp for one bundle's
// end block is exactly equal to the bundle block timestamp for the next bundle's start block. This way
// there are no gaps in block timestamps between bundles.
const endBlockForChain = Math.min(_endBlockForChain + 1, spokePoolClient.latestBlockSearched);
const endBlockForChain = Math.min(_endBlockForChain + 1, spokePoolClient.latestHeightSearched);
const [startTime, _endTime] = [
await spokePoolClient.getTimestampForBlock(startBlockForChain),
await spokePoolClient.getTimestampForBlock(endBlockForChain),
Expand Down
2 changes: 1 addition & 1 deletion src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function getWidestPossibleExpectedBlockRange(
// filled during the challenge period.
const latestPossibleBundleEndBlockNumbers = chainIdListForBundleEvaluationBlockNumbers.map(
(chainId: number, index) =>
spokeClients[chainId] && Math.max(spokeClients[chainId].latestBlockSearched - endBlockBuffers[index], 0)
spokeClients[chainId] && Math.max(spokeClients[chainId].latestHeightSearched - endBlockBuffers[index], 0)
);
return chainIdListForBundleEvaluationBlockNumbers.map((chainId: number, index) => {
const lastEndBlockForChain = clients.hubPoolClient.getLatestBundleEndBlockForChain(
Expand Down
Loading