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 ð.Checkpoint{ 198 Epoch: types.Epoch(epoch), 199 Root: bRoot, 200 }, nil 201 }