Proof of Contribution
PoCo is a protocol designed to provide trust in an open and decentralized environment of untrusted machines.
The iExec platform provides a network where application providers, workers, and users can gather and work together. The fully decentralized nature of iExec implies that no single agent is trusted by default, and that those agents require incentives to contribute correctly.
In this context, Proof-of-Contribution (PoCo) is the protocol used by iExec for consensus over off-chain computing.
Protocol
Objectives
PoCo is a protocol designed to provide trust in an open and decentralized environment of untrusted machines.
In addition to providing trust, PoCo also orchestrates the different contributions to the iExec network, ensuring payments are always fair and timely.
A major quality of PoCo lies in the fact that it is a modular protocol. It comes with features that are context-specific.
Result consolidation
PoCo relies on replication to achieve result consolidation. This is purely a software solution that enforces a confidence level on the result. This confidence level can be customized by the requester.
This layer also supports the onchain consolidation of execution results carried out in Trusted Execution Environments (TEE) such as Intel SGX.
Secure payment
Once a deal is sealed on the iExec Marketplace, requester funds are locked to ensure all resource providers are paid for their contributions. Resources can take the form of data, applications or computing power.
Workers must achieve consensus on the execution result to get the requester’s funds. If consensus is not achieved, the requester is reimbursed.
Worker and scheduler must stake RLC to participate as a computing providers. Bad behaviour from an actor results in a loss of stake.
This is essential on the public blockchain, but all values can be set to 0 for private blockchain solutions.
Permissioning
For an execution to happen, a deal must be signed between the different parties involved. A permission mechanism can be used to control access to applications, datasets and worker pools.
The secure payment layer can be disabled for a private blockchain, or it can also be used in the context of the public blockchain to increase security. An example of permissioning is dataset restriction for a specific application.
Overview
PoCo describes the succession of contributions that are required to achieve consensus on a given result. Its logic is detailed in two blog articles:
The nominal workflow is also available in the technical report section
Below are the details of the implementations:
1. Deal:
A deal is sealed by the Clerk. This marks the beginning of the execution. An event is created to notify the worker pool’s scheduler.
The consensus timer starts when the deal is signed. The corresponding task must be completed before the end of this countdown. Otherwise, the scheduler gets punished by a loss of stake and reputation, and the user reimbursed.
2. Initialization:
The scheduler calls the initialize
method. Given a deal id and a position in the request order (within the deal window), this function initializes the corresponding task and returns the taskid.bytes32 taskid = keccak256(abi.encodePacked(_dealid, idx));
3. Authorization signature:
The scheduler designates workers that participate in this task. The scheduler’s Ethereum wallet signs a message containing the worker’s Ethereum address, the taskid, and (optionally) the Ethereum address of the worker's enclave. If the worker doesn't use an enclave, this field must be filled with address(0)
.
This Ethereum signature (authorization) is sent to the worker through an off-chain channel implemented by the middleware.
4. Task computation:
Once the authorization is received and verified, the worker computes the requested tasks. Results from this execution are placed in the /iexec_out
folder. The following values are then computed:
bytes32 digest: a digest (sha256) of the result folder.
bytes32 hash: the hash of the digest, used to produce a consensus
bytes32 seal: the salted hash of the digest, used to prove a worker’s knew the digest value before it is published.
resultHash == keccak256(abi.encodePacked( taskid, resultDigest))
resultSeal == keccak256(abi.encodePacked(worker, taskid, resultDigest))
In computer science, a deterministic algorithm is an algorithm which, given a particular input, will always produce the same output.
Both the digest
, the hash
and the seal
are automatically computed based on the output of the application. If the output is not entirely deterministic, then the application can specify a deterministic file that should be used for building consensus. In order to do so, the application just has to provide the path to the deterministic file using a specific entry deterministic-output-path
in ${IEXEC_OUT}/computed.json
.
Alternatively, if the application is used in a doracle context (the results are designed to be processed on-chain by receiver smart-contracts), then the value of this callback must be specified in ${IEXEC_OUT}/computed.json
under the entry callback-data
.
If a TEE was used to produce the result, the post-processing enclave will automatically produce an enclave-signature
entry that contains the enclave signature (of the resultHash and resultSeal). TEE certification of results is transparent to the application developer.
5. Contribution:
Once the execution has been performed, the worker pushes its contribution using the contribute
method. The contribution contains:
bytes32 taskid
bytes32 resultHash
bytes32 resultSeal
address enclaveChallenge
The address of the enclave (specified in the scheduler’s authorization). If no enclave is specified, this parameter should be set to address(0)
.
bytes enclaveSign
The enclave signature. This is required if the enclaveChallenge
is not address(0)
. Otherwise, it should be set to the empty byte string 0x
.
bytes workerpoolSign
The signature computed by the scheduler at step 2.
6. Consensus:
During the contribution, the consensus is updated and verified. Contributions are possible until the consensus is reached, at which point the contributions are closed. We then enter a 2h reveal phase.
7. Reveal:
During the reveal phase, workers that have contributed to the consensus must call the reveal
method with the resultDigest
. This verifies that the resultHash
and resultSeal
they provided are valid. Failure to reveal is equivalent to a bad contribution, and results in a loss of stake and reputation.
8. Finalize:
Once all contributions have been revealed, or at the end of the reveal period if some (but not all) reveals are missing, the scheduler must call the finalize
method. This finalizes the task, rewards good contribution and punishes bad ones. This must be called before the end of the consensus timer.
Staking and Payment
Among the objectives of PoCo, we want to ensure a worker that contributes correctly is rewarded and, at the same time, that a requester won’t be charged unless a consensus is achieved. This is achieved by locking the requester’s funds for the duration of the consensus, and unlocking them depending on the outcome.
Staking is used to prevent bad behavior and encourage good contributions.
Your account, managed by the Escrow
part of the IexecClerk
, separates between balance.stake
(available, can be withdrawn) and balanced.locked
(unavailable, frozen by a running task). The Escrow
exposes the following mechanism:
lock
: Moves value from the balance.stake
to balance.lock
Locks the requester stake for payment
Locks the scheduler stake to protect against failed consensus
Locks the worker stake when making a contribution
unlock
: Moves value from the balance.lock
back to the balance.stake
Unlock the requester stake when consensus fails
Unlock the scheduler stake when consensus is achieved
Unlock the worker stake when they contributed to a successful consensus
seize
: Confiscate value from balance.lock
Seize the requester stake when the consensus is achieved (payment)
Seize the scheduler stake when consensus fails (send to the reward kitty)
Seize the worker stake when a contribution fails (redistributed to the other workers in the task)
reward
: Award value to the balance.stake
Reward the scheduler when consensus is achieved
Reward the worker when they contributed to a successful consensus
Reward the app and dataset owner
The requester payment is composed of 3 parts, one for the worker pool, one for the application and one for the dataset. When a consensus is finalized, the payment is seized from the requester and the application and dataset owners are rewarded accordingly. The worker pool part is put inside the totalReward
. Stake from the losing workers is also added to the totalReward
. The scheduler takes a fixed portion of the totalReward
as defined in the worker pool smart contract (schedulerRewardRatioPolicy
).
The remaining reward is then divided between the successful workers proportionally to the impact their contribution made on the consensus. If there is anything left (division rounding, a few nRLC at most) the scheduler gets it. The scheduler also gets part of the reward kitty.
Parameters
FINAL_DEADLINE_RATIO = 10
, CONTRIBUTION_DEADLINE_RATIO = 7
, REVEAL_DEADLINE_RATIO = 2
Parameters of the consensus timer. They express the number of reference timers (category duration) that are dedicated to each phase. These settings correspond to a 70%-20%-10% distribution between the contribution phase, the reveal phase and the finalize phase.
FINAL_DEADLINE_RATIO
This describes the total duration of the consensus. At the end of this timer the consensus must be finalized. If it is not, the user can make a claim to get a refund.CONTRIBUTION_DEADLINE_RATIO
This describes the duration of the contribution period. The consensus can finalize before that, but no contribution will be allowed after the timer to ensure enough time is left for the reveal and finalize steps.REVEAL_DEADLINE_RATIO
This describes the duration of the reveal period. Whenever a contribution triggers a consensus, a reveal period of this duration is reserved for the workers to reveal their contribution. Note that this period will necessarily start before the end of the contribution phase.
Let's consider a task of category GigaPlus, which reference duration is 1 hour. If the task was submitted at 9:27AM, the contributions must be sent before 4:27PM (16:27). Whenever a contribution triggers a consensus, a 2 hours long reveal period will start. Whatever happens, the consensus has to be achieved by 7:27PM (19:27).
WORKERPOOL_STAKE_RATIO = 30
Percentage of the worker pool price that has to be staked by the scheduler. For example, for a 20 RLC
task, with an additional 1 RLC
for the application and 5 RLC
for the dataset, the worker will have to lock 26 RLC
in total and the scheduler will have to lock (stake) 30% * 20 = 6 RLC
.
This stake is lost and transferred to the reward kitty if the consensus is not finalized by the end of the consensus timer.
KITTY_RATIO = 10
Percentage of the reward kitty for the scheduler per successful execution. If the reward kitty contains 42 RLC when a finalize is called, then the scheduler will get 4.2 extra RLC and the reward kitty will be left with 37.8 RLC.
KITTY_MIN = 1 RLC
Minimum reward on successful execution (up to the reward kitty value).
If the reward kitty contains 42.0 RLC, the reward is 4.2
If the reward kitty contains 5.0 RLC, the reward should be 0.5 but gets raised to 1.0
If the reward kitty contains 0.7 RLC, the reward should be 0.07 but gets raised to 0.7 (the whole kitty)
reward = kitty.percentage(KITTY_RATIO).max(KITTY_MIN).min(kitty)
Example
Let's consider a worker pool with the policies workerStakeRatioPolicy = 35%
and workerStakeRatioPolicy = 5%
.
A requester offers
20 RLC
to run a task. The task is free, but it uses a dataset that costs1 RLC
. The requester locks21 RLC
and the scheduler30% * 20 = 6 RLC
. The trust objective is99%
(trust = 100
)3 workers contribute:
The first one (
score = 12 → power = 3
) contributes17
. It has to lock7 RLC
(35% of the20 RLC
awarded to the worker pool).The second worker (
score = 100 → power = 32
) contributes42
. It also locks7 RLC
.The third worker (
score = 300 → power = 99
) contributes42
. It also locks7 RLC
.
After the third contribution, the value
42
has reached a99.87%
likelihood. Consensus is achieved and the two workers who contributed toward42
have to reveal.After both workers reveal, the scheduler finalizes the task:
The requester locked value of
21 RLC
is seized.The dataset owner gets
1 RLC
for the use of its dataset.Stake from the scheduler is unlocked.
Stakes from workers 2 and 3 are also unlocked.
The first worker stake is seized, and it loses a third of its score. The corresponding
7 RLC
are added to thetotalReward
.We now have
totalReward = 27 RLC
:We save 5% for the scheduler,
workersReward = 95% * 27 = 25.65 RLC
Worker 2 has weight
log2(32) = 5
and worker 3 has a weightlog2(99) = 6
. Total weight is5+6=11
Worker 2 takes
25.65 * 5/11 = 11.659090909 RLC
Worker 3 takes
25.65 * 6/11 = 13.990909090 RLC
Scheduler takes the remaining
1.350000001 RLC
If the reward kitty is not empty, the scheduler also takes a part of it.
Replication & Trust
How to achieve trust ?
The PoCo offers a consensus mechanism that can certify the likelihood of a result to be valid. This consensus relies on the scoring of workers and the replication of a task’s execution to combine the score of the workers that come up with the same result. This consensus is largely based on Sarmenta’s work [Sarmenta2002] with specific tuning of the scoring function [Trust2018].
Contribution credibility
Each worker’s contribution has an associated credibility. This credibility derives from the worker’s history score. As described in [Trust2018], a worker score is a positive integer that is incremented for each valid and verified contribution. In case of bad contribution, a worker loses one third of it’s score. This credibility can be expressed as a likelihood percentage but also as a weight value that can be used to detect consensus without resorting to floating point arithmetics, more details in [Trust2018].
Requiring a trust level
Based on [Sarmenta2002] describing the way to combine worker’s contribution and to evaluate a result’s likelihood, a requester can ask for the level of trust as an input for the PoCo processing, to impose a certain quality of service. The trust level corresponds to a minimum correctness likelihood that a result must achieve to be valid. For example, a trust level of 0 means any contribution would be accepted, regardless of the score of the worker proposing it. On the other hand a trust level of 99.99% means a result will only be accepted if the contribution towards it result shows a correctness probability higher than 99.99%.
The trust level is expressed, on-chain, by an integer trust
such that threshold = 1 - 1 / trust
.
Limitation
This consensus mechanism requires replicable applications. Non-deterministic applications, long-running jobs such as webservers, do not meet this requirement out of the box. When it is possible, the application developer must provide a deterministic result using the deterministic-output-path
entry of the ${IEXEC_OUT}/computed.json
file. Otherwise, the user can still run its application on the iExec platform but would have to disable the PoCo’s consensus layer. Hardware security as TEE is an option to overpass this limitation.
References
Brokering
Overview
We studied many possible evolutions of the brokering process. The first requirement is to include bid orders to complete the already available ask orders. It soon became clear that the evolution had to go beyond a simple interaction. We considered on-chain and off-chain approach and finally went for a solution where both the pairing and the order book are off-chain. It might sound counterintuitive, but it has many advantages.
If the orders and the pairing are handled off-chain, how can we build an on-chain agreement knowing that all parties have agreed? Is there a threat to the platform security?
This option relies on the use of cryptographic signatures for order authentication. We represent orders using structures containing all the required details. The hash of this structure uniquely identifies the order. The structure by itself is worthless as anyone could write and publish it. However, if we add a valid cryptographic signature (of the identifying hash), then the origin of the order can be certified with the same level of security as if it was published on-chain. This role is fulfilled by a smart-contract called the iExecClerk.
An overview of the iExecODB (Open Decentralized Brokering) is available in this blog article:
Orders structure
As discussed earlier, iExec introduces the offchain signature of orders as a new core element of the iExec Open Decentralized Brokering, the iExec Clerk should match these orders. There are 4 types of orders corresponding to the 4 actors involved: the worker pool, the application, the dataset and the requester. Each order types has to follow a specific structure and is signed using EIP-712 structure signature mechanism.
Orders description
AppOrder
app
Address of the smartcontract describing the application. Must be registered in the AppRegistry.appprice
Price of a run of the application.volume
Number of run authorized (by this order).tag
Special requirements for the application (see tag).datasetrestrict
Matching restrictions. Dataset or group of datasets that can be matched. Let null value to disable.workerpoolrestrict
Matching restrictions. Workerpool or group of worker pools that can be matched. Let null value to disable.requesterrestrict
Matching restrictions. Requester or group of requesters that can be matched. Let null value to disable.salt
A random value to ensure order uniqueness.sign
cryptographic signature of the order, the smart contract is securely linked to application owner.
DatasetOrder
dataset
Address of the smartcontract describing the dataset. Must be registered in the DatasetRegistry.datasetprice
Price of a use of the dataset.volume
Number of authorized uses (by this order).tag
Special requirements of the dataset (see tag).apprestrict
Matching restrictions. App or group of apps that can be matched. Let null value to disable.workerpoolrestrict
Matching restrictions. Workerpool or group of workerpools that can be matched. Let null value to disable.requesterrestrict
Matching restrictions. Requester or group of requesters that can be matched. Let null value to disable.salt
A random value to ensure order uniqueness.sign
cryptographic signature of the order, the smart contract is securely linked to dataset owner.
WorkerpoolOrder
workerpool
Address of the smartcontract describing the worker pool. Must be registered in the WorkerpoolRegistry.workerpoolprice
Price of an execution on the worker pool.volume
Number of executions proposed (by this order).tag
Special features proposed by the workerpool (see tag).category
Order category.trust
Trust level used to consolidated results.apprestrict
Matching restrictions. App or group of apps that can be matched. Let null value to disable.datasetrestrict
Matching restrictions. Dataset or group of datasets that can be matched. Let null value to disable.requesterrestrict
Matching restrictions. Requester or group of requesters that can be matched. Let null value to disable.salt
A random value to ensure order uniqueness.sign
cryptographic signature of the order, the smart contract is securely linked to worker pool manager.
RequesterOrder
app
Address of the smartcontract describing the application. Must be registered in the AppRegistry.appmaxprice
Maximum price allowed by the requester for the payment of the application.dataset
Address of the smartcontract describing the dataset. Must be registered in the DatasetRegistry. Null if no dataset is required.datasetmaxprice
Maximum price allowed by the requester for the payment of the dataset (if any).workerpool
Matching restrictions. Worker pool or group of worker pools that are allowed to run tasks from this order. Leave null value to disable check.workerpoolmaxprice
Maximum price allowed by the requester for the payment of the execution (scheduler + workers).requester
Address of the requester (paying for the executions).volume
Number of tasks that are part of this order (size of the Bag Of Task).tag
Special features required by the requester (see tag).category
tasks category required.trust
Minimum trust level required by the requester.beneficiary
Address of the beneficiary of the computation. Used to require output data encryption.callback
Address to callback with the results (following EIP1154 interface). Let empty (null) if no callback is required. Learn more about the callback mechanism.params
Parameters of the application (application specific).salt
A random value to ensure order uniqueness.sign
Cryptographic signature of the order, the smart contract is securely linked to requester.
Tag
The tag specifies the need or the availability of features that go beyond the specifications of the category. The tag is a requirement when it is part of an app order, a dataset order or a requester order. On the other hand, the tag in the workerpool order expresses the availability of the corresponding features.
In V3, tags are 32 bytes (256 bits) long array where each bit corresponds to a feature.
For orders matching, the worker pool order must enable all bits that are enabled in any of the app order, dataset order and requester order. Meaning that if the app order tag is 0x12 = 0x10 | 0x02
, the dataset order is 0x81 = 0x80 | 0x01
and the requester order is 0x03 = 0x02 | 0x01
, then the worker pool order must, at least, have a tag 0x93 = 0x80 | 0x10 | 0x02 | 0x01
.
Matching Conditions
In order to trigger an execution, a deal must be registered by the iExec Clerk. Deals are produced when orders are successfully matched by the clerk. A match requires 3 or 4 orders depending on the requester requirements, the dataset order is optional.
Orders compatibility required:
1. The worker pool’s category and the requester’s category must be equal.
2. The worker pool’s trust must be greater or equal to the requester’s trust.
3. The app’s, dataset’s and worker pool’s prices must be less or equal to the requester’s appmaxprice, datasetmaxprice and workerpoolmaxprice.
4. The worker pool’s tag must enable all the features required by the app’s tag, the dataset’s tag and the worker pool’s tag.
5. If TEE tag is required, then application must be TEE compatible.
6. The app provided by the apporder must match the one required by the requester.
7. The dataset provided by the datasetorder must match the one required by the requester.
8. If the requester specified a worker pool restriction, the worker pool must match this value or be part of the corresponding group.
9. The application must fit the dataset’s and the worker pool’s application restrictions (if any).
10. The dataset must fit the application’s and the worker pool’s restrictions (if any).
11. The worker pool must fit the application’s and the dataset’s restrictions (if any).
12. The requester must fit the application’s, the dataset’s and the worker pool’s restrictions (if any).
13. All resources must be registered in the corresponding registries.
14. All orders must be signed or presigned.
15. The deal produced must contain at least one task.
16. Requester and worker pool must be able to stake.
FAQ : How to write an order ?
[Requester] How do I enable PoCo’s consolidation of results?
A requester can enable the trust layer of the PoCo by setting the trust value in the requestorder. As described here, the trust is defined with the required confidence level. If the requester wants à 99.99% confidence level on the results, it must set the trust
field to 10000
.
[Requester] How do I run a non deterministic application despite requiring determinism?
The PoCo requires an application to be deterministic for the replication layer to provide trust in the result. If an application is not deterministic, consensus cannot be achieved and replication is not necessary.
In order to obtain a result, the requester must prevent replication by asking a trust
value of 0
. To protect your result, the requester can ask to run in an enclave by setting the tag
to 0x1
.
[Requester] How do I protect my result using encryption?
The result of an execution can be valuable to the end user, and the requester might want to protect this result from leaking with encryption.
Anyone can set up an encryption key in an SMS (Secret Management Service) of its choice and set up the SMS address in the directory.
Once a user set an encryption key (see TODO), any computation result can be encrypted with, you have to set up the beneficiary address in the RequesterOrder.
An application can only perform result encryption inside an enclave. No encryption key will be provided by the SMS to an application that doesn't run outside an enclave.
(TODO: potential issue, key leaking to malicious application with the requester attacking a beneficiary)
[Dataset owner] How do I limit the usage of my dataset to a specific application?
iExec’s Data wallet and Data store is a complete solution to monetize valuable datasets preserving privacy. Before uploading a dataset you should encrypt it using the iExec SDK. Through this process, the encryption key becomes the valuable data that has to be protected.
The encryption key must be stored in an SMS and the address of the corresponding SMS recorded in the directory. The SMS stores this encryption key and will only communicate it to an application running in an enclave.
Before a worker runs this application, the worker must first prove that its access is legitimate by providing the scheduler authorization. The SMS will verify that this authorization’s signature is valid and that the corresponding task is registered onchain. This means that any deal signed in the iExec Clerk will grant access to the dataset’s encryption key.
Therefore, in order to restrict the dataset’s usage, the dataset owner should set up restriction before signing a brokering order. This is done through the apprestrict
field of the datasetorder. The dataset owner can deploy a SimpleGroup
smart contract, have the apprestrict
field point to it, and then whitelisting the applications that will have access to the dataset’s encryption key.
[Scheduler] How do I protect myself from non-deterministic applications?
When a scheduler publishes an order, it makes a commitment to achieve consensus on any task that is part of a deal made. While everything is done to ensure an application cannot hurt a worker pool’s machines, not reaching the consensus would cause a loss of stake for the scheduler. The scheduler must therefore take action to prevent this.
Whenever the scheduler proposes to certify a result using the PoCo’s trust layer, it should make sure the application’s developer took the actions required to make it compatible with the PoCo. On the other hand, any application could be executed with the PoCo’s trust layer disabled.
A scheduler could therefore emit two kinds of workerpoolorder:
A workerpoolorder offering execution with the PoCo’s trust layer disabled (
trust = 0
) and accepting all applications (apprestrict = 0
)A workerpoolorder offering secure execution of whitelisted tasks. The application whitelist would use the
GroupInterface
to be verified by the iExec Clerk. This group could either be managed by the scheduler or by a certification authority that would check application's determinism.
Other technical choices
Callback
Some requester might want an onchain callback with the result of the execution. The callback mechanism is based on [EIP-1154]. The result is a bytes
value that is set during the finalize
. The IexecProxy
implements both side of the [EIP-1154].
Pull:
Results are identified by their taskid
and can be pulled through the resultFor
method.
Push:
In order to use the push approach, the requester can use the callback
field to specify the address of a smart contract that implements the receiveResult
method specified in [EIP-1154]. This method will be called during the finalization with a minimum of 200000 nanoRLC gas to proceed [*].
In order to protect the scheduler and the workers, any error raised during this callback will be disregarded and will not prevent the finalization from happening. The same mechanism goes for the callback running out of gas.
Consensus & Reveal duration
When orders match, IexecClerk records the deal which details the parameters of the task. If a consensus on a result is achieved before the countdown, the task is successful. After the countdown, as no consensus is reached, the execution is failed. The duration of the consensus timer is a balance between the quality of service offered to the requester (short timer) and the margin available for the scheduler and the worker to achieve consensus (and go through the reveal process).
The maximum duration of a task is governed by the category the task fits in. While the consensus duration can obviously not be shorter than the task runtime, a significant margin is required for the scheduler to do its job correctly. Multiple workers are likely to contribute and extra time must be planed for the revealing and finalization steps.
The consensus timer starts when the deal is recorded by the IexecClerk.
In case of failure, the requester can claim the refund.
In the v3-alpha version, this timer lasts for 10 category runtime, for all category. The reveal timer starts whenever a consensus is reached and determines the time frame the workers have to reveal their contributions. This should be long enough for worker to have time to reveal while not being too long so that the requester waits too long for its result or the consensus fails because the scheduler cannot finalize in time. In the v3-alpha version, this timer lasts for 2 runtime whatever the category runtime. This leaves a gap of at least 1 time the category runtime for the scheduler to finalize the task.
Reward kitty
When a consensus fails, the requester gets a refund and the scheduler loses its stake. In order to remove an attack vector, the requester does not get any of the seized stake. If this was a feature, anyone could build a flawed application that would not reach consensus to drain money from the scheduler. This would force the scheduler to whitelist all applications thus reducing the usability of the platform. Seized stake from the scheduler goes into a specific account called the reward kitty. No one controls this account, nor can withdraw from it. However, the tokens are not burned. Whenever a task is finalized, the scheduler that organized the execution of this task is rewarded by the requester and also gets a small part of the reward kitty.
As described in the protocol parameters section, this reward is reward = kitty.percentage(KITTY_RATIO).max(KITTY_MIN).min(kitty)
.
References
Last updated