【本文作者:Alex Van de Sande,译者:王建(万云平台区块链技术专家)/蔡佳慧(万云实习生区块链技术爱好者),国内首次刊登于万云微信公众号】
以太坊并不是那种需要一个STEM文凭(指:科学-science,科技-technology,工程-engineering,及数学-mathematics相关文凭。)才能理解的高深莫测的智能合约应用建立平台,它的目标其实是成为万维网中各种应用架构的支柱。在这篇文章里,我们尝试去阐述如何实现这个目标,并提供一些基本例子来展示如何开始构建一个去中心化的应用。
目标读者
这篇文章面向有以下背景的读者:
对于Web技术有基本了解,并知道如何构建一个简单的基于javascript和html的应用。同时,希望使用这些技能为以太坊生态系统构建应用。
没有服务器,应用是如何运行的
当前,Web应用中的服务器所做的工作已经远超当初的设想。除了提供静态网页,它们还保管私有信息,处理用户验证,同时还提供复杂数据分析与保存。而用户计算机(在Web技术刚被发明的年代,这种设备会被认为是超级计算机)所做的仅仅是加载信息,并展示给用户而已。
与此不同的是,更为去中心化的系统结构则会允许一种更加模块化的处理方式。在这种方式中,不同的机器与不同的协议将会处理特定的任务,有些属于用户方面,而也有些属于配置于点对点网络中的专用机器方面。因此所有的数据逻辑(什么会被储存,谁会去储存,如何解决冲突等)是由区块链上的智能合约解决的,静态文档由Swarm提供,同时实时通信在Whisper上进行。用户设备保留用户认证信息并运行程序界面。
几乎没有单节点保留大量未加密数据,这么做将会降低数据泄漏与攻击的危险。同时,通过将其分散于全网络,也可以降低应用的加载与花销成本。由于所有这些协议都是去中心化的,任何人都可以连接到网络并开始提供特定服务:比如说,如果用户在一个功能强大的笔记本电脑上浏览,这台电脑也可以向网络邻居们提供静态文件。
一个去中心化的系统结构同时鼓励创新:由于交互界面脱离于数据,任何人都可以为同一个应用提出一个全新的用户界面,创建一个更富生机与竞争力的生态系统。可以说,Twitter历史上最具趣味性及创新力的时期之一便是它作为中央数据中心提供服务,同时任何人都可以创建他们自己的Twitter应用。
观察它如何运作
如果你想在学习这个应用之前实验它,我们推荐你下载Mist并阅读我们关于如何安装应用并运行的入门教程(下载链接:https://github.com/ethereum/mist/releases/tag/v0.9.0)。如果你只是想要看一看完整的应用,你可以直接从Github上将其下载下来(下载链接:https://github.com/ethereum/stake-voice)。
我们现在上手操作
我们将会建立一个叫做“Stake Voice”的非常简单的应用。其主旨是允许以太币投注人对任何他们想投的事情进行投票,同时这个应用将会计算所有同意与不同意这个陈述的以太币总和。
下面代码是这个应用里用Solidity语言编写合约,Solidity是一种类似于javascript的语言,非常简单:
第一行建立了合约名称,同时第二行创建了一个命名为“LogVote”的事件,它将会在日志文件中记录以下内容:
-
将会被投票的提案的哈希值
-
投票者是同意还是反对提案
-
投票者的地址
函数“vote”接着会启动日志,应用程序稍后会计数。它同时会检查确保没有意外发送的以太币。当任何以太币被存入智能合约时,“匿名”函数会被执行,并会自动拒绝接收以太币。
如果你想要学习关于Solidity更多的编程内容,我们推荐你从以太网solidity教程( https://ethereum.org/dao)开始,阅读官方文档页面( https://solidity.readthedocs.io/en/latest/)并在你的浏览器上尝试在线编译器( https://ethereum.github.io/browser-solidity/#version=soljson-latest.js)。
大致上就是如此了:你选择一个哈希值,选择一方然后运行Vote()。所以这又如何转化成一个投票应用?
无服务器架构
遵循KISS原则,我们正在制作尽量小却仍然可使用的产品。这意味着我们不会使用数据库来储存提案,也不会使用普通javascript与纯粹html之外的功能。
因此,我们将使用应用本身的URL来保存提案文本,并且我们会使用URL来向用户展示提案内容,再生成一个用来检测投票的哈希值。用户可以使用社交媒体来分享他们想要辩论的提案,或者直接使用链接。
从基础开始
拿出你最喜欢的html框架,并在你本地机器上建立一个简单网站,然后在Mist上打开它。Mist上所有的页面都可以访问一个名叫web3的javascript对象,这也是你的主要工作区域。我们要做的第一件事就是检查web3是否存在:
一些应用开发者也许会想要去加载他们自己的web3对象,以保证向前的兼容性。要做到这些,只需要在</body>标签前加上:
然后在初始函数上加上这个去加载你自定义的web3提供方(provider):
从区块链上加载信息
你检查到你连接到区块链网络上了吗?但是到底是哪一个区块链网络呢?是主体(main)以太坊网络吗?也许是一个测试网络(testnet)或者是一个私有(private)网络?或许未来某一天,你会分叉(fork)一个以太坊源码,构建一个属于你自己的全新品牌区块链。检查网络的最好办法是查看你想要加载的合约地址中是否包含了代码。
此外,为了执行一个合约你需要知道两个基本事项:它的地址和ABI(ABI是使用json编码的接口文档)。
现在有了上面这些内容,你就能够在启动函数上检测合约是否存在了:
你甚至可以递归地运行这条命令,尝试用不同的地址去连接(假定你测试网络上)。一旦你找到你的合约,你就可以在这里加载它了:
你用web3对象创建的javascript对象,能够直接在浏览器中执行所有以太坊命令。如果你仅仅想要加载一个合同的实例,你甚至可以在一行代码里做到:
用户鉴别
获取用户账户披露了这个用户的大量信息:账户余额中有多少以太币和其他代币,以及其交易历史。因此,默认让所有应用获取这一信息将会创建一个超级cookie,由于对隐私的侵犯,这是不可以接收的。另一方面,要求用户为每一个网站创建一个带有登录信息的账户,这不仅对用户来说是一个痛苦,而且把你的隐私交给第三方来掌控,这种方式将会让个第三方变成一个可被黑客随意掘取的巨大蜂蜜罐。
面对这一困境,多数用户选择将自己个人信息及验证信息交由一个几十亿美元公司处理。隐私权不应该为了取得实用性而妥协:用户应该在掌控他们的个人信息的同时,能够轻松地验证身份信息以登入任何应用。
使用Mist,应用不拥有关于用户的信息,除非用户决定公布其自身信息给应用。当你想要查询自己的账户信息,你应该调用getAccounts函数:
目前,返回对象是一个数组,其中包含了用户拥有本地访问权限的简单账户,但是在未来,其中还会包含用户用于自身识别的智能合约账户。这将会使得用户拥有权限来访问目前只供给集中式验证器(centralized authenticators)的特性,比如双重身份验证或者云备份。用户同样将会拥有权限来访问未来针对智能合约的改进,比如在你遗失密钥的时候允许一些受信任的朋友来给你访问账户的权限,或者对于不活动账户行使自动继承权。
每一个未来的以太坊浏览器将会解决用户如何向应用辨别自身的问题。在Mist中我们由两种方式:要么用户可以通过敲击“connect”按键(目前仅叫做“no accounts”按键)来启动它,或者应用可以通过调用“requestAccount”api来要求身份验证。
注意:这个列表上的账户仅仅只是用户声明拥有密钥的账户,但是用户并没有提供证明,因此你可以展示一个不同的界面,但是不要给用户发送任何与账户有关的加密信息。如果需要用户证明他们的身份,你需要让用户对信息签名,同时Mist也会在未来支持它,请注意这会要求用户做一个额外步骤--输入密码。所以只在必要的时候,你才会需要用户签名。
投票
一旦你有了合约对象,投票就仅仅只是从javascript中调用它。调用函数会下面这段代码处提供操作器将会解决用户们如何向应用辨别会包含用户们弹出一个Mist交易面板,用户可以在面板上检查交易内容,并输入密码。
因此,首先我们要创建两个可以调用投票函数的可点击对象:
注意我们调用的一个函数参数为true,另一个则为false。投票函数可以像下面这么简单:
“Ethervote”是我们之前创建的对象,并且“vote”是它函数中的一个,对应着合约函数的一个:
根据函数的要求,我们需要传入两个参数,并增加包含交易信息的第三个对象,比如:谁发送了此交易,以及两个可选项:包含多少gas或者购买gas的费用。
由此,这将会创建一个面板,以要求用户确认交易——但是多数情况下它会返回一个错误信息,因为目前web3.eth.accounts对象默认是一个空数组,所以你需要检查其是否为空,若为空,则从用户处请求账户:
你应该只在用户初始化一项操作的情况下请求账户:无中生有地显示一项交易只会理所当然地激怒用户,更可能使他/她关闭你的应用。如果我们观察到应用对于这一功能的滥用行为,我们可以对于何时弹出警报施加更为严格的要求。
查看合约
最后,为了累计所有的投票,我们需要查看合约事件,以及投票内容。为了完成这些工作,在我们实例化“ethervote”后我们需要去运行这个函数一次来查看事件:
上述代码会从第180万(这是合约被上传的时间点)高度读取所有的区块,没读取一个区块时,都会执行receivedEvent()函数;并且,每产生一个新区块时,这个函数就会被再次触发,所以你不需要连续调用。那么,这个函数到底有什么用呢?
从初始solidity合约中,你可以看到LogVote带有三个参数,proposalHash,Pro和Addr:
所以这个函数的作用是它会使用函数web3.eth.getBalance来检查投票地址当下的以太币余额。所有的余额都会返回以wei为单位的数字,也就是一个以太币的1/1000000000000000000,这对于这个特定的应用并非非常的有用,所以我们也使用另一个包含的web3函数来将其转换为任何我们需要的以太币单位。这里,我们会使用finney,也就是一个以太币的一千分之一。
接下来,这个函数将会保存投票者的余额及位置于一个以用户地址为Key的Map上。所以如果有人投了两次票,只有他们最后的意见会被保留。
另外我们还能鉴别用户,并展示他们是否投票。
累积票数
最后,我们应该加入一个独立的函数来计算票数的总数:
为什么我们会想要通过一个独立的函数来累积票数呢?因为票数权重是建立在每一个账户的当前余额之上的,我们应该对于每一个新区块重新计算余额,即使我们并没有收到新的事件。为了做到这个,你可以增加这个函数,使它每收到一个新区块时就会自动执行;
最后,直到加到最终的总数为止。我们之前已经在同步模式下使用了eth.getBalance,在此模式下,应用会等待前一个操作的结果再继续。此处,因为我们可以对于每一个区块调用大量操作,我们将会在异步模式下运行它:你可以异步调用getBalance函数,等到节点返回后,再进行票数统计。
就像你根据代码所作的一样,应用所做的就是在循环投票地址中的每一个并得到它们的余额,只要它返回结果,它会将其加到支持或反对阵营中,并计算总和。
额外的好处
一些附加说明:当没有事件时,什么也不会被返回,并且投票也不会被计算,所以你应该对依赖于区块链上事件的所有函数增加一个超时函数。
现在你可以随意地使用所有你现有的网络开发工具来施展你想要的任何魔法。使用数字来构建一个漂亮的3D可视化效果或者关联你最爱的社交媒体来分享最佳问题。
Mist也尝试提供一些基本的导览,以及UI方式来简化你的代码。如果你想要你的应用不包含header并且占据mist应用的所有高度,只要将这个加入到你的<head>标签:
并且如果你想要使用Mist自身来导览?你的应用,你可以使用Mist.menu对象:
以太坊强大的一点是你可以在不需要许可的情况下,扩展这项简单的合约功能:你可以把每个额外的功能放到一个独立的合约里,让每个独立合约的功能简单且可以更轻松的调试。这也意味着别人可以在他们自己的应用中使用你创建的合约并增加新的功能。同时,所有的应用使用相同的数据和后端。
你可以在Github上(https://github.com/ethereum/stake-voice)在线体验这个应用,但是这不是权威的规范,只是它众多可能接口中的一个。同一个应用也可能在你的电脑上或一个IPFS网络( https://ipfs.io)中作为一个本地html文档工作,并且在未来它可以使用Swarm技术,在Mist里直接下载应用。
关于如何尝试的一些建议:
-
创建当前可用发言列表。任何人都可以通过查看提案文本的sha3来检查它们,所以你不需要任何许可。
-
创建线性化的评论,在这里用户可以回复评论并通过投票来表达支持或反对,就像一个基于Reddit的去中心化的投票制。
-
除了使用以太币余额,你还可以使用一些其它的以太坊代币,比如TheDAO或者Digix Gold来给你的问题加权。因为所有合约保存的原始位置是发送方,你可以检查全部发送方的余额。或者也许你可以基于信誉(reputation)、甚至是业力(karma)或者其它方式来创建你自己的货币。