consensus-specs icon indicating copy to clipboard operation
consensus-specs copied to clipboard

Trustless validator pools during phases 0 & 1

Open moles1 opened this issue 5 years ago • 4 comments

After the phase 2 rollout, validator deposits can be made with withdrawal credentials representing the address of a smart contract on a shard.

However, during phases 0 & 1, the only option for withdrawal credentials is the pubkey of a BLS keypair (or an aggregate key) submitted by the depositor. This poses a significant problem for trustless validator pools wanting to operate during this time:

  • Assume there is a trustless staking pool consisting of 8 participants, each contributing 4 ETH. Each participant generates a BLS keypair, and the aggregate of these keys is submitted as the withdrawal credentials for the validator.
  • If one participant loses their private key, the pool is prevented from withdrawing. Or, a participant can extort the rest of the pool for up to 4 ETH each, refusing to sign a withdrawal message until the fee is paid.
  • Using a threshold BLS signature mitigates this problem, but has its own issues. e.g. if a threshold of 6/8 is chosen, then a single actor can use 24 of their own ETH to form an incomplete pool which they have complete control of. Alternatively, 6 of 8 actual participants could form a cartel and take control of a pool.

This makes it difficult for decentralized staking pools to operate during phases 0 & 1, potentially moving the ecosystem towards centralized / custodial platforms. There do not appear to be viable solutions to this problem outside of the protocol; some possible in-protocol solutions are outlined below:

moles1 avatar Apr 21 '20 23:04 moles1

One possible solution would be to record shares in a validator on the beacon chain itself. A naive implementation might look like the following:

Set MAX_VALIDATOR_SHARES to an arbitrary constant, e.g. 32

Update validator container to:

class Validator(Container):
    pubkey: BLSPubkey
    withdrawal_credentials: Vector[Bytes32, MAX_VALIDATOR_SHARES]  # Commitments to pubkeys for withdrawals
    withdrawal_shares: Vector[Gwei, MAX_VALIDATOR_SHARES]  # Relative shares for each set of withdrawal credentials, by index
    effective_balance: Gwei  # Balance at stake
    slashed: boolean
    # Status epochs
    activation_eligibility_epoch: Epoch  # When criteria for activation were met
    activation_epoch: Epoch
    exit_epoch: Epoch
    withdrawable_epoch: Epoch  # When validator can withdraw funds

In the process_deposit method, if deposit.data.pubkey is found in validator_pubkeys (and before increase_balance is called):

  • Set validator to state.validators[index]
  • Set validator_balance to state.balances[index]
  • Set total_withdrawal_shares to the sum of validator.withdrawal_shares
  • If total_withdrawal_shares != validator_balance, for each validator.withdrawal_shares, update share to share * validator_balance / total_withdrawal_shares
  • Check for deposit.data.withdrawal_credentials in validator.withdrawal_credentials:
    • If found, then add deposit.data.amount to validator.withdrawal_shares at matching index;
    • If not found, and len(validator.withdrawal_credentials) < MAX_VALIDATOR_SHARES, then append deposit.data.withdrawal_credentials to validator.withdrawal_credentials and append deposit.data.amount to validator.withdrawal_shares

During operations where validator balance is transferred (such as transfers and withdrawals):

  • Verify the signed message against each set of withdrawal credentials in validator.withdrawal_credentials and find the matching share index
  • Verify that validator.withdrawal_shares[index] > 0
  • Set transfer_amount to validator.withdrawal_shares[index] * validator_balance / total_withdrawal_shares (as described above)
  • Perform balance transfer using transfer_amount
  • Set validator.withdrawal_shares[index] to 0
  • Run decrease_balance

moles1 avatar Apr 21 '20 23:04 moles1

Another possible solution involves adding an alternate withdrawal mechanism to the spec, where the submitted withdrawal credentials represent an eth 1.0 address, instead of a BLS pubkey (/ eth 2.0 address).

During the construction of validator deposit data:

  • Set ETH1_WITHDRAWAL_PREFIX to an arbitrary constant, e.g. Bytes1('0x01')
  • Set withdrawal_credentials[:1] to ETH1_WITHDRAWAL_PREFIX
  • Set withdrawal_credentials[1:21] to the eth 1.0 withdrawal address
  • Set withdrawal_credentials[21:] to null bytes

This solution requires no changes to the spec for phases 0 & 1, but obviously does require a strong commitment to an eth1 + eth2 merge e.g. as described in this ethresearch post. It also assumes that there will be a guaranteed mechanism whereby the beacon chain can process withdrawal messages from an eth1 address.

moles1 avatar Apr 21 '20 23:04 moles1

(Thanks to @dankrad for participating in initial discussions around this issue and helping to explore options!)

moles1 avatar Apr 21 '20 23:04 moles1