-
Notifications
You must be signed in to change notification settings - Fork 1
Free consensus updates border condition #172
Conversation
println!("update_has_next_sync_committee {}", update_has_next_sync_committee); | ||
println!("update.attested_header.slot {}", update.attested_header.slot); | ||
println!("latest_finalized_state.slot {}", latest_finalized_state.slot); | ||
|
||
ensure!( | ||
update.attested_header.slot > latest_finalized_state.slot || |
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.
Perhaps this should rather be:
update.attested_header.slot > latest_finalized_state.slot || | |
update.finalized_header.slot > latest_finalized_state.slot || |
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.
It would deviate from the spec though: https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#validate_light_client_update
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'd prefer to not make this change deviate from the spec. To avoid spam of free consensus update maybe we can check the update interval even for sync committee updates?
polkadot-sdk/bridges/snowbridge/pallets/ethereum-client/src/lib.rs
Lines 661 to 663 in dfee885
if update.next_sync_committee_update.is_some() { | |
return Pays::No; | |
} |
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.
What happens if the sync committee update slot is smaller than the current finalized header? Then using the interval check does not work
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.
The reason why I think my change is fundamentally the correct one is because we assume if an update makes it through verify_update
, it is relevant and will change the light client state. This border case passes through verify_update
to apply_update
, but makes no light client state change. Which should not happen imo.
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.
if an update makes it through verify_update, it is relevant and will change the light client state.
I'd assume it's a normal case update.finalized_header.slot < latest_finalized_state.slot
for sync committee updates. Still prefer to strictly follow the spec unless absolute nessessary to change.
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.
What happens if the sync committee update slot is smaller than the current finalized header? Then using the interval check does not work
What I mean is something like this to avoid spamming.
if update.next_sync_committee_update.is_some() &&
update.finalized_header.slot >=
latest_slot.saturating_add((T::FreeHeadersInterval::get() / 2) as u64)
{
return Pays::No;
}
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.
if an update makes it through verify_update, it is relevant and will change the light client state.
I'd assume it's a normal case
update.finalized_header.slot < latest_finalized_state.slot
for sync committee updates. Still prefer to strictly follow the spec unless absolute nessessary to change.
Yes, but if update.finalized_header.slot < latest_finalized_state.slot
then the update must contain a sync committee update and the NextSyncCommittee must be empty: https://github.com/paritytech/polkadot-sdk/blob/master/bridges/snowbridge/pallets/ethereum-client/src/lib.rs#L332
For the case I highlighted in the PR description, update_has_next_sync_committee
is false and the finalized update is older than the finalized header.
I understand your reluctance to deviate from the spec, but fundamentally it is worrying to be that an update is essentially irrelevant but can somehow pass the validation and make it to the apply function.
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.
What happens if the sync committee update slot is smaller than the current finalized header? Then using the interval check does not work
What I mean is something like this to avoid spamming.
if update.next_sync_committee_update.is_some() && update.finalized_header.slot >= latest_slot.saturating_add((T::FreeHeadersInterval::get() / 2) as u64) { return Pays::No; }
I don't understand the logic? Why divide FreeHeadersInterval
by 2 here?
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.
To add some loose check for sync committee update avoid spamming, just an example may not be a correct way.
// If the next sync committee is not known and this update sets it, the update is free.
// If the sync committee update is in a period that we have not received an update for,
// the update is free.
I like the impl in your recent commit.
|
||
let pays_fee = Self::check_refundable(update, latest_finalized_state.slot); | ||
let actual_weight = match update.next_sync_committee_update { | ||
None => T::WeightInfo::submit(), | ||
Some(_) => T::WeightInfo::submit_with_sync_committee(), | ||
}; | ||
|
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.
Move this up so we can check if the NextSyncCommittee
is empty before updating this update.
@yrong @alistair-singh @vgeddes @acatangiu I have prototyped a fix for this without changing the spec. However, I think my original change is the most correct solution. Our use case differs from the Ethereum consensus spec because we don't track the head of the chain, only finalized updates. So my suggested change is more correct and reduces the need for other hacks to filter our irrelevant sync committee updates. I checked it in the Ethereum foundation R&D: #172 (comment) |
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.
+1
Co-authored-by: Adrian Catangiu <[email protected]>
* illustrates border condition * adds test payloads * cleanup * update comment * fmt * shuffle things around * a few more test checks * Update bridges/snowbridge/pallets/ethereum-client/src/lib.rs Co-authored-by: Adrian Catangiu <[email protected]> --------- Co-authored-by: Adrian Catangiu <[email protected]>
# Description A fix for a border condition introduced with new feature #5201. A malicious relayer could spam the Ethereum client with sync committee updates that have already been imported for the period. This PR adds a storage item to track the last imported sync committee period, so that subsequent irrelevant updates are not free. Original PR: Snowfork#172 ## Integration Downstream projects are not affected. Relayers will not be able to spam the Ethereum client with irrelevant sync committee updates for free. ## Review Notes Adds a storage item to track the last free sync committee update period, so that duplicate imports are not free. --------- Co-authored-by: Adrian Catangiu <[email protected]>
# Description A fix for a border condition introduced with new feature #5201. A malicious relayer could spam the Ethereum client with sync committee updates that have already been imported for the period. This PR adds a storage item to track the last imported sync committee period, so that subsequent irrelevant updates are not free. Original PR: Snowfork#172 ## Integration Downstream projects are not affected. Relayers will not be able to spam the Ethereum client with irrelevant sync committee updates for free. ## Review Notes Adds a storage item to track the last free sync committee update period, so that duplicate imports are not free. --------- Co-authored-by: Adrian Catangiu <[email protected]>
# Description A fix for a border condition introduced with new feature #5201. A malicious relayer could spam the Ethereum client with sync committee updates that have already been imported for the period. This PR adds a storage item to track the last imported sync committee period, so that subsequent irrelevant updates are not free. Original PR: Snowfork#172 ## Integration Downstream projects are not affected. Relayers will not be able to spam the Ethereum client with irrelevant sync committee updates for free. ## Review Notes Adds a storage item to track the last free sync committee update period, so that duplicate imports are not free. --------- Co-authored-by: Adrian Catangiu <[email protected]> (cherry picked from commit 1790e42)
In the current implementation, there exists a border condition where the consensus update is free, where it should be paid.
The condition is:
Test output:
submit 1 =============
update_has_next_sync_committee true
update.attested_header.slot 129
latest_finalized_state.slot 64
condition 1
SyncCommitteeUpdated at period 0
Pays::No
submit 2 =============
update_has_next_sync_committee false
update.attested_header.slot 128
latest_finalized_state.slot 64
SyncCommitteeUpdated at period 0
Pays::No (should be Yes)
Summary
To articulate the problem: It is possible to send an update with a sync committee, where neither the finalized header nor the sync committee would be updated, yet the update is free. We need to filter out irrelevant updates.
Resolves: SNO-1142