quentangle

Posted on Nov 21, 2021Read on Mirror.xyz

创建你的第一个以太坊预言机

区块链如何获取数据

区块链天生就是自给自足的。传统区块链的整个确定性模型取决于以下事实:在事务执行期间(更新系统的“状态”),区块链不能执行任何源自区块链外部源的程序。在某些时候,整个系统的所有外部数据必须来自事务的输入,该事务已需要被提交到块中。

例如,以太坊区块链允许开发人员部署智能合约,这些合约在给定输入的情况下执行逻辑步骤。智能合约中的执行程序不能在区块链之外做任何事情。它无法通过互联网与网络服务联系。将数据导入智能合约的唯一方法是将其传递给事务。更新区块链状态的唯一方法是通过向系统发送新事务来触发状态的改变。

考虑这样一个问题,如果允许智能合约访问API,来检索智能合约的执行过程中使用的某些数据时,会发生什么。如果今天将合约部署到新块中,则API可能会返回

{ "foo": "bar" }

但是到了第二天,API就可能返回

{ "foo": "baz" }

从现在开始一个月有人正在同步以太坊区块链,执行包含我们智能合约的区块,并且API会返回与一个月前返回值完全不同的响应。此时新同步的区块链的状态将与上个月存在的区块链的状态不同。

这不再是一个完全自我确定的区块链。在同步后,同一个区块会出现不同的结果。

换句话说:在没有互联网连接的情况下,给定完整的块,节点必须能够从头开始重新创建区块链的最终状态。

那么对于想要创建和部署智能合约以在其现有应用中用于可审计目的的开发人员来说,这意味着什么呢?答案是预言机(Oracle)。你需要创建一个预言机。

什么是预言机?

预言机是一种通过事务向智能合约提供“可信任”数据的服务。“可信任”是因为,信任是个人问题。考虑到预言机的一些特定实现,两个实体可能不会以相同的方式“信任”数据。

预言机通常是实现某些区块链特定功能的Web服务,例如哈希和签名某些数据,或者创建新的事务并将其提交到网络。

让我们看一个简单的例子

我们将创建三个服务来实现一个简单的循环“预言机”工作流程。

在链上,智能合约需要一个位于白名单中的地址作为合约参数。这个智能合约将实现一个updateWeather的方法,这个方法仅响应来自白名单地址的事务。这个方法接受天气数据作为输入参数,通过“event”这一以太坊特有的概念来重复这项数据。事件Event应该被理解为类似于传统软件开发中的stdout日志输出。从智能合约发出的事件可以在javascript应用程序中异步订阅。

网络上有两个nodejs进程。其中一个是“预言机”。它存在于运行时循环中,该循环从开放天气API检索天气数据,然后将天气数据提交给智能合约以用于历史审计目的。

另一个nodejs进程只是订阅智能合约发出的天气事件,和console.log上的结果。如上所述,每次成功执行“天气预言机”方法时都会发出事件。

一个简单的数据流示意图,数据从基于web服务的预言机,到智能合约,再到另一个日志事件服务器

免责声明

以下代码已经大大简化,以便于理解。它删除了应有的错误处理,不适合生产环境中使用。

智能合约

这个合约暴露一个公开的预言机地址,它通过构造函数中的输入参数设置。

contract WeatherOracle {  
  address public oracleAddress;

    constructor (address _oracleAddress) public {
    oracleAddress = _oracleAddress;
  }

    // ...
}

接下来,我们将定义一个Event事件,它将在weatherUpdate函数的事务执行成功时发出。为简单起见,事件将只发出一个字符串,即温度。

event WeatherUpdate (string temperature);

最后是updateWeather函数。它公开可见,这意味着它可以从外部事务中调用。

function updateWeather (string temperature) public {
    require(msg.sender == oracleAddress);
    emit WeatherUpdate (temperature);
  }

请注意require语句。当msg.sender(发送事务的地址)等于公开设置的oracleAddress列入白名单的地址时,才继续执行。

就这些。

预言机服务

我们的预言机是一个简单的nodejs服务。它使用request库来调用外部天气API,解析响应,制作并向已部署的智能合约提交事务,然后等待再次执行。

点击API端点(存储在环境变量中),以启动工作流程。

const options = { uri: process.env.WEATHER_URL, json: true };
const start = () => {
  request(options)
  .then(parseData)
  .then(updateWeather)
  .then(restart)
  .catch(error);
};

解析响应

const parseData = (body) => {
  return new Promise((resolve, reject) => {
    const temperature = body.main.temp.toString();
    resolve({ temperature });
  });
};

在已部署的智能合约上创建一个调用updateWeather函数的以太坊事务。请注意,account()是一个异步函数,它从其他地方定义的配置中加载以太坊帐户,而contract是一个javascript对象,表示已部署的WeatherOracle智能合约的位置和界面。这些智能合约特定的功能由web3npm包提供给你:)

const updateWeather = ({ temperature }) => {
  return new Promise((resolve, reject) => {
    account().then(account => {
      contract.updateWeather(temperature, { from: account }, (err, res) => {
        resolve(res);
      });
    });
  });
};

最后,如果超时,我们会根据环境配置重启进程。wait函数将在给定超时时间后解析。

const restart = () => {
  wait(process.env.TIMEOUT).then(start);
};

就这样!上面的代码实现了一个简单的服务,该服务从API获取数据并将其提供给智能合约。

掌握秘诀了吗

在创建以太坊事务时,我们称之为{from:account}。此帐户对象是一个javascript对象,它是签署事务的完整帐户(可以获取私钥),并且必须包含一些ETH作为支付交易的交易费。

服务中私钥被定义为环境变量,用于实例化account对象。由于智能合约的updateWeather方法中的require行的存在,此私钥必须是用于实例化WeatherOracle智能合约的公开地址的私钥。

如果任何其他地址创建了一个在合约上调用updateWeather的事务,那么该事务将失败并且不会发出该事件。

说到发出事件,我们需要确保以下工作。

事件消费者

这是另一个简单的nodejs服务。同样,contract是一个javascript对象,表示已部署的WeatherOracle智能合约的位置和接口。调用WeatherUpdate事件名称并传入回调函数,这就是异步事件侦听所需的全部内容。

const consume = () => {
  contract.WeatherUpdate((error, result) => {
    console.log("NEW WEATHER DATA EVENT ON SMART CONTRACT");
    console.log("BLOCK NUMBER: ");
    console.log("  " + result.blockNumber)
    console.log("WEATHER DATA: ");
    console.log(result.args);
    console.log("\\n");
  });
}

随着此服务的运行,它会定期将数据输出到stdout,因为有效的事务会被挖掘成块。

NEW WEATHER DATA EVENT ON SMART CONTRACT
BLOCK NUMBER:
  3424586
WEATHER DATA:
{ temperature: '74.75' }

现在你会了吧。

tl;dr(太长不看)

如果您想获得完整的项目以查看代码的实际效果,请在GitHub上找到它们

原文地址