-
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: implements BlockNumberService #13
Conversation
GRT-38 Implement BlocknumberService
Follow Figma diagrams: https://www.figma.com/board/BbciqJb5spg35ZglTsRRBb/Offchain?node-id=233-2491&t=fNurQJG1q77rduKS-4to implement AC:
|
export const chains = { | ||
mainnet: "eip155:1", | ||
polygon: "eip155:137", | ||
arbitrum: "eip155:42161", | ||
} as Record<SupportedChains, Caip2ChainId>; |
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 something that feels weird for me. It works, but feels super weird. Any suggestions?
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.
Supported chains should be part of the config file from the runtime or on a shared
module, not here i think , this is just an abstraction of the CAIP2
switch (chainId) { | ||
case "eip155:1": | ||
return new EvmBlockNumberProvider(client, { | ||
blocksLookback: 10_000n, | ||
deltaMultiplier: 2n, | ||
}); | ||
|
||
default: | ||
throw new UnsupportedChain(chainId); | ||
} |
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.
@0xkenj1 what do you think about this one? Should we have some kind of config file/constant value with the following attributes per chain?
- Provider class to use
- blocksLookback
- deltaMultiplier
Something like:
providers:
- "eip155:1":
- provider: EvmBlockNumberProvider
blocksLookback: 10000
deltaMultiplier: 2
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.
2 things here:
-
i would just use the same config for all the evm's for now , to avoid extra complexity. Lets leave some default values, and in the future if we need it we can do an easy refactor to inject the config.
-
on the switch statement, we can just pick the namespace from the CAIP2 in order to decide which blockNumberService we should instantiate. Lets avoid hardcoding each of the chains.
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.
After reading the code below, the switch statement should run after a validation, like:
if( chainId is not in SupportedChains ){
throw ``;
}
we can inject SupportedChains on the constructor, or having them on the shared
module as constants would be good also
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.
Given that we'll be deciding which provider to instantiate based on the namespace, what do you think about building a functionstatic getNamespace(chainId: string | Caip2ChainId)
inside Caip2
?
And I agree, let's go with a one-size-fits-all config for every chain and later we can add more customization.
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.
Yea, perfect, thats what we need :)
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.
And regarding the supported chains, with this convo I think it'd make more sense to validate the supported chains with a shared
module/config file inside the constructor, yeah.
This buildProvider
method being a static one and using (at the moment) only the namespace to operate seems like a not so good place. Probably in the future if we define a custom config per chain (for its provider) we'll be able to do something like:
if (config.getProviderConfig(chainId) == null) throw new Error();
switch (namespace) {
...
}
switch (chainId) { | ||
case "eip155:1": | ||
return new EvmBlockNumberProvider(client, { | ||
blocksLookback: 10_000n, | ||
deltaMultiplier: 2n, | ||
}); | ||
|
||
default: | ||
throw new UnsupportedChain(chainId); | ||
} |
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.
2 things here:
-
i would just use the same config for all the evm's for now , to avoid extra complexity. Lets leave some default values, and in the future if we need it we can do an easy refactor to inject the config.
-
on the switch statement, we can just pick the namespace from the CAIP2 in order to decide which blockNumberService we should instantiate. Lets avoid hardcoding each of the chains.
export const chains = { | ||
mainnet: "eip155:1", | ||
polygon: "eip155:137", | ||
arbitrum: "eip155:42161", | ||
} as Record<SupportedChains, Caip2ChainId>; |
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.
Supported chains should be part of the config file from the runtime or on a shared
module, not here i think , this is just an abstraction of the CAIP2
switch (chainId) { | ||
case "eip155:1": | ||
return new EvmBlockNumberProvider(client, { | ||
blocksLookback: 10_000n, | ||
deltaMultiplier: 2n, | ||
}); | ||
|
||
default: | ||
throw new UnsupportedChain(chainId); | ||
} |
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.
After reading the code below, the switch statement should run after a validation, like:
if( chainId is not in SupportedChains ){
throw ``;
}
we can inject SupportedChains on the constructor, or having them on the shared
module as constants would be good also
Pending |
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 findings, but we are almost there :)
GJ ser! 💯
packages/shared/src/constants.ts
Outdated
export const supportedChains = { | ||
evm: { | ||
namespace: "eip155", | ||
chains: { |
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.
chains: { | |
reference: { |
packages/shared/src/constants.ts
Outdated
ethereum: "eip155:1", | ||
polygon: "eip155:137", | ||
arbitrum: "eip155:42161", |
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.
ethereum: "eip155:1", | |
polygon: "eip155:137", | |
arbitrum: "eip155:42161", | |
ethereum: "1", | |
polygon: "137", | |
arbitrum: "42161", |
this.reference = params.reference; | ||
} | ||
|
||
export class Caip2 { |
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.
export class Caip2 { | |
export abstract class Caip2 { |
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.
export class Caip2 { | |
export class Caip2Utils { |
??
packages/shared/src/constants.ts
Outdated
@@ -0,0 +1,10 @@ | |||
export const supportedChains = { |
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 think maybe this should be an env var or configurable by the user insted of part of a shared
package.
Each user may want to choose which blockchains they support.
Maybe the input structure should be something like
{
evm: {
namespace: "eip155",
chains: {
ethereum: {
reference: "1",
rpcs: ["...", "..."]
}
}
}
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 i would rather pick reference as key of "chains" we are not gona be looking up using human readable, 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.
thinking again here. maybe what we need is a user input structure and also another structure of validChains (what we have now) to validate user input against it. make sense ?
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.
You envision something like:
MONITORED_CHAINS=eip155:1,eip155:137 ebo-agent run
(I guess also via a config file apart from env vars)
And that user-input list of chains should be a subset of our supportedChains
set of values?
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.
yea, exactly, a subset of the supported chains defined by us. The user should be also able to set rpcUrls for each of the chains.
return new EvmBlockNumberProvider( | ||
client, | ||
DEFAULT_PROVIDER_CONFIG, | ||
Logger.getInstance(), // Should we drop this arg? |
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.
dont think we should, maybe we can use this.logger
but its the same at the end 🤣
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.
Aaah yesss let's add a logger
member to this service.
}, [] as string[]); | ||
} | ||
|
||
public static buildProvider( |
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 smells like a Factory method pattern pattern
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.
Whoops missed to answer this one; it is indeed a Factory method pattern! Want me to do some special tweaks on it?
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 was wondering if we should remove this method from the BlockNumberService
class, i thing it has nothing to do with the main purpose of the class.
Also the static
modifier is telling me that this method doesn't belong to this class 🤣
Probably creating a new class called BlocknumberProviderFactory
to have an abstraction on the instantiation of the objects
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 see. We can extract this to a new class, absolutely, makes testing cleaner too.
} | ||
|
||
public async getEpochBlockNumbers(timestamp: number, chains: Caip2ChainId[]) { | ||
const epochBlockNumbers = await Promise.all( |
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.
probably here we will need a ts-retry
, to implement a retry policy.
this.blockNumberProviders = this.buildBlockNumberProviders(chainRpcUrls); | ||
} | ||
|
||
public async getEpochBlockNumbers(timestamp: number, chains: Caip2ChainId[]) { |
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.
also remember that will be 1 request -> 1 chain
so lets also add a getEpochBlockNumber
method
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.
Oh yes.
Do you think leveraging the multiple chains method would be ok? ie:
getEpochBlockNumber(timestamp, chain) {
getEpochBlockNumbers(timestamp, [chain])
}
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 would do it the other way round, like:
getEpochBlockNumbers(timestamp, [chain]) {
Promise.all([..., getEpochBlockNumbers(timestamp, chain), ...]
}
const DEFAULT_PROVIDER_CONFIG = { | ||
blocksLookback: 10_000n, | ||
deltaMultiplier: 2n, | ||
}; |
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.
shared
package? 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.
I see where you are going, yes. Probably we could define a type ProviderConfig
modeling this config object that goes in the shared
package but keep the const
here as I suspect these default values will only be used by the blockNumberService
internals? Makes sense?
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.
Makes totally sense, i was thinking that maybe it is better having almost all the constants on the centralized shared
package, to only update values there when is needed and avoid modifying the class file's.
Lets leave it as it is.
@0xkenj1 most significant changes are:
Let me know what you think! |
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.
GJ!!!!! This is huge!!! 🥇
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.
🚀
switch (chainNamespace) { | ||
case EBO_SUPPORTED_CHAINS_CONFIG.evm.namespace: | ||
return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG, logger); | ||
|
||
default: | ||
throw new UnsupportedChain(chainId); | ||
} |
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.
a switch for just two cases is too much i think but if this is expected to grow then feel free to ignore this and leave it as it is
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.
It will grow, yep
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.
missing natspec
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 adding docs to the BlockNumberProviderFactory
too 👌
🤖 Linear
Closes GRT-38
Part of GRT-77
Description
CAIP2
chain id class, using it as a static module that only validates a string being compliant.BlockNumberService
, enabling the agent to get from multiple chains their block numbers at a particular timestamp.