The Keeper is the one who initiates execution of a task.
Overview
The keeper is a combination of two blockchain addresses and an off-chain bot. The purpose of the bot is to watch all jobs available in the PowerAgent and offer its candidature for task execution as soon as the conditions for the execution are met.
Keeper is an externally owned account linked to an abstract Keeper object by a key-Worker (abstract object used in PowerAgent to represent keepers) relation; groups of Workers also may share Admins. The purpose of the keeper to invoke Agent's function execute_44g58pv when conditions are met.
Addresses
A keeper needs two addresses: Admin address for management and Worker address for execution.
Admin - this address is used for management of CVP stake and registration and configuration of Worker addresses.
Worker - is registered as the keeper address in the Agent contract. On behalf of this address the tasks are executed. The rewards may be configured to be paid immediately to this address or be collected on the Agent contract and thereafter withdrawn, refer to <section>.
Bot
The bot connects to the blockchain via a websocket RPC connection and continuously listens to events from the blockchain. When the conditions for the execution are met, the bot sends the transaction from the Worker address to the Agent contract. It is recommended to set up a private node to maintain the quality of connection on one's own.
ā ā ā
Specification
This section describes the on-chain part of the Keeper only. For off-chain software, refer to the <section>.
Structure
The Keeper is represented in the Agent as the following structure Keeper:
Keeper structure
struct Keeper {
//address of the keeper
address worker;
//keeperās stake in CVP
uint88 cvpStake;
//indicator of the keeper being active (i.e. able to execute jobs)
bool isActive;
}
Mappings
All mappings related to the Keeper structure in the Agent:
Keeper mappings list
//Internally tracked ID (enumeration of keepers)
mapping(uint256 => Keeper) keepers;
//Keeperās admin, which is defined as an address that is permitted to authorise changes to this keeper
mapping(uint256 => address) keeperAdmins;
//The totality of the CVP amount for which a keeperās stake is slashed
mapping(uint256 => uint256) slashedStakeOf;
//The compensation in native chain tokens a keeper has accrued
mapping(uint256 => uint256) compensations;
//The amount of CVP for which the redemption process has started and is currently pending
mapping(uint256 => uint256) pendingWithdrawalAmounts;
//The timestamp at which the time-out ends, allowing the keeper or the admin thereof to call finalizeRedeem and withdraw the pending amount.
mapping(uint256 => uint256) pendingWithdrawalEndsAt;
//Reverse mapping of keepers, defining an isomorphism between keeper addresses and IDs.
mapping(address => uint256) workerKeeperIds;
RanDAO-specific mappings
// Gives a set of pending (locking in a certain sense) jobs for a keeper
mapping(uint256 => EnumerableSet.Bytes32Set) internal keeperLocksByJob;
// Gives timestamp at which keeper activation (staggered in time in RanDAO realisation) can be finalised
mapping(uint256 => uint256) public keeperActivationCanBeFinalizedAt;
Functions
The list of functions used to manage and update Keeper information:
registerAsKeeper
Registers a new keeper and stores the information in the corresponding mappings.
function registerAsKeeper (
//keeper worker address
address worker_,
//initial CVP stake deposit amount
uint256 initialDepositAmount_
//returns id of the newly registered keeper
) public virtual returns (uint256 keeperId)
{
//asserts the provided address is not already a worker
_assertWorkerNotAssigned()
//asserts the provided CVP meets the minimal amount requirement
minKeeperCvp_assertion()
...
}
setWorkerAddress
Updates a keeperās worker address. Should be called from keeper admin address.
function setWorkerAddress (
//id of the keeper which worker address should be changed
uint256 keeperId_,
//a new address for the worker
address worker_
) external
{
//Only Admin address is allowed to change the worker address
_assertOnlyKeeperAdmin()
//Assert that this address is not already registered as a worker
_assertWorkerNotAssigned()
...
}
withdrawCompensation
Immediately withdraws the keeperās compensation in native tokens.
functinon withdrawCompensation() (
//id of the keeper to withdraw compensation for
uint256 keeperId_,
//address to which to withdraw the compensation
address payable to_,
//the amount to withdraw
uint256 amount_
) external
{
//Assert that there is something to withdraw
_assertNonZeroAmount()
//Only keeper admin and worker addresses are allowed to withdraw compensation
_assertOnlyKeeperAdminOrWorker()
//Can't withdraw more compensation than is available
amount_not_exceeds_available_assertion()
...
}
stake
Deposits CVP to a given keeperās stake.
function stake (
//id of the keeper to stake CVP for
uint256 keeperId_,
//amount of CVP to stake
uint256 amount_
) external
{
//Assert that the amount provided is not zero
_assertNonZeroAmount()
//Assert that there is a keeper assigned to this id
_assertKeeperIdExists()
//Assert that the final amount after stake doesn't overflow max amount (uint88)
no_amount_after_overflow_assertion()
...
}
initiateRedeem
Prepares CVP for withdrawal by first burning staked tokens to compensate the slashed amount (if any) and then accumulating the remainder as amount pending for withdrawal. The cooldown period, specified by the agent config, is re-initiated after every call of the function, even if the pending value before the call was nonzero.
function initiateRedeem (
//id of the keeper to reedem from
uint256 keeperId_,
//amount of CVP to redeem
uint256 amount_,
//Returns the timestamp when the redeem can be finalized
) external returns (uint256 pendingWithdrawalAfter)
{
//Only keeper admin
_assertOnlyKeeperAdmin()
//Can't redeem nothing
_assertNonZeroAmount()
//Hook before the Redeem, specifiable by particular realisations
_beforeInitiateRedeem()
//Can't redeem more than is staked
amount_not_exceeds_stake_assertion()
//If the keeper undergoes slashing, can't withdraw the part that is reserved for slashing until all slashed amount is compensated
amount_sufficient_to_compensate_slashed_stake_assertion()
...
}
finalizeRedeem
Finalizes CVP withdrawal and sends the previously staked tokens to a specified address.
function finalizeRedeem (
//id of the keeper to finalize redeem for
uint256 keeperId_
//address to transfer the redeemed CVP to
address to_
//Returns the amount of CVP redeemed in case of success
) external returns (uint256 redeemedCvp)
{
//Assert that the withdrawal cooldown period has passed
withdrawal_time_limit_elapsed_assertion()
//Assert that withdrawal of a nonzero amount is actually pending
pending_withdrawal_extant()
...
}
Job lock-related RanDAO-specific functions
releaseJob
Manually releases a keeper from executing a job.
function releaseJob(
//key of the job to release
bytes32 jobKey_
) external {
//only Admin can release
_assertOnlyKeeperAdmin(assignedKeeperId);
// Release if insufficient credits
if (_releaseKeeperIfRequired(jobKey_, assignedKeeperId)) {
return;
}
// 2. Check interval timeouts otherwise
// 2.1 If interval job
uint256 period2EndsAt = lastExecutionAt + rdConfig.period1 + rdConfig.period2;
if (period2EndsAt > block.timestamp) {
revert TooEarlyToRelease(jobKey_, period2EndsAt);
} // else can release
// 2.2 If resolver job
// if slashing process initiated
if (_jobSlashingPossibleAfter != 0) {
//admissibility period end timestamp
uint256 period2EndsAt = _jobSlashingPossibleAfter + rdConfig.period2;
//can't release if locked by initiated slashing
if (period2EndsAt > block.timestamp) {
revert TooEarlyToRelease(jobKey_, period2EndsAt);
}
// if no slashing initiated
} else {
revert CantRelease();
}
}
_releaseKeeper(jobKey_, assignedKeeperId);
}