分类中心化预言机数据源->中心化节点->去中心化网络去中心化预言机多个数据节点形成去中心预言机网络,每个节点都会收集数据,达成共识后输入到区块链上的智能合约技术上避免了单点失败数据上通过网络对多个数据源进行验证DataFeedDataFeed采用的是多层级数据聚合机制
数据源 -> 中心化节点 -> 去中心化网络
多个数据节点形成去中心预言机网络,每个节点都会收集数据,达成共识后输入到区块链上的智能合约
Data Feed 采用的是多层级数据聚合机制,简单来说就是数据会经过多层级的聚合处理。首先价格数据聚合器也就是 CoinGecko、Coinmarketcap 这种的数据网站会在众多原始数据源中进行第一次聚合,这些数据聚合器会通过计算生成一个交易量加权平均价来确保交易数据的质量,接着就是 Chainlink 的节点运营商会从这些数据聚合器中获取一次价格数据并进行第二次聚合,通常来说会选取其中位数价格,但为什么是中位数呢?如果取平均值的话就会出现某个异常极端数据把最终价格给拉高或拉低的情况,因此从理论情况下,一般取中位数可以较好的保障预言机服务的可靠性。
那么最后一层数据聚合就是发生在去中心化预言机网络 DON 层面,所有节点运营商上传其获取的中位数数据以及节点签名并生成一个预言机报告(OCR)在链上发布,该报告涵盖了所有预言机节点上传的数据以及签名。预言机报告(OCR)每次在链上发布,都会先验证每个节点的签名,然后再对所有数据进行一次聚合(如提取中位数),并将其储存在参考合约中,数据一旦储存就难以再被篡改。
并且一个 DON 中至少需要有 2⁄3 的节点上传结果和签名,预言机报告(OCR)才会被链上接受,这类似于以太坊的权益证明共识机制:只有 2⁄3 的节点投票验证通过才可以出块。这种机制很大程度的保障了 Data Feed 最终数据的难以篡改。
因此根据个人理解,Data Feed 服务的数据经过了多重聚合处理并由更可靠的节点运营商以及共识机制进行传输,以此来保障数据的准确性和难以篡改性。
数据源 -> 数据提供商 -> ChainLink 节点 -> ChainLink 合约 -> 用户合约
为什么需要Proxy合约? 因为链下的预言机网络可能会升级
为什么?
VRF 三个函数组成:
合约自动化执行旧有的两种方式:
开发人员通过一个中心化服务器去执行Solidity 的 Cron Job, 监控合约状态, 并且发送交易给链上合约
缺点
单点失败风险
占用团队资源和时间
Bounty 模式 (ETH Alarm Clock)
给交易触发的个人账户提供资金,交易执行成功即可获得经济奖励
缺点
基于去中心化的预言机网络(DON), DON中的节点会主动调用注册的Job, 这些Job被称为UpKeep
它们可以被用来验证条件是否满足,并且基于预设逻辑给智能合约发送交易。可以被看作时一个主动的代理用户和合约交互。
如果链下模拟checkUpKeep返回的是true, DON就会给区块链发送交易,执行performUpKeep
使用步骤:
AutomationCompatible.sol 用户主要维护这个合约,继承AutomationCompatibleInterface 合约, 然后实现这两个函数
checkUpKeep(bytes calldata) external view override
returns (bool upKeepNeed, bytes memory)
{
Check the predefined condition
// 可以把计算量大的逻辑放这里,不更改合约状态的,可以得到其返回值
}
PerformUpKeep(calldata) override { Logics to perform }
AutomationRegister.sol
```solidity
register(config data) {
PendingRequest
}
approve(config data) {
Write upkeep into registry
}
AutomationRegistry
registerUpKeep (config data) {
upKeeps[]
}
checkUpKeep (upkeep id) {
Simulate checkUpkeep
}
performUpKeep(upkeep id, calldata) {
call consumer contract
}
通过 Chainlink Function,开发者可以在去中心化预言机网络中自定义运算逻辑,从而在不将计算负担放在区块链网络上的情况下,满足复杂的智能合约需求。我们来看看 Chainlink Function 是如何运作的:
简而言之,当 Chainlink Function 收到请求后,Chainlink Function 就会在去中心化预言机网络(DON)内通过每个节点独立执行开发者自定义的运算逻辑来获取和处理外部数据,然后利用 OCR 2.0 协议(Chainlink Data Feeds 底层共识协议)对最终数据达成共识并上传到链上。但需要注意的是,尽管 Chainlink Function 确保了数据传输的可靠性、安全性和难以篡改性,但它不能保证数据源的数据真实性,参与方需要对数据来源进行审慎评估。
开发者可以使用 Chainlink Functions 连接到任何公共或私人数据 API,如获取最近的游戏或体育结果,或从 Token Terminal 提取 Web3 协议上的指标数据(如协议收入、用户费用、活跃用户、TVL)
开发者可以通过让 Chainlink Function 获取数据并在智能合约中引用数据之前对其执行高级计算,如从社交媒体 API 检索数据,计算情绪,并在链上报告转换结果以触发操作(如用户收到限量版 NFT)
开发者可以使用 Chainlink Functions 连接到受密码保护的物联网设备数据或企业系统,将 Web3 协议与现有技术和网络集成。例如,开发者可以从智能手表或智能污染传感器获取数据,或将智能合约连接到企业 ERP 系统(如 SAP)以构建供应链应用程序,或连接到 Stripe API 以检查用户的账户余额
开发者可以与 Chainlink Functions 集成,将他们的智能合约连接到外部去中心化数据库,如 IPFS 和 Filecoin。这将允许开发者利用 DON 作为计算层和 IPFS 用于低成本去中心化存储的链下计算 dApp。例如,开发者可以通过使用 Chainlink 函数获取链下投票并将投票结果转发到链上以触发基于智能合约的操作,从而为 DAO 构建去中心化的链下投票系统
开发人员可以连接到 Web2 应用程序并构建复杂运算逻辑的混合智能合约
开发人员可以几乎从任何 Web2 系统(如 AWS S3、Firebase 或 Google Cloud Storage 甚至是特斯拉汽车)获取数据
开发人员可以接入 Al 进行回应(例如,在 OpenAl 的 ChatGPT APl 或为 DeFi 交易生成建议的云提供商)
anyapi 应用页面 https://market.link
查看不同的数据提供商所提供的关于股票的数据,事件型数据,宏观经济数据,身份数据等等
自定义数据源: 市场,事件,金融以及支付数据的API => chainlink 节点 Aapp请求
TransferAndCall 接受一个请求,并也需要接收相应地佣金。再去监视被请求的一方,有没有执行这个请求方所发送的请求,如果被请求方完成了这个请求,才会将佣金打给被请求方
主要组件:
跨链消息:
步骤:
祝福(Blessing):每个风险管理节点会监控每个目标链上提交的消息的所有 Merkle 根。Committing DON 提交这些 Merkle 根。 风险管理节点通过获取源链上的所有消息独立重建 Merkle 树。然后,它检查 Committing DON 提交的 Merkle 根与重建的 Merkle 树的根之间是否匹配。如果两个 Merkle 根匹配,则风险管理节点就会在目标链上祝福该根到风险管理合约。风险管理合约跟踪选票。当达到规定人数时,风险管理合约就会认可受祝福的Merkle 根。
诅咒(Cursing):如果风险管理节点检测到异常,风险管理节点就会诅咒 CCIP 系统。达到指定投票数后,风险管理合约就会将 CCIP 认定为受诅咒的。 CCIP 将自动在该链上暂停并等待合约所有者在可能解除诅咒之前评估状况。风险管理节点暂停 CCIP 的情况有两种:
跨链质押: 将资金质押在一条链,然后在另一条链基于第一条链的质押获取部分流动性
跨链治理: 将治理的提案和投票自动化分配至不容的链
跨链交易: 在多条链上交易资产
新的Dapp类型: 在安全性高的主网重写主要逻辑并质押资产,而通过效率比较高的L2进行计算和存储
企业端应用:验证去中心化,降低在跨链过程中,多方信息传递的风险
Chainlink CCIP通道是源和目标之间的一条独特通道blockchain 。通道是单向的。例如,以太坊主网 => Polygon主网 和Polygon主网 => 以太坊主网是两个不同的通道。
Chainlink 去中心化的 预言机 网络(或称DON )运行Chainlink OCR2 。该协议分轮运行,在此期间可能会就观察到的数据值达成一致。该过程的输出结果是一份由法定参与者证明的报告。然后该报告由参与者之一在链上传输。没有任何一个参与者负责每一轮的传输,并且所有参与者都将尝试以循环方式进行传输,直到传输完成。在 CCIP 的背景下,一条通道包含两个 OCR DON 委员会,用于监控源和目的地之间的交易blockchain :提交 DON和执行 DON 。
![[Pasted image 20241013150407.png]]
sender合约 负责启动 USDC 的转移代币 和数据。其工作原理如下:
sendMessagePayLINK
函数:
stake
函数以及向接收方提供的参数(受益人地址和金额)合约 在目标链上。EVM2AnyMessage
结构构造 CCIP 消息。getFee
计算必要的费用函数。ccipSend
将 CCIP 消息发送到目标链函数。MessageSent
事件。
staker合约 管理 USDC 的质押和赎回代币. 工作原理如下:
stake
函数:
msg.sender
)转移到合约,然后铸造等量的质押代币给受益人。redeem
函数:
处理传入的跨链消息,对其进行处理,并与 Staker 进行交互合约 代表受益人质押 USDC。操作方式如下:
ccipReceive
函数:
processMessage
函数,这是Solidity的try/catch错误处理机制。processMessage
内部,它调用_ccipReceive
函数 以进行进一步的消息处理。_ccipReceive
函数:
stake
合约的stake函数进行low-level调用, 使用编码函数的签名和来自所接收数据MessageReceived
事件。messageId
将添加到s_failedMessages
中,消息内容存储在s_messageContents
中。MessageFailed
事件,允许稍后识别和重新处理失败的消息。retryFailedMessage
函数:
RESOLVED
以防止多次重试。getFailedMessages
函数:
```solidity
/// @notice Sends data and transfer tokens to receiver on the destination chain.
/// @notice Pay for fees in LINK.
/// @dev Assumes your contract has sufficient LINK to pay for CCIP fees.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _beneficiary The address of the beneficiary of the staked tokens on the destination blockchain.
/// @param _amount token amount.
/// @return messageId The ID of the CCIP message that was sent.
function sendMessagePayLINK(
uint64 _destinationChainSelector,
address _beneficiary,
uint256 _amount
)
external
onlyOwner
validateDestinationChain(_destinationChainSelector)
returns (bytes32 messageId)
{
address receiver = s_receivers[_destinationChainSelector];
if (receiver == address(0))
revert NoReceiverOnDestinationChain(_destinationChainSelector);
if (_amount == 0) revert AmountIsZero();
uint256 gasLimit = s_gasLimits[_destinationChainSelector];
if (gasLimit == 0)
revert NoGasLimitOnDestinationChain(_destinationChainSelector);
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(linkToken) means fees are paid in LINK
Client.EVMTokenAmount[]
memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({
token: address(i_usdcToken),
amount: _amount
});
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(receiver), // ABI-encoded receiver address
data: abi.encodeWithSelector(
IStaker.stake.selector,
_beneficiary,
_amount
), // Encode the function selector and the arguments of the stake function
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit
Client.EVMExtraArgsV2({
gasLimit: gasLimit, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: address(i_linkToken)
});
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Chainlink节点通过与以太坊客户端节点连接,通过连接的以太坊客户端来获取链上的用户预言机请求,通过提交交易将获取到的数据写入到链上。所以Chainlink节点必须要依靠一个以太坊客户端节点才能发挥作用。您可以自己搭建以太坊客户端节点,也可以选择以太坊RPC服务提供方的服务,只需要一个WebSocket链接提供给Chainlink节点即可。
Oracle合约是Chainlink节点在区块链上的,它与用户合约直接进行沟通,接收用户的预言机请求,将请求结果通过用户注册的回调函数,写入到用户的合约中。
Oracle合约无需自己编写,我们直接部署Chainlink开源的Oracle合约代码即可。如果用remix部署,只需新建一个包含下面两行代码的文件即可:
pragma solidity 0.6.6; import "https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.6/Oracle.sol";
需要注意的是,oracle合约会暂时接受用户发起Chainlink请求是所支付的LINK费用,所以请务必妥善保存合约owner的私钥。
部署好Oracle合约之后,需要将开放权限给节点的地址,允许节点可以提交事务。
按照文档中的例子,在节点中添加Job(作业)。
打开节点管理界面的Jobs标签页,点击New Job添加新的Job,将每个类型的Job的JSON拷贝到文本框中,用刚刚部署好的Oracle合约地址,替换JSON中的YOUR_ORACLE_CONTRACT_ADDRESS
字段。每创建成功一个Job,都会生成一个Job ID,在Jobs标签页可以找到所有的Jobs。
接下来我们测试一下我们部署的Chainlink服务的可用性。
pragma solidity ^0.6.0;
import "https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/evm-contracts/src/v0.6/ChainlinkClient.sol";
contract APIConsumer is ChainlinkClient {
uint256 public ethereumPrice;
address private oracle;
bytes32 private jobId;
uint256 private fee;
constructor() public {
setPublicChainlinkToken();
// 填入我们刚刚部署好的oracle地址
oracle = 0x5F66a231a29CE1513dc6c16407fDCe9D0aEE2cB0;
// 填入我们刚刚创建的地址
jobId = "4b9b6e7d89154a8d855affed3985aafd";
// 支付给Oracle的最小费用,可用在Configuration页面MINIMUM_CONTRACT_PAYMENT字段查看到
fee = 1 * 10 ** 18; // 1 LINK
}
function requestEthereumPrice() public returns (bytes32 requestId)
{
Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
// 设置所要请求的API地址
request.add("get", "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD");
// API返回结果的解析路径
request.add("path", "USD");
// 为去除小数点将结果乘以的倍数
request.addInt("times", 100);
// 发送请求
return sendChainlinkRequestTo(oracle, request, fee);
}
function fulfill(bytes32 _requestId, uint256 _price) public recordChainlinkFulfillment(_requestId)
{
ethereumPrice = _price;
}
}
部署好用户合约之后,向用户合约转入LINK,调用requestEthereumPrice
方法,就可以测试我们的Chainlink预言机节点是否可以正常工作啦。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!