Day 9
预言机系统
高级阶段 · 预计学习时间 120 分钟
学习目标
- 理解区块链预言机的作用
- 掌握预言机完整生命周期
- 实现预言机注册和扩展
- 发送和响应预言机查询
- 设计链上链下数据交互
预言机概述
什么是预言机
预言机(Oracle)是区块链与外部世界之间的桥梁,负责将链外数据安全地传输到链上。
为什么需要预言机?
区块链是封闭系统,无法直接访问外部数据。智能合约需要外部数据做决策(如价格、天气、比赛结果),预言机提供可信的数据输入机制。
应用场景
| 场景 | 数据类型 | 示例 |
|---|---|---|
| DeFi | 价格数据 | ETH/USD 汇率 |
| 保险 | 事件数据 | 航班延误、自然灾害 |
| 博彩 | 结果数据 | 比赛分数、选举结果 |
| 供应链 | 追踪数据 | 物流状态、温度记录 |
| IoT | 传感器数据 | 环境监测、设备状态 |
Aeternity 预言机特点
- 原生支持:预言机作为一级公民内置于协议
- 专用交易类型:独立的交易类型和状态管理
- 简单高效:无需复杂的合约实现
区块链
┌───────────────────────────────────────────────┐
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 预言机A │ │ 预言机B │ │ 预言机C │ │
│ │ (价格) │ │ (天气) │ │ (体育) │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ ┌─────┴───────────────┴───────────────┴─────┐ │
│ │ 智能合约层 │ │
│ └───────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
↑ ↑ ↑
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐
│ 价格 API │ │ 天气 API │ │ 体育 API │
└─────────────┘ └─────────────┘ └─────────────┘
预言机生命周期
状态转换
┌─────────┐ register ┌──────────┐ extend ┌──────────┐
│ 不存在 │──────────→│ 活跃 │←────────→│ 活跃 │
└─────────┘ └────┬─────┘ └──────────┘
│
│ TTL 到期
↓
┌──────────┐
│ 过期 │
└──────────┘
核心操作
| 操作 | 说明 |
|---|---|
| Register | 将账户注册为预言机,定义查询/响应格式和费用 |
| Extend | 延长预言机有效期 |
| Query | 任何人可向预言机发送查询,支付查询费用 |
| Respond | 预言机所有者回复查询,获得费用 |
费用结构
| 操作 | 费用类型 | 支付方 | 接收方 |
|---|---|---|---|
| 注册 | 交易费 | 预言机 | 矿工 |
| 查询 | 交易费 + 查询费 | 查询者 | 矿工 + 预言机 |
| 响应 | 交易费 | 预言机 | 矿工 |
| 扩展 | 交易费 | 预言机 | 矿工 |
实践任务
任务 9.1: 注册预言机
from aeternity.node import NodeClient, Config
from aeternity.signing import Account
from aeternity import oracles
# 连接测试网
config = Config(external_url='https://testnet.aeternity.io')
client = NodeClient(config)
# 创建预言机账户(需要有测试币)
oracle_account = Account.generate()
print(f"预言机账户: {oracle_account.get_address()}")
# 定义预言机参数
query_format = "string" # 查询格式
response_format = "string" # 响应格式
query_fee = 100000 # 查询费用 (aettos)
oracle_ttl = 1000 # 预言机 TTL (区块数)
print(f"\n预言机配置:")
print(f" 查询格式: {query_format}")
print(f" 响应格式: {response_format}")
print(f" 查询费用: {query_fee} aettos")
print(f" 有效期: {oracle_ttl} 区块")
# 注册预言机
try:
register_tx = client.oracle_register(
account=oracle_account,
query_format=query_format,
response_format=response_format,
query_fee=query_fee,
oracle_ttl=oracle_ttl
)
print(f"\n注册成功:")
print(f" 交易哈希: {register_tx.hash}")
print(f" 预言机 ID: {register_tx.oracle_id}")
# 预言机 ID 格式: ok_{account_pubkey}
oracle_id = f"ok_{oracle_account.get_address()[3:]}"
print(f" 预言机地址: {oracle_id}")
except Exception as e:
print(f"\n注册失败: {e}")
任务 9.2: 查询预言机状态
# 预言机 ID(替换为实际 ID)
oracle_id = "ok_xxx..."
print(f"查询预言机: {oracle_id}")
try:
oracle_info = client.get_oracle_by_pubkey(oracle_id)
current_height = client.get_current_key_block_height()
print(f"\n预言机信息:")
print(f" ID: {oracle_info.id}")
print(f" 查询格式: {oracle_info.query_format}")
print(f" 响应格式: {oracle_info.response_format}")
print(f" 查询费用: {oracle_info.query_fee} aettos")
print(f" TTL: 区块 {oracle_info.ttl}")
remaining = oracle_info.ttl - current_height
if remaining > 0:
print(f" 剩余有效期: {remaining} 区块")
print(f" 状态: ✅ 活跃")
else:
print(f" 状态: ❌ 已过期")
except Exception as e:
print(f"\n查询失败: {e}")
任务 9.3: 发送预言机查询
# 查询者账户
querier = Account.generate()
# 目标预言机
oracle_id = "ok_xxx..."
# 获取预言机信息
oracle_info = client.get_oracle_by_pubkey(oracle_id)
print(f"预言机查询费用: {oracle_info.query_fee} aettos")
# 构建查询
query_data = "ETH/USD" # 查询内容
query_fee = oracle_info.query_fee
query_ttl = 50 # 查询有效期
response_ttl = 50 # 响应等待期
print(f"\n发送查询:")
print(f" 内容: {query_data}")
print(f" 费用: {query_fee} aettos")
try:
query_tx = client.oracle_query(
account=querier,
oracle_id=oracle_id,
query=query_data,
query_fee=query_fee,
query_ttl=query_ttl,
response_ttl=response_ttl
)
print(f"\n查询成功:")
print(f" 交易哈希: {query_tx.hash}")
print(f" 查询 ID: {query_tx.query_id}")
except Exception as e:
print(f"\n查询失败: {e}")
任务 9.4: 响应预言机查询
import time
# 预言机账户
oracle_account = Account.from_secret_key_string("你的私钥")
oracle_id = f"ok_{oracle_account.get_address()[3:]}"
# 获取待处理的查询
def get_pending_queries(client, oracle_id):
"""获取预言机的待处理查询"""
queries = []
try:
result = client.get_oracle_queries_by_pubkey(oracle_id)
for query in result.oracle_queries:
if not query.response: # 未响应的查询
queries.append(query)
except Exception as e:
print(f"获取查询失败: {e}")
return queries
# 检查待处理查询
pending = get_pending_queries(client, oracle_id)
print(f"待处理查询: {len(pending)} 个")
for query in pending:
print(f"\n查询详情:")
print(f" 查询 ID: {query.id}")
print(f" 内容: {query.query}")
print(f" 费用: {query.fee} aettos")
# 处理查询(模拟外部数据获取)
query_content = query.query
if "ETH/USD" in query_content:
response_data = "1850.00"
elif "BTC/USD" in query_content:
response_data = "43500.00"
else:
response_data = "UNKNOWN"
# 发送响应
try:
response_tx = client.oracle_respond(
account=oracle_account,
oracle_id=oracle_id,
query_id=query.id,
response=response_data
)
print(f" 响应: {response_data}")
print(f" 交易哈希: {response_tx.hash}")
except Exception as e:
print(f" 响应失败: {e}")
任务 9.5: 完整预言机服务
class PriceOracle:
"""价格预言机服务"""
def __init__(self, client: NodeClient, account: Account):
self.client = client
self.account = account
self.oracle_id = None
def register(self, query_fee=100000, oracle_ttl=5000):
"""注册预言机"""
print("注册价格预言机...")
tx = self.client.oracle_register(
account=self.account,
query_format="string",
response_format="string",
query_fee=query_fee,
oracle_ttl=oracle_ttl
)
self.oracle_id = tx.oracle_id
print(f"注册成功: {self.oracle_id}")
return self.oracle_id
def get_price(self, pair: str) -> str:
"""获取价格数据(模拟外部 API)"""
mock_prices = {
"ETH/USD": "1850.00",
"BTC/USD": "43500.00",
"AE/USD": "0.085",
}
return mock_prices.get(pair, "N/A")
def process_queries(self):
"""处理所有待处理查询"""
if not self.oracle_id:
print("预言机未注册")
return
result = self.client.get_oracle_queries_by_pubkey(self.oracle_id)
pending = [q for q in result.oracle_queries if not q.response]
print(f"待处理查询: {len(pending)} 个")
for query in pending:
pair = query.query
price = self.get_price(pair)
print(f" 处理: {pair} → {price}")
self.client.oracle_respond(
account=self.account,
oracle_id=self.oracle_id,
query_id=query.id,
response=price
)
def run_service(self, poll_interval=10):
"""运行预言机服务"""
print(f"\n启动预言机服务...")
print(f"预言机 ID: {self.oracle_id}")
print("按 Ctrl+C 停止\n")
try:
while True:
self.process_queries()
time.sleep(poll_interval)
except KeyboardInterrupt:
print("\n服务已停止")
# 使用示例
oracle = PriceOracle(client, oracle_account)
oracle.register(query_fee=50000, oracle_ttl=10000)
oracle.run_service(poll_interval=15)
智能合约中使用预言机
Sophia 合约示例
@compiler >= 6
contract OracleConsumer =
// 预言机相关类型
type oracle_id = oracle(string, string)
type query_id = oracle_query(string, string)
// 合约状态
record state = {
owner : address,
oracle : option(oracle_id),
last_price : option(string),
last_query : option(query_id)
}
// 设置预言机
stateful entrypoint set_oracle(oracle_addr : oracle_id) =
require(Call.caller == state.owner, "Only owner")
put(state{ oracle = Some(oracle_addr) })
// 查询价格
stateful payable entrypoint query_price(pair : string) : query_id =
require(is_some(state.oracle), "Oracle not set")
let oracle = Option.force(state.oracle)
let fee = Oracle.query_fee(oracle)
require(Call.value >= fee, "Insufficient query fee")
// 发送查询
let q = Oracle.query(
oracle,
pair,
fee,
RelativeTTL(50), // 查询 TTL
RelativeTTL(50) // 响应 TTL
)
put(state{ last_query = Some(q) })
q
// 获取响应
stateful entrypoint get_response() : option(string) =
require(is_some(state.oracle), "Oracle not set")
require(is_some(state.last_query), "No pending query")
let oracle = Option.force(state.oracle)
let query = Option.force(state.last_query)
switch(Oracle.get_answer(oracle, query))
None => None
Some(response) =>
put(state{ last_price = Some(response) })
Some(response)
知识检查点
完成 Day 9 后,你应该能够:
相关资源