Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fault-tolerant rewards distribution #80

Open
dzhelezov opened this issue Aug 25, 2023 · 0 comments
Open

Fault-tolerant rewards distribution #80

dzhelezov opened this issue Aug 25, 2023 · 0 comments

Comments

@dzhelezov
Copy link
Contributor

dzhelezov commented Aug 25, 2023

Scope

The reward distributor:

  • reads the aggregated logs from IPFS (uploaded and aggregated by the logs collector)
  • calculates the rewards due for each epoch
  • submits the claim to the rewards smart contract

Objectives

The objective is to make the reward distribution resilient in the following sense:

  • fault tolerant: up to 2 out of 5 instances may go down)
  • secure: up to 2 keys can be hacked/compromised)
  • capped damage: in case of an emergency (e.g. all keys have been compromised), the maximal loss is capped

Design

5 independent instances of the rewards distributor are deployed. Each instance has access to its private key stored locally.

Each collector instance calculates the reward commitment for each epoch and each epoch is treated independently. After the last epoch block, the chain is split into time slots of 256 blocks (the length of the slot is dictated by blockhash() buitl-in which gives access to the last 256 block hashes).

Commitment

The reward commitment is done with

commit(uint epochNumber, address[] memory recipients, uint256[] memory amounts)

The timeslot starting height is calculated as slotStart := block.number / 256 * 256 and the contract checks the eligibility by checking calculating the index distributorIndex := blockhash(slotStart) % TOTAL_RD and checking that

distributors[distributorIndex] == msg.sender

Next, the contract checks that

epochNumber == approvedEpoch + 1

where approvedEpoch is the last epoch for which the rewards were approved.

The submission process looks as follows:

  • Each RD keeps track of epochs for which the rewards have not been distributed yet
  • Each RD listens to new blocks
  • If it's eligible for the current block slot, sequentially submit claims for all epochs (note that there could be a few if there is a backlog)
  • Otherwise, check the current pending claims and submit approvals if the commitments match

Approval

The method is similar to commit

approve(uint epochNumber, address[] memory recipients, uint256[] memory amounts)

The smart contract keeps track of the number of approvals for each commitment and epoch, and if it exceeds the threshold (2 approvals) it triggers the distribution and the approvedEpoch is bumped by one. Passing the addresses and amounts to the arguments is intentional, it prevents from keeping unapproved rewards in the contract memory.

Freeze

The contract assumes a special ADMIN role that can pause the contract. Once freezed, no commitment or approvals are accepted.

Limits per Epoch

The admin account can also set the max limit to rewards distributed per Epoch. If the total committed rewards exceed the cap, the commitment is rejected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant