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

fix: relayer fork handling #531

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

sander2
Copy link
Contributor

@sander2 sander2 commented Mar 11, 2025

Added fork handling.

If the latest relayed block is no longer on the main bitcoin chain, there was a problem with relaying new blocks, as fetching the block from esplora would fail. Unfortunately, we need to supply the whole block header (not just the hash) when changing the tip, so I had to fetch the block header from elsewhere. The easiest would be to just store the latest blockheader in the contract, but that would require redeployment which I didn't really want to do because of Fiamma. Plus, it would increase gas cost, although that's maybe not that big of deal

So I ended up starting a goldsky pipeline and get the blockheader from the calldata of the tx that submitted. Which works quite well but it does feel a bit over engineered

Summary by CodeRabbit

  • Chores

    • Updated project dependencies to enhance JSON processing.
  • New Features

    • Added a startup message to confirm when the relayer begins running.
    • Enhanced relayer operations for more reliable block synchronization.

Copy link

vercel bot commented Mar 11, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
bob-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 11, 2025 9:40am

Copy link

coderabbitai bot commented Mar 11, 2025

Walkthrough

This pull request introduces a new dependency in the project's Cargo configuration and adds a console log message to signal startup. It significantly refactors the relayer's functionality by updating the trait bound, removing an obsolete method, and adding new methods for block height management and state verification. The update also includes adjustments to the digest update mechanism through an integration with a GraphQL query and incorporates a set of tests covering the new behaviors.

Changes

File(s) Change Summary
relayer/Cargo.toml Added new dependency: serde_json = "1.0.140" under the [dependencies] section.
relayer/src/main.rs Introduced a log message println!("Starting relayer..."); immediately after parsing application arguments.
relayer/src/relayer.rs Updated the Relayer struct’s trait bound to include TransactionResponse = Transaction. Removed find_latest_height and added relayed_height, has_relayed, latest_common_height, and get_heaviest_relayed_block_header methods. Also refactored update_best_digest to utilize the new header-fetching method and added a test module for verifying changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Main
    participant Relayer
    participant Contract

    User->>Main: Start Application
    Main->>Relayer: run()
    Relayer->>Relayer: latest_common_height()
    Relayer->>Relayer: has_relayed(blockhash)
    Relayer->>Contract: get_heaviest_relayed_block_header()
    Contract-->>Relayer: RelayHeader
    Relayer->>Relayer: update_best_digest(new_header)
    Relayer-->>Main: Process complete
Loading

Suggested Reviewers

  • gregdhill

Poem

Hop, hop, through code we bound,
New dependencies and logs abound.
A relayer now leaps with methods new,
Verifying blocks with a keen view.
In fields of syntax, our rabbit cheers—
Carrots of code and happy gears!
🥕🐇 Happy coding to all!

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
relayer/src/main.rs (1)

40-41: Use a structured logger instead of raw println!.

While println! provides a quick solution for indicating that the relayer is starting, consider using a logging library (e.g., env_logger, tracing) to unify and manage log levels and formats across your application. This allows you to control verbosity more effectively and integrate with distributed tracing if needed.

relayer/src/relayer.rs (3)

67-78: Transport error handling logic is good, but check for corner cases.

Your code correctly interprets a TransportError(RpcError::ErrorResp(_)) as a “not relayed” scenario. However, consider logging additional error details or differentiating between other TransportError variants that may need explicit handling.


107-107: Slight duplication in logic with run_once.

run also calls latest_common_height before iteratively pulling headers. Consider factoring out shared logic if duplication grows.


336-410: Unit tests appear thorough, but consider environment dependencies.

You have tests verifying your relayer logic that rely on external endpoints and networks (e.g., anvil, sepolia, btc-signet). Mocks or integration test frameworks might be helpful for local reproducibility.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ffe160 and c3c5280.

📒 Files selected for processing (3)
  • relayer/Cargo.toml (1 hunks)
  • relayer/src/main.rs (1 hunks)
  • relayer/src/relayer.rs (4 hunks)
🔇 Additional comments (9)
relayer/Cargo.toml (1)

21-21: Good addition of serde_json dependency.

The serde_json crate is widely used and stable for handling JSON serialization and deserialization. Adding it here aligns well with your new code in relayer.rs that processes JSON responses from the GraphQL API.

relayer/src/relayer.rs (8)

4-7: Trait bound and import changes look good.

Specifying TransactionResponse = Transaction clarifies the type returned by the network, and importing Transaction, SolCall, RpcError is consistent with your new usage.


11-12: Imports for reqwest::Client and serde_json::Value are appropriate.

These are needed for handling your GraphQL request/response. No concerns here.


52-52: Trait bound update is clear and concise.

Defining the network trait to expect TransactionResponse = Transaction ensures compatibility with the contract calls and transaction lookups.


59-65: Potential unwrap() risk for try_into().unwrap().

In relayed_height, you do self.contract.findHeight(...).call().await?._0.try_into().unwrap(). If the returned value is not convertible to u32, this will panic. Ensure that contract constraints guarantee valid conversions, or consider graceful error handling with ?.

Would you like a script to confirm that all runtime values fit within u32?


100-100: run_once method clarity.

run_once calling latest_common_height() is a logical approach for simulating a single cycle. This helps test code or short-run scenarios.


253-253: Retaining find_lca logic is a suitable approach.

It’s a valid approach for detecting the nearest common ancestor. Just keep in mind large reorg edge cases.


255-255: Fetching current best from the contract is consistent.

By retrieving the actual “heaviest” header from the smart contract, you ensure an authoritative starting point for your LCA.


277-334: GraphQL approach is functionally correct but watch for potential errors and availability.

The approach to query Goldsky’s endpoint and parse its JSON works, but be mindful of:

  • Network errors or timeouts
  • Subgraph indexing delays
  • Unexpected schema changes
  • Potential data unavailability

Also, consider fallback or retries if the GraphQL service fails.

Let us know if you'd like a script to simulate subgraph downtime or parse edge-case responses for robust testing.

Comment on lines +80 to 96
async fn latest_common_height(&self) -> Result<u32> {
// Start at the tip of the relayed chain, then move back until we find a block that matches bitcoin chain.
// We do it like this because calling esplora.get_block_hash for a block in a fork will fail.
let mut height = self.relayed_height().await?;

Ok(latest_height)
loop {
let actual_hash = self.esplora_client.get_block_hash(height).await?;
let is_relayed = self.has_relayed(actual_hash).await?;

if is_relayed {
return Ok(height);
}

println!("Found fork: {actual_hash} at height {height}");
height -= 1;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Infinite loop risk when decrementing height.

If height reaches 0 while iterating backward searching for a common block, the loop may underflow, resulting in a panic or unintended behavior:

loop {
  ...
  height -= 1;
}

Consider adding a check to stop if height is already 0.

Apply this diff to handle zero safely:

 if height == 0 {
     return Err(eyre!("No common height found before reaching 0"));
 }
 height -= 1;

@sander2 sander2 mentioned this pull request Mar 28, 2025
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.

1 participant