Day 6

预言机系统

中级阶段 · 预计学习时间 3-4 小时

学习目标
  • 理解预言机的作用
  • 注册预言机
  • 查询和响应预言机
预言机概述

预言机(Oracle)是区块链与外部世界的桥梁,允许链上合约访问链外数据(如价格、天气、比赛结果)。

工作流程
角色操作
Oracle 运营者注册预言机 → 监听查询 → 响应查询
查询者发送查询(支付费用)→ 获取响应
注册预言机
import "strings"

// 预言机参数
querySpec := "string"       // 查询格式
responseSpec := "string"    // 响应格式
queryFee := big.NewInt(100) // 查询费用

// TTL 类型: 0 = 相对区块数, 1 = 绝对区块高度
oracleTTLType := uint64(0)
oracleTTLValue := uint64(500) // 500 区块后过期

abiVersion := uint16(0) // 原始数据,无 ABI

// 创建注册交易
regTx, err := transactions.NewOracleRegisterTx(
    alice.Address,
    querySpec,
    responseSpec,
    queryFee,
    oracleTTLType,
    oracleTTLValue,
    abiVersion,
    ttlnoncer,
)
if err != nil {
    log.Fatal(err)
}

// 签名和广播...
// Oracle ID = ak_ 替换为 ok_
oracleID := strings.Replace(alice.Address, "ak_", "ok_", 1)
fmt.Printf("Oracle ID: %s\n", oracleID)
查询预言机

任何用户都可以向预言机发送查询,需支付查询费用。

query := "What is the price of AE?"

queryTTLType := uint64(0)
queryTTLValue := uint64(50)

responseTTLType := uint64(0)
responseTTLValue := uint64(50)

queryTx, err := transactions.NewOracleQueryTx(
    bob.Address,         // 查询者
    oracleID,            // 预言机 ID
    query,               // 查询内容
    queryFee,            // 费用
    queryTTLType,
    queryTTLValue,
    responseTTLType,
    responseTTLValue,
    ttlnoncer,
)

// 签名和广播...
// 交易确认后可以获取 Query ID (oq_...)
响应查询

预言机运营者监听查询并响应。

queryID := "oq_..."  // 从查询交易获取
response := "1.50 USD"

respTx, err := transactions.NewOracleRespondTx(
    alice.Address,    // 预言机所有者
    oracleID,
    queryID,
    response,
    responseTTLType,
    responseTTLValue,
    ttlnoncer,
)

// 签名和广播...
fmt.Printf("已响应查询: %s\n", response)
预言机运营流程
// 简化的预言机服务循环
func runOracleService(node *naet.Node, oracle *account.Account, oracleID string) {
    fmt.Println("预言机服务启动...")
    fmt.Printf("Oracle ID: %s\n", oracleID)
    
    for {
        // 1. 获取待处理查询
        queries, err := node.GetOracleQueries(oracleID)
        if err != nil {
            log.Println("获取查询失败:", err)
            continue
        }
        
        // 2. 处理每个查询
        for _, q := range queries {
            if q.Response == "" {
                // 获取外部数据(模拟)
                response := getExternalData(q.Query)
                
                // 发送响应
                respondToQuery(node, oracle, oracleID, q.ID, response)
            }
        }
        
        time.Sleep(10 * time.Second)
    }
}

func getExternalData(query string) string {
    // 实际应用中调用外部 API
    if strings.Contains(query, "AE") {
        return "0.085 USD"
    }
    return "Unknown"
}
知识检查点