详解php与ethereum客户端交互

2018-09-07 12:19

阅读:853

  php与ethereum rpc server通信

  一、Json RPC

  Json RPC就是基于json的远程过程调用,这么解释比较抽象。简单来说,就是post一个json格式的数据调用rpc server中的方法. 而这个json格式是固定的, 总的来说有这么几项:

   { method: , params: [], id: idNumber } method: 方法名 params: 参数列表 id: 对过程调用的唯一标识号

  二、构建一个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 = : $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(Method name has no scalar value); } // check if (is_array($params)) { // no keys $params = $params[0]; } else { throw new Exception(Params must be given as array); } // sets notification or request task if ($this->notification) { $currentId = NULL; } else { $currentId = $this->id; } // prepares the request $request = array( method => $method, params => $params, id => $currentId ); $request = json_encode($request); $this->debug && $this->debug.=***** Request *****.\n.$request.\n.***** End Of request *****.\n\n; // performs the HTTP POST $opts = array (http => array ( method => POST, header => Content-type: application/json, content => $request )); $context = stream_context_create($opts); if ($fp = fopen($this->url, r, false, $context)) { $response = ; while($row = fgets($fp)) { $response.= trim($row).\n; } $this->debug && $this->debug.=***** Server response *****.\n.$response.***** End of server response *****.\n; $response = json_decode($response,true); } else { throw new Exception(Unable to connect to .$this->url); } // debug output if ($this->debug) { echo nl2br($debug); } // final checks and return if (!$this->notification) { // check if ($response[id] != $currentId) { throw new Exception(Incorrect response id (request id: .$currentId., response id: .$response[id].)); } if (!is_null($response[error])) { throw new Exception(Request error: . var_export($response[error], true)); } return $response[result]; } else { return true; } } } ?>

  比较简单的代码,如果比较懒,拿过去用就行了。也可以上己找一个rpc client.

  三、调用RPC的两类方法

  有两类方法需要调用. 一类是RPC server自带方法,另一类就是合约方法.

  RPC server方法调用json格式

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

  RPC Server自带方法的列表

  调用自带方法比较简单,参考上述链接,大部分都有示例.

  合约方法调用json格式

  调用合约方法必须使用自带方法中的eth_call. 而合约方法名称和合约方法参数列表则使用params进行体现, 比如: 我们要调用合约中的balanceOf方法, 则json数据应该如何构造呢?

  首先看看getBalanace的函数实现:

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

  提炼出函数原型:

   balanceOf(address)

  在geth控制台下运行命令:

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

  得到函数hash 0x70a08231

  假设待查询的地址 address _owner = 0x38aabef4cd283ccd5091298dedc88d27c5ec5750, 则去掉前面的0x, 并在左边补24个零(一般地址长度为42位, 去掉0x后为40位),构成64位十六进制参数.

  最终得到的参数为 0x70a0823100000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750

  假设我们的合约地址为 0xaeab4084194B2a425096fb583Fbcd67385210ac3.

  则得到最终的json数据为:

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

  把以上json数据以post方式发送给服务器,就可以调用合约方法balanceOf, 查询给定的地址中的代币余额.

  调用合约中的其他方法也要新遵循上面的方式, 我们再分析一下transfer方法, 加深印象:

  首先, 看看代码中的函数实现:

   function transfer(address _to, uint256 _value) public returns (bool)

  其次, 提炼出函数原型:

   transfer(address,uint256) //注意逗号后面不能有空格

  再次, 在控制台运行sha3函数:

   web3.sha3(transfer(address,uint256)).substring(0, 10)

  得到函数hash 0xa9059cbb

  第一个参数假设 address _to = 0x38aabef4cd283ccd5091298dedc88d27c5ec5750, 则去0x, 补零到64位.

  第二个参数假设 uint256 _value = 43776, 则化为十六进制0xab00后, 去0x, 补零到64位.

  连接起来

  0xa9059cbb00000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750000000000000000000000000000000000000000000000000000000000000ab00

  构建json数据:

   { method: eth_call, params: [{from: 0x38aabef4cd283ccd5091298dedc88d27c5ec5750, to: 0xaeab4084194B2a425096fb583Fbcd67385210ac3, data: 0xa9059cbb00000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750000000000000000000000000000000000000000000000000000000000000ab00}, latest], id: 1 } from 转出者地址 to 合约地址 data 上述操作得到的十六进制数

  把以上的步骤转化为代码.

  构建一个以太坊RPC client

   <?php require ./jsonRPCClient.php; //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 = 0x38aabef4cd283ccd5091298dedc88d27c5ec5750; //合约地址 const CONTRACT = 0xaeab4084194B2a425096fb583Fbcd67385210ac3; public static function __callStatic($method, $params) { $params = count($params) < 1 ? [] : $params[0]; try { if (is_null(self::$client)) { self::$client = new jsonRPCClient(

  代码比较简单, 要注意几点:

   transfer函数的value单位很小, 是 10 ^ -18, 所以如果你想转1000个,其实是要乘于 10的18次方, 这里的18是decimals. 由于第1点, 应该使用bcpow代替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([password])); var_dump(EthereumRPCClient::personal_unlockAccount([EthereumRPCClient::COINBASE, password, 3600]); var_dump(EthereumRPCClient::getBalance(0x....));


评论


亲,登录后才可以留言!