Maison  >  Article  >  développement back-end  >  Explication détaillée de l'interaction entre le client PHP et Ethereum

Explication détaillée de l'interaction entre le client PHP et Ethereum

php中世界最好的语言
php中世界最好的语言original
2018-05-16 11:52:452840parcourir

Cette fois, je vais vous apporter une explication détaillée de l'utilisation interactive de PHP et du client Ethereum. Quelles sont les précautions pour l'utilisation interactive de PHP et du client Ethereum. Ce qui suit est un cas pratique. , jetons un coup d'oeil.

php communique avec le serveur Ethereum RPC

1 Json RPC

Json RPC est un appel de procédure à distance basé sur json. . Cette explication est plutôt abstraite. Pour faire simple, il s'agit de poster une donnée au format json et d'appeler la méthode dans le serveur rpc. Le format json est fixe. En général, on retrouve les éléments suivants :

{
  "method": "",
  "params": [],
  "id": idNumber
}
  • <.> méthode : nom de la méthode

  • params : liste de paramètres

  • id : numéro d'identification unique pour l'appel de procédure

2. Créez un client Json RPC

<?php
class jsonRPCClient {
  
  /**
   * Debug state
   *
   * @var boolean
   */
  private $debug;
  
  /**
   * The server URL
   *
   * @var string
   */
  private $url;
  /**
   * The request id
   *
   * @var integer
   */
  private $id;
  /**
   * If true, notifications are performed instead of requests
   *
   * @var boolean
   */
  private $notification = false;
  
  /**
   * Takes the connection parameters
   *
   * @param string $url
   * @param boolean $debug
   */
  public function construct($url,$debug = false) {
    // server URL
    $this->url = $url;
    // proxy
    empty($proxy) ? $this->proxy = &#39;&#39; : $this->proxy = $proxy;
    // debug state
    empty($debug) ? $this->debug = false : $this->debug = true;
    // message id
    $this->id = 1;
  }
  
  /**
   * Sets the notification state of the object. In this state, notifications are performed, instead of requests.
   *
   * @param boolean $notification
   */
  public function setRPCNotification($notification) {
    empty($notification) ?
              $this->notification = false
              :
              $this->notification = true;
  }
  
  /**
   * Performs a jsonRCP request and gets the results as an array
   *
   * @param string $method
   * @param array $params
   * @return array
   */
  public function call($method,$params) {
    
    // check
    if (!is_scalar($method)) {
      throw new Exception(&#39;Method name has no scalar value&#39;);
    }
    
    // check
    if (is_array($params)) {
      // no keys
      $params = $params[0];
    } else {
      throw new Exception(&#39;Params must be given as array&#39;);
    }
    
    // sets notification or request task
    if ($this->notification) {
      $currentId = NULL;
    } else {
      $currentId = $this->id;
    }
    
    // prepares the request
    $request = array(
            &#39;method&#39; => $method,
            &#39;params&#39; => $params,
            &#39;id&#39; => $currentId
            );
    $request = json_encode($request);
    $this->debug && $this->debug.=&#39;***** Request *****&#39;."\n".$request."\n".&#39;***** End Of request *****&#39;."\n\n";
    // performs the HTTP POST
    $opts = array (&#39;http&#39; => array (
              &#39;method&#39; => &#39;POST&#39;,
              &#39;header&#39; => &#39;Content-type: application/json&#39;,
              &#39;content&#39; => $request
              ));
    $context = stream_context_create($opts);
    if ($fp = fopen($this->url, &#39;r&#39;, false, $context)) {
      $response = &#39;&#39;;
      while($row = fgets($fp)) {
        $response.= trim($row)."\n";
      }
      $this->debug && $this->debug.=&#39;***** Server response *****&#39;."\n".$response.&#39;***** End of server response *****&#39;."\n";
      $response = json_decode($response,true);
    } else {
      throw new Exception(&#39;Unable to connect to &#39;.$this->url);
    }
    
    // debug output
    if ($this->debug) {
      echo nl2br($debug);
    }
    
    // final checks and return
    if (!$this->notification) {
      // check
      if ($response[&#39;id&#39;] != $currentId) {
        throw new Exception(&#39;Incorrect response id (request id: &#39;.$currentId.&#39;, response id: &#39;.$response[&#39;id&#39;].&#39;)&#39;);
      }
      if (!is_null($response[&#39;error&#39;])) {
        throw new Exception(&#39;Request error: &#39;. var_export($response[&#39;error&#39;], true));
      }
      
      return $response[&#39;result&#39;];
      
    } else {
      return true;
    }
  }
}
?>
Code relativement simple, si vous êtes paresseux, utilisez-le. Vous pouvez également aller sur packagist.org pour trouver vous-même un client rpc

3. Deux types de méthodes pour appeler RPC

Il existe deux types de méthodes nécessaires. à appeler. L'une est les méthodes intégrées du serveur RPC, l'autre type est les méthodes de contrat.

La méthode du serveur RPC appelle le format json

{
  "method": "eth_accounts",
  "params": [],
  "id": 1
}
Liste des méthodes intégrées du serveur RPC.

Comparaison des appels de méthodes intégrées Simple, reportez-vous au lien ci-dessus, la plupart d'entre elles ont des exemples

Méthode de contrat appelle le format json

Pour appeler une méthode de contrat, vous devez utiliser eth_call dans la méthode intégrée. Le nom de la méthode de contrat et la liste des paramètres de la méthode de contrat. Ensuite, utilisez les paramètres pour la réflexion : si nous voulons appeler la méthode balanceOf dans le contrat, comment les données json doivent-elles être construites ?

Premier aperçu de l'implémentation de la fonction getBalanace :

function balanceOf(address _owner) public view returns (uint256 balance)
Extraire le prototype de la fonction :

balanceOf(address)
Exécutez la commande sous la console geth :

web3.sha3("balanceOf(address)").substring(0, 10)
Récupérez le hachage de fonction "0x70a08231"

Supposons que l'adresse à interroger soit l'adresse _owner = " 0x38aabef4cd283ccd5091298dedc88d27c5ec5750", puis supprimez le "0x" devant et ajoutez 24 zéros à gauche (l'adresse générale la longueur est de 42 bits, et elle est de 40 bits après suppression du '0x'), formant un paramètre hexadécimal de 64 bits

Le paramètre final est "0x70a0823100000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750"<.>

Supposons que notre adresse contractuelle soit "0xaeab4084194B2a425096fb583Fb cd67385210ac3".

Les données json finales obtenues sont :

Ce qui précède Les données json sont envoyées au serveur en mode post, et la méthode de contrat "balanceOf" peut être appelé pour interroger le solde du jeton à l'adresse indiquée.
{
  "method": "eth_call",
  "params": [{"from": "0x38aabef4cd283ccd5091298dedc88d27c5ec5750", "to": "0xaeab4084194B2a425096fb583Fbcd67385210ac3", "data": "0x70a0823100000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750"}, "latest"],
  "id": 1
}

L'appel d'autres méthodes dans le contrat doit également suivre la méthode ci-dessus. Analysons à nouveau la méthode de transfert pour approfondir notre impression :

Tout d'abord, regardez l'implémentation de la fonction dans le code :

Deuxièmement, extrayez le prototype de la fonction :
function transfer(address _to, uint256 _value) public returns (bool)

Encore une fois, exécutez la fonction sha3 sur la console :
transfer(address,uint256) //注意逗号后面不能有空格

Obtenez le hachage de fonction "0xa9059cbb"
web3.sha3("transfer(address,uint256)").substring(0, 10)

Le premier paramètre suppose l'adresse _to = "0x38aabef4cd283ccd5091298dedc88d27c5ec5750", puis allez à "0x" et remplissez des zéros à 64 bits.

En supposant uint256 _value = 43776 pour le deuxième paramètre, il sera converti en "0xab00" hexadécimal, puis supprimera "0x", et ajoutera des zéros à 64 bits.

Connectez-vous

"0xa9059cbb00000000000000000000000038aabef4cd283ccd5091298 dedc88d27c5ec57500000000000000000000000000000000000000000000000000000000000ab00"

Construire les données JSON :

{
  "method": "eth_call",
  "params": [{"from": "0x38aabef4cd283ccd5091298dedc88d27c5ec5750", "to": "0xaeab4084194B2a425096fb583Fbcd67385210ac3", "data": "0xa9059cbb00000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750000000000000000000000000000000000000000000000000000000000000ab00"}, "latest"],
  "id": 1
}
    de l'adresse du transférant
  • à l'adresse du contrat
  • données le nombre hexadécimal obtenu par l'opération ci-dessus
  • Convertissez les étapes ci-dessus en code.

Création d'un client Ethereum RPC

Le code est relativement simple, mais il y a quelques points à noter :
<?php 
require &#39;./jsonRPCClient.php&#39;;
//php自带的dechex无法把大整型转换为十六进制
function bc_dechex($decimal)
{
  $result = [];
  while ($decimal != 0) {
    $mod = $decimal % 16;
    $decimal = floor($decimal / 16);
    array_push($result, dechex($mod));    
  }
  return join(array_reverse($result));
}
class EthereumRPCClient
{
  public static $client = null;
  
  //布署合约的账户地址
  const COINBASE = &#39;0x38aabef4cd283ccd5091298dedc88d27c5ec5750&#39;;
  
  //合约地址
  const CONTRACT = &#39;0xaeab4084194B2a425096fb583Fbcd67385210ac3&#39;;
  public static function callStatic($method, $params)
  {
    $params = count($params) < 1 ? [] : $params[0];
    try {
      if (is_null(self::$client)) {
        self::$client = new jsonRPCClient(&#39;http://127.0.0.1:8545&#39;, true);  
      }
    } catch (\Exception $e) {
      echo $e->getMessage();
    }
    return call_user_func([self::$client, $method], $params);
  }
  public static function getBalance($address)
  {
    $method_hash = &#39;0x70a08231&#39;;
    $method_param1_hex = str_pad(substr($address, 2), 64, &#39;0&#39;, STR_PAD_LEFT);
    $data = $method_hash . $method_param1_hex;
    $params = [&#39;from&#39; => $address, &#39;to&#39; => self::CONTRACT, &#39;data&#39; => $data];
    $total_balance = self::eth_call([$params, "latest"]);
    return hexdec($total_balance) / (pow(10, 18));
  }
  public static function transfer($to, $value)
  {
    self::personal_unlockAccount([self::COINBASE, "123456", 3600]);
    $value = bcpow(10, 18) * $value;
    $method_hash = &#39;0xa9059cbb&#39;;
    $method_param1_hex =str_pad(substr($to, 2), 64, &#39;0&#39;, STR_PAD_LEFT);  
    $method_param2_hex = str_pad(strval(bc_dechex($value)), 64, &#39;0&#39;, STR_PAD_LEFT);
    $data = $method_hash . $method_param1_hex . $method_param2_hex;
    $params = [&#39;from&#39; => self::COINBASE, &#39;to&#39; => self::CONTRACT, &#39;data&#39; => $data];
    return self::eth_sendTransaction([$params]);
  }
}

    L'unité de valeur de la fonction de transfert est très petite, qui est 10 ^ -18, donc si vous Si vous souhaitez transférer 1000, vous devez en fait multiplier par 10 à la puissance 18, où 18 est une décimale.
  • En raison du point 1, bcpow doit être utilisé à la place de la fonction pow.
  • 不能使用php自带的dechex函数. 因为dechex要求整型不能大于 PHP_INT_MAX, 而这个数在32位机上为4294967295。由于第1 点, 所有的数都要乘于10的18次方, 所以得到的数要远远大于PHP_INT_MAX. 建议自己实现10进制转16进制,如果你不知道如何实现,参考上述代码。

  • 在运行某些合约方法, 比如transfer时, 要先unlock用户.

  • 发送交易之后, 一定要在服务器端启动挖矿, 这样交易才会真的写入到区块, 比如你调用transfer之后,却发现对方没有到账,先别吃惊,启动挖矿试试。如果想启用自动挖码, 在geth --rpc ...最后加上 --mine.

测试:

<?php 
var_dump(EthereumRPCClient::personal_newAccount([&#39;password&#39;]));
var_dump(EthereumRPCClient::personal_unlockAccount([EthereumRPCClient::COINBASE, "password", 3600]);
var_dump(EthereumRPCClient::getBalance("0x...."));

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

PHP使用file_get_contents发送http请求步骤详解

PHP实现随机剔除算法

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn