关于Chainlink测试网络的实现的技术概论

引言:5月16日,微软资深工程师Aleksey Klintsevich(领英档案https://www.linkedin.com/in/alekseyklintsevich)参与Chainlink测试后撰写了《Technical Overview of the Chainlink Testnet Implementation(关于Chainlink测试网络的实现的技术概论)》。通过种种迹象,我们有理由相信Sergey和他的团队正在和微软一起工作!Keep your balances, Linkies! 以下是条子哥为大家带来的原文翻译:

Chainlink试图通过分散式预言机将链下数据与链上智能合约连接起来。

这段时间,我对Chainlink的工作方式感兴趣。 以下是我得出的结论。

Chainlink的实现可以分为两个不同的部分:Chainlink节点和Chainlink智能合约。

=====分隔符=====

Chainlink智能合约。

Chainlink智能合约将部署在区块链中,并将被其他智能合约用于发起请求并接收链下数据。

Chainlink测试网络的实现基于以太坊区块链,因此,智能合约将使用Solidity 编程语言。智能合约会使用Chainlink来请求链下数据,将成为Chainlinked智能合约的子类。Chainlinked智能合约为函数,数据结构和其他Chainlink智能合约的提供必要的输入。

要通过API请求链下数据,请求式智能合约必须知道将在请求数据时触发的工作流程作业ID(JobSpec)。 每个JobSpec都有一个唯一的ID。 JobSpec作业ID可以通过请求的智能合约构造函数传入(并在作业运行中引用)或硬编写到newRun函数中。

请求数据的智能合约功能必须创建ChainlinkLib.Run的实例。 ChainlinkLib.Run保存执行链下请求所需的信息 - 它是使用newRun函数创建的。newRun函数获取JobSpec的作业ID,该请求将在请求数据时触发,智能合约发送响应的地址(本例中为——当前合同的地址)以及为即将发送数据Chainlink节点数字签名。

请求智能合约可以为Run添加更多数据——例如要调用的API的URL,如何解析URL请求的响应等。

例如,URL“https://min-api.cryptocompare.com/data/

price?fsym=ETH&tsyms=USD,EUR,JPY”返回以下JSON响应。

通过将USD添加到路径数组中,链下JobSpec会知道将JSON响应的美元部分发送回请求的智能合约。

然后ChainlinkLib.Run以及该合约愿意为请求支付的Link数量会被传递到chainlinkRequest函数中。

因此,请求智能合约必须用Link token支付手续费。

chainlinkRequest函数最终将调用Oracle智能合约的requestData函数。

requestData函数将为请求分配一个唯一的ID,并将大部分

ChainlinkLib.Run信息存储在一个名为callbacks的映射中,并使用唯一ID作为键。 然后该函数将发出RunRequest日志,该日志将包含请求的唯一ID,JobSpec的作业ID,数据(例如要调用的URL以及如何解析返回的JSON)以及一些其他必要的元数据。

缺失的link(双重含义)目前是权益/匹配合约,这将推动预言机节点与请求智能合约的匹配过程。 目前这不在公开回购计划中。 史蒂夫在Gitter上描述了权益/匹配合约的工作原理如下:

“当节点最初将LINK存入一个匹配的合约时,它们建立一个”可用余额“。 该笔余额由匹配合约持有,但只要它们愿意,可以通过预言机退回。比如说,当一个预言机提出一个SLA请求并被选中时,SLA所需的LINK数量将被匹配合约移出可用余额并指定用于SLA余额。当SLA结束时,未被罚没的LINK余额会重新回到可用余额,提供服务所挣得的LINK也一样。”

现在已经有了关于链下数据的请求,让我们来看看在Chainlink节点启动后会发生什么,它如何能够发现区块链中的请求,运行所需的JobSpec并将数据返回给发起请求的智能合约。

=====分隔符=====

Chainlink节点:

Chainlink节点是用Golang编写的。 Golang具有独特的功能,这是大多数编程语言中都做不到的,例如Goroutines和Channels。

如果你没有经验,最好理解这两个特色。

Chainlink节点公开了一个命令行界面,允许用户启动Chainlink节点,显示所有/单个作业运行,创建JobSpec,开始作业运行,备份数据库,导入密钥文件等。

用户要在noden指令后输入Chainlink执行文件的名称来启动Chainlink节点。 此外,还要在命令行中设置密码。

调用该命令行最终会调用RunNode函数。 RunNode函数的主要目标是连接到以太坊区块链,创建ChainlinkApplication对象,使用KeyStore进行身份验证,调用ChainlinkApplication Start moethod,并设置Chainlink REST API(允许创建JobSpecs,桥适配器等)。

ChainlinkApplication的对象很重要,因为它保留对控制节点工作流程的对象的引用。实例化后,ChainlinkApplication将保留这些引用:

HeadTracker“以thread safe方式保留并存储此特定节点所经历的最新区块号。”

EthereumListener“从以太坊节点的websocket管理推送通知,以监听新的head和log event。”EthereumListener控制RunLog和Ethlog JobSpecs的逻辑。

Scheduler控制Cron和RunAt JobSpecs的逻辑。

Store“包含数据库,Config,KeyStore和TxManager的字段,用于保持应用程序状态与数据库同步。”实例化后,store将通过连接到config.go的EthereumURL字段,在配置中设置与以太坊区块链的端口连接 。

Exiter通过特定的退出代码退出Chainlink节点。

Keystore认证:

在ChainlinkApplication对象被实例化之后,发生了对Keystore的认证。 Keystore是你的以太钱包私钥的加密版本,可通过输入密码进行解密。Keystore一旦解密,它将允许你签署交易并从你的账户转移资金。 如果在Chainlink节点启动时在命令行中输入了密码,Chainlink将校验它是否可以解锁文件系统中所有可配置目录中的Keystore文件。如果密码无法解锁所有Keystore文件,那么Chainlink不会启动。如果在启动节点时未提供密码,则会提示用户输入密码。 如果Keystore文件不存在,则将创建Keystore文件。为了行使LINK token的权益,用户必须将Keystore导入到Chainlink节点中。

ChainlinkApplication Start method:

一旦认证完成,ChainlinkApplication Start模式被调用 - 这将启动Chainlink节点。

该method创建一个通道,当观察到SIGINT或SIGTERM系统调用时会收到提示(常见的终止信号,如按Ctrl+C或在进程上调用kill命令将触发此系统调用)。 然后Goroutine开始在channel上监听 - 如果观察到终止信号,ChainLinkApplication将调用一个method,使其能够正常退出。

然后,该method调用Store,HeadTracker和SchedulerStart method。

StoreStart method:

Store Start method在TxManager当前活动帐户中设置第一个解锁keystore的帐户。 TxManager负责与以太坊区块链连接(发送已签署的交易,在当前交易中注入Gas等)。 除了设置当前活动账户之外,Store Start method还会设置当前活动账户的nonce(来自账户的Txid)。nonce需要从当前活动账户发送交易。

HeadTrackerStart method:

Headtracker订阅了以太坊区块链上新产生的区块。 当以太坊区块链上出现一个新区块时,BlockHeader object(表示以太坊区块链上block header的数据结构)将被传送到一个名为headers的channel中。

Headtracker connect method将从本地数据库查询所有启动JobSpec的日志(RunLog和Ethlog)——对于每个JobSpec,将调用NewRPCLogSubscription method。

此method将创建新日志订阅——新日志将被传送到sub.logs通道。 Goroutine listenToLogs将通过监听sub.logs channel来回填日志并处理新的日志条目。

回填日志是Chainlink的一个重要特性,因为这意味着如果Chainlink节点停机/退出,它将能够处理在Chainlink节点停机期间发生的日志/数据请求。 “处理日志”意味着验证日志的类型是否正确——在RunLog验证中,日志的签名要与Oracle.sol中发出的签名相同,日志中的作业ID要与Runlog的作业ID相匹配处理日志的实例,并且LINK余额满足发起请求的最低值。 如果验证通过,则开始工作。

节点开始工作后会发生什么?

JobSpecs如上图所示。Chainlink按顺序在JobSpec中运行每个任务,在每次成功运行任务后保存结果。 每个任务运行都映射到特定适配器的运行。Chainlink定义RunResult数据结构,该数据结构保存当前TaskRun的结果。 每个TaskRun的结果都会设置在RunResult的Data字段中,并发送到当前的TaskRun。 这样做是为了使当前TaskRun可以使用之前TaskRun运行的结果。

Chainlink定义了每个任务在运行前必须符合SLA的最小确认数量(用于日志启动的JobSpecs)。 经过x次确认后,Chainlink可以确信启动作业的区块是有效的(如果区块仍然存在)。如果运行不符合最小数量的确认,则暂停并进入挂起确认状态。

其中一个最重要的适配器称为EthTx。 这是负责将链下数据发送到链上智能合约的适配器。 在RunLog场景中,EthTx(如果在JobSpec中指定的话)将发送请求的唯一ID(从Oracle.sol中的requestData函数中发出的RunRequest日志中读取 – 以下图为例),并将Data变量的结果发送给 位于Oracle.sol中的fulfillData函数。

一旦将这些数据发送到fulfillDataFunction,该函数将把请求的唯一ID移入回调映射中。回调映射将返回包含必要信息的Callback数据结构,以将数据发送到正确的智能合约地址和函数。从Callback数据结构中获得必要的信息后,数据将被发送到请求的智能合约!请求就是这样满足了!

如何重启卡在未决确认状态中的任务?

回到HeadTracker Start method,该method最后要做的就是启动一个名为listenToNewHeads的Goroutine,它将监听headers channel。 Chainlink需要监听新的heads,因为它需要将新的heads存入数据库,以便获知最后处理的head。

如果节点关闭,然后启动,它会读取最后处理的head。 另外,这个Goroutine将使Chainlink处理所有在之前运行时,因不符合SLA最小块确认而未完成的任务。 如果任务运行满足其SLA最小数据块确认,Chainlink节点将启动另一个任务运行。

Scheduler Start method

Scheduler Start method将确保Cron和RunAt JobSpecs按照指定的时间表运行。

Cron:“Cron启动器是安排重复任务的简单方法,使用标准的cron语法和额外的字段来指定第二级粒度。”

RunAt:“RunAt启动程序会在指定的时间触发一个关闭的任务运行。”

对于Cron任务,Chainlink使用这个库。可以在AddFunc和run methods中看到该库的主要功能。在Scheduler Start method中,首要操作之一是调用Cron库的Start method,该method将启动GoRoutine中的Cron库runmethod。

每个CronJobSpec将使用AddJob method添加到Cron库中,该method将调用Cron库AddFuncmethod。 AddFunc method将函数及其重复计划作为输入。 Cron库会在每个固定的周期间隔调用传递给AddFunc method的函数 - 在这种情况下,传入函数调用BeginRun method(开始新的任务运行)。

AddFunc method调用会最终将输入的(函数及其重复计划)封装在名为Entry的对象内。 Entry对象将被添加到Cron库Entries集合中。 在添加到Entries集合后,Entry对象将被传送到补充channel。

Cron补充channel用于Cron run method。

Run method重复条目集合中的所有条目并查找每个函数的下一个计划时间。从这些时间中,run method会查找最早的计划时间点(如果有的话)。 从最早的预定时间开始,该method将创建一个定时器对象(称为timer),当定时器到期时,该对象将流入一个channel(称为C)。

然后函数输入一个select语句,允许它在多个channel上等待。第一个channel将是下一个预定函数运行的timer channel(上面讨论过)。 另一个channel将成为笔者在AddFunc method中讨论的补充channel(在添加新的cron时传送)。 下一个频道是快照channel,当有人请求库中当前存在的所有条目的“快照”时,该快照channel将被传送。最后一个channel将是关闭channel,当Scheduler / Cron库被请求停止时将会调用该channel。

当定时器对象通道被传送时,将执行所有具有小于当前时间的“下一个”运行时间条目的函数。执行后,条目(们)将被赋予新的下一个运行时间,根据类别安排运行时间,select语句将再次开始监听所有channel。

当添加channel(从AddFunc method)时,该method将停止当前计时器(因为添加的函数的运行时间可能比当前最早运行时间更早)。之后,从最早的运行时间创建一个新的计时器对象,并且select语句再次在所有channel上开始监听。

当存在对所有cron条目的请求时,快照channel将被传送。

当Scheduler / Cron库被请求停止时,关闭channel将被调用,它将结束定时器对象并返回。

RunAt函数与Cron任务非常相似,只是它们只运行一次。

对于每个RunAt JobSpec,Chainlink将启动一个新的Gorutine,它将使用select语句在两个channel上等待(就像在Cron库中一样)。 Chainlink想要正常退出时,第一个channel将被传送。 当RunAt JobSpec预定运行时,第二个channel将被传送。 当第二个channel被传送时,Chainlink将通过调用BeginRun method执行在JobSpec中格式化的运行。

=====分隔符=====

总之,通过让Chainlink节点监听特定的以太坊日志(请求),解析日志中的数据,运行连续的任务管道,然后将链接数据的结果发送管道到请求的智能合约。

干得漂亮!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180517G0AYSI00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券