From 0d381c89fe655b2628f02e67095ef03cb1721398 Mon Sep 17 00:00:00 2001 From: Toni Tabak Date: Fri, 2 Sep 2022 16:04:28 +0200 Subject: [PATCH] feat: new Block utility object, rpc fetch clenup, open rpc fetch api --- __tests__/defaultProvider.test.ts | 16 +++-- __tests__/rpcProvider.test.ts | 14 ++++- __tests__/utils/utils.test.ts | 17 ++++++ src/provider/rpc.ts | 72 ++++++++++------------ src/provider/sequencer.ts | 5 +- src/provider/utils.ts | 99 ++++++++++++++----------------- 6 files changed, 113 insertions(+), 110 deletions(-) diff --git a/__tests__/defaultProvider.test.ts b/__tests__/defaultProvider.test.ts index 8e4d68025..637ab7ba9 100644 --- a/__tests__/defaultProvider.test.ts +++ b/__tests__/defaultProvider.test.ts @@ -139,10 +139,11 @@ describe('defaultProvider', () => { describeIfNotDevnet('Provider', () => { const provider = getTestProvider(); + let latestBlock; describe(`Provider methods if not devnet`, () => { describe('getBlock', () => { - test('pending', async () => { - const latestBlock = await provider.getBlock(); + test('getBlock by tag pending', async () => { + latestBlock = await provider.getBlock(); expect(latestBlock).toHaveProperty('block_hash'); expect(latestBlock).toHaveProperty('parent_hash'); expect(latestBlock).toHaveProperty('block_number'); @@ -153,11 +154,8 @@ describe('defaultProvider', () => { expect(Array.isArray(latestBlock.transactions)).toBe(true); }); - test('Block Hash 0x8a30a1212d142cb0053fe9921e1dbf64f651d328565bd2e7ac24059c270f43', async () => { - const block = await provider.getBlock( - '0x8a30a1212d142cb0053fe9921e1dbf64f651d328565bd2e7ac24059c270f43' - ); - + test('getBlock by Hash', async () => { + const block = await provider.getBlock(latestBlock.block_hash); expect(block).toHaveProperty('block_hash'); expect(block).toHaveProperty('parent_hash'); expect(block).toHaveProperty('block_number'); @@ -168,8 +166,8 @@ describe('defaultProvider', () => { expect(Array.isArray(block.transactions)).toBe(true); }); - test('Block Number 102634', async () => { - const block = await provider.getBlock(102634); + test('getBlock by Number', async () => { + const block = await provider.getBlock(latestBlock.block_number); expect(block).toHaveProperty('block_hash'); expect(block).toHaveProperty('parent_hash'); expect(block).toHaveProperty('block_number'); diff --git a/__tests__/rpcProvider.test.ts b/__tests__/rpcProvider.test.ts index 4b963977e..ecec93fbc 100644 --- a/__tests__/rpcProvider.test.ts +++ b/__tests__/rpcProvider.test.ts @@ -8,10 +8,22 @@ describeIfRpc('RPCProvider', () => { rpcProvider = getTestProvider() as RpcProvider; }); - describe('RPC methods', () => { + describe('RPC methods', async () => { + const latestBlock = await rpcProvider.getBlock('latest'); + test('getChainId', async () => { const chainId = await rpcProvider.getChainId(); expect(chainId).toBe('0x534e5f474f45524c49'); }); + + test('getBlockWithTxHashes', async () => { + const blockResponse = await rpcProvider.getBlockWithTxHashes(latestBlock.block_number); + expect(blockResponse).toHaveProperty('transactions'); + }); + + test('getBlockWithTxs', async () => { + const blockResponse = await rpcProvider.getBlockWithTxs(latestBlock.block_number); + expect(blockResponse).toHaveProperty('transactions'); + }); }); }); diff --git a/__tests__/utils/utils.test.ts b/__tests__/utils/utils.test.ts index 9875bc6c6..55d4ec347 100644 --- a/__tests__/utils/utils.test.ts +++ b/__tests__/utils/utils.test.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import { constants, hash, json, number, stark } from '../../src'; +import { Block } from '../../src/provider/utils'; import { pedersen } from '../../src/utils/hash'; const { IS_BROWSER } = constants; @@ -112,3 +113,19 @@ describe('calculateContractAddressFromHash()', () => { ); }); }); + +describe('new Block()', () => { + test('Block identifier and queryIdentifier', () => { + const blockA = new Block(0); + expect(blockA.identifier).toMatchObject({ block_number: 0 }); + expect(blockA.queryIdentifier).toBe('blockNumber=0'); + + const blockB = new Block('latest'); + expect(blockB.identifier).toBe('latest'); + expect(blockB.queryIdentifier).toBe('blockNumber=latest'); + + const blockC = new Block('0x01'); + expect(blockC.identifier).toMatchObject({ block_hash: '0x01' }); + expect(blockC.queryIdentifier).toBe('blockHash=0x01'); + }); +}); diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index a6b4f6eb5..a41fe4a91 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -24,7 +24,7 @@ import { parseCalldata, parseContract, wait } from '../utils/provider'; import { RPCResponseParser } from '../utils/responseParser/rpc'; import { randomAddress } from '../utils/stark'; import { ProviderInterface } from './interface'; -import { BlockIdentifier, BlockIdentifierClass } from './utils'; +import { Block, BlockIdentifier } from './utils'; export type RpcProviderOptions = { nodeUrl: string }; @@ -44,37 +44,32 @@ export class RpcProvider implements ProviderInterface { }); } + public fetch(method: any, params: any): Promise { + return fetch(this.nodeUrl, { + method: 'POST', + body: stringify({ method, jsonrpc: '2.0', params, id: 0 }), + headers: { 'Content-Type': 'application/json' }, + }); + } + + protected errorHandler(error: any) { + if (error) { + const { code, message } = error; + throw new Error(`${code}: ${message}`); + } + } + protected async fetchEndpoint( method: T, request?: RPC.Methods[T]['REQUEST'] ): Promise { - const requestData = { - method, - jsonrpc: '2.0', - params: request, - id: 0, - }; - try { - const rawResult = await fetch(this.nodeUrl, { - method: 'POST', - body: stringify(requestData), - headers: { - 'Content-Type': 'application/json', - }, - }); + const rawResult = await this.fetch(method, request); const { error, result } = await rawResult.json(); - if (error) { - const { code, message } = error; - throw new Error(`${code}: ${message}`); - } else { - return result as RPC.Methods[T]['RESPONSE']; - } + this.errorHandler(error); + return result as RPC.Methods[T]['RESPONSE']; } catch (error: any) { - const data = error?.response?.data; - if (data?.message) { - throw new Error(`${data.code}: ${data.message}`); - } + this.errorHandler(error?.response?.data); throw error; } } @@ -93,17 +88,15 @@ export class RpcProvider implements ProviderInterface { public async getBlockWithTxHashes( blockIdentifier: BlockIdentifier = 'pending' ): Promise { - const blockIdentifierGetter = new BlockIdentifierClass(blockIdentifier); - return this.fetchEndpoint('starknet_getBlockWithTxHashes', [ - blockIdentifierGetter.getIdentifier(), - ]); + const block = new Block(blockIdentifier); + return this.fetchEndpoint('starknet_getBlockWithTxHashes', [block.identifier]); } public async getBlockWithTxs( blockIdentifier: BlockIdentifier = 'pending' ): Promise { - const blockIdentifierGetter = new BlockIdentifierClass(blockIdentifier); - return this.fetchEndpoint('starknet_getBlockWithTxs', [blockIdentifierGetter.getIdentifier()]); + const block = new Block(blockIdentifier); + return this.fetchEndpoint('starknet_getBlockWithTxs', [block.identifier]); } public async getNonce(contractAddress: string): Promise { @@ -116,11 +109,11 @@ export class RpcProvider implements ProviderInterface { blockIdentifier: BlockIdentifier = 'pending' ): Promise { const parsedKey = toHex(toBN(key)); - const blockIdentifierGetter = new BlockIdentifierClass(blockIdentifier); + const block = new Block(blockIdentifier); return this.fetchEndpoint('starknet_getStorageAt', [ contractAddress, parsedKey, - blockIdentifierGetter.getIdentifier(), + block.identifier, ]); } @@ -149,11 +142,8 @@ export class RpcProvider implements ProviderInterface { } public async getClassAt(contractAddress: string, blockIdentifier: BlockIdentifier): Promise { - const blockIdentifierGetter = new BlockIdentifierClass(blockIdentifier); - return this.fetchEndpoint('starknet_getClassAt', [ - blockIdentifierGetter.getIdentifier(), - contractAddress, - ]); + const block = new Block(blockIdentifier); + return this.fetchEndpoint('starknet_getClassAt', [block.identifier, contractAddress]); } public async getEstimateFee( @@ -292,10 +282,8 @@ export class RpcProvider implements ProviderInterface { public async getTransactionCount( blockIdentifier: BlockIdentifier ): Promise { - const blockIdentifierGetter = new BlockIdentifierClass(blockIdentifier); - return this.fetchEndpoint('starknet_getBlockTransactionCount', [ - blockIdentifierGetter.getIdentifier(), - ]); + const block = new Block(blockIdentifier); + return this.fetchEndpoint('starknet_getBlockTransactionCount', [block.identifier]); } /** diff --git a/src/provider/sequencer.ts b/src/provider/sequencer.ts index 032b4894a..4e7b48a7f 100644 --- a/src/provider/sequencer.ts +++ b/src/provider/sequencer.ts @@ -33,7 +33,7 @@ import { randomAddress } from '../utils/stark'; import { buildUrl } from '../utils/url'; import { GatewayError, HttpError } from './errors'; import { ProviderInterface } from './interface'; -import { BlockIdentifier, getFormattedBlockIdentifier } from './utils'; +import { Block, BlockIdentifier } from './utils'; type NetworkName = 'mainnet-alpha' | 'goerli-alpha'; @@ -129,7 +129,8 @@ export class SequencerProvider implements ProviderInterface { const queryString = Object.entries(query) .map(([key, value]) => { if (key === 'blockIdentifier') { - return `${getFormattedBlockIdentifier(value)}`; + const block = new Block(value); + return `${block.queryIdentifier}`; } return `${key}=${value}`; }) diff --git a/src/provider/utils.ts b/src/provider/utils.ts index 7884e7341..17ebb8f87 100644 --- a/src/provider/utils.ts +++ b/src/provider/utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import type { BlockNumber } from '../types'; import { BigNumberish, isHex, toBN, toHex } from '../utils/number'; @@ -33,73 +34,59 @@ export function txIdentifier(txHash?: BigNumberish, txId?: BigNumberish): string // null appends nothing to the request url export type BlockIdentifier = BlockNumber | BigNumberish; -type BlockIdentifierObject = - | { type: 'BLOCK_NUMBER'; data: BlockNumber } - | { type: 'BLOCK_HASH'; data: BigNumberish }; -export class BlockIdentifierClass { - blockIdentifier: BlockIdentifier; +export class Block { + hash: BlockIdentifier = null; - constructor(blockIdentifier: BlockIdentifier) { - this.blockIdentifier = blockIdentifier; + number: BlockIdentifier = null; + + tag: BlockIdentifier = null; + + private setIdentifier: (_identifier: BlockIdentifier) => void; + + constructor(_identifier: BlockIdentifier) { + this.setIdentifier = function (__identifier: BlockIdentifier) { + if (typeof __identifier === 'string' && isHex(__identifier)) { + this.hash = __identifier; + } else if (typeof __identifier === 'number') { + this.number = __identifier; + } else { + this.tag = __identifier; + } + }; + + this.setIdentifier(_identifier); } - getIdentifier() { - if (typeof this.blockIdentifier === 'string' && isHex(this.blockIdentifier)) { - return { block_hash: this.blockIdentifier }; + get queryIdentifier(): any { + if (this.number !== null) { + return `blockNumber=${this.number}`; } - if (typeof this.blockIdentifier === 'number') { - return { block_number: this.blockIdentifier }; + if (this.hash !== null) { + return `blockHash=${this.hash}`; } - return this.blockIdentifier; + return `blockNumber=${this.tag}`; } -} -/** - * Identifies the block to be queried. - * - * @param blockIdentifier - block identifier - * @returns block identifier object - */ -export function getBlockIdentifier(blockIdentifier: BlockIdentifier): BlockIdentifierObject { - if (blockIdentifier === null || blockIdentifier === 'latest') { - return { type: 'BLOCK_NUMBER', data: 'latest' }; // default to latest block - } - if (blockIdentifier === 'pending') { - return { type: 'BLOCK_NUMBER', data: 'pending' }; - } - if (typeof blockIdentifier === 'number' || typeof blockIdentifier === 'bigint') { - return { type: 'BLOCK_NUMBER', data: blockIdentifier }; - } - if (typeof blockIdentifier === 'string' && blockIdentifier.startsWith('0x')) { - return { type: 'BLOCK_HASH', data: blockIdentifier }; - } - if (typeof blockIdentifier === 'string' && !Number.isNaN(parseInt(blockIdentifier, 10))) { - return { type: 'BLOCK_NUMBER', data: parseInt(blockIdentifier, 10) }; - } - if (typeof blockIdentifier === 'string') { - throw new Error(`Invalid block identifier: ${blockIdentifier}`); - } - return { type: 'BLOCK_HASH', data: blockIdentifier }; -} + get identifier(): any { + if (this.number !== null) { + return { block_number: this.number }; + } -/** - * Gets the block identifier for API request - * - * [Reference](https://github.com/starkware-libs/cairo-lang/blob/fc97bdd8322a7df043c87c371634b26c15ed6cee/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L164-L173) - * - * @param blockIdentifier - * @returns block identifier for API request - */ -export function getFormattedBlockIdentifier(blockIdentifier: BlockIdentifier = null): string { - const blockIdentifierObject = getBlockIdentifier(blockIdentifier); - if (blockIdentifierObject.type === 'BLOCK_NUMBER' && blockIdentifierObject.data === null) { - return ''; + if (this.hash !== null) { + return { block_hash: this.hash }; + } + + return this.tag; } - if (blockIdentifierObject.type === 'BLOCK_NUMBER') { - return `blockNumber=${blockIdentifierObject.data}`; + + set identifier(_identifier: BlockIdentifier) { + this.setIdentifier(_identifier); } - return `blockHash=${toHex(toBN(blockIdentifierObject.data))}`; + + valueOf = () => this.number; + + toString = () => this.hash; }