-
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: search block by timestamp with binsearch #11
Conversation
GRT-38 Implement BlocknumberService
Follow Figma diagrams: https://www.figma.com/board/BbciqJb5spg35ZglTsRRBb/Offchain?node-id=233-2491&t=fNurQJG1q77rduKS-4to implement AC:
|
03f487a
to
b567846
Compare
package.json
Outdated
"packageManager": "[email protected]+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903" | ||
"packageManager": "[email protected]+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903", | ||
"dependencies": { | ||
"winston": "^3.13.1" |
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.
caret 🤣
|
||
type BlockWithNumber = Omit<Block, "number"> & { number: bigint }; | ||
|
||
export class EvmProvider implements BlockNumberProvider { |
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.
Lets rename EvmProvider here, as we talked some minutes ago
import logger from "../utils/logger.js"; | ||
import { BlockNumberProvider } from "./blockNumberProvider.js"; | ||
|
||
const BINARY_SEARCH_DELTA_MULTIPLIER = 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.
Maybe a we can add this as default parameter, wdyt? Or in the constructor, to avoid modifiying BlockNumberProvider interface
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.
Now that we've defined the way to go with the upcoming BlockNumber service, I agree this should be in the default value for some constructor parameter.
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.
Natspec left for all of the methods
this.client = client; | ||
} | ||
|
||
async getEpochBlockNumber( |
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 can add some comments on this method to make sure it is understandable for anyone who jumps into the code
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.
Absolutely
`Working with latest block (number: ${upperBoundBlock.number}, timestamp: ${upperBoundBlock.timestamp})...`, | ||
); | ||
|
||
if (_timestamp >= upperBoundBlock.timestamp) return upperBoundBlock.number; |
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 rather throw an error here, its a very edge case, i would prefer not to assume finalization just in case. We can discuss this offline
|
||
private async calculateLowerBoundBlock( | ||
timestamp: bigint, | ||
lastBlock: BlockWithNumber, |
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.
lets call this upperBoundBlock as it is on the caller function
): Promise<bigint> { | ||
const _timestamp = BigInt(timestamp); | ||
const upperBoundBlock = await this.client.getBlock({ blockTag: "finalized" }); | ||
|
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 can also fetch the block 0 and cache it to make sure that we are using a valid timestamp before making any computation.
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.
Oooh nice, I like this idea
) { | ||
let currentBlockNumber: bigint; | ||
let { fromBlock: low, toBlock: high } = between; | ||
|
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.
add validation here if(from > toblock) throw ...
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.
Lets move this logger to a new package called common
or shared
. This will allow us to reuse it accross all our codebase.
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.
:)
import { UnsupportedBlockTimestamps } from "../../src/exceptions/unsupportedBlockTimestamps.js"; | ||
import { EvmProvider } from "../../src/providers/evmProvider.js"; | ||
|
||
describe("EvmProvider", () => { |
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.
Rename here
import { EvmProvider } from "../../src/providers/evmProvider.js"; | ||
|
||
describe("EvmProvider", () => { | ||
describe("getEpochBlockNumber", () => { |
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 also test private methods ? let me know your thoughts on that
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'm debating myself over this, as within this class we have a pretty complex behavior and we might want to go a little deeper with testing.
Generally I don't feel comfortable testing private methods, as this is (generally) a signal that we want to extract some behavior in another class with public methods or that the modelling/abstraction is lacking in some aspect.
In this case, I've managed to cover a nice bunch of edge cases without knowing any implementation specific details, except for the fails when finding multiple blocks with the same timestamp
case, where knowing the implementation was needed to cover the actual situation of finding multiple blocks with the same timestamp (eg. binsearch would not detect same timestamp blocks in this situation [t1, (t1*, t2)]
, I need to specify a pretty tailored scenario [tx, ty, (tz*, tz), tw]
for the algorithm to detect two blocks with the same timestamp by looking at the element in the middle).
With this said, I feel like we are okay-ish as we are now, unless we can come up with more complex test cases that are binsearch related. In that case, I'd definitely consider testing the main "binsearch" private methods or extract them to a BinSearch
service or something like that with some public and testable methods.
I'll think a little bit more about it; keep suggestions coming!
b567846
to
c7a9035
Compare
/** | ||
* Fetches and caches the first block. Cached block will be returned if the cache is hit. | ||
* | ||
* @returns the chain's first block | ||
*/ | ||
private async getFirstBlock(): Promise<Block> { | ||
if (this.firstBlock !== null) return this.firstBlock; | ||
|
||
this.firstBlock = await this.client.getBlock({ blockNumber: 0n }); | ||
|
||
return this.firstBlock; | ||
} | ||
|
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.
NIceeeeee 💯
|
||
const firstBlock = await this.getFirstBlock(); | ||
|
||
if (_timestamp < firstBlock.timestamp) throw new TimestampNotFound(_timestamp); |
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 throw an InvalidTimestamp error, given that in this case, we will never find the block for the timestamp.
I think it's a different case than TimestampNotFound, since on this one we didn't find it but we can in the future.
Correct me if i am wrong pls
|
||
expect(evmProvider.getEpochBlockNumber(timestamp)).rejects.toBeDefined(); | ||
}); | ||
}); |
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 consider the case in which any of the external calls fail? We may want to be sure that the methods are throwing. Let me know wdyt :)
GRT-39 Implement EVMProvider
Follow figma diagram: https://www.figma.com/board/BbciqJb5spg35ZglTsRRBb/Offchain?node-id=233-2491&t=fNurQJG1q77rduKS-4 AC:
|
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.
Amazing job !! 👏
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.
WOW this is so cool 👏
🤖 Linear
Closes GRT-39
Description
Implements the
EvmProvider.getEpochBlockNumber
using a binary search with an optimization that takes into account that the current epoch begins near the last block.