如何可靠地进行 API 调用?

Posted

技术标签:

【中文标题】如何可靠地进行 API 调用?【英文标题】:How to make an API call in solidity? 【发布时间】:2020-09-16 06:48:57 【问题描述】:

我正在尝试制定一份智能合约,它将支付我的英雄联盟锦标赛的获胜者。但是我遇到了一个问题。我需要调用 API 来获得比赛的获胜者,我已经创建了一个简单的 URL。

"example-winner.com/winner"

它会返回带有获胜者地址的简单 JSON:

"winner":"0xa7D0......."

但是,我不确定如何通过 API 调用外部函数。我知道我需要使用某种预言机技术。

有什么想法吗?以下是我的代码:

pragma solidity ^0.4.24;
contract LeagueWinners
    address public manager;
    address[] public players;
    uint256 MINIMUM = 1000000000000000;
    constructor() public
        manager = msg.sender;
    
    function enter() public payable
        assert(msg.value > MINIMUM);
        players.push(msg.sender);
    
    function getWinner() public
        assert(msg.sender == manager);
        // TODO
        // Get the winner from the API call
        result = 0; // the result of the API call
        players[result].transfer(address(this).balance);
        // returns an adress object
        // all units of transfer are in wei
        players = new address[](0);
        // this empties the dynamic array
    

【问题讨论】:

【参考方案1】:

您可以使用 Chainlink 作为您的Oracle。

正如许多人所提到的,您将需要一个 oracle 来获取您的 API 调用。需要注意的重要一点是,您的合约实际上是要求预言机为您进行 API 调用,而不是自己进行 API 调用。这是因为区块链是确定性的。如需更多信息,请参阅this thread。

要回答您的问题,您可以使用去中心化预言机服务Chainlink。

你会添加一个函数:

  function getWinner() 
    public
    onlyOwner
  
    Chainlink.Request memory req = buildChainlinkRequest(JOB, address(this), this.fulfill.selector);
    req.add("get", "example-winner.com/winner");
    req.add("path", "winner");
    sendChainlinkRequestTo(ORACLE, req, ORACLE_PAYMENT);
  

出于以下示例的目的,我们将假设您想要返回 uint256 而不是地址。您可以返回 bytes32,然后将其转换为地址,但为简单起见,假设 API 返回获胜者的索引。您必须找到可以发出http.get 请求并返回uint256 对象的节点和jobId。您可以从market.link 找到节点和作业。每个测试网(Ropsten、Mainnet、Kovan 等)都有不同的节点地址,因此请确保选择正确的。

对于这个演示,我们将使用 LinkPool 的 ropsten 节点

address ORACLE=0x83F00b902cbf06E316C95F51cbEeD9D2572a349a;
bytes32 JOB= "c179a8180e034cf5a341488406c32827";

理想情况下,您应该选择多个节点来运行您的工作,以使其无需信任且去中心化。您可以read here 了解有关预协调器和聚合数据的更多信息。 披露我是该博客的作者

您的完整合同如下所示:

pragma solidity ^0.6.0;

import "github.com/smartcontractkit/chainlink/evm-contracts/src/v0.6/ChainlinkClient.sol";


contract GetData is ChainlinkClient 
    uint256 indexOfWinner;
    address public manager;
    address payable[] public players;
    uint256 MINIMUM = 1000000000000000;
  
  // The address of an oracle 
    address ORACLE=0x83F00b902cbf06E316C95F51cbEeD9D2572a349a;
    //bytes32 JOB= "93fedd3377a54d8dac6b4ceadd78ac34";
    bytes32 JOB= "c179a8180e034cf5a341488406c32827";
    uint256 ORACLE_PAYMENT = 1 * LINK;

  constructor() public 
    setPublicChainlinkToken();
    manager = msg.sender;
  

function getWinnerAddress() 
    public
    onlyOwner
  
    Chainlink.Request memory req = buildChainlinkRequest(JOB, address(this), this.fulfill.selector);
    req.add("get", "example-winner.com/winner");
    req.add("path", "winner");
    sendChainlinkRequestTo(ORACLE, req, ORACLE_PAYMENT);
  

  // When the URL finishes, the response is routed to this function
  function fulfill(bytes32 _requestId, uint256 _index)
    public
    recordChainlinkFulfillment(_requestId)
  
    indexOfWinner = _index;
    assert(msg.sender == manager);
    players[indexOfWinner].transfer(address(this).balance);
    players = new address payable[](0);
  
  
  function enter() public payable
        assert(msg.value > MINIMUM);
        players.push(msg.sender);
     
    
  modifier onlyOwner() 
    require(msg.sender == manager);
    _;
  
    
    // Allows the owner to withdraw their LINK on this contract
  function withdrawLink() external onlyOwner() 
    LinkTokenInterface _link = LinkTokenInterface(chainlinkTokenAddress());
    require(_link.transfer(msg.sender, _link.balanceOf(address(this))), "Unable to transfer");
  
  
  

这可以满足您的所有需求。


如果不能调整API返回uint,可以返回一个bytes32,然后转成地址或者字符串。

 function bytes32ToStr(bytes32 _bytes32) public pure returns (string memory) 
     bytes memory bytesArray = new bytes(32);
     for (uint256 i; i < 32; i++) 
         bytesArray[i] = _bytes32[i];
         
     return string(bytesArray);
     

【讨论】:

【参考方案2】:

你不能。虚拟机在区块链本身之外没有任何 I/O。相反,您需要告诉您的智能合约谁是获胜者,然后智能合约可以读取该变量的值。

这种设计模式也被称为“oracle”。谷歌“以太坊预言机”了解更多信息。

基本上,您的网络服务器可以调用您的智能合约。您的智能合约无法调用您的网络服务器。如果您需要您的智能合约来访问第三方服务,那么您的网络服务器将需要发出请求,然后通过调用智能合约中的函数将结果转发给solidity。

【讨论】:

【参考方案3】:

您没有正确解释您要做什么。您在使用solidity 代码时遇到问题吗?或者更确切地说是与您的服务器?这是一个经过编辑的版本。看看有没有帮助。

pragma solidity ^0.4.24;
contract LeagueWinners
    address public manager;
    //address[] public players;
    uint256 MINIMUM = 1000000000000000;
    constructor() public
        manager = msg.sender;
    

    struct Player 
        address playerAddress;
        uint score;
    

    Player[] public players;


    // i prefer passing arguments this way
    function enter(uint value) public payable
        assert(msg.value > MINIMUM);
        players.push(Player(msg.sender, value));
    

    //call this to get the address of winner
    function winningPlayer() public view
            returns (address winner)
    
        uint winningScore = 0;
        for (uint p = 0; p < players.length; p++) 
            if (players[p].score > winningScore) 
                winningScore = players[p].score;
                winner = players[p].playerAddress;
            
        
    

    // call this to transfer fund
    function getWinner() public
        require(msg.sender == manager, "Only a manager is allowed to perform this operation");
        // TODO

        address winner = winningPlayer();
        // Get the winner from the API call
        //uint result = 0; // the result of the API call
        winner.transfer(address(this).balance);
        // returns an adress object
        // all units of transfer are in wei
        delete players;
        // this empties the dynamic array
    

至少我对你的问题的理解是这样的。

【讨论】:

以上是关于如何可靠地进行 API 调用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Golang 中更快地进行 api 调用?

C++默认初始化和值初始化:哪个是哪个,啥时候调用以及如何可靠地初始化一个模板类型成员

在 C# 中,如何可靠地杀死进程树 [重复]

如何可靠地使 send(2) 进行短发送?

如何可靠地确定Passbook是否可用?

如何正确地从 ReactJS + Redux 应用程序进行 REST 调用?