You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.16 11:36:04