写点什么

使用 Solidity 和 Node.js 构建简单的区块链预言机

作者:devpoint
  • 2022 年 7 月 30 日
  • 本文字数:4460 字

    阅读完需:约 15 分钟

使用 Solidity 和 Node.js 构建简单的区块链预言机

区块链上的预言机是允许区块链世界与来自 WEB 其余部分的数据交互的框架,将其称为 WEB 2.0 世界。随着智能合约应用的不断扩展,处理独特用例所需的各种数据也将不断扩大。


事实上,WEB 2.0WEB 3.0 是两个不同的网络,目前最实用的数据都存在于 WEB 2.0 上。通过创建一组协议来使智能合约能够访问这些数据,新一代的 WEB、系统设计和区块链将会出现。


当前的协议倾向于使用预言机的概念来构建混合系统,这些系统依赖于智能合约和链下 API 来桥接 WEB 2.0 数据以及其他区块链。最著名的预言机是 Chainlink,它提供定价数据、与其他区块链的连接、对大多数 API 的访问以及各种其他数据馈送。


其他主要的例子包括代币桥,它允许在链下服务的帮助下在链之间移动代币和数据。随着时间的推移,可能会出现更多独特的预言机。


目前,像 Singularity NET 这样的组织正在努力构建预言机,以创建市场并轻松访问提供人工智能推理等服务的 API。


在本文中,目标是通过浏览当前用例的一般架构并使用 soliditynode.js 构建一个简单的链上天气 oracle 来对 oracle 概念进行一般定义,以进一步可视化该协议的功能。

事件驱动的预言机设计


在处理使用链下服务代表智能合约执行某些操作的问题时,要记住的最重要的事情是智能合约和服务之间没有正式的消息传递过程。有了这个前提条件,就知道智能合约不能 push ,服务必须是 listenwatchpull


该服务只有两个链上项目可以监视,即状态变量和事件。察状态变量很麻烦,因为它需要与合约进行多次交互。另一方面,事件不需要直接交互。


像这样发出智能合约事件:


emit newEvent(block.timestamp)
复制代码


事件可以看作是开发人员定义的智能合约操作的日志。就像其他类型的日志一样,其他服务可以订阅此提要以监视特定类型的事件,从它们的参数中收集数据,并用它们做任何他们想做的事情。任何有权访问区块链的人都可以看到这些日志,并且可以通过 web3.js 等库进行访问。


鉴于这种独特的通信系统,智能合约可以廉价地“通知”外部世界中的服务事件,或需要完成的预言机案例工作。了解事件以及链上到链下的消息传递是预言机设计中最重要的部分。


一旦服务发现新事件并触发其操作,它就可以获取事件中有价值的数据和唯一的作业 ID,并像任何其他程序一样执行链下操作。


任务完成后,服务可以使用 web3 库与合约进行交易。典型的交易可以“上传”带有作业 ID 的请求/事件的结果,因此智能合约可以继续处理它计划对这些链下数据执行的任何操作。把这一切放在一起,它看起来像这样:


  1. Oracle 智能合约发出一个包含作业信息的事件。

  2. 链下预言机服务监听事件并在事件触发时拉取信息。

  3. 链下预言机与任何服务或数据交互以接收结果。

  4. 链下预言机与预言机智能合约进行交易以更新工作数据。

  5. 智能合约生态系统根据需要使用数据。


当然,这是对像 Chainlink 这样的预言机设计的过度简化,其中包括许多节点和共识协议,以确保预言机数据是分散的。虽然有趣且重要,但简单的理解将是构建该概念的最佳背景。

构建一个简单的预言机

为了更好的理解预言机的概念,本文选择了天气相关的服务,输入的数据容易传输,开放 API 也很丰富。鉴于对事件驱动预言机的理解,系统设计如下所示:



从系统设计来看,有一个 Solidity 合约和一个 node.js 程序,这个设计的要求很简单:


  • Solidity 智能合约

  • node.js

  • Web3.js

  • 天气 API(使用 OpenWeather)


创建项目目录 node-oracle,进入项目目录,初始化项目:


npm init
复制代码


安装依赖:


npm install hardhat chai @nomiclabs/hardhat-waffle @nomiclabs/hardhat-ethers ethers ethereum-waffle axios dotenv web3 --save-dev 
复制代码


现在编写智能合约,在项目根目录下创建智能合约文件夹并进入目录 contracts,运行以下命令来创建一个 truffle 项目,这样就可以开始写智能合约,其中写入以下代码:


// SPDX-License-Identifier: MITpragma solidity >=0.4.22 <0.9.0;
contract WeatherOracle { // 遍历 jobId => 检查智能合约交互的完成状态 // 默认所有的为 false mapping(uint => bool) public jobStatus;
// 如果jobStatus值为0,则表示结果实际上为0 mapping(uint => uint) public jobResults;
// 当前可用的 jobId uint jobId;
// 事件触发的预言机 API event NewJob(uint lat, uint lon, uint jobId);
constructor(uint initialId){ jobId = initialId; }
function getJobId() public view returns (uint) { return jobId; }
function getWeather(uint lat, uint lon) public { emit NewJob(lat, lon, jobId); jobId++; }
function updateWeather(uint temp, uint _jobId)public { jobResults[_jobId] = temp; jobStatus[_jobId] = true; }}
复制代码


已经逐行提供了包含更多细节的在线注释,智能合约是使用几个 map 来保存 jobIds(true = complete, false = incomplete) 工作的状态和结果。


该合约还提供了一个触发工作的函数,函数 getWeather 将位置数据作为参数,创建一新的 jobId,并发出一个带有相关位置和工作信息的事件。


这个预言机的一个重要特性是 updateWeather 函数上的操作符修饰符。为了只允许服务与这个函数交互,这个修饰符是必需的,否则,任何人都可以更新 job 数据。

测试合约

在部署之前,先来测试合约以确保可以逻辑都是正确的。在项目根目录中创建文件夹 test ,此文件夹可以包含客户端测试和以太坊测试。


test 文件夹中添加 test.js 文件,该文件将在一个文件中包含合约测试,代码如下:


const { expect } = require("chai");const { ethers } = require("hardhat");describe("WeatherOracle", function () {    let oracleContract;    before(async () => {        const oracleFactory = await ethers.getContractFactory("WeatherOracle");        oracleContract = await oracleFactory.deploy(1);        await oracleContract.deployed();    });    it("Should have currentJobId", async () => {        let currentJobId = await oracleContract.getJobId();        expect(currentJobId).to.equal(1);    });});
复制代码


然后在项目根目录下执行脚本:


npx hardhat test
复制代码


选择创建 Create an empty hardhat.config.js,替换为以下代码:


/** * @type import('hardhat/config').HardhatUserConfig */require("@nomiclabs/hardhat-waffle");
const config = { alchemy: "9aa3d95b3bc440fa88ea12eaa46161", // 测试网络token privateKey: "e5275ae4ad0f4fd33eb5539e4d8af9fceaeb1c0f90fd63446aa48bb6e", // 钱包私钥};
module.exports = { solidity: "0.8.4", networks: { ropsten: { url: `https://ropsten.infura.io/v3/${config.alchemy}`, accounts: [config.privateKey], chainId: 3, }, },};
复制代码


再次执行测试命令:


npx hardhat test
复制代码

部署(到 Ropsten 测试网络)

在项目根目录下创建文件夹 scripts,然后在文件夹中创建文件 deploy.js


const main = async () => {    const oracleFactory = await ethers.getContractFactory("WeatherOracle");    const oracleContract = await oracleFactory.deploy(1);
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account: ", deployer.address);
console.log("Account balance: ", (await deployer.getBalance()).toString()); await oracleContract.deployed(); console.log("Contract deployed to: ", oracleContract.address);};
const runMain = async () => { try { await main(); process.exit(0); } catch (error) { console.log(error); process.exit(1); }};runMain();
复制代码


要部署合约,请在项目根目录下运行命令:


npx hardhat run scripts/deploy.js --network ropsten
复制代码


执行完成之后可以看到结果:


Deploying contracts with the account:  0x13b48Cf2a42160f0A255Ad79B39E695C0c84Account balance:  48072570908448484Contract deployed to:  0x37531376D0Dd20072fA1a359C2791893d326
复制代码

node 服务

有了一个区块链预言机合约,可以在 node.js 中构建一个链下数据提供者,如下所示:


require("dotenv").config();const fs = require("fs");const axios = require("axios");const Web3 = require("web3");
const web3 = new Web3( Web3.givenProvider || "https://ropsten.infura.io/v3/9aa3d95b3bc440f88ea12eaa4456161");const contractAddress = "0x3753137066D0Dd20072fA1a35532791893d326";const contractAbi = JSON.parse( fs.readFileSync("../artifacts/contracts/oracle.sol/WeatherOracle.json")).abi;
const contract = new web3.eth.Contract(contractAbi, contractAddress);
function callAPI(lat, long) { return axios .get( `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${long}&appid=923ce2ef7d3a60a4fe0e7834d56beaaa` ) .then((res) => { return res.data.main.temp; }) .catch((err) => { return "Error"; });}contract.events.NewJob(async (lat, lon, jobId) => { //use lat and lon to call API const temp = await callAPI(lat, lon); if (temp !== "Error") { // 如果接收到temp,发送数据到区块链上的updateWeather函数 await contract.methods.updateWeather(temp, jobId).send(); }});
const run = async () => { const [lat, lon] = [39, 116]; await contract.methods.getWeather(lat, lon);};run();
复制代码


上面的 node.js 服务,设置了一个 web3 提供程序以使用网络正在使用的任何 URL 提供程序。请注意,交互钱包地址必须是运营商,必须确保 web3 提供商包含该帐户。


使用订阅的 web3 库来监听事件。在新事件上,代码使用内置 API 接口和事件参数来获取请求天气数据。


在该调用的结果上,web3 库updateWeather通过合约 abi 调用该函数以将结果上传到合约。成功后,初始设计要求已全部满足,智能合约可以访问该工作的结果以执行其需要做的任何事情。


通过这样一个简单的示例,可以进一步了解构建简单的链下接口。

结论

区块链预言机将向 web 2.0 数据源开放 web 3.0 世界。这反过来体现了 web 3.0 更多的优势。事件驱动的预言机设计创建了一个将智能合约功能与传统软件工程技术相结合的框架。


随着这一概念的扩展,一个完整的生态系统可能会出现,考虑到与其他智能合约(未来的 API)交互的简单性,它允许最流畅的数据访问网络。


为这个生态系统做出贡献可能会有很大的机会,从一个像天气这样简单的应用程序开始,作为深入探索和发现新事物的第一步。


发布于: 刚刚阅读数: 2
用户头像

devpoint

关注

细节的追求者 2011.11.12 加入

专注前端开发,用技术创造价值!

评论

发布
暂无评论
使用 Solidity 和 Node.js 构建简单的区块链预言机_区块链_devpoint_InfoQ写作社区