这个信息技术故事讲的是以太坊,讲讲我们将以太坊区块链与产品 DreamTeam 结合时遇到的缺陷,DreamTeam 是第一个电子竞技和游戏招聘和管理网络。在撰写本文时,我们平台上的注册用户总数已超过 50 万。
在我们的平台上,区块链负责所有与支付相关的操作。所有“金钱相关”的操作都是使用以太坊智能合约来处理的。当用户创建团队、添加或删除参与者、接收补偿金、彼此之间的交易等时,就需要用到智能合约了。在这种情况下使用区块链和智能合约,以保证支付和避免欺诈,而不幸的是在电子竞技中会经常见到这种欺诈现象。
数字
3 月 30 日星期五,我们在以太坊网上为我们的用户开放了区块链解决方案。截止到大约上线后的 45 天内,我们已经达成了来自于主账户 (管理团队和支付平台的支付) 的 5 万笔挖矿交易。这大约即为每小时 42 笔交易,占以太坊网络总能力的 0.25%(以消耗的 gas 来计算),考虑到时间段,这个数量相当令人惊讶。
DreamTeam 区块链交易的间隔为 30 分钟
一旦我们迁移到实时以太坊网络 (mainnet),这些数字就会发生变化,因为大多数交易不会在那里发生。这背后有几个原因:
目前的大部分交易只是初始奖金,队员和队伍一旦“激活”他们的区块链钱包就会得到这笔奖金。我们不会在实时网络上发放奖金,因此交易的数量在一开始就会急剧减少 (目前超过 63% 的交易是初始奖金)。
在我们的平台上,只有一定比例的用户预期会使用“区块链”团队和付费服务,一旦这些转为真钱,会再次减少交易的数量。
考虑到这两点,我们预计只有 10% 左右的交易发生在真正的以太坊网络上,顶多,理论上我们的业务会占用以太坊网络的总能力的 0.025%,大约每小时发生 4 笔交易。
然而,这一统计数据并没有考虑到几乎 80% 的交易发生在高峰期 (见上图),这使得:
a) 在高峰时期合理时间内被挖掘的以太坊交易的价格高昂 (每笔转让交易的费用在 0.25 美元以上);
b) 确认更便宜的交易要等待很长的时间 (约 0.15 美元的费用交易要超过 10 小时)。
也就是说,当前“传统”区块链解决方案背后的真相是,它们的可扩展性不如常规数据平台。一旦公链流行起来,它最终将变得用起来很是昂贵。因此,作为最受欢迎的交易账簿,以太坊公司现在的价格就是相当昂贵。在我们的案例以及其他商业案例中,不断增长的用户数量和即将到来的特性一定会使以太坊的成交率更高,这可能会致使以太坊仅仅成为我们“代币制”的一个短期解决方案。
我在之前的文章中已经提到,我们都知道以太坊区块链尚未实现可扩展性。以太坊中即将出现的分片特性可能会极大地增加网络吞吐量,但是,它来得有些太晚了,在这种情况下,我们可能会决定迁移到另一个区块链平台。以太坊之外还有很多选择,只是,还没有任何一个像以太坊那样受欢迎和可靠。
所以,这篇文章主要内容还是以以太坊为主。
以太坊集成和后端体系结构
一旦我们开始处理实际价值,事情总会变得复杂起来。至于区块链,重要的是构建一个系统,来确保在通往以太坊网络的途中不会丢失任何交易。我想分享一些我们在项目开发过程中遇到的陷阱。
技术栈
我们使用微服务体系结构和持续集成,其中区块链解决方案由四个逻辑部分组成:
- 使用 Solidity 和 Truffle 框架来开发和部署以太坊智能合约。
- Geth 区块链客户机节点。
- 使用 MongoDB 的 NodeJS 后端。
- 用于所有区块链交易的 RabbitMQ 消息代理。
让我们简单地浏览一下所有技术内幕,并讨论为什么我们决定以这种方式构建平台。
后端
以太坊是为去中心化的 web 开发的,该平台的主要工具是基于 JavaScript 编写的。因此,在后端方面我们直接决定用 NodeJS。我们还决定使用 MongoDB,它是一个针对 NodeJS 的“传统”面向文档的数据库,能与 JavaScript 无缝集成。
我们的后端架构最重要的部分之一是使用 RabbitMQ 消息代理。对于那些不熟悉 RabbitMQ 的人,我建议您阅读此文或此文。简而言之,RabbitMQ 是一个专用服务,其他服务 (如区块链服务) 向其推送“消息”,然后这些“消息”被其他服务 (或相同的服务) 使用。
使用 RabbitMQ 有许多优点。但特别地,在向网络发布交易之前,我们首先向消息代理发送“交易发布请求消息”,然后由实际将交易发布到以太坊网络的工作者(worker)接收。这使我们能够实现以下套路:
- 把消息放到消息代理队列中,直到它得到处理,这将最小化由于各种原因 (网络故障、中断、人为错误等) 而导致交易无法发布的风险。
- 将交易发布到队列让我们可以容易让区块链交易的处理先停止一会儿 (暂时杀掉消息消费者),进行一些维护之后再恢复交易处理 (将消费者带回)。所有消息都将简单地堆栈在队列中,直到它们在任何平台定期维护之后得以全部发布和处理。
我们的一个智能合约是可升级的,我们成功地使用#2 在生产环境中升级了合约,感知不到任何停机时间。
用于以太坊的 Truffle 框架
截止到撰写本文时,Truffle 仍是以太坊开发最受欢迎的框架。它有一个很好的 API,它将开发人员从底层以太坊的工作中解放出来 (比如组装和签署原始交易、编译可靠代码、使用智能合约 ABI 等等),而且也引入了一些模式,它们可能适合某些项目工作流程,也可能不适合。
在 Truffle 中,为了将智能合约部署到网络上 (或对智能合约进行任何初始设置),您得要编写 JavaScript 的迁移脚本,其中每次迁移都只运行一次,针对每个网络设定一个一个地按顺序执行。根据这个思想,第一次迁移向网络部署了一个小的智能合约 (migrations .sol),然后 Truffle 使用该契约跟踪哪些迁移已经在网络上运行。一旦部署了这个智能合约,它的地址将被记录到一个工件文件 (Migrations.json) 中。Truffle 读取这个文件是为了了解哪些迁移已经运行了,哪些还没有运行。
现在,假设您想部署一个新的智能合约,然后 (可能稍后) 调用其中的一些“初始化”方法,您需要编写三个迁移脚本,它们将:
- 部署迁移智能合约。
- 部署您的智能合约。
- 调用您的智能合约中的初始化函数。
注意,最后两个操作可以合并到一个迁移脚本中;我使用了 3 个脚本,为的是说明迁移是一个接一个开发、然后一个接一个运行的原子操作。
这些迁移脚本一旦由 Truffle 运行,就会在网络上产生以下交易:
- 迁移智能合约部署。
- 保存迁移 1 到网络上 (Truffle 将调用迁移智能合约)。
- 您的智能合约部署。
- 保存迁移 2 到网络。
- 智能合约函数调用。
- 保存迁移 3 到网络。
如您所见,为了在网络上做 2 个交易,Truffle 需要再做 4 个交易,最终在网络上一共做了 6 个交易。
所以如果您的目标只是将一个简单的智能合约部署到网络中,可能希望避免使用 Truffle 迁移工作流。使用 Truffle(例如,手工部署智能合约) 实现此目的的一种方法是使用单个迁移脚本或围绕 Truffle 迁移脚本进行手动自动化。在任何场景中,您都可以使用 truffle migration -reset 在 migrations 目录中按顺序运行所有脚本 (这也不需要 Migrations.sol)。
Geth 节点同步
Geth 客户端节点只有在正确设置之后才能正常运行,不幸的是,这个过程可不快。今天 (2018 年 5 月 18 日) 运行 Geth 节点需要:
- testnet 至少需要 66 Gb 的 SSD 磁盘空间,而 mainnet 至少需要 92 Gb(一些志愿者把统计数据发布在了你可以追溯到它的地方)。
- 至少 8.3 Gb 内存。
- 引导时需要良好的带宽,但它占用的互联网流量很少 (平均 25kb 每分钟)。
- 同步时的平均处理能力。
- 磁盘 I/O 越好,Geth 节点同步状态就越快。
它需要大约 0.5-2 天的时间来同步 testnet 的节点,而将其同步到 mainnet 则需要 1-3 天的时间。
如果您尝试同步 Geth 节点,可能想知道为什么 Geth 同步在最后 100-200 块上,同时不断地下载“已知状态”。我建议您阅读这篇很棒的文章,它详细解释了为什么会发生这种情况,但简而言之,要做到“同步”,您的节点需要在处理所有请求之前下载所有状态项。如果超过1.4 亿,这需要1-3 天下载(取决于磁盘I/O、网络和处理器)。
以下是我们监控得到的一些与Geth 相关的图表(绘制间隔为1 分钟):
Geth 节点每日内存使用情况 (同步后)
Geth 节点 CPU 使用情况 (同步后)
Geth 节点网络使用情况 (同步后)
交易发布
在以太坊中一个非常奇怪但又非常必要的事情是,从特定帐户发送的每个交易必须具有唯一的顺序整数,这称为 nonce。在以太坊中,使用 nonce 数字来防止双花攻击(double spend attacks)。这个整数引入了两个重要的影响:
- 从一个帐户提交的所有交易都按顺序处理 (挖掘)。例如,如果您向网络提交了 2 个 nonce 整数各为 1 和 2 的交易 (每个交易的序列唯一整数都是必须的),那么在第一个交易处理 (被挖掘) 之前,第二次交易是根本就不会被挖掘到的。但是在你提交多个具有顺序 nonce 数的交易到网络, 有可能这些交易 (或者全部) 将在同一块得以挖掘 (如果你分别提供良好的 gas 价格的话, 但它也取决于矿工是否选择这些交易)。
- 您需要额外跟踪提交的每个 nonce 数字,或者只使用一个节点以防止 nonce 冲突。
第一点是特别痛苦的,如果你做了这样一个需授权的智能合约,只有一个地址被授权“管理”某件事,你需要经常“管理”这个智能合约。设定较低或普通的 gas 价格并形成大量交易,将导致为了等新的交易被挖掘可能会需要几乎无穷无尽的等待时间。
解决第一个问题的方法是将交易发布拆分为多个以太坊账户。然而,在这种情况下,您永远不会知道将在哪个交易之前挖掘哪个交易。在我们的智能合约下,甚至可以指定哪个客户对应于哪个团队 ID,以避免打乱我们的团队对智能合约的管理。
Nonce 碰撞
为考虑一个节点崩溃的情况,我们尝试了扩展 Geth 节点数量,此时我们遇到了另一件神秘的事件,那就是 Nonce 碰撞。
事实证明, 不幸的是,您不能只是仅仅扩展 Geth 节点的数量。
我们在三个 Geth 节点之前使用了一个简单的负载均衡器,该节点将每个交易发送到三个节点中的一个。问题是每次我们一次提交许多交易,其中一些交易就神秘地消失了。我们花了一两天的时间才最终发现这是一个 nonce 碰撞的问题。
当您向网络提交原始交易时,是没问题的,因为您自己跟踪 nonce 数字。在这种情况下,您只需要一个节点将原始交易发布到网络。但是,如果您正在使用节点中内置的帐户解锁机制,并且在发布交易 (使用 web3) 时不指定 nonce,那么该节点将尝试自己选择适当的 nonce 值,然后签署交易。
由于网络延迟,如果两个节点接收相同的交易发布请求,它们可能会生成相同的 nonce 值。在接收交易发布请求时,他们不知道他们都收到了具有相同 nonce 的交易。因此,当通过网络传播这些交易时,其中一个将最终被删除,因为它的“交易 nonce 太低了”。
为了修复由于向系统添加负载均衡器而引入的 nonce 冲突,我们需要创建一种不同的负载均衡器。例如,一个总是使用一个特定节点的负载均衡器,并且只在第一个节点宕机时才切换到另一个节点。
gas 限值
gas 限值也是一个非常重要的数值。您可以以两种方式提交 gas 限值方面的交易:
#1 只需要在代码中硬编码 gasLimit = blockGasLimit(或另一个您认为对您的交易来说足够的数字)。
#2 使用诸如 estimateGas 之类的东西来估计交易所需的 gas 限值。
我一直都建议使用选项#2,因为它切实影响了交易挖掘时间。在这种情况下,交易的处理时间要快得多。
另外,请注意,选项#2 只是对在网络当前状态下运行此交易将使用的 gas 用量的严格估计。在实际处理交易之前,网络的状态可能会发生变化,并最终导致 gas 超量异常。我建议在 gas 估值中再额外增加 1-5%,并在将交易发布到网络之前,进一步研究如何以及什么会实际影响您的 gas 估计值。
结论
毫无疑问,以太坊会是世界上最伟大的未来塑造者之一。但在以太坊中,以及在任何其他“革命性”的事物中,有许多地方并不理想,还有待改进。现在,有很多与太坊类似的东西,现在就有 EOS、恒星、卡达诺、Lisk 和其他许多正在积极开发的有前途的平台,多到令你很难做出选择。但是以太坊仍然是一个领导者,因为社区和大家长久以来对它的信任才能真正使平台走下去。
查看英文原文: Ethereum Blockchain in a Real Project with 500k+ Users
评论