Fiducia Oracle Network

It is often necessary we fetch external data in our blockchain applications. For traditional back-end applications this can be done with simple HTTP REST-ful fetch requests, but not the case in blockchain applications. Firstly it is usually not necessary for every nodes in the network to fetch from the remote end to verify the data. Secondly the http request does not return a deterministic result as there maybe network delays or even errors. This potentially cause block production delays and problems and affect the stability of the blockchain application.

Through REI and FCN, Fiducia Network provides smart contracts access to off-chain data from APIs, data sources, and computation processes, including grid nodes running full fledged applications.

The following is an implementation of the FID node and REI to access off-chain data using the Substrate ecosystem components.

This project consists of a Fiducia Interoperable Data blockchain node with a custom module that fetch prices of a few cryptocurrencies, each from two external sources and then aggregate them by simply averaging and recording them back to on-chain storage.

Fiducia Oracle Network Implementation

Fiducia Oracle Network is an interoperable blockchain that is able to fetch price information from data sources. The Development stack of Fiducia Oracle Network consists of: 1) Substrate SDK 2) WebAssembly 3) Grid Computing Node

1. Data Fetch Transaction

In this file, in addition to the regular lib setup, we specify a SubmitDFTransaction type and set it as the associated type for our pallet trait. We then implement the system::offchain::CreateTransaction trait afterwards as follows. This is necessary for the off-chain worker to send signed or unsigned transactions back on-chain.

// -- snip --
type SubmitDFTransaction = system::offchain::TransactionSubmitter<
  data_fetch::crypto::Public,
  Runtime,
  UncheckedExtrinsic
>;

parameter_types! {
  pub const BlockFetchDur: BlockNumber = 2;
}

impl data_fetch::Trait for Runtime {
  type Event = Event;
  type Call = Call;
  type SubmitSignedTransaction = SubmitPFTransaction;
  type SignAndSubmitTransaction = SubmitPFTransaction;
  type SubmitUnsignedTransaction = SubmitPFTransaction;
  type BlockFetchDur = BlockFetchDur;
}

impl system::offchain::CreateTransaction<Runtime, UncheckedExtrinsic> for Runtime {
  type Public = <Signature as Verify>::Signer;
  type Signature = Signature;

  fn create_transaction<TSigner: system::offchain::Signer<Self::Public, Self::Signature>> (
    call: Call,
    public: Self::Public,
    account: AccountId,
    index: Index,
  ) -> Option<(Call, <UncheckedExtrinsic as sp_runtime::traits::Extrinsic>::SignaturePayload)> {
    // ...
  }
}

// -- snip --

2. Data Verification and Block Inclusion

Note the necessary included modules at the top for:

  • system::{ offchain, ...}, off-chain workers, for submitting transaction

  • simple_json, a lightweight json-parsing library that work in no_std environment

  • sp_runtime::{...}, some libraries to handle HTTP requests and sending transactions back on-chain.

The main logic of this pallet goes as:

  • After a block is produced, offchain_worker function is called. This is the main entry point of the Substrate off-chain worker. It checks that for every BlockFetchDur blocks (set in lib.rs), the off-chain worker goes out and fetch the price based on the config data in FETCHED_CRYPTOS.

  • For each entry of the FETCHED_CRYPTOS, it executes the fetch_price function, and get the JSON response back from fetch_json function. The JSON is parsed for a specific format to return the price in (USD dollar * 1000) in integer format.

  • We then submit an unsigned transaction to call the on-chain extrinsics record_price function to record the price. All unsigned transactions by default are regarded as invalid transactions, so we explicitly enable them in the validate_unsigned function.

  • On the next block production, the price is stored on-chain. The SrcPricePoints stores the actual price + timestamp (price point), and TokenSrcPPMap and RemoteSrcPPMap are the indices mapping the cryptocurrency and remote src to some price points. Finally UpdateAggPP are incremented by one so later on we know how many new price points have arrived since we last calculated the mean of the crypto price.

  • Once a block is produced, offchain_worker function kicks in again, and see that UpdateAggPP have mapping value(s) greater than 0, then the aggregate_pp function is called. This function retrieves the last UpdateAggPP mapping value, being passed in to the function as freq parameter, of that crypto prices and find the mean of each crypto price. Then we submit an unsigned transaction to call the on-chain extrinsics record_agg_pp function to record the mean price of the cryptocurrencies, with logic similar to the record_price function.

User Interface

Configuration

The User Interface is based on ReactJS library. Configuration is stored in the src/config directory, with common.json being loaded first, then the environment-specific json file, and finally environment variables, with precedence.

  • development.json affects the development environment

  • test.json affects the test environment, triggered in yarn test command.

  • production.json affects the production environment, triggered in yarn build command.

Some environment variables are read and integrated in the template config object, including:

  • REACT_APP_PROVIDER_SOCKET overriding config[PROVIDER_SOCKET]

  • REACT_APP_DEVELOPMENT_KEYRING overriding config[DEVELOPMENT_KEYRING]

More on React environment variables.

When writing and deploying your own front end, you should configure:

  • CUSTOM_TYPES in src/config/common.json. See Extending types.

  • PROVIDER_SOCKET in src/config/production.json pointing to your own deployed node.

  • DEVELOPMENT_KEYRING in src/config/common.json be set to false. See Keyring.

Specifying Connecting Node

There are two ways to specify it:

  • With PROVIDER_SOCKET in {common, development, production}.json.

  • With rpc=<ws or wss connection> query parameter after the URL. This overrides the above setting.

Reusable Components

useSubstrate Custom Hook

The custom hook useSubstrate provides access to the Polkadot js API and thus the keyring and the blockchain itself. Specifically it exposes this API.

{
  socket,
  types,
  keyring,
  keyringState,
  api,
  apiState,
}
  • socket - The remote provider socket it is connecting to.

  • types - The custom types used in the connected node.

  • keyring - A keyring of accounts available to the user.

  • keyringState - One of "READY" or "ERROR" states. keyring is valid only when keyringState === "READY".

  • api - The remote api to the connected node.

  • apiState - One of "CONNECTING", "READY", or "ERROR" states. api is valid only when apiState === "READY".

TxButton Component

The TxButton handles basic query and transaction requests to the connected node. You can reuse this component for a wide variety of queries and transactions. See src/Transfer.js for a transaction example and src/ChainState.js for a query example.

Account Selector

The Account Selector provides the user with a unified way to select their account from a keyring. If the Balances module is installed in the runtime, it also displays the user's token balance. It is included in the template already.

Last updated