Skip to content

Latest commit

 

History

History
502 lines (323 loc) · 29.1 KB

Governance-Stake-Slashing-Pool-Spec.md

File metadata and controls

502 lines (323 loc) · 29.1 KB

Governance Stake Slashing Oracle Pool Spec

  • Author: Robert Kornacki
  • Released: July 23 2020
  • Topics: Oracles, DeFi, Smart Contracts, dApp, UTXOs, State Machines

Introduction

This is an informal specification which defines an oracle pool implementation which allows entry by predefined/whitelisted trusted oracles. It includes stake slashing as well as governance for changing the pool posting price.

This spec follows Ergo Improvement Proposal 6: Informal Smart Contract Protocol Specification Format.

This implementation of oracle pools splits the state of the pool into two distinct stages:

  1. Epoch Preparation
  2. Live Epoch

The posting schedule (the number of block between new datapoint postings by an oracle pool) is defined as:

[Epoch Preparation Duration] + [Live Epoch Duration]

The duration of each stage can be customized for the given use case at hand to create a posting schedule that makes sense. For example since Ergo has a 2 minute block time, if one wishes to have a new finalized datapoint every hour, you can set the durations to:

Epoch Preparation Duration = 10
Live Epoch Duration = 20

The Epoch Preparation stage offers an in-between period between Live Epochs where the pool has a chance to do "house-keeping". In other words things such as collecting funds into the pool from deposits users have made, stake slashing, and governance.

During the Live Epoch stage oracles post their individual datapoint into their own designated UTXO on-chain. Once the live epoch concludes (based on block height) no oracle can post datapoints to the pool. At this point all of the oracles race to find all of the other oracle's datapoint boxes in order to post a collection transaction. This most often happens immediately in the first block after the Live Epoch ends. However if the blockchain is congested then this can take longer which will eat into the duration of the following Epoch Preparation stage.

The diagram below displays how the epoch logic works for a pool with a 60 block posting schedule (10 block Epoch Preparation, 50 block Live Epoch):

Ergo Oracle Pool Epochs

The datapoint collection transaction itself folds all of the individual datapoints, thereby averaging them out and coming up with a finalized datapoint which is then saved in register R4 of the oracle pool's box. This transaction pays out all of the oracles who submitted good data. Thus at the end of each live epoch after all datapoints have been collected, anyone on the Blockchain has access to a new finalized datapoint from the oracle pool that is ready to be used as a data-input.

In this design all of the oracles are incentivized to be the first to submit the collection transaction to generate the new finalized datapoint. The first to get their collection accepted into a block (hereby dubbed the collector), gets double the reward for the current epoch payout. Thus we have incentives for both oracles submitting their individual datapoints on time as well as finalizing the oracle pool's datapoint every epoch.

Oracles must put up a predefined amount of collateral in order to participate. This collateral can be slashed if an oracle does not perform their job properly by:

  1. Failing to post a datapoint in the latest epoch
  2. Failing to include all other oracle datapoint boxes as a collector

There is an outlier range which all oracles must be within in order to get rewarded for providing data in the given epoch. This current scheme does not pay them out if they are not within this outlier range (rather than slashing which is a bit aggressive).

Governance voting may happen every time an oracle commits a datapoint. They simply include the new oracle payout price they are voting for in register R7 of their datapoint box. If 75% of all oracles vote for the exact same new payout price, then the posting price is officially updated.

Submitting funds to the pool, oracles submitting datapoints, governance votes, and collateral slashing are all parallelized which allows for the pool to move through epochs smoothly.

The diagram below shows off the state machines which comprise a basic oracle pool. This explains at a high level how a simple oracle pool works.

State Machine Diagrams

Do note, this only displays the state transitions (actions), which map onto spending paths, and thus does not include data-input relations. Furthermore, this diagram is for the basic oracle pool design, thus does not include stake slashing or governance. These choices were made to keep things understandable and to prevent the diagram from becoming overcrowded. The current spec is more complicated than the above diagram, however it is merely an extension on top of the basic design. Thus this diagram is still key in understanding the current informal spec.

Stage ToC

  1. Live Epoch
  2. Epoch Preparation
  3. Datapoint
  4. Pool Deposit

Action ToC

  1. Bootstrap Oracle Pool
  2. Bootstrap Oracle
  3. Commit Datapoint
  4. Collect Datapoints
  5. Slash Uncommitted
  6. Slash Collector
  7. Fund Oracle Pool
  8. Collect Funds
  9. Start Next Epoch
  10. Create New Epoch

Stage: Live Epoch

This stage signifies that the oracle pool is currently in an active/live epoch.

While the oracle pool box is in this stage oracles are allowed to post new datapoints via Commit Datapoint. At the end of the epoch all of said datapoints can be collected via Collect Datapoints.

The oracle pool box at this stage must also hold the pool's NFT/singleton token. This NFT is required in order to guarantee the identity of the pool thereby differentiating it from another instance of the same contract posted by an unknown bad actor. Read more about the NFT here.

Registers

  • R4: The latest finalized datapoint (from the previous epoch)
  • R5: Block height that the current epoch will finish on
  • R6: Blake2b256 Hash of the "Epoch Preparation" stage contract(ErgoTree).
  • R7: Oracle payout price.

Hard-coded Values

  • Live epoch duration
  • Epoch preparation duration
  • Outlier range(%) that oracles are allowed to deviate by.
  • Minimum collateral an oracle is required to put up
  • The oracle pool NFT/singleton token id

Actions/Spending Paths


Stage: Epoch Preparation

This is the alternative stage that the oracle pool box can be in which occurs after Collect Datapoints has been called during the previous Live Epoch. This stage provides an intermediate period between epochs where many of the "house-keeping" duties of the oracle pool can take place.

Progression into the proceeding epoch from this stage (and thus into the Live Epoch stage once again) is possible via the Start Next Epoch action.

During this epoch preparation period collateral slashing can be initiated and collecting Pool Deposit boxes can be done. This provides a period of establishing equilibrium after every datapoint collection for both the individual oracles who may have been wronged, and for the funds of the oracle pool to be collected in order to allow for the protocol to continue.

If the oracle pool has insufficient funds and no Pool Deposit boxes are available to collect, the pool may skip an epoch due to it being underfunded. If an epoch is skipped then a new epoch (following a new posting schedule) must be created via Create New Epoch. This is only possible once the pool box has had its funds replenished and it can pay out oracle once more.

The oracle pool box at this stage must also hold the pool's NFT/singleton token. This NFT is required in order to guarantee the identity of the pool thereby differentiating it from another instance of the same contract posted by an unknown bad actor. Read more about the NFT here.

Registers

  • R4: The latest finalized datapoint (from the previous epoch)
  • R5: Block height the upcoming Live Epoch will finish on
  • R6: The address of the latest collector (oracle who submitted the previous Collect Datapoints action)
  • R7: A list of all of the oracles who had their datapoints collected in the last epoch
  • R8: The box id of the previous Live Epoch box
  • R9: Oracle payout price.

Hard-coded Values

  • Live epoch duration
  • Epoch preparation duration
  • Minimum collateral an oracle is required to put up
  • The oracle pool NFT/singleton token id

Actions/Spending Paths


Stage: Datapoint

Each oracle owns a box which is always in the Datapoint stage. This box holds the latest datapoint that the oracle has posted.

This box also holds information about which epoch the datapoint was posted, so that the protocol can keep track of whether or not the oracle has posted their datapoint in the latest epoch.

Registers

  • R4: The address of the oracle (never allowed to change after bootstrap).
  • R5: The box id of the latest Live Epoch box.
  • R6: The oracle's datapoint.
  • R7: The oracle's vote for a new payout price. (Can be empty if not voting to change price)

Actions/Spending Paths


Stage: Pool Deposit

Anyone on the Ergo Blockchain can fund the given oracle pool by creating a box with Ergs in it at this stage/contract address. This contract checks to make sure that it can only be collected into the Oracle Pool box based off of the hard-coded ID of the singleton token and the address of the Oracle Pool contract.

Hard-coded Values

  • ID of the NFT/singleton token of the oracle pool
  • Address of the Oracle Pool contract

Actions/Spending Paths


Action: Bootstrap Oracle Pool


In order to create a new oracle pool, a NFT/singleton token must be created prior and will be used in bootstrapping. This singleton token will be locked initially inside the bootstrapped Epoch Preparation box. As the pool progresses forward in and out of the Live Epoch stage, the NFT/singleton token must always be within the box and cannot be spent elsewhere. This token thereby marks the given oracle pool's current box (in whichever of the two stages it is currently in) and makes it unique/unforgable.

Before creation, the oracle pool must decide on the:

  • Epoch length (Hardcoded into contract)
  • The addresses of the oracles (Hardcoded)
  • Post price (Hardcoded)
  • Oracle collateral minimum (Hardcoded)
  • The block height that the first epoch ends (Stored in R5 of bootstrapped Epoch Preparation)

Inputs

  1. A box with an NFT/singleton token which will be used to identify the pool.
  2. One or more boxes with Ergs to be used for initial oracle pool payments.

Outputs

Output #1

An Epoch Preparation box with:

  • The Input NFT
  • The Input Ergs
  • R4: A default/placeholder datapoint value (has no effect in bootstrap)
  • R5: The block height that the first epoch ends
  • R6: A default placeholder address (has no effect in bootstrap)
  • R7: An empty list (has no effect in bootstrap)
  • R8: A default/placeholder block id (has no effect in bootstrap)
  • R9: The payout price (in Ergs) that each oracle will receive on successful posting.

Action: Bootstrap Oracle

Each oracle who is to be part of the oracle pool must be bootstrapped in. This current protocol uses a trusted bootstrap funnel to do so (meaning a single trusted actor bootstraps all participants into the protocol).

Prior to bootstrapping oracles, a new "oracle pool participant" token must be created which has a total quantity equal to the number of oracles who will be part of the oracle pool. Each oracle's box that will be bootstrapped will hold one of said participant token in order to make it unique and also easy to find.

Thus the bootstrapper creates boxes which are each owned by a predefined single oracle who will use their given box to commit datapoints to the oracle pool. This box will always hold the oracle pool token, hold the given oracle's address in R4, and always be in the Datapoint stage.

Once bootstrapped, the oracle must wait until the block height gets close enough to begin the first epoch via Start Next Epoch. Once the epoch begins, they must submit sufficient collateral during their first Commit Datapoint in order to officially enter as an active participant.

Inputs

  1. A box with a single oracle pool participant token.

Outputs

Output #1

A box in Datapoint stage which:

  • Holds a single oracle pool token.
  • R4: The address of the oracle who will be a participant in the pool
  • R5: Placeholder box id value.
  • R6: Placeholder datapoint value.
  • R7: Placeholder/no vote value.

Action: Commit Datapoint

During an active epoch when the oracle pool box is in the Live Epoch stage, any oracle can commit a new datapoint for said epoch.

In order to commit a datapoint, the output Datapoint box must hold at least minimum collateral Ergs that the pool requires. As such if an oracle's collateral was slashed previously, this means that they must include extra inputs that hold Ergs when performing this action thereby recollateralizing their box.

If an oracle never had their collateral slashed, then their previously held collateral is sufficient and therefore they do not need to provide extra input boxes holding Ergs.

When a new datapoint is commit, the Live Epoch must be used as a data-input in order to acquire its box id. This box id is then put in R5 of the new Datapoint output, thereby ensuring that the datapoint was posted in the current epoch.

An oracle can also include a "vote" for a new oracle pool price by placing an integer in R7.

Data-Inputs

  1. Live Epoch

Inputs

  1. The oracle's Datapoint box.
  2. One or more boxes with Ergs (Only needed if collateral was slashed previously)

Outputs

  1. Datapoint.

Action Conditions

  1. The output's R5 is equal to the data-input's box id.
  2. R4 of the Datapoint input box is equal to R4 of output box.
  3. R6 of the output box is not empty/holds a value of the correct type (ex. Int)
  4. Ergs total in the output box is greater than the pool's required minimum collateral threshold.
  5. Output still holds the oracle pool token.
  6. Only the owner (address in Datapoint R4) can spend the box.

Action: Collect Datapoints

Allows an oracle to use all of the individual oracle Datapoint boxes (from the current epoch) as data-inputs and fold the datapoints together into the finalized oracle pool datapoint and thereby finishing the current epoch.

This action can only be initiated if the current height is greater than the block height in R5 of the existing Live Epoch box (which represents the end height of the epoch). Due to all oracles being incentivized to collect via double payout, it is expected that at least one oracle will post the collection tx at the exact height of the new epoch, thereby generating the new Epoch Preparation box.

An oracle is rewarded for the epoch if they posted a datapoint that is within the outlier range compared to the previous finalized datapoint (which is a % hardcoded in the Live Epoch contract) of the finalized datapoint.

Only datapoints commit during the latest epoch (checked by comparing R5 of data-inputs with the input Live Epoch box) and which are within the outlier range are allowed to be collected.

Lastly if 75% of oracles submit a "vote" to change the oracle pool posting price (by placing an integer value in R7 of their Datapoint box) then the pool posting price in R9 of the Epoch Preparation output box will be updated.

Finalize Datapoint Function

This is the function which produces the finalized datapoint by folding down the input oracle datapoints produced during the epoch. The simplest function we can use is an average.

[Summed Total Of Oracle Input Datapoints] / [Number Of Oracle Input Datapoints]

Data-Inputs

  1. Every Datapoint box which has a datapoint that is within the outlier range.

Inputs

  1. The Live Epoch box.

Outputs

Output #1

The Epoch Preparation box with the new datapoint

Output #2+

Payment boxes which are holding Ergs that are sent to each oracle who successfully provided a datapoint within the outlier range, plus an extra payment box to the collector (meaning the collector can get 1 or 2 payment boxes depending if they provide accurate data).

The amount of Ergs inside each payment box is equal to [Oracle Payout Price] which is held in R7 of the Live Epoch box.

Action Conditions

  1. The transaction must be signed by the address held in R4 of Data-Input #1. (This only allows the oracles to issue this action.)
  2. Output #1 has the oracle pool NFT.
  3. Output #1 has Ergs equivalent to: [Input #1 Ergs] - [Pool Payout]
  4. Output #1 R4 is the result of the Finalize Datapoint Function
  5. Output #1 R5 is equal to: [Input #1 R5] + [Epoch Prep Length] + [Live Epoch Length]
  6. Output #1 R6 holds the address of the collector (who earns the extra payout).
  7. Output #1 R7 is a list comprised of the addresses of all of the successful oracles who provided a datapoint within the hardcoded outlier range (compared to finalized datapoint in R4 of Input #1). The addresses are acquired from the data-input Datapoint box's R4.
  8. Output #1 R8 is the box id of Input #1.
  9. A payment box output is generated for every single oracle who's address is in the list in Output #1 R7.
  10. A (potentially second) payment box output is generated for the collector who's address is in R6 of Output #1.
  11. Each payment box has a total amount of Ergs inside equal to the [Oracle Payout Price] held in R7 of the Input.
  12. Each data-input Datapoint box has an R5 that is equal to Input #1 box id.
  13. If 75%+ of all oracle Datapoint boxes have the same value in R7, then said value is placed in R9 of Output #1. Else the R7 from Input #1 is used.
  14. At least 1 valid data-input box is provided.
  15. The blake2b256 hash of Output #1's address is equal to the hash held in R6 of Input #1.
  16. Every data-input Datapoint box has a datapoint within the outlier range.

Action: Slash Uncommitted

An oracle's collateral can be slashed during Epoch Preparation if they did not commit a datapoint before the previous epoch had ended.

This is done by checking if R8 of the Epoch Preparation data-input does not match the R5 of the Datapoint input box, thereby proving that the oracle's latest datapoint was posted in an old epoch (meaning the oracle missed the latest epoch).

The collateral Ergs are moved into an Pool Deposit box so that the collateral can be collected later on and used to fund future oracle pool postings.

(Later on may be good idea to scale this to allow multiple Datapoint inputs if multiple oracles all missed posting and need their collateral to be slashed)

Data-Inputs

  1. Epoch Preparation

Inputs

  1. Datapoint

Outputs

  1. Datapoint
  2. Pool Deposit

Action Conditions

  1. R8 of the data-input is not equal to R5 of the input box. (Ensure that input is from an old epoch, meaning an epoch was missed)
  2. R4 of the input box is equal to R4 of output Datapoint box.
  3. Datapoint output box still holds the oracle pool token.
  4. Datapoint output box has no collateral Ergs in the box (or 1 nanoErg if needed by protocol)
  5. The Pool Deposit output holds a total number of Ergs equivalent to the collateral Ergs of the input box.

Action: Slash Collector

A collector's collateral can be slashed after an epoch has ended during Epoch Preparation if they missed collecting (on purpose or otherwise) any oracle's Datapoint boxes which were posted in the previous epoch.

This works by using the Epoch Preparation box as a data-input and inspecting R8 (the previous box id) and seeing if it matches the box id stored in the oracle's Datapoint box in R5. If yes, this means that the datapoint was commit in the previous epoch.

Furthermore if the address held in R4 of the oracle's Datapoint box is not in the address list in R7 of the Epoch Preparation box, this means that the collector did not collect said Datapoint box.

Thus if both of the above checks, that the datapoint was commit in the previous epoch and that it was missed by the collector, are true then the collector has his collateral slashed.

The slashed collateral is moved into a Pool Deposit box to be collected back into the oracle pool in the future.

Data-Inputs

  1. Epoch Preparation.
  2. The oracle's missed Datapoint box.

Inputs

  1. The collector's Datapoint box which holds their collateral.

Outputs

  1. The collector's Datapoint box with no collateral inside.
  2. Pool Deposit with the collateral Ergs inside.

Action Conditions

  1. Output #1 still has the same R4, and oracle pool token as the input.
  2. Output #1 now has no collateral Ergs inside (or 1 nanoErg)
  3. Output #2 has an amount of Ergs equal to the collateral amount of Ergs in input #1.
  4. Epoch Preparation data-input's R8 equals R5 of the Datapoint data-input (meaning datapoint was posted in previous epoch).
  5. Epoch Preparation data-input's R7 (list of addresses of collected oracles) does not include the address in R4 of the Datapoint data-input (meaning collector missed this box).
  6. The address in R4 of the input is equal to R6 of the Epoch Preparation box (which proves said oracle was the latest collector) --

Action: Fund Oracle Pool

A user can fund a given oracle pool by locking Ergs in the Pool Deposit stage. This is technically also a bootstrap action, however the user's participation in the multi-stage protocol ends immediately as there are no more actions which they can perform.

Inputs

  1. One or more boxes with Ergs.

Outputs

  1. A box in the Pool Deposit stage with Ergs inside.

Action: Collect Funds

One of the oracles can collect any/all boxes on the blockchain that are in the Pool Deposit stage. Their Ergs are deposited into the Epoch Preparation box thereby funding the oracle pool.

If a pool is ever underfunded, then this action must be performed to increase the funds above the threshold of one oracle pool payment before the next/a new epoch can begin.

Inputs

  1. The Epoch Preparation box.
  2. One or more Pool Deposit boxes.

Outputs

  1. The Epoch Preparation box with everything the same but with an increased number of Ergs held.

Action Conditions

  1. Input #1 holds the oracle pool NFT (the NFT id is hardcoded in the Pool Deposit contract)
  2. Output #1 holds the oracle pool NFT.
  3. Output #1 has exactly the same registers as Input #1.
  4. Output #1 holds equivilant to its previous total plus the summed value of all input Pool Deposit boxes.

Action: Start Next Epoch

After the previous epoch has ended via Collect Datapoints the oracle pool box must stay in the Epoch Preparation stage until the blockchain height has passed the following:

[Finish Block Height Of Upcoming Epoch (R5)] - [Live Epoch Duration]

This provides a preparation period where Start Next Epoch cannot be used. Thus the next Live Epoch cannot officially start and datapoints cannot be updated. The oracles can use this period to collect funds into the pool and additionally slash collateral from bad acting oracles from the previous epoch.

Starting the next epoch requires the Epoch Preparation box to have sufficient Ergs in order to payout the oracles at the end of the epoch. Thus even if the block height passes the epoch preparation buffer period, if the box has no funds then the live epoch cannot begin. Once sufficient funds are collected via Collect Funds action, then the live epoch can start.

If the finish block height of an epoch has passed without the live epoch being started in time (due to lack of funds), the oracle pool must instead use Create New Epoch to continue the protocol.

Inputs

  1. The Epoch Preparation box.

Outputs

  1. The Live Epoch box in a new epoch.

Action Conditions

  1. The current block height is greater than [Finish Block Height Of Upcoming Epoch (R5)] - [Live Epoch Duration].
  2. The input box has more Ergs than the cost for one oracle pool posting payout.
  3. R4 of both the input and output are equivalent.
  4. R5 of both the input and output are equivalent.
  5. The oracle pool NFT and all held Ergs from the input are in the output.
  6. Output R7 is equal to input R9. (Preserving the posting price.)
  7. R6 of the output must hold the address/ErgoTree of the input.

Action: Create New Epoch

If the oracle pool is in the Epoch Preparation stage and is underfunded, it can miss starting its next Live Epoch (because Start Next Epoch requires sufficient funds).

Therefore, this action allows creating a brand new upcoming epoch after funds have been collected and a previous epoch has been missed. This is done by checking R5 of the Epoch Preparation box and seeing if the block height has passed. If so, it means that none of the oracles started said epoch (which they have a game theoretic incentive to do so because they get paid) due to the pool not having sufficient funds to payout the oracles for the next epoch.

When a new epoch is created, the resulting R5 (the finish height) of the new Live Epoch box, must be between:

Minimum:
[Current Block Height] + [Live Epoch Length] + [Epoch Preparation Length]

Maximum:
[Current Block Height] + [Live Epoch Length] + [Epoch Preparation Length] + [Buffer]

This buffer allows a bit of leeway for the tx to be accepted on-chain while also setting an upper limit for when the next Live Epoch begins. A reasonable value for this buffer can be 4 blocks for example.

Here in Create New Epoch we set the next Live Epoch's finish block height based off of when the action is submitted on-chain. Start Next Epoch merely increments the previous epoch's finish height, thereby keeping to the old posting schedule. This is the biggest difference between the two actions.

Inputs

  1. The Epoch Preparation box.

Outputs

  1. The Live Epoch box in a new epoch.

Action Conditions

  1. The current block height is greater than R5 of the input.
  2. The input box has more Ergs than the cost for one oracle pool posting payout.
  3. The oracle pool NFT and all held Ergs from the input are in the output.
  4. R4 of both the input and output are equivalent.
  5. R5 of the output must be within the valid range described within the preamble.
  6. Output R7 is equal to input R9. (Preserving the posting price.)
  7. R6 of the output must hold the address/ErgoTree of the input.