How to… write decentralized oracles in Ride

Inal Kardanov
Waves Protocol
Published in
8 min readMay 13, 2020

--

Decentralized oracles written in the Ride programming language help resolve the issue of delivering real-world data to the blockchain.

To introduce you to the Ride programming language, we will explore several smart contract examples. Let’s start with a relatively simple smart contract for decentralized oracles, called Oraculus.

Blockchain and real-world data

A blockchain is designed to work extremely efficiently with its own internal data but is unaware of external data such as real world exchange rates, or other data not on the blockchain. A blockchain cannot retrieve data from an external API, due to its internal processes. If nodes address an external API at different times, they may get different results and will never be able to reach consensus, since it is unclear which of these nodes should be trusted.

To resolve this issue, instead of the “pull” model (in which a blockchain reaches out to the real world for data) we can use the “push” model. Data suppliers write data to the blockchain for decentralized apps (dApps) to use.

Entities that save data to the blockchain are called blockchain oracles. But the problem is that all of them operate in a centralized way since they need to trust a single source of data. Overall, a system is only as decentralized as its most centralized element. Therefore, a decentralized app that uses data from a single oracle to make key decisions is in fact, highly centralized. Logically, by influencing the behavior of just one component, the oracle, you can influence the behavior of the entire app.

The idea of a decentralized oracle is an attractive solution, but there is no easy way to implement it.

Here are some solutions on how this issue could be resolved.

Simplest solution: multi-signature

The easiest solution is a multi-signature approach. Several users reach consensus and sign the same data. For instance, let’s say we want to receive data about the USD/EUR exchange rate and we have five oracles that need to agree on a consensus value (from outside the blockchain), sign the relevant transaction and send it to a blockchain account that will only accept it if it includes at least three signatures out of five. The simplest smart contract would look like this:

But this approach has several issues:

  • If the oracles fail to reach consensus, the data simply won’t be sent to the blockchain
  • Data suppliers have no financial motivation
  • The oracle list is predetermined and limited.

To resolve these issues, we need to build a fully-fledged decentralized app, which operates as a marketplace for two kinds of participants:

  1. Apps that need data
  2. Oracles that can supply data for a reward.

Let’s formulate the main requirements for such a dApp and implement it using Ride and the Waves blockchain.

A decentralized oracle as a dApp

The main principles for decentralized oracle operation will be as follows:

  • A dApp’s owner shall be able to request data in a specific format and offer oracles a specific reward
  • Anyone should be able to register their oracle and reply to requests, collecting rewards in return
  • All operations by oracles should be auditable.

Data request

Any blockchain account can send a query to oracles. A reward (in WAVES tokens) for the supply of correct data has to be attached to every query. A query has to include the following:

  • id is a unique identifier for each query, generated by the sender. Requirements: the dApp doesn’t have the same key, 64 characters maximum
  • question is a query in a specific format for each data type. At the top, the data type is stated, followed by // and JSON metadata. For instance, for the Temperature data type, the query will look: Temperature//{“lat”: “55.7558”, “lon”: “37.6173”, “timestamp”: 150000000000, “responseFormat”: “NN.NN”}
  • consensusType is a data aggregation rule. For line data, only full consensus (complete agreement of replies) is accepted, while for numerical data, median and average are also possible
  • minOraclesCount is the minimum number of oracles that have to provide data for a consensus result. Its value cannot be smaller than 3
  • maxOraclesCount is the maximum number of oracles that can reply to queries. Its value cannot exceed 6
  • oraclesWhiteList is a list of oracles (public keys separated by commas) that will have to provide data. If its value equals a blank line, any oracle can reply to the query
  • tillHeight is a deadline for reaching consensus. If by that time, consensus between oracles has not been reached (i.e. a number of replies exceeding minOraclesCount has not been supplied) the query sender can withhold the reward.

The data query format is up to query senders and oracles, but I suggest the following examples:

  • Temperature//{“lat”: “55.7558”, “lon”: “37.6173”, “timestamp”: 150000000000, “responseFormat”: “NN.NN”}
  • Pricefeed//{“pair”: “WAVES/USDN”, “timestamp”: 150000000000, “responseFormat”: “NN.NNNN”}
  • Sports//{“event”: “WC2020”, “timestamp”: 150000000000, “responseFormat”: “%s”}
  • Random//{“round”: 100, “responseFormat”: “%s”}

Collecting oracle replies

Any Waves account can be registered as an oracle for a specific data type. For that, it’s sufficient to call a dApp’s method and state the type of provided data as the argument. A call example can look like this: registerAsOracle(“Temperature”). At the point of the transaction, a dApp’s state will register when the oracle was registered as a certain data type supplier, and the following will be written: {oraclePublicKey}_Temperature={current_height}.

The oracle replies with the method response(id: String, data: String) and responseNumber(id: String, data: Integer).

Result counting

To count results, you need to invoke the method getResult(id: String). Results can only be counted if more oracles than stated in minOraclesCount have replied. To choose correct replies, oracle ratings are taken into account, rather than a simple majority. Ratings are formed in the following way:

  • At registration, each oracle receives a rating of 100
  • For each correct reply, an oracle receives +1 to its rating, while for each incorrect reply -1 is received.

Let’s assume that the query Sports//{“event”: “WorldCup2020”, “timestamp”: 150000000000, “responseFormat”: “%s”} generated replies from five oracles:

1. Oracle0, rating = 102, reply = “France”

2. Oracle1, rating = 200, reply = “Croatia

3. Oracle2, rating = 63, reply = “France”

4. Oracle3, rating = 194, reply = “France”

5. Oracle4, rating = 94, reply = “Croatia”

The result will be France, as the cumulative rating of oracles that gave this answer is 359, while the cumulative rating of oracles that replied Croatia is 294.

Following the result counting procedure, Oracle0, Oracle2, and Oracle3 will receive +1 to their ratings and they will be able to collect rewards, while the ratings of Oracle1 and Oracle4 will be reduced by one point, and they won’t collect any rewards.

Implementation

Let’s consider the step-by-step implementation of this dApp. It makes sense to start with the oracle registration method, which will accept the type of data supplied by an oracle as an argument. If an oracle with the same public key provides several types of data, it will have to register for each one separately.

The next logical step will be the implementation of functionality for sending data queries. As described above, a data query should include the following arguments:

  • id is a unique identifier for each query
  • question is a query in a predetermined format
  • consensusType is a data aggregation rule: consensus, median or average
  • minOraclesCount is the minimum number of oracles
  • maxOraclesCount is the maximum number of oracles
  • oraclesWhiteList is an oracle list (public keys, separated by commas, or else a blank line)
  • tillHeight is a deadline for reaching consensus.

This function has to write to the smart contract state:

  • Query parameters
  • The size of the reward
  • The sender’s public key
  • The keys by which we will subsequently write the given number of replies
  • Replies
  • Public keys of oracles that have replied
  • The query completion flag.

At the time the query is sent, arguments have to be checked for the following conditions:

  • If a white list of oracles is provided, the length of a line containing their public addresses should not exceed 1,000 characters (the function checkOraclesWhiteListLengthLt1000)
  • A query’s unique identifier should not exceed 32 characters (the function checkRequestIdLt32)
  • A query identifier should not have been used already (the function checkIdIsNotUsed)
  • Each query should stipulate a reward in WAVES tokens (the function checkPaymentInWavesGt0)
  • The minimum number of oracles is 3 and the maximum number is 6 (the function checkOraclesCountGt3Lt6)
  • The minimum number should either be lower or equal to the maximum number (the function checkOraclesWhiteListCountGtMinCount).

All listings of code below include invocations of auxiliary functions, which we have not shown in this article. However, you can find them in the repository Ride examples.

We now have functions for oracle registration and sending user queries. Now let’s implement functionality for sending a reply by one oracle.

Data query reply

Each oracle can reply to a query as long as there are no restrictions, such as a whitelist, and the query has not yet expired (due to the number of replies or the deadline). At the moment of reply, an oracle’s public key and its reply are written to the dApp’s storage. For this, at the moment of query sending the keys {id}_responders and {id}_responses are created. Data in these keys is stored as lines with the divider ;.

At the time of reply, we also increase the counter for the number of replies and the total number of points for this reply (points equal to the oracles’ total ratings).

Result collection

Upon receiving data from the oracles, the result is determined (as a consensus solution, average value, or median) and the oracles’ ratings are adjusted. In this function, we could also immediately pay out part of the reward to the oracle that provided correct results, but due to the smart contract complexity limit of 4,000, we won’t be able to do this within the same function. However, we can indicate in the account storage who has the right to collect part of the reward and allow the oracles to call a separate function for reward collection. Remember that only oracles whose reply corresponded to the result (or all oracles with a diversion of up to 10% for queries for average or median value) have the right to collect part of the reward.

https://gist.github.com/KardanovIR/c52f4d72a7440cd239f91b07c56789d1

Reward collection

The reward collection function for an oracle allows it to collect the reward only once, also checking that this oracle has replied to the query and its reply is considered correct.

We now have the main functionality for an oracle consensus smart contract. Test examples of such contracts’ operation can be found in the repository Ride examples.

Room for improvement

The implemented functionality is a basic proof-of-concept for decentralized oracles. We have resolved the issues stated at the beginning of the article:

  • The blockchain will always receive data, even if not all oracles have reached consensus
  • The process’s participants have an economic incentive to provide data
  • The oracle list can be maximally broad but, at the same time, it can be limited — if, for instance, we want to receive data only from oracles we trust rather than from any of them.

As query formats are typified, data provision can be automated — for instance, as a browser extension that tracks queries at a dApp’s address and replies to the data if its type is supported. A scenario under which a user with an open browser can cash in on data provision, without doing anything physically, is also possible.

In many cases, data is required not on a case-by-case basis, but as a constant flow. In our dApp, functionality for such a ‘data subscription’ has not been implemented, but we would be thrilled if the community contributes to the improvement of this example.

Pull requests are welcome here, or you can send suggestions to this email address!

--

--

Inal Kardanov
Waves Protocol

Co-founder & CTO of Billy. Software engineer. Blockchain, ML&AI developer. All opinions are my own.