目标: 1. 运行一个Chainlink Oracle节点。 2. 创建oracle任务并进行测试。
一、 搭建Chainlink node系统服务
整体系统包含一个数据库服务以及节点服务。
- 运行数据库(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
- 运行节点服务
创建项目目录:
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的响应数据。
- 合约部署
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
- 添加白名单
节点系统回写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
"""
成功后如下:
至此,Oracle节点已配置完毕。
四、 发起Oracle请求
请求流程: 应用合约->link token 合约-> Operator合约-> node系统--抓取数据
响应流程:响应数据--node系统-->Operator合约 -> 应用合约
- 部署测试合约:[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
- 充值linktoken
由于发起oracle调用时,应用合约需要支付一定数量的linktoken。因此,需要向ATestnetConsumer合约进行预充值。
- 发起Oracle调用
调用ATestnetConsumer合约的方法requestEthereumPrice
进行Oracle请求,如下图:
参数:
_oracle: oracle合约地址
_jobId: 节点支持的job的唯一标识, 注意需要去掉字符"-"
url: 获取数据的目标url
Txhash: https://sepolia.etherscan.io/tx/0xea264ba394abf2d7a7fbe4a716906bae27945676b9c2f497aa976ee30227b2c0
发起成功后,节点系统会自动监听链事件以及执行相应的任务,成功执行后的截图如下:
Operator UI Job:
查看ATestnetConsumer合约收到oracle数据的,截图如下: