github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/operations/voluntaryexits/service.go (about) 1 package voluntaryexits 2 3 import ( 4 "context" 5 "sort" 6 "sync" 7 8 types "github.com/prysmaticlabs/eth2-types" 9 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 10 iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface" 11 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 12 "github.com/prysmaticlabs/prysm/shared/params" 13 "go.opencensus.io/trace" 14 ) 15 16 // PoolManager maintains pending and seen voluntary exits. 17 // This pool is used by proposers to insert voluntary exits into new blocks. 18 type PoolManager interface { 19 PendingExits(state iface.ReadOnlyBeaconState, slot types.Slot, noLimit bool) []*ethpb.SignedVoluntaryExit 20 InsertVoluntaryExit(ctx context.Context, state iface.ReadOnlyBeaconState, exit *ethpb.SignedVoluntaryExit) 21 MarkIncluded(exit *ethpb.SignedVoluntaryExit) 22 } 23 24 // Pool is a concrete implementation of PoolManager. 25 type Pool struct { 26 lock sync.RWMutex 27 pending []*ethpb.SignedVoluntaryExit 28 } 29 30 // NewPool accepts a head fetcher (for reading the validator set) and returns an initialized 31 // voluntary exit pool. 32 func NewPool() *Pool { 33 return &Pool{ 34 pending: make([]*ethpb.SignedVoluntaryExit, 0), 35 } 36 } 37 38 // PendingExits returns exits that are ready for inclusion at the given slot. This method will not 39 // return more than the block enforced MaxVoluntaryExits. 40 func (p *Pool) PendingExits(state iface.ReadOnlyBeaconState, slot types.Slot, noLimit bool) []*ethpb.SignedVoluntaryExit { 41 p.lock.RLock() 42 defer p.lock.RUnlock() 43 44 // Allocate pending slice with a capacity of min(len(p.pending), maxVoluntaryExits) since the 45 // array cannot exceed the max and is typically less than the max value. 46 maxExits := params.BeaconConfig().MaxVoluntaryExits 47 if noLimit { 48 maxExits = uint64(len(p.pending)) 49 } 50 pending := make([]*ethpb.SignedVoluntaryExit, 0, maxExits) 51 for _, e := range p.pending { 52 if e.Exit.Epoch > helpers.SlotToEpoch(slot) { 53 continue 54 } 55 if v, err := state.ValidatorAtIndexReadOnly(e.Exit.ValidatorIndex); err == nil && 56 v.ExitEpoch() == params.BeaconConfig().FarFutureEpoch { 57 pending = append(pending, e) 58 if uint64(len(pending)) == maxExits { 59 break 60 } 61 } 62 } 63 return pending 64 } 65 66 // InsertVoluntaryExit into the pool. This method is a no-op if the pending exit already exists, 67 // or the validator is already exited. 68 func (p *Pool) InsertVoluntaryExit(ctx context.Context, state iface.ReadOnlyBeaconState, exit *ethpb.SignedVoluntaryExit) { 69 ctx, span := trace.StartSpan(ctx, "exitPool.InsertVoluntaryExit") 70 defer span.End() 71 p.lock.Lock() 72 defer p.lock.Unlock() 73 74 // Prevent malformed messages from being inserted. 75 if exit == nil || exit.Exit == nil { 76 return 77 } 78 79 existsInPending, index := existsInList(p.pending, exit.Exit.ValidatorIndex) 80 // If the item exists in the pending list and includes a more favorable, earlier 81 // exit epoch, we replace it in the pending list. If it exists but the prior condition is false, 82 // we simply return. 83 if existsInPending { 84 if exit.Exit.Epoch < p.pending[index].Exit.Epoch { 85 p.pending[index] = exit 86 } 87 return 88 } 89 90 // Has the validator been exited already? 91 if v, err := state.ValidatorAtIndexReadOnly(exit.Exit.ValidatorIndex); err != nil || 92 v.ExitEpoch() != params.BeaconConfig().FarFutureEpoch { 93 return 94 } 95 96 // Insert into pending list and sort. 97 p.pending = append(p.pending, exit) 98 sort.Slice(p.pending, func(i, j int) bool { 99 return p.pending[i].Exit.ValidatorIndex < p.pending[j].Exit.ValidatorIndex 100 }) 101 } 102 103 // MarkIncluded is used when an exit has been included in a beacon block. Every block seen by this 104 // node should call this method to include the exit. This will remove the exit from 105 // the pending exits slice. 106 func (p *Pool) MarkIncluded(exit *ethpb.SignedVoluntaryExit) { 107 p.lock.Lock() 108 defer p.lock.Unlock() 109 exists, index := existsInList(p.pending, exit.Exit.ValidatorIndex) 110 if exists { 111 // Exit we want is present at p.pending[index], so we remove it. 112 p.pending = append(p.pending[:index], p.pending[index+1:]...) 113 } 114 } 115 116 // Binary search to check if the index exists in the list of pending exits. 117 func existsInList(pending []*ethpb.SignedVoluntaryExit, searchingFor types.ValidatorIndex) (bool, int) { 118 i := sort.Search(len(pending), func(j int) bool { 119 return pending[j].Exit.ValidatorIndex >= searchingFor 120 }) 121 if i < len(pending) && pending[i].Exit.ValidatorIndex == searchingFor { 122 return true, i 123 } 124 return false, -1 125 }