Chainlink是如何实现“万能插头”的--外部适配器的开发和应用

背景

在以太坊原生语言solidity中调用API可以将链下数据传输至链上智能合约应用。世界各地的开发者可以利用Chainlink的去中心化区块链预言机将链下真实世界的数据和事件接入区块链环境。Chainlink内置的核心适配器可以轻松配置并验证来自任何开放API的数据。

然而,Chainlink核心适配器往往在灵活性和功能性上无法满足开发者所有的需求,比如:

API接口认证(保护API秘钥隐私) 保障隐私,降低延迟,并实现高吞吐量的链下计算,以降低gas费用 将数据传输至其他区块链(互操作性) 其他核心适配器无法满足的功能

外部适配器可以访问优质数据并使智能合约能够非常灵活地连接至付费web API。目前众多安全可靠的预言机网络都已接入外部适配器,其中包括超过35个价格参考数据,总共保障了30多亿美元的DeFi资产。本文将探讨以下问题:

什么是外部适配器? 如何开发外部适配器? 如何运行外部适配器? 如何使用外部适配器?

什么是外部适配器?

适配器通常分成两类:

核心适配器 外部适配器

核心适配器是Chainlink核心节点客户端内置的功能。Httpget、Copy和Jsonparse都属于核心适配器。而外部适配器则是指开发者根据具体要求基于Chainlink预言机网络定制化的功能。本文不会深入探讨接入其他区块链的技术细节,但是Chainlink外部适配器和外部启动器实现定制化,将使Chainlink能够与任何区块链兼容,因此能极大丰富其功能,这是Chainlink两大关键价值的其中一个。

这里顺便提一句,如果想要智能合约在端到端保持去中心化,就必须允许其他预言机节点也运行外部适配器。可以把外部适配器想象成Chainlink节点的开源软件包。也就是说,你可以选择不亲自运行节点,但同时也能够在智能合约中使用节点的定制化功能。你可以让其他节点帮你运行外部适配器,现在有许多项目集成了Chainlink但不亲自运行节点。这样,智能合约开发者就可以专注于去中心化应用的商业逻辑,并将节点运行工作交给专业的节点运营商处理。你可以在market.link等第三方节点分类信息平台上挂出你的外部适配器,也可以#ask-a-node-operator (委托节点运营商)运行你的外部适配器,你只需要负责测试和开发工作。

当然,如果你想要自己运行节点,也完全可以这么做!

如何开发外部适配器

快速启动

开发外部适配器最简单的方式就是把它做成一个API接口。这样一来,就可以灵活定制链下计算方式,使用任何编程语言,并且调用API传输并接收数据。关于如何开发API接口有许多教学资料,接下来我们将在nodejs中看一个简单的Chainlink适配器模板。另外还有python版本的示例 ,如果你感兴趣也可以查看。我们会基于下面这个代码库进行开发,这是一个功能完整的外部适配器,你可以轻松定制所需的数据。你也可以从零开始开发,不过用这个代码库开发会简单很多。

接下来,你需要使用yarn和nodejs。首先,将代码库克隆到本地,使用cd命令进入到项目中。

git clone https://github.com/thodges-gh/CL-EA-NodeJS-Template.git ExternalAdapterTemplate
cd ExternalAdapterTemplate

然后,安装依赖并启动API服务器。

yarn
yarn start

API/外部适配器的服务器会被启动,并等待被调用。

patrick@iMac: [~/code/ExternalAdapterTemplate -  (master)] $ yarn start
yarn run v1.22.4
$ node app.js
Listening on port 8080!

要注意,Chainlink节点会扫描所有适配器,一旦发现了在它适配器/任务清单中的外部适配器,就会进行调用。我们可以自己调用一次,模拟Chainlink节点请求的过程。

这是一个curl命令,向我们刚开始运行的API服务器发送HTTP post请求。你可以在本地终端窗口尝试一下。

curl -X POST -H "content-type:application/json" "http://localhost:8080/" --data '{ "id": 0, "data": { "from": "ETH", "to": "USD" } }'

jsonhttp://localhost:8080/是API服务器等待响应的地址,--data后面的参数是我们为了让Chainlink节点看懂数据请求而发送的参数。Chainlink节点发送的请求包括:

一个 id 一个 data object

格式如下: {"id":"0", "data":{}}

data object中,可以清楚地看到fromto参数。这些是我们设置的定制化参数,让外部适配器能够获得各种不同的价格数据。可以看到,当运行curl脚本时,会返回以下结果:

{"jobRunID":0,"data":{"USD":441.49,"result":441.49},"result":441.49,"statusCode":200}

注:由于以太币价格一直在变动,因此这里的数字可能会发生变化!

json所有外部适配器都会返回一个对象,其中至少包含:

jobRunID data object

最好还能包含statusresult字段,这样可以轻松处理并改正错误。在这个示例中,返回的结果是441.49,即当前以太币价格。我们之所以会获得这个数据,是因为我们的外部适配器也调用了一个API,那就是:

https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD

这个外部适配器实际上里面包裹着一个API接口,这也是外部适配器的常见框架。

以上就是外部适配器的功能,那么我们接下来看一下它的运行原理。

获取天气数据

在PatrickAlphaC模板代码库中的weather-api分支中可以查看。

OpenWeatherMap我们先来修改一下代码,以便从一个新的API接口()获取天气数据。为了方便教学,我们先在这里免费注册,获得一个免费的API秘钥。账号通过验证后,我们就可以在这里看到API秘钥,以及城市当前天气数据API文档。注:你注册后大概需要等待十分钟秘钥才会完全生效。

我们现在要把这个外部适配器稍作修改,本来它获取的是以太币价格,现在要改为获取城市天气数据。

我们要修改index.js文件(如果要上线则需修改test/index_test.js测试文件)。app.js定义了外部适配器/API服务器响应请求的方式,我们现在先不用管。在之前示例里的dataobject中,我们使用了两个参数,即fromto参数。我们可以通过更新customParams来定制化参数:

更新前的customParams:

const customParams = {
 base: ['base', 'from', 'coin'],
 quote: ['quote', 'to', 'market'],
 endpoint: false
}

更新后的customParams:

const customParams = {
 city:['q', 'city', 'town'],
 endpoint: false
 }

这样,我们无论输入citytown还是q ,都表示city

createReqeust常量是最关键的一个环节,因为这里需要连接到URL。如果查看天气数据文档,可以看到这样的一个示例地址:`https://api.openweathermap.or...;CITY_NAME>&appid=<YOUR_API_KEY>

可以输入API秘钥和城市名boston,对API进行测试,然后将URL复制到浏览器(如果返回错误说API秘钥无效,可以等几分钟再试一次)

用这个URL就可以知道需要做哪些更新:

更改前:

const endpoint = validator.validated.data.endpoint || 'price'

更改后:

const endpoint = validator.validated.data.endpoint || 'weather'

更改前:

const url = 'https://min-api.cryptocompare.com/data/${endpoint}'

更改后:

const url = 'https://api.openweathermap.org/data/2.5/${endpoint}'

现在我们要添加参数。我们的两个参数是城市(文档里的城市是q)和API秘钥( appid

城市可以在原来的代码上进行修改:

const fsym = validator.validated.data.base.toUpperCase()

改成:

const q = validator.validated.data.city.toUpperCase()

然而,我们不能将API秘钥写死在代码中。

API认证

我们应该编辑这行代码:

const tsyms = validator.validated.data.quote.toUpperCase()

将它改成:

const appid = process.env.API_KEY;

要将API秘钥放到.envrc文件中,而不是放到源代码中,因此要新建这样一个.envrc文件:

export API_KEY=<YOUR_KEY_HERE>

别忘了要添加到.gitignore中!如果你在运行curl命令测试时遇到任何问题,也可以在终端运行 export API_KEY=<API_KEY>

注:点击这里了解关更多环境变量

然后需要将参数从:

const params = {
  fsym,
  tsyms
}

改成:

const params = {
 q,
 appid
}

将结果从:

response.data.result = Requester.validateResultNumber(response.data, [tsyms])

改成:

response.data.result = Requester.validateResultNumber(response.data, ['main','temp'])

然后就搞定了!现在,main object的temp值就等于天气API输出的气温了,因此我们的response.data.result参数如上所示。

现在你已经成功开发出了API秘钥认证的外部适配器!

我们来调用一下API。

curl -X POST -H "content-type:application/json" "http://localhost:8080/" --data '{ "id": 0, "data": { "city":"Boston"} }

示例返回值:

{"jobRunID":0,"data":{"coord":{"lon":-71.06,"lat":42.36},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"base":"stations","main":{"temp":296.81,"feels_like":298.49,"temp_min":295.15,"temp_max":297.59,"pressure":1008,"humidity":78},"visibility":10000,"wind":{"speed":2.6,"deg":360},"clouds":{"all":90},"dt":1599162213,"sys":{"type":1,"id":3486,"country":"US","sunrise":1599127933,"sunset":1599174905},"timezone":-14400,"id":4930956,"name":"Boston","cod":200,"result":296.81},"result":296.81,"statusCode":200}

很好!现在就能完美运行了!我们创建了关键内容,让Chainlink节点能够理解数据请求。你可以清楚地看到在链下展开运算的好处,智能合约可以将大量计算工作放到链下运行,然后再将计算结果返回至链上。

现在已经做出了外部适配器,接下来的问题就是如何让Chainlink节点运行外部适配器?如何调用外部适配器?我们这里先暂时跳过亲自运行外部适配器的部分,因为实际上我们不需要亲自运行。在index.js的底部有一些wrapper,可以让节点运营商采用无服务器的方式部署代码。所以我们可以这么做:

将适配器添加至第三方节点分类信息平台,比如market.link 让节点运营商运行我们的适配器(#ask-a-node-operator discord) 亲自运行节点(不必要)

你可以发现,你甚至都不用亲自运行节点就可以使用自己的外部适配器。这是最理想的情况,因为我们希望自己的适配器可以被其他预言机节点使用,这样就可以有许多节点访问数据,并使Chainlink网络保持极高的去中心化水平。

我们将在之后的视频或博客文章中详细探讨如何运行外部适配器。如果你已经开发出了新的适配器,可以先暂时让节点帮你运行,并加入我们的社区!

使用外部适配器

假设现在有一个节点帮你运行外部适配器,那么你就可以通过这个节点将数据传输至你的智能合约。在这个示例中,我们开发出了一个叫做Alpha Vantage的外部适配器。Alpha Vantage是一个股票和加密货币价格数据API,需要API秘钥才能访问。我们来看看如何通过Linkpool kovan节点运行的外部适配器获取特斯拉股票价格数据。首先要找到这个外部适配器,我们需要在外部适配器的网页中寻找。将页面一直往下拉,直到找到我们想要的Alpha Vantage外部适配器。

imgAlpha Vantage外部适配器

图中描述了外部适配器的所有功能。我们可以点击“supported nodes”按钮,查看可以运行这个适配器的节点。我们发现有一个节点可以支持它,然后可以在任务页面查看这个节点的具体信息。你可以看到Alpha Vantage适配器在这个节点的适配器清单中。

接下来就是如何使用适配器。我们先像往常一样将job ID和预言机ID添加至代码中。然后添加Alpha Vantage文档中的所有参数。

bytes32 jobId = “802ec94e00184b789a016b8e71ae9fb4”;
address oracle = 0x56dd6586DB0D08c6Ce7B2f2805af28616E082455;
 function requestTSLAPrice() public {
  Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfillEthereumPrice.selector);
  req.add("function", "GLOBAL_QUOTE");
  req.add("symbol", "TSLA");
  string[] memory copyPath = new string[](2);
  copyPath[0] = "Global Quote";
  copyPath[1] = "05. price";
  req.addStringArray("copyPath", copyPath);
  req.addInt("times", 100000000);
  sendChainlinkRequestTo(oracle, req, fee);
 }

以上是完整版代码,你可以尝试使用。你也可以使用任何API的文档进行尝试,因为现在你应该已经发现其实套路都是一样的!

总结

正如你所看到的,外部适配器是非常强大的工具,开发者可以用来丰富智能合约的功能并提升其连通性。另外,还可以使用API认证和链下计算等layer2解决方案来提升外部适配器的灵活性。如果这篇文章对你有任何启发,欢迎参加Chainlink黑客松,尝试应用你新学到的技能!这次黑客松的奖金超过4万美元,希望大家能踊跃参加!

如果当你读到这篇文章时黑客松已经结束了,那么也欢迎加入我们在Twitter, Discord, 或 Reddit上的社区,转发并加上#chainlink和#ChainlinkEA标签,了解Chainlink最新活动消息,并尝试应用你的技能!

如果Chainlink预言机可以为你目前开发的产品提供任何附加价值;抑或你希望参与Chainlink网络的开源开发工作,请查看开发者文档或加入我们在Discord频道上的技术讨论群。

加入中文开发者社区:neils_(开发者社区管理员团长)

img