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  }