Skip to content

Commit e2dcac4

Browse files
authored
Only reset builder withdrawal epoch if its balance has been swept (#5384)
@0xMushow raised a point on discord that an attacker could continually deposit 1 ETH every `MIN_BUILDER_WITHDRAWABILITY_DELAY` epochs to prevent a builder from getting their balance back. One solution to this would be to only reset the withdrawalable epoch on top-ups if the balance has already been swept. This guarantees that the builder will get their balance back & prevents the original attack which this block of code prevented. See the following for more details: * #5373 Big thanks to @0xMushow for recognizing this!
1 parent d9dc09f commit e2dcac4

2 files changed

Lines changed: 64 additions & 11 deletions

File tree

specs/gloas/beacon-chain.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,13 +1700,13 @@ def process_builder_deposit_request(state: BeaconState, request: BuilderDepositR
17001700
builder_index = BuilderIndex(builder_pubkeys.index(request.pubkey))
17011701
builder = state.builders[builder_index]
17021702

1703-
# Increase balance by deposit amount
1704-
builder.balance += request.amount
1705-
1706-
# If exited, reset the withdrawable epoch
1707-
if builder.withdrawable_epoch != FAR_FUTURE_EPOCH:
1703+
# If exited and swept, reset the withdrawable epoch
1704+
if builder.withdrawable_epoch != FAR_FUTURE_EPOCH and builder.balance == 0:
17081705
epoch = get_current_epoch(state)
17091706
builder.withdrawable_epoch = epoch + MIN_BUILDER_WITHDRAWABILITY_DELAY
1707+
1708+
# Increase balance by deposit amount
1709+
builder.balance += request.amount
17101710
```
17111711

17121712
##### Builder exit requests

tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_builder_deposit_request.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -712,12 +712,13 @@ def test_process_builder_deposit_request__no_reuse_nonzero_balance(spec, state):
712712

713713
@with_gloas_and_later
714714
@spec_state_test
715-
def test_process_builder_deposit_request__exited_builder_top_up(spec, state):
715+
def test_process_builder_deposit_request__exited_builder_top_up_zero_balance(spec, state):
716716
"""
717-
Test top-up to an exited builder resets its withdrawable epoch.
717+
Test top-up to a fully-swept exited builder resets its withdrawable epoch.
718718
719719
Input State Configured:
720-
- Existing builder at index 0 that has exited (withdrawable_epoch in the past)
720+
- Existing builder at index 0 that has exited (withdrawable_epoch in the
721+
past) and has been fully swept (balance == 0)
721722
722723
Output State Verified:
723724
- Builder balance increased (top-up)
@@ -727,22 +728,24 @@ def test_process_builder_deposit_request__exited_builder_top_up(spec, state):
727728
amount = spec.MIN_DEPOSIT_AMOUNT
728729
pre_builder_count = len(state.builders)
729730

730-
# Advance an epoch and mark builder 0 as exited (withdrawable_epoch in the past)
731+
# Advance an epoch and mark builder 0 as exited (withdrawable_epoch in the
732+
# past) and fully swept (balance == 0)
731733
deposit_request = prepare_process_builder_deposit_request(
732734
spec,
733735
state,
734736
pubkey=builder_pubkey,
735737
amount=amount,
736738
signed=True,
737739
advance_epochs=1,
738-
builder_modifications={0: {"withdrawable_epoch": "current_epoch-1"}},
740+
builder_modifications={0: {"withdrawable_epoch": "current_epoch-1", "balance": 0}},
739741
)
740742
pre_state = state.copy()
741743
expected_withdrawable_epoch = (
742744
spec.get_current_epoch(state) + spec.config.MIN_BUILDER_WITHDRAWABILITY_DELAY
743745
)
744746
# Sanity check: the exited epoch differs from the expected reset value
745-
assert state.builders[0].withdrawable_epoch != expected_withdrawable_epoch
747+
assert pre_state.builders[0].withdrawable_epoch != expected_withdrawable_epoch
748+
assert pre_state.builders[0].balance == 0
746749

747750
yield from run_builder_deposit_request_processing(spec, state, deposit_request)
748751

@@ -756,3 +759,53 @@ def test_process_builder_deposit_request__exited_builder_top_up(spec, state):
756759
expected_builder_balance_delta=amount,
757760
expected_builder_withdrawable_epoch=expected_withdrawable_epoch,
758761
)
762+
763+
764+
@with_gloas_and_later
765+
@spec_state_test
766+
def test_process_builder_deposit_request__exited_builder_top_up_nonzero_balance(spec, state):
767+
"""
768+
Test that a top-up to an exited builder that still holds a balance does NOT
769+
reset its withdrawable epoch.
770+
771+
Input State Configured:
772+
- Existing builder at index 0 that has exited (withdrawable_epoch in the
773+
past) and still holds a large non-zero balance
774+
775+
Output State Verified:
776+
- Builder balance increased by the top-up amount
777+
- withdrawable_epoch unchanged (no reset)
778+
"""
779+
builder_pubkey = state.builders[0].pubkey
780+
balance = 1 * spec.ETH_TO_GWEI
781+
amount = spec.Gwei(1 * spec.ETH_TO_GWEI)
782+
pre_builder_count = len(state.builders)
783+
784+
# Advance an epoch and mark builder 0 as exited (withdrawable_epoch in the
785+
# past) while keeping a non-zero balance (funds not yet swept)
786+
deposit_request = prepare_process_builder_deposit_request(
787+
spec,
788+
state,
789+
pubkey=builder_pubkey,
790+
amount=amount,
791+
signed=True,
792+
advance_epochs=1,
793+
builder_modifications={0: {"withdrawable_epoch": "current_epoch-1", "balance": balance}},
794+
)
795+
pre_state = state.copy()
796+
# Sanity check: the builder is exited but still holds a balance
797+
assert pre_state.builders[0].withdrawable_epoch != spec.FAR_FUTURE_EPOCH
798+
assert pre_state.builders[0].balance > 0
799+
800+
yield from run_builder_deposit_request_processing(spec, state, deposit_request)
801+
802+
assert_process_builder_deposit_request(
803+
spec,
804+
state,
805+
pre_state,
806+
builder_deposit_request=deposit_request,
807+
expected_builder_count=pre_builder_count,
808+
expected_builder_index=0,
809+
expected_builder_balance_delta=amount,
810+
expected_builder_withdrawable_epoch=pre_state.builders[0].withdrawable_epoch,
811+
)

0 commit comments

Comments
 (0)