在区块链的世界里,智能合约无法直接访问链外数据,这就像一台没有互联网连接的电脑——功能强大却信息闭塞。预言机(Oracle)正是连接区块链与现实世界的桥梁,而今天,我将带你从零开始构建一个去中心化预言机服务。
为什么我们需要去中心化预言机?
传统中心化预言机存在单点故障风险,一旦预言机节点被攻击或宕机,依赖它的所有智能合约都会受到影响。去中心化预言机通过多节点共识机制,确保数据源的可靠性和抗攻击性。
想象一下:一个去中心化保险合约需要获取天气数据来触发赔付。如果只有一个数据源,恶意攻击者可能伪造数据获取不当利益。而多个独立节点从不同API获取数据并达成共识,就能极大提高系统的安全性。
架构设计:三层预言机网络
我们的去中心化预言机将采用三层架构:
- 数据源层:多个外部API接口
- 共识层:预言机节点网络
- 合约层:链上聚合合约
// 预言机核心合约
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