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

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

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

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

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

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

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

在本文中,目标是通过浏览当前用例的一般架构并使用 soliditynode.js 构建一个简单的链上天气预言机来对预言机概念进一步的了解。

事件驱动的预言机设计

image.png

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

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

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

emit newEvent(block.timestamp)

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

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

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

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

  1. Oracle 智能合约发出一个包含作业信息的事件。
  2. 链下预言机服务监听事件并在事件触发时拉取信息。
  3. 链下预言机与任何服务或数据交互以接收结果。
  4. 链下预言机与预言机智能合约进行交易以更新工作数据。
  5. 智能合约生态系统根据需要使用数据。

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

构建一个简单的预言机

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

image.png

从系统设计来看,有一个 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: MIT
pragma 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: "9aa3d95b3bc440fa88ea12eaa4456161", // 测试网络token
    privateKey:
        "e5275ae4ad0f4fd33e539e4d8af9fceaeb1c0f905f423cc6aa48bb6e", // 钱包私钥
};
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:  0xDC13b48Cf2a42160f820A5Ad79B39E695C0c84
Account balance:  4807844068484
Contract deployed to:  0x0006544b9c915Ab3cb0e21000E4a4ABE746

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/9aa3d95b3bc440fa88ea12eaa4456161"
);
const contractAddress = "0x3753137066D0Dd20072fA1a359C532791893d326";
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天前
|
前端开发 JavaScript NoSQL
使用 Node.js、Express 和 React 构建强大的 API
本文详细介绍如何使用 Node.js、Express 和 React 构建强大且动态的 API。从开发环境搭建到集成 React 前端,再到利用 APIPost 高效测试 API,适合各水平开发者。内容涵盖 Node.js 运行时、Express 框架与 React 库的基础知识及协同工作方式,还涉及数据库连接和前后端数据交互。通过实际代码示例,助你快速上手并优化应用性能。
|
5月前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js:从零开始构建后端服务
【10月更文挑战第42天】在数字时代的浪潮中,掌握一门后端技术对于开发者来说至关重要。Node.js,作为一种基于Chrome V8引擎的JavaScript运行环境,允许开发者使用JavaScript编写服务器端代码,极大地拓宽了前端开发者的技能边界。本文将从Node.js的基础概念讲起,逐步引导读者理解其事件驱动、非阻塞I/O模型的核心原理,并指导如何在实战中应用这些知识构建高效、可扩展的后端服务。通过深入浅出的方式,我们将一起探索Node.js的魅力和潜力,解锁更多可能。
|
1月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
1月前
|
JavaScript 前端开发 Java
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
柯里化是一种强大的函数式编程技术,它通过将函数分解为单参数形式,实现了灵活性与可复用性的统一。无论是参数复用、延迟执行,还是函数组合,柯里化都为现代编程提供了极大的便利。 从 Redux 的选择器优化到复杂的数据流处理,再到深度嵌套的函数优化,柯里化在实际开发中展现出了非凡的价值。如果你希望编写更简洁、更优雅的代码,柯里化无疑是一个值得深入学习和实践的工具。从简单的实现到复杂的应用,希望这篇博客能为你揭开柯里化的奥秘,助力你的开发之旅! 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
3月前
|
中间件 API
Next.js 实战 (八):使用 Lodash 打包构建产生的“坑”?
这篇文章介绍了作者在使用Nextjs15进行项目开发时遇到的部署问题。在部署过程中,作者遇到了打包构建时的一系列报错,报错内容涉及动态代码评估在Edge运行时不被允许等问题。经过一天的尝试和调整,作者最终删除了lodash-es库,并将radash的部分源码复制到本地,解决了打包报错的问题。文章最后提供了项目的线上预览地址,并欢迎读者留言讨论更好的解决方案。
81 10
|
5月前
|
JSON 缓存 JavaScript
深入浅出:使用Node.js构建RESTful API
在这个数字时代,API已成为软件开发的基石之一。本文旨在引导初学者通过Node.js和Express框架快速搭建一个功能完备的RESTful API。我们将从零开始,逐步深入,不仅涉及代码编写,还包括设计原则、最佳实践及调试技巧。无论你是初探后端开发,还是希望扩展你的技术栈,这篇文章都将是你的理想指南。
|
4月前
|
JSON JavaScript 前端开发
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将引导您步入Node.js的奇妙世界,通过实践操作,掌握如何使用这一强大的JavaScript运行时环境构建高效、可扩展的RESTful API。我们将一同探索Express框架的使用,学习如何设计API端点,处理数据请求,并实现身份验证机制,最终部署我们的成果到云服务器上。无论您是初学者还是有一定基础的开发者,这篇文章都将为您打开一扇通往后端开发深层知识的大门。
95 12
|
5月前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
145 5
|
5月前
|
JavaScript NoSQL API
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发如同一座灯塔,指引着数据的海洋。本文将带你航行在Node.js的海域,探索如何从一张白纸到完成一个功能完备的RESTful API。我们将一起学习如何搭建开发环境、设计API结构、处理数据请求与响应,以及实现数据库交互。准备好了吗?启航吧!
|
5月前
|
缓存 负载均衡 JavaScript
构建高效后端服务:Node.js与Express框架实践
在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。