Skip to content

Commit e665389

Browse files
committed
feat: tip metric, test update
1 parent 5bfb64a commit e665389

File tree

4 files changed

+272
-109
lines changed

4 files changed

+272
-109
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
.eslintcache
66
.vscode
77
.idea
8+
.claude
89
coverage
910
coverage-ts
1011
dist

__tests__/rpcProvider.test.ts

Lines changed: 106 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,26 @@ import { RpcProvider as BaseRpcProvider } from '../src/provider/rpc';
4242
import { RpcProvider as ExtendedRpcProvider } from '../src/provider/extensions/default';
4343
import { StarknetId } from '../src/provider/extensions/starknetId';
4444

45+
/**
46+
* Helper function to create expected zero tip estimate for tests
47+
*/
48+
function expectZeroTipEstimate() {
49+
return {
50+
minTip: 0n,
51+
maxTip: 0n,
52+
averageTip: 0n,
53+
medianTip: 0n,
54+
modeTip: 0n,
55+
recommendedTip: 0n,
56+
p90Tip: 0n,
57+
p95Tip: 0n,
58+
metrics: expect.objectContaining({
59+
blocksAnalyzed: expect.any(Number),
60+
transactionsFound: expect.any(Number),
61+
}),
62+
};
63+
}
64+
4565
describeIfRpc('RPCProvider', () => {
4666
let rpcProvider: RpcProvider;
4767
let provider: ProviderInterface;
@@ -481,31 +501,37 @@ describeIfRpc('RPCProvider', () => {
481501
describe('Tip Estimation', () => {
482502
describeIfRpc('getEstimateTip', () => {
483503
test('should estimate tip from latest block or handle insufficient data', async () => {
484-
try {
485-
const tipEstimate = await rpcProvider.getEstimateTip('latest', {
486-
minTxsNecessary: 1, // Use low threshold for test reliability
487-
maxBlocks: 10, // Use more blocks to increase chance of finding data
488-
});
504+
const tipEstimate = await rpcProvider.getEstimateTip('latest', {
505+
minTxsNecessary: 1, // Use low threshold for test reliability
506+
maxBlocks: 10, // Use more blocks to increase chance of finding data
507+
});
489508

490-
expect(tipEstimate).toBeDefined();
491-
expect(tipEstimate).toEqual({
492-
minTip: expect.any(BigInt),
493-
maxTip: expect.any(BigInt),
494-
averageTip: expect.any(BigInt),
495-
medianTip: expect.any(BigInt),
496-
modeTip: expect.any(BigInt),
497-
recommendedTip: expect.any(BigInt),
498-
});
509+
expect(tipEstimate).toBeDefined();
510+
expect(tipEstimate).toEqual({
511+
minTip: expect.any(BigInt),
512+
maxTip: expect.any(BigInt),
513+
averageTip: expect.any(BigInt),
514+
medianTip: expect.any(BigInt),
515+
modeTip: expect.any(BigInt),
516+
recommendedTip: expect.any(BigInt),
517+
p90Tip: expect.any(BigInt),
518+
p95Tip: expect.any(BigInt),
519+
metrics: expect.objectContaining({
520+
blocksAnalyzed: expect.any(Number),
521+
transactionsFound: expect.any(Number),
522+
}),
523+
});
499524

525+
// If there's insufficient data, all values should be 0n
526+
if (tipEstimate.recommendedTip === 0n) {
527+
expect(tipEstimate).toEqual(expectZeroTipEstimate());
528+
} else {
500529
// Verify tip relationships
501530
expect(tipEstimate.minTip).toBeLessThanOrEqual(tipEstimate.maxTip);
502531
expect(tipEstimate.recommendedTip).toBeGreaterThan(0n);
503532

504533
// Verify recommended tip is median tip (no buffer)
505534
expect(tipEstimate.recommendedTip).toBe(tipEstimate.medianTip);
506-
} catch (error) {
507-
// In test environments, there might not be enough V3 invoke transactions with tips
508-
expect((error as Error).message).toContain('Insufficient transaction data');
509535
}
510536
});
511537

@@ -549,26 +575,28 @@ describeIfRpc('RPCProvider', () => {
549575
});
550576

551577
test('should work with custom maxBlocks or handle insufficient data', async () => {
552-
try {
553-
const tipEstimate = await rpcProvider.getEstimateTip('latest', {
554-
minTxsNecessary: 1,
555-
maxBlocks: 20, // Analyze more blocks
556-
});
578+
const tipEstimate = await rpcProvider.getEstimateTip('latest', {
579+
minTxsNecessary: 1,
580+
maxBlocks: 20, // Analyze more blocks
581+
});
557582

558-
expect(tipEstimate).toBeDefined();
583+
expect(tipEstimate).toBeDefined();
584+
585+
// If there's insufficient data, all values should be 0n
586+
if (tipEstimate.recommendedTip === 0n) {
587+
expect(tipEstimate).toEqual(expectZeroTipEstimate());
588+
} else {
559589
expect(tipEstimate.recommendedTip).toBeGreaterThan(0n);
560-
} catch (error) {
561-
expect((error as Error).message).toContain('Insufficient transaction data');
562590
}
563591
});
564592

565-
test('should throw error with insufficient transaction data', async () => {
566-
await expect(
567-
rpcProvider.getEstimateTip('latest', {
568-
minTxsNecessary: 1000, // Unreasonably high requirement
569-
maxBlocks: 1,
570-
})
571-
).rejects.toThrow('Insufficient transaction data');
593+
test('should return zero values with insufficient transaction data', async () => {
594+
const tipEstimate = await rpcProvider.getEstimateTip('latest', {
595+
minTxsNecessary: 1000, // Unreasonably high requirement
596+
maxBlocks: 1,
597+
});
598+
599+
expect(tipEstimate).toEqual(expectZeroTipEstimate());
572600
});
573601

574602
describeIfDevnet('with devnet transactions', () => {
@@ -628,27 +656,31 @@ describeIfRpc('RPCProvider', () => {
628656
await createBlockForDevnet();
629657
}
630658

631-
try {
632-
// Test with different block ranges
633-
const smallRange = await rpcProvider.getEstimateTip('latest', {
634-
minTxsNecessary: 1,
635-
maxBlocks: 1,
636-
});
659+
// Test with different block ranges
660+
const smallRange = await rpcProvider.getEstimateTip('latest', {
661+
minTxsNecessary: 1,
662+
maxBlocks: 1,
663+
});
637664

638-
const largeRange = await rpcProvider.getEstimateTip('latest', {
639-
minTxsNecessary: 1,
640-
maxBlocks: 10,
641-
});
665+
const largeRange = await rpcProvider.getEstimateTip('latest', {
666+
minTxsNecessary: 1,
667+
maxBlocks: 10,
668+
});
642669

643-
expect(smallRange).toBeDefined();
644-
expect(largeRange).toBeDefined();
670+
expect(smallRange).toBeDefined();
671+
expect(largeRange).toBeDefined();
645672

646-
// Both should have valid recommendations
673+
// If insufficient data, values should be 0n
674+
if (smallRange.recommendedTip === 0n) {
675+
expect(smallRange).toEqual(expectZeroTipEstimate());
676+
} else {
647677
expect(smallRange.recommendedTip).toBeGreaterThan(0n);
678+
}
679+
680+
if (largeRange.recommendedTip === 0n) {
681+
expect(largeRange).toEqual(expectZeroTipEstimate());
682+
} else {
648683
expect(largeRange.recommendedTip).toBeGreaterThan(0n);
649-
} catch (error) {
650-
// Expected if devnet transactions don't include tips
651-
expect((error as Error).message).toContain('Insufficient transaction data');
652684
}
653685
});
654686
});
@@ -660,27 +692,34 @@ describeIfRpc('RPCProvider', () => {
660692
batch: 50, // Enable batching
661693
});
662694

663-
try {
664-
const tipEstimate = await batchedProvider.getEstimateTip('latest', {
665-
minTxsNecessary: 1,
666-
maxBlocks: 10,
667-
});
695+
const tipEstimate = await batchedProvider.getEstimateTip('latest', {
696+
minTxsNecessary: 1,
697+
maxBlocks: 10,
698+
});
668699

669-
expect(tipEstimate).toBeDefined();
670-
expect(tipEstimate.recommendedTip).toBeGreaterThan(0n);
700+
expect(tipEstimate).toBeDefined();
701+
702+
// Verify the structure is correct
703+
expect(tipEstimate).toEqual({
704+
minTip: expect.any(BigInt),
705+
maxTip: expect.any(BigInt),
706+
averageTip: expect.any(BigInt),
707+
medianTip: expect.any(BigInt),
708+
modeTip: expect.any(BigInt),
709+
recommendedTip: expect.any(BigInt),
710+
p90Tip: expect.any(BigInt),
711+
p95Tip: expect.any(BigInt),
712+
metrics: expect.objectContaining({
713+
blocksAnalyzed: expect.any(Number),
714+
transactionsFound: expect.any(Number),
715+
}),
716+
});
671717

672-
// Verify the structure is the same as non-batched
673-
expect(tipEstimate).toEqual({
674-
minTip: expect.any(BigInt),
675-
maxTip: expect.any(BigInt),
676-
averageTip: expect.any(BigInt),
677-
medianTip: expect.any(BigInt),
678-
modeTip: expect.any(BigInt),
679-
recommendedTip: expect.any(BigInt),
680-
});
681-
} catch (error) {
682-
// Batching doesn't change data availability
683-
expect((error as Error).message).toContain('Insufficient transaction data');
718+
// If insufficient data, values should be 0n
719+
if (tipEstimate.recommendedTip === 0n) {
720+
expect(tipEstimate).toEqual(expectZeroTipEstimate());
721+
} else {
722+
expect(tipEstimate.recommendedTip).toBeGreaterThan(0n);
684723
}
685724
});
686725

__tests__/utils/tip.test.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ describe('Tip Analysis', () => {
124124
medianTip: 25n, // (20+30)/2 = 25
125125
modeTip: 20n, // appears twice
126126
recommendedTip: 25n, // median tip directly (no buffer)
127+
p90Tip: expect.any(BigInt), // 90th percentile
128+
p95Tip: expect.any(BigInt), // 95th percentile
129+
metrics: expect.objectContaining({
130+
blocksAnalyzed: expect.any(Number),
131+
transactionsFound: expect.any(Number),
132+
}),
127133
});
128134
});
129135

@@ -147,6 +153,12 @@ describe('Tip Analysis', () => {
147153
medianTip: 200n,
148154
modeTip: 100n, // First occurrence when all have same count
149155
recommendedTip: 200n, // median tip directly (no buffer)
156+
p90Tip: expect.any(BigInt), // 90th percentile
157+
p95Tip: expect.any(BigInt), // 95th percentile
158+
metrics: expect.objectContaining({
159+
blocksAnalyzed: expect.any(Number),
160+
transactionsFound: expect.any(Number),
161+
}),
150162
});
151163
});
152164

@@ -208,16 +220,27 @@ describe('Tip Analysis', () => {
208220
expect(result.averageTip).toBe(20n); // (10+20+30)/3 = 20
209221
});
210222

211-
test('should throw error when insufficient transaction data', async () => {
223+
test('should return zero values when insufficient transaction data', async () => {
212224
const transactions = [createMockInvokeTransaction('10'), createMockInvokeTransaction('20')]; // Only 2 transactions, and we'll test with default minTxsNecessary of 10
213225

214226
const mockBlock = createMockBlock(100, transactions);
215227
mockProvider.getBlockWithTxs.mockResolvedValue(mockBlock);
216228

217-
await expect(getTipStatsFromBlocks(mockProvider, 'latest')).rejects.toThrow(LibraryError);
218-
await expect(getTipStatsFromBlocks(mockProvider, 'latest')).rejects.toThrow(
219-
'Insufficient transaction data'
220-
);
229+
const result = await getTipStatsFromBlocks(mockProvider, 'latest');
230+
expect(result).toEqual({
231+
recommendedTip: 0n,
232+
medianTip: 0n,
233+
modeTip: 0n,
234+
averageTip: 0n,
235+
minTip: 0n,
236+
maxTip: 0n,
237+
p90Tip: 0n,
238+
p95Tip: 0n,
239+
metrics: expect.objectContaining({
240+
blocksAnalyzed: expect.any(Number),
241+
transactionsFound: expect.any(Number),
242+
}),
243+
});
221244
});
222245

223246
test('should respect custom minTxsNecessary', async () => {
@@ -301,9 +324,21 @@ describe('Tip Analysis', () => {
301324
const emptyBlock = createMockBlock(100, []);
302325
mockProvider.getBlockWithTxs.mockResolvedValue(emptyBlock);
303326

304-
await expect(getTipStatsFromBlocks(mockProvider, 'latest')).rejects.toThrow(
305-
'Insufficient transaction data: found 0 transactions'
306-
);
327+
const result = await getTipStatsFromBlocks(mockProvider, 'latest');
328+
expect(result).toEqual({
329+
recommendedTip: 0n,
330+
medianTip: 0n,
331+
modeTip: 0n,
332+
averageTip: 0n,
333+
minTip: 0n,
334+
maxTip: 0n,
335+
p90Tip: 0n,
336+
p95Tip: 0n,
337+
metrics: expect.objectContaining({
338+
blocksAnalyzed: expect.any(Number),
339+
transactionsFound: expect.any(Number),
340+
}),
341+
});
307342
});
308343

309344
test('should calculate median correctly for even number of values', async () => {

0 commit comments

Comments
 (0)