搭车者以太坊智能合约指南 (二)

月亮🌛   |     |   866 次阅读

  1. 合约代码迭代

让我们修改合约来支持多个文件验证。把原文件复制到名为contracts/ProofOfExistence2.sol的新文件中,并且采取以下改变。主要的变化包括:我们把‘proof’变量变成了bytes32的数组,并且命名为‘proofs’,我们把它变成私有,然后加入一个通过循环访问数组来检查一个文件是否被公正的函数。

// Proof of Existence contract, version 2
contract ProofOfExistence2 {
  // state
  bytes32[] private proofs;  // store a proof of existence in the contract state
  // *transactional function*
  function storeProof(bytes32 proof) {
    proofs.push(proof);
  }  // calculate and store the proof for a document
  // *transactional function*
  function notarize(string document) {
    var proof = calculateProof(document);
    storeProof(proof);
  }  // helper function to get a document's sha256
  // *read-only function*
  function calculateProof(string document) constant returns (bytes32) {
    return sha256(document);
  }  // check if a document has been notarized
  // *read-only function*
  function checkDocument(string document) constant returns (bool) {
    var proof = calculateProof(document);
    return hasProof(proof);
  }  // returns true if proof is stored
  // *read-only function*
  function hasProof(bytes32 proof) constant returns (bool) {
    for (var i = 0; i < proofs.length; i++) {
      if (proofs[i] == proof) {
        return true;
      }
    }
    return false;
  }
}

让我们与新的函数互动一下:(不要忘了更新migrations/2_deploy_contracts.js来加入新的合约并且运行‘truffle mirgrate--reset’)


// deploy contracts
truffle(default)>  migrate --reset// Get the new version of the contract
truffle(default)> var poe = ProofOfExistence2.deployed()// let's check for some new document, and it shouldn't be there.
truffle(default)> poe.checkDocument('hello').then(console.log)Promise { <pending> }
false// let's now add that document to the proof store
truffle(default)> poe.notarize('hello')Promise { <pending> }// let's now check again if the document has been notarized!
truffle(default)> poe.checkDocument('hello').then(console.log)Promise { <pending> }
true
// success!// we can also store other documents and they are recorded too
truffle(default)> poe.notarize('some other document');
truffle(default)> poe.checkDocument('some other document').then(console.log)Promise { <pending> }
true

这一版比第一版强,但是仍然有些问题。注意每一次我们想要检查一个文件是否有被公正过时都需要循环访问所有存在的‘proofs’。储存proofs更好的结构会是用映射(map)。走运的是,Solidity支持映射结构,在这个语言里称此结构为mappings。另外一个我们会在这一版代码做出的改进是我们会去掉那些多余的标识只读(read-only)或交易(transactional)函数的那些注释。我想现在你已经都知道这些了:)下面是最终版本,我想应该不难理解,因为是从之前的版本一点点变过来的:


// Proof of Existence contract, version 3
contract ProofOfExistence3 {  mapping (bytes32 => bool) private proofs;  // store a proof of existence in the contract state
  function storeProof(bytes32 proof) {
    proofs[proof] = true;
  }  // calculate and store the proof for a document
  function notarize(string document) {
    var proof = calculateProof(document);
    storeProof(proof);
  }  // helper function to get a document's sha256
  function calculateProof(string document) constant returns (bytes32) {
    return sha256(document);
  }  // check if a document has been notarized
  function checkDocument(string document) constant returns (bool) {
    var proof = calculateProof(document);
    return hasProof(proof);
  }  // returns true if proof is stored
  function hasProof(bytes32 proof) constant returns(bool) {
    return proofs[proof];
  }
}

这下看起来已经足够好了。它跟第二版运行起来没有差别。记得更新移动文档(migration file)同时再次运行‘truffle migrate -- reset’来测试一下它。这个教程中的所有代码都可以在这里找到。

  1. 在真正的测试网络上部署

在你用testrpc在模拟网络上大量测试你的合约之后,你就可以在真正的网络上测试你的合约啦!这就需要你有一个真正的testnet/livenet以太坊客户端。点击这里看如何安装geth的说明。
开发的过程中,你应该在testnet模式中运行你的节点,这样你就可以在没有损失真金白银的风险下进行所有的测试。Testnet模式(在以太坊也叫Morden)基本上与真正的以太坊一模一样,但是这里的以太币token没有任何金钱价值。不要发懒,记得永远要在testnet模式下开发,如果你因为编程错误而损失以太币,你会非常后悔的。
在testnet模式下运行geth, 打开RPC服务器:

geth --testnet --rpc console 2>> geth.log

这会打开一个你可以输入基本口令来控制你的节点/客户端的控制器。你的节点会开始下载testnet区块链,你可以在eth.blockNumber上查看下载进度。区块链下载的同时,你仍然可以运行口令。比如,让我们设置一个账户:(千万要记住密码!)

> personal.newAccount()
Passphrase:
Repeat passphrase:
"0xa88614166227d83c93f4c50be37150b9500d51fc"

让我们发送一些以太币过去并且查询余额。你可以从这里获得免费testnet以太币:https://zerogox.com/ethereum/wei_faucet. 只需复制粘帖你刚刚生成的那个地址,这个水龙头就是给你发送一个以太币。想要查询余额,运行以下代码:

> eth.getBalance(eth.accounts[0])
0

它会告诉你没有余额因为你还没有与全网络同步。在你等待的同时,去testnet block explorer去查询一下余额。那里,你也可以看到testnet目前最高的块数(写这个的时候是#1355293),你可以将这个信息与eth.blockNumber的信息结合去判断你的节点是否已经完成同步。
一旦你的节点同步好,你就可以开始通过Truffle在testnet上部署你的合约了。首先,解锁你的主geth账户,这样Truffle就可以使用它。确认里面有一些余额,否则你将不能够把新的合约推向网络。

> personal.unlockAccount(eth.accounts[0], "mypassword", 24*3600)
true
> eth.getBalance(eth.accounts[0])
1000000000000000000

准备好了吧!如果这两个的某一个无法运行,检查之前的步骤以确保你正确的完成了它们。现在,运行:

$ truffle migrate --reset

注意这次会需要更长的时间来完成,因为我们是在连接到真正的网络而不是一个用testrpc模拟出来的网络。一旦完成,你就可以用之前同样的方法跟智能合约互动。
在testnet上部署的版本ProofOfExistence3可以在这个地址找到:0xcaf216d1975f75ab3fed520e1e3325dac3e79e05.
我想把如何在以太坊现场网络部署合约的细节留给读者。你只应该在模拟网络和testnet大量测试你的合约之后再做这个。千万记得,任何编程错误都可能导致在livenet上的金钱损失!
以太坊中智能合约的安全性问题很具有挑战性。参见 Emin Gun Sirer的 “智能合约挺难弄对的”。
考虑到智能合约是定义金钱如何移动的电脑代码的性质,我不得不在安全问题上稍做提示。我会在以后的文章里深度的讨论合约安全性问题(像这里),但是这里我会先简单的提几点。

一些你应该知道(并且避免)的问题:

重入攻击(reentrancy):不要在合约里使用外部调用。如果迫不得已,确保它是你做得最后一件事。
发送失败(send can fail):发送资金时,你的代码应该为发送失败的情况做好准备。
循环可能引发汽油限制(Loops can trigger gas limit):当你在状态变量上做循环的时候千万当心,变量的大小会增长这可能导致汽油消耗到达极限。
调用栈深度限制(Call stack depth limit):不要使用递归,记住任何调用都可能因为调用栈到达极限而失败。
时间戳依赖性(Timestamp dependency):不用在代码的关键部分使用时间戳,因为矿工可以操纵它们。

这些是智能合约中可能导致资金盗窃以及毁坏的一些意外行为的例子。中心思想是:如果你在撰写智能合约,你就在写真正处理金钱的代码。你应该加一万个当心!写测试,反复检查代码,并且做代码审核。
避免明显安全问题的最好方法就是对语言有扎扎实实的理解。我建议熟读Solidity文档,如果你有时间。我们将会需要更多更好的工具来完善智能合约安全。