Left-facing arrow
back to blog
SOFTWARE DEVELOPMENT

Building your first Ethereum Oracle

How do blockchains get data?

Blockchains are inherently self-sufficient. The entire deterministic model of a traditional blockchain is hinged on the fact that during transaction execution (which updates the “state” of the system) blockchains cannot perform any logic which is derived from sources external to the blockchain. All external data to the overall system must have, at some point, come from the input to a transaction, which has been recorded into a block.

On the Ethereum blockchain, for example, developers are allowed to deploy smart contracts that perform logic given some inputs. The executing logic in a smart contract cannot do anything outside of the blockchain. It cannot reach out and hit a web service from the internet. The only way to get data into a smart contract is to pass it in with the transaction. The only way to update blockchain state is to trigger that state change by sending a new transaction into the system.

Consider what would happen if a smart contract were allowed to hit an API endpoint to retrieve some data that was used in the smart contract’s execution. If the contract were deployed today into a new block, the API endpoint might return

<code>{ "foo": "bar" }<code>

But then tomorrow, the API operator changes the endpoint response to return

<code>{ "foo": "baz" }<code>

A month from now someone is newly syncing the Ethereum blockchain, the block containing our smart contract is executed, and the API returns a different response than what it returned a month ago. The state of the newly synced blockchain will be different than the state of a blockchain that existed last month.

This is no longer a fully self-deterministic blockchain. My blockchain looks different than your blockchain after the same sync, with the same blocks.

Said another way: given the full set of blocks, a node must be able to recreate the final state of the blockchain from scratch, with no internet connection.

So what does that mean for a developer, who wants to create and deploy a smart contract to be used for auditability purposes in their existing app? Oracles, developer. You need to make an oracle.

What is an oracle?

An oracle is a service that provides “trusted” data to a smart contract, through transactions. “Trusted” because, trust is a personal issue. Two entities might not “trust” data in the same way, given some specific implementation of an oracle.

Oracles are typically web services that implement some blockchain-specific functionalities, such as hashing and signing some data, or creating and submitting new transactions to the network.

Let’s see a simple example!

We’ll create three services to implement a simple circular “oracle” workflow.

At the bottom, on the blockchain, will be a smart contract with a whitelisted address as a contract parameter. This smart contract will implement a function called <code>updateWeather<code> that only responds to transactions from the whitelisted address. The function accepts weather data as input parameters, and echos out the data in an “event”, which is an Ethereum specific concept. Events should be thought of much like <code>stdout<code> logging in traditional software development. Events being emitted from a smart contract can be subscribed to asynchronously in a javascript application.

Living on the web will be two <code>nodejs<code> processes. One of them is the “oracle”. It exists on a runtime loop that retrieves weather data from an open weather API, then submits the weather data to the smart contract for historical audit-ability purposes.

The other <code>nodejs<code> process simply subscribes to weather events emitted from the smart contract, and <code>console.logs<code> the results. The events, as described above, are emitted every time the special “oracle weather” function is successfully executed.

Simple data flow of data moving from web-service-based Oracle, to Smart Contract, to another server logging events

Disclaimer

The following code has been greatly simplified for ease-of-understanding. It has been stripped of proper error handling, and is in no way suitable for production environments.

Smart Contract

The contract exposes one public <code>oracleAddress<code>, which gets set via an input parameter in the constructor.

-- CODE language-shell -- contract WeatherOracle { address public oracleAddress; constructor (address _oracleAddress) public { oracleAddress = _oracleAddress; } // ... }

Next we’ll define an Event, which will be emitted during a successful transaction on the <code>weatherUpdate<code> function. For sake of simplicity, the event will just emit a single string, the temperature.

<code>event WeatherUpdate (string temperature);<code>

And finally the <code>updateWeather<code> function. It has public visibility, which means it can be called from an outside transaction.

-- CODE language-shell -- function updateWeather (string temperature) public { require(msg.sender == oracleAddress); emit WeatherUpdate (temperature); }

Notice the <code>require<code> statement. Execution will only continue past this line if the <code>msg.sender<code> (address that sent the transaction) is equal to the publicly set <code>oracleAddress<code> whitelisted address.

That’s it!

Oracle Service

Our oracle is a simple <code>nodejs<code> service. It uses the <code>request<code> library to call an external weather API, parses the response, crafts and submits a transaction to the deployed smart contract, then waits to do it all again.

Hit the API endpoint (which is stored in an environment variable), to kickstart the workflow.

-- CODE language-shell -- const options = { uri: process.env.WEATHER_URL, json: true }; const start = () => { request(options) .then(parseData) .then(updateWeather) .then(restart) .catch(error); };

Parse the response.

-- CODE language-shell -- const parseData = (body) => { return new Promise((resolve, reject) => { const temperature = body.main.temp.toString(); resolve({ temperature }); }); };

Craft an Ethereum transaction that calls the <code>updateWeather<code> function on the deployed smart contract. Note that <code>account()<code> is an asynchronous function that loads an Ethereum account from elsewhere-defined configs, and <code>contract<code> is a javascript object that represents the location and interface of the deployed <code>WeatherOracle<code> smart contract. These smart-contract-specific functions are brought to you by the <code>web3<code> npm package :)

-- CODE language-shell -- const updateWeather = ({ temperature }) => { return new Promise((resolve, reject) => { account().then(account => { contract.updateWeather(temperature, { from: account }, (err, res) => { resolve(res); }); }); }); };

Finally we just restart the process after a timeout, based on an environment config. The <code>wait<code> function will resolve after the given timeout.

-- CODE language-shell -- const restart = () => { wait(process.env.TIMEOUT).then(start); };

That’s it! The above code implements a simple service that fetches data from an API and feeds it into a smart contract.

Did you catch the secret sauce?

When crafting the Ethereum transaction, we said it was <code>{ from: account }<code>. This <code>account<code> object is a javascript object that is the full account (read: private key) that is signing the transaction, and which must contain some ETH as gas to pay for the transaction.

Defined as an environment variable on the service is a private key, which is used to instantiate the <code>account<code> object. This private key MUST be the key behind the pubic address used to instantiate the <code>WeatherOracle<code> smart contract, due to the <code>require<code> line in the smart contract’s <code>updateWeather<code>function.

If any other address creates a transaction that calls <code>updateWeather<code> on the contract, the transaction will fail and the event won’t be emitted.

Speaking of emitting events, let’s make sure those work.

Event Consumer

This is yet another simple <code>nodejs<code> service. Again, <code>contract<code> is a javascript object that represents the location and interface of the deployed <code>WeatherOracle<code> smart contract. Calling the <code>WeatherUpdate<code> event name and passing in a callback is all you need for asynchronous event listening.

-- CODE language-shell -- const consume = () => { contract.WeatherUpdate((error, result) => { console.log("NEW WEATHER DATA EVENT ON SMART CONTRACT"); console.log("BLOCK NUMBER: "); console.log(" " + result.blockNumber); console.log("WEATHER DATA: "); console.log(result.args); console.log("\n"); }); }

As this service runs it will periodically output data to <code>stdout<code>, as valid transactions get mined into blocks.

-- CODE language-shell -- NEW WEATHER DATA EVENT ON SMART CONTRACT BLOCK NUMBER: 3424586 WEATHER DATA: { temperature: '74.75' }

And there you have it.

tl;dr

If you’d rather just get the full projects to see the code in action, find them on GitHub

https://github.com/decentorganization/weather-oracle-contract
https://github.com/decentorganization/weather-oracle-service

Adam Gall

Build with Us

We are always interested in connecting with people who want to fund, innovate
or work in the open financial system.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.