Skip to content

Commit

Permalink
add cache tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shunjizhan committed Nov 5, 2024
1 parent 95b49d3 commit 3404c82
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 45 deletions.
41 changes: 0 additions & 41 deletions packages/eth-providers/src/__tests__/evm-rpc-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,47 +55,6 @@ describe('getReceiptAtBlock', async () => {
});
});

// TODO: maybe setup a subway to test
describe.skip('all cache', async () => {
const provider = EvmRpcProvider.from(ACALA_NODE_URL);
await provider.isReady();

afterAll(async () => await provider.disconnect());

it('getBlockHeader at latest block => header cache', async () => {
const { time: time1, res: header1 } = await runWithTiming(() => provider._getBlockHeader('latest'), 1);
const { time: time2, res: header2 } = await runWithTiming(() => provider._getBlockHeader('latest'), 1);

// latest header should already be cached at the start
console.log('latest header:', { time1, time2 });
expect(time1).to.be.lt(10);
expect(time2).to.be.lt(10);
expect(header1.toJSON()).to.deep.equal(header2.toJSON());
});

it('getBlockHeader at random block => header cache', async () => {
const { time: time1, res: header1 } = await runWithTiming(() => provider._getBlockHeader(1234567), 1);
const { time: time2, res: header2 } = await runWithTiming(() => provider._getBlockHeader(1234567), 1);

// second time should be 100x faster with cache, in poor network 800ms => 0.5ms
console.log('getBlockHeader:', { time1, time2 });
expect(time2).to.be.lt(time1 / 20); // conservative multiplier
expect(time2).to.be.lt(10); // no async call
expect(header1.toJSON()).to.deep.equal(header2.toJSON());
});

it('getBlockData at random block => header cache + storage cache + receipt cache', async () => {
const { time: time1, res: blockData1 } = await runWithTiming(() => provider.getBlockData(1234321), 1);
const { time: time2, res: blockData2 } = await runWithTiming(() => provider.getBlockData(1234321), 1);

// second time should be 100x faster with cache, usually 1500ms => 3ms
console.log('getBlockData: ', { time1, time2 });
expect(time2).to.be.lt(time1 / 20); // conservative multiplier
expect(time2).to.be.lt(30); // no async call
expect(blockData1).to.deep.equal(blockData2);
});
});

describe.concurrent('rpc test', async () => {
const provider = EvmRpcProvider.from(endpoint);

Expand Down
115 changes: 115 additions & 0 deletions packages/eth-providers/src/__tests__/provider-cache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import dotenv from 'dotenv';

import { EvmRpcProvider } from '../rpc-provider';
import { apiCache } from '../utils/ApiAtCache';
import { runWithTiming } from '../utils';

dotenv.config();

const ACALA_NODE_URL = 'wss://acala-rpc.dwellir.com';

describe.only('provider cache', async () => {
let provider: EvmRpcProvider;
let provider2: EvmRpcProvider;

beforeAll(async () => {
provider = EvmRpcProvider.from(ACALA_NODE_URL);
provider2 = EvmRpcProvider.from(ACALA_NODE_URL); // provider 2 to query some info without affecting cache
await provider.isReady();
await provider2.isReady();
});

afterAll(async () => await Promise.all([
provider.disconnect(),
provider2.disconnect(),
]));

it('get apiAt', async() => {
const curBlock = await provider.getBlockNumber();
const randomBlock = curBlock - Math.floor(Math.random() * 100000);
const blockHash = await provider2._getBlockHash(randomBlock);

const { time: time1, res: apiAt1 } = await runWithTiming(() => apiCache.getApiAt(provider.api, blockHash), 1);
const { time: time2, res: apiAt2 } = await runWithTiming(() => apiCache.getApiAt(provider.api, blockHash), 1);

expect(time1).to.be.gt(0, 'first get apiAt failed!');
expect(time2).to.be.gt(0, 'second get apiAt failed!');
console.log('get random apiAt:', { time1, time2 });

expect(time2).to.be.lt(time1 / 20); // conservative multiplier
expect(time2).to.be.lt(50); // no async call so should be almost instant
expect(apiAt1).to.equal(apiAt2); // should be the same instance
});

it('get block hash', async() => {
const curBlock = await provider.getBlockNumber();
const randomBlock = curBlock - Math.floor(Math.random() * 100000);

const { time: time1, res: hash1 } = await runWithTiming(() => provider._getBlockHash(randomBlock), 1);
const { time: time2, res: hash2 } = await runWithTiming(() => provider._getBlockHash(randomBlock), 1);

expect(time1).to.be.gt(0, 'first get block hash failed!');
expect(time2).to.be.gt(0, 'second get block hash failed!');
console.log('get random block hash:', { time1, time2 });

expect(time2).to.be.lt(time1 / 20); // conservative multiplier
expect(time2).to.be.lt(50); // no async call so should be almost instant
expect(hash1).to.deep.equal(hash2);
});

it('get block', async() => {
const curBlock = await provider.getBlockNumber();
const randomBlock = curBlock - Math.floor(Math.random() * 100000);
const blockHash = await provider2._getBlockHash(randomBlock);

const { time: time1, res: blockNumber1 } = await runWithTiming(() => provider._getBlockNumber(blockHash), 1);
const { time: time2, res: blockNumber2 } = await runWithTiming(() => provider._getBlockNumber(blockHash), 1);

expect(time1).to.be.gt(0, 'first get block number failed!');
expect(time2).to.be.gt(0, 'second get block number failed!');
console.log('get random block number:', { time1, time2 });

expect(time2).to.be.lt(time1 / 20); // conservative multiplier
expect(time2).to.be.lt(50); // no async call so should be almost instant
expect(blockNumber1).to.deep.equal(blockNumber2);
});

it('get block header', async() => {
const curBlock = await provider.getBlockNumber();
const randomBlock = curBlock - Math.floor(Math.random() * 100000);

const { time: time1, res: header1 } = await runWithTiming(() => provider._getBlockHeader(randomBlock), 1);
const { time: time2, res: header2 } = await runWithTiming(() => provider._getBlockHeader(randomBlock), 1);

expect(time1).to.be.gt(0, 'first get header failed!');
expect(time2).to.be.gt(0, 'second get header failed!');
console.log('get random header:', { time1, time2 });

expect(time2).to.be.lt(time1 / 20); // conservative multiplier
expect(time2).to.be.lt(50); // no async call so should be almost instant
expect(header1.toJSON()).to.deep.equal(header2.toJSON());
});

it('get block data', async () => {
const curBlock = await provider.getBlockNumber();
const randomBlock = curBlock - Math.floor(Math.random() * 100000);

const { time: time1, res: blockData1 } = await runWithTiming(() => provider.getBlockData(randomBlock), 1);
const { time: time2, res: blockData2 } = await runWithTiming(() => provider.getBlockData(randomBlock), 1);
const { time: time3, res: blockData3 } = await runWithTiming(() => provider.getBlockData(randomBlock, true), 1);

expect(time1).to.be.gt(0, 'first get blockData failed!');
expect(time2).to.be.gt(0, 'second get blockData failed!');
expect(time3).to.be.gt(0, 'third get blockData failed!');
console.log('get random blockData:', { time1, time2, time3 });

expect(time2).to.be.lt(time1 / 20); // conservative multiplier
expect(time2).to.be.lt(50); // no async call so should be almost instant
expect(time3).to.be.lt(time1 / 20); // conservative multiplier
expect(time3).to.be.lt(50); // no async call so should be almost instant
expect(blockData1).to.deep.equal(blockData2);
expect(blockData3.hash).to.deep.equal(blockData2.hash);
});
});

8 changes: 4 additions & 4 deletions packages/eth-providers/src/utils/ApiAtCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { ApiPromise } from '@polkadot/api';
import LRUCache from 'lru-cache';

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

constructor(maxCacheSize: number = 100) {
this.#apiAtCache = new LRUCache<string, ApiDecoration<'promise'>>({
this.#cache = new LRUCache<string, ApiDecoration<'promise'>>({
max: maxCacheSize,
});
}
Expand All @@ -15,14 +15,14 @@ class ApiAtCache {
api: ApiPromise,
blockHash: string
): Promise<ApiDecoration<'promise'>> => {
const cached = this.#apiAtCache.get(blockHash);
const cached = this.#cache.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);
this.#cache.set(blockHash, apiAt);

return apiAt;
};
Expand Down

0 comments on commit 3404c82

Please sign in to comment.