Skip to content

Commit

Permalink
encapsulate api cache to a class
Browse files Browse the repository at this point in the history
  • Loading branch information
shunjizhan committed Nov 4, 2024
1 parent d7fb75a commit 95b49d3
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 30 deletions.
36 changes: 11 additions & 25 deletions packages/eth-providers/src/base-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from '@ethersproject/abstract-provider';
import { AcalaEvmTX, checkSignatureType, parseTransaction } from '@acala-network/eth-transactions';
import { AccessList, accessListify } from 'ethers/lib/utils';
import { ApiDecoration, SubmittableExtrinsic } from '@polkadot/api/types';
import { ApiPromise } from '@polkadot/api';
import { AsyncAction } from 'rxjs/internal/scheduler/AsyncAction';
import { AsyncScheduler } from 'rxjs/internal/scheduler/AsyncScheduler';
Expand All @@ -27,6 +26,7 @@ import { Logger } from '@ethersproject/logger';
import { ModuleEvmModuleAccountInfo } from '@polkadot/types/lookup';
import { Network } from '@ethersproject/networks';
import { Observable, ReplaySubject, Subscription, firstValueFrom, throwError } from 'rxjs';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { filter, first, timeout } from 'rxjs/operators';
import { getAddress } from '@ethersproject/address';
import { hexDataLength, hexValue, hexZeroPad, hexlify, isHexString, joinSignature } from '@ethersproject/bytes';
Expand Down Expand Up @@ -91,6 +91,7 @@ import { BlockCache, CacheInspect } from './utils/BlockCache';
import { MaxSizeSet } from './utils/MaxSizeSet';
import { SubqlProvider } from './utils/subqlProvider';
import { _Metadata } from './utils/gqlTypes';
import { apiCache } from './utils/ApiAtCache';

export interface HeadsInfo {
internalState: {
Expand Down Expand Up @@ -303,7 +304,6 @@ export abstract class BaseProvider extends AbstractProvider {
readonly verbose: boolean;
readonly maxBlockCacheSize: number;
readonly queryCache: LRUCache<string, any>;
readonly apiCache: LRUCache<string, ApiDecoration<'promise'>>;
readonly blockCache: BlockCache;
readonly finalizedBlockHashes: MaxSizeSet;

Expand Down Expand Up @@ -341,7 +341,6 @@ export abstract class BaseProvider extends AbstractProvider {
this.verbose = verbose;
this.maxBlockCacheSize = maxBlockCacheSize;
this.queryCache = new LRUCache({ max: storageCacheSize });
this.apiCache = new LRUCache({ max: 100 });
this.blockCache = new BlockCache(this.maxBlockCacheSize);
this.finalizedBlockHashes = new MaxSizeSet(this.maxBlockCacheSize);

Expand Down Expand Up @@ -572,19 +571,6 @@ export abstract class BaseProvider extends AbstractProvider {
await this.api.disconnect();
};

getApiAt = async (blockHash: string): Promise<ApiDecoration<'promise'>> => {
const cached = this.apiCache.get(blockHash);
if (cached) return cached;

const apiAt = await this.api.at(blockHash);

// do we need to check for finalization here?
// ApiAt is only a decoration and the actuall result is from rpc call, so should be fine?
this.apiCache.set(blockHash, apiAt);

return apiAt;
};

getNetwork = async (): Promise<Network> => {
await this.isReady();

Expand All @@ -609,7 +595,7 @@ export abstract class BaseProvider extends AbstractProvider {
);

getTimestamp = async (blockHash: string): Promise<number> => {
const apiAt = await this.getApiAt(blockHash);
const apiAt = await apiCache.getApiAt(this.api, blockHash);
const timestamp = await apiAt.query.timestamp.now();
return timestamp.toNumber();
};
Expand Down Expand Up @@ -717,7 +703,7 @@ export abstract class BaseProvider extends AbstractProvider {

const substrateAddress = await this.getSubstrateAddress(address, blockHash);

const apiAt = await this.getApiAt(blockHash);
const apiAt = await apiCache.getApiAt(this.api, blockHash);
const accountInfo = await apiAt.query.system.account(substrateAddress);

return nativeToEthDecimal(accountInfo.data.free.toBigInt());
Expand Down Expand Up @@ -759,7 +745,7 @@ export abstract class BaseProvider extends AbstractProvider {
const contractInfo = evmAccountInfo?.contractInfo.unwrapOr(null);
if (!contractInfo) { return '0x'; }

const apiAt = await this.getApiAt(blockHash);
const apiAt = await apiCache.getApiAt(this.api, blockHash);
const code = await apiAt.query.evm.codes(contractInfo.codeHash);

return code.toHex();
Expand Down Expand Up @@ -796,7 +782,7 @@ export abstract class BaseProvider extends AbstractProvider {
callRequest: SubstrateEvmCallRequest,
at?: string,
): Promise<CallReturnInfo> => {
const api = at ? await this.api.at(at) : this.api;
const api = at ? await apiCache.getApiAt(this.api, at) : this.api;

// call evm rpc when `state_call` is not supported yet
if (!api.call.evmRuntimeRPCApi) {
Expand Down Expand Up @@ -857,7 +843,7 @@ export abstract class BaseProvider extends AbstractProvider {
Promise.resolve(position).then(hexValue),
]);

const apiAt = await this.getApiAt(blockHash);
const apiAt = await apiCache.getApiAt(this.api, blockHash);
const code = await apiAt.query.evm.accountStorages(address, hexZeroPad(resolvedPosition, 32));

return code.toHex();
Expand Down Expand Up @@ -961,7 +947,7 @@ export abstract class BaseProvider extends AbstractProvider {
extrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>,
at?: string,
) => {
const apiAt = await this.api.at(at ?? await this.bestBlockHash);
const apiAt = await apiCache.getApiAt(this.api, at ?? await this.bestBlockHash);

const u8a = extrinsic.toU8a();
const lenIncreaseAfterSignature = 100; // approximate length increase after signature
Expand Down Expand Up @@ -1141,7 +1127,7 @@ export abstract class BaseProvider extends AbstractProvider {

getSubstrateAddress = async (address: string, blockTag?: BlockTag): Promise<string> => {
const blockHash = await this._getBlockHash(blockTag);
const apiAt = await this.getApiAt(blockHash);
const apiAt = await apiCache.getApiAt(this.api, blockHash);
const substrateAccount = await apiAt.query.evmAccounts.accounts(address);

return substrateAccount.isEmpty
Expand All @@ -1151,7 +1137,7 @@ export abstract class BaseProvider extends AbstractProvider {

getEvmAddress = async (substrateAddress: string, blockTag?: BlockTag): Promise<string> => {
const blockHash = await this._getBlockHash(blockTag);
const apiAt = await this.getApiAt(blockHash);
const apiAt = await apiCache.getApiAt(this.api, blockHash);
const evmAddress = await apiAt.query.evmAccounts.evmAddresses(substrateAddress);

return getAddress(evmAddress.isEmpty ? computeDefaultEvmAddress(substrateAddress) : evmAddress.toString());
Expand All @@ -1168,7 +1154,7 @@ export abstract class BaseProvider extends AbstractProvider {
this._getBlockHash(blockTag),
]);

const apiAt = await this.getApiAt(blockHash);
const apiAt = await apiCache.getApiAt(this.api, blockHash);
const accountInfo = await apiAt.query.evm.accounts(address);

return accountInfo.unwrapOr(null);
Expand Down
31 changes: 31 additions & 0 deletions packages/eth-providers/src/utils/ApiAtCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ApiDecoration } from '@polkadot/api/types';
import { ApiPromise } from '@polkadot/api';
import LRUCache from 'lru-cache';

class ApiAtCache {
#apiAtCache: LRUCache<string, ApiDecoration<'promise'>>;

constructor(maxCacheSize: number = 100) {
this.#apiAtCache = new LRUCache<string, ApiDecoration<'promise'>>({
max: maxCacheSize,
});
}

getApiAt = async (
api: ApiPromise,
blockHash: string
): Promise<ApiDecoration<'promise'>> => {
const cached = this.#apiAtCache.get(blockHash);
if (cached) return cached;

const apiAt = await api.at(blockHash);

// do we need to check for finalization here?
// ApiAt is only a decoration and the actuall result is from rpc call, so should be fine?
this.#apiAtCache.set(blockHash, apiAt);

return apiAt;
};
}

export const apiCache = new ApiAtCache(100);
17 changes: 12 additions & 5 deletions packages/eth-providers/src/utils/parseBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { GenericExtrinsic } from '@polkadot/types';
import { TransactionReceipt } from '@ethersproject/abstract-provider';

import { BIGNUMBER_ZERO, ONE_HUNDRED_GWEI } from '../consts';
import { apiCache } from './ApiAtCache';
import {
findEvmEvent,
formatter,
Expand All @@ -37,26 +38,32 @@ export const getAllReceiptsAtBlock = async (
blockHash: string,
targetTxHash?: string
): Promise<TransactionReceipt[]> => {
const apiAtTargetBlock = await api.at(blockHash); // TODO: integrate cache for this
const apiAt = await apiCache.getApiAt(api, blockHash);

const [block, blockEvents] = await Promise.all([
api.rpc.chain.getBlock(blockHash),
apiAtTargetBlock.query.system.events(),
apiAt.query.system.events(),
]);

return parseReceiptsFromBlockData(api, block, blockEvents, targetTxHash);
return await parseReceiptsFromBlockData(api, block, blockEvents, targetTxHash, true);
};

export const parseReceiptsFromBlockData = async (
api: ApiPromise,
block: SignedBlock,
blockEvents: FrameSystemEventRecord[],
targetTxHash?: string
targetTxHash?: string,
// this method is also used by subql, so disable cacheing by default to avoid potential compatibilty issues
useCache: boolean = false,
): Promise<TransactionReceipt[]> => {
const { header } = block.block;
const blockNumber = header.number.toNumber();
const blockHash = header.hash.toHex();
const _apiAtParentBlock = api.at(header.parentHash); // don't wait here in case not being used

// don't wait here in case not being used
const _apiAtParentBlock = useCache
? apiCache.getApiAt(api, header.parentHash.toHex())
: api.at(header.parentHash);

Check warning on line 66 in packages/eth-providers/src/utils/parseBlock.ts

View check run for this annotation

Codecov / codecov/patch

packages/eth-providers/src/utils/parseBlock.ts#L66

Added line #L66 was not covered by tests

const succeededEvmExtrinsics = block.block.extrinsics
.map((extrinsic, idx) => {
Expand Down

0 comments on commit 95b49d3

Please sign in to comment.