mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 23:48:06 -05:00
Unrealized justification (#10659)
* unrealized justification API * Add time elapse logging * add unrealized justification checkpoint * Use UnrealizedJustificationCheckpoint * Refactor unrealized checkpoints * Move logic to state package * do not use ctx on a sum * fix ctx * add tests * fix conflicts * unhandled error * Fix ordering in computing checkpoints * gaz * keep finalized checkpoint if nothing justified * gaz * copy checkpoint * fix check for nil * Add state package tests * Add tests * Radek's review * add more tests * Update beacon-chain/core/epoch/precompute/justification_finalization.go Co-authored-by: terencechain <terence@prysmaticlabs.com> * deduplicate to stateutil * missing file * Add stateutil test * Minor refactor, don't export certain things * Fix exports in tests * remove unused error Co-authored-by: terence tsao <terence@prysmaticlabs.com> Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
@@ -28,6 +28,7 @@ go_library(
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -43,11 +44,13 @@ go_test(
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/epoch:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/v1:go_default_library",
|
||||
"//beacon-chain/state/v2:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
|
||||
@@ -2,6 +2,7 @@ package precompute
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
@@ -9,6 +10,24 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
var errNilState = errors.New("nil state")
|
||||
|
||||
// UnrealizedCheckpoints returns the justification and finalization checkpoints of the
|
||||
// given state as if it was progressed with empty slots until the next epoch.
|
||||
func UnrealizedCheckpoints(st state.BeaconState) (*ethpb.Checkpoint, *ethpb.Checkpoint, error) {
|
||||
if st == nil || st.IsNil() {
|
||||
return nil, nil, errNilState
|
||||
}
|
||||
|
||||
activeBalance, prevTarget, currentTarget, err := st.UnrealizedCheckpointBalances()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
justification := processJustificationBits(st, activeBalance, prevTarget, currentTarget)
|
||||
return computeCheckpoints(st, justification)
|
||||
}
|
||||
|
||||
// ProcessJustificationAndFinalizationPreCompute processes justification and finalization during
|
||||
// epoch processing. This is where a beacon node can justify and finalize a new epoch.
|
||||
// Note: this is an optimized version by passing in precomputed total and attesting balances.
|
||||
@@ -34,12 +53,55 @@ func ProcessJustificationAndFinalizationPreCompute(state state.BeaconState, pBal
|
||||
return state, nil
|
||||
}
|
||||
|
||||
return weighJustificationAndFinalization(state, pBal.ActiveCurrentEpoch, pBal.PrevEpochTargetAttested, pBal.CurrentEpochTargetAttested)
|
||||
newBits := processJustificationBits(state, pBal.ActiveCurrentEpoch, pBal.PrevEpochTargetAttested, pBal.CurrentEpochTargetAttested)
|
||||
|
||||
return weighJustificationAndFinalization(state, newBits)
|
||||
}
|
||||
|
||||
// weighJustificationAndFinalization processes justification and finalization during
|
||||
// processJustificationBits processes the justification bits during epoch processing.
|
||||
func processJustificationBits(state state.BeaconState, totalActiveBalance, prevEpochTargetBalance, currEpochTargetBalance uint64) bitfield.Bitvector4 {
|
||||
newBits := state.JustificationBits()
|
||||
newBits.Shift(1)
|
||||
// If 2/3 or more of total balance attested in the previous epoch.
|
||||
if 3*prevEpochTargetBalance >= 2*totalActiveBalance {
|
||||
newBits.SetBitAt(1, true)
|
||||
}
|
||||
|
||||
if 3*currEpochTargetBalance >= 2*totalActiveBalance {
|
||||
newBits.SetBitAt(0, true)
|
||||
}
|
||||
|
||||
return newBits
|
||||
}
|
||||
|
||||
// updateJustificationAndFinalization processes justification and finalization during
|
||||
// epoch processing. This is where a beacon node can justify and finalize a new epoch.
|
||||
//
|
||||
func weighJustificationAndFinalization(state state.BeaconState, newBits bitfield.Bitvector4) (state.BeaconState, error) {
|
||||
jc, fc, err := computeCheckpoints(state, newBits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := state.SetPreviousJustifiedCheckpoint(state.CurrentJustifiedCheckpoint()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := state.SetCurrentJustifiedCheckpoint(jc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := state.SetJustificationBits(newBits); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := state.SetFinalizedCheckpoint(fc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// computeCheckpoints computes the new Justification and Finalization
|
||||
// checkpoints at epoch transition
|
||||
// Spec pseudocode definition:
|
||||
// def weigh_justification_and_finalization(state: BeaconState,
|
||||
// total_active_balance: Gwei,
|
||||
@@ -77,88 +139,57 @@ func ProcessJustificationAndFinalizationPreCompute(state state.BeaconState, pBal
|
||||
// # The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source
|
||||
// if all(bits[0:2]) and old_current_justified_checkpoint.epoch + 1 == current_epoch:
|
||||
// state.finalized_checkpoint = old_current_justified_checkpoint
|
||||
func weighJustificationAndFinalization(state state.BeaconState,
|
||||
totalActiveBalance, prevEpochTargetBalance, currEpochTargetBalance uint64) (state.BeaconState, error) {
|
||||
func computeCheckpoints(state state.BeaconState, newBits bitfield.Bitvector4) (*ethpb.Checkpoint, *ethpb.Checkpoint, error) {
|
||||
prevEpoch := time.PrevEpoch(state)
|
||||
currentEpoch := time.CurrentEpoch(state)
|
||||
oldPrevJustifiedCheckpoint := state.PreviousJustifiedCheckpoint()
|
||||
oldCurrJustifiedCheckpoint := state.CurrentJustifiedCheckpoint()
|
||||
|
||||
// Process justifications
|
||||
if err := state.SetPreviousJustifiedCheckpoint(state.CurrentJustifiedCheckpoint()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newBits := state.JustificationBits()
|
||||
newBits.Shift(1)
|
||||
if err := state.SetJustificationBits(newBits); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Note: the spec refers to the bit index position starting at 1 instead of starting at zero.
|
||||
// We will use that paradigm here for consistency with the godoc spec definition.
|
||||
|
||||
// If 2/3 or more of total balance attested in the previous epoch.
|
||||
if 3*prevEpochTargetBalance >= 2*totalActiveBalance {
|
||||
blockRoot, err := helpers.BlockRoot(state, prevEpoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get block root for previous epoch %d", prevEpoch)
|
||||
}
|
||||
if err := state.SetCurrentJustifiedCheckpoint(ðpb.Checkpoint{Epoch: prevEpoch, Root: blockRoot}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newBits = state.JustificationBits()
|
||||
newBits.SetBitAt(1, true)
|
||||
if err := state.SetJustificationBits(newBits); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
justifiedCheckpoint := state.CurrentJustifiedCheckpoint()
|
||||
finalizedCheckpoint := state.FinalizedCheckpoint()
|
||||
|
||||
// If 2/3 or more of the total balance attested in the current epoch.
|
||||
if 3*currEpochTargetBalance >= 2*totalActiveBalance {
|
||||
if newBits.BitAt(0) {
|
||||
blockRoot, err := helpers.BlockRoot(state, currentEpoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get block root for current epoch %d", prevEpoch)
|
||||
return nil, nil, errors.Wrapf(err, "could not get block root for current epoch %d", currentEpoch)
|
||||
}
|
||||
if err := state.SetCurrentJustifiedCheckpoint(ðpb.Checkpoint{Epoch: currentEpoch, Root: blockRoot}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newBits = state.JustificationBits()
|
||||
newBits.SetBitAt(0, true)
|
||||
if err := state.SetJustificationBits(newBits); err != nil {
|
||||
return nil, err
|
||||
justifiedCheckpoint.Epoch = currentEpoch
|
||||
justifiedCheckpoint.Root = blockRoot
|
||||
} else if newBits.BitAt(1) {
|
||||
// If 2/3 or more of total balance attested in the previous epoch.
|
||||
blockRoot, err := helpers.BlockRoot(state, prevEpoch)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "could not get block root for previous epoch %d", prevEpoch)
|
||||
}
|
||||
justifiedCheckpoint.Epoch = prevEpoch
|
||||
justifiedCheckpoint.Root = blockRoot
|
||||
}
|
||||
|
||||
// Process finalization according to Ethereum Beacon Chain specification.
|
||||
justification := state.JustificationBits().Bytes()[0]
|
||||
if len(newBits) == 0 {
|
||||
return nil, nil, errors.New("empty justification bits")
|
||||
}
|
||||
justification := newBits.Bytes()[0]
|
||||
|
||||
// 2nd/3rd/4th (0b1110) most recent epochs are justified, the 2nd using the 4th as source.
|
||||
if justification&0x0E == 0x0E && (oldPrevJustifiedCheckpoint.Epoch+3) == currentEpoch {
|
||||
if err := state.SetFinalizedCheckpoint(oldPrevJustifiedCheckpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalizedCheckpoint = oldPrevJustifiedCheckpoint
|
||||
}
|
||||
|
||||
// 2nd/3rd (0b0110) most recent epochs are justified, the 2nd using the 3rd as source.
|
||||
if justification&0x06 == 0x06 && (oldPrevJustifiedCheckpoint.Epoch+2) == currentEpoch {
|
||||
if err := state.SetFinalizedCheckpoint(oldPrevJustifiedCheckpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalizedCheckpoint = oldPrevJustifiedCheckpoint
|
||||
}
|
||||
|
||||
// 1st/2nd/3rd (0b0111) most recent epochs are justified, the 1st using the 3rd as source.
|
||||
if justification&0x07 == 0x07 && (oldCurrJustifiedCheckpoint.Epoch+2) == currentEpoch {
|
||||
if err := state.SetFinalizedCheckpoint(oldCurrJustifiedCheckpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalizedCheckpoint = oldCurrJustifiedCheckpoint
|
||||
}
|
||||
|
||||
// The 1st/2nd (0b0011) most recent epochs are justified, the 1st using the 2nd as source
|
||||
if justification&0x03 == 0x03 && (oldCurrJustifiedCheckpoint.Epoch+1) == currentEpoch {
|
||||
if err := state.SetFinalizedCheckpoint(oldCurrJustifiedCheckpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalizedCheckpoint = oldCurrJustifiedCheckpoint
|
||||
}
|
||||
|
||||
return state, nil
|
||||
return justifiedCheckpoint, finalizedCheckpoint, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package precompute_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute"
|
||||
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
|
||||
v2 "github.com/prysmaticlabs/prysm/beacon-chain/state/v2"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
types "github.com/prysmaticlabs/prysm/consensus-types/primitives"
|
||||
@@ -123,3 +126,127 @@ func TestProcessJustificationAndFinalizationPreCompute_JustifyPrevEpoch(t *testi
|
||||
assert.DeepEqual(t, params.BeaconConfig().ZeroHash[:], newState.FinalizedCheckpoint().Root)
|
||||
assert.Equal(t, types.Epoch(0), newState.FinalizedCheckpointEpoch(), "Unexpected finalized epoch")
|
||||
}
|
||||
|
||||
func TestUnrealizedCheckpoints(t *testing.T) {
|
||||
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
|
||||
balances := make([]uint64, len(validators))
|
||||
for i := 0; i < len(validators); i++ {
|
||||
validators[i] = ðpb.Validator{
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
}
|
||||
balances[i] = params.BeaconConfig().MaxEffectiveBalance
|
||||
}
|
||||
pjr := [32]byte{'p'}
|
||||
cjr := [32]byte{'c'}
|
||||
je := types.Epoch(3)
|
||||
fe := types.Epoch(2)
|
||||
pjcp := ðpb.Checkpoint{Root: pjr[:], Epoch: fe}
|
||||
cjcp := ðpb.Checkpoint{Root: cjr[:], Epoch: je}
|
||||
fcp := ðpb.Checkpoint{Root: pjr[:], Epoch: fe}
|
||||
tests := []struct {
|
||||
name string
|
||||
slot types.Slot
|
||||
prevVals, currVals int
|
||||
expectedJustified, expectedFinalized types.Epoch // The expected unrealized checkpoint epochs
|
||||
}{
|
||||
{
|
||||
"Not enough votes, keep previous justification",
|
||||
129,
|
||||
len(validators) / 3,
|
||||
len(validators) / 3,
|
||||
je,
|
||||
fe,
|
||||
},
|
||||
{
|
||||
"Not enough votes, keep previous justification, N+2",
|
||||
161,
|
||||
len(validators) / 3,
|
||||
len(validators) / 3,
|
||||
je,
|
||||
fe,
|
||||
},
|
||||
{
|
||||
"Enough to justify previous epoch but not current",
|
||||
129,
|
||||
2*len(validators)/3 + 3,
|
||||
len(validators) / 3,
|
||||
je,
|
||||
fe,
|
||||
},
|
||||
{
|
||||
"Enough to justify previous epoch but not current, N+2",
|
||||
161,
|
||||
2*len(validators)/3 + 3,
|
||||
len(validators) / 3,
|
||||
je + 1,
|
||||
fe,
|
||||
},
|
||||
{
|
||||
"Enough to justify current epoch",
|
||||
129,
|
||||
len(validators) / 3,
|
||||
2*len(validators)/3 + 3,
|
||||
je + 1,
|
||||
fe,
|
||||
},
|
||||
{
|
||||
"Enough to justify current epoch, but not previous",
|
||||
161,
|
||||
len(validators) / 3,
|
||||
2*len(validators)/3 + 3,
|
||||
je + 2,
|
||||
fe,
|
||||
},
|
||||
{
|
||||
"Enough to justify current and previous",
|
||||
161,
|
||||
2*len(validators)/3 + 3,
|
||||
2*len(validators)/3 + 3,
|
||||
je + 2,
|
||||
fe,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
base := ðpb.BeaconStateAltair{
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
|
||||
Validators: validators,
|
||||
Slot: test.slot,
|
||||
CurrentEpochParticipation: make([]byte, params.BeaconConfig().MinGenesisActiveValidatorCount),
|
||||
PreviousEpochParticipation: make([]byte, params.BeaconConfig().MinGenesisActiveValidatorCount),
|
||||
Balances: balances,
|
||||
PreviousJustifiedCheckpoint: pjcp,
|
||||
CurrentJustifiedCheckpoint: cjcp,
|
||||
FinalizedCheckpoint: fcp,
|
||||
InactivityScores: make([]uint64, len(validators)),
|
||||
JustificationBits: make(bitfield.Bitvector4, 1),
|
||||
}
|
||||
for i := 0; i < test.prevVals; i++ {
|
||||
base.PreviousEpochParticipation[i] = 0xFF
|
||||
}
|
||||
for i := 0; i < test.currVals; i++ {
|
||||
base.CurrentEpochParticipation[i] = 0xFF
|
||||
}
|
||||
if test.slot > 130 {
|
||||
base.JustificationBits.SetBitAt(2, true)
|
||||
base.JustificationBits.SetBitAt(3, true)
|
||||
} else {
|
||||
base.JustificationBits.SetBitAt(1, true)
|
||||
base.JustificationBits.SetBitAt(2, true)
|
||||
}
|
||||
|
||||
state, err := v2.InitializeFromProto(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = altair.InitializePrecomputeValidators(context.Background(), state)
|
||||
require.NoError(t, err)
|
||||
|
||||
jc, fc, err := precompute.UnrealizedCheckpoints(state)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, test.expectedJustified, jc.Epoch)
|
||||
require.DeepEqual(t, test.expectedFinalized, fc.Epoch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user