.. _Accessing Contracts and Transactions: ******************************************************************************** 访问合约和交易 ******************************************************************************** RPC ================================================================================ 在前面的章节中,我们介绍了合约的编写、部署和交互。现在到了深入了解以太坊网络和智能合约交互细节到时间。 以太坊节点都提供 `RPC `_ 接口,这些接口向Ðapp提供访问以太坊区块链和节点支持的功能,例如编译智能合约代码。它使用 `JSON-RPC 2.0 `_ 规范(不支持通知或变量命名)的子集作为序列化协议,并且支持HTTP和IPC。 如果你对细节不关心仅仅只关心javascript库的使用,那么你可以跳过下面的章节,直接阅读: :ref:`Using Web3 ` 。 协议 ================================================================================ RPC接口使用了一些不属于JSON-RPC 2.0规范的协议。 * 数字使用十六进制编码,这个决定是因为一些语言不支持或有限支持超大数字类型。防止这种类型错误的发生,数值被编码为十六进制,并且交由开发者进行妥善处理,请参考wiki上的示例 `hex encoding section `_ 。 * 默认的区块编号,一些RPC方法接受区块编号,在一些情况下,不能能获取区块编号或者非常不方便获得区块编号。在这种情况下,默认区块编号可以是这些字符串中的其中一个 ["earliest", "latest", "pending"] 。查看 `wiki page `_ 来获取RPC方法可用的默认区块参数列表。 部署合约 ================================================================================ 我们将通过不同的步骤,仅使用RPC接口来部署如下的合约。 .. code:: js contract Multiply7 { event Print(uint); function multiply(uint input) returns (uint) { Print(input * 7); return input * 7; } } 需要做的第一件事,就是确保HTTP RPC接口被启用 ,这意味着我们需要在启动时给geth提供 ``--rpc`` 标识,给eth提供 ``-j`` 标识。在这个例子中我们使用一个geth私有开发节点,这样的话我们就不需要使用正式网络上的以太币。 .. code:: bash > geth --rpc --dev --mine --minerthreads 1 --unlock 0 console 2>>geth.log 我们在这个端口启用HTTP RPC接口: ``http://localhost:8545``。 .. note:: geth支持 `CORS(跨域资源共享) `_,查看参数 ``--rpccorsdomain`` 来获取更多信息。 我们可以通过 `curl `_ 获取账户地址和余额来验证接口是否已经运行,请注意下面例子中的数据和你本地的节点不一样,如果你想尝试如下的命令请修改请求的参数。 .. code:: bash > curl --data '{"jsonrpc":"2.0","method":"eth_coinbase", "id":1}' localhost:8545 {"id":1,"jsonrpc":"2.0","result":["0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a"]} > curl --data '{"jsonrpc":"2.0","method":"eth_getBalance", "params": ["0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", "latest"], "id":2}' localhost:8545 {"id":2,"jsonrpc":"2.0","result":"0x1639e49bba16280000"} 还记得我们前面说过数字都是以十六进制编码的吗?在这个情况中账户余额返回以wei为单位的十六进制编码字符串,如果我们想得到以ether为单位的数值我们可以通过命令台使用web3。 .. code:: js > web3.fromWei("0x1639e49bba16280000", "ether") "410" 现在我们在私有开发链上有了一些以太币,可以进行合约的部署了,第一步需要验证solidity编译器处于可用状态,我们可以通过 ``eth_getCompilers`` 这个RPC方法来获取当前可用的编译器。 .. code:: bash > curl --data '{"jsonrpc":"2.0","method": "eth_getCompilers", "id": 3}' localhost:8545 {"id":3,"jsonrpc":"2.0","result":["Solidity"]} 我们看到solidity编译器可用,如果不可用请参考 `相关文档 `_ 说明。 下一步把Multiply7合约编译为可以发送给以太坊虚拟机的字节码。 .. code:: bash > curl --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": 4}' localhost:8545 {"id":4,"jsonrpc":"2.0","result":{"Multiply7":{"code":"0x6060604052605f8060106000396000f3606060405260e060020a6000350463c6888fa18114601a575b005b60586004356007810260609081526000907f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da90602090a15060070290565b5060206060f3","info":{"source":"contract Multiply7 { event Print(uint); function multiply(uint input) returns (uint) { Print(input * 7); return input * 7; } }","language":"Solidity","languageVersion":"0.2.2","compilerVersion":"0.2.2","compilerOptions":"--bin --abi --userdoc --devdoc --add-std --optimize -o /tmp/solc205309041","abiDefinition":[{"constant":false,"inputs":[{"name":"input","type":"uint256"}],"name":"multiply","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"","type":"uint256"}],"name":"Print","type":"event"}],"userDoc":{"methods":{}},"developerDoc":{"methods":{}}}}}} 现在我们已经编译完成了代码,我们需要决定花费多少瓦斯来部署它,RPC接口的 ``eth_estimateGas`` 方法可以给我们提供一个参考值。 .. code:: bash > curl --data '{"jsonrpc":"2.0","method": "eth_estimateGas", "params": [{"from": "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", "data": "0x6060604052605f8060106000396000f3606060405260e060020a6000350463c6888fa18114601a575b005b60586004356007810260609081526000907f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da90602090a15060070290565b5060206060f3"}], "id": 5}' localhost:8545 {"id":5,"jsonrpc":"2.0","result":"0xb8a9"} 最终,我们部署好了合约。 .. code:: bash > curl --data '{"jsonrpc":"2.0","method": "eth_sendTransaction", "params": [{"from": "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", "gas": "0xb8a9", "data": "0x6060604052605f8060106000396000f3606060405260e060020a6000350463c6888fa18114601a575b005b60586004356007810260609081526000907f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da90602090a15060070290565b5060206060f3"}], "id": 6}' localhost:8545 {"id":6,"jsonrpc":"2.0","result":"0x3a90b5face52c4c5f30d507ccf51b0209ca628c6824d0532bcd6283df7c08a7c"} 交易被节点接受并且交易的哈希被返回,我们可以通过这个哈希来追踪交易。 下一步来决定我们部署合约的地址,交易的每一次执行都会创建一个收据,这个收据包含交易的各种信息,例如交易包含在哪个区块中、交易过程花费了多少瓦斯等。如果一个交易创建了一个合约,它也会包含合约的地址,我们可以通过RPC接口的 ``eth_getTransactionReceipt`` 方法来获取收据。 .. code:: bash > curl --data '{"jsonrpc":"2.0","method": "eth_getTransactionReceipt", "params": ["0x3a90b5face52c4c5f30d507ccf51b0209ca628c6824d0532bcd6283df7c08a7c"], "id": 7}' localhost:8545 {"id":7,"jsonrpc":"2.0","result":{"transactionHash":"0x3a90b5face52c4c5f30d507ccf51b0209ca628c6824d0532bcd6283df7c08a7c","transactionIndex":"0x0","blockNumber":"0x4c","blockHash":"0xe286656e478a1b99030e318d0f5c3a61a644f25e63deaa8be52e80da1e7b0c47","cumulativeGasUsed":"0xb8a9","gasUsed":"0xb8a9","contractAddress":"0x6ff93b4b46b41c0c3c9baee01c255d3b4675963d","logs":[]}} 我们可以看到合约创建于 ``0x6ff93b4b46b41c0c3c9baee01c255d3b4675963d`` ,如果你获取到null收据,那么说明交易还没有被打包进区块中,请检查你的矿工还在工作并且等待一会再重新确认。 与智能合约交互 ================================================================================ 我们的合约已经部署完毕,可以与之进行交互了。前面已经说过,我们有 `两种方法 ` 可以与合约进行交互: `sendTransaction` 和 `call` 。在下面的例子中,我们将使用sendTransaction的方式来与合约中的multiply方法交互。 通过阅读 `eth_sendTransaction `_ 的文档,我们发现需要提供一些参数,分别需要指定:``from`` , ``to`` 和 ``data`` 。`from` 是我们发起交易账户的公开地址, `to` 是合约的地址, `data` 参数有一点复杂,它的内容包含了哪个函数将被调用并且规定了调用的参数,这是通过ABI进行实现的,ABI定义了如何为以太坊虚拟机(EVM)定义和编码数据,你可以阅读 `ABI细节wiki `_ 。 `data` 的字节序列是一个函数选择器,定义了哪个函数将会被调用,它是通过Keccak哈希计算函数名称和调用参数,然后取其哈希值的前4字节并用十六进制进行编码,我们前面定义的乘法函数接受一个256位的整型数,这让我们有: .. code:: js > web3.sha3("multiply(uint256)").substring(0, 8) "c6888fa1" 更多细节,请点击 `函数选择器 `_ 。 下一步是对参数进行编码,我们的参数只有一个uint256,我们假设这个值为6, `参数编码规则 `_ 中的如下章节指出了我们应该如何编码uint256。 `int: enc(X) 是X的大端二进制补码编码,0xff填充在负数的高位(左侧),用零填充使得长度为32字节。` 这样编码结果为 ``0000000000000000000000000000000000000000000000000000000000000006`` 。 将函数编码和参数编码进行组合,就得到我们的 ``data`` ,也就是 ``0xc6888fa100000000000000000000000000000000000000000000000000000006``. 我们来尝试调用: .. code:: bash > curl --data '{"jsonrpc":"2.0","method": "eth_sendTransaction", "params": [{"from": "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", "to": "0x6ff93b4b46b41c0c3c9baee01c255d3b4675963d", "data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"}], "id": 8}' localhost:8545 {"id":8,"jsonrpc":"2.0","result":"0x759cf065cbc22e9d779748dc53763854e5376eea07409e590c990eafc0869d74"} 调用完毕后我们得到交易的哈希,如果我们获取收据我们可以得到如下内容: .. code-block:: js :emphasize-lines: 7 { blockHash: "0xbf0a347307b8c63dd8c1d3d7cbdc0b463e6e7c9bf0a35be40393588242f01d55", blockNumber: 268, contractAddress: null, cumulativeGasUsed: 22631, gasUsed: 22631, logs: [{ address: "0x6ff93b4b46b41c0c3c9baee01c255d3b4675963d", blockHash: "0xbf0a347307b8c63dd8c1d3d7cbdc0b463e6e7c9bf0a35be40393588242f01d55", blockNumber: 268, data: "0x000000000000000000000000000000000000000000000000000000000000002a", logIndex: 0, topics: ["0x24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da"], transactionHash: "0x759cf065cbc22e9d779748dc53763854e5376eea07409e590c990eafc0869d74", transactionIndex: 0 }], transactionHash: "0x759cf065cbc22e9d779748dc53763854e5376eea07409e590c990eafc0869d74", transactionIndex: 0 } 收据中包含日志,这个日志是以太坊虚拟机在交易执行的过程中创建的,并且被包含在收据中。如果我们查看乘法函数,会发现Print事件将输入的参数乘以7,通过ABI编码规则,我们可以解码得到预期的结果42。除了data字段之外,值得注意的是topics字段,通过它可以得知是哪个事件创建了日志。 .. code:: js > web3.sha3("Print(uint256)") "24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da" 你可以阅读更多有关event、topics的内容,可以查看 `Solidity教程 `_ 。 这里我们仅仅做了简单的介绍,想要查看完整可用的RPC方法列表请查看: `RPC的wiki页面 `_ 。 .. _using_web3.js: Web3.js ================================================================================ 从上面的例子中我们看到JSON-RPC接口的使用是非常云长乏味并且容易出错的,特别是我们需要使用ABI时,Web3.js是一个在以太坊RPC接口上工作的javascript库,它的目标是提供一个更友好的交互界面,同时减少出错的概率。 使用web3来部署输入乘7的合约将会是如下的样子: .. code:: js var source = 'contract Multiply7 { event Print(uint); function multiply(uint input) returns (uint) { Print(input * 7); return input * 7; } }'; var compiled = web3.eth.compile.solidity(source); var code = compiled.Multiply7.code; var abi = compiled.Multiply7.info.abiDefinition; web3.eth.contract(abi).new({from: "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", data: code}, function (err, contract) { if (!err && contract.address) console.log("deployed on:", contract.address); } ); deployed on: 0x0ab60714033847ad7f0677cc7514db48313976e2 加载一个已经部署的合约并且通过交易来进行交互: .. code:: js var source = 'contract Multiply7 { event Print(uint); function multiply(uint input) returns (uint) { Print(input * 7); return input * 7; } }'; var compiled = web3.eth.compile.solidity(source); var Multiply7 = web3.eth.contract(compiled.Multiply7.info.abiDefinition); var multi = Multiply7.at("0x0ab60714033847ad7f0677cc7514db48313976e2") multi.multiply.sendTransaction(6, {from: "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a"}) 注册一个名为 ``Print`` 的事件来创建日志。 .. code:: js multi.Print(function(err, data) { console.log(JSON.stringify(data)) }) {"address":"0x0ab60714033847ad7f0677cc7514db48313976e2","args": {"":"21"},"blockHash":"0x259c7dc07c99eed9dd884dcaf3e00a81b2a1c83df2d9855ce14c464b59f0c8b3","blockNumber":539,"event":"Print","logIndex":0, "transactionHash":"0x5c115aaa5418118457e96d3c44a3b66fe9f2bead630d79455d0ecd832dc88d48","transactionIndex":0} 更多信息请查看 `web3.js的wiki页面 `_ 。 命令行 ================================================================================ geth的 `命令行 `_ 提供javascript运行时命令行接口,它可以连接到本地或远程的geth或eth节点,运行时会加载web3.js库供用户使用,这就使得用户可以通过web3.js命令来部署和调用智能合约,事实上,前面 :ref:`Web3.js ` 中的代码可以直接拷贝到命令行中进行运行。 查看线上合约和交易 ================================================================================ 你可以通过多种方式来查看以太坊区块链上的合约和交易,点击查看 :ref:`区块链查询 `。 .. _blockchain_explorers: 区块链查询网站 -------------------------------------------------------------------------------- - `EtherChain `_ - `EtherCamp `_ - `EtherScan `_ (and for `Testnet `_) 其他资源 -------------------------------------------------------------------------------- * `EtherNodes `_ - 节点的地理位置和客户端类型划分 * `EtherListen `_ - 实时查看以太坊交易信息