github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/core/helpers/weak_subjectivity.go (about)

     1  package helpers
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"strconv"
     9  	"strings"
    10  
    11  	types "github.com/prysmaticlabs/eth2-types"
    12  	iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
    13  	eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    14  	"github.com/prysmaticlabs/prysm/shared/mathutil"
    15  	"github.com/prysmaticlabs/prysm/shared/params"
    16  )
    17  
    18  // ComputeWeakSubjectivityPeriod returns weak subjectivity period for the active validator count and finalized epoch.
    19  //
    20  // Reference spec implementation:
    21  // https://github.com/ethereum/eth2.0-specs/blob/master/specs/phase0/weak-subjectivity.md#calculating-the-weak-subjectivity-period
    22  //
    23  // def compute_weak_subjectivity_period(state: BeaconState) -> uint64:
    24  //    """
    25  //    Returns the weak subjectivity period for the current ``state``.
    26  //    This computation takes into account the effect of:
    27  //        - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and
    28  //        - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch).
    29  //    A detailed calculation can be found at:
    30  //    https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf
    31  //    """
    32  //    ws_period = MIN_VALIDATOR_WITHDRAWABILITY_DELAY
    33  //    N = len(get_active_validator_indices(state, get_current_epoch(state)))
    34  //    t = get_total_active_balance(state) // N // ETH_TO_GWEI
    35  //    T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI
    36  //    delta = get_validator_churn_limit(state)
    37  //    Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH
    38  //    D = SAFETY_DECAY
    39  //
    40  //    if T * (200 + 3 * D) < t * (200 + 12 * D):
    41  //        epochs_for_validator_set_churn = (
    42  //            N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T))
    43  //        )
    44  //        epochs_for_balance_top_ups = (
    45  //            N * (200 + 3 * D) // (600 * Delta)
    46  //        )
    47  //        ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups)
    48  //    else:
    49  //        ws_period += (
    50  //            3 * N * D * t // (200 * Delta * (T - t))
    51  //        )
    52  //
    53  //    return ws_period
    54  func ComputeWeakSubjectivityPeriod(st iface.ReadOnlyBeaconState) (types.Epoch, error) {
    55  	// Weak subjectivity period cannot be smaller than withdrawal delay.
    56  	wsp := uint64(params.BeaconConfig().MinValidatorWithdrawabilityDelay)
    57  
    58  	// Cardinality of active validator set.
    59  	N, err := ActiveValidatorCount(st, CurrentEpoch(st))
    60  	if err != nil {
    61  		return 0, fmt.Errorf("cannot obtain active valiadtor count: %w", err)
    62  	}
    63  	if N == 0 {
    64  		return 0, errors.New("no active validators found")
    65  	}
    66  
    67  	// Average effective balance in the given validator set, in Ether.
    68  	t, err := TotalActiveBalance(st)
    69  	if err != nil {
    70  		return 0, fmt.Errorf("cannot find total active balance of validators: %w", err)
    71  	}
    72  	t = t / N / params.BeaconConfig().GweiPerEth
    73  
    74  	// Maximum effective balance per validator.
    75  	T := params.BeaconConfig().MaxEffectiveBalance / params.BeaconConfig().GweiPerEth
    76  
    77  	// Validator churn limit.
    78  	delta, err := ValidatorChurnLimit(N)
    79  	if err != nil {
    80  		return 0, fmt.Errorf("cannot obtain active validator churn limit: %w", err)
    81  	}
    82  
    83  	// Balance top-ups.
    84  	Delta := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().MaxDeposits))
    85  
    86  	if delta == 0 || Delta == 0 {
    87  		return 0, errors.New("either validator churn limit or balance top-ups is zero")
    88  	}
    89  
    90  	// Safety decay, maximum tolerable loss of safety margin of FFG finality.
    91  	D := params.BeaconConfig().SafetyDecay
    92  
    93  	if T*(200+3*D) < t*(200+12*D) {
    94  		epochsForValidatorSetChurn := N * (t*(200+12*D) - T*(200+3*D)) / (600 * delta * (2*t + T))
    95  		epochsForBalanceTopUps := N * (200 + 3*D) / (600 * Delta)
    96  		wsp += mathutil.Max(epochsForValidatorSetChurn, epochsForBalanceTopUps)
    97  	} else {
    98  		wsp += 3 * N * D * t / (200 * Delta * (T - t))
    99  	}
   100  
   101  	return types.Epoch(wsp), nil
   102  }
   103  
   104  // IsWithinWeakSubjectivityPeriod verifies if a given weak subjectivity checkpoint is not stale i.e.
   105  // the current node is so far beyond, that a given state and checkpoint are not for the latest weak
   106  // subjectivity point. Provided checkpoint still can be used to double-check that node's block root
   107  // at a given epoch matches that of the checkpoint.
   108  //
   109  // Reference implementation:
   110  // https://github.com/ethereum/eth2.0-specs/blob/master/specs/phase0/weak-subjectivity.md#checking-for-stale-weak-subjectivity-checkpoint
   111  //
   112  // def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool:
   113  //    # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint
   114  //    assert ws_state.latest_block_header.state_root == ws_checkpoint.root
   115  //    assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch
   116  //
   117  //    ws_period = compute_weak_subjectivity_period(ws_state)
   118  //    ws_state_epoch = compute_epoch_at_slot(ws_state.slot)
   119  //    current_epoch = compute_epoch_at_slot(get_current_slot(store))
   120  //    return current_epoch <= ws_state_epoch + ws_period
   121  func IsWithinWeakSubjectivityPeriod(
   122  	currentEpoch types.Epoch, wsState iface.ReadOnlyBeaconState, wsCheckpoint *eth.WeakSubjectivityCheckpoint) (bool, error) {
   123  	// Make sure that incoming objects are not nil.
   124  	if wsState == nil || wsState.IsNil() || wsState.LatestBlockHeader() == nil || wsCheckpoint == nil {
   125  		return false, errors.New("invalid weak subjectivity state or checkpoint")
   126  	}
   127  
   128  	// Assert that state and checkpoint have the same root and epoch.
   129  	if !bytes.Equal(wsState.LatestBlockHeader().StateRoot, wsCheckpoint.StateRoot) {
   130  		return false, fmt.Errorf("state (%#x) and checkpoint (%#x) roots do not match",
   131  			wsState.LatestBlockHeader().StateRoot, wsCheckpoint.StateRoot)
   132  	}
   133  	if SlotToEpoch(wsState.Slot()) != wsCheckpoint.Epoch {
   134  		return false, fmt.Errorf("state (%v) and checkpoint (%v) epochs do not match",
   135  			SlotToEpoch(wsState.Slot()), wsCheckpoint.Epoch)
   136  	}
   137  
   138  	// Compare given epoch to state epoch + weak subjectivity period.
   139  	wsPeriod, err := ComputeWeakSubjectivityPeriod(wsState)
   140  	if err != nil {
   141  		return false, fmt.Errorf("cannot compute weak subjectivity period: %w", err)
   142  	}
   143  	wsStateEpoch := SlotToEpoch(wsState.Slot())
   144  
   145  	return currentEpoch <= wsStateEpoch+wsPeriod, nil
   146  }
   147  
   148  // LatestWeakSubjectivityEpoch returns epoch of the most recent weak subjectivity checkpoint known to a node.
   149  //
   150  // Within the weak subjectivity period, if two conflicting blocks are finalized, 1/3 - D (D := safety decay)
   151  // of validators will get slashed. Therefore, it is safe to assume that any finalized checkpoint within that
   152  // period is protected by this safety margin.
   153  func LatestWeakSubjectivityEpoch(st iface.ReadOnlyBeaconState) (types.Epoch, error) {
   154  	wsPeriod, err := ComputeWeakSubjectivityPeriod(st)
   155  	if err != nil {
   156  		return 0, err
   157  	}
   158  
   159  	finalizedEpoch := st.FinalizedCheckpointEpoch()
   160  	return finalizedEpoch - (finalizedEpoch % wsPeriod), nil
   161  }
   162  
   163  // ParseWeakSubjectivityInputString parses "blocks_root:epoch_number" string into a checkpoint.
   164  func ParseWeakSubjectivityInputString(wsCheckpointString string) (*eth.Checkpoint, error) {
   165  	if wsCheckpointString == "" {
   166  		return nil, nil
   167  	}
   168  
   169  	// Weak subjectivity input string must contain ":" to separate epoch and block root.
   170  	if !strings.Contains(wsCheckpointString, ":") {
   171  		return nil, fmt.Errorf("%s did not contain column", wsCheckpointString)
   172  	}
   173  
   174  	// Strip prefix "0x" if it's part of the input string.
   175  	wsCheckpointString = strings.TrimPrefix(wsCheckpointString, "0x")
   176  
   177  	// Get the hexadecimal block root from input string.
   178  	s := strings.Split(wsCheckpointString, ":")
   179  	if len(s) != 2 {
   180  		return nil, errors.New("weak subjectivity checkpoint input should be in `block_root:epoch_number` format")
   181  	}
   182  
   183  	bRoot, err := hex.DecodeString(s[0])
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	if len(bRoot) != 32 {
   188  		return nil, errors.New("block root is not length of 32")
   189  	}
   190  
   191  	// Get the epoch number from input string.
   192  	epoch, err := strconv.ParseUint(s[1], 10, 64)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	return &eth.Checkpoint{
   198  		Epoch: types.Epoch(epoch),
   199  		Root:  bRoot,
   200  	}, nil
   201  }