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

Enables transfers of balance proportions > 32 ETH #936

Merged
merged 14 commits into from
Apr 23, 2019
21 changes: 10 additions & 11 deletions specs/core/0_beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ These configurations are updated for releases, but may be out of sync during `de
| Name | Value | Unit |
| - | - | :-: |
| `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei |
| `MAX_DEPOSIT_AMOUNT` | `2**5 * 10**9` (= 32,000,000,000) | Gwei |
| `MAX_EFFECTIVE_BALANCE` | `2**5 * 10**9` (= 32,000,000,000) | Gwei |
| `EJECTION_BALANCE` | `2**4 * 10**9` (= 16,000,000,000) | Gwei |
| `HIGH_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei |

Expand Down Expand Up @@ -955,7 +955,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
while True:
candidate = first_committee[(current_epoch + i) % len(first_committee)]
random_byte = hash(generate_seed(state, current_epoch) + int_to_bytes8(i // 32))[i % 32]
if get_effective_balance(state, candidate) * 256 > MAX_DEPOSIT_AMOUNT * random_byte:
if get_effective_balance(state, candidate) * 256 > MAX_EFFECTIVE_BALANCE * random_byte:
return candidate
i += 1
```
Expand Down Expand Up @@ -1010,7 +1010,7 @@ def get_effective_balance(state: BeaconState, index: ValidatorIndex) -> Gwei:
"""
Return the effective balance (also known as "balance at stake") for a validator with the given ``index``.
"""
return min(get_balance(state, index), MAX_DEPOSIT_AMOUNT)
return min(get_balance(state, index), MAX_EFFECTIVE_BALANCE)
```

### `get_total_balance`
Expand Down Expand Up @@ -1272,7 +1272,7 @@ The private key corresponding to `withdrawal_pubkey` will be required to initiat

### `Deposit` logs

Every Ethereum 1.0 deposit, of size between `MIN_DEPOSIT_AMOUNT` and `MAX_DEPOSIT_AMOUNT`, emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12 signature) is not verified by the deposit contract.
Every Ethereum 1.0 deposit, of size at least `MIN_DEPOSIT_AMOUNT`, emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract.

### `Eth2Genesis` log

Expand All @@ -1294,7 +1294,7 @@ For convenience, we provide the interface to the contract here:

* `__init__()`: initializes the contract
* `get_deposit_root() -> bytes32`: returns the current root of the deposit tree
* `deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96])`: adds a deposit instance to the deposit tree, incorporating the input arguments and the value transferred in the given call. Note: the amount of value transferred *must* be within `MIN_DEPOSIT_AMOUNT` and `MAX_DEPOSIT_AMOUNT`, inclusive. Each of these constants are specified in units of Gwei.
* `deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96])`: adds a deposit instance to the deposit tree, incorporating the input arguments and the value transferred in the given call. Note: the amount of value transferred *must* be at least `MIN_DEPOSIT_AMOUNT`. Each of these constants are specified in units of Gwei.

## On genesis

Expand Down Expand Up @@ -1324,7 +1324,7 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit],

# Process genesis activations
for index, validator in enumerate(state.validator_registry):
if get_effective_balance(state, index) >= MAX_DEPOSIT_AMOUNT:
if get_effective_balance(state, index) >= MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH

Expand Down Expand Up @@ -1723,7 +1723,7 @@ def process_registry_updates(state: BeaconState) -> None:
# Process activation eligibility and ejections
for index, validator in enumerate(state.validator_registry):
balance = get_balance(state, index)
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and balance >= MAX_DEPOSIT_AMOUNT:
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and balance >= MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_epoch = get_current_epoch(state)

if is_active_validator(validator, get_current_epoch(state)) and balance < EJECTION_BALANCE:
Expand Down Expand Up @@ -2072,8 +2072,6 @@ def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None:

##### Transfers

Note: Transfers are a temporary functionality for phases 0 and 1, to be removed in phase 2.

Verify that `len(block.body.transfers) <= MAX_TRANSFERS` and that all transfers are distinct.

For each `transfer` in `block.body.transfers`, run the following function:
Expand All @@ -2088,10 +2086,11 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None:
assert get_balance(state, transfer.sender) >= max(transfer.amount, transfer.fee)
# A transfer is valid in only one slot
assert state.slot == transfer.slot
# Only withdrawn or not-yet-deposited accounts can transfer
# Sender must be not yet eligible for activation, withdrawn, or transfer balance over MAX_EFFECTIVE_BALANCE
assert (
state.validator_registry[transfer.sender].activation_eligibility_epoch == FAR_FUTURE_EPOCH or
get_current_epoch(state) >= state.validator_registry[transfer.sender].withdrawable_epoch or
state.validator_registry[transfer.sender].activation_epoch == FAR_FUTURE_EPOCH
transfer.amount + transfer.fee + MAX_EFFECTIVE_BALANCE <= get_balance(state, transfer.sender)
)
# Verify that the pubkey is valid
assert (
Expand Down
10 changes: 5 additions & 5 deletions test_libs/pyspec/tests/block_processing/test_process_deposit.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_success(state):
deposit_data_leaves,
pubkey,
privkey,
spec.MAX_DEPOSIT_AMOUNT,
spec.MAX_EFFECTIVE_BALANCE,
)

pre_state.latest_eth1_data.deposit_root = root
Expand All @@ -45,7 +45,7 @@ def test_success(state):
assert len(post_state.validator_registry) == len(state.validator_registry) + 1
assert len(post_state.balances) == len(state.balances) + 1
assert post_state.validator_registry[index].pubkey == pubkeys[index]
assert get_balance(post_state, index) == spec.MAX_DEPOSIT_AMOUNT
assert get_balance(post_state, index) == spec.MAX_EFFECTIVE_BALANCE
assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count

return pre_state, deposit, post_state
Expand All @@ -56,7 +56,7 @@ def test_success_top_up(state):
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)

validator_index = 0
amount = spec.MAX_DEPOSIT_AMOUNT // 4
amount = spec.MAX_EFFECTIVE_BALANCE // 4
pubkey = pubkeys[validator_index]
privkey = privkeys[validator_index]
deposit, root, deposit_data_leaves = build_deposit(
Expand Down Expand Up @@ -95,7 +95,7 @@ def test_wrong_index(state):
deposit_data_leaves,
pubkey,
privkey,
spec.MAX_DEPOSIT_AMOUNT,
spec.MAX_EFFECTIVE_BALANCE,
)

# mess up deposit_index
Expand Down Expand Up @@ -124,7 +124,7 @@ def test_bad_merkle_proof(state):
deposit_data_leaves,
pubkey,
privkey,
spec.MAX_DEPOSIT_AMOUNT,
spec.MAX_EFFECTIVE_BALANCE,
)

# mess up merkle branch
Expand Down
143 changes: 143 additions & 0 deletions test_libs/pyspec/tests/block_processing/test_process_transfer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from copy import deepcopy
import pytest

import eth2spec.phase0.spec as spec

from eth2spec.phase0.spec import (
get_active_validator_indices,
get_balance,
get_beacon_proposer_index,
get_current_epoch,
process_transfer,
set_balance,
)
from tests.helpers import (
get_valid_transfer,
next_epoch,
)


# mark entire file as 'transfers'
pytestmark = pytest.mark.transfers


def run_transfer_processing(state, transfer, valid=True):
"""
Run ``process_transfer`` returning the pre and post state.
If ``valid == False``, run expecting ``AssertionError``
"""
post_state = deepcopy(state)

if not valid:
with pytest.raises(AssertionError):
process_transfer(post_state, transfer)
return state, None


process_transfer(post_state, transfer)

proposer_index = get_beacon_proposer_index(state)
pre_transfer_sender_balance = state.balances[transfer.sender]
pre_transfer_recipient_balance = state.balances[transfer.recipient]
pre_transfer_proposer_balance = state.balances[proposer_index]
sender_balance = post_state.balances[transfer.sender]
recipient_balance = post_state.balances[transfer.recipient]
assert sender_balance == pre_transfer_sender_balance - transfer.amount - transfer.fee
assert recipient_balance == pre_transfer_recipient_balance + transfer.amount
assert post_state.balances[proposer_index] == pre_transfer_proposer_balance + transfer.fee

return state, post_state


def test_success_non_activated(state):
transfer = get_valid_transfer(state)
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH

pre_state, post_state = run_transfer_processing(state, transfer)

return pre_state, transfer, post_state


def test_success_withdrawable(state):
next_epoch(state)

transfer = get_valid_transfer(state)

# withdrawable_epoch in past so can transfer
state.validator_registry[transfer.sender].withdrawable_epoch = get_current_epoch(state) - 1

pre_state, post_state = run_transfer_processing(state, transfer)

return pre_state, transfer, post_state


def test_success_active_above_max_effective(state):
sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
amount = spec.MAX_EFFECTIVE_BALANCE // 32
set_balance(state, sender_index, spec.MAX_EFFECTIVE_BALANCE + amount)
transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0)

pre_state, post_state = run_transfer_processing(state, transfer)

return pre_state, transfer, post_state


def test_active_but_transfer_past_effective_balance(state):
sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
amount = spec.MAX_EFFECTIVE_BALANCE // 32
set_balance(state, sender_index, spec.MAX_EFFECTIVE_BALANCE)
transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0)

pre_state, post_state = run_transfer_processing(state, transfer, False)

return pre_state, transfer, post_state


def test_incorrect_slot(state):
transfer = get_valid_transfer(state, slot=state.slot+1)
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH

pre_state, post_state = run_transfer_processing(state, transfer, False)

return pre_state, transfer, post_state


def test_insufficient_balance(state):
sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
amount = spec.MAX_EFFECTIVE_BALANCE
set_balance(state, sender_index, spec.MAX_EFFECTIVE_BALANCE)
transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount + 1, fee=0)

# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH

pre_state, post_state = run_transfer_processing(state, transfer, False)

return pre_state, transfer, post_state


def test_no_dust(state):
sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
balance = state.balances[sender_index]
transfer = get_valid_transfer(state, sender_index=sender_index, amount=balance - spec.MIN_DEPOSIT_AMOUNT + 1, fee=0)

# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH

pre_state, post_state = run_transfer_processing(state, transfer, False)

return pre_state, transfer, post_state


def test_invalid_pubkey(state):
transfer = get_valid_transfer(state)
state.validator_registry[transfer.sender].withdrawal_credentials = spec.ZERO_HASH

# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH

pre_state, post_state = run_transfer_processing(state, transfer, False)

return pre_state, transfer, post_state
46 changes: 45 additions & 1 deletion test_libs/pyspec/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
DepositData,
Eth1Data,
ProposerSlashing,
Transfer,
VoluntaryExit,
# functions
convert_to_indexed,
get_active_validator_indices,
get_balance,
get_attesting_indices,
get_block_root,
get_crosslink_committees_at_slot,
Expand Down Expand Up @@ -76,7 +78,7 @@ def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=N
pubkey=pubkey,
# insecurely use pubkey as withdrawal key as well
withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:],
amount=spec.MAX_DEPOSIT_AMOUNT,
amount=spec.MAX_EFFECTIVE_BALANCE,
signature=signature,
)
item = deposit_data.hash_tree_root()
Expand Down Expand Up @@ -323,6 +325,48 @@ def get_valid_attestation(state, slot=None):
return attestation


def get_valid_transfer(state, slot=None, sender_index=None, amount=None, fee=None):
if slot is None:
slot = state.slot
current_epoch = get_current_epoch(state)
if sender_index is None:
sender_index = get_active_validator_indices(state, current_epoch)[-1]
recipient_index = get_active_validator_indices(state, current_epoch)[0]
transfer_pubkey = pubkeys[-1]
transfer_privkey = privkeys[-1]

if fee is None:
fee = get_balance(state, sender_index) // 32
if amount is None:
amount = get_balance(state, sender_index) - fee

transfer = Transfer(
sender=sender_index,
recipient=recipient_index,
amount=amount,
fee=fee,
slot=slot,
pubkey=transfer_pubkey,
signature=ZERO_HASH,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems wrong. This line should go away.

)
transfer.signature = bls.sign(
message_hash=signing_root(transfer),
privkey=transfer_privkey,
domain=get_domain(
state=state,
domain_type=spec.DOMAIN_TRANSFER,
message_epoch=get_current_epoch(state),
)
)

# ensure withdrawal_credentials reproducable
state.validator_registry[transfer.sender].withdrawal_credentials = (
spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(transfer.pubkey)[1:]
)

return transfer


def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0):
message_hash = AttestationDataAndCustodyBit(
data=attestation_data,
Expand Down
8 changes: 4 additions & 4 deletions test_libs/pyspec/tests/test_sanity.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def test_deposit_in_block(state):
index = len(test_deposit_data_leaves)
pubkey = pubkeys[index]
privkey = privkeys[index]
deposit_data = build_deposit_data(pre_state, pubkey, privkey, spec.MAX_DEPOSIT_AMOUNT)
deposit_data = build_deposit_data(pre_state, pubkey, privkey, spec.MAX_EFFECTIVE_BALANCE)

item = deposit_data.hash_tree_root()
test_deposit_data_leaves.append(item)
Expand All @@ -257,7 +257,7 @@ def test_deposit_in_block(state):
state_transition(post_state, block)
assert len(post_state.validator_registry) == len(state.validator_registry) + 1
assert len(post_state.balances) == len(state.balances) + 1
assert get_balance(post_state, index) == spec.MAX_DEPOSIT_AMOUNT
assert get_balance(post_state, index) == spec.MAX_EFFECTIVE_BALANCE
assert post_state.validator_registry[index].pubkey == pubkeys[index]

return pre_state, [block], post_state
Expand All @@ -268,7 +268,7 @@ def test_deposit_top_up(state):
test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)

validator_index = 0
amount = spec.MAX_DEPOSIT_AMOUNT // 4
amount = spec.MAX_EFFECTIVE_BALANCE // 4
pubkey = pubkeys[validator_index]
privkey = privkeys[validator_index]
deposit_data = build_deposit_data(pre_state, pubkey, privkey, amount)
Expand Down Expand Up @@ -414,7 +414,7 @@ def test_transfer(state, config):
spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer_pubkey)[1:]
)
# un-activate so validator can transfer
pre_state.validator_registry[sender_index].activation_epoch = spec.FAR_FUTURE_EPOCH
pre_state.validator_registry[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH

post_state = deepcopy(pre_state)
#
Expand Down