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

不言
不言original
2018-04-28 10:28:312094parcourir

Cet article vous indique les points de connaissances pertinents sur l'interaction entre les clients PHP et Ethereum. Il a une certaine valeur de référence. Les amis dans le besoin peuvent le suivre et en tirer des leçons.

php communique avec le serveur Ethereum RPC

1 Json RPC

Json RPC est basé. sur l'appel de procédure distante de json, cette explication est relativement 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 : pour le processus appeler Numéro d'identification unique

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;
    }
  }
}
?>

C'est un code relativement simple. Si vous êtes paresseux, prenez-le et 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 qui doivent être appelées. Un type est la méthode propre au serveur RPC et l'autre type est la méthode contractuelle

La méthode du serveur RPC appelle le format json

.
{
  "method": "eth_accounts",
  "params": [],
  "id": 1
}

Liste des méthodes du serveur RPC avec les méthodes

Appeler la méthode intégrée est relativement simple, reportez-vous au lien ci-dessus, la plupart d'entre elles ont des exemples.

Appel de méthode de contrat au format json


Appeler la 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 sont reflétés à l'aide de params. appeler la méthode balanceOf dans le contrat, comment les données json doivent-elles être construites ?

Tout d'abord, jetez un œil à l'implémentation de la fonction getBalanace :

function balanceOf(address _owner) public view returns (uint256 balance)

Extraire le prototype de fonction :

balanceOf(address)

Exécutez la commande sous la console Geth :

web3.sha3("balanceOf(address)").substring(0, 10)

Obtenez le hachage de fonction "0x70a08231"

Supposons que l'adresse à interroger adresse _owner = "0x38aabef4cd283ccd5091298dedc88d27c5ec5750", puis supprimez le "0x" devant et ajoutez 24 zéros sur le gauche (la longueur générale de l'adresse est de 42 bits, et elle est de 40 bits après avoir supprimé le « 0x ») pour former un paramètre hexadécimal de 64 bits. 750"

Supposons que notre adresse contractuelle soit "0xaeab4084194B2a425 096fb583Fbcd67385210ac3".

alors les données json finales obtenues sont :

{
  "method": "eth_call",
  "params": [{"from": "0x38aabef4cd283ccd5091298dedc88d27c5ec5750", "to": "0xaeab4084194B2a425096fb583Fbcd67385210ac3", "data": "0x70a0823100000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750"}, "latest"],
  "id": 1
}
Envoyer les données json ci-dessus au serveur en mode post, vous pouvez alors appeler la méthode du contrat "balanceOf" pour interroger le solde du jeton à l'adresse donnée.

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

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

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

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

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

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

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

Construire les données json :

à partir de l'adresse du cédant

{
  "method": "eth_call",
  "params": [{"from": "0x38aabef4cd283ccd5091298dedc88d27c5ec5750", "to": "0xaeab4084194B2a425096fb583Fbcd67385210ac3", "data": "0xa9059cbb00000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750000000000000000000000000000000000000000000000000000000000000ab00"}, "latest"],
  "id": 1
}

adresse du contrat
  • données numéro hexadécimal obtenu par l'opération ci-dessus
  • Convertir les étapes ci-dessus en code.
  • Construire un client Ethereum RPC

Le code est relativement simple, 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 souhaitez transférer 1000 fois, vous devez en fait multiplier par 10 à la puissance 18, 18 ici est une décimale.

En raison du point 1, bcpow doit être utilisé à la place de la fonction pow.
  • Vous ne pouvez pas utiliser dechex fourni avec la fonction php car dechex nécessite que le. Le type entier ne peut pas être supérieur à PHP_INT_MAX, et ce nombre est 4294967295 sur une machine 32 bits. En raison du point 1, tous les nombres doivent être multipliés par 10 à la puissance 18, donc le nombre obtenu est beaucoup plus grand que PHP_INT_MAX. Il est recommandé de convertir vous-même le nombre décimal en hexadécimal. Si vous ne savez pas comment l'implémenter, reportez-vous. au code ci-dessus.
  • Lors de l'exécution de certaines méthodes de contrat, telles que le transfert, l'utilisateur doit d'abord être déverrouillé
  • Après l'envoi de la transaction, celle-ci doit être démarrée. du côté du serveur Mining, afin que la transaction soit effectivement écrite dans le bloc. Par exemple, après avoir appelé le transfert, vous constatez que l'autre partie n'a pas reçu le compte. Ne soyez pas surpris. Si vous souhaitez activer l'extraction automatique de code, ajoutez --mine à la fin de geth --rpc... >

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