Potuz
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Witholding attack mitigation ## 1. The attack Consider the following forkchoice diagram ![](https://i.imgur.com/A1jQU2Y.png) - At the start of Epoch 12, the previous Epoch 11 has not been justified. The justified checkpoint has Epoch 10 (marked in red). - The attacker is witholding a chain of valid blocks from Epoch 11, starting from the block marked in yellow. Those blocks have enough votes within them to justify Epoch 11. - The honest chain in Epoch 12 has advanced, from their perspective the attacker's blocks are missing in Epoch 11. - The blocks in Epoch 12 have enough votes to justify Epoch 11, these nodes have `unrealized_justified_checkpoint.epoch = 11`. But the realized justification checkpoint is still at Epoch 10. In this scenario, the attacker releases his blocks: ![](https://i.imgur.com/Rs1SKvw.png) An honest validator seeing these blocks, since they are from the previous Epoch, will pull tips, realizing the justified checkpoint with Epoch 11. Thus, honest validators will see two valid chains, one with an unrealized justified checkpoint at epoch 11, and the attacker's chain with realized justified checkpoint at epoch 11. Honest validators are forced, by FFG rules, to switch to the attacker's branch ![](https://i.imgur.com/0HH0tml.png) **Remark** It was noticed by D. Ryan that this attack can happen with relatively the same frequency even without pull-tips. ## 2. Defensive tip pulling We propose the following defense against the above attack. Let `current_epoch` be the store's current epoch, that is `compute_epoch_at_slot(get_current_slot(store))`. The one rule we add is: Whenever `store.justified_checkpoint.Epoch + 1 == current_epoch` then pull tips from all Epochs *up to `store.justified_checkpoint.Epoch`*. Explicitly this means the following. Let `n` be a forkchoice node. We consider `n` to be viable for head if it satisfies the following - It is a descendant of the current justified checkpoint - Let `state = store.block_states[n.root]`, `future_state = process_justification_and_finalization(state)` and `bits = future_state.justification_bits`. We require `bits[1] = 0b1`, that is after processing justification, the previous epoch would be justified. **Illustration**: ![](https://i.imgur.com/uXOjCXp.png) ## 3. Possible specification We propose the following specification based on A. Asgaonkar's [pulled tips specification](https://github.com/adiasg/consensus-specs/pull/4). It is very poorly optimized so that it changes only one function. ```python= def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: block = store.blocks[block_root] children = [ root for root in store.blocks.keys() if store.blocks[root].parent_root == block_root ] # If any children branches contain expected finalized/justified checkpoints, # add to filtered block-tree and signal viability to parent. if any(children): filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] if any(filter_block_tree_result): blocks[block_root] = block return True return False correct_justified = false correct_finalized = false if store.justified_checkpoint.epoch == GENESIS_EPOCH: corrent_justified = true corret_finalized = true if store.realized_justifications[block_root] == store.justified_checkpoint: correct_justified = true if store.realized_finalizations[block_root] == store.finalized_checkpoint: correct_finalized = true block_epoch = compute_epoch_at_slot(block.slot) current_epoch = compute_epoch_at_slot(get_current_slot(store)) if !correct_justified and block_epoch == current_epoch: if store.justified_checkpoint.epoch + 1 == current_epoch: head_state = store.block_states[block_root] future_state = process_justification_and_finalization(head_state) correct_justified = future_state.justification_bits[1] == 0b1 if correct_justified: correct_finalized = true # If expected finalized/justified, add to viable block-tree and signal viability to parent. if correct_justified and correct_finalized: blocks[block_root] = block return True # Otherwise, branch not viable return False ``` <!-- Earlier version: correct_justified = false correct_finalized = false if store.justified_checkpoint.epoch == GENESIS_EPOCH: corrent_justified = true corret_finalized = true if store.realized_justifications[block_root] == store.justified_checkpoint: correct_justified = true if store.realized_finalizations[block_root] == store.finalized_checkpoint: correct_finalized = true if !correct_justified: current_epoch = compute_epoch_at_slot(get_current_slot(store)) if store.justified_checkpoint.epoch + 1 == current_epoch: head_state = store.block_states[block_root] future_state = process_justification_and_finalization(head_state) correct_justified = future_state.justification_bits[1] == 0b1 if correct_justified: correct_finalized = true --> **Note**: In the code block `if !correct_justified and block_epoch == current_epoch` we only check if leaf blocks from the current epoch have justified the previous epoch (i.e., `correct_justified = future_state.justification_bits[1] == 0b1`). We do not check leaf blocks from the previous epoch because they are already "pulled-up" by the pull-up tips logic and their justifications are checked in the previous code block using `store.realized_justifications`. ## 4. Proof that the attack is no longer effective In the situation described in Section 1. After the attacker reveals his blocks and they are imported to forkchoice, an honest validator will have `store.justified_checkpoint.epoch == 11`. The honest chain will still have valid tips as viable as they will satisfy the condition: ```python= ... correct_justified = ( future_state.justification_bits[1] = 0b1 and get_ancestor(store, block_root, justified_slot) == justified_root ) ... ``` The attackers blocks will have to compete with LMD weight vs the honest chain's weight plus proposer boost. ## 5. Voting analysis ### 5.1 Deadlocks One possible concern is regarding attestations by honest validators. The source vote is taken from the head state `justification_checkpoint`. A validator voting for the attacker's branch will vote with `source.epoch = 11` while a validator voting for the honest branch will vote with `source.epoch = 10`. This represents a deep departure from current specifications, in that honest validators voting in the honest chain will have differing `10 = source.epoch != store.justified_checkpoint.epoch = 11`. Votes for either branch during epoch 12 will have epoch 12 as target. Epoch 11 will be justified during epoch 13 on either the attackers branch or the honest branch, since this was a requirement for a viable head. Thus, any future vote by honest validators will have `source.epoch >= 11`, and `target.epoch >= 13` without the possibility of deadlocks. ### 5.2 Split views An earlier iteration of this defensive mechanism suffered from a possible network split, where the attacker would release his blocks to some validators during epoch 11, and others during epoch 12. We show here that both sets of validators will continue voting on the honest branch. Indeed those validators that saw the attackers block during epoch 11, will have `store.justified_checkpoint.epoch = 11` during all of epoch 12 since they would have updated their justification point during `on_tick`. Because of the new rule, all blocks from the honest chain that carry enough FFG information to justify 11 will be viable for head. On the other hand, those validators that saw the attacker's blocks during epoch 12, will start the epoch with `store.justified_checkpoint.epoch = 10`. They will import the attacker's blocks with `on_block`, at which point the *pull tips* mechanism will update the store's justification checkpoint to epoch 11. The new rule will make any tip from the current epoch 12 viable for head as long as it carries information to justify epoch 11 during the transition to 13. ### 5.3 Split vote A possible drawback of this approach is that honest validators see an LMD fight between the attacker's blocks and the honest chain. Even though the full network may be participating, and voting for epoch 12 as target, one branch is using epoch 10 as source and the other is using epoch 11 as source, therefore these votes will not count towards the same supermajority link in justifying epoch 12. An attacker with enough attestation stake may leverage this to cause a split vote and keep the network not justifying. At this stage the attack becomes equivalent to the bouncing attack to which we are susceptible anyway. The current mitigation does not make this attack more or less plausible. ## 6 Non-defensive tip pulling An alternative to defensive tip pulling is to always *pull up to `store.justification_checkpoint.epoch`*. In the situation of the attack described above the behavior of honest validator is **identical** to defensive tip pulling. This mechanism has the advantage of lower specification complexity. In terms of specification, this amounts to changing `filter_block_tree` according to [Pull #2 in Asgaonkar's specification](https://github.com/adiasg/consensus-specs/pull/2) from ```python store.u_justified_checkpoints[block_root] == store.justified_checkpoint ``` to requiring that the block is a descendant of `store.justified_checkpoin` and ```python= store.u_justified_checkpoints[block_root] >= store.justified_checkpoint ``` A drawback of this implementation is that whenever the previous epoch has not been justified, it allows for a fork. Consider the situation ![](https://i.imgur.com/xECo6Pj.png) The chain is following honestly with low participation and we have not justified epoch 11. There exists one block that will be the first block during 12 (this may be modified slightly to work in a future epoch) that has enough FFG votes to justify 11. In this case this is the red block. The attacker can fork off the last honest block (in this case the green block) as long as his block has enough FFG votes to justify epoch 11. These forks are tipically shallow since any attestation toward justifying epoch 11 included in the orphaned block (green) will have to be countered by some attestation in the attacking block (red). Given that attestations for epoch 11 would have been from the previous epoch, these forks would be possible only at the beginning of the epoch. Note however that these forks will be possible every time that the network fails to justify an epoch by a small drop in participation. Regardless of the percentage stake of the attacker. Defensive tip pulling as in section 2 does not suffer from this since both the green and the red blocks will be considered valid, given that epoch 11 has not been justified. ## 7. Finality reversal Care must be taken to avoid a situation where we could revert finality. Consider the following scenario ![](https://i.imgur.com/akZqtfP.jpg) - During Epoch N-2, it didn't justify. The justified checkpoint is in N-3. - During Epoch N-1, the proposer of block A has enough votes to justify both N-1 (checkpoint root J) and N-2 (checkpoint root F), thus enough to finalize N-3. But this block is witheld - During Epoch N, the canonical chain advances, enough attestations to justify N-1 were included in blocks during N, but not enough to justify neither N-2 nor N itself. In this situation, the canonical chain head, block C has `justified_checkpoint.Epoch = N-3`, and the unrealized ones: `u_justified_checkpoint.Epoch == N-1` and `u_finalized_checkpoint.Epoch < N-3` - The attacker's block A is revealed, pull tips immediately realize the justifications, so that we have ``` store.justified_checkpoint.epoch = N-1 store.finalized_checkpoint.epoch = N-3 ``` Now the canonical head block C satifies the check of the current (as of commit `431861f9b3af`) PR #11: ```python= # If previous epoch is justified, pull up all tips to at least the previous epoch if current_epoch > GENESIS_EPOCH and store.justified_checkpoint.epoch == current_epoch - 1: correct_justified = ( store.u_justified_checkpoints[block_root].epoch >= current_epoch - 1 ) correct_finalized = correct_justified ``` Therefore with the proposed fix, the chain C will become canonical. - The chain advances and crosses the Epoch boundary to N+1. At this point Epoch N-1 becomes justified on state but since Epoch N-2 is not, the finalized checkpoint is still before N-3. To account for this situation we need to make sure that the canonical chain actually increases finalization ```python= correct_finalized = store.u_finalized_checkpoints[block_root].epoch >= store.finalized_checkpoint.epoch ``` ## 8. Other considerations ### 8.1 finality reversal in Beacon API Even though the pull tips mechanism already allow for a virtual reversal of finality. The proposed mitigation attack here makes it more evident. Consider a validator that has been temporarily offline and did not see neither the attacker's blocks nor the honest chain in the situation of the attack of section 1. Suppose this validator sees the attacker's blocks before. His head state will have `justification_checkpoint.epoch = 11`. As soon as the honest chain appears, his head state will have `justification_checkpoint.epoch = 10`. This drop will continue throughout the full epoch 12, as opposed to the tipical situation with pull tips in which this drop is only until the node imports a single block which immediately realizes the new justification. We believe this will impact considerably the UX experience unless some extra care is taken with the endpoints API. ### 8.2 An optimization with a security drawback On the the drawbacks of pulling tips up to N-1 is that either we need to track this intermediate justified checkpoint, or check that the node is a descendant of the current justified checkpoint as in the specification in section 3 or the filtering in section 6. Relaxing this condition to allow any block with unrealized justification >= current justification, may result in blocks in epoch 12 that would justify epoch 12 on a **different fork**. Perhaps allowing this when epoch 11 (`current_epoch - 1`) is not justified yet, is not so problematic.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully