chainlink源码分析
介绍
这个服务是链下节点为用户提供可验证的 VRF 随机数。涉及两个合约,Consumer 和 Coordinator ,其中 Consumer 比较简单,核心的方法都是在 Coordinator 合约上的,会主要针对这个合约讲解。
而使用 VRF 服务,有两种方式
- VRF Direct funding 和使用 AnyAPI 的操作差不多,每次 request 都需要先进入到 LInkToken 里面去交钱。
- Subscription Method 订阅制度,通过在 Coordinator 上创建一个订阅账户,并通过 Linktoken 给订阅账户预充一笔钱,这样每次使用服务就不用去 LinkToken 里面走一圈了,直接在 Coordinator 上扣就行了。而且可以将多个 Consumer 绑定到同一个订阅账户里,这样多个 Consumer 就可以公用一个订阅账户的预充钱去请求 VRF 服务了。对于资产管理方便了很多。
前者比较简单,我们会重点分析 Subscription Method 。
角色
在 chainlink 的 VRF 设计当中,主要有四种角色。
- Consumer 实际请求和消费 VRF 随机数的合约
- Subscriber VRF服务的订阅账户,其中需要预充钱,同时会挂靠多个 Consumer, 所有 Consumer 的消费都是由 Subscriber 账户统一支付的。
- Coordinator 协调和管理订阅服务和 VRF 服务的地方,用户和Oracle之间的交互都是由Coordinator作为中间人来进行的。扣费,转发VRF请求,随机数验证,随机数callback都发生在这里。
- 订阅板块 资金管理方便,预充钱省gas 用户可以创建订阅账户,用户往这个订阅账户里面预充钱,这样在使用 VRF 服务的时候,就不用走 LinkToken 转钱了,而是直接在订阅账户里面扣钱就行。同时一个订阅账户可以挂靠很多个消费者,这些消费者共用这个订阅账户里的资金,这样就不用为多个合约分别进行资金管理了,而是通过同一个订阅账户来管理资金。
- VRF协调板块
- 接受请求,发出 VRF Event
- 接收链下节点提交的 VRF 随机数,并做验证
- 费用处理
- 通过回调函数,将 VRF 随机数返回给消费者
- Oracle管理板块 Oracle 通过 Coordinator 来接任务,所以他们在 Coordinator上也是有 balances 的,他们每完成一次任务,Coordinator 都会发钱到他们的 balance 里。Oracle 随时可以将他们 balance 里的钱取成 Link 代币到他们自己的账户里面。
- Oracle 提供 VRF 服务的 Oracle。需要在 Coordinator 中注册。收益由 Coordinator 记录,可以随时去 Coordinator 中将收益取出来。
核心功能清单
- 创建订阅
- 取消订阅
- 给订阅账户充钱
- 添加Consumer到订阅账户
- Oracle 加入
- Oracle 退出
- Oracle 取钱
- 请求随机数
- 随机数验证后回填
Consumer 代码解析
这一部分,主要是一个规则协议,用户需要实现两个方法
- requestRandomness 用来申请随机数的方法
- fulfillRandomness 用来消费随机数的方法
requestRandomness
不通过订阅制度,直接选择交钱请求随机数时才会使用到这个方法。 使用订阅制度则不需要这个方法,只需要实现好自己的 callback 方法就好。
function requestRandomness(bytes32 _keyHash, uint256 _fee) internal returns (bytes32 requestId) {
// 给 LinkToken 合约转钱,并调用 Coordinator 的 onTokentransfer 方法
LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, USER_SEED_PLACEHOLDER));
// This is the seed passed to VRFCoordinator. The oracle will mix this with
// the hash of the block containing this request to obtain the seed/input
// which is finally passed to the VRF cryptographic machinery.
// 传递给 Corrdinator 的 seed,Coordinator 还会将 block hash 组合进去
uint256 vRFSeed = makeVRFInputSeed(_keyHash, USER_SEED_PLACEHOLDER, address(this), nonces[_keyHash]);
// nonces[_keyHash] must stay in sync with
// VRFCoordinator.nonces[_keyHash][this], which was incremented by the above
// successful LINK.transferAndCall (in VRFCoordinator.randomnessRequest).
// This provides protection against the user repeating their input seed,
// which would result in a predictable/duplicate output, if multiple such
// requests appeared in the same block.
nonces[_keyHash] = nonces[_keyHash] + 1;
return makeRequestId(_keyHash, vRFSeed);
}
fulfillRandomWords
不管是通过订阅还是直接直接转钱哪种方式,都需要实现这里来接收并使用随机数。
// 这个帮你写好了,回传先调这里,然后再导向你自己写的 fulfillRandomWords 方法
function rawFulfillRandomness(bytes32 requestId, uint256 randomness) external {
require(msg.sender == vrfCoordinator, "Only VRFCoordinator can fulfill");
fulfillRandomness(requestId, randomness);
}
// 自己实现这个方法去决定怎么使用这个随机数
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;
Coordinator 状态变量
// 一个订阅账户最多只能挂靠100个消费者
uint16 public constant MAX_CONSUMERS = 100;
// LinkToken 合约
LinkTokenInterface public immutable LINK;
// 一个链上的 datafeed 合约,用于访问 eth 市场价,并以此计算消耗的 gas 的实际价值
AggregatorV3Interface public immutable LINK_ETH_FEED;
// 可以获取 blockhash 值的合约
BlockhashStoreInterface public immutable BLOCKHASH_STORE;
// (consumer, subID) -> nonce
// 记录每一个 consumer 在 subId 指定的订阅账户上的 nonce 值
mapping(address => mapping(uint64 => uint64)) /* consumer */ /* subId */ /* nonce */
private s_consumers;
// 记录订阅者的配置信息
mapping(uint64 => SubscriptionConfig) /* subId */ /* subscriptionConfig */
private s_subscriptionConfigs;
struct SubscriptionConfig {
address owner; // 拥有者可以 充值/接受退款/取消订阅
address requestedOwner; // 这个是在订阅所有权转移的流程中会用到
address[] consumers; // 这个订阅里绑定的 consumers
}
// 记录订阅者的账户信息
mapping(uint64 => Subscription) /* subId */ /* subscription */
private s_subscriptions;
struct Subscription {
// There are only 1e9*1e18 = 1e27 juels in existence, so the balance can fit in uint96 (2^96 ~ 7e28)
uint96 balance; // Common link balance used for all consumer requests.
uint64 reqCount; // 这个订阅账户一共发了多少个请求,会用来对你的消费等级做评估,不同等级收费不一样。发送的请求越多,对你的收费就会越低,有点像会员等级制度
}
// 用来记录最新订阅者的id的,主要是用在新增订阅者的时候可以快速计算其id
uint64 private s_currentSubId;
// 所有订阅账户预充的总的link币数量
uint96 private s_totalBalance;
// 200s内需要执行fulfill
uint16 public constant MAX_REQUEST_CONFIRMATIONS = 200;
// 一次最多请求500个随机数
uint32 public constant MAX_NUM_WORDS = 500;
// 额外检查需要支付的 gas fee
uint256 private constant GAS_FOR_CALL_EXACT_CHECK = 5_000;
// keyhash 到 oracle 地址的映射
mapping(bytes32 => address) /* keyHash */ /* oracle */
private s_provingKeys;
// 所有的 keyhash
bytes32[] private s_provingKeyHashes;
// oracle 地址账户拥有多少钱,他们到时候可以提走的
mapping(address => uint96) /* oracle */ /* LINK balance */
private s_withdrawableTokens;
// 每个请求对应的请求信息的hash,主要是在oracle提交结果的时候,做request的匹配性验证
mapping(uint256 => bytes32) /* requestID */ /* commitment */
private s_requestCommitments;
// 每单位link值多少wei
int256 private s_fallbackWeiPerUnitLink;
// 配置信息
Config private s_config;
struct Config {
uint16 minimumRequestConfirmations;
uint32 maxGasLimit;
// Reentrancy protection.
bool reentrancyLock;
// 这个时间用来判断我们通过 eth data feed 服务获取到的 link->eth 价格是不是一个很久的数据
// 如果是很久的,我们会使用默认的 s_fallbackWeiPerUnitLink 来作为转换标准
uint32 stalenessSeconds;
// Gas to cover oracle payment after we calculate the payment.
// We make it configurable in case those operations are repriced.
// 这个组要是用来计算用户需要为产生的gas费交多少link的时候,我们发生实际的预充账户转钱,必定发生在计算费用之后
// 所以转钱这几个步骤产生的gas费用,我们就只能预估了。这个就是预估值。
uint32 gasAfterPaymentCalculation;
}
// 费用配置
// 做消费等级判断的,一个订阅账户消费的request越多,它的消费等级就越高,单词收费就越少
FeeConfig private s_feeConfig;
struct FeeConfig {
uint32 fulfillmentFlatFeeLinkPPMTier1;
uint32 fulfillmentFlatFeeLinkPPMTier2;
uint32 fulfillmentFlatFeeLinkPPMTier3;
uint32 fulfillmentFlatFeeLinkPPMTier4;
uint32 fulfillmentFlatFeeLinkPPMTier5;
uint24 reqsForTier2;
uint24 reqsForTier3;
uint24 reqsForTier4;
uint24 reqsForTier5;
}
Coordinator 方法分析
构造函数
// 主要为了注册,Corrdindator 会用到的三个外部合约的地址
constructor(
address link, // LinkToken合约,实际发生Link代币交易的地方
address blockhashStore,
address linkEthFeed // 一个 eth 的 data feed 合约
) ConfirmedOwner(msg.sender) {
LINK = LinkTokenInterface(link);
LINK_ETH_FEED = AggregatorV3Interface(linkEthFeed);
BLOCKHASH_STORE = BlockhashStoreInterface(blockhashStore);
}
Oracle相关函数
注册Oracle
/**
* @notice Registers a proving key to an oracle.
* @param oracle address of the oracle
* @param publicProvingKey key that oracle can use to submit vrf fulfillments
*/
function registerProvingKey(address oracle, uint256[2] calldata publicProvingKey) external onlyOwner { // 只有 owner 才能操作
bytes32 kh = hashOfKey(publicProvingKey);
if (s_provingKeys[kh] != address(0)) { // 检查是否注册过
revert ProvingKeyAlreadyRegistered(kh);
}
s_provingKeys[kh] = oracle; // 存储注册信息
s_provingKeyHashes.push(kh); // 将 key 放到列表里
emit ProvingKeyRegistered(kh, oracle); // 抛出 event
}
// 生成 key hash
function hashOfKey(uint256[2] memory publicKey) public pure returns (bytes32) {
return keccak256(abi.encode(publicKey));
}
取消注册 Oracle
/**
* @notice Deregisters a proving key to an oracle.
* @param publicProvingKey key that oracle can use to submit vrf fulfillments
*/
function deregisterProvingKey(uint256[2] calldata publicProvingKey) external onlyOwner { // 只有 owner 才能操作
bytes32 kh = hashOfKey(publicProvingKey);
address oracle = s_provingKeys[kh];
if (oracle == address(0)) { // 检查是否存在
revert NoSuchProvingKey(kh);
}
delete s_provingKeys[kh]; // 删除注册信息
// 从 key 列表里删除
for (uint256 i = 0; i < s_provingKeyHashes.length; i++) {
if (s_provingKeyHashes[i] == kh) {
bytes32 last = s_provingKeyHashes[s_provingKeyHashes.length - 1];
// Copy last element and overwrite kh to be deleted with it
s_provingKeyHashes[i] = last;
s_provingKeyHashes.pop();
}
}
emit ProvingKeyDeregistered(kh, oracle); // 抛出事件
}
subscription相关
注册订阅
这里会在 Coordinator 上面创建一个订阅账户。大概看一下,就是对 subscription 相关的状态变量进行操作。
function createSubscription() external override nonReentrant returns (uint64) {
s_currentSubId++;
uint64 currentSubId = s_currentSubId;
address[] memory consumers = new address[](0);
s_subscriptions[currentSubId] = Subscription({balance: 0, reqCount: 0});
s_subscriptionConfigs[currentSubId] = SubscriptionConfig({
owner: msg.sender, // 这个账号的 owner 就是交易发起人,这个 owner 也会作为订阅账号注销后,退钱的账户
requestedOwner: address(0), // 发生订阅账户所有权转移后会使用到
consumers: consumers
});
emit SubscriptionCreated(currentSubId, msg.sender);
return currentSubId;
}
更换订阅账户的 Owner
只有原本的 Owner 才有权限发起这个操作进行订阅账户的所有权转移
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner)
external
override
onlySubOwner(subId)
nonReentrant
{
// Proposing to address(0) would never be claimable so don't need to check.
if (s_subscriptionConfigs[subId].requestedOwner != newOwner) {
s_subscriptionConfigs[subId].requestedOwner = newOwner;
emit SubscriptionOwnerTransferRequested(subId, msg.sender, newOwner);
}
}
发起转移后,其实只是记录了一下 requestedOwner 。要彻底完成转移,还需要 requestedOwner 自己去接收所有权,通过 acceptSubscriptionOwnerTransfer 函数
function acceptSubscriptionOwnerTransfer(uint64 subId) external override nonReentrant {
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
// 发起接收所有权,必须是记录中的 requestedOwner
if (s_subscriptionConfigs[subId].requestedOwner != msg.sender) {
revert MustBeRequestedOwner(s_subscriptionConfigs[subId].requestedOwner);
}
// 这里才会完成所有权转移,修改 owner 字段
address oldOwner = s_subscriptionConfigs[subId].owner;
s_subscriptionConfigs[subId].owner = msg.sender;
s_subscriptionConfigs[subId].requestedOwner = address(0);
emit SubscriptionOwnerTransferred(subId, oldOwner, msg.sender);
}
取消订阅
有两个入口,一个是 Coordinator 合约发起删除订阅 ,一个是 订阅账户自己发起删除订阅 。主要就是将注册信息删除,需要把 subscription 和其中的 consumers 相关的信息删除,还要将订阅账户里面剩余的钱通过 LinkToken 合约退还给 Owner 。
// Coordinator 合约删除订阅账户
function ownerCancelSubscription(uint64 subId) external onlyOwner {
// 检查订阅是否存在
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
cancelSubscriptionHelper(subId, s_subscriptionConfigs[subId].owner);
}
// 订阅账户自己发起删除
function cancelSubscription(uint64 subId, address to) external override onlySubOwner(subId) nonReentrant {
// 该订阅账户是否还有正在操作中的 request
if (pendingRequestExists(subId)) {
revert PendingRequestExists();
}
cancelSubscriptionHelper(subId, to);
}
// 实际进行删除操作的方法
function cancelSubscriptionHelper(uint64 subId, address to) private nonReentrant {
SubscriptionConfig memory subConfig = s_subscriptionConfigs[subId];
Subscription memory sub = s_subscriptions[subId];
uint96 balance = sub.balance;
// Note bounded by MAX_CONSUMERS;
// If no consumers, does nothing.
for (uint256 i = 0; i < subConfig.consumers.length; i++) {
// 先把该订阅中的 consumers 都删了
delete s_consumers[subConfig.consumers[i]][subId];
}
// 在删除订阅
delete s_subscriptionConfigs[subId];
delete s_subscriptions[subId];
// 退钱
s_totalBalance -= balance;
if (!LINK.transfer(to, uint256(balance))) {
revert InsufficientBalance();
}
// 抛出事件
emit SubscriptionCanceled(subId, to, balance);
}
订阅账户是否还存在进行中的请求
这个函数主要是在取消订阅的时候使用。如果该订阅账户还存在进行中的任务,则暂时还无法取消订阅
function pendingRequestExists(uint64 subId) public view override returns (bool) {
SubscriptionConfig memory subConfig = s_subscriptionConfigs[subId];
for (uint256 i = 0; i < subConfig.consumers.length; i++) {
for (uint256 j = 0; j < s_provingKeyHashes.length; j++) {
(uint256 reqId, ) = computeRequestId(
s_provingKeyHashes[j],
subConfig.consumers[i],
subId,
s_consumers[subConfig.consumers[i]][subId]
);
if (s_requestCommitments[reqId] != 0) {
return true;
}
}
}
return false;
}
消费者账号管理
添加消费者到订阅账户
只有订阅账户的 owner 才有权限添加 consumer 进来
function addConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
// Already maxed, cannot add any more consumers.
// 看看容量还够不够
if (s_subscriptionConfigs[subId].consumers.length == MAX_CONSUMERS) {
revert TooManyConsumers();
}
// 是否已经存在了
if (s_consumers[consumer][subId] != 0) {
// Idempotence - do nothing if already added.
// Ensures uniqueness in s_subscriptions[subId].consumers.
return;
}
// 添加
// Initialize the nonce to 1, indicating the consumer is allocated.
s_consumers[consumer][subId] = 1;
s_subscriptionConfigs[subId].consumers.push(consumer);
emit SubscriptionConsumerAdded(subId, consumer);
}
把消费者从订阅账户移除
只有订阅账户的 owner 才有权限将 consumer 移除
function removeConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
// 看consumer存不存在
if (s_consumers[consumer][subId] == 0) {
revert InvalidConsumer(subId, consumer);
}
// Note bounded by MAX_CONSUMERS
address[] memory consumers = s_subscriptionConfigs[subId].consumers;
uint256 lastConsumerIndex = consumers.length - 1;
// 遍历寻找到要移除的consumer
// 将最后一个consumer复制过来覆盖要删除的consumer
// 将最后一个移除
for (uint256 i = 0; i < consumers.length; i++) {
if (consumers[i] == consumer) {
address last = consumers[lastConsumerIndex];
// Storage write to preserve last element
s_subscriptionConfigs[subId].consumers[i] = last;
// Storage remove last element
s_subscriptionConfigs[subId].consumers.pop();
break;
}
}
delete s_consumers[consumer][subId];
emit SubscriptionConsumerRemoved(subId, consumer);
}
配置相关
设置Config
对 s_config ,s_feeConfig 还有 s_fallbackWeiPerUnitLink 进行更改
/**
* @notice Sets the configuration of the vrfv2 coordinator
* @param minimumRequestConfirmations global min for request confirmations
* @param maxGasLimit global max for request gas limit
* @param stalenessSeconds if the eth/link feed is more stale then this, use the fallback price
* @param gasAfterPaymentCalculation gas used in doing accounting after completing the gas measurement
* @param fallbackWeiPerUnitLink fallback eth/link price in the case of a stale feed
* @param feeConfig fee tier configuration
*/
function setConfig(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitLink,
FeeConfig memory feeConfig
) external onlyOwner {
if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) {
revert InvalidRequestConfirmations(
minimumRequestConfirmations,
minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
if (fallbackWeiPerUnitLink <= 0) {
revert InvalidLinkWeiPrice(fallbackWeiPerUnitLink);
}
s_config = Config({
minimumRequestConfirmations: minimumRequestConfirmations,
maxGasLimit: maxGasLimit,
stalenessSeconds: stalenessSeconds,
gasAfterPaymentCalculation: gasAfterPaymentCalculation,
reentrancyLock: false
});
s_feeConfig = feeConfig;
s_fallbackWeiPerUnitLink = fallbackWeiPerUnitLink;
emit ConfigSet(
minimumRequestConfirmations,
maxGasLimit,
stalenessSeconds,
gasAfterPaymentCalculation,
fallbackWeiPerUnitLink,
s_feeConfig
);
}
Getter 函数
大概看看就好了,就是获取一下状态变量
// 获取 config
function getConfig()
external
view
returns (
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation
)
{
return (
s_config.minimumRequestConfirmations,
s_config.maxGasLimit,
s_config.stalenessSeconds,
s_config.gasAfterPaymentCalculation
);
}
// 获取 fee config
function getFeeConfig()
external
view
returns (
uint32 fulfillmentFlatFeeLinkPPMTier1,
uint32 fulfillmentFlatFeeLinkPPMTier2,
uint32 fulfillmentFlatFeeLinkPPMTier3,
uint32 fulfillmentFlatFeeLinkPPMTier4,
uint32 fulfillmentFlatFeeLinkPPMTier5,
uint24 reqsForTier2,
uint24 reqsForTier3,
uint24 reqsForTier4,
uint24 reqsForTier5
)
{
return (
s_feeConfig.fulfillmentFlatFeeLinkPPMTier1,
s_feeConfig.fulfillmentFlatFeeLinkPPMTier2,
s_feeConfig.fulfillmentFlatFeeLinkPPMTier3,
s_feeConfig.fulfillmentFlatFeeLinkPPMTier4,
s_feeConfig.fulfillmentFlatFeeLinkPPMTier5,
s_feeConfig.reqsForTier2,
s_feeConfig.reqsForTier3,
s_feeConfig.reqsForTier4,
s_feeConfig.reqsForTier5
);
}
function getTotalBalance() external view returns (uint256) {
return s_totalBalance;
}
function getFallbackWeiPerUnitLink() external view returns (int256) {
return s_fallbackWeiPerUnitLink;
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function getRequestConfig()
external
view
override
returns (
uint16,
uint32,
bytes32[] memory
)
{
return (s_config.minimumRequestConfirmations, s_config.maxGasLimit, s_provingKeyHashes);
}
Fund相关
recoverFunds 总账户维护
/**
* @notice Recover link sent with transfer instead of transferAndCall.
* @param to address to send link to
*/
function recoverFunds(address to) external onlyOwner {
// 该合约实际在 linkToken 当中拥有的 link 代币数量
uint256 externalBalance = LINK.balanceOf(address(this));
// 订阅者,oracle 等账户在该合约中记录的拥有的 link 代币数量
uint256 internalBalance = uint256(s_totalBalance);
// 一般来说上面两个值是维持相等的
// 如果实际拥有的 token 合约记录的总 token 少,说明出问题了
if (internalBalance > externalBalance) {
// 抛出异常
revert BalanceInvariantViolated(internalBalance, externalBalance);
}
订阅账户充钱
这个充钱的流程会走 LinkToken 合约,用户通过其中的 transferAndCall 函数来调用 Coordinator 合约中的 onTokenTransfer 方法。整体比较简单,就是用户通过 LinkToken 合约给 Coordinator 转了多少钱,我就给他的账户里面增加相应的金额即可。
function onTokenTransfer(
address, /* sender */
uint256 amount,
bytes calldata data
) external override nonReentrant {
if (msg.sender != address(LINK)) {
revert OnlyCallableFromLink();
}
if (data.length != 32) {
revert InvalidCalldata();
}
uint64 subId = abi.decode(data, (uint64));
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
// We do not check that the msg.sender is the subscription owner,
// anyone can fund a subscription.
uint256 oldBalance = s_subscriptions[subId].balance;
s_subscriptions[subId].balance += uint96(amount);
s_totalBalance += uint96(amount);
emit SubscriptionFunded(subId, oldBalance, oldBalance + amount);
}
Oracle取走收益
就是从 coordinator 记录的 oracle 的 balance 里提走指定数量的 link 币。代码很简单,看看就好
function oracleWithdraw(address recipient, uint96 amount) external nonReentrant {
// 钱够不够
if (s_withdrawableTokens[msg.sender] < amount) {
revert InsufficientBalance();
}
// 扣掉取走的钱
s_withdrawableTokens[msg.sender] -= amount;
s_totalBalance -= amount;
// 去 LinkToken 合约转账
if (!LINK.transfer(recipient, amount)) {
revert InsufficientBalance();
}
}
VRF业务流程相关
请求随机数
没有修饰器做权限限制,所有外部合约都可以调这个方法。
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function requestRandomWords(
bytes32 keyHash, // 指明哪一个 oracle 来做
uint64 subId, // 指明自己对应的订阅账户
uint16 requestConfirmations, //
uint32 callbackGasLimit, // 回调的gas消费上限
uint32 numWords // 请求几个随机数
) external override nonReentrant returns (uint256) {
// 订阅账户是否存在
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
// 检查这个消费者是否真的在这个订阅账户里面
uint64 currentNonce = s_consumers[msg.sender][subId];
if (currentNonce == 0) {
revert InvalidConsumer(subId, msg.sender);
}
// Input validation using the config storage word.
// 判断传入的 requestConfirmations 是否符合要求
if (
requestConfirmations < s_config.minimumRequestConfirmations || requestConfirmations > MAX_REQUEST_CONFIRMATIONS
) {
revert InvalidRequestConfirmations(
requestConfirmations,
s_config.minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
// 检查传入的 gaslimit 是否超出设定的规范
if (callbackGasLimit > s_config.maxGasLimit) {
revert GasLimitTooBig(callbackGasLimit, s_config.maxGasLimit);
}
// 检查传入的 numWords 是否超出设定的规范
if (numWords > MAX_NUM_WORDS) {
revert NumWordsTooBig(numWords, MAX_NUM_WORDS);
}
// 这里面没有对 keyHash 做验证,所以用户是可以指定一个不存在的 keyHash ,但是他们不会被 fulfill
// Note we do not check whether the keyHash is valid to save gas.
// The consequence for users is that they can send requests
// for invalid keyHashes which will simply not be fulfilled.
uint64 nonce = currentNonce + 1;
// 计算 requestId 和 preSeed ,这里只是 preSeed
// 最终的 Seed ,还会往 preSeed 里面填充一个 BlockHash。
(uint256 requestId, uint256 preSeed) = computeRequestId(keyHash, msg.sender, subId, nonce);
// 记录这次请求的内容的hash,留待做匹配验证
s_requestCommitments[requestId] = keccak256(
abi.encode(requestId, block.number, subId, callbackGasLimit, numWords, msg.sender)
);
// 抛出随机数请求的event,链下对应节点捕获到了后,会进入随机数生成的阶段
emit RandomWordsRequested(
keyHash,
requestId,
preSeed,
subId,
requestConfirmations,
callbackGasLimit,
numWords,
msg.sender
);
// 更新 consumer 的 nonce
s_consumers[msg.sender][subId] = nonce;
return requestId;
}
// 计算 requestId 和 preSeed,随便看看就好
function computeRequestId(
bytes32 keyHash,
address sender,
uint64 subId,
uint64 nonce
) private pure returns (uint256, uint256) {
uint256 preSeed = uint256(keccak256(abi.encode(keyHash, sender, subId, nonce)));
return (uint256(keccak256(abi.encode(keyHash, preSeed))), preSeed);
}
返回随机数
/*
* @notice Fulfill a randomness request
* @param proof contains the proof and randomness
* @param rc request commitment pre-image, committed to at request time
* @return payment amount billed to the subscription
* @dev simulated offchain to determine if sufficient balance is present to fulfill the request
*/
function fulfillRandomWords(Proof memory proof, RequestCommitment memory rc) external nonReentrant returns (uint96) {
uint256 startGas = gasleft();
// 对传进来的结果进行验证,该函数的详细分析在下面
(bytes32 keyHash, uint256 requestId, uint256 randomness) =
getRandomnessFromProof(proof, rc);
// 用随机数构建指定数量的 randomWords
uint256[] memory randomWords = new uint256[](rc.numWords);
for (uint256 i = 0; i < rc.numWords; i++) {
randomWords[i] = uint256(keccak256(abi.encode(randomness, i)));
}
// 删除 request 的备案
delete s_requestCommitments[requestId];
// 对 Consumer 进行回调,通知随机数结果
VRFConsumerBaseV2 v;
bytes memory resp = abi.encodeWithSelector(v.rawFulfillRandomWords.selector, requestId, randomWords);
// Call with explicitly the amount of callback gas requested
// Important to not let them exhaust the gas budget and avoid oracle payment.
// Do not allow any non-view/non-pure coordinator functions to be called
// during the consumers callback code via reentrancyLock.
// Note that callWithExactGas will revert if we do not have sufficient gas
// to give the callee their requested amount.
s_config.reentrancyLock = true;
// 以指定的 gaslimit 进行回调
bool success = callWithExactGas(rc.callbackGasLimit, rc.sender, resp);
s_config.reentrancyLock = false;
// 记录该订阅账号总共请求了多少次服务
// Increment the req count for fee tier selection.
uint64 reqCount = s_subscriptions[rc.subId].reqCount;
s_subscriptions[rc.subId].reqCount += 1;
// 这部分是在计算,consumer 需要支付多少钱,主要是由两部分组成
// (给oracle的雇佣费 + oracle发起回调交易产生的 gas fee (会换算成link))
uint96 payment = calculatePaymentAmount(
startGas,
s_config.gasAfterPaymentCalculation,
getFeeTier(reqCount),
tx.gasprice
); // 后面有对该函数的详细解析
// 账户余额不够,抛出异常
if (s_subscriptions[rc.subId].balance < payment) {
revert InsufficientBalance();
}
/*
s_config.gasAfterPaymentCalculation 指的是后续操作会消耗的gas费用,这里只能预估了,因为其必须发生在计算完费用之后。
*/
// 订阅账户扣钱
// 提供服务的 oracle 账户加钱
s_subscriptions[rc.subId].balance -= payment;
s_withdrawableTokens[s_provingKeys[keyHash]] += payment;
// Include payment in the event for tracking costs.
emit RandomWordsFulfilled(requestId, randomness, payment, success);
return payment;
}
随机数验证
function getRandomnessFromProof(Proof memory proof, RequestCommitment memory rc)
private
view
returns (
bytes32 keyHash,
uint256 requestId,
uint256 randomness
)
{
// 计算 keyHash
keyHash = hashOfKey(proof.pk);
// 检查是不是注册了的oracle
address oracle = s_provingKeys[keyHash];
if (oracle == address(0)) {
revert NoSuchProvingKey(keyHash);
}
// 计算 requestId
requestId = uint256(keccak256(abi.encode(keyHash, proof.seed)));
// 检查这个 request 有没有备案
bytes32 commitment = s_requestCommitments[requestId];
if (commitment == 0) {
revert NoCorrespondingRequest();
}
// 通过备案的hash,检查信息是否一致
if (
commitment != keccak256(abi.encode(requestId, rc.blockNum, rc.subId, rc.callbackGasLimit, rc.numWords, rc.sender))
) {
revert IncorrectCommitment();
}
// 获取指定高度的 blockhash
bytes32 blockHash = blockhash(rc.blockNum);
if (blockHash == bytes32(0)) {
// 通过 BLOCKHASH_STORE (这是另一个合约) 获取 blcokhash
blockHash = BLOCKHASH_STORE.getBlockhash(rc.blockNum);
if (blockHash == bytes32(0)) {
revert BlockhashNotInStore(rc.blockNum);
}
}
// The seed actually used by the VRF machinery, mixing in the blockhash
// 通过 preSeed (proof.seed 是 preSeed) 和 blockhash 组合成最终 seed
uint256 actualSeed = uint256(keccak256(abi.encodePacked(proof.seed, blockHash)));
// 验证vrf随机数 (公钥+seed来验证)
randomness = VRF.randomValueFromVRFProof(proof, actualSeed); // Reverts on failure
}
费用计算
涉及两个函数
- getFeeTier
根据订阅账户记录的发起的请求数量,有不同的收费级别。这个收费级别的设置是在
FeeConfig s_feeConfig
中设置的。function getFeeTier(uint64 reqCount) public view returns (uint32) { FeeConfig memory fc = s_feeConfig; if (0 <= reqCount && reqCount <= fc.reqsForTier2) { return fc.fulfillmentFlatFeeLinkPPMTier1; } if (fc.reqsForTier2 < reqCount && reqCount <= fc.reqsForTier3) { return fc.fulfillmentFlatFeeLinkPPMTier2; } if (fc.reqsForTier3 < reqCount && reqCount <= fc.reqsForTier4) { return fc.fulfillmentFlatFeeLinkPPMTier3; } if (fc.reqsForTier4 < reqCount && reqCount <= fc.reqsForTier5) { return fc.fulfillmentFlatFeeLinkPPMTier4; } return fc.fulfillmentFlatFeeLinkPPMTier5; }
- calculatePaymentAmount
function calculatePaymentAmount( uint256 startGas, // 这里传入回调开始的时候剩余的初始 gas uint256 gasAfterPaymentCalculation, // 帐户付款这些相关的操作需要支付的 gas uint32 fulfillmentFlatFeeLinkPPM, // 回调收费 uint256 weiPerUnitGas // gas 到 wei 价格 ) internal view returns (uint96) { // 获取 wei 到 link 的价格 int256 weiPerUnitLink; weiPerUnitLink = getFeedData(); if (weiPerUnitLink <= 0) { revert InvalidLinkWeiPrice(weiPerUnitLink); } // (1e18 juels/link) (wei/gas * gas) / (wei/link) = juels // startGas - gasleft() 计算的时目前消耗的 gas 数量 // gasAfterPaymentCalculation是在设置的后面发生balance钱转移的操作会消耗的gas数量 // 整个计算是为了计算目前消耗的gas值多少link币,这个钱要用户给的 uint256 paymentNoFee = (1e18 * weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft())) / uint256(weiPerUnitLink); // 使用回调服务的雇佣费 uint256 fee = 1e12 * uint256(fulfillmentFlatFeeLinkPPM); if (paymentNoFee > (1e27 - fee)) { revert PaymentTooLarge(); // Payment + fee cannot be more than all of the link in existence. } // 换算成link的gas费 + 雇佣费 = 这次服务总的费用 return uint96(paymentNoFee + fee); }