【快速入门】本地Chainlink node搭建以及oracle功能测试

目标: 1. 运行一个Chainlink Oracle节点。 2. 创建oracle任务并进行测试。

一、 搭建Chainlink node系统服务

整体系统包含一个数据库服务以及节点服务。

    1. 运行数据库(PostgreSQL )

初始化密码为: mysecretpassword

docker方式:

docker run --name cl-postgres -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d postgres

docker-compose 方式:

挂载自定义创建的pgdata目录,用于数据持久化。

name: cl-postgres
services:
    postgres:
        container_name: cl-postgres
        environment:
            - POSTGRES_PASSWORD=mysecretpassword
        volumes:
            - pgdata:/var/lib/postgresql/data        
        ports:
            - 5432:5432

容器运行成功如下:

CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS         PORTS                   NAMES
066f300c0470   postgres   "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes  0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   cl-postgres
    1. 运行节点服务

创建项目目录:

mkdir chainlink-sepolia

创建文件:
config.toml

echo "[Log]
Level = 'warn'
[WebServer]
AllowOrigins = '\*'
SecureCookies = false
[WebServer.TLS]
HTTPSPort = 0
[[EVM]]
ChainID = '11155111'
[[EVM.Nodes]]
Name = 'Sepolia'
WSURL = 'wss://CHANGE_ME'
HTTPURL = 'https://CHANGE_ME'
" > config.toml

secrets.toml

echo "[Password]
Keystore = 'mysecretkeystorepassword'
[Database]
URL = 'postgresql://postgres:mysecretpassword@host.docker.internal:5432/postgres?sslmode=disable'
" > secrets.toml

.api(可选)

用于初始化节点系统的登录用户

echo "liang@zxl.com
CHANGE_THIS_EXAMPLE_PASSWORD
" > .api

启动节点服务

docker方式:

docker run --name chainlink -v ./chainlink-sepolia:/chainlink -it -p 6688:6688 --add-host=host.docker.internal:host-gateway smartcontract/chainlink:2.17.0 node -config /chainlink/config.toml -secrets /chainlink/secrets.toml start -a /chainlink/.api

docker-compose方式:

root@localhost chainlink-sepolia]# cat docker-compose.yml
name: chanlinknode
services:
    chainlink:
        platform: linux/x86_64/v8
        container_name: chainlink
        volumes:
            - ./:/chainlink
        stdin_open: true
        tty: true
        ports:
            - 6688:6688
        extra_hosts:
            - host.docker.internal:host-gateway
        image: smartcontract/chainlink:2.17.0
        command: node -config /chainlink/config.toml -secrets /chainlink/secrets.toml
            start -a /chainlink/.api
[root@localhost chainlink-sepolia]# 

运行成功后如下:

CONTAINER ID   IMAGE                           COMMAND                  CREATED         STATUS                   PORTS                                       NAMES
f19ed18f4fc5   smartcontract/chainlink:2.17.0   "chainlink node -con…"   37 seconds ago   Up 35 seconds (healthy)   0.0.0.0:6688->6688/tcp, :::6688->6688/tcp   chainlink

访问系统UI地址:http://localhost:6688 进行操作。

二、 部署Oracle 合约(operator )

用于实现oracle功能,即接收应用的oracle请求数据,以及向应用回传oracle的响应数据。

    1. 合约部署
      Operator.sol内容如下:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@chainlink/contracts@1.2.0/src/v0.8/operatorforwarder/Operator.sol";

地址: 0x12f2218d29AF3D2C1592dC80B45a5923c6E4F5B7
Txhash: https://sepolia.etherscan.io/tx/0x9b031e41beb1badb7f380a9ee7be4de8ed8bddcb331993c1672f1910773086ff

    1. 添加白名单

节点系统回写Oracle数据时至合约时需要白名单里的sender才可以调用。

在节点系统Operator UI里找到相应的sender地址,并调用合约方法(setAuthorizedSenders(address[] senders)进行白名单的添加。

钱包地址:0x92C03C2EB8689D0a5ADAf6d8cB537E1c6965e392
Txhash: https://sepolia.etherscan.io/tx/0x69571cd1746b68185fdd6fccb5cbf131bb66c24ca85ba12f670721b4ba7b080c

三、 添加一个Oracle Job

用于自动执行oracle功能。

在UI里的Jobs菜单里创建一个示例Job:

# THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY.
# THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE.
# DO NOT USE THIS CODE IN PRODUCTION.

name = "Get > Uint256 - (TOML)"
schemaVersion = 1
type = "directrequest"
# evmChainID for Sepolia Testnet
evmChainID = "11155111"
# Optional External Job ID: Automatically generated if unspecified
# externalJobID = "b1d42cd5-4a3a-4200-b1f7-25a68e48aad8"
contractAddress = "0x12f2218d29AF3D2C1592dC80B45a5923c6E4F5B7"
maxTaskDuration = "0s"
minIncomingConfirmations = 0
observationSource = """
    decode_log   [type="ethabidecodelog"
                  abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)"
                  data="$(jobRun.logData)"
                  topics="$(jobRun.logTopics)"]

    decode_cbor  [type="cborparse" data="$(decode_log.data)"]
    fetch        [type="http" method=GET url="$(decode_cbor.get)" allowUnrestrictedNetworkAccess="true"]
    parse        [type="jsonparse" path="$(decode_cbor.path)" data="$(fetch)"]

    multiply     [type="multiply" input="$(parse)" times="$(decode_cbor.times)"]

    encode_data  [type="ethabiencode" abi="(bytes32 requestId, uint256 value)" data="{ \\"requestId\\": $(decode_log.requestId), \\"value\\": $(multiply) }"]
    encode_tx    [type="ethabiencode"
                  abi="fulfillOracleRequest2(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes calldata data)"
                  data="{\\"requestId\\": $(decode_log.requestId), \\"payment\\":   $(decode_log.payment), \\"callbackAddress\\": $(decode_log.callbackAddr), \\"callbackFunctionId\\": $(decode_log.callbackFunctionId), \\"expiration\\": $(decode_log.cancelExpiration), \\"data\\": $(encode_data)}"
                  ]
    submit_tx    [type="ethtx" to="0x12f2218d29AF3D2C1592dC80B45a5923c6E4F5B7" data="$(encode_tx)"]

    decode_log -> decode_cbor -> fetch -> parse -> multiply -> encode_data -> encode_tx -> submit_tx
"""

成功后如下:


image.png

至此,Oracle节点已配置完毕。

四、 发起Oracle请求

请求流程: 应用合约->link token 合约-> Operator合约-> node系统--抓取数据
响应流程:响应数据--node系统-->Operator合约 -> 应用合约

  1. 部署测试合约:[ATestnetConsumer]
    ATestnetConsumer.sol 内容如下:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {Chainlink, ChainlinkClient} from "@chainlink/contracts@1.2.0/src/v0.8/ChainlinkClient.sol";
import {ConfirmedOwner} from "@chainlink/contracts@1.2.0/src/v0.8/shared/access/ConfirmedOwner.sol";
import {LinkTokenInterface} from "@chainlink/contracts@1.2.0/src/v0.8/shared/interfaces/LinkTokenInterface.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract ATestnetConsumer is ChainlinkClient, ConfirmedOwner {
    using Chainlink for Chainlink.Request;

    uint256 private constant ORACLE_PAYMENT = (1 * LINK_DIVISIBILITY) / 10; // 0.1 * 10**18
    uint256 public currentPrice;

    event RequestEthereumPriceFulfilled(
        bytes32 indexed requestId,
        uint256 indexed price
    );

    /**
     *  Sepolia
     *@dev LINK address in Sepolia network: 0x779877A7B0D9E8603169DdbD7836e478b4624789
     * @dev Check https://docs.chain.link/docs/link-token-contracts/ for LINK address for the right network
     */
    constructor() ConfirmedOwner(msg.sender) {
        _setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789);
    }

    function requestEthereumPrice(
        address _oracle,
        string memory _jobId,
        string memory url
    ) public onlyOwner {
        Chainlink.Request memory req = _buildChainlinkRequest(
            stringToBytes32(_jobId),
            address(this),
            this.fulfillEthereumPrice.selector
        );
        req._add(
            "get",
            //"https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD"
            url
        );
        req._add("path", "USD");
        req._addInt("times", 100);
        _sendChainlinkRequestTo(_oracle, req, ORACLE_PAYMENT);
    }

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

    function getChainlinkToken() public view returns (address) {
        return _chainlinkTokenAddress();
    }

    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(_chainlinkTokenAddress());
        require(
            link.transfer(msg.sender, link.balanceOf(address(this))),
            "Unable to transfer"
        );
    }

    function cancelRequest(
        bytes32 _requestId,
        uint256 _payment,
        bytes4 _callbackFunctionId,
        uint256 _expiration
    ) public onlyOwner {
        _cancelChainlinkRequest(
            _requestId,
            _payment,
            _callbackFunctionId,
            _expiration
        );
    }

    function stringToBytes32(
        string memory source
    ) private pure returns (bytes32 result) {
        bytes memory tempEmptyStringTest = bytes(source);
        if (tempEmptyStringTest.length == 0) {
            return 0x0;
        }

        assembly {
            // solhint-disable-line no-inline-assembly
            result := mload(add(source, 32))
        }
    }
}

合约地址:0xE1518D16F98b8f9502a19c41d8A9fA16E36d27Ef

  1. 充值linktoken

由于发起oracle调用时,应用合约需要支付一定数量的linktoken。因此,需要向ATestnetConsumer合约进行预充值。

  1. 发起Oracle调用

调用ATestnetConsumer合约的方法requestEthereumPrice 进行Oracle请求,如下图:

image.png

参数:
_oracle: oracle合约地址
_jobId: 节点支持的job的唯一标识, 注意需要去掉字符"-"
url: 获取数据的目标url

Txhash: https://sepolia.etherscan.io/tx/0xea264ba394abf2d7a7fbe4a716906bae27945676b9c2f497aa976ee30227b2c0

发起成功后,节点系统会自动监听链事件以及执行相应的任务,成功执行后的截图如下:
Operator UI Job:


image.png

查看ATestnetConsumer合约收到oracle数据的,截图如下:


image.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容