framework

package module
v0.0.0-...-c804a74 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 16, 2021 License: MIT Imports: 12 Imported by: 0

README

Dfinity Oracles

Dfinity Oracles is a framework for building blockchain oracles for the Internet Computer.

Table of Contents

Background

Applications on the Internet Computer often require information that comes from outside sources. For example, an application might require the current temperature of various cities in the world, or it might require the current prices of various stocks and cryptocurrencies.

Oracles are useful here in order to move information from traditional APIs to software running on the Internet Computer.

Tutorial and Examples

  • Tutorial - step-by-step setup of a sample oracle for retrieving the temperature in different cities.
  • Dfinity Weather Oracle Example - a complete working example oracle for retrieving various weather conditions in 20 different cities.
  • Dfinity Crypto Oracle Example - a complete working example oracle for retrieving current ETH and BTC prices.

Framework Reference

Now that we have an example app working with the library, let's take a deeper look at what the DFINITY Oracle Framework is doing behind the scenes.

oracle.Bootstrap()

The oracle framework bootstraps the oracle canister by calling into dfx for the following setup tasks:

  • Creating the oracle canister DFX project if it doesn't exist.
  • Starting the DFX network locally in the background.
  • Creating the oracle writer identity if it doesn't exist.
  • Creating the oracle canister if it doesn't exist.
  • Building and installing the canister (if the canister already exists, this performs an upgrade instead).
  • Starting the oracle canister if it isn't already running.
  • Claiming the oracle owner role if not already claimed, allowing it to manage canister roles.
  • Assigning the oracle writer identity the writer role, allowing it to update canister data.
oracle.Run()

The oracle framework starts the oracle service and periodically updates the mappings in the canister. Once this service is running, it will update the canister at the configured time interval.

The Oracle Update Lifecycle

The oracle framework performs updates at the interval configured via UpdateInterval. This update consists of several steps, which are described below.

Acquiring data

Through the information provided in config, we have a list of API endpoints (Endpoints), and for each endpoint, its URL (Endpoint), a collection of field names and their corresponding JSONPaths within API responses (JSONPaths), and an optional normalization function (NormalizeFunc).

For more details about JSONPath syntax, see this JSONPath reference.

When the oracle framework performs an update, it will first make GET requests to every endpoint in config, resulting in one JSON response per endpoint. The framework then extracts the desired fields from these responses by each field's JSONPath expression, resulting in a map[string]interface{} (a mapping from strings to anything).

In our example, we make requests to both WeatherAPI and WeatherBit, and extract the temperature, pressure, and humidity. We have two different configurations of these endpoints - one for Tokyo, and one for Delhi.

This value is then passed to NormalizeFunc, which is responsible for turning it into a map[string]float64. If no NormalizeFunc is specified, then a default one will be used - every field's value will simply be casted as a float64.

Summarizing data

Oracles generally acquire redundant data from many independent sources, then combine them into one trustworthy value.

From the previous step, every endpoint resulted in a map[string]float64 result, and each of these results is supposed to represent a redundant copy of the same piece of data (e.g., the current temperature in Tokyo). We now need to turn those into a single value.

This is the job of SummarizeFunc, which accepts a collection of map[string]float64 values, and is responsible for converting that into a single map[string]float64 value. In our example, we've created a custom summarization function called summarizeWeatherData. There are two particularly useful oracle framework utility functions that it calls:

  • summary.GroupByKey([]map[string]float64) map[string][]float64: takes a list of mappings, and converts it into a mapping where each key contains a list of all values in those mappings under those keys.
  • summary.MeanWithoutOutliers([]float64) float64: takes a list of numbers, removes outliers (outliers are values that are more than 2 standard deviations from the median, so a 95% confidence interval), and takes the mean of the remaining values. This is more stable than the median while still rejecting rogue values.
Updating the canister

As part of the bootstrap step, the oracle framework created a writer identity for the oracle to use - an identity that is allowed to write new values to the mappings stored in the canister.

After the previous step, we now have a map[string]float64 for Tokyo, and a map[string]float64 for Delhi. We've written some simple Candid IDL serialization functions in Go that then turn this into a string suitable for passing into DFX.

The final step is then to write this serialized string to the canister using the writer identity.

Testing the Framework

Although the writer identity is the only one capable of changing the values inside the oracle canister, reading those values can be done by anyone. To test it out, enter the following command:

cd weather_oracle
dfx canister call weather_oracle get_map_field_value ("London", "temperature_celsius")

This would return the currently stored temperature in Celsius of London within the canister.

Similarly,

dfx canister call weather_oracle get_map_field_value ("Tokyo", "humidity_pct")

would return the currently stored humidity percentage of Tokyo within the canister.

Oracle Revocation

The framework assigns the owner identity to the user who deploys the canister, and this cannot be edited once deployed.

The owner identity is capable of assigning the writer role to other identities, and also of permanently disabling the oracle if they believe it has been compromised.

If the writer identity is compromised, but not the owner identity, the owner identity can simply assign a new writer, and users can continue to use the oracle as they did before.

If the owner identity is compromised (e.g., private key leaked), they cannot take control away from the original owner, because the owner field is not editable. However, the original owner can permanently disable the oracle, preventing attackers from using it for their own gain. This disincentivizes attackers from attempting to take control of the oracle in the first place.

As an oracle owner, we can permanently disable the oracle using the following command:

cd weather_oracle
dfx canister call weather_oracle self_destruct

After this, if you try to get the value through the command given in the Testing section, you will receive an error.

Documentation

Index

Constants

View Source
const CodeTemplate = `` /* 5016-byte string literal not displayed */

Variables

This section is empty.

Functions

This section is empty.

Types

type DFXService

type DFXService struct {
	// contains filtered or unexported fields
}

DFXService contains various fields to be used by the DFX interface

func NewDFXService

func NewDFXService(config *models.Config, log *logrus.Logger) *DFXService

NewDFXService creates an instance of DFX Service

type Oracle

type Oracle struct {
	// contains filtered or unexported fields
}

Oracle is an instance of an oracle

func NewOracle

func NewOracle(config *models.Config, engine *models.Engine) *Oracle

NewOracle creates a new oracle instance

func (*Oracle) Bootstrap

func (o *Oracle) Bootstrap() error

Bootstrap bootstraps the canister installation

func (*Oracle) Run

func (o *Oracle) Run()

Run starts the Oracle service

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL