lighthouse/eth2/state_processing/src/per_block_processing/verify_transfer.rs

201 lines
6.8 KiB
Rust
Raw Normal View History

2019-03-06 06:14:54 +00:00
use super::errors::{TransferInvalid as Invalid, TransferValidationError as Error};
use bls::get_withdrawal_credentials;
2019-04-16 04:14:38 +00:00
use tree_hash::SignedRoot;
use types::*;
2019-03-06 05:24:56 +00:00
/// Indicates if a `Transfer` is valid to be included in a block in the current epoch of the given
/// state.
///
2019-03-06 05:24:56 +00:00
/// Returns `Ok(())` if the `Transfer` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.6.1
2019-05-10 04:47:09 +00:00
pub fn verify_transfer<T: EthSpec>(
state: &BeaconState<T>,
transfer: &Transfer,
spec: &ChainSpec,
2019-03-06 03:46:12 +00:00
) -> Result<(), Error> {
verify_transfer_parametric(state, transfer, spec, false)
}
/// Like `verify_transfer` but doesn't run checks which may become true in future states.
///
/// Spec v0.6.1
2019-05-10 04:47:09 +00:00
pub fn verify_transfer_time_independent_only<T: EthSpec>(
state: &BeaconState<T>,
transfer: &Transfer,
spec: &ChainSpec,
) -> Result<(), Error> {
verify_transfer_parametric(state, transfer, spec, true)
2019-03-06 03:46:12 +00:00
}
/// Parametric version of `verify_transfer` that allows some checks to be skipped.
///
/// When `time_independent_only == true`, time-specific parameters are ignored, including:
///
/// - Balance considerations (e.g., adequate balance, not dust, etc).
/// - `transfer.slot` does not have to exactly match `state.slot`, it just needs to be in the
/// present or future.
/// - Validator transfer eligibility (e.g., is withdrawable)
///
/// Spec v0.6.1
2019-05-10 04:47:09 +00:00
fn verify_transfer_parametric<T: EthSpec>(
state: &BeaconState<T>,
2019-03-06 03:46:12 +00:00
transfer: &Transfer,
spec: &ChainSpec,
time_independent_only: bool,
) -> Result<(), Error> {
// Load the sender balance from state.
2019-03-17 01:25:37 +00:00
let sender_balance = *state
.balances
2019-03-17 01:25:37 +00:00
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
// Safely determine `amount + fee`.
let total_amount = transfer
.amount
.checked_add(transfer.fee)
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
// Verify the sender has adequate balance.
verify!(
time_independent_only || sender_balance >= transfer.amount,
2019-03-17 01:25:37 +00:00
Invalid::FromBalanceInsufficient(transfer.amount, sender_balance)
);
// Verify balances are not "dust" (i.e., greater than zero but less than the minimum deposit
// amount).
verify!(
time_independent_only
2019-03-06 03:46:12 +00:00
|| (sender_balance == total_amount)
2019-03-17 01:25:37 +00:00
|| (sender_balance >= (total_amount + spec.min_deposit_amount)),
Invalid::InvalidResultingFromBalance(
sender_balance - total_amount,
spec.min_deposit_amount
)
);
// If loosely enforcing `transfer.slot`, ensure the slot is not in the past. Otherwise, ensure
// the transfer slot equals the state slot.
if time_independent_only {
2019-03-06 03:46:12 +00:00
verify!(
state.slot <= transfer.slot,
Invalid::TransferSlotInPast(state.slot, transfer.slot)
);
} else {
verify!(
state.slot == transfer.slot,
Invalid::StateSlotMismatch(state.slot, transfer.slot)
);
}
// Load the sender `Validator` record from the state.
2019-03-17 01:25:37 +00:00
let sender_validator = state
.validator_registry
2019-03-17 01:25:37 +00:00
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let epoch = state.slot.epoch(spec.slots_per_epoch);
// Ensure one of the following is met:
//
// - Time independent checks are being ignored.
// - The sender has not been activated.
// - The sender is withdrawable at the state's epoch.
// - The transfer will not reduce the sender below the max effective balance.
verify!(
time_independent_only
|| sender_validator.activation_eligibility_epoch == spec.far_future_epoch
2019-03-06 03:46:12 +00:00
|| sender_validator.is_withdrawable_at(epoch)
|| total_amount + spec.max_effective_balance <= sender_balance,
2019-03-17 01:25:37 +00:00
Invalid::FromValidatorIneligableForTransfer(transfer.sender)
);
// Ensure the withdrawal credentials generated from the sender's pubkey match those stored in
// the validator registry.
//
// This ensures the validator can only perform a transfer when they are in control of the
// withdrawal address.
let transfer_withdrawal_credentials = Hash256::from_slice(
&get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..],
);
verify!(
2019-03-17 01:25:37 +00:00
sender_validator.withdrawal_credentials == transfer_withdrawal_credentials,
2019-03-07 06:23:11 +00:00
Invalid::WithdrawalCredentialsMismatch(
2019-03-17 01:25:37 +00:00
sender_validator.withdrawal_credentials,
2019-03-07 06:23:11 +00:00
transfer_withdrawal_credentials
)
);
// Verify the transfer signature.
let message = transfer.signed_root();
let domain = spec.get_domain(
transfer.slot.epoch(spec.slots_per_epoch),
Domain::Transfer,
&state.fork,
);
verify!(
transfer
.signature
.verify(&message[..], domain, &transfer.pubkey),
Invalid::BadSignature
);
Ok(())
}
/// Executes a transfer on the state.
///
/// Does not check that the transfer is valid, however checks for overflow in all actions.
///
/// Spec v0.6.1
2019-05-10 04:47:09 +00:00
pub fn execute_transfer<T: EthSpec>(
state: &mut BeaconState<T>,
transfer: &Transfer,
spec: &ChainSpec,
) -> Result<(), Error> {
2019-03-17 01:25:37 +00:00
let sender_balance = *state
.balances
2019-03-17 01:25:37 +00:00
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let recipient_balance = *state
.balances
2019-03-17 01:25:37 +00:00
.get(transfer.recipient as usize)
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.recipient)))?;
2019-03-17 01:25:37 +00:00
let proposer_index =
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
let proposer_balance = state.balances[proposer_index];
let total_amount = transfer
.amount
.checked_add(transfer.fee)
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
state.balances[transfer.sender as usize] =
2019-03-17 01:25:37 +00:00
sender_balance.checked_sub(total_amount).ok_or_else(|| {
Error::Invalid(Invalid::FromBalanceInsufficient(
total_amount,
sender_balance,
))
})?;
state.balances[transfer.recipient as usize] = recipient_balance
.checked_add(transfer.amount)
2019-03-17 01:25:37 +00:00
.ok_or_else(|| {
Error::Invalid(Invalid::ToBalanceOverflow(
recipient_balance,
transfer.amount,
))
})?;
state.balances[proposer_index] =
proposer_balance.checked_add(transfer.fee).ok_or_else(|| {
Error::Invalid(Invalid::ProposerBalanceOverflow(
proposer_balance,
transfer.fee,
))
})?;
Ok(())
}