github.com/diadata-org/diadata@v1.4.593/documentation/oracle-documentation/polkadot-medianizer.md (about)

     1  ---
     2  description: >-
     3    This example demonstrates the functionality of the Laminar chain's oracle
     4    pallet including the aggregation of price values.
     5  ---
     6  
     7  # Polkadot Medianizer
     8  
     9  ### Laminar Chain
    10  
    11  The Laminar chain is built using the [Substrate framework](https://substrate.dev). In our example we are going to setup a network consisting if two nodes, one is operated by Alice and one is operated by Bob.
    12  
    13  #### Setup
    14  
    15  First we download the Laminar chain [sources](https://github.com/laminar-protocol/laminar-chain) and bootstrap the network. After that we execute the following statements in the chain's directory.
    16  
    17  ```text
    18  example$ git clone https://github.com/laminar-protocol/laminar-chain.git
    19  example$ cd laminar-chain
    20  example/laminar-chain$ make init
    21  example/laminar-chain$ make build
    22  ```
    23  
    24  `make init` will setup the environment by downloading and installing all necessary dependencies and the required toolchain.
    25  
    26  `make build` builds the chain's code so we can execute it.
    27  
    28  After that copy the `chainSpec.json` file from this repository into the chain's directory. More on the spec file will be explained a little bit later.
    29  
    30  #### Cleanup
    31  
    32  When running a node being part of the laminar chain network it creates and stores lots of data because it's constantly building new blocks and extending the actual blockchain. We are going to store all that data in a subdirectory of `/tmp` for each node \(its base directory\). If the base directory doesn't exist it will be created but it is necessary to purge all data of a specific node on subsequent launches:
    33  
    34  ```text
    35  example/laminar-chain$ SKIP_WASM_BUILD= cargo run -- purge-chain --base-path /tmp/alice
    36  ```
    37  
    38  This wipes the base directory of Alice's node. For wiping Bob's node's data adjust the base-path parameter accordingly. The action asks for confirmation which is given by answering with `y`.
    39  
    40  **Bootstrap the network**
    41  
    42  We are going to launch Alice's node first:
    43  
    44  ```text
    45  example/laminar-chain$ SKIP_WASM_BUILD= cargo run -- --base-path /tmp/alice --chain=chainSpec.json --alice --port 30333 --ws-port 9945 --rpc-port 9933
    46  ```
    47  
    48  * This creates and runs a node that stores its data in the specified base directory.
    49  * We also pass the chain specification file into the node. We use this file to configure the scenario in which all participating parties will own a sufficient amount of gas in order to be able to "pay" for their transmitted transactions.
    50  * The parameter `--alice` will make Alice the node's authority uses her predefined key pair.
    51  * We specify three ports: One for the other peers to connect to the node, a websocket port for the off-chain worker to connect to and a port for incoming RPCs.
    52  
    53  After that we fire up Bob's node and tell it to connect to Alice. So in another terminal session we are executing
    54  
    55  ```text
    56  example/laminar-chain$ SKIP_WASM_BUILD= cargo run -- --base-path /tmp/bob --chain=chainSpec.json --bob --port 30334 --ws-port 9946 --rpc-port 9934 --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp
    57  ```
    58  
    59  The main difference is the `bootnodes` parameter which specifies the network and identity information of Alice's node.
    60  
    61  #### Use the Laminar chain's oracle
    62  
    63  Now the two nodes start working together and our laminar chain network is ready for playing with the oracle.
    64  
    65  **About oracles**
    66  
    67  A blockchain oracle is an interface for the chain to the real world and vice versa. By feeding off-chain data to the oracle they enter the chain's scope and can get processed by smart contracts. In this case our oracle is called an **inbound oracle**. For our example imagine a smart contract that operates on currency prices. It might trigger an action as soon as the price for a specific currency exceeds a specific value. By feeding the current prices from external sources periodically to the oracle we provide the smart contract with the required input data on-chain.
    68  
    69  **Oracle pallet**
    70  
    71  A Substrate chain's runtime consists of modules called pallets. In the case of Laminar one of these pallets is our [oracle](https://github.com/open-web3-stack/open-runtime-module-library/tree/master/oracle). It can be interacted with in two ways:
    72  
    73  1. Feeding new key-value pairs to the oracle via signed transactions.
    74  2. Retrieving key-value pairs from the oracle either by other modules on-chain or by off-chain workers via RPC.
    75  
    76  The Laminar implementation configures the oracle pallet to use the type `CurrencyId` as key type. That means it can be one of the predefined values _FBTC_, _FETH_, _FEUR_, etc. The value uses the type `Price`.
    77  
    78  In this example we are going to use an off-chain worker to feed currency prices and fetch them afterwards.
    79  
    80  **Our off-chain worker**
    81  
    82  The component we create to simulate new price reports and oracle queries lives off-chain and will be written in Typescript. It is completely separate from anything stored on the Laminar chain itself. We rather interact with the chain through specific calls made using the [Polkadot API for JavaScript](https://polkadot.js.org/docs/api). This API defines the technical aspects of how we can interact with the chain \(and specifically with the oracle\), i. e. how to build transactions and make RPCs. For the specific parameters regarding the Laminar chain we also make use of the [Laminar chain JS SDK](https://github.com/laminar-protocol/laminar-chain.js).
    83  
    84  Let's start by switching the directory
    85  
    86  ```text
    87  example/laminar-chain$ cd ..
    88  example$ mkdir medianizer
    89  example/medianizer$ touch package.json
    90  ```
    91  
    92  and setting up the dependencies in the `package.json` file:
    93  
    94  ```text
    95  {
    96    "name": "medianizer-example",
    97    "version": "0.1.0",
    98    "description": "",
    99    "main": "index.ts",
   100    "scripts": {
   101      "test": "echo \"Error: no test specified\" && exit 1",
   102      "start": "node --require ts-node/register index.ts"
   103    },
   104    "dependencies": {
   105      "@laminar/api": "^0.2.0-beta.144",
   106      "@polkadot/api": "^3.8.1"
   107    },
   108    "devDependencies": {
   109      "ts-node": "^9.1.1",
   110      "typescript": "^4.1.5"
   111    },
   112    "keywords": [],
   113    "author": "",
   114    "license": "ISC"
   115  }
   116  ```
   117  
   118  Next step is coding the off-chain worker in the file `index.ts`. We import the required types
   119  
   120  ```text
   121  import { ApiPromise, WsProvider, Keyring } from "@polkadot/api";
   122  import { options } from "@laminar/api";
   123  ```
   124  
   125  and build the frame for the program:
   126  
   127  ```text
   128  const run = async () => {
   129    // Everything is going to happen here in this block ...
   130  };
   131  
   132  export default run;
   133  
   134  if (require.main === module) {
   135      run().then(() => process.exit(0))
   136  }
   137  ```
   138  
   139  Now we can run
   140  
   141  ```text
   142  example/medianizer$ yarn install
   143  example/medianizer$ yarn start
   144  ```
   145  
   146  for fetching the dependencies and launching the program. However nothing is going on right now so let's write the actual code. From now on we put everything into the async block that we just prepared.
   147  
   148  We start creating an API object:
   149  
   150  ```text
   151  const wsProvider = new WsProvider('ws://localhost:9945');
   152  const api = await ApiPromise.create({
   153      ...options({}),
   154      provider: wsProvider });
   155  ```
   156  
   157  Please note that our API object uses a websocket to communicate with the node. And this websocket connects to port 9945 which we have specified above when firing up Alice's node. So all the requests are submitted to her node.
   158  
   159  Next step is configuring the key material. We want to transmit signed transactions that contain the prices to be fed in the oracle. Therefore the off-chain worker needs to know the key pairs of Alice, Bob and Charlie \(yes, he's also going to take part in this example\). Usually the key pairs would be imported, e. g. from a file. But as for the chain nodes we use the predefined key material which is addressed by `//[Name]`. We derive those key pairs from the keyring we create specifying the key pair format SR25519.
   160  
   161  ```text
   162  const keyring = new Keyring({ type: 'sr25519'});
   163  
   164  const alice = keyring.addFromUri("//Alice");
   165  const bob = keyring.addFromUri("//Bob");
   166  const charlie = keyring.addFromUri("//Charlie");
   167  ```
   168  
   169  Alice wants to trigger a transaction that feeds the oracle. If we start typing `api.tx.` the code completion shows us the available extrinsics but the oracle isn't one of them. That's because the default methods of the Substrate runtime are known at compile time but not the custom chain specific extrinsics that might be part of the runtime. Let's find out what's really available by querying the node:
   170  
   171  ```text
   172  const transactions = await api.tx;
   173  console.log(transactions);
   174  ```
   175  
   176  When executing the program we get a list of much more actually available extrinsics - and one of them is `laminarOracle`. The list tells us even more about the oracle: the available query method `feedValues`. This is exactly what we needed to know.x
   177  
   178  Now Alice reports a current BTC price of 1, i. e. she feeds the oracle with the corresponding key-value pair:
   179  
   180  ```text
   181  await api.tx['laminarOracle'].feedValues([['FBTC', 1]] as any).signAndSend(alice);
   182  ```
   183  
   184  This line consists of several interesting pieces:
   185  
   186  1. We use the API object to trigger an extrinsic \(`tx` for transaction\). The available extrinsics depend on the used chain. In this case we address the oracle pallet of the laminar chain which is addressed by `laminarOracle`.
   187  2. On that extrinsic we call the method `feedValues` in order to submit key-value pairs that should be fed into the oracle. In this case we just submit a single key-value pair for the Bitcoin price.
   188  3. As this operation is a transaction that is going to be included in a block and costs Alice some gas it needs to be signed by her. This is what happens in the last part of the line.
   189  
   190  When this line returns the transaction has been finalized and we can now query the oracle with the current Bitcoin price. But there's again the question of how to do that. As mentioned earlier the oracle offers retrieving the current values via RPC. So let's find out the available RPCs:
   191  
   192  ```text
   193  const methods = await api.rpc.rpc.methods;
   194  console.log(methods);
   195  ```
   196  
   197  The output again gives us what we need. Two calls should catch our attention: `oracle_getAllValues` and `oracle_getValue`. That means we have the two functions `getAllValues` and `getValue` available for the RPC `oracle.`
   198  
   199  Now we query the current BTC price:
   200  
   201  ```text
   202  const result = await api.rpc['oracle'].getValue('Laminar', 'FBTC');
   203  console.log(`Current aggregated BTC price: ${result}`);
   204  ```
   205  
   206  As you can see we use an RPC as the query doesn't change the state of the chain and therefore the operation is free. We specify the correct RPC \(which is `oracle`\) and query the value to the key associated with the Bitcoin price \(which is `FBTC`\). The output is a pair of two values: The current price and the timestamp of the transaction that contained the feeding operation.
   207  
   208  That's it! Now we can run the program again with `yarn start`. The oracle will be fed and queried and the output is the expected BTC price of 1.
   209  
   210  **Data aggregation**
   211  
   212  In the previous section we fed the oracle with one single data point and queried the oracle to retrieve a value. It isn't surprising at all that we read the value that we had written into the oracle just before. But it requires a closer look to what value is actually returned by `getValue`. The oracle pallet states that this method returns a "combined value" which indicates that there is some data aggregation happening.
   213  
   214  In fact the way the prices of each currency are aggregated is entirely up to the chain implementation. Currently the Laminar chain doesn't implement any aggregation algorithm but the oracle pallet comes with a default implementation: It takes all fed values of a specific currency submitted within the last 3 days and returns the median value. If there aren't any values to calculate the median from it just returns the most recent value.
   215  
   216  Let's try it out. Disable the lines of the previous section that interacted with the oracle \(via tx and RPC\) and add the following:
   217  
   218  ```text
   219  const feedPrice = async (value: string, sender: any, completion: () => void) => {
   220      const unsub = await api.tx['laminarOracle'].feedValues([['FBTC', value]] as any).signAndSend(sender, (result) => {
   221          if (result.status.isInBlock) {
   222              console.debug(`Transaction included at blockHash ${result.status.asInBlock}`);
   223          } else if (result.status.isFinalized) {
   224              console.debug(`Transaction finalized at blockHash ${result.status.asFinalized}`);
   225              unsub();
   226              completion();
   227          }
   228      });
   229  };
   230  ```
   231  
   232  For more convenience we define a function `feedPrice` which takes a price value and a sender and triggers the transaction accordingly. We use the [subscription API](https://polkadot.js.org/docs/api/start/api.query.subs) of Polkadot JS in order to make sure that we wait for every transaction to be finalized in a block. Now our three users submit their values:
   233  
   234  ```text
   235  await new Promise<void>((resolve) => { feedPrice("1", alice, resolve); });
   236  await new Promise<void>((resolve) => { feedPrice("5", bob, resolve); });
   237  await new Promise<void>((resolve) => { feedPrice("10", charlie, resolve); });
   238  
   239  const result = await api.rpc['oracle'].getValue('Laminar', 'FBTC');
   240  console.log(result);
   241  ```
   242  
   243  When we query the oracle for the BTC price it returns the median value of 5 as expected.
   244  
   245  **Important!** Remember to purge the chain status and restart the two nodes before executing the code of this section.
   246  
   247  ### TL;DR - Quick demo
   248  
   249  ```text
   250  mkdir example
   251  cd example
   252  ```
   253  
   254  Download sources for the example and the laminar chain:
   255  
   256  ```text
   257  git clone https://github.com/diadata-org/medianizer-example.git
   258  git clone https://github.com/laminar-protocol/laminar-chain.git
   259  ```
   260  
   261  Setup laminar chain:
   262  
   263  ```text
   264  cd laminar-chain
   265  make init
   266  make build
   267  cp ../medianizer-example/chain/chainSpec.json .
   268  ```
   269  
   270  Run first chain node \(Answer with "y" when asked for confirmation before purging\):
   271  
   272  ```text
   273  SKIP_WASM_BUILD= cargo run -- purge-chain --base-path /tmp/alice
   274  
   275  SKIP_WASM_BUILD= cargo run -- --base-path /tmp/alice --chain=chainSpec.json --alice --port 30333 --ws-port 9945 --rpc-port 9933 --node-key 0000000000000000000000000000000000000000000000000000000000000001
   276  ```
   277  
   278  Run second chain node \(e.g. in another terminal session\):
   279  
   280  ```text
   281  SKIP_WASM_BUILD= cargo run -- purge-chain --base-path /tmp/bob
   282  
   283  SKIP_WASM_BUILD= cargo run -- --base-path /tmp/bob --chain=chainSpec.json --bob --port 30334 --ws-port 9946 --rpc-port 9934 --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp
   284  ```
   285  
   286  After both nodes have started to communicate with each other run medianizer example \(in yet another terminal session\):
   287  
   288  ```text
   289  cd ../medianizer-example
   290  yarn install
   291  yarn start
   292  ```
   293