# WINkLink 可验证随机数服务

# 概览

可验证随机函数(VRF)是公钥版密钥加密哈希,可作为随机数使用。 仅私钥持有者可进行哈希运算,但任何公钥持有者均可验证哈希运算结果是否正确。 VRF 可用于生成安全可靠的随机数。

随机数由 seed(由用户提供)、nonce(VRFCoordinator 合约的私有状态)、区块哈希(请求事件所在区块)和预言机节点的密钥决定。

VRF 的生成过程如下:

  • Dapp 合约发出生成随机数的链上请求;
  • 链下预言机节点监听到该请求后,将生成随机数并附上加密证明以供验证,随后将其回传至预言机合约(VRFCoordinator);
  • 经预言机合约验证后,该随机数将通过回调函数发布至 Dapp 合约。

上述流程可确保预言机运营商、矿工、用户乃至智能合约开发人员等任何人都无法篡改或操纵随机数。

WINkLink VRF 是专为 Dapp 合约设计的公平、可验证的随机数生成来源。 Dapp 合约的开发者可将 WINkLink VRF 用作防篡改随机数生成器(RNG),为任何依赖随机数的应用程序创建可靠的智能合约,包括:

  • 区块链游戏和 NFT
  • 职责和资源的随机分配(例如随机分配法官审理案件)
  • 选择具有代表性的共识机制样本

WINkLink VRF 解决方案由链上和链下两部分组成:

  • VRF Coordinator(链上部分):可与 VRF 服务交互。 当发起随机数请求后,VRF Coordinator 将触发一个事件,并对VRF 服务生成的随机数和证明进行验证。
  • VRF Wrapper(链上部分):对 VRF Coordinator 进行封装,为调用合约提供接口。
  • VRF 服务(链下部分):通过订阅 VRF Coordinator 事件日志监听请求,并根据区块哈希和 nonce 计算随机数, 随后将包含随机数和随机数生成证明的交易发送至 VRFCoordinator。

# 直接资金流

vrf-direct-funding-flow.png

# 订阅流

vrf-subscription-flow.png

# 波场 Nile VRF 合约

为方便开发者使用,Nile 测试网部署了 WinkMid 合约,并封装了 WIN 代币。 开发者可直接使用该合约地址,无需额外部署。 Nile 测试网还提供水龙头地址,可供用户领取 TRX 和 WIN 测试代币。

内容
WIN Token TNDSHKGBmgRx9mDYA9CnxPx55nu672yQw2
WinkMid TLLEKGqhH4MiN541BDaGpXD7MRkwG2mTro
BlockHashStore TBpTbK9KQzagrN7eMKFr5QM2pgZf6FN7KA
VRFCoordinatorV2 TDidecxMyGMgqvYS7nmpMQCZ16HqqV5Fke
VRFV2Wrapper TMNRLGXhe3gzbUyWccuQAKhfVKFyqmLE1W
Fee 10 WIN

测试网水龙头地址: https://nileex.io/join/getJoinPage (opens new window)

# 波场主网 VRF 合约

内容
WIN Token TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7
WinkMid TVMhaFMynYqTRLB1xShYL7wBwdmQHH6bKV
BlockHashStore TRGmef4qUdNJ4xTEL96hToGuMTNst57aS1
VRFCoordinatorV2 TZCz1BcnYviUNDiLvG6ZeuC477YupDgDA7
VRFV2Wrapper TGDVwQRKwtNHrwy4RFG49b2HTHkvWckP5N
Fee 10 WIN

# VRF 请求流程

  1. Dapp 合约调用 VRFV2Wrapper 的 calculateRequestPrice 函数,估算生成随机数需要的交易成本。

  2. Dapp 合约调用 WinkMid 的 transferAndCall 函数,向 Wrapper 支付计算出的请求价格。 此方法会发送 Wink 代币并执行 VRFV2Wrapper 的 onTokenTransfer 逻辑。

  3. VRFV2Wrapper 的 onTokenTransfer 逻辑触发 VRFCoordinatorV2 的 requestRandomWords 函数,并请求随机数。

  4. VRFCoordinatorV2 合约发布 RandomWordsRequested 事件。

  5. VRF 节点捕获此事件并等待指定数量的区块确认, 随后通过 fulfillRandomWords 函数向 VRFCoordinatorV2 合约返回随机值及其证明。

  6. VRFCoordinatorV2 合约在链上对证明进行验证,随即调用 VRFV2Wrapper 的 fulfillRandomWords 函数。

  7. 最后,VRFV2Wrapper 回调 Dapp 合约,完成请求。

# 准备事项

WINkLink 的维护者需要对波场 TRON 有一定的了解,且熟悉智能合约的部署和调用流程。 建议阅读波场相关的官方文档 ,特别是 TronIDE 上进行合约部署的相关文章。

准备节点账户。 建议阅读节点账户准备相关的文档。

# VRFCoordinatorV2 合约

VRFCoordinatorV2 合约部署在波场 TRON 公链上,拥有以下功能:

  • 接收 Dapp 合约的随机数请求并发布 VRFRequest 事件
  • 数据请求发送时会附带WIN转账,作为使用费用
  • 接受 WINkLink 节点提交的随机数和证明
  • 将随机数发送至 Dapp 合约之前,VRFCoordinator 合约会对其证明进行验证
  • 计算履行请求对应的 WINkLink 节点奖励

部署 VRFCoordinator 合约时,构造函数所需的参数如下:

constructor(
address wink,
address blockhashStore,
address winkMid
)

blockHashStore 为 BlockHashStore 地址;win WIN 为 WIN 代币地址;_winkMid 为 WinkMid 合约地址。

TIP

Nile 测试网

# VRFV2Wrapper 合约

VRFV2Wrapper 可简化交互,允许 Dapp 直接调用 VRFCoordinatorV2 合约。

配置参数
keyHash : 节点 keyhash
maxNumWords : 每个 VRF 请求包含的随机数个数上限,目前为 10

# 授权节点账户

节点账户需要授权才能向 VRFCoordinatorV2 合约提交数据,否则将报错。

VRFCoordinatorV2 合约的所有者需要调用以下合约,并将节点账户添加到白名单:

function registerProvingKey(address oracle, uint256[2] calldata publicProvingKey) external onlyOwner

oracle 为注册节点地址,用于接收支付的 WIN 代币 Dapp;publicProvingKey 为注册节点使用的公钥,用于生成随机数。

调用示例:

registerProvingKey(TYmwSFuFuiDZCtYsRFKCNr25byeqHH7Esb,['6273228386041830135141271310112248407537170435188969735053134748570771583756',67273502359025519559461602732298865784327759914690240925031700564257821594585'])

# Dapp 合约

设置 Consumer 合约的主要步骤如下:

  • a) 导入并继承 VRFV2WrapperConsumerBase
// SPDX-License-Identifier: MIT
// An example of a consumer contract that directly pays for each request.
pragma solidity ^0.8.7;

import "./VRFV2WrapperConsumerBase.sol";

contract VRFv2DirectFundingConsumer is VRFV2WrapperConsumerBase{}
  • b) 合约必须执行 fulfillRandomWords 函数,该函数为 VRF 回调函数。 随机数返回合约后,添加处理逻辑。
function fulfillRandomWords(
uint256 _requestId,
uint256[] memory _randomWords
)
  • c) 合约调用 requestRandomness 函数,触发 VRF 请求。
function requestRandomWords()
external
onlyOwner
returns (uint256 requestId)
{
  requestId = requestRandomness(
  msg.sender,
  callbackGasLimit,
  requestConfirmations,
  numWords
);
s_requests[requestId] = RequestStatus({
  paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit, numWords),
  randomWords: new uint256[](0),
  fulfilled: false
});
requestIds.push(requestId);
lastRequestId = requestId;
emit RequestSent(requestId, numWords);
return requestId;
}

# Dapp 合约示例

部署 sample consumer contract VRFv2DirectFundingConsumer.sol。

构造参数:
_winkAddress:Wink 代币合约地址
_winkMid: winkMid 合约地址
_wrapper:VRFV2Wrapper 合约地址
_numWords: 每个 vrf 请求的随机数个数上限

// SPDX-License-Identifier: MIT
// An example of a consumer contract that directly pays for each request.
pragma solidity ^0.8.7;

import "./ConfirmedOwner.sol";
import "./VRFV2WrapperConsumerBase.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract VRFv2DirectFundingConsumer is
VRFV2WrapperConsumerBase,
ConfirmedOwner
{
    address winkAddress;

    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(
        uint256 requestId,
        uint256[] randomWords,
        uint256 payment
    );

    struct RequestStatus {
        uint256 paid; // amount paid in wink
        bool fulfilled; // whether the request has been successfully fulfilled
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus)
    public s_requests; /* requestId --> requestStatus */

    // past requests Id.
    uint256[] public requestIds;
    uint256 public lastRequestId;

    // Depends on the number of requested values that you want sent to the
    // fulfillRandomWords() function. Test and adjust
    // this limit based on the network that you select, the size of the request,
    // and the processing of the callback request in the fulfillRandomWords()
    // function.
    uint32 callbackGasLimit = 0;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFV2Wrapper.getConfig().maxNumWords.
    uint32 numWords;

    constructor(
        address _winkAddress,
        address _winkMid,
        address _wrapper,
        uint32 _numWords
    )
    ConfirmedOwner(msg.sender)
    VRFV2WrapperConsumerBase(_winkAddress, _winkMid, _wrapper) {
        winkAddress = _winkAddress;
        numWords = _numWords;
    }

    function requestRandomWords()
    external
    onlyOwner
    returns (uint256 requestId)
    {
        requestId = requestRandomness(
            callbackGasLimit,
            requestConfirmations,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit, numWords),
            randomWords: new uint256[](0),
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        require(s_requests[_requestId].paid > 0, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(
            _requestId,
            _randomWords,
            s_requests[_requestId].paid
        );
    }

    function getRequestStatus(
        uint256 _requestId
    )
    external
    view
    returns (uint256 paid, bool fulfilled, uint256[] memory randomWords)
    {
        require(s_requests[_requestId].paid > 0, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        return (request.paid, request.fulfilled, request.randomWords);
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ConfirmedOwnerWithProposal.sol";

/**
 * @title The ConfirmedOwner contract
 * @notice A contract with helpers for basic contract ownership.
 */
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
    constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./OwnableInterface.sol";

/**
 * @title The ConfirmedOwner contract
 * @notice A contract with helpers for basic contract ownership.
 */
contract ConfirmedOwnerWithProposal is OwnableInterface {
    address private s_owner;
    address private s_pendingOwner;

    event OwnershipTransferRequested(address indexed from, address indexed to);
    event OwnershipTransferred(address indexed from, address indexed to);

    constructor(address newOwner, address pendingOwner) {
        require(newOwner != address(0), "Cannot set owner to zero");

        s_owner = newOwner;
        if (pendingOwner != address(0)) {
            _transferOwnership(pendingOwner);
        }
    }

    /**
     * @notice Allows an owner to begin transferring ownership to a new address,
   * pending.
   */
    function transferOwnership(address to) public override onlyOwner {
        _transferOwnership(to);
    }

    /**
     * @notice Allows an ownership transfer to be completed by the recipient.
   */
    function acceptOwnership() external override {
        require(msg.sender == s_pendingOwner, "Must be proposed owner");

        address oldOwner = s_owner;
        s_owner = msg.sender;
        s_pendingOwner = address(0);

        emit OwnershipTransferred(oldOwner, msg.sender);
    }

    /**
     * @notice Get the current owner
   */
    function owner() public view override returns (address) {
        return s_owner;
    }

    /**
     * @notice validate, transfer ownership, and emit relevant events
   */
    function _transferOwnership(address to) private {
        require(to != msg.sender, "Cannot transfer to self");

        s_pendingOwner = to;

        emit OwnershipTransferRequested(s_owner, to);
    }

    /**
     * @notice validate access
   */
    function _validateOwnership() internal view {
        require(msg.sender == s_owner, "Only callable by owner");
    }

    /**
     * @notice Reverts if called by anyone other than the contract owner.
   */
    modifier onlyOwner() {
        _validateOwnership();
        _;
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface OwnableInterface {
    function owner() external returns (address);

    function transferOwnership(address recipient) external;

    function acceptOwnership() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./TRC20Interface.sol";
import "./VRFV2WrapperInterface.sol";

/** *******************************************************************************
 * @notice Interface for contracts using VRF randomness through the VRF V2 wrapper
 * ********************************************************************************
 * @dev PURPOSE
 *
 * @dev Create VRF V2 requests without the need for subscription management. Rather than creating
 * @dev and funding a VRF V2 subscription, a user can use this wrapper to create one off requests,
 * @dev paying up front rather than at fulfillment.
 *
 * @dev Since the price is determined using the gas price of the request transaction rather than
 * @dev the fulfillment transaction, the wrapper charges an additional premium on callback gas
 * @dev usage, in addition to some extra overhead costs associated with the VRFV2Wrapper contract.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFV2WrapperConsumerBase. The consumer must be funded
 * @dev with enough WINK to make the request, otherwise requests will revert. To request randomness,
 * @dev call the 'requestRandomness' function with the desired VRF parameters. This function handles
 * @dev paying for the request based on the current pricing.
 *
 * @dev Consumers must implement the fullfillRandomWords function, which will be called during
 * @dev fulfillment with the randomness result.
 */
abstract contract VRFV2WrapperConsumerBase {
    TRC20Interface internal immutable WINK_TOKEN;
    WinkMid internal immutable WINK_MID;
    VRFV2WrapperInterface internal immutable VRF_V2_WRAPPER;

    /**
     * @param _winkMid is the address of WinkMid
   * @param _vrfV2Wrapper is the address of the VRFV2Wrapper contract
   */
    constructor(address _wink, address _winkMid, address _vrfV2Wrapper) {
        WINK_TOKEN = TRC20Interface(_wink);
        WINK_MID = WinkMid(_winkMid);
        VRF_V2_WRAPPER = VRFV2WrapperInterface(_vrfV2Wrapper);
    }

    /**
     * @dev Requests randomness from the VRF V2 wrapper.
   *
   * @param _callbackGasLimit is the gas limit that should be used when calling the consumer's
   *        fulfillRandomWords function.
   * @param _requestConfirmations is the number of confirmations to wait before fulfilling the
   *        request. A higher number of confirmations increases security by reducing the likelihood
   *        that a chain re-org changes a published randomness outcome.
   * @param _numWords is the number of random words to request.
   *
   * @return requestId is the VRF V2 request ID of the newly created randomness request.
   */
    function requestRandomness(
        uint32 _callbackGasLimit,
        uint16 _requestConfirmations,
        uint32 _numWords
    ) internal returns (uint256 requestId) {
        uint64 amount = VRF_V2_WRAPPER.calculateRequestPrice(_callbackGasLimit, _numWords);
        WINK_TOKEN.approve(address(WINK_MID), amount);
        WINK_MID.transferAndCall(
            address(VRF_V2_WRAPPER),
            amount,
            abi.encode(_callbackGasLimit, _requestConfirmations, _numWords)
        );
        return VRF_V2_WRAPPER.lastRequestId();
    }

    /**
     * @notice fulfillRandomWords handles the VRF V2 wrapper response. The consuming contract must
   * @notice implement it.
   *
   * @param _requestId is the VRF V2 request ID.
   * @param _randomWords is the randomness result.
   */
    function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal virtual;

    function rawFulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) external {
        require(msg.sender == address(VRF_V2_WRAPPER), "only VRF V2 wrapper can fulfill");
        fulfillRandomWords(_requestId, _randomWords);
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface VRFV2WrapperInterface {
    /**
     * @return the request ID of the most recent VRF V2 request made by this wrapper. This should only
   * be relied option within the same transaction that the request was made.
   */
    function lastRequestId() external view returns (uint256);

    /**
     * @notice Calculates the price of a VRF request with the given callbackGasLimit at the current
   * @notice block.
   *
   * @dev This function relies on the transaction gas price which is not automatically set during
   * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   */
    function calculateRequestPrice(uint32 _callbackGasLimit, uint32 _numWords) external view returns (uint64);

    //   /**
    //   * @notice Estimates the price of a VRF request with a specific gas limit and gas price.
    //   *
    //   * @dev This is a convenience function that can be called in simulation to better understand
    //   * @dev pricing.
    //   *
    //   * @param _callbackGasLimit is the gas limit used to estimate the price.
    //   * @param _requestGasPriceWei is the gas price in wei used for the estimation.
    //   */
    //   function estimateRequestPrice(uint32 _callbackGasLimit, uint256 _requestGasPriceWei) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

abstract contract TRC20Interface {

    function totalSupply() public view virtual returns (uint);

    function balanceOf(address guy) public view virtual returns (uint);

    function allowance(address src, address guy) public view virtual returns (uint);

    function approve(address guy, uint wad) public  virtual returns (bool);

    function transfer(address dst, uint wad) public virtual returns (bool);

    function transferFrom(address src, address dst, uint wad) public virtual returns (bool);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

abstract contract WinkMid {

    function setToken(address tokenAddress) public virtual;

    function transferAndCall(address to, uint64 tokens, bytes calldata _data) public virtual returns (bool success);

    function balanceOf(address guy) public view virtual returns (uint);

    function allowance(address src, address guy) public view virtual returns (uint);

}

# 如何设置可验证随机函数合约

# WinkMid 合约

WINkLink 用 WIN 代币(TRC20)作为整个生态的基础代币。

WINkLink 使用了 transferAndCall 功能,即在转账 TRC20 代币给合约的同时调用合约的某一回调函数,该功能类似 ERC677,但接口参数不同。

考虑到绝大多数已发行的代币无法再修改合约或增加接口,WINkLink 提供 WinkMid 包装合约,可用来包装任一 TRC20 代币,并提供 transferAndCall 接口。

合约代码可于 WinkMid.sol 查看。

为方便开发者使用,Nile 测试网部署了 WinkMid 合约,并封装了 WIN 代币。 开发者可直接使用该合约地址,无需额外部署。 Nile 测试网还提供水龙头地址,用户可以领取 TRX 和 WIN 测试代币。

TIP

Nile 测试网

WIN TRC20 合约地址: TNDSHKGBmgRx9mDYA9CnxPx55nu672yQw2

WinkMid 合约地址: TJpkay8rJXUWhvS2uL5AmMwFspQdHCX1rw

测试网水龙头地址: https://nileex.io/join/getJoinPage (opens new window)

部署 WinkMid 合约时,开发者需在构造函数中提供被封装的 TRC20 代币地址(即 WIN 代币地址)。

WinkMid 合约可帮助用户进行合约调用,开发者无需直接进行调用操作。

部署 Coordinator 合约时需在构造函数中提供 WIN 代币地址和 WinkMid 合约地址。

# VRFCoordinatorV2

Coordinator 主要负责处理所有 VRF 请求和 fulfillment, 请使用相应的参数部署合约。

发起请求之前,预言机必须以 base58 编码的形式用证明密钥向 Coordinator 注册其节点地址,否则请求将失败。

# VRFV2Wrapper

Wrapper 合约是直接付费 Consumer 的访问层。该合约通过 WinkMid 的 transferAndCall 函数为订阅服务提供充足的 Wink 代币,保证内部流通。

传入数据为 ABI 编码格式的订阅 ID 值,例如,0x0000000000000000000000000000000000000000000000000000000000000007 表示订阅 ID 为 7。

TIP

keyhash 指的是预言机节点的 keyhash,可通过 Operator UI 或 CLI 获取

# Consumers

  • VRFv2DirectFundingConsumer

在发起请求时,直接付费的 Consumer 会直接从用户账户中扣除 Wink 代币。 请求时,该 Consumer 接口会与 Wrapper 合约进行交互。

  • VRFv2SubscriptionConsumer

为确保订阅服务处于开启状态,订阅服务 Consumer 需要使用订阅服务管理器。 出现请求时,该 Consumer 接口凭有效的订阅 ID 直接与 Coordinator 合约进行交互。

WARNING

代码中提供的 Consumer 合约仅作示例,用户应根据自身情况编写自己的 Consumer 合约。

# 如何启动可验证随机数服务节点

# 节点部署

合约部署完毕后,即可开始 WINLink 节点部署。

WINkLink 节点(项目目录节点)的代码请参考:https://github.com/tron-oracle/winklink-2.0/tree/main (opens new window).

WARNING

当前节点实现包含通过交易所 API 访问代币价格的适配器。 请在中国大陆以外的稳定网络环境中运行节点

# 准备节点账户

每个 WINLink 节点必须与一个波场帐户关联,以便调用聚合器合约传输数据。

账户地址和私钥生成后,开发人员可以在测试网水龙头页面测试 TRX 代币。该代币用于支付调用智能合约产生的手续费。

节点初始运行时将生成账户,私钥将存储在密钥链中。 节点将使用该账户进行喂价传输。

WARNING

生成的账户尚未激活,请向该账户转账任意数量的 TRX 以完成激活

# 所需环境

WINkLink 节点依赖 PostgreSQL 数据库。 详情请参考官方文档:https://www.postgresql.org (opens new window) .

TIP

这里假定本机部署的 PostgreSQL 实例的用户名和密码分别是 root:root。 在生产环境中请使用强密码或其他验证方式。

WINkLink 节点使用的编程语言为 Go,因此需要搭建 Golang 环境。

# 节点配置

WINkLink 节点的配置文件格式为 TOML, 主配置为 tools/config/config.toml。 你可以使用 secrets.toml 指定要使用的 db 实例。 以下为参考模板。

# secrets.toml
[Database]
URL = 'postgresql://root:root@localhost:5432/winklink?sslmode=disable' # Require
AllowSimplePasswords = true

[Password]
Keystore = 'keystorePassword' # Required

[Tron]
TronApiKey = 'apiKey'

确认好节点配置文件后,需创建 vrfpasswordapicredentials 文件,并写入用户 ID 和密码,以访问节点 API:

# apicredentials
example.user@fake.email
totallyNotFakePassword (16 characters long)
# vrfpassword
totallyNotFakePassword (16 characters long)

TIP

请妥善托管您的个人信息。

# 搭建节点 Docker 镜像

使用以下指令构建标准的 Linux 镜像:

# build a docker image
docker buildx build --platform linux/amd64 -t winklink-2.0 -f core/winklink.Dockerfile .

将构建好的 Docker 镜像打上标签并推送到所需的存储库进行部署。

# 用源代码启动节点

安装 go1.20 (opens new window)

前往 winklink-2.0 源代码的基本目录

搭建命令行界面

make install

使用以下指令及对应配置项启动 WINkLink 节点:

winklink -c /tools/config/config.toml -s /tools/config/secrets.toml node start -p /tools/secrets/vrfpassword -a /tools/secrets/apicredentials

WARNING

节点帐号必须有足够的 TRX 代币,用于合约调用。 可以通过测试网水龙头申请测试代币。

# 为节点添加 VRF 任务

以下是创建一个所需最少参数的 VRF 任务规范示例模版

type = "vrf"
schemaVersion = 1
name = "vrf-delete-test"
forwardingAllowed = false
coordinatorAddress = "THE-SMART-CONTRACT-EIP55-COORDINATOR-ADDRESS"
fromAddresses = [ "THE-CURRENT-NODE-EIP55-ADDRESS" ]
minIncomingConfirmations = 1
publicKey = "THE-CURRENT-NODE-PUBLIC-KEY"
observationSource = """
decode   [type="tvmabidecodelog"]
vrf      [type=vrfbuilder]
tvmcall  [type=tvmcall contract="THE-SMART-CONTRACT-TRON-ADDRESS" extractRevertReason=true]

decode->vrf->tvmcall
"""

创建完成后,节点便可以处理收到的 VRF 请求。