Skip to content

InjectiveLabs/chainlink-injective

Repository files navigation

chainlink-injective

OCR2 median reporting plugin integration demo with libocr2 and the OCR Cosmos module, tailored for the Injective Chain.

Below are the instructions on running this oracle node.

Prepare the environment

Step 1

Clone https://github.com/InjectiveLabs/injective-core on branch dev and build the chain node. It will have the OCR chain module that is ready to accept transmissions. Installing the node is as simple as running make install in the corresponding repo.

Step 2

Finally, clone https://github.com/InjectiveLabs/chainlink-injective (this repo on master) and let's run it. Make sure injectived executable is available on your system. Also jq is required.

Step 3

Install Docker. Run Chainlink Node and its Web UI

> docker-compose -f test/docker-compose.yml up -d

# check the logs of the node
> docker logs -f test_chainlink-node_1

Open http://localhost:6688 in your browser to view the Chainlink Web UI. You can enter the following credentials:

username: test@test
password: test_test

Step 4

Add external adapters bridges (or using Web UI / CLI) for all 4 oracles:

> ./test/test_init_bridges.sh

Adding Bridge 'injective-ea0' (http://host.docker.internal:8866) to Chainlink node
Bridge has been added to Chainlink node
Done adding Bridge 'injective-ea0'

Adding Bridge 'injective-ea1' (http://host.docker.internal:8867) to Chainlink node
Bridge has been added to Chainlink node
Done adding Bridge 'injective-ea1'

Adding Bridge 'injective-ea2' (http://host.docker.internal:8868) to Chainlink node
Bridge has been added to Chainlink node
Done adding Bridge 'injective-ea2'

Adding Bridge 'injective-ea3' (http://host.docker.internal:8869) to Chainlink node
Bridge has been added to Chainlink node
Done adding Bridge 'injective-ea3'

Step 5

According to Adding External Initiators to Nodes, let's add our OCR2 oracle as an external initiator to the Chainlink Node. This can be achieved using a CLI tool, but there is a quicker script that uses the API.

> ./test/test_init_eis.sh

Adding External Initiator 'injective-ei0' (http://host.docker.internal:8866) to Chainlink node...
EI has been added to Chainlink node
Done adding EI 'injective-ei0'

Adding External Initiator 'injective-ei1' (http://host.docker.internal:8867) to Chainlink node...
EI has been added to Chainlink node
Done adding EI 'injective-ei1'

Adding External Initiator 'injective-ei2' (http://host.docker.internal:8868) to Chainlink node...
EI has been added to Chainlink node
Done adding EI 'injective-ei2'

Adding External Initiator 'injective-ei3' (http://host.docker.internal:8869) to Chainlink node...
EI has been added to Chainlink node
Done adding EI 'injective-ei3'

It will save 4 generated files with CI/IC credentials into

./test/oracles/oracle0/external_initiator_injective-ei0.env
./test/oracles/oracle1/external_initiator_injective-ei1.env
./test/oracles/oracle2/external_initiator_injective-ei2.env
./test/oracles/oracle3/external_initiator_injective-ei3.env

Later they will be loaded by test_oracles_start.sh script.

Running a network with 3-node consensus

Start a mock network of 3 injectived nodes with proper consensus, governance and some pre-baked accounts with balances.

> CLEANUP=1 ./test/e2e_multinode.sh injectived

Omit CLEANUP=1 when running this command second time, it restarts the chain (all 3 nodes). If you include it again, it will wipe state, then restart. The last argument is the path to the node binary, can be arbitrary to $GOHOME/bin/injectived or where it was installed.

Starting nodes...
Waiting for chains to start...

Logs:
  * tail -f ./var/data/injective-1.n0.log
  * tail -f ./var/data/injective-1.n1.log
  * tail -f ./var/data/injective-1.n2.log

Env for easy access:
export H1='--home ./var/data/injective-1/n0/'
export H2='--home ./var/data/injective-1/n1/'
export H3='--home ./var/data/injective-1/n2/'

Command Line Access:
  * injectived --home ./var/data/injective-1/n0 status
  * injectived --home ./var/data/injective-1/n1 status
  * injectived --home ./var/data/injective-1/n2 status

You can use tail -f ./var/data/injective-1.n0.log to tail the logs of the first node, that listens on :9900 (main GRPC interface for clients) and :26657 (Tendermint Core low-level RPC). Both will be used for our ocr-pricefeed example and are specified in the env config.

Running migrations

Since everything has been pre-baked already, even the Oracle's private key, we have nothing to migrate except make some SetConfig proposals. Voting time on the mock chain is set to 5 seconds, so the voting will pass quickly.

Run migrations with:

> make test

Running Suite: Injective/Cosmos OCR module E2E Test Suite
=========================================================
Random Seed: 1633702647
Will run 1 of 1 specs

OCR Feed Configs Proposals to set configs
  Submits Governance Proposals and Funds Feed Reward Pool
  /Users/xlab/Documents/dev/InjectiveLabs/chainlink-injective/test/e2e/ocr_configs.go:81

• [SLOW TEST:18.322 seconds]
OCR Feed Configs
/Users/xlab/Documents/dev/InjectiveLabs/chainlink-injective/test/e2e/ocr_configs.go:20
  Proposals to set configs
  /Users/xlab/Documents/dev/InjectiveLabs/chainlink-injective/test/e2e/ocr_configs.go:21
    Submits Governance Proposals and Funds Feed Reward Pool
    /Users/xlab/Documents/dev/InjectiveLabs/chainlink-injective/test/e2e/ocr_configs.go:81
------------------------------

Ran 1 of 1 Specs in 18.327 seconds
SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS

The created proposals will set a config for both feeds (LINK/USDC, INJ/USDC) and authrize our test oracles to sign and transmit reports onchain.

Verifying on-chain state

After the chain consensus has been started and migrations completed, the module must contain 2 feeds ready to receive transmissions. To query the chain state of the OCR module from CLI, use the following commands:

> injectived --home ./var/data/injective-1/n0 q ocr

Usage:
  injectived query ocr [flags]
  injectived query ocr [command]

Available Commands:
  feed-config         Gets ocr feed config
  feed-config-info    Gets ocr feed config info
  latest-round        Gets ocr latest round by feed id.
  latest-transmission Gets ocr latest transmission details by feed id.
  module-state        Gets ocr module state.
  owed-amount         Gets owed amount by transmitter address.
  params              Gets ocr params

For example, get the feed config for LINK/USDC feed:

> injectived --home ./var/data/injective-1/n0 q ocr feed-config LINK/USDC
feed_config:
  f: 1
  module_params:
    billing_admin: ""
    description: LINK/USDC Feed
    feed_admin: ""
    feed_id: LINK/USDC
    link_denom: peggy0x514910771AF9Ca656af840dff83E8264EcF986CA
    link_per_observation: "10"
    link_per_transmission: "69"
    max_answer: "99999999999999999.000000000000000000"
    min_answer: "0.000000000000000001"
    unique_reports: false
  offchain_config: CICg2eYdEIDkl9ASGIDkl9ASIIC8wZYLKIDkl9ASMP4BOgQBAQEBQiA1xYd9JqzdrbTZFe37XGakJ6PO2DKCkhWa/JCYDRRcXEIgyGOsc7xyDHmzTLBT2Bqb3yxwlPcxT/MubKbqdRnaIgpCILcyCNCyP4LCCxDvZZv/zqcTfkBM4xz0TPfmZWsGxuvSQiBqEloiNpBcFmFZd7bQsFmxmFfV+hDSUuhfn1gHigJHCko0MTJEM0tvb1dFb3k0S3JQM3V3ZDR1Wm1ERkJmS3VyMkY1elNOVFZNU3d5bVE5aU5DRnQ3Wko0MTJEM0tvb1dIZ29La3phTkdLWUszOVBNanlIM3RQQngxaURIbUVIenJCQ211S2huNEM4Rko0MTJEM0tvb1dKTFJYN04xYVAxWFNTN3ZIemlyZWVCY3M3bTlLdjMyMUZxWENDUGN3QjJQMko0MTJEM0tvb1dUMm1QYTVvbnFYR2tpY3ZhUVVIU1c2ZDZBVldjNUNMcXhNU1FUZlFDRGdjcVIQEICt4gQggK3iBCiAyK+gJViA8ouoCWCA8ouoCWiA8ouoCXCA8ouoCXiA8ouoCYIBjAEKIMHwJZ/qY+JBiwXbDkUKYFj1+ucexJlht/ogU1aUKoIWEiAaG/dTwlBdq1IEsoTx/v19xOuD9jBGZMnC9Dy/QlZmYBoQcbGYD26K+CjDtSQkDI1MjxoQGIWmae/UnJO5bWRb5VD8VRoQEt+NJj5AFhYMQhmheTFPvxoQfSpuEtwekIegGHx4B+U3ow==
  offchain_config_version: "2"
  onchain_config: AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAABNCYXLHTYIdptk0WJwAAA==
  signers:
  - inj1s4d8ygx4ej9k5wkge00uhcmdzd44udmfx98g78
  - inj1zm0y9tdptfxtkc86f3hsuhk74fx2j2sylyd57d
  - inj1u34x223x5y9fr3d09kyupycuqya8mlms2j5kua
  - inj1555f842w0jfdns23n0z466jtjdlhj6xv3c267k
  transmitters:
  - inj1s4d8ygx4ej9k5wkge00uhcmdzd44udmfx98g78
  - inj1zm0y9tdptfxtkc86f3hsuhk74fx2j2sylyd57d
  - inj1u34x223x5y9fr3d09kyupycuqya8mlms2j5kua
  - inj1555f842w0jfdns23n0z466jtjdlhj6xv3c267k
feed_config_info:
  config_count: "1"
  f: 1
  latest_config_block_number: "37"
  latest_config_digest: AALzp3/8+dhfITFitsGbqt3JuOWnFUuySdxKoI/6VW4=
  "n": 4

We use 4 oracles there because N=4 > F*3.

Running OCR2 oracle

Install the binary by running make install, it will make injective-ocr2 available on your system, or at least in Go home bin.

Usage: injective-ocr2 [OPTIONS] COMMAND [arg...]

Injective OCR2 compatible oracle and external adapter for Chainlink Node.

Options:
  -e, --env                The environment name this app runs in. Used for metrics and error reporting. (env $ORACLE_ENV) (default "local")
  -l, --log-level          Available levels: error, warn, info, debug. (env $ORACLE_LOG_LEVEL) (default "info")
      --svc-wait-timeout   Standard wait timeout for external services (e.g. Cosmos daemon GRPC connection) (env $ORACLE_SERVICE_WAIT_TIMEOUT) (default "1m")

Commands:
  start                    Starts the OCR2 service.
  keys                     Keys management.
  version                  Print the version information and exit.

Make sure PostgreSQL databases created

In a PostgreSQL-enabled console run:

> createdb ocr2-oracle0
> createdb ocr2-oracle1
> createdb ocr2-oracle2
> createdb ocr2-oracle3

Run ALL 4 oracle instances at once:

> ./test/test_oracles_start.sh

[start] running 4 oracles
[post-start]
Logs:
  * tail -f ./var/oracles/oracle0.log
  * tail -f ./var/oracles/oracle1.log
  * tail -f ./var/oracles/oracle2.log
  * tail -f ./var/oracles/oracle3.log

Stopping:
  * ./test/test_stop_oracles.sh

Monitor the logs of the first oracle (the bridge that will be used in Job spec):

> tail -f ./var/oracles/oracle0.log

time="2021-10-08T17:28:26+03:00" level=info msg="Using Cosmos Sender inj1s4d8ygx4ej9k5wkge00uhcmdzd44udmfx98g78"
time="2021-10-08T17:28:26+03:00" level=info msg="Waiting for GRPC services"
time="2021-10-08T17:28:28+03:00" level=info msg="Using PeerID 12D3KooWEoy4KrP3uwd4uZmDFBfKur2F5zSNTVMSwymQ9iNCFt7Z for P2P identity"
time="2021-10-08T17:28:28+03:00" level=info msg="Using OCR2 key ID 013208ee22ef424aa5d3a5abc3784459d8d72f6d602bbd19a94b626f8c9d932b"

[GIN-debug] GET    /health                   --> github.com/InjectiveLabs/chainlink-injective/api.handleShowHealth.func1 (3 handlers)
[GIN-debug] POST   /runs                     --> github.com/InjectiveLabs/chainlink-injective/api.(*httpServer).handleJobRun.func1 (3 handlers)
[GIN-debug] POST   /jobs                     --> github.com/InjectiveLabs/chainlink-injective/api.(*httpServer).handleJobCreate.func1 (4 handlers)
[GIN-debug] DELETE /jobs/:jobid              --> github.com/InjectiveLabs/chainlink-injective/api.(*httpServer).handleJobStop.func1 (4 handlers)

Double check that all 4 external initiators (injective-ei*) are actually registered within Chainlink Node, as they are not being displayed under Bridges tab in Web UI.

> docker exec -it test_chainlink-node_1 /bin/bash
#
# Inside test_chainlink-node_1 container instance:
> chainlink admin login --file /run/secrets/apicredentials
> chainlink initiators list

╔ External Initiators:
╬════╬═══════════════╬═══════════════════════════════════════╬══════════════════════════════════╬══════════════════════════════════════════════════════════════════╬════════════════════════════════╬════════════════════════════════╬
║ ID ║     NAME      ║                  URL                  ║            ACCESSKEY             ║                          OUTGOINGTOKEN                           ║           CREATEDAT            ║           UPDATEDAT            ║
╬════╬═══════════════╬═══════════════════════════════════════╬══════════════════════════════════╬══════════════════════════════════════════════════════════════════╬════════════════════════════════╬════════════════════════════════╬
║  1 ║ injective-ei0 ║ http://host.docker.internal:8866/jobs ║ 90c2142bfa0c4573bcff7955c32c8b17 ║ Y2HGB4rkqxMQvRoJbvkTVkkM1N2ni+4rS7JUEhP6ha9g1a0X6FCKH+4sV0TS1drP ║ 2021-10-08 12:25:10.89611      ║ 2021-10-08 12:25:10.89611      ║
║    ║               ║                                       ║                                  ║                                                                  ║ +0000 UTC                      ║ +0000 UTC                      ║
╬════╬═══════════════╬═══════════════════════════════════════╬══════════════════════════════════╬══════════════════════════════════════════════════════════════════╬════════════════════════════════╬════════════════════════════════╬
║  2 ║ injective-ei1 ║ http://host.docker.internal:8867/jobs ║ 6161f044fa644d968752079b03111daa ║ I2zfNdIBfdxxraCX1LU+RV+uE5xCvgEE5/q/X1imbpzJp8192FGPyq/hbEJIa9Mk ║ 2021-10-08 12:25:11.131688     ║ 2021-10-08 12:25:11.131688     ║
║    ║               ║                                       ║                                  ║                                                                  ║ +0000 UTC                      ║ +0000 UTC                      ║
╬════╬═══════════════╬═══════════════════════════════════════╬══════════════════════════════════╬══════════════════════════════════════════════════════════════════╬════════════════════════════════╬════════════════════════════════╬
║  3 ║ injective-ei2 ║ http://host.docker.internal:8868/jobs ║ 437fa2ca57364d2eb4f2790b8984f4db ║ MczBesfIBdGp/0UkjRjjtQ0Q3IdzUTxXRouYznPuFwPrqOizH4ErQ+gVcnd6ogRe ║ 2021-10-08 12:25:11.443282     ║ 2021-10-08 12:25:11.443282     ║
║    ║               ║                                       ║                                  ║                                                                  ║ +0000 UTC                      ║ +0000 UTC                      ║
╬════╬═══════════════╬═══════════════════════════════════════╬══════════════════════════════════╬══════════════════════════════════════════════════════════════════╬════════════════════════════════╬════════════════════════════════╬
║  4 ║ injective-ei3 ║ http://host.docker.internal:8869/jobs ║ 8b80a95cb2f94c60a580bf287b8edde3 ║ FEkxJEDsGPoVy1gWJFpShZOdJIyI+SDSX1T9vYkITRKI3yeOJL2DbUuogSetOMUk ║ 2021-10-08 12:25:11.773275     ║ 2021-10-08 12:25:11.773275     ║
║    ║               ║                                       ║                                  ║                                                                  ║ +0000 UTC                      ║ +0000 UTC                      ║
╬════╬═══════════════╬═══════════════════════════════════════╬══════════════════════════════════╬══════════════════════════════════════════════════════════════════╬════════════════════════════════╬════════════════════════════════╬

> chainlink bridges list
╔ Bridges
╬═══════════════╬═══════════════════════════════════════╬═══════════════╬
║     NAME      ║                  URL                  ║ CONFIRMATIONS ║
╬═══════════════╬═══════════════════════════════════════╬═══════════════╬
║ injective-ea0 ║ http://host.docker.internal:8866/runs ║             0 ║
╬═══════════════╬═══════════════════════════════════════╬═══════════════╬
║ injective-ea1 ║ http://host.docker.internal:8867/runs ║             0 ║
╬═══════════════╬═══════════════════════════════════════╬═══════════════╬
║ injective-ea2 ║ http://host.docker.internal:8868/runs ║             0 ║
╬═══════════════╬═══════════════════════════════════════╬═══════════════╬
║ injective-ea3 ║ http://host.docker.internal:8869/runs ║             0 ║
╬═══════════════╬═══════════════════════════════════════╬═══════════════╬

It's time to schedule a new Job. There are multiple ways to create Jobs in Chainlink node, either via Web UI or API. We will use the following Job spec but adapted for each oracle, so there will be 4 jobs for injective-ei{1-4}/injective-ea{1-4} correspondingly:

type            = "webhook"
schemaVersion   = 1

externalInitiators = [
  { name = "injective-ei0", spec = "{\"feedId\": \"LINK/USDC\",\"p2pBootstrapPeers\": [\"12D3KooWEoy4KrP3uwd4uZmDFBfKur2F5zSNTVMSwymQ9iNCFt7Z@127.0.0.1:4466\"],\"isBootstrapPeer\": false,\"keyID\": \"013208ee22ef424aa5d3a5abc3784459d8d72f6d602bbd19a94b626f8c9d932b\",\"observationTimeout\": \"10s\",\"blockchainTimeout\": \"10s\",\"contractConfigConfirmations\": 1}" },
]

observationSource   = """
   ticker [type=http method=GET url="https://api.binance.com/api/v3/ticker/price?symbol=LINKUSDC"];
   parsePrice [type="jsonparse" path="price"]
   multiplyDecimals [type="multiply" times=1000000]
   sendToBridge [type=bridge name="injective-ea0" requestData=<{"jobID":$(jobSpec.externalJobID), "result":$(multiplyDecimals)}>]

   ticker -> parsePrice -> multiplyDecimals -> sendToBridge
"""

To add all 4 Jobs automatically:

> ./test/test_jobs_start.sh

Starting job 'job_linkusdc_ei0' (./test/jobs/job_linkusdc_ei0.toml) via Chainlink node
Job has been added via Chainlink node
Done adding Job 'job_linkusdc_ei0'

Starting job 'job_linkusdc_ei1' (./test/jobs/job_linkusdc_ei1.toml) via Chainlink node
Job has been added via Chainlink node
Done adding Job 'job_linkusdc_ei1'

Starting job 'job_linkusdc_ei2' (./test/jobs/job_linkusdc_ei2.toml) via Chainlink node
Job has been added via Chainlink node
Done adding Job 'job_linkusdc_ei2'

Starting job 'job_linkusdc_ei3' (./test/jobs/job_linkusdc_ei3.toml) via Chainlink node
Job has been added via Chainlink node
Done adding Job 'job_linkusdc_ei3'

Each oracle node will spawn its own OCR2 peer! They will be comunicating and the elected transmitters will send the median result to the chain. Make sure to check jobs logs:

Logs:
  * tail -f ./var/oracles/oracle0.log
  * tail -f ./var/oracles/oracle1.log
  * tail -f ./var/oracles/oracle2.log
  * tail -f ./var/oracles/oracle3.log

And you can query onchain state using on of the CLI subcommands on the chain client:

> injectived --home ./var/data/injective-1/n0 q ocr latest-transmission LINK/USDC
config_digest: AAKocozoxdRepNyjfzFr9pBpJoTzQ0IT3Wl1efeXa7E=
data:
  answer: "27480000.000000000000000000"
  observations_timestamp: "1633819742"
  transmission_timestamp: "1633819745"
epoch_and_round:
  epoch: "45"
  round: "1"

> injectived --home ./var/data/injective-1/n0 q ocr latest-transmission LINK/USDC
config_digest: AAKocozoxdRepNyjfzFr9pBpJoTzQ0IT3Wl1efeXa7E=
data:
  answer: "27550000.000000000000000000"
  observations_timestamp: "1633820296"
  transmission_timestamp: "1633820299"
epoch_and_round:
  epoch: "93"
  round: "1"

It works! 🎉

About

Repo with Chainlink <> Injective OCR2 integration components and oracle

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages