diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 83698a771..084bec844 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -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 | @@ -958,7 +958,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 ``` @@ -1013,7 +1013,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` @@ -1275,7 +1275,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 @@ -1297,7 +1297,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 @@ -1327,7 +1327,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 @@ -1726,7 +1726,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: @@ -2075,8 +2075,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: @@ -2091,10 +2089,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 ( diff --git a/test_libs/pyspec/tests/block_processing/test_process_deposit.py b/test_libs/pyspec/tests/block_processing/test_process_deposit.py index 4031e650d..2e6f4cd95 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_deposit.py +++ b/test_libs/pyspec/tests/block_processing/test_process_deposit.py @@ -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 @@ -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 @@ -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( @@ -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 @@ -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 diff --git a/test_libs/pyspec/tests/block_processing/test_process_transfer.py b/test_libs/pyspec/tests/block_processing/test_process_transfer.py new file mode 100644 index 000000000..71d0894bd --- /dev/null +++ b/test_libs/pyspec/tests/block_processing/test_process_transfer.py @@ -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 \ No newline at end of file diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index a91aaa117..d181dadfe 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -21,10 +21,12 @@ from eth2spec.phase0.spec import ( 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, @@ -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() @@ -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, + ) + 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, diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index 3201ba936..facb8e158 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -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) @@ -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 @@ -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) @@ -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) #