Analysis of Chainlink — The Decentralised Oracle Network

In this article, I’m going to be deep diving into Chainlink. From giving the problem it’s aiming to solve; the solution they’re currently developing for that problem and my own deep-dive into the technology they’ve built so far.

This article is a follow-up to my first review on the old Chainlink test-net found here. Since the platform described in that article has been completely re-built from the ground-up, it warrants a new analysis of what has been built.

Credit to SmartContract @ https://chain.link/

Disclaimer: I’ve been personally invested in ChainLink since it was first tradable on EtherDelta, and I’m also the co-founder of a staking network for ChainLink called LinkPool. In this review I will throw my own interests aside and look at the current status of the platform critically, as that is also vital for me to keep level headed on what I’m also building.

EDIT: As of 12/18, I’m now a member of the Chainlink team. This article was written completely prior to that without any foresight of me joining.


What is Chainlink and what’s their goal?

First and foremost, to give the tagline on their website chain.link:

The Chainlink network provides reliable tamper-proof inputs and outputs for complex smart contracts on any blockchain.

To people who aren’t really technically minded and haven’t worked with smart contracts on any blockchain that supports them, that may not mean much at all. You may even think, since smart contracts are immutable pieces of code, aren’t their inputs and outputs already tamper-proof by design?

The answer to that question is both yes and no. If you as an end-user are directly interacting with a smart contract (think a decentralised exchange like IDEX), then what you’re inputting is tamper-proof data. This is due to your fingertips inputting that data (as long as your keys aren’t stolen) and forming consensus with yourself to what’s right and wrong. There’s no way that the data your inputting can be manipulated and misconstrued once you’ve sent it, you signed it with your keys after all.

The output to that data you’ve inputted is then calculated in a tamper-proof manner in that smart contract. With referring to the IDEX example, that output could be a new buy order for a ERC20 token at a certain price, it could be the withdrawal of a token amount you’ve specified. If your expected outcome doesn’t match reality, its only because of the contract code (or agreement) that you’ve taken part in, not the data.

You now may be asking yourself…

So why is Chainlink needed then?

All of the examples I gave above are for inputs/outputs that can be calculated solely with blockchain data. With that I mean the smart contract has all the data in its sight to be able to calculate the output from the input you gave it.

To break that down slightly further with the IDEX buy order example:

  • You place a buy order with 1 ETH for a ERC20 token at 0.001 ether per token.
  • You have two inputs for this buy order, your physical Ethereum and the price per token you’d like to pay.
  • The contract has complete visibility and access to your Ethereum and buy order price, as you’re directly inputting this into the smart contract.
  • Now the output of this contract is calculated, by simply by diving the 1 ETH by your order price 0.001 to give you your request token amount of 1000.

This is a scenario that happens constantly on decentralised exchanges, and due to the public nature of blockchains, every transaction that occurs can be audited and verified by anyone. Now, the big question is:

Why can’t I use FIAT currencies to directly buy tokens on decentralised exchanges?

Let’s break down the above buy order example, replacing ETH for USD:

  • You place a buy order with 300 USD for an ERC20 token at 50c per token.
  • You have two inputs for this buy order, the sheer amount of USD and the price per token you’d like to pay.

Problem 1: The contract sees that you’ve inputted 300 USD, but it has zero visibility of whether you actually have 300 USD. You can’t directly send USD to a contract like you do with Ethereum.

  • Your buy order gets filled and the person who’s sold the tokens needs to be given 300 USD in return.

Problem 2: The contract has no concept of transferring existing FIAT currencies, it cannot execute any typical wire transfers like any existing banking infrastructure does.

Chainlink intends to solve these problems by connecting smart contract platforms to the outside world, allowing smart contracts to deal with existing FIAT currencies and have sight of all important data in a trustless manner.

High level overview of Chainlink and their working partners, connecting blockchains to payment providers and data sources. (Credit to SmartContract @ https://chain.link/)

How do they solve the problem?

The problem I’m referring to is one that is quite widely known in the crypto community as the “Oracle Problem”. There is quite a few articles that describe the oracle problem, but one I personally like is this interview.

To summarise, oracle problem is exactly what I’ve just described above, a gateway for smart contracts to interact with the outside world. The reason that this is a problem is due the sheer difficulty of making oracles completely trustless.

Any competent blockchain developer can make an oracle right now, and its not hard at all. Although, if you build an oracle to give your contract a connection to the outside world, you’d have the following issues:

  • If your oracle goes down, your contract ceases to function.
  • Who’s to say that your oracle is correct in the data it’s giving your contract? It could be giving any falsified data.
  • Lack of practicality. Your oracle is more than likely just going to work with your contract, providing only one type of connection to the outside world.
  • No notation of your own reputation, an indicator disclosing your oracles accuracy to any auditors.
  • If you run your own oracle for your own service, there’s a degree of incentive to provide wrong results, as it could result in more money in your own pocket. An example of that is insurance contracts and their settlements.

This is where Chainlink comes in, as it boasts the following features:

  • No centralisation of oracles, anyone can run their oracle software and take part in the network.
  • On-chain smart contract based data aggregation.
  • Complete abstraction in types of connections to the outside world. It can support any data source. Whether that be public data sources, subscription based ones or even completely private sources.
  • Reputation system. Each oracle will have an on-chain identity with a reputation, giving the end-user an understanding of the quality of the oracles being used.
  • Penalty/Collateral system. When a Chainlink oracle accepts a request, it will put forward LINK tokens as collateral. If the oracle provides bad data, it forfeits those tokens and tarnishes its own reputation.

To cross-reference the first set of issues to the features Chainlink boasts:

  • If any Chainlink oracle goes down, your contract will always function as it will simply use different oracles.
  • Any data provided by Chainlink will be provided by multiple oracles, to which one answer is then returned back to the contract. This is possible by the oracles forming consensus to what is correct with the on-chain mechanisms.
  • Unlimited practicality. Due to Chainlink oracles supporting literally any connection it has developer support for, it can be used for any practical application within any contract.
  • Each oracle has on-chain identity and reputation verifiable by end-users and auditors alike.
  • Each oracle has financial and reputational incentive to always be honest. If they try to post bad data, money is lost and they’re less likely to receive more requests in the future.
Credit to SmartContract @ https://chain.link/

Solution Review

So by now, hopefully you’ve got a high-level but fundamental understanding of what Chainlink is about and the problem they’re trying to solve. If more technical details bore you, then I’d stop reading at this point. I’m going to be delving into what the team has currently built, how they’ve built it and the design decisions.

Software Design Choices

Any oracle network will depend on the software written to act as the oracle. In the context of Chainlink, this oracle will be ran by thousands of people globally, so it needs to be remarkably stable, easy to set-up and monitor.

Their oracle, branded as a “Chainlink Node” is written in Golang and located on their GitHub.

For anyone who’s been involved with Chainlink for long enough, they’ll remember that the above wasn’t always the case and the team has since completely rebuilt their oracle software from the ground up, releasing the new version in Feburary this year.

  • The old version of the Chainlink node was built in Ruby using a typical relational database that was PostgreSQL.
  • The new version of the Chainlink node is built in Golang using a key/value Golang native database called BoltDB.

The reasons for the complete overhaul as given from the team were due to the ease of operation and the benefit of being able to scale to handle a far higher volume of requests with the same computing resources.

The decision to move to Golang is a great one. Golang is far more performant than Ruby is for more complicated web based services and has a far lower memory footprint, making it easier and cheaper to run the Chainlink node at a larger scale.

I’m personally on the fence about them using BoltDB. The main advantage of using BoltDB is to provide a simple, fast and reliable file-based database for applications. In the short-term, BoltDB will be perfectly fine, but once we see large amount of volumes of traffic in the Chainlink network, BoltDB will be bottleneck of the system with far larger read/write times than what you’d see in a typical relational database like PostgreSQL.

I understand the decision completely though, by using BoltDB it means the end-user has far less of a skill curve to setting the node software up. You don’t have to run the database as a separate service, rather it’s all built in and done seamlessly with no extra work needed. Perfect for getting thousands of people to run nodes on your network.

Although, for the larger entities who would want to implement features like high availability, fail-over or disaster recovery; using BoltDB makes that difficult as you can’t setup a master/slave design like you’d traditionally do, rather needing shared network disks and infinite database lock waits. It also makes it more difficult to monitor the performance of the database, as you don’t get the typical slow query logs, metrics and other niceties you do in typical relational databases.

This reasoning is why I’m on the fence. On one side I love it because of its simplicity and ease for people to set up their own oracles, but then on one side I dislike it because it throttles how it can be set-up and the potential performance impacts at a larger scale.

Adaptability

As I wrote about in the section in how they solve the “Oracle Problem”, Chainlink boasts the ability to be able to support absolutely any data source or blockchain with what it calls “external adaptors”. There’s a post written by the team on them here.

Chainlink’s ability to support external adaptors is a very, very important one. Emphasis on the very. Without external adaptors, the only data sources that Chainlink oracles would be able to support are just public ones. For anyone who needs some context on public API’s vs key-based API’s:

The public API example is Binance’s ticker for the LINK/BTC pair, and the key based example is for the daily time series of MSFT in AlphaVantage. The public example gives a response, while the key-based API says you need to specify a key to access this API.

For Chainlink oracles to be able to use AlphaVantage (the key based API), then each node operator needs to have their own API key which they specify for each request. If the end-user gave their API key, then it’d be on the public blockchain for everyone to see and they’re most likely going to hit their rate limit for that API, not including it being a security issue.

This is where external adaptors come into play, they’re micro-services that simply fetch data on behalf of the node and then return it back to it in a specific format (I’ll go into that). By using separate micro-services, it allows each node operator to sign up to these key-based API’s, specify their own key to the external adaptor and then support providing that data source to any contract that wishes to use it.

These adaptors are then added to the Chainlink node either via their easy to use UI, or by directly posting to the API. All you need to do is specify its name and url, for example:

  • Name: externalAdaptor
  • URL: http://myexternaladaptor.com/endpoint

External Adaptor Schema
To develop an external adaptor for Chainlink, it’s remarkably simple. For practical examples, I’ve built a few myself:

  • Asset Price Adaptor: Aggregates price information based on weighted average of the trade volume. (link)
  • Alpha Vantage: Support for all endpoints within the Alpha Vantage platform. (link)
  • XML to JSON: Basic adaptor that parses XML responses and converts them to JSON to be parsed by the Chainlink node. (link)

The adaptor has to parse a message in the following JSON schema, sent directly from the node:

{ 
"jobRunId": "1234",
"data": {
"base": "BTC",
"quote": "USD"
}
}

The jobRunID is the primary identifier of the job on the node which is requesting data from the adaptor. The data object is far more interesting, as this is what allows end-users to specify parameters on request to external adaptors.

The example above is a payload I used for testing my asset price adaptor, and as you’ve probably guessed, it tells the adaptor which base/quote trading pair to fetch the price for.

When responding, the payload has to be in the same format, for example:

{
"jobRunId": "1234",
"data": {
"base": "BTC",
"quote": "USD",
"id": "BTC-USD",
"price": "6754.1794331023375",
"volume": "195359536.70301655",
"exchanges": [
"GDAX",
"Bitfinex",
"HitBTC",
"Bitstamp"
],
"errors": null
},
"status": "",
"error": null,
"pending": false
}

This is the response that the asset price adaptor would return back to the node, exact same model but with extra keys and data more populated than before. In this case with asset price, its returned the price of BTC-USD and with what exchanges it used to get that data. Everything within the data object is persisted down the pipeline in the Chainlink oracle, so that data can be used in further subsequent adaptors.

If the adaptor failed in the request, it would set error with the error string of what happened and then the node would mark the run as ‘errored’ with the response given from the adaptor.

External Adaptor Conclusion
I love the implementation of external adaptors and for good reason. They’re remarkably simple to develop as they’re practically just API proxies; they can be fully parameterised for customisation on what the end-user actually needs and open up the use-cases of what smart contracts can do in general.

Typical oracle providers like Oraclise only support public API’s, and as much that’s vital, it’s limiting. Once we see this technology maturing, we’ll see external adaptors developed that’ll be used for very specific, weird and wonderful use-cases within smart contracts.

Blockchain Inoperability

One of the areas that can set any oracle network apart from others is its ability to work across different blockchains. As Chainlink’s own media shows, they’re showing support for Ethereum, Bitcoin and Hyperledger.

Chainlink currently only supports Ethereum, and the official answer from the team around this is that they’re going to fully complete its Ethereum integration before starting on others.

Supporting these chains natively would be a huge accomplishment, especially considering Chainlink isn’t its own blockchain. For example, you have projects like Blockcolider that run their own blockchain, including blocks from Ethereum and Bitcoin into its own. If you don’t have that, then you’ve still got to form consensus on Ethereum as there’s no other public chain supported that can decentrally compute.

This raises a few questions:

  • Can I create Chainlink requests on Bitcoin when there’s no smart contract functionality?
  • If requests can originate from other networks, how is the LINK token used for payment?
  • Can Chainlink feed data into different blockchains, or is solely a read-only based access model?

Due to what I said in my disclaimer, its been important for me to think about these questions and provide solutions, or at least think to what is currently possible.

This will be an interesting one for Chainlink node operators, as you’ll most likely have to maintain and manage your full Bitcoin and Hyperledger nodes (depending on your connectivity to a given permissioned chain).

Personally, I see limitations to what is possible with blockchain inoperability due to the nature of it being built natively on Ethereum, but there’s still some very exciting possibilities that it will allow now without much effort:

  • Escrow contracts with BTC: Chainlink nodes monitor specified BTC addresses for the deposit of escrow. Once the conditions in a smart contract have been met, then the equivalent escrow in ETH is transferred to the seller.
  • Cross-chain data and conditions between Ethereum and Hyperledger: Due to both being smart contract platforms, data can easily be transferred between public and permissioned chains and back based on criteria that can be specified in both. On-chain aggregation on both networks depending.

In the escrow example, I specified that once the conditions are met the equivalent value in ETH would be transferred to the seller from what was deposited as BTC. This is one of the limitations, as if you’ve got multiple oracles all trying to send that deposited BTC to another BTC address, then one will work and the rest will fail. If only one oracle was set to undertake this task, it breaks the trustless approach as an oracle could falsely report BTC has been transferred when it hasn’t, giving a false positive.

To conclude, this is one area I’d like to see more developed and one I will be keeping a close eye on when it does. I see a lot of use-cases with the way the system is designed now, but with it being problematic in facilitating transactions in Bitcoin, it feels limited.

Note: If anyone reading this has an understanding of how this can be done, get in-touch.


End-User Experience

Last but not least, is how users actually integrate their smart contracts with Chainlink. This section of the article will most likely not age well, as this is an area that is constantly changing with active development.

Solidity Integration

One of the ways that a user can use Chainlink will be creating requests directly inside smart contracts on Ethereum. To me, the most important method of request generation, as it allows for automated request creation with simple parameters. It allows developers to focus on how they use data in their contracts rather than focus on how data is fetched in the first place.

To demonstrate this, there’s some terminology to explain first:

  • Consumer Contract: A consumer contract is what an end-user will deploy that directly integrates with Chainlink.
  • Oracle Contract: This is a deployed contract by the Chainlink node operator that represents a given node on the network.
  • Requester Contract: One of two parts of a consumer contract, this one integrates directly with Chainlink to request the data.
  • Fulfilment Contract: The second part of a consumer contract, one which simply has functions to receive data requested by the requester contract.

To dive straight in, lets look at a ‘consumer’ smart contract, source.

A ‘requester’ section of the contract:

function requestEthereumPrice(string _currency) 
public
onlyOwner
{
ChainlinkLib.Run memory run = newRun(PRICE_SPEC_ID, this, "fulfillEthereumPrice(bytes32,uint256)");
run.add("url", "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD,EUR,JPY");
string[] memory path = new string[](1);
path[0] = _currency;
run.addStringArray("path", path);
run.addInt("times", 100);
chainlinkRequest(run, LINK(1));
}

This is a very simple example of what can be done. To follow through each line, a new Chainlink Run type is initialised by giving the Spec ID that is on the node, and the signature of the method to call when it returns the data back.

After that, you can see that to the Run type, they’re adding a url parameter, and then adding an array with the key path . URL is self explanatory, it’s what the contract wants the Chainlink node to call. Path is the JSON selector for selecting the specific piece of data to be returned to the contract.

By looking at the function parameters, _currency is what quote currency to use when returning the Ethereum price. Since the URL has hardcoded parameters of USD, EUR and JPY, only those quote currencies would work when specified.

To now view the ‘fulfilment’ part of the contract:

function fulfillEthereumPrice(bytes32 _requestId, uint256 _price)
public
checkChainlinkFulfillment(_requestId)
{
emit RequestEthereumPriceFulfilled(_requestId, _price);
currentPrice = _price;
}

Simple method on return, the contract checks whether it’s actually the oracle replying with the checkChainlinkFulfillment modifier, emits the event of fulfilment and then sets the price in the contract.

What’s missing?
Obviously keep in mind that Chainlink is currently undergoing active development and is in an alpha state, so what is currently missing will be done in the future, it’s just not there yet.

Currently, you only can submit requests to one node at a time, and the node needs to be preconfigured to accept this job:

address constant ROPSTEN_ORACLE_ADDRESS = 0xB68145133973411b7B3F2726A625FE3f3808240D;

bytes32 constant PRICE_SPEC_ID = bytes32("8b05126a278f4f0abe02dd482aa802f8");

Those two constants in the contract are:

  • The address of the ‘Oracle’ contract as mentioned in terminology above. Since this oracle address is hard-coded into the contract, that one node will only ever accept that request.
  • The ID of the Job Spec on the Chainlink node. This is what I’m referring to when saying the node has to be preconfigured to accept a given job. The node operator, in this case Chainlink themselves, have created a job on their node to facilitate the requests that are specified in this contract.

To give the JSON for this Job Spec:

{
"initiators": [{ "type": "runlog" }],
"tasks": [
{ "type": "httpGet" },
{ "type": "jsonParse" },
{ "type": "multiply", "times": 100 },
{ "type": "ethuint256" },
{ "type": "ethtx" }
]
}

This JSON specification defines what the node has to do when it receives the request.

To begin with, initiators is what the node should look for to start the request. In this case, runlog is listening for on-chain Solidity contract events that occur within the consumer contracts.

The tasks are then the series of steps to undertake when the initiators are met. The type specified is the adaptor to be used. This can be a mix of core and external adaptors that are made by the community. To run through:

  • httpGet : Make a HTTP GET request. This core adaptor listens for the url parameter specified in the contract.
  • jsonParse : Parse a specified path from the response. This listens for the path parameter specified in the contract.
  • multiply : Self explanatory, multiplies the value by 100. This listens to the times parameter. In this case it’s hard-coded into the job rather than the contract.
  • ethuint256 : Convert the JSON parsed value into the uint256 format in Ethereums EVM.
  • ethtx : Submit the result back on-chain to the contract that requested it.

Since the Consumer contract has an hard-coded ID, this means the JSON specified above has to be sent manually to the node and then the ID set in the Consumer contract prior to deployment.

There was the notation of SpecAndRun in Chainlink, but this has since been removed. This allowed Chainlink nodes to create jobs automatically when a contract created a request to a node. You didn’t need to specify a Job ID directly in the contract, the node would simply just create it for you when you made the request.

Personally, I was disappointed when I got told SpecAndRun was being removed, but this takes me onto the next section as it’s clear that creating Chainlink requests purely by using Solidity contracts is not the main focus initially.

Listing Services

Listing services are a Chainlink notation of using a web-based front-end to create Chainlink Service Agreements. There’s not much to write regarding these listing services as there’s not an example yet, but my own understanding is that they’ll be very similar to the old test-net I wrote about in my first review.

To get an understanding of how listing services will work, we need to look into service agreements that are actively undergoing development as I write this.

Service agreements are complete specifications of what a Chainlink node is undertaking. It contains the Job Spec as detailed above, how much the node is going to be paid, and how long the node will be serving that agreement for. More importantly, it specifies a list of oracles to undertake the service agreement specified.

Service agreements are created on the node by posting JSON to the service_agreements endpoint. There’s one key difference to this endpoint and the others, it currently doesn’t need any authentication.

It won’t stay unauthenticated for long, it’s an alpha endpoint that’s currently locked from use outside of development mode. Although, it’s an important endpoint as this will tie into the listing service.

Keeping the solidity integration in mind, the listing service will allow you to specify the same parameters that are written in code but in a nice web GUI. Once you’ve specified all the parameters, you’ll then get a list of nodes you want to undertake your given request, and then your contract gets deployed.

Once the contract is deployed, the listing service will then post the service agreement to each node that you’ve selected for your request. Each node will then sign the service agreement and post on-chain saying its received and happy to undertake that request.

Based on SpecAndRun being removed, and the new focus being on service agreements that are created through a more open API, the direction seems to be shifted from complete Solidity integration, to initially using a listing service that creates a service agreement that can be used on-chain between multiple oracles.

Concerns
One of the bigger benefits and ease of running a Chainlink node was the fact that you could keep it completely private, with no in-bound access to the node at all.

Now with service agreements, that cannot happen as the endpoint needs to be open publicly. The endpoint also is listening on the same port as the main UI/GUI, which for less advanced users will open up their whole node to the public internet. I’m advocating to change this, as its not complicated to run just this endpoint on a separate port to make it easier to create firewall rules for.

To then change focus to the listing service, it will be website containing a centralised database of all the nodes that it has listed. If this website got hacked, then suddenly there’s a data dump of the hostnames/IP’s of all Chainlink nodes, instantly making them targets for attacks. Whether that be DDoS, dictionary attacks or anything else. Having a requirement of needing an open port publicly is a trade-off that I’m against, rather I feel this should be done solely on-chain, similar to how SpecAndRun was developed.

Then again, moving it on-chain also opens up nodes to other forms of attack. Attacks that could result in wider financial loss. Such an example is ETH being drained from Chainlink nodes that are being controlled to keep signing service agreements, slowly draining their balance.


Conclusion

I’ve not yet found a competitor to Chainlink that comes close to functionality offered or where they are in-terms of development. Even though I’ve expressed concerns with some specific areas of the system, overall it’s clearly a well thought out network that has some great minds behind it.

The initial version of their main-net won’t solve the “Oracle Problem” in its entirety, but it will be a huge step forward in doing just that. The ability to specify multiple oracles to undertake one single request and aggregate that answer back on-chain will still be a first in the crypto world. Then it’s only just a matter of time until we see more of the on-chain mechanisms deployed, but that’s an article for the future.


For future updates around Chainlink and what I’m working on, follow me on twitter @HuxtableJonny.

Thanks for reading!