-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Service Agreements and the Coordinator Contract
- Oracle Nodes register themselves by putting down a LINK deposit in the Deposit Contract.
- Oracle Node calls Deposit on the Oracle Contract with:
- LINK amount
- Oracle Node calls Deposit on the Oracle Contract with:
-
CreateServiceAgreement(proposed SA) - The Requester sends a proposed SA over to each of the Oracle Nodes it has selected to provide data to it.
- Requester calls CreateServiceAgreement on each Oracle Node’s API:
- Full ServiceAgreement (see below)
- A signature covering the SAID (hash of the SA parameters)
- Requester calls CreateServiceAgreement on each Oracle Node’s API:
-
Each selected Oracle accepts the SA proposal. The oracle signs the hash of the encoded SA, aka the SAID, and returns their signature to the Requester. If the Oracle cannot perform the SA, or chooses not to (e.g. insufficient payment), then they send a NACK and the Requester starts again with a new set of Oracle Nodes.
- Oracle Nodes respond to the Requester with:
- A signature covering the SAID
- Oracle Nodes respond to the Requester with:
-
The Requester submits the SA with all of the Oracles’ signatures to the Oracle Contract. If all of the SA parameters are valid(see Encumbrance section for validity of each parameter) and the Oracle signatures are valid signatures covering the SAID and are ordered in the same order as the list of oracles, then the SA is recorded and each Oracle’s withdrawable deposit is decremented by the amount specified in the SA.
- Requester calls the Oracle Contract’s InititiateServiceAgreement with:
- ABI encoded SA
- List of Oracle Node signatures, ordered as they are listed in the SA.
- Request Handler decrements each Oracle’s withdrawable deposit according to the SA.
- Requester calls the Oracle Contract’s InititiateServiceAgreement with:
-
The Requester directs the Oracle nodes to create a Run of the Job Specification. (a) This request could be sent directly to the Oracle Contract, or be routed through the Consuming Contract. We believe going through the Consuming Contract will be more common, so that is how it’s represented here.
- Requester calls ExecuteServiceAgreement on the Consuming Contract with:
- SAID
- This Run Request’s parameters
- Consumer Contract calls RequestRun on the Oracle Contract with:
- SAID
- This Run Request’s parameters
- Callback address
- Callback function selector
- Oracle Contract issues a Run Log(a specific Solidity Event Log) containing:
- Request ID
- SAID
- This Run Request’s parameters
- Requester calls ExecuteServiceAgreement on the Consuming Contract with:
-
The Oracle Nodes all report their answers and aggregate them together. In the happy path, all Oracle Nodes report before the expiration, and the last answer pays the gas fee of aggregating and closing out the deposits. If not all Oracle Nodes report answers by the Request Expiration, then any account can ping the Oracle Contract to close the request. Deposits are returned to the cooperative/responsive Oracle Nodes. Deposits of uncooperative Oracle Nodes are split between the requester and the account who sent the closing transaction(TBD: how to split the deposit). The Oracle Contract sends the final reported answer to the Consuming Contract.
- Each Oracle Node calls Fulfill on the Oracle Contract with:
- Request ID
- Their reported response
- The Oracle passes all of the responses on to its specified aggregation function. The Answer Aggregator determines all of the cooperative and uncooperative nodes.
- If not enough nodes have responded, the cooperative nodes receive their payment and the uncooperative nodes lose part of their deposits along with reputation penalties.
- Answer aggregator reports cooperative/uncooperative nodes to the deposit handler for financial penalties.
- Answer Aggregator report cooperative/uncooperative nodes for reputation penalties.
- Answer Aggregator reports the final Answer to the consuming contract.
- Each Oracle Node calls Fulfill on the Oracle Contract with:
Specifies all of the details requested work. This is broken into two pieces, the off-chain Job Spec and the on-chain Encumbrance. The SA is the subset of the Job Spec’s parameters that can be enforced on-chain; e.g. payment amount, oracle deposit amount, oracle list, etc.
In order to be referred to reliably, the SA can be sent in with any JSON but it is always normalized by the Oracle Node in order to create a deterministic JSON string to work with. Using a deterministic function allows the Oracle Nodes to agree to work with each other without communicating directly. Normalization handles JSON key ordering, character encoding, float representation, etc.
In order for the SA to be enforced, and for all of the Oracle Nodes to have the guarantee that they are operating on the same data the Oracles commit to the SA by ABI encoding it and signing a hash of the ABI encoding. The hash of the ABI encoded SA is known as the SAID.
The Job Spec is a series of steps for each Oracle Node to perform and report the result of; e.g. httpGet, jsonParse, ethTx. The Job Spec is a template for specific invocations of work, aka Job Runs, which can have unique parameters; e.g. URL, jsonPath, format.
The Encumbrance is all of the parts of the agreement that can be enforced on-chain.
Current list of Encumbrance parameters:
- Request Payment Amount: amount paid per request to each oracle
- Oracle Staked Amount: amount expected per Oracle to be submitted
- Minimum responses: number of nodes that must respond in order for the answer to return to the consuming contract
- Submitted By: deadline for the SA to be confirmed on-chain
- Request Expiration Time: amount of time an Oracle Node has to respond after a Run Request is submitted
- Aggregating Function: specifies the type of data expected back
- End At: the deadline to submit a Run Request
- Oracle Addresses: list of all Oracle Nodes participating in Job Spec
- Requester: verified by signing the SAID, checked by the oracles before signing and on-chain. This field prevents unintentional collisions between SAIDs.
- Hash of Service Agreement Body: Hash of normalized Service Agreement JSON. Provides the Oracle Nodes a guarantee that they are all operating on the same data.
hash(abi.encode(encumbranceParams... requester, hash(normalized(JobSpecJSON))))
The full encoding is submitted to the Oracle Contract. The Oracle Contract checks the validity of all of the parameters(see the Encumbrance definition for validity definitions for each parameter), then hashes them to get the Spec ID. It then checks all of the signatures of the Oracle Nodes to make sure that all of the oracles listed in the SA have actually agreed to the SA.
The Deposit Contract has a corresponding managing contract, in our case it’s the Oracle Contract. The Deposit Contract keeps track of the Withdrawable Balance for any account. Any account can call Deposit()
with LINK, and that amount of LINK is credited to the account’s withdrawable balance. When that account calls Withdraw(n)
they can withdraw any amount up to their Withdrawable Balance. The managing contract can increment and decrement the withdrawable balance.
In the simplest case, when a request is accepted by an oracle its withdrawable balance is decremented. When the request is fulfilled it is incremented. Further rewards and punishments can be added on in the future.
For simplicity’s sake, the diagram above shows a locking up of deposits per request. Additionally, to incentivize Oracles to stick around for the entire lifetime of a Job Spec, we will likely add an additional deposit locking round for the opening and closing of a Job Spec.
From the white paper:
Amount of penalty payments: If penalty payments were locked in to assure a node operator’s performance, the result would be a financial metric of an oracle provider’s commitment not to engage in an “exit scam” attack, where the provider takes users’ money and doesn’t provide services. This metric would involve both a temporal and a financial dimension.
Basic functions:
- Deposit: The node deposits an amount of LINK to the Coordinator contract
- Lock: When a service agreement which requires a penalty fee is accepted, that amount is locked, which makes it unavailable for withdraw
- Unlock: When the service agreement that required a penalty fee ends, that amount is unlocked, which makes it available for withdraw
- Withdraw: The node withdraws any unlocked amount of LINK from the oracle contract
Depositing LINK requires a small amount of gas to pay for the transaction. Your Ethereum address on the node will need to be funded for the deposit transaction to succeed.
You may withdraw your LINK which is not currently locked in a service agreement as a penalty fee at any time. Once again, your Ethereum address on the node will pay for the transaction fee.
Additional actions used in the scenarios:
- Require: The requester may require a specified amount of LINK be locked as a penalty fee for a request
- Ends: The service agreement ends when it has expired, unlocking the LINK on deposit that was previously required
- NodeA deposits 100 LINK to the Coordinator contract
NodeA -(Deposit 100 LINK)> Coordinator
Balances: 100 Total, 0 Penalty, 100 Withdrawable - SA1 created that requires each node to lock 10 LINK as a penalty fee
SA1 -(Require 10 LINK)> Coordinator
- NodeA accepts
NodeA -(Lock 10 LINK)> Coordinator
Balances: 100 Total, 10 Penalty, 90 Withdrawable - SA1 ends
Coordinator -(Unlock 10 LINK)> NodeA
Balances: 100 Total, 0 Penalty, 100 Withdrawable
- NodeA never deposits LINK to the Coordinator contract
- SA1 created that requires each node to lock 10 LINK as a penalty fee
SA1 -(Require 10 LINK)> Coordinator
- NodeA is unable to accept
- NodeA deposits 100 LINK to the Coordinator contract
NodeA -(Deposit 100 LINK)> Coordinator
Balances: 100 Total, 0 Penalty, 100 Withdrawable - SA1 created that requires each node to lock 110 LINK as a penalty fee
SA1 -(Require 110 LINK)> Coordinator
- NodeA is unable to accept
- NodeA deposits 100 LINK to the Coordinator contract
NodeA -(Deposit 100 LINK)> Coordinator
Balances: 100 Total, 0 Penalty, 100 Withdrawable - SA1 created that requires each node to lock 100 LINK as a penalty fee
SA1 -(Require 100 LINK)> Coordinator
- NodeA accepts
NodeA -(Lock 100 LINK)> Coordinator
Balances: 100 Total, 100 Penalty, 0 Withdrawable - SA2 created that requires each node to lock 10 LINK as a penalty fee
SA2 -(Require 10 LINK)> Coordinator
- NodeA is unable to accept SA2
- SA1 ends
Coordinator -(Unlock 100 LINK)> NodeA
Balances: 100 Total, 0 Penalty, 100 Withdrawable
- NodeA deposits 100 LINK to the Coordinator contract
NodeA -(Deposit 100 LINK)> Coordinator
Balances: 100 Total, 0 Penalty, 100 Withdrawable - SA1 created that requires each node to lock 50 LINK as a penalty fee
SA1 -(Require 50 LINK)> Coordinator
- NodeA accepts SA1
NodeA -(Lock 50 LINK)> Coordinator
Balances: 100 Total, 50 Penalty, 50 Withdrawable - SA2 created that requires each node to lock 40 LINK as a penalty fee
SA2 -(Require 40 LINK)> Coordinator
- NodeA accepts SA2
NodeA -(Lock 40 LINK)> Coordinator
Balances: 100 Total, 90 Penalty, 10 Withdrawable - SA1 ends
Coordinator -(Unlock 50 LINK)> NodeA
Balances: 100 Total, 40 Penalty, 50 Withdrawable - SA2 ends
Coordinator -(Unlock 40 LINK)> NodeA
Balances: 100 Total, 0 Penalty, 100 Withdrawable