# Keeper assignment and release in RanDAO realisation

## Keeper assignment

### Choice algorithm

The algorithm for a RanDAO-based choice of a next keeper is encoded in the \_assignNextKeeper function. We outline it here

<pre class="language-solidity" data-overflow="wrap"><code class="lang-solidity">function _assignNextKeeper(bytes32 jobKey_) internal {
<strong>  //get the randao realisation for the current block, stored in the block.difficulty
</strong>  uint256 pseudoRandom = _getPseudoRandom();
  //get the cardinality of the keeper set so that the index may be taken modulo it
  uint256 totalActiveKeepers = activeKeepers.length();
  //get the minimal CVP stake demanded of the keeper by the job
  uint256 _jobMinKeeperCvp = jobMinKeeperCvp[jobKey_];
  //obtain the index by taking the pseudorandom input, adding the job key so that variance in chosen keeper indices is observed within any single block, and mapping the result into the keeper index set by taking the modulo operation
  //the resultant variable index is the index of the keeper selected for execution
  uint256 index;
  unchecked {
        index = ((pseudoRandom + uint256(jobKey_)) % totalActiveKeepers);
      }
  //iterate over all the keepers, starting with the randomly selected one, until a keeper admissible for the job at hand is found or all gas has been expended
  while (true) {
        //effectively modulo operation
        if (index  >= totalActiveKeepers) {
          index = 0;
        }
        //obtain the ID of the next keeper by the index selected
        uint256 _nextExecutionKeeperId = activeKeepers.at(index);
        //required stake is set to be the global minimal demanded CVP unless the job has a specification of its own
        uint256 requiredStake = _jobMinKeeperCvp > 0 ? _jobMinKeeperCvp : minKeeperCvp;
        //load the keeper object at the selected ID
        Keeper memory keeper = keepers[_nextExecutionKeeperId];
        //if the keeper is admissible, i.e. active and has sufficient stake, then assign him as the next keeper of the job and terminate the passage
        if (keeper.isActive &#x26;&#x26; keeper.cvpStake >= requiredStake) {
          jobNextKeeperId[jobKey_] = _nextExecutionKeeperId;
          
          keeperLocksByJob[_nextExecutionKeeperId].add(jobKey_);
          emit KeeperJobLock(_nextExecutionKeeperId, jobKey_);
          return;
        }
        index += 1;
        }
    }
}
</code></pre>

### Calling the internal function of keeper assignment

#### Exogeneously

Exogeneous manual keeper assignment is available to the job(s) owner if needed:

```solidity
function assignKeeper(bytes32[] calldata jobKeys_) external
```

#### Conditionally (on job credits)

Of more interest is the wrapped conditional keeper assignment, since we do not wish keepers to be assigned to jobs with below-minimum resources with which to pay them. This is handled by the internal function

{% code overflow="wrap" %}

```solidity
function _assignNextKeeperIfRequired(
//key of the job for which to assign a keeper
bytes32 jobKey_
) internal returns (
//assignment success indicator
bool assigned
)
//get the job credits balance (11 bytes, or 256-168 = 88 bits, starting from 128-th bit (12th byte) in binary representation)
uint256 credits = (binJob << 128) >> 168;
//check job owner credits instead if the owner pays
if (ConfigFlags.check(binJob, CFG_USE_JOB_OWNER_CREDITS)) {
  credits = jobOwnerCredits[jobOwners[jobKey_]];
}
//if a keeper is not assigned yet
if (jobNextKeeperId[jobKey_] == 0 && 
//and the job/owner has enough credits with respect to the threshold given by the Agent parameter jobMinCreditsFinney
credits >= (uint256(rdConfig.jobMinCreditsFinney) * 0.001 ether)) {
  //then assign a keeper and return True
  _assignNextKeeper(jobKey_);
  return true;
}
//else False
return false;
```

{% endcode %}

#### Cases in which conditional assignment is invoked

The conditional keeper assignment happens when:

1. The job config is updated, with the credits source having changed or a previously inactive job activating
2. Credits are deposited to a Job (via the overridden hook `_afterDepositJobCredits(bytes32 jobKey)` invoked in function `depositJobCredits(bytes32 jobKey_)` in the core contract)

#### Cases in which unconditional assignment is invoked

Unconditional keeper assignment happens when:

1. Job execution is successful, and the old keeper needs to be swapped for a new one (inside the `function _afterExecutionSucceeded(bytes32 jobKey, uint256 actualKeeperId_, uint256 binJob_) internal` hook).
2. After a job is registered (inside the `function _afterRegisterJob(bytes32 jobKey) internal` hook)

## Keeper release

### Technical implementation of releasing a keeper

Keeper release is performed by the internal function `_releaseKeeper(bytes32 jobKey_, uint256 keeperId_)` and consists of clearing the Job parameters pertaining to the next keeper and possible slashing and removing the job from the keeper's pending pool:

{% code overflow="wrap" %}

```solidity
function _releaseKeeper(
    //key of job for which to release
    bytes32 jobKey_, 
    //ID of keeper which to release; in actuality could be inferred from the job's NextKeeperId mapping
    uint256 keeperId_) internal {
    //remove job from the keeper's pending jobs pool
    keeperLocksByJob[keeperId_].remove(jobKey_);
    //remove the assigned keeper from the Job's corresponding mapping
    jobNextKeeperId[jobKey_] = 0;
    //slashing is no longer possible, since there is no keeper assigned
    jobSlashingPossibleAfter[jobKey_] = 0;
    //so clear the reserved slasher as well
    jobReservedSlasherId[jobKey_] = 0;
    //event for WS listeners
    emit KeeperJobUnlock(keeperId_, jobKey_);
  }
```

{% endcode %}

#### Calling the keeper release function

We need to exercise utmost care in calling such function so that it does not provide malicious keepers with a way to escape punishment.&#x20;

Firstly, there is a defined conditional release on insufficiency of Job credits, wrapped into a function `releaseKeeperIfRequired`, itself wrapping `releaseKeeperIfRequiredBinJob`. Since `releaseKeeperIfRequired` is a simple wrapping of a call to `releaseKeeperIfRequiredBinJob` with a freshly read binary job representation passed as an argument, we only describe the latter function.&#x20;

<pre class="language-solidity" data-overflow="wrap"><code class="lang-solidity">function _releaseKeeperIfRequiredBinJob(
  //job for which to release
  bytes32 jobKey_,
  //keeper whom to release
  uint256 keeperId_,
  //binary job representation corresponding to the job with jobKey_
  uint256 binJob_,
  //whether to check if a keeper has already been released
  bool checkAlreadyReleased
) internal returns (bool released) {
  //get the job credits balance (11 bytes, or 256-168 = 88 bits, starting from 128-th bit (12th byte) in binary representation)
  uint256 credits = (binJob_ &#x3C;&#x3C; 128) >> 168;
  //if job owner pays, get his credits instead
  if (ConfigFlags.check(binJob_, CFG_USE_JOB_OWNER_CREDITS)) {
    credits = jobOwnerCredits[jobOwners[jobKey_]];
  }
  //if the keeper is not yet released (or the check flag is False), and credits of the job are insufficient with respect to an Agent-wide acceptable minimum
  if ((!checkAlreadyReleased || jobNextKeeperId[jobKey_] != 0) &#x26;&#x26; credits &#x3C; (uint256(rdConfig.jobMinCreditsFinney) * 0.001 ether)) {
    //then release and return True
    _releaseKeeper(jobKey_, keeperId_);
    return true;
  }
  //else False
  return false;
<strong>}
</strong></code></pre>

This function is called when:

1. Assigning a keeper (in keeper assignment, a check of `_releaseKeeperIfRequiredBinJob` (with the `checkAlreadyReleased` flag passed as `False`) being `False` ensures no keeper is assigned for an job with too few tokens)
2. Job credits are withdrawn (via the hook `_afterWithdrawJobCredits(bytes32 jobKey_) internal`)
3. `releaseJob` is called by a keeper admin (the check happens before any slashing checks and is the only way for a job to be released without satisfying these checks, e.g. when the admissibility period is not over yet, and the keeper is technically locked by the possibility of being slashed, he still can be released from his duty if the job does not meet the minimum credit demand)
4. The job config is updated, with the credits source having changed, and `_assignNextKeeperIfRequired` returning `False`.&#x20;

Unwrapped `_releaseKeeperIfRequiredBinJob` is called when:

1. Assigning a new keeper; `checkAlreadyReleased` is passed as `False`, so this has the meaning of exiting the keeper assignment process when the job/owner does not have sufficient tokens.&#x20;

Unconditional release is called when:

1. The job owner calls `releaseJob`
2. The keeper admin calls `releaseJob`, the Job has sufficient (from the Agent's standpoint) credits, and the admissibility period of slashing has elapsed
3. The keeper is disabled
4. After execution of the job being automated reverts
5. The job is deactivated
6. After execution of the job being automated succeeds
