基于以太坊的RPC智能合约入门 - ethereumj

無名 发表于: 2018-03-27   最后更新时间: 2018-07-04 17:49:21  
{{totalSubscript}} 订阅, 8,813 游览

现在,开始通过使用RPC接口,来创建一个基于以太坊的智能合约。首先,先确保你的区块运行正常。

## 查询该节点的矿工地址
curl --data '{"jsonrpc":"2.0","method":"eth_coinbase", "id":1}' localhost:8080
{"jsonrpc":"2.0","id":1,"result":"0xa2baba3d6dfdc58b4db8b8e71fc6ebb86cc1cb46"}

## 查询余额
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xa2baba3d6dfdc58b4db8b8e71fc6ebb86cc1cb46", "latest"],"id":1}' https://localhost:8080/rpc
{"jsonrpc":"2.0","id":1,"result":"0x1cc0f99e8e85f1a2b400"}

## 余额作为十六进制字符串以`Wei`的形式返回。如果希望余额作为数字以太币为单位,可以从控制台用web3(此步骤可以忽略):
web3.fromWei("0x1639e49bba16280000", "ether")
"410"

如果上面都没有问题,那么,开始我们的智能合约创建吧。

概要

RPC

每个以太坊的节点都提供了RPC。这个界面给Ðapp(去中心化应用)访问以太坊区块链的权限和节点提供的功能,比如编译智能合约代码,它用JSON-RPC 2.0规范(不支持提醒和命名的参数) 的子集作为序列化协议,在HTTP和IPC (linux/OSX上的unix域接口,在Windows上叫pipe’s)上可用。

惯例

RPC界面会使用一些惯例,但它们不是JSON-RPC 2.0规范的一部分,这些惯例如下:

  • 数字是十六进制编码。做这个决定是因为有些语言对运行极大的数字没有或有很少的限制。为了防止这些错误数字类型是十六进制编码,由开发者来分析这些数字并正确处理它们。

  • 默认区块数字。RPC方法接受区块数字。在一些情况下,给出区块数字是不可能的或者不太方便。在那样的情况下,默认区块数字可以是以下字符串中的一个[”earliest”, “latest”, “pending”]。(就像我查询余额的时候,带的"latest")。

开始编写智能合约

合约代码

contract Multiply7 {
   event Print(uint);
   function multiply(uint input) returns (uint) {
      Print(input * 7);
      return input * 7;
   }
}

第一步,验证solidity编译器可用,可以用eth_getCompilers RPC method方法来检索可用的编译器:

curl --data '{"jsonrpc":"2.0","method": "eth_getCompilers", "id": 3}' localhost:8080
{"jsonrpc":"2.0","id":3,"result":["solidity"]}

可以看到solidity编译器可用。

第二步,把Multiply7合约编译到可以发送给以太坊虚拟机的字节代码中:

curl -X POST --data '{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["contract Multiply7 { event Print(uint);function multiply(uint input) returns (uint) {Print(input * 7); return input * 7;}}"],"id":1}' https://localhost:8080/rpc
{"jsonrpc":"2.0","id":1,"result":{"code":"0x606060405234610000575b60de806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063c6888fa114603c575b6000565b3460005760546004808035906020019091905050606a565b6040518082815260200191505060405180910390f35b60007f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da600783026040518082815260200191505060405180910390a16007820290505b9190505600a165627a7a7230582035ac7f06abdc2da4cccbf5c2df32441b0116f129d1f43e40fded43385160e4110029","info":{"source":"contract Multiply7 { event Print(uint);function multiply(uint input) returns (uint) {Print(input * 7); return input * 7;}}","language":"Solidity","languageVersion":"0","compilerVersion":"0.4.8+commit.60cc1668.Linux.g++","abiDefinition":[{"anonymous":false,"constant":false,"payable":false,"name":"multiply","inputs":[{"name":"input","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}],"type":"function","stateMutability":null},{"anonymous":false,"constant":false,"payable":false,"name":"Print","inputs":[{"indexed":false,"name":"","type":"uint256"}],"outputs":[],"type":"event","stateMutability":null}],"userDoc":null,"developerDoc":null}}}

现在我们编译好了代码,需要决定花多少 gas 去部署它。RPC界面有eth_estimateGas方法,会给我们一个预估数量:

curl --data '{"jsonrpc":"2.0","method": "eth_estimateGas", "params": [{"from": "0xa2baba3d6dfdc58b4db8b8e71fc6ebb86cc1cb46", "data": "0x606060405234610000575b60de806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063c6888fa114603c575b6000565b3460005760546004808035906020019091905050606a565b6040518082815260200191505060405180910390f35b60007f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da600783026040518082815260200191505060405180910390a16007820290505b9190505600a165627a7a7230582035ac7f06abdc2da4cccbf5c2df32441b0116f129d1f43e40fded43385160e4110029"}], "id": 5}' https://localhost:8080/rpc
{"jsonrpc":"2.0","id":5,"result":"0x01b425"}

部署合约(ps:gas要填写上面返回的,我的是"0x01b425"):

curl --data '{"jsonrpc":"2.0","method": "eth_sendTransaction", "params": [{"from": "0xa2baba3d6dfdc58b4db8b8e71fc6ebb86cc1cb46", "gas": "0x01b425", "data": "0x606060405234610000575b60de806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063c6888fa114603c575b6000565b3460005760546004808035906020019091905050606a565b6040518082815260200191505060405180910390f35b60007f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da600783026040518082815260200191505060405180910390a16007820290505b9190505600a165627a7a7230582035ac7f06abdc2da4cccbf5c2df32441b0116f129d1f43e40fded43385160e4110029"}], "id": 6}' https://localhost:8080/rpc
{"jsonrpc":"2.0","id":6,"result":"0x834a53cb72d73ab545ad083b91f45219a3f7bf189ea2604ea3dcc5065854ed5f"}

交易由节点接受,交易散表被返回。可以用这个散表来跟踪交易。

下一步,决定部署合约的地址。每个执行的交易都会创建一个接收。这个接收包含交易的各种信息,比如交易被包含在哪个区块,以太坊虚拟机用掉多少gas。如果交易创建了一个合约,它也会包含合约地址。我们可以用eth_getTransactionReceipt RPC方法检查接收,示例如下:

curl --data '{"jsonrpc":"2.0","method": "eth_getTransactionReceipt", "params": ["0x834a53cb72d73ab545ad083b91f45219a3f7bf189ea2604ea3dcc5065854ed5f"], "id": 7}' localhost:8080/rpc
{"jsonrpc":"2.0","id":7,"result":{"transactionHash":"0x834a53cb72d73ab545ad083b91f45219a3f7bf189ea2604ea3dcc5065854ed5f","transactionIndex":"0x0","blockHash":"0x5a3e38889bcd3274a44fd8d0026a486866b07c08f40799a4d53c78b297860a55","blockNumber":"0x6a4c","cumulativeGasUsed":"0x01b425","gasUsed":"0x01b425","contractAddress":"0x729853c2e3dcad1806db9bba235f407d131454b5","logs":[]}}

可以看到,合约地址0x729853c2e3dcad1806db9bba235f407d131454b5被创建。如果你得到了零或null,说明还没有被纳入区块。这时,要检查看看你的矿工是否在运行,然后重新试一遍。

调用智能合约

现在已经部署了合约,有两种方法进行互动,即发送交易或调用。在本节的例子中,将会发送交易到合约的multiply方法里。

在我们的实例中,需要具体说明from、to 和data参数。

  • From是我们账户的公共地址
  • to是合约地址
  • Data参数有一点复杂,它包括了规定调用哪个方法和哪个参数的负载量。这就需要ABI发挥作用了,ABI规定了如何为以太坊虚拟机规定和编码数据。
  • 负载量的字节是功能选择符,规定了调用哪个方法。它取Keccak散表的头4个字节,涵盖功能名称参数类型,并进行十六进制编码。multiply功能接受一个参数。示例如下:
web3.sha3("multiply(uint256)").substring(0, 8)
"c6888fa1"

下一步是编码参数。我们只有一个unit256,假定提供了值6。ABI规定了编码uint字节的方法,如下:

int: enc(X) is the big-endian two’s complement encoding of X, padded on the higher-oder (left) side with 0xff for negative X and with zero 字节s for positive X such that the length is a multiple of 32 bytes.

它会编码到
0000000000000000000000000000000000000000000000000000000000000006
然后将功能选择符和编码参数结合起来,数据就会变成0xc6888fa10000000000000000000000000000000000000000000000000000000000000006

最后,我们发起交易:

curl --data '{"jsonrpc":"2.0","method": "eth_sendTransaction", "params": [{"from": "0xa2baba3d6dfdc58b4db8b8e71fc6ebb86cc1cb46", "to": "0x729853c2e3dcad1806db9bba235f407d131454b5", "data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"}], "id": 8}' https://localhost:8080/rpc
{"jsonrpc":"2.0","id":8,"result":"0x35b1a255b0416d472dab17270914e1d2e68578461807bee848531241b31d5aff"}

由于我们发送了交易,于是有交易散表返回。如果检索接收,可以看到一些新内容,如下:

curl --data '{"jsonrpc":"2.0","method": "eth_getTransactionReceipt", "params": ["0x35b1a255b0416d472dab17270914e1d2e68578461807bee848531241b31d5aff"], "id": 7}' localhost:8080/rpc
{"jsonrpc":"2.0","id":7,"result":{"transactionHash":"0x35b1a255b0416d472dab17270914e1d2e68578461807bee848531241b31d5aff","transactionIndex":"0x0","blockHash":"0xc2581518768edd34852cc2bec632a523d59da4d76157fbf7cc49111c2823a434","blockNumber":"0x6ac2","cumulativeGasUsed":"0x58de","gasUsed":"0x58de","contractAddress":null,"logs":[{"logIndex":"0x0","blockNumber":"0x6ac2","blockHash":"0xc2581518768edd34852cc2bec632a523d59da4d76157fbf7cc49111c2823a434","transactionHash":"0x35b1a255b0416d472dab17270914e1d2e68578461807bee848531241b31d5aff","transactionIndex":"0x0","address":"0x729853c2e3dcad1806db9bba235f407d131454b5","data":"0x000000000000000000000000000000000000000000000000000000000000002a","topics":["0x24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da"]}]}}

看着不好读,我优化一下返回的日志:

{
"jsonrpc":"2.0",
"id":7,
"result":{
"transactionHash":"0x35b1a255b0416d472dab17270914e1d2e68578461807bee848531241b31d5aff",
"transactionIndex":"0x0",
"blockHash":"0xc2581518768edd34852cc2bec632a523d59da4d76157fbf7cc49111c2823a434",
"blockNumber":"0x6ac2",
"cumulativeGasUsed":"0x58de",
"gasUsed":"0x58de",
"contractAddress":null,
"logs":[{
    "logIndex":"0x0",
    "blockNumber":"0x6ac2",
    "blockHash":"0xc2581518768edd34852cc2bec632a523d59da4d76157fbf7cc49111c2823a434",
    "transactionHash":"0x35b1a255b0416d472dab17270914e1d2e68578461807bee848531241b31d5aff",
    "transactionIndex":"0x0",
    "address":"0x729853c2e3dcad1806db9bba235f407d131454b5",
    "data":"0x000000000000000000000000000000000000000000000000000000000000002a",
    "topics":["0x24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da"]
}]}
}

接收包含一个日志。日志由以太坊虚拟机在交易执行时生成,包含接收。如果我们看Multiply功能,可以看到打印事件和输入次数7一起被提出。由于打印事件的参数是uint256,因此可以根据ABI规则对它进行编码,这样就会得到预期的十进制42。

web3.sha3("Print(uint256)")
"24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da"

结果一致。

更新于 2018-07-04

查看ethereumj更多相关的文章或提一个关于ethereumj的问题,也可以与我们一起分享文章