从零到一:构建你的第一个去中心化预言机服务

8 阅读1分钟

在区块链的世界里,智能合约无法直接访问链外数据,这就像一台没有互联网连接的电脑——功能强大却信息闭塞。预言机(Oracle)正是连接区块链与现实世界的桥梁,而今天,我将带你从零开始构建一个去中心化预言机服务。

为什么我们需要去中心化预言机?

传统中心化预言机存在单点故障风险,一旦预言机节点被攻击或宕机,依赖它的所有智能合约都会受到影响。去中心化预言机通过多节点共识机制,确保数据源的可靠性和抗攻击性。

想象一下:一个去中心化保险合约需要获取天气数据来触发赔付。如果只有一个数据源,恶意攻击者可能伪造数据获取不当利益。而多个独立节点从不同API获取数据并达成共识,就能极大提高系统的安全性。

架构设计:三层预言机网络

我们的去中心化预言机将采用三层架构:

  1. 数据源层:多个外部API接口
  2. 共识层:预言机节点网络
  3. 合约层:链上聚合合约
// 预言机核心合约
pragma solidity ^0.8.0;

contract DecentralizedOracle {
    struct DataRequest {
        string query;
        uint256 minResponses;
        uint256 expiration;
        bool fulfilled;
        bytes32[] responses;
        address[] responders;
    }
    
    struct Node {
        address nodeAddress;
        uint256 stake;
        uint256 reputation;
        bool isActive;
    }
    
    mapping(bytes32 => DataRequest) public requests;
    mapping(address => Node) public nodes;
    address[] public activeNodes;
    
    uint256 public minimumStake = 1 ether;
    uint256 public responseTimeout = 5 minutes;
    
    event RequestCreated(bytes32 requestId, string query);
    event ResponseSubmitted(bytes32 requestId, address node, bytes32 data);
    request Fulfilled(bytes32 requestId, bytes32 finalData);
    
    // 注册预言机节点
    function registerNode() external payable {
        require(msg.value >= minimumStake, "Insufficient stake");
        require(!nodes[msg.sender].isActive, "Already registered");
        
        nodes[msg.sender] = Node({
            nodeAddress: msg.sender,
            stake: msg.value,
            reputation: 100,
            isActive: true
        });
        
        activeNodes.push(msg.sender);
    }
    
    // 创建数据请求
    function createRequest(
        string memory _query,
        uint256 _minResponses
    ) external returns (bytes32) {
        require(_minResponses <= activeNodes.length, "Not enough nodes");
        
        bytes32 requestId = keccak256(abi.encodePacked(
            _query,
            block.timestamp,
            msg.sender
        ));
        
        requests[requestId] = DataRequest({
            query: _query,
            minResponses: _minResponses,
            expiration: block.timestamp + responseTimeout,
            fulfilled: false,
            responses: new bytes32[](0),
            responders: new address[](0)
        });
        
        emit RequestCreated(requestId, _query);
        return requestId;
    }
}

实现预言机节点

预言机节点需要定期监控链上请求,获取外部数据并提交到区块链。以下是使用Node.js实现的基础节点:

// oracle-node/index.js
const Web3 = require('web3');
const axios = require('axios');
const config = require('./config.json');

class OracleNode {
    constructor() {
        this.web3 = new Web3(config.rpcUrl);
        this.contract = new this.web3.eth.Contract(
            config.contractABI,
            config.contractAddress
        );
        this.privateKey = config.privateKey;
        this.account = this.web3.eth.accounts.privateKeyToAccount(
            this.privateKey
        );
        this.web3.eth.accounts.wallet.add(this.account);
    }
    
    // 监控新请求
    async monitorRequests() {
        console.log('开始监控预言机请求...');
        
        this.contract.events.RequestCreated({
            fromBlock: 'latest'
        })
        .on('data', async (event) => {
            const requestId = event.returnValues.requestId;
            const query = event.returnValues.query;
            
            console.log(`收到新请求: ${requestId}, 查询: ${query}`);
            
            // 获取外部数据
            const data = await this.fetchExternalData(query);
            
            // 提交响应
            await this.submitResponse(requestId, data);
        })
        .on('error', (error) => {
            console.error('监控错误:', error);
        });
    }
    
    // 从外部API获取数据
    async fetchExternalData(query) {
        try {
            // 解析查询参数
            const params = this.parseQuery(query);
            
            // 从多个数据源获取数据
            const promises = config.dataSources.map(source => 
                this.queryDataSource(source, params)
            );
            
            const results = await Promise.allSettled(promises);
            
            // 验证数据一致性
            const validResults = results
                .filter(r => r.status === 'fulfilled')
                .map(r => r.value);
                
            if (validResults.length < config.minConsensus) {
                throw new Error('未达成数据共识');
            }
            
            // 计算中位数(数值数据)或多数共识(分类数据)
            return this.aggregateResults(validResults);
            
        } catch (error) {
            console.error('获取数据失败:', error);
            return null;
        }
    }
    
    // 查询单个数据源
    async queryDataSource(source, params) {
        let url = source.url;
        
        // 替换URL中的参数
        Object.keys(params).forEach(key => {
            url = url.replace(`{${key}}`, params[key]);
        });
        
        const response = await axios({
            method: source.method,
            url: url,
            headers: source.headers,
            timeout: 5000
        });
        
        // 提取数据
        return this.extractData(response.data, source.dataPath);
    }
    
    // 提交响应到区块链
    async submitResponse(requestId, data) {
        try {
            if (!data) {
                console.log('无有效数据,跳过提交');
                return;
            }
            
            // 将数据转换为bytes32
            const dataHash = this.web3.utils.soliditySha3(
                {type: 'string', value: JSON.stringify(data)}
            );
            
            const tx = this.contract.methods.submitResponse(
                requestId,
                dataHash
            );
            
            const gas = await tx.estimateGas({
                from: this.account.address
            });
            
            const txData = {
                from: this.account.address,
                gas: Math.floor(gas * 1.2),
                gasPrice: await this.web3.eth.getGasPrice()
            };
            
            const signedTx = await this.web3.eth.accounts.signTransaction(
                {
                    ...txData,
                    to: config.contractAddress,
                    data: tx.encodeABI()
                },
                this.privateKey
            );
            
            const receipt = await this.web3.eth.sendSignedTransaction(
                signedTx.rawTransaction
            );
            
            console.log(`响应提交成功,交易哈希: ${receipt.transactionHash}`);
            
        } catch (error) {
            console.error('提交响应失败:', error);
        }
    }
    
    // 数据聚合算法
    aggregateResults(results) {
        // 对于数值数据,使用中位数
        if (this.isNumeric(results[0])) {
            const sorted = results.map(Number).sort((a, b) => a - b);
            const mid = Math.floor(sorted.length / 2);
            
            return sorted.length % 2 !== 0 
                ? sorted[mid] 
                : (sorted[mid - 1] + sorted[mid]) / 2;
        }
        
        // 对于分类数据,使用多数投票
        const frequency = {};
        results.forEach(result => {
            frequency[result] = (frequency[result] || 0) + 1;
        });
        
        return Object.entries(frequency)
            .sort((a, b) => b[1] - a[1])[0][0];
    }
    
    isNumeric(value) {
        return !isNaN(parseFloat(value)) && isFinite(value);
    }
}

// 启动节点
const node = new OracleNode();
node.monitorRequests();

数据聚合与共识机制

当多个节点提交响应后,链上合约需要聚合这些数据并达成共识:

// 数据聚合合约扩展
contract DecentralizedOracle {
    // ... 之前的代码 ...
    
    // 节点提交响应
    function submitResponse(
        bytes32 _requestId,
        bytes32 _data
    ) external onlyActiveNode {
        DataRequest storage request = requests[_requestId];
        
        require(!request.fulfilled, "Request already fulfilled");
        require(block.timestamp < request.expiration, "Request expired");
        require(!hasResponded(_requestId, msg.sender), "Already responded");
        
        request.responses