Skip to content

feat: update chain_head#1836

Merged
morph-dev merged 5 commits into
ethereum:masterfrom
morph-dev:lc_watch
May 19, 2025
Merged

feat: update chain_head#1836
morph-dev merged 5 commits into
ethereum:masterfrom
morph-dev:lc_watch

Conversation

@morph-dev
Copy link
Copy Markdown
Collaborator

What was wrong?

The ChainHead wasn't being updated

How was it fixed?

Now we update the ChainHead whenever:

  • We store related beacon content (OptimisticUpdate, FinalityUpdate, HistoricalSummaries)
  • LightClient applies updates to its Store
    • This wasn't as easy because we don't have access to updates that LightClient is using.
    • I added support for broadcasting applied updates using tokio::sync::watch::channel
      • First commit adds this support. I'm fine with moving it into separate PR if that's desired

Future work:

  • Start using ChainHead for validating content
  • Start adding ephemeral content

To-Do

@morph-dev morph-dev requested a review from KolbyML May 17, 2025 21:22
@morph-dev morph-dev self-assigned this May 17, 2025
Copy link
Copy Markdown
Member

@KolbyML KolbyML left a comment

Choose a reason for hiding this comment

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

Why are the process_*_update() functions triggered by both the database updates from the LightClient and the BeaconNetworkStorage I understand why historical_summaries is only done in BeaconNetworkStorage, I am confused what the reason for having process_optimistic_update() and process_finality_update() triggered by both is

Other then that the PR looks pretty good, I am just curious on a few details

Comment thread bin/trin/src/run.rs Outdated
portalnet_config.clone(),
storage_config_factory.create(&Subnetwork::Beacon, Distance::MAX)?,
header_oracle.clone(),
chain_head.clone(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
chain_head.clone(),
chain_head,

do we need clone here?

Comment on lines +98 to +100
let mut watch_receivers = client.get_light_client_watch_receivers().await;
let mut beacon_client = beacon_client_clone.lock().await;
*beacon_client = Some(client);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
let mut watch_receivers = client.get_light_client_watch_receivers().await;
let mut beacon_client = beacon_client_clone.lock().await;
*beacon_client = Some(client);
let mut watch_receivers = client.get_light_client_watch_receivers().await;
*beacon_client_clone.lock().await = Some(client);

shouldn't this lock be scoped so it is dropped? currently this mutex would never drop due to the loop {} below. where as the lock used to instantly be unlocked, due to the object being dropped, which is no longer the case

We could use {} to scope it, or manually drop the object, but any of these 3 solutions should work

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

You are right, I missed this. Thanks

Comment on lines +381 to +387
if self.store.optimistic_header.slot == update.attested_header.beacon.slot
|| self.store.finalized_header.slot == update.finalized_header.beacon.slot
{
self.watch_senders
.update
.send_replace(Some(LightClientUpdate::Electra(update.clone())));
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This feels weird, I get why we have to do this because apply_generic_update doesn't get the actual update, it feels non-intuitive at first glance though

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I modified the condition, and hopefully made it cleaner and more correct. WDYT?

Comment on lines +391 to +399
let generic_update = GenericUpdate::from(update);
self.apply_generic_update(&generic_update);
if self.store.optimistic_header.slot == update.attested_header.beacon.slot
|| self.store.finalized_header.slot == update.finalized_header.beacon.slot
{
self.watch_senders
.finality_update
.send_replace(Some(LightClientFinalityUpdate::Electra(update.clone())));
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
let generic_update = GenericUpdate::from(update);
self.apply_generic_update(&generic_update);
if self.store.optimistic_header.slot == update.attested_header.beacon.slot
|| self.store.finalized_header.slot == update.finalized_header.beacon.slot
{
self.watch_senders
.finality_update
.send_replace(Some(LightClientFinalityUpdate::Electra(update.clone())));
}
let generic_update = GenericUpdate::from(update);
self.apply_generic_update(&generic_update);
if self.store.optimistic_header.slot == update.attested_header.beacon.slot
|| self.store.finalized_header.slot == update.finalized_header.beacon.slot
{
self.watch_senders
.finality_update
.send_replace(Some(LightClientFinalityUpdate::Electra(update.clone())));
}

what is the reasoning in if self.store.optimistic_header.slot == update.attested_header.beacon.slot here for the LightClientFinalityUpdate one?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure I understand the question. The LightClientFinalityUpdate can update the optimistic header as well.

@morph-dev
Copy link
Copy Markdown
Collaborator Author

Why are the process_*_update() functions triggered by both the database updates from the LightClient and the BeaconNetworkStorage I understand why historical_summaries is only done in BeaconNetworkStorage, I am confused what the reason for having process_optimistic_update() and process_finality_update() triggered by both is

Other then that the PR looks pretty good, I am just curious on a few details

Ideally, it shouldn't be needed. But because of the several reasons (see #1815), the light-client isn't aware of every update that is gossiped to us (hence, called from BeaconNetworkStorage on store), and BeaconNetworkStorage isn't aware of updates that are processed/applied by light-client (hence, called from LightClient on apply).

I do plan to clean this up as process of working on #1815, but that feels like slightly lower priority at the moment.

@morph-dev morph-dev requested a review from KolbyML May 19, 2025 09:49
Copy link
Copy Markdown
Member

@KolbyML KolbyML left a comment

Choose a reason for hiding this comment

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

:shipit: PR looks fine, wouldn't our code work without the lightclient changes though. Ideally the LightClient shouldn't be making FindContent requests on the network, and should only be listening to P2P recieved on beacon. If Beacon is always getting and storing the latest Finalized and Optimistic update, I don't get the point in listening from the light client end, but I don't think it is worth blocking this PR over that.

@morph-dev
Copy link
Copy Markdown
Collaborator Author

Ideally the LightClient shouldn't be making FindContent requests on the network, and should only be listening to P2P recieved on beacon.

Yes, I agree. But currently that's not how it works. As I said, working on #1815 will improve this.

If Beacon is always getting and storing the latest Finalized and Optimistic update ...

At the moment, it's not. We need to improve several thing on the beacon side for this to be reliable (part of the #1815 )

@morph-dev morph-dev merged commit 44a974d into ethereum:master May 19, 2025
16 checks passed
@morph-dev morph-dev deleted the lc_watch branch May 19, 2025 20:36
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.

2 participants