Skip to content

Commit

Permalink
Merge pull request #744 from LIT-Protocol/feature/lit-4083-use-an-eve…
Browse files Browse the repository at this point in the history
…n-older-blockhash-from-providers

feat: optimize and delay even more fallback rpcs for blockhash
  • Loading branch information
FedericoAmura authored Dec 23, 2024
2 parents b599a54 + 57beba8 commit 5fd2674
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 33 deletions.
22 changes: 17 additions & 5 deletions packages/core/src/lib/lit-core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,21 @@ describe('LitCore', () => {
timestamp: currentTime,
}),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
jest.spyOn(core as any, '_getProviderWithFallback').mockResolvedValue({
...mockProvider,
provider: mockProvider,
testResult: {
hash: mockBlockhash,
number: 12345,
timestamp: currentTime,
},
});

// Execute
const result = await core.getLatestBlockhash();

// Assert
expect(fetch).toHaveBeenCalledWith(mockBlockhashUrl);
expect(mockProvider.getBlock).toHaveBeenCalledWith(-1); // safety margin
expect(result).toBe(mockBlockhash);
});

Expand All @@ -132,16 +137,21 @@ describe('LitCore', () => {
timestamp: currentTime,
}),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
jest.spyOn(core as any, '_getProviderWithFallback').mockResolvedValue({
...mockProvider,
provider: mockProvider,
testResult: {
hash: mockBlockhash,
number: 12345,
timestamp: currentTime,
},
});

// Execute
const result = await core.getLatestBlockhash();

// Assert
expect(fetch).toHaveBeenCalledWith(mockBlockhashUrl);
expect(mockProvider.getBlock).toHaveBeenCalledWith(-1); // safety margin
expect(result).toBe(mockBlockhash);
});

Expand All @@ -164,8 +174,10 @@ describe('LitCore', () => {
getBlock: jest.fn().mockResolvedValue(null), // Provider also fails
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
jest.spyOn(core as any, '_getProviderWithFallback').mockResolvedValue({
...mockProvider,
provider: mockProvider,
testResult: null,
});

// Execute & Assert
Expand Down
65 changes: 37 additions & 28 deletions packages/core/src/lib/lit-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
LIT_CURVE,
LIT_CURVE_VALUES,
LIT_ENDPOINT,
LIT_ERROR,
LIT_ERROR_CODE,
LIT_NETWORK,
LIT_NETWORKS,
Expand Down Expand Up @@ -69,7 +68,6 @@ import {
NodeClientErrorV0,
NodeClientErrorV1,
NodeCommandServerKeysResponse,
NodeErrorV3,
RejectedNodePromises,
SendNodeCommand,
SessionSigsMap,
Expand All @@ -82,6 +80,10 @@ import { composeLitUrl } from './endpoint-version';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Listener = (...args: any[]) => void;

type providerTest<T> = (
provider: ethers.providers.JsonRpcProvider
) => Promise<T>;

interface CoreNodeConfig {
subnetPubKey: string;
networkPubKey: string;
Expand Down Expand Up @@ -119,8 +121,8 @@ export type LitNodeClientConfigWithDefaults = Required<
const EPOCH_PROPAGATION_DELAY = 45_000;
// This interval is responsible for keeping latest block hash up to date
const BLOCKHASH_SYNC_INTERVAL = 30_000;
// When fetching the blockhash from a provider (not lit), we use a previous block to avoid a nodes not knowing about the new block yet
const BLOCKHASH_COUNT_PROVIDER_DELAY = -1;
// When fetching the blockhash from a provider (not lit), we use a 5 minutes old block to ensure the nodes centralized indexer has it
const BLOCKHASH_COUNT_PROVIDER_DELAY = -30; // 30 blocks ago. Eth block are mined every 12s. 30 blocks is 6 minutes, indexer/nodes must have it by now

// Intentionally not including datil-dev here per discussion with Howard
const NETWORKS_REQUIRING_SEV: string[] = [
Expand Down Expand Up @@ -766,24 +768,31 @@ export class LitCore {
};
}

private _getProviderWithFallback =
async (): Promise<ethers.providers.JsonRpcProvider | null> => {
for (const url of FALLBACK_RPC_URLS) {
try {
const provider = new ethers.providers.JsonRpcProvider({
url: url,
private _getProviderWithFallback = async <T>(
providerTest: providerTest<T>
): Promise<{
provider: ethers.providers.JsonRpcProvider;
testResult: T;
} | null> => {
for (const url of FALLBACK_RPC_URLS) {
try {
const provider = new ethers.providers.JsonRpcProvider({
url: url,

// https://docs.ethers.org/v5/api/utils/web/#ConnectionInfo
timeout: 60000,
});
await provider.getBlockNumber(); // Simple check to see if the provider is working
return provider;
} catch (error) {
logError(`RPC URL failed: ${url}`);
}
// https://docs.ethers.org/v5/api/utils/web/#ConnectionInfo
timeout: 60000,
});
const testResult = await providerTest(provider); // Check to see if the provider is working
return {
provider,
testResult,
};
} catch (error) {
logError(`RPC URL failed: ${url}`);
}
return null;
};
}
return null;
};

/**
* Fetches the latest block hash and log any errors that are returned
Expand Down Expand Up @@ -854,20 +863,20 @@ export class LitCore {
log(
'Attempting to fetch blockhash manually using ethers with fallback RPC URLs...'
);
const provider = await this._getProviderWithFallback();
const { testResult } =
(await this._getProviderWithFallback<ethers.providers.Block>(
// We use a previous block to avoid nodes not having received the latest block yet
(provider) => provider.getBlock(BLOCKHASH_COUNT_PROVIDER_DELAY)
)) || {};

if (!provider) {
if (!testResult || !testResult.hash) {
logError('All fallback RPC URLs failed. Unable to retrieve blockhash.');
return;
}

try {
// We use a previous block to avoid nodes not having received the latest block yet
const priorBlock = await provider.getBlock(
BLOCKHASH_COUNT_PROVIDER_DELAY
);
this.latestBlockhash = priorBlock.hash;
this.lastBlockHashRetrieved = priorBlock.timestamp;
this.latestBlockhash = testResult.hash;
this.lastBlockHashRetrieved = testResult.timestamp;
log(
'Successfully retrieved blockhash manually: ',
this.latestBlockhash
Expand Down

0 comments on commit 5fd2674

Please sign in to comment.