Skip to content
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

Merged
merged 20 commits into from
Jul 26, 2024
Merged

Conversation

0xyaco
Copy link
Collaborator

@0xyaco 0xyaco commented Jul 23, 2024

🤖 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.

Copy link

linear bot commented Jul 23, 2024

GRT-38 Implement BlocknumberService

Follow Figma diagrams: https://www.figma.com/board/BbciqJb5spg35ZglTsRRBb/Offchain?node-id=233-2491&t=fNurQJG1q77rduKS-4to implement BlocknumberService.

AC:

  • BlocknumberService successfully implemented
  • CAIP-2 standard is used for blockchains ids (https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md)
  • BlocknumberService should have the capability to distinguish between an EVM or nonEVM chain to use the corresponding provider to fetch blocknumbers in the future (EVMProvider, BlockmetaProvider)
  • Unit tests

@0xyaco 0xyaco force-pushed the feat/evm-block-search branch 2 times, most recently from 03f487a to b567846 Compare July 23, 2024 21:55
package.json Outdated
"packageManager": "[email protected]+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903"
"packageManager": "[email protected]+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903",
"dependencies": {
"winston": "^3.13.1"
Copy link
Collaborator

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 {
Copy link
Collaborator

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;
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

Copy link
Collaborator

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(
Copy link
Collaborator

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

Copy link
Collaborator Author

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;
Copy link
Collaborator

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,
Copy link
Collaborator

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" });

Copy link
Collaborator

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.

Copy link
Collaborator Author

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;

Copy link
Collaborator

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 ...

Copy link
Collaborator

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.

Copy link
Collaborator

@0xkenj1 0xkenj1 left a 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", () => {
Copy link
Collaborator

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", () => {
Copy link
Collaborator

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

Copy link
Collaborator Author

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!

Base automatically changed from feat/bn-service to dev July 24, 2024 19:19
@0xyaco 0xyaco force-pushed the feat/evm-block-search branch from b567846 to c7a9035 Compare July 25, 2024 16:37
@0xyaco 0xyaco marked this pull request as ready for review July 25, 2024 16:45
@0xkenj1 0xkenj1 self-requested a review July 25, 2024 18:29
Comment on lines +87 to +99
/**
* 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;
}

Copy link
Collaborator

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);
Copy link
Collaborator

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();
});
});
Copy link
Collaborator

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 :)

Copy link

linear bot commented Jul 25, 2024

GRT-39 Implement EVMProvider

Follow figma diagram: https://www.figma.com/board/BbciqJb5spg35ZglTsRRBb/Offchain?node-id=233-2491&t=fNurQJG1q77rduKS-4

AC:

  • getEpochBlocknumber() successfully implemented
  • Unit tests

Copy link
Collaborator

@0xkenj1 0xkenj1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing job !! 👏

@0xkenj1 0xkenj1 requested a review from 0xnigir1 July 25, 2024 19:45
Copy link
Collaborator

@0xnigir1 0xnigir1 left a 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 👏

@0xyaco 0xyaco merged commit faffdfd into dev Jul 26, 2024
5 checks passed
@0xyaco 0xyaco deleted the feat/evm-block-search branch July 26, 2024 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants