UDN-企业互联网技术人气社区

板块导航

浏览  : 765
回复  : 2

[原生js] 智能合约编写实例

[复制链接]
小辫儿的头像 楼主
发表于 2016-12-31 15:20:03 | 显示全部楼层 |阅读模式
  前言

  本文主要介绍智能合约的工作原理及其部署过程。

  合约部署流程

  一般来说,部署智能合约的步骤为:

  • 启动一个以太坊节点 (例如geth或者testrpc)。
  • 使用solc编译智能合约。 => 获得二进制代码。
  • 将编译好的合约部署到网络。(这一步会消耗以太币,还需要使用你的节点的默认地址或者指定地址来给合约签名。) => 获得合约的区块链地址和ABI(合约接口的JSON表示,包括变量,事件和可以调用的方法)。(译注:作者在这里把ABI与合约接口弄混了。ABI是合约接口的二进制表示。)
  • 用web3.js提供的JavaScript API来调用合约。(根据调用的类型有可能会消耗以太币。)

  下图表示了部署流程:
1.png

  你的DApp可以给用户提供一个界面先部署所需合约再使用之(如图1到4步),也可以假设合约已经部署了(常见方法),直接从使用合约(如图第6步)的界面开始。

  智能合约实例

  接下来我们将使用geth的控制台开发一个简单的智能合约并编译部署在私链上,最后与之交互。完成这些后,我们就能对智能合约的运行机制理解得更加深刻。本例子结合了汪晓明关于以太坊的开发的演示视频和以太坊项目有关交易合约的wiki

  打开测试网络的控制台

  输入以下命令:
  1. geth --datadir "~/ethdev" --dev console 2>> geth.log
复制代码

  显示如下:
  1. zcc@ubuntu:~$ geth --datadir "~/ethdev" --dev console 2>> geth.log
  2. Welcome to the Geth JavaScript console!

  3. instance: Geth/v1.4.18-stable/linux/go1.6.2
  4. coinbase: 0xb005804a49e73acb17d1e7645dfd0a33dde6eb0e
  5. at block: 217 (Tue, 01 Nov 2016 05:21:38 PDT)
  6. datadir: /home/zcc/ethdev
  7. modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0

  8. >
复制代码

  这样我们就打开了测试网的控制台,之后的步骤如无特别说明都将在控制台中完成。

  检查编译器

  我们接下来的智能合约的例子是使用solidity语言开发的。因此,至此之前我们要确保solidity编译器已经正确安装了。输入以下命令检查:
  1. > eth.getCompilers()
  2. ["Solidity"]
复制代码

  我们发现solidity的编译器已经正确安装了。如果返回值为空数组,那么输入以下命令安装:
  1. sudo add-apt-repository ppa:ethereum/ethereum
  2. sudo apt-get update
  3. sudo apt-get install solc
复制代码

  如果输入第一条命令的时候返回错误,请尝试重启系统。

  编写智能合约

  我们编写一个求解与7相乘结果的函数,即输入一个值 a ,返回 a*7 的值。
  1. > source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; }}"
  2. "contract test { function multiply(uint a) returns(uint d) { return a * 7; }}"
复制代码

  编译智能合约
  1. > contract = eth.compile.solidity(source).test
  2. {
  3.   code: "0x606060405260388060106000396000f3606060405260e060020a6000350463c6888fa18114601c575b6002565b3460025760076004350260408051918252519081900360200190f3",
  4.   info: {
  5.     abiDefinition: [{
  6.         constant: false,
  7.         inputs: [...],
  8.         name: "multiply",
  9.         outputs: [...],
  10.         payable: false,
  11.         type: "function"
  12.     }],
  13.     compilerOptions: "--bin --abi --userdoc --devdoc --add-std --optimize -o /tmp/solc359648392",
  14.     compilerVersion: "0.4.3",
  15.     developerDoc: {
  16.       methods: {}
  17.     },
  18.     language: "Solidity",
  19.     languageVersion: "0.4.3",
  20.     source: "contract test { function multiply(uint a) returns(uint d) { return a * 7; }}",
  21.     userDoc: {
  22.       methods: {}
  23.     }
  24.   }
  25. }
复制代码

  准备一个创建合约的账户

  在以太坊上创建智能合约就是使用一个外部账户(EOA)向区块链中发送一个交易。因此,我们需要准备一个有余额并被激活的以太坊外部账户。

  查看是否有可用账户:
  1. > personal.listAccounts
  2. []
复制代码

  返回为空,说明没有可用账户。创建一个外部账户:
  1. > personal.newAccount('123456')
  2. "0x62b1746767522b36f6421e630fa0198151d72964"
复制代码

  注意: personal.newAccount() 函数里的参数是账号密码,返回值是创建的新账号地址。

  这个时候,我们再次使用 personal.listAccounts 命令查看可用账户:
  1. > personal.listAccounts
  2. ["0x62b1746767522b36f6421e630fa0198151d72964"]
复制代码

  我们看到函数返回值为一个数组,数组目前只有一个元素,就是我们刚才创建的账号。

  我们查看一下刚才创建的账户余额:
  1. > web3.eth.getBalance(personal.listAccounts[0])
  2. 0
复制代码

  返回值为0,说明新创建的账户没有以太币。这个时候我们就可以开启挖矿来获得以太币。

  首先开启挖矿:
  1. > miner.start()
  2. true
复制代码

  为了检测挖矿的状态,我们可以再另开起一个终端用于检测挖矿的状态。在新开起的终端中输入以下命令实时显示挖矿的状态:
  1. tail -f geth.log
复制代码

  这样我们就能看到如下所示的挖矿状态:
  1. zcc@ubuntu:~$ tail -f geth.log
  2. I1102 10:10:21.382666 eth/backend.go:201] Blockchain DB Version: 3
  3. I1102 10:10:21.382691 eth/backend.go:226] ethash used in test mode
  4. I1102 10:10:21.384471 core/blockchain.go:214] Last header: #219 [7a2335e9…] TD=28838912
  5. I1102 10:10:21.384501 core/blockchain.go:215] Last block: #219 [7a2335e9…] TD=28838912
  6. I1102 10:10:21.384507 core/blockchain.go:216] Fast block: #219 [7a2335e9…] TD=28838912
  7. I1102 10:10:21.389663 p2p/server.go:313] Starting Server
  8. I1102 10:10:23.428074 p2p/discover/udp.go:217] Listening, enode://c7afa32e281c06bb529b9c0f0d9537ab4386cac1b2ba7de6bd777bdb15e3b7d0cbd260fc57f82b8f33b0c0ff83772b7d4da7c68017f8e05a2c08c77754d6ed62@[::]:39428
  9. I1102 10:10:23.429805 whisper/whisper.go:176] Whisper started
  10. I1102 10:10:23.439973 p2p/server.go:556] Listening on [::]:46364
  11. I1102 10:10:23.440985 node/node.go:296] IPC endpoint opened: /home/zcc/ethdev/geth.ipc
  12. I1102 10:13:11.541025 miner/miner.go:119] Starting mining operation (CPU=1 TOT=2)
  13. I1102 10:13:11.541389 miner/worker.go:539] commit new work on block 220 with 0 txs & 0 uncles. Took 196.057µs
  14. I1102 10:13:11.541464 ethash.go:259] Generating DAG for epoch 0 (size 32768) (0000000000000000000000000000000000000000000000000000000000000000)
  15. I1102 10:13:11.541625 ethash.go:291] Generating DAG: 0%
  16. I1102 10:13:11.541678 ethash.go:291] Generating DAG: 1%
  17. I1102 10:13:11.541716 ethash.go:291] Generating DAG: 2%
  18. I1102 10:13:11.541751 ethash.go:291] Generating DAG: 3%
  19. I1102 10:13:11.541787 ethash.go:291] Generating DAG: 4%
  20. I1102 10:13:11.541822 ethash.go:291] Generating DAG: 5%
  21. ...
  22. I1102 10:13:11.547403 ethash.go:291] Generating DAG: 95%
  23. I1102 10:13:11.547450 ethash.go:291] Generating DAG: 96%
  24. I1102 10:13:11.547497 ethash.go:291] Generating DAG: 97%
  25. I1102 10:13:11.547543 ethash.go:291] Generating DAG: 98%
  26. I1102 10:13:11.547590 ethash.go:291] Generating DAG: 99%
  27. I1102 10:13:11.547646 ethash.go:291] Generating DAG: 100%
  28. I1102 10:13:11.547691 ethash.go:276] Done generating DAG for epoch 0, it took 6.229048ms
  29. I1102 10:13:11.548248 eth/backend.go:454] Automatic pregeneration of ethash DAG ON (ethash dir: /home/zcc/.ethash)
  30. I1102 10:13:11.548375 eth/backend.go:461] checking DAG (ethash dir: /home/zcc/.ethash)
  31. I1102 10:13:24.938687 miner/worker.go:342] ��  Mined block (#220 / 1fdebe6d). Wait 5 blocks for confirmation
  32. I1102 10:13:24.938953 miner/worker.go:539] commit new work on block 221 with 0 txs & 0 uncles. Took 86.067µs
  33. I1102 10:13:24.939156 miner/worker.go:539] commit new work on block 221 with 0 txs & 0 uncles. Took 66.063µs
  34. I1102 10:13:29.874128 miner/worker.go:342] ��  Mined block (#221 / ff995b19). Wait 5 blocks for confirmation
复制代码

  因为我之前已经挖过矿了,所以可以看到现在开始挖的区块编号为220。

  再次使用 web3.eth.getBalance(personal.listAccounts[0]) 命令来查看新建立的账号的余额:
  1. > web3.eth.getBalance(personal.listAccounts[0])
  2. 65000000000000000000
复制代码

  这时我们看到当前账户余额为6.5e19wei,即65ether。我们可以参看 Denominations 来互相转换以太币的几种单位。

  至此,用于创建合约的外部账户已经准备完毕。

  指定创建合约的外部账户

  我们首先需要从当前的账户里选择一个作为创建智能合约的外部账户:
  1. > address = eth.accounts[0]
  2. "0x62b1746767522b36f6421e630fa0198151d72964"
复制代码

  然后将该账户激活:
  1. > personal.unlockAccount(address)
  2. Unlock account 0x62b1746767522b36f6421e630fa0198151d72964
  3. Passphrase:
  4. true
复制代码

  我们在使用 personal.unlockAccount() 函数的时候,可以选择指定一个解锁时间(以秒为单位),超出这个时间后会自动上锁。如果不指定的话,那么用户的账户就会在一定时间后自动上锁。例如:
  1. > personal.unlockAccount(address,'123456',10000)
  2. true
复制代码

  personal.unlockAccount() 函数里第一个参数是要解锁的账户,第二个参数是账号密码,第三个是需要解锁的时间。在这个例子中,我们设定在10000秒内账户都处于激活状态。设定账号的解锁时间是个好方式,尤其是之后我们需要使用这个账号再次进行交易操作时,这样就省去了再次解锁账号的麻烦。不过,解锁时间不应设定过长,否则可能有安全隐患。

  部署合约

  部署合约就是将编译好的合约字节码通过外部账号发送交易的形式部署到以太坊区块链上。输入以下命令:
  1. > abi = [{constant:false,inputs:{name:'a',type:'uint256'}}]
  2. [{
  3.     constant: false,
  4.     inputs: {
  5.       name: "a",
  6.       type: "uint256"
  7.     }
  8. }]
  9. > MyContract = eth.contract(abi)
  10. {
  11.   abi: [{
  12.       constant: false,
  13.       inputs: {
  14.         name: "a",
  15.         type: "uint256"
  16.       }
  17.   }],
  18.   eth: {
  19.     accounts: ["0x62b1746767522b36f6421e630fa0198151d72964"],
  20.     blockNumber: 292,
  21.     coinbase: "0x62b1746767522b36f6421e630fa0198151d72964",
  22.     compile: {
  23.       lll: function(),
  24.       serpent: function(),
  25.       solidity: function()
  26.     },
  27.     defaultAccount: undefined,
  28.     defaultBlock: "latest",
  29.     gasPrice: 20000000000,
  30.     hashrate: 3498,
  31.     mining: true,
  32.     pendingTransactions: [],
  33.     syncing: false,
  34.     call: function(),
  35.     contract: function(abi),
  36.     estimateGas: function(),
  37.     filter: function(fil, callback),
  38.     getAccounts: function(callback),
  39.     getBalance: function(),
  40.     getBlock: function(),
  41.     getBlockNumber: function(callback),
  42.     getBlockTransactionCount: function(),
  43.     getBlockUncleCount: function(),
  44.     getCode: function(),
  45.     getCoinbase: function(callback),
  46.     getCompilers: function(),
  47.     getGasPrice: function(callback),
  48.     getHashrate: function(callback),
  49.     getMining: function(callback),
  50.     getNatSpec: function(),
  51.     getPendingTransactions: function(callback),
  52.     getStorageAt: function(),
  53.     getSyncing: function(callback),
  54.     getTransaction: function(),
  55.     getTransactionCount: function(),
  56.     getTransactionFromBlock: function(),
  57.     getTransactionReceipt: function(),
  58.     getUncle: function(),
  59.     getWork: function(),
  60.     iban: function(iban),
  61.     icapNamereg: function(),
  62.     isSyncing: function(callback),
  63.     namereg: function(),
  64.     resend: function(),
  65.     sendIBANTransaction: function(),
  66.     sendRawTransaction: function(),
  67.     sendTransaction: function(),
  68.     sign: function(),
  69.     signTransaction: function(),
  70.     submitTransaction: function(),
  71.     submitWork: function()
  72.   },
  73.   at: function(address, callback),
  74.   getData: function(),
  75.   new: function()
  76. }
  77. > myContract = MyContract.new({from:address,data:contract.code})
  78. {
  79.   abi: [{
  80.       constant: false,
  81.       inputs: {
  82.         name: "a",
  83.         type: "uint256"
  84.       }
  85.   }],
  86.   address: undefined,
  87.   transactionHash: "0xd10602e2099ab5873c762f070eb90a9fd559270484fbcebd4170d441848b9232"
  88. }
复制代码

  这时,我们可以检查一下交易池,查看当前交易的待处理状况:
  1. > txpool.status
  2. {
  3.   pending: 1,
  4.   queued: 0
  5. }
复制代码

  我们可以看到当前的交易池中有一个交易正在等待确认。然后,我们查看待确认交易的详细内容:
  1. > eth.getBlock("pending",true).transactions
  2. [{
  3.     blockHash: "0x0299731121321b817206fb07187f94cd4537e3196e940b45e95b4e1709aadbf4",
  4.     blockNumber: 294,
  5.     from: "0x62b1746767522b36f6421e630fa0198151d72964",
  6.     gas: 90000,
  7.     gasPrice: 20000000000,
  8.     hash: "0xd10602e2099ab5873c762f070eb90a9fd559270484fbcebd4170d441848b9232",
  9.     input: "0x606060405260388060106000396000f3606060405260e060020a6000350463c6888fa18114601c575b6002565b3460025760076004350260408051918252519081900360200190f3",
  10.     nonce: 0,
  11.     to: null,
  12.     transactionIndex: 0,
  13.     value: 0
  14. }]
复制代码

  我们从显示出来的结果可以看出当前交易的一些内容。例如, from 数据项就是我们发送交易的地址, input 就是合约编译完成的字节码,这些内容均与我们之前的设定相同。而且,我们可以看到新的交易创建在第294号区块中。

  与此同时,我们也可以查看一下刚才在新的终端中创建的挖矿日志。我们在日志中找到这样的一行信息:
  1. I1102 11:37:46.573298 eth/api.go:1183] Tx(0xd10602e2099ab5873c762f070eb90a9fd559270484fbcebd4170d441848b9232) created: 0x115ced3f8b7ea92d324902e3a3a421a07540eb2b
复制代码

  这说明交易已经发送到区块链中了,正在等待矿工的确认。

  耐心等待一段时间,等待矿工确认完成后,我们再次使用 txpool.status 命令查看交易池的状态:
  1. > txpool.status
  2. {
  3.   pending: 0,
  4.   queued: 0
  5. }
复制代码

  我们发现交易池已经没有待确认的交易了。我们使用 eth.getBlock(294) 命令查看第294号区块的信息:
  1. > eth.getBlock(294)
  2. {
  3.   difficulty: 131072,
  4.   extraData: "0xd783010412844765746887676f312e362e32856c696e7578",
  5.   gasLimit: 4712388,
  6.   gasUsed: 36946,
  7.   hash: "0x4da580cce8bc5ed34aa5a7bbeeb730d98cd7b425698f40433365f1463bc572ee",
  8.   logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  9.   miner: "0x62b1746767522b36f6421e630fa0198151d72964",
  10.   nonce: "0x02956290420fe41f",
  11.   number: 294,
  12.   parentHash: "0x3fd3a8126d25dec98aaf2696c2812de2d2f4f28bae729dacd78634f046d8a1cc",
  13.   receiptRoot: "0x44c28d68986fb814e431117f3759b24d5e668feeff8dacaadb7c08fd11b4f2fc",
  14.   sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  15.   size: 696,
  16.   stateRoot: "0xab235f738e080d41efb4692633401917dfb9f04625d9476b6875e5d335f1b014",
  17.   timestamp: 1478057886,
  18.   totalDifficulty: 38670080,
  19.   transactions: ["0xd10602e2099ab5873c762f070eb90a9fd559270484fbcebd4170d441848b9232"],
  20.   transactionsRoot: "0xc983a661a81a1173bd0196a44e8d1092250f6c6a21acc1847cd7416de9d76f55",
  21.   uncles: []
  22. }
复制代码

  我们发现hash值为 0xd10602e2099ab5873c762f070eb90a9fd559270484fbcebd4170d441848b9232 的交易确实在第294号区块中。

  与合约交互

  首先我们需要使用 eth.contract 来定义一个合约类,定义的合约类遵从ABI定义:
  1. > Multiply7 = eth.contract(contract.info.abiDefinition)
  2. {
  3.   abi: [{
  4.       constant: false,
  5.       inputs: [{...}],
  6.       name: "multiply",
  7.       outputs: [{...}],
  8.       payable: false,
  9.       type: "function"
  10.   }],
  11.   eth: {
  12.     accounts: ["0x62b1746767522b36f6421e630fa0198151d72964"],
  13.     blockNumber: 346,
  14.     coinbase: "0x62b1746767522b36f6421e630fa0198151d72964",
  15.     compile: {
  16.       lll: function(),
  17.       serpent: function(),
  18.       solidity: function()
  19.     },
  20.     defaultAccount: undefined,
  21.     defaultBlock: "latest",
  22.     gasPrice: 20000000000,
  23.     hashrate: 39840,
  24.     mining: true,
  25.     pendingTransactions: [],
  26.     syncing: false,
  27.     call: function(),
  28.     contract: function(abi),
  29.     estimateGas: function(),
  30.     filter: function(fil, callback),
  31.     getAccounts: function(callback),
  32.     getBalance: function(),
  33.     getBlock: function(),
  34.     getBlockNumber: function(callback),
  35.     getBlockTransactionCount: function(),
  36.     getBlockUncleCount: function(),
  37.     getCode: function(),
  38.     getCoinbase: function(callback),
  39.     getCompilers: function(),
  40.     getGasPrice: function(callback),
  41.     getHashrate: function(callback),
  42.     getMining: function(callback),
  43.     getNatSpec: function(),
  44.     getPendingTransactions: function(callback),
  45.     getStorageAt: function(),
  46.     getSyncing: function(callback),
  47.     getTransaction: function(),
  48.     getTransactionCount: function(),
  49.     getTransactionFromBlock: function(),
  50.     getTransactionReceipt: function(),
  51.     getUncle: function(),
  52.     getWork: function(),
  53.     iban: function(iban),
  54.     icapNamereg: function(),
  55.     isSyncing: function(callback),
  56.     namereg: function(),
  57.     resend: function(),
  58.     sendIBANTransaction: function(),
  59.     sendRawTransaction: function(),
  60.     sendTransaction: function(),
  61.     sign: function(),
  62.     signTransaction: function(),
  63.     submitTransaction: function(),
  64.     submitWork: function()
  65.   },
  66.   at: function(address, callback),
  67.   getData: function(),
  68.   new: function()
  69. }
复制代码

  然后得到合约实例:
  1. > myMultiply7 = Multiply7.at(myContract.address)
  2. {
  3.   abi: [{
  4.       constant: false,
  5.       inputs: [{...}],
  6.       name: "multiply",
  7.       outputs: [{...}],
  8.       payable: false,
  9.       type: "function"
  10.   }],
  11.   address: "0x115ced3f8b7ea92d324902e3a3a421a07540eb2b",
  12.   transactionHash: null,
  13.   allEvents: function(),
  14.   multiply: function()
  15. }
复制代码

  我们可以看到,在实例中能够调用的函数有两个: allEvents 和 multiply 。 multiply 显然是一开始我们定义合约的时候写的函数。我们可以使用两种方法来调用 multiply 函数: sendTransaction(3, {from: address}) 和 call(3) 。

  方法一:使用 sendTransaction(3, {from: address}) 调用:
  1. > myMultiply7.multiply.sendTransaction(3, {from:address})
  2. "0x1c46e9e85f5db00ba2bcbb2e8774f61cdb81620a46a19ea9cf6a532795d5950c"
复制代码

  我们可以发现返回的值为一个字符串,这个字符串代表的是发送的交易的hash值。使用这个方法调用合约将会使调用的结果成为全局共识的一部分。

  方法二:使用 call(3) 调用:
  1. > myMultiply7.multiply.call(3)
  2. 21
复制代码

  我们可以发现返回值为21,这个值刚好是 3*7 的运算结果。使用这个方法调用合约只会在本地上运行。

  从以上两种方法的调用结果来看,如果你只想得知运算的结果,那么只需要使用方法二。而如果想要改变合约的状态,那么就使用方法一。

相关帖子

发表于 2016-12-31 15:20:33 来自手机 | 显示全部楼层
不错的帖子,支持下
使用道具 举报

回复

发表于 2016-12-31 15:20:33 | 显示全部楼层
路过 帮顶 嘿嘿
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部