【快速入门】本地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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容