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

Sync execution update on demand #123

Merged
merged 5 commits into from
Mar 14, 2024
Merged

Conversation

yrong
Copy link

@yrong yrong commented Mar 12, 2024

No description provided.

.finalized_header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
Self::store_execution_header(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to verify and store these executions at all? Because I see in https://github.com/Snowfork/snowbridge/pull/1154/files#diff-ba222152cf61a695305ad44e12ccc757ffa95a1f6c1c8ee698e21a7650155229R165 we sync the execution header containing the message, without checking if has been synced already. We also do not check if a header was already stored in process_execution_header_update, which we maybe should add, i.e. check if it exists in storage.

Copy link
Author

Choose a reason for hiding this comment

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

I think it's still nessessary. As you can see we fetchEthereumNonce
https://github.com/Snowfork/snowbridge/blob/e8557c79fa5e20142cb6ae053ce33a3f3a517623/relayer/relays/execution/main.go#L116 at the LastExecutionHeaderState which should be a state finalized.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah I see, good point. 👍🏻

What do you think about potentially importing the same execution header twice? Maybe we should check if it exists in process_execution_header_update?

Copy link
Author

Choose a reason for hiding this comment

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

Potentially, and I add some extra check here
https://github.com/Snowfork/snowbridge/blob/e8557c79fa5e20142cb6ae053ce33a3f3a517623/relayer/relays/beacon/header/header.go#L353-L360 to make sure SyncExecutionHeader work as expected.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That check isn't related to checking if the header has already been imported on the light client side though. I think in process_execution_header_update we should check if the execution header has already been imported, before we try and verify it. What do you think?

Copy link
Author

@yrong yrong Mar 12, 2024

Choose a reason for hiding this comment

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

I prefer we do the check on relayer side, Snowfork/snowbridge@af93289

Copy link

Choose a reason for hiding this comment

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

I think it's still nessessary. As you can see we fetchEthereumNonce
https://github.com/Snowfork/snowbridge/blob/e8557c79fa5e20142cb6ae053ce33a3f3a517623/relayer/relays/execution/main.go#L116 at the LastExecutionHeaderState which should be a state finalized.

Since execution headers can now be (re)imported in arbitrary order, ie not in sequence, I don't think its safe for the light client to maintain a LastExecutionHeaderState storage item.

For example, an attacker can import execution header number 0 into the light client and then effectively force other relayers to resume relaying execution headers after number 0, since they are using LastExecutionHeaderState to know when to relay.

Similarly, each execution relayer only relays messages for its own channel. And so we need the ability for different execution relayers to be able to resume relaying from the correct ethereum block.

Just a rough idea - Simple solution I think is for the relayers to store a local file on disk containing the block number they need to resume relaying from. And they can also query the channel nonces on chain, to make sure the block number in the file points to the last sync'ed nonce.

Copy link
Author

Choose a reason for hiding this comment

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

An attacker can import execution header number 0 into the light client and then effectively force other relayers to resume relaying execution headers after number 0

Nice catch, made a fix in 6d8536d

Also good point about remove LastExecutionHeaderState storage item completely and the relayers to store a local file on disk containing the block number they need to resume relaying from, but I'd rather improve that in a separate PR later.

Copy link

@vgeddes vgeddes Mar 13, 2024

Choose a reason for hiding this comment

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

Thanks Ron, though 6d8536d doesn't solve all the problems with LastExecutionHeaderState.

Also, circling back to the original comments in this thread:

Clara: Do we need to verify and store these executions at all?

Ron: I think it's still nessessary. As you can see we fetchEthereumNonce
https://github.com/Snowfork/snowbridge/blob/e8557c79fa5e20142cb6ae053ce33a3f3a517623/relayer/relays/execution/main.go#L116 at the LastExecutionHeaderState which should be a state finalized.

My understanding from this that we are including the execution header in the Update struct passed to submit(update), purely so that LastExecutionHeaderState can be initialized? If that's the case, we should not do that because LastExecutionHeaderState needs to be removed in any case.

Or is there another reason why submit_execution_header cannot be the only way of submitting execution headers?

Copy link
Author

@yrong yrong Mar 13, 2024

Choose a reason for hiding this comment

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

b82df5b for the light client and Snowfork/snowbridge@ab191f9 for the relayer.

Just remove the LastExecutionState on chain and update the relaying algorithm to not depend on it. It did clean up the codebase a lot.

Some more details in https://github.com/Snowfork/snowbridge/pull/1154/files#r1524053814 for the change on relayer side.

Btw: @vgeddes I'm not sure we still need a local file system for that, please add more details if that's required.

Copy link
Collaborator

@claravanstaden claravanstaden left a comment

Choose a reason for hiding this comment

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

Looks great Ron! I have two comments, and then I just want to clarify, this doesn't affect the execution headers stored in the ring buffer, right?

bridges/snowbridge/pallets/ethereum-client/src/lib.rs Outdated Show resolved Hide resolved
bridges/snowbridge/primitives/beacon/src/types.rs Outdated Show resolved Hide resolved
bridges/snowbridge/primitives/beacon/src/updates.rs Outdated Show resolved Hide resolved
.finalized_header
.hash_tree_root()
.map_err(|_| Error::<T>::HeaderHashTreeRootFailed)?;
Self::store_execution_header(
Copy link

Choose a reason for hiding this comment

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

I think it's still nessessary. As you can see we fetchEthereumNonce
https://github.com/Snowfork/snowbridge/blob/e8557c79fa5e20142cb6ae053ce33a3f3a517623/relayer/relays/execution/main.go#L116 at the LastExecutionHeaderState which should be a state finalized.

Since execution headers can now be (re)imported in arbitrary order, ie not in sequence, I don't think its safe for the light client to maintain a LastExecutionHeaderState storage item.

For example, an attacker can import execution header number 0 into the light client and then effectively force other relayers to resume relaying execution headers after number 0, since they are using LastExecutionHeaderState to know when to relay.

Similarly, each execution relayer only relays messages for its own channel. And so we need the ability for different execution relayers to be able to resume relaying from the correct ethereum block.

Just a rough idea - Simple solution I think is for the relayers to store a local file on disk containing the block number they need to resume relaying from. And they can also query the channel nonces on chain, to make sure the block number in the file points to the last sync'ed nonce.

Copy link
Collaborator

@claravanstaden claravanstaden left a comment

Choose a reason for hiding this comment

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

Nice, much simpler! ✨

@yrong yrong merged commit 5bac4ee into snowbridge Mar 14, 2024
7 checks passed
@yrong yrong deleted the ron/execution-update-on-demand branch March 14, 2024 06:54
github-merge-queue bot pushed a commit to paritytech/polkadot-sdk that referenced this pull request Apr 2, 2024
This PR includes the following 2 improvements:

## Ethereum Client

Author: @yrong 
### Original Upstream PRs
- Snowfork#123
- Snowfork#125

### Description
The Ethereum client syncs beacon headers as they are finalized, and
imports every execution header. When a message is received, it is
verified against the import execution header. This is unnecessary, since
the execution header can be sent with the message as proof. The recent
Deneb Ethereum upgrade made it easier to locate the relevant beacon
header from an execution header, and so this improvement was made
possible. This resolves a concern @svyatonik had in our initial Rococo
PR:
#2522 (comment)

## Inbound Queue

Author: @yrong 
### Original Upstream PR
- Snowfork#118

### Description
When the AH sovereign account (who pays relayer rewards) is depleted,
the inbound message will not fail. The relayer just will not receive
rewards.

Both these changes were done by @yrong, many thanks. ❤️

---------

Co-authored-by: claravanstaden <Cats 4 life!>
Co-authored-by: Ron <yrong1997@gmail.com>
Co-authored-by: Vincent Geddes <vincent@snowfork.com>
Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
Ank4n pushed a commit to paritytech/polkadot-sdk that referenced this pull request Apr 9, 2024
This PR includes the following 2 improvements:

## Ethereum Client

Author: @yrong 
### Original Upstream PRs
- Snowfork#123
- Snowfork#125

### Description
The Ethereum client syncs beacon headers as they are finalized, and
imports every execution header. When a message is received, it is
verified against the import execution header. This is unnecessary, since
the execution header can be sent with the message as proof. The recent
Deneb Ethereum upgrade made it easier to locate the relevant beacon
header from an execution header, and so this improvement was made
possible. This resolves a concern @svyatonik had in our initial Rococo
PR:
#2522 (comment)

## Inbound Queue

Author: @yrong 
### Original Upstream PR
- Snowfork#118

### Description
When the AH sovereign account (who pays relayer rewards) is depleted,
the inbound message will not fail. The relayer just will not receive
rewards.

Both these changes were done by @yrong, many thanks. ❤️

---------

Co-authored-by: claravanstaden <Cats 4 life!>
Co-authored-by: Ron <yrong1997@gmail.com>
Co-authored-by: Vincent Geddes <vincent@snowfork.com>
Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
dharjeezy pushed a commit to dharjeezy/polkadot-sdk that referenced this pull request Apr 9, 2024
This PR includes the following 2 improvements:

## Ethereum Client

Author: @yrong 
### Original Upstream PRs
- Snowfork#123
- Snowfork#125

### Description
The Ethereum client syncs beacon headers as they are finalized, and
imports every execution header. When a message is received, it is
verified against the import execution header. This is unnecessary, since
the execution header can be sent with the message as proof. The recent
Deneb Ethereum upgrade made it easier to locate the relevant beacon
header from an execution header, and so this improvement was made
possible. This resolves a concern @svyatonik had in our initial Rococo
PR:
paritytech#2522 (comment)

## Inbound Queue

Author: @yrong 
### Original Upstream PR
- Snowfork#118

### Description
When the AH sovereign account (who pays relayer rewards) is depleted,
the inbound message will not fail. The relayer just will not receive
rewards.

Both these changes were done by @yrong, many thanks. ❤️

---------

Co-authored-by: claravanstaden <Cats 4 life!>
Co-authored-by: Ron <yrong1997@gmail.com>
Co-authored-by: Vincent Geddes <vincent@snowfork.com>
Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
claravanstaden added a commit that referenced this pull request Jun 19, 2024
This PR includes the following 2 improvements:

Author: @yrong
- #123
- #125

The Ethereum client syncs beacon headers as they are finalized, and
imports every execution header. When a message is received, it is
verified against the import execution header. This is unnecessary, since
the execution header can be sent with the message as proof. The recent
Deneb Ethereum upgrade made it easier to locate the relevant beacon
header from an execution header, and so this improvement was made
possible. This resolves a concern @svyatonik had in our initial Rococo
PR:
paritytech#2522 (comment)

Author: @yrong
- #118

When the AH sovereign account (who pays relayer rewards) is depleted,
the inbound message will not fail. The relayer just will not receive
rewards.

Both these changes were done by @yrong, many thanks. ❤️

---------

Co-authored-by: claravanstaden <Cats 4 life!>
Co-authored-by: Ron <yrong1997@gmail.com>
Co-authored-by: Vincent Geddes <vincent@snowfork.com>
Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants