Building an “Oracle” for an Ethereum contract

Lovable Technology
5 min readOct 11, 2016

What is an Oracle?

Smart contracts, by their nature, are able to run algorithmic calculations and store and retrieve data. Because every node runs every calculation, it’s not practical (and presently impossible) to make arbitrary network requests from an Ethereum contract. Oracles fill this void by watching the blockchain for events and responding to them by publishing the results of a query back to the contract. In this way, contracts can interact with the off-chain world.

This introduces obvious trust problems. A decentralized contract that requires trusting a single outside data source is a bit of a contradiction. This can be mitigated by having multiple independent oracles respond to the same queries to form a consensus.

For more on Oracles, check out Oraclize, a FinTech company providing a ‘reliable connection’ between distributed apps. Their explanation of an oracle is a good place to start.

The Tinypay.co DNS Oracle

The oracle for Tinypay (described in more detail here) has to do three simple things:

  • Pick up ‘ClientCreated’ events from the contract
  • Verify DNS records using the data from the event
  • Send a ‘ConfirmClient’ transaction to the contract once the domain is confirmed

I went through a few iterations getting the right implementation, and I want to walk you through them as a tour of development on Ethereum.

You can use RPC directly… but you probably shouldn’t

The first time I wrote the oracle, I used Go. I tried to do all of the communication with the Ethereum node using the RPC APIs directly.

That was interesting, because I was able to learn a lot about how the Ethereum protocol stores and marshals data at a low level. I had to manually recreate the ABI (Application Binary Interface) in code and use that to both send and decipher messages. The ABI is necessary for defining how the contract can be interacted with, and how data is extracted from the raw bytes on the wire.

The actual extracting of data from the events proved to be more complex than I was ready for. Handling events was not yet completed in the Go client. I was forced to poll the RPC endpoint manually, and to figure out how to decode the binary data from the raw events. The Go client certainly seems to be the primary focus of the Ethereum team, and they’re well aware of the hole in the client around watching and decoding events. I expect this to be shored up soon, making the Go client a first class option for writing Oracles and other Ethereum client apps.

The low level RPC APIs and decoding APIs proved to be very low-level, and they were making rapid iteration much harder, so…

Web3 is a good abstraction

For the second iteration I switched to node.js and used the web3 library for communicating with the geth node. This gave me built in abstractions for event watching, data extraction and formatting, and generally made life a lot easier.

I started with the very useful ‘tinyoracle’ guide by Alex Beregszaszi which got me well on my way to a good second version

The following code extracts are somewhat edited, the full code can be found in the github repository (tag v0.0.2 for this iteration)

var Web3 = require('web3');
var web3 = new Web3();
var contracts = require(path.join(__dirname, 'gen_contracts.json'));// First we instruct web3 to use the RPC provider
web3.setProvider(
new web3.providers.HttpProvider(
'http://' + opts.rpc_host + ':' + opts.rpc_port));
// This isn't strictly necessary here, but goes to show the step required to "unlock" the account before sending transactions.
if (!web3.personal.unlockAccount(
web3.eth.coinbase, opts.wallet_password)) {
console.error('Could not unlock');
process.exit();
}
// Here we register the filter with the ethereum node, and then begin polling for updates.
function runLoop(o) {
var filter = web3.eth.filter({address: o.contract_address});
filter.watch(function (err, results) {
if (err) {
console.log('WATCH ERROR: ', err);
process.exit();
}
console.debug(results);
});
}
// If the contract isn't deployed yet, we deploy it here
if (!opts.contract_address) {
// This block of code loads the ABI for interpreting contract data.
var dmC = web3.eth.contract(JSON.parse(contracts.DomainMicropay.abi));
var x = {
from: web3.eth.coinbase,
data: contracts.DomainMicropay.bin,
gas: 1000000
};
// send the transaction for installing the contract.
dmC.new(x, function (err, resp) {
if (err) {
console.error('Loading contract', err);
process.exit();
}
var addr = resp.address;
if (!addr) {
console.log('Pending tx: ', resp.transactionHash);
} else {
console.log('Deployed Address: ', addr);
opts.contract_address = addr;
runLoop(opts);
}
});
} else {
runLoop(opts); // in either case, start the polling event loop.
}

Truffle is what you want to use though

Finally, for the third iteration, I gave up trying to roll it all myself. We were already using the excellent tool, truffle, from ConsenSys in our web front end. I just copied the generated artifacts into my node.js project, and included it directly, and I was in business.

Using Truffle we were able to compile our Solidity contracts into a javascript library that captured important details like the deployed address of the contract, and completely abstracted away the low level RPC communication. Watching events, and sending transactions, and querying data became simple API calls generated directly from our contract.

// This code extract shows the whole event loop abstracted behind the actual event name: ClientConfirmed and ClientCreated.
startWatcher: function (rpcUrl, unlockPass) {
password = unlockPass || password;
web3.setProvider(new web3.providers.HttpProvider(rpcUrl));
DomainMicropay.setProvider(web3.currentProvider);
contract.ClientConfirmed({}, eventOpts(), function (err, data) {
if (err) {
console.log('Error ClientConfirmed: ', err);
return;
}
console.log('Event ClientConfirmed: ', data.args.domain);
});
contract.ClientCreated({}, eventOpts(), function (err, data) {
if (err) {
console.log('Error ClientCreated: ', err);
return;
}
console.log('Event ClientCreated: ', data.args.domain);
contract.getPaymentContractForDomain
.call(data.args.domain)
.then(beginDomainVerification(data))
.catch(errFn('Unhandled Error: '));
});
}

As you can see, Truffle provides some really nice abstractions for using and interacting with smart contracts. It’s not perfect, and doesn’t solve some problems like contract versioning, etc. But we’ll have to cover those things in another post.

Must Win would love to help develop your next “DApp”. If you’re looking for help understanding or utilizing block chain tech, reach out to we@mustwin.com and reference this post.

John Weldon writes back-end code for various projects and has experience in a wide range of technologies and programming languages.

--

--

Lovable Technology

World Class Engineers and Designers Helping You Ship Lovable Web and Mobile Technology.