如何在Chainlink智能合约中添加2个节点验证同一URL数据上链
Chainlink双节点数据验证实现方案
实现逻辑
- 替换原单oracle、单jobId配置为两组节点参数
- 新增请求批次结构体,存储同一批次下两个节点的请求ID、返回值、完成状态
- 发起请求时同时向两个节点发送参数完全相同的拉取请求,绑定到同一批次
- 回调阶段先校验调用方为授权的oracle节点,存储对应返回值,待两个节点返回值均到账且通过一致性校验后,才将最终结果写入链上公共变量
完整实现代码
pragma solidity ^0.6.0; import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol"; contract APIConsumer is ChainlinkClient { uint256 public volume; // 双节点配置 address private oracle1; address private oracle2; bytes32 private jobId1; bytes32 private jobId2; uint256 private fee; // 请求批次结构体,存储同一批次的两个请求相关信息 struct RequestBatch { bytes32 requestId1; bytes32 requestId2; uint256 response1; uint256 response2; bool isCompleted; } // 批次计数器 uint256 public batchCounter; // 批次ID映射批次详情 mapping(uint256 => RequestBatch) public batches; // 请求ID映射所属批次ID和请求序号 mapping(bytes32 => uint256[2]) public requestToBatch; /** * 网络:Kovan * 此处替换为你自己的两个节点oracle地址、对应jobId * 手续费:单请求0.1 LINK,单次双节点请求共消耗0.2 LINK */ constructor() public { setPublicChainlinkToken(); oracle1 = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e; // 替换为第一个节点oracle地址 jobId1 = "29fa9aa13bf1468788b7cc4a500a45b8"; // 替换为第一个节点对应jobId oracle2 = 0xYourSecondOracleAddressHere; // 替换为第二个节点oracle地址 jobId2 = "yourSecondJobIdHere"; // 替换为第二个节点对应jobId fee = 0.1 * 10**18; // 单请求0.1 LINK } /** * 同时向两个节点创建Chainlink请求拉取API响应 */ function requestVolumeData() public returns (uint256 batchId) { batchId = batchCounter++; RequestBatch storage batch = batches[batchId]; // 构建第一个节点的请求 Chainlink.Request memory request1 = buildChainlinkRequest(jobId1, address(this), this.fulfill.selector); request1.add( "get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD" ); request1.add("path", "RAW.ETH.USD.VOLUME24HOUR"); int256 timesAmount = 10**18; request1.addInt("times", timesAmount); bytes32 requestId1 = sendChainlinkRequestTo(oracle1, request1, fee); // 构建第二个节点的请求,参数完全一致 Chainlink.Request memory request2 = buildChainlinkRequest(jobId2, address(this), this.fulfill.selector); request2.add( "get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD" ); request2.add("path", "RAW.ETH.USD.VOLUME24HOUR"); request2.addInt("times", timesAmount); bytes32 requestId2 = sendChainlinkRequestTo(oracle2, request2, fee); // 存储批次信息 batch.requestId1 = requestId1; batch.requestId2 = requestId2; requestToBatch[requestId1] = [batchId, 1]; requestToBatch[requestId2] = [batchId, 2]; } /** * 接收uint256格式的响应结果,双节点均返回且数据一致才更新最终值 */ function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId) { // 校验调用方为授权的oracle节点 require(msg.sender == oracle1 || msg.sender == oracle2, "Invalid oracle caller"); // 获取请求所属批次 uint256[2] memory batchInfo = requestToBatch[_requestId]; uint256 batchId = batchInfo[0]; uint256 requestIndex = batchInfo[1]; RequestBatch storage batch = batches[batchId]; require(!batch.isCompleted, "Batch already completed"); // 存储对应请求的返回值 if (requestIndex == 1) { batch.response1 = _volume; } else { batch.response2 = _volume; } // 两个请求均已返回,校验一致性 if (batch.response1 != 0 && batch.response2 != 0) { // 可根据业务需求调整校验逻辑,比如允许小范围偏差 require(batch.response1 == batch.response2, "Responses from two nodes do not match"); volume = batch.response1; batch.isCompleted = true; } } }
注意事项
- 构造函数中的
oracle2和jobId2占位需要替换为你实际使用的第二个节点的对应参数 - 若业务允许两个节点返回值存在小范围误差,可以将一致性校验逻辑修改为差值小于指定阈值即判定有效
- 单次请求需要保证合约内有至少0.2 LINK的余额,用于支付两个节点的响应手续费
内容的提问来源于stack exchange,提问作者akhil
相关产品推荐
相关产品推荐

