php与以太坊rpc服务器通信1.JsonRPCJsonRPC是基于json的远程过程调用,所以解释的比较抽象。简单来说就是post一个json格式的数据来调用rpcserver中的方法。而且这个json格式是固定的,一般来说有这么几项:{"method":"","params":[],"id":idNumber}method:方法名params:参数列表id:唯一标识号过程调用2.构建一个JsonRPC客户端url=$url;//代理空($proxy)?$this->proxy='':$this->proxy=$proxy;//调试状态为空($debug)?$this->调试=false:$this->调试=真;//消息ID$this->id=1;}/***设置对象的通知状态。在这种状态下,执行通知,而不是请求。**@paramboolean$notification*/publicfunctionsetRPCNotification($notification){empty($notification)?$this->notification=false:$this->notification=true;}/***执行jsonRCP请求并获取结果作为数组**@paramstring$method*@paramarray$params*@returnarray*/publicfunction__call($method,$params){//检查是否(!is_scalar($method)){thrownewException('方法名没有标量值');}//检查if(is_array($params)){//没有键$params=$params[0];}否则{thrownewException('参数必须以数组形式给出');}//设置通知或请求任务if($this->notification){$currentId=NULL;}else{$currentId=$this->id;}//准备请求$request=array('method'=>$method,'params'=>$params,'id'=>$currentId);$request=json_encode($request);$this->debug&&$this->debug.='*****Request*****'."\n".$request."\n".'*****请求结束*****'."\n\n";//执行HTTPPOST$opts=array('http'=>array('method'=>'POST','header'=>'Content-type:application/json','content'=>$request));$context=stream_context_create($opts);如果($fp=fopen($this->url,'r',false,$context)){$response='';while($row=fgets($fp)){$response.=trim($row)."\n";}$this->debug&&$this->debug.='*****服务器响应*****'."\n".$response.'*****服务器响应结束*****'."\n";$response=json_decode($response,true);}else{thrownewException('无法连接到'.$this->url);}//调试输出if($this->debug){echonl2br($debug);}//最终检查并返回if(!$this->notification){//检查if($response['id']!=$currentId){thrownewException('Incorrectresponseid(requestid:'.$currentId.',responseid:'.$response['id'].')');}if(!is_null($response['error'])){thrownewException('请求错误:'.var_export($response['error'],true));}返回$response['result'];}else{返回真;}}}?>代码比较简单,如果比较Lazy,拿来用就行了也可以自己去packagist.org找一个rpc客户端。3、调用RPC的两种方法需要调用的方法有两种。一种是RPC服务器自带的方法,一种是contract方法。RPCserver方法调用json格式{"method":"eth_accounts","params":[],"id":1}RPCServer内置方法列表调用内置方法比较简单。参考上面的链接,大部分都有例子。json格式的合约方法调用调用合约方法必须使用内置方法中的eth_call。params中体现了合约方法名和合约方法参数列表。例如:如果我们要调用合约中的balanceOf方法,那么json数据应该如何结构化?先看getBalanace函数实现:functionbalanceOf(address_owner)publicviewreturns(uint256balance)提取函数原型:balanceOf(address)在geth控制台下运行命令:web3.sha3("balanceOf(address)")。substring(0,10)得到函数hash"0x70a08231",假设要查询的地址是address_owner="0x38aabef4cd283ccd5091298dedc88d27c5ec5750",然后去掉前面的"0x",左边填入24个0(一般地址长度is42bits,andafterremoving'0x',itwillbe40位),构成64位十六进制参数.最终得到的参数为"0x70a0823100000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750"假设我们的合约地址为"0xaeab4084194B2a425096fb583Fbcd67385210ac3".则得到最终的json数据为:{"method":"eth_call","params":[{"from":"0x38aabef4cd283ccd5091298dedc88d27c5ec5750","to":"0xaeab4084194B2a425096fb583Fbcd67385210ac3","data":"0x70a0823100000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750"},"latest"],"id":1}将上述json数据以post方式发送到服务器后,可以调用合约方法balanceOf查询给定地址的代币余额。调用合约。其他方法的其他方法也应该按照上面的方法,我们再分析一下transfer方法加深印象:首先看代码中的函数实现:functiontransfer(address_to,uint256_value)publicreturns(bool)二,提取函数原型:transfer(address,uint256)//注意逗号后面不能有空格再次在控制台运行sha3函数:web3.sha3("transfer(address,uint256)").substring(0,10)得到函数哈希“0xa9059cbb”第一个参数假定地址_to=“0x38aabef4cd283ccd5091298dedc88d27c5ec5750”,然后删除“0x”并用零填充到64位。Thesecondparameterassumesuint256_value=43776,thenconvertsitintohexadecimal"0xab00"andremoves"0x",补零到64位.连接起来"0xa9059cbb00000000000000000000000038aabef4cd283ccd5091298dedc88d27c5ec5750000000000000000000000000000000000000000000000000000000000000ab00"构建json数据:{"method":"eth_call","params":[{{“来自”:“0x38AABEF4CD283CCD5091298DEDC88D27C5EC5750“ec5750000000000000000000000000000000000000000000000000000000000000ab00"},"latest"],"id":1}从发送地址到合约地址的数据上述操作得到的十六进制数将上述步骤转化为代码。php';//php内置的dechex无法将大整数转为十六进制函数bc_dechex($decimal){$result=[];while($decimal!=0){$mod=$decimal%16;$decimal=floor($decimal/16);array_push($result,dechex($mod));}returnjoin(array_reverse($result));}classEthereumRPCClient{publicstatic$client=null;//布署约的账户地址constCOINBASE='0x38AABEF4CD283CCD5091298DEDC88D27C5EC5750';//合约地址const合同='0xAEAB4084194B2A42A42A425096FB5B583FB583FBCD67385210AC3';publicstatic$____________________{if(is_null(self::$client)){self::$client=newjsonRPCClient('http://127.0.0.1:8545',true);}}catch(\Exception$e){echo$e->getMessage();}返回call_user_func([self::$client,$method],$params);}publicstaticfunctiongetBalance($address){$method_hash='0x70a08231';$method_param1_hex=str_pad(substr($address,2),64,'0',STR_PAD_LEFT);$data=$method_hash。$method_param1_hex;$params=['from'=>$address,'to'=>self::CONTRACT,'data'=>$data];$total_balance=self::eth_call([$params,"latest"]);返回hexdec($total_balance)/(pow(10,18));}publicstaticfunctiontransfer($to,$value){self::personal_unlockAccount([self::COINBASE,"123456",3600]);$value=bcpow(10,18)*$value;$method_hash='0xa9059cbb';$method_param1_hex=str_pad(substr($to,2),64,'0',STR_PAD_LEFT);$method_param2_hex=str_pad(strval(bc_dechex($value)),64,'0',STR_PAD_LEFT);$数据=$满足hod_hash。$method_param1_hex。$method_param2_hex;$params=['from'=>self::COINBASE,'to'=>self::CONTRACT,'data'=>$data];返回self::eth_sendTransaction([$params]);}}代码比较简单,有几点需要注意:传递函数的取值单位很小,是10^-18,所以如果要传递1000,需要乘以10到18次方,这里的18是小数。由于第一点,应该使用bcpow而不是pow函数。php自带的dechex函数不能用。因为dechex要求整数类型不能大于PHP_INT_MAX,而这个数在32位机器上是4294967295。因为第一点,所有的数都必须乘以10的18次方,所以得到的数远大于PHP_INT_MAX。建议自己将十进制转为十六进制。如果您不知道如何操作,请参考上面的代码。在运行某些合约方法时,比如转账,必须先解锁用户。发送交易后,必须在服务器端开始挖矿,这样交易才会真正写入区块。比如你调用转账后,发现对方没有账户不要惊讶,直接开始挖矿试试。如果要开启自动挖矿,在geth--rpc....末尾加上--mine。测试:([EthereumRPCClient::COINBASE,"密码",3600]);var_dump(EthereumRPCClient::getBalance("0x...."));
