-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add blockmeta block number provider #37
Conversation
|
||
return blockNumberAt; | ||
} catch (err) { | ||
const isAxios404 = isAxiosError(err) && err.status === 404; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
considering this is an axios error are we sure it shouldn't be err.response.status?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an interesting question. I cannot find any doc describing the error properties, although checking its source code it looks like the err.status
is basically the same as err.response?.status
.
I'll change to the more explicit property access though, as you suggested 👌 Feels like it removes any ambiguous interpretation!
* | ||
* @param response an AxiosResponse of a request to BlockByTime endpoint | ||
* @param isoTimestamp the timestamp that was sent in the request | ||
* @returns |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are we returning?
* @returns | ||
*/ | ||
private parseBlockByTimeResponse(response: AxiosResponse, isoTimestamp: string) { | ||
const { data } = response; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should specify what we're returning in the function def right?
return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG, logger); | ||
return new EvmBlockNumberProvider(evmClient, DEFAULT_PROVIDER_CONFIG, logger); | ||
|
||
case EBO_SUPPORTED_CHAINS_CONFIG.solana.namespace: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solana 👀 going shopping for those 200GB ram for running validators xd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xd
private readonly options: BlockmetaClientConfig, | ||
private readonly logger: ILogger, | ||
) { | ||
const { baseUrl, bearerToken } = options; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this a long lived Bearer Token like an API Key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, people have to get this token through the The Graph webapp which is kinda weird as they have no documented endpoint to automate it.
But now that you mention, we should definitely notify when this token is going to expire, even if it's an extremely long-lived token (1 year).
const isAxios404 = isAxiosError(err) && err.response?.status === 404; | ||
const isUndefinedBlockNumber = err instanceof UndefinedBlockNumber; | ||
|
||
if (!isAxios404 && !isUndefinedBlockNumber) throw err; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would we want to also handle Unauthorized errors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thinking on this, we could use some ep on constructor (like an echo get or smth simple) to check that credentials are working or else throw on constructor, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a nice idea. I'm generally reluctant about async-ing constructors but I think that by using a static method we could do something like:
// BlockmetaJsonBlockNumberProvider.ts
async static initialize(config, ...) {
const provider = new BlockmetaJsonBlockNumberProvider();
const successfulConnection = await provider.testConnection();
if (successfulConnection) return provider;
else throw new Error("BANG");
}
Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a pattern on our best practices :)
https://dev.to/somedood/the-proper-way-to-write-async-constructors-in-javascript-1o8c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol we are aligned without even trying
evmClient: PublicClient<FallbackTransport<HttpTransport[]>>, | ||
blockmetaConfig: { baseUrl: URL; servicePath: string; bearerToken: string }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im thinking if this should be a config object like:
{
evmClient?: PublicClient<FallbackTransport<HttpTransport[]>>,
blockmetaConfig?: { baseUrl: URL; servicePath: string; bearerToken: string }
}: ProviderConfig
and here validate that the proper client/config is defined depending on the switch case. passing an evmClient when instantiating a Solana's one is not necessary actually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's kinda too "functional-y" as it is right now, this factory could probably be a instance initialized with the config you mention and then by defining the new BlockNumberProviderFactory().build()
instance method, there's no need to pass the config as it's already inside the factory instance.
return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG, logger); | ||
return new EvmBlockNumberProvider(evmClient, DEFAULT_PROVIDER_CONFIG, logger); | ||
|
||
case EBO_SUPPORTED_CHAINS_CONFIG.solana.namespace: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xd
private readonly options: BlockmetaClientConfig, | ||
private readonly logger: ILogger, | ||
) { | ||
const { baseUrl, bearerToken } = options; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Config or options ? 🤣
|
||
/** @inheritdoc */ | ||
async getEpochBlockNumber(timestamp: Timestamp): Promise<bigint> { | ||
if (timestamp > Number.MAX_SAFE_INTEGER || timestamp < Number.MIN_SAFE_INTEGER) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (timestamp > Number.MAX_SAFE_INTEGER || timestamp < Number.MIN_SAFE_INTEGER) | |
if (timestamp > Number.MAX_SAFE_INTEGER || timestamp < 0) |
Number.MIN_SAFE_INTEGER = -9007199254740991
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for fun I've requested the BlockByTime/After
after '{"time": "1950-01-01T00:00:00.000Z"}'
and got:
"num":"20737620", "time":"1915-04-21T01:10:00.999Z"
There's probably a weird overflow bug in substreams lol should we report it? Doesn't seem too big of an issue as no sane person would try to fetch blocks before computers even existed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, maybe we should just tell the graph team on slack
const isAxios404 = isAxiosError(err) && err.response?.status === 404; | ||
const isUndefinedBlockNumber = err instanceof UndefinedBlockNumber; | ||
|
||
if (!isAxios404 && !isUndefinedBlockNumber) throw err; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a pattern on our best practices :)
https://dev.to/somedood/the-proper-way-to-write-async-constructors-in-javascript-1o8c
*/ | ||
private async getBlockNumberAt(isoTimestamp: string): Promise<bigint> { | ||
const { servicePath } = this.options; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we validate isoTimestamp here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ended up passing around Date
instances and each method is in charge of calling the toISOString()
method on it. No need for validating now 🪄
*/ | ||
private async getBlockNumberBefore(isoTimestamp: string): Promise<bigint> { | ||
const { servicePath } = this.options; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
* @param isoTimestamp the timestamp that was sent in the request | ||
* @returns the block number inside a BlockByTime service response | ||
*/ | ||
private parseBlockByTimeResponse(response: AxiosResponse, isoTimestamp: string): bigint { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private parseBlockByTimeResponse(response: AxiosResponse, isoTimestamp: string): bigint { | |
private parseBlockByTimeResponse(data: unknown, isoTimestamp: string): bigint { |
Opinions ? @0xnigir1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i wouldn't use unknown because it's an AxiosResponse object, what we should validate is response.data
, probablyAxiosRespose<unknown>
might be better (if im not wrong it's generic)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AxiosResponse<unknown>
works for me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what i wanted to point out here was that the signature shouldn't rely on Axios types, just receives the data
*/ | ||
private parseBlockByTimeResponse(response: AxiosResponse, isoTimestamp: string): bigint { | ||
const { data } = response; | ||
// TODO: validate with zod instead |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hehehe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some suggestions but don't want to block 👍🏻
import { InvalidTokenError, jwtDecode } from "jwt-decode"; | ||
|
||
import { BlockmetaConnectionFailed } from "../exceptions/blockmetaConnectionFailed.js"; | ||
import { UndefinedBlockNumber } from "../exceptions/undefinedBlockNumber.js"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we should import these from index.js but we can clean up later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, got a task already for that! 👌
import { BlockNumberProvider } from "./blockNumberProvider.js"; | ||
|
||
type BlockByTimeResponse = { | ||
num: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this should be blockNum so it's a bit more descriptive--I had to see where it's being used to understand the type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is based on the blockmeta BlockByTime
service response, sadly we cannot change it as the keys are defined by the third-party service.
🤖 Linear
Closes GRT-131
Description
Implements the provider for consuming block meta service by using HTTP POST requests with JSON content.
The
blockmeta.BlockByTime
service has two operations:BlockByTime/At
which gives you the block that's been mined at a specific timestampBlockByTime/Before
which gives you the most recent block that's been mined before a specific timestampGiven that
BlockByTime/Before
is not inclusive, some extra logic that coordinates the two operations need to be added to get the relevant block for EBO operation.