分享实录|以太坊开发需知

image

1

以太坊开发与传统应用开发的差别

相比起传统应用而言,以太坊开发引入了新的基础设施,由此必不可少的带来了部署和运维的复杂度,好比做为系统设计者,咱们须要作出选择:javascript

  • 自建节点,仍是信任第三方节点?前端

  • 公有链、联盟链、私有链?java

因为加入了新的设计单元:智能合约,咱们将面对node

  • 设计的复杂度git

    合约的升级问题:由于智能合约一旦发布就没法更改,万一须要更新合约错误或规则,怎么办?程序员

    合约的组织问题。github

  • 与通常代码不一样,合约的好坏直接与金钱挂钩web

    不安全的合约会形成客户的金钱损失,立竿见影。算法

    合约的每一步都须要消耗gas,不讲究的合约会形成执行成本高居不下。typescript

而且,以太坊自己的限制一样也会影响到整个应用系统的设计和选型:

  • 交易确认须要时间:20笔/秒

  • 交易易受外界影响

    交易费的高低

    流行应用会形成网络拥堵,从影响交易的确认

相比起传统CS编程,与以太坊进行交互要复杂得多:

  • 须要有钱包帐户

  • 发出去的交易须要签名

  • 因为整个过程是异步为主,所以交易须要验证

对于区块链自己的定位,一样也会影响设计:

  • 仅仅用做数据共享和防篡改的基础设施?

  • 围绕区块链打造价值网络?

    Token设计模式

    Token引入对于业务自己带来的影响

这一点尤为差别巨大,不仅仅像传统开发那样仅仅只须要了解用户的业务就能够开足马力前进。Token设计自己须要必定的经济常识,虽然说这部分能够由专业背景的人来设计,但对于开发者和架构师而言,不了解必要的基础知识确定会对开发的顺利进行有阻碍。

2

以太坊Dapp的典型技术架构

Serverless风格

image

这种架构很是明了,客户端直接与部署在以太坊节点上的智能合约打交道就行了。它的优势和缺点都很明显:

优势:

  • 轻量级

  • 运维简单

  • 完全的去中心化

缺点:

  • 胖客户端:交互 + 业务逻辑

  • 智能合约难以承载复杂业务逻辑

典型场景:投票、博彩、小游戏等

CS + 区块链

image

这种架构至关于传统CS(注:这里的传统相对于区块链应用而言,所以像桌面客户端 + 服务器、Web系统、先后端、移动互联网应用等都属于本文中所说的传统应用。)融入了区块链,客户端和服务器都和区块链直接交互。

为何客户端也须要跟区块链直接交互?缘由很简单:区块链应用的帐户信息(尤为是私钥)通常都由用户本身保管,不会放在服务器上。服务器上只会存放系统本身的帐户信息。

这种系统的优缺点以下:

优势:

  • 传统应用和区块链融合

  • 适用复杂业务逻辑,服务器彻底能够包含复杂业务逻辑,合约只承载与价值流转相关的商业规则。

缺点

  • 重量级

  • 运维负担重

  • 部分中心化,话说回来,在我看来,中心化算不上太坏,由于中心化自己表明了专业化。

典型场景:具备复杂业务逻辑的应用系统,如物品溯源、信用质押、供应链金融等等。

Server + 区块链

image

这种架构至关于上面的一种变体:客户端委托服务器完成与区块链相关的交互,甚至于客户端彻底都不知道区块链的存在。为什么不推荐采用这种架构呢?缘由很明显:它要求客户端绝对信任服务器。

这种架构的优缺点以下:

优势:

  • 对客户端屏蔽了区块链的复杂度

缺点:

  • 私钥中心化管理

典型场景:客户端绝对信任服务器

最后,说说关于密钥的存放:

  • 若合约部署于第三方节点,如Infura,毫无疑问只能是本身管理。

  • 假如是自建节点,那么你有两种选择

    方式1:托管

    方式2:自管

同时出于保障资金安全的角度:

  • 控制托管帐户的可用资金,每当可用资金用完,从自管帐户中转入

  • 对于自管帐户,最好也分散风险,创建多个自管帐户,将资金分散其中,避免被一锅端。

3

以太坊应用的开发流程

以太坊应用的开发流程以下图,相比起传统开发流程没有本质的区别,只是测试过程相对繁琐:先本地环境测试,再上测试网试运行,最后部署于主网。只是因为合约的更新麻烦,所以建议尽可能提早多作一些测试,将问题提早消灭掉。

image

4

以太坊开发注意事项

谈完差别,看过架构和展现了开发流程以后,接下来就进入正题,说说本文的重点:以太坊开发的那些坑。

智能合约

智能合约开发的经常使用工具:

  • Solidity + Truffle + VS Code

  • 经常使用类库:

    Token和ICO相关:OpenZepplin和TokenMarketNet/ICO

    可升级合约:ZOS

关于合约的执行成本,我以前写过一篇文章(https://www.jianshu.com/p/cfaa4fdb32ac)有详细介绍,这里就再也不赘述,请参见原文,避免没必要要的金钱损失。

关于合约的安全,我在这篇文章中(https://www.jianshu.com/p/ec5ad71e28aa)略有说起。但远远不够,这段时间以来,我也翻阅了相关资料,整理以下:

  • Overflow & Underflow,使用OpenZeppelin的SafeMath lib

  • 可见性和delegatecall说明以下,相关推荐:优先external, 并留意避免在delegatecall中包含恶意代码

    public,无限制

    external,仅外部调用

    private,仅本合约内

    internal,相似protect

    delegatecall,相似js中的apply,被调用代码和调用合约处于一个上下文

  • 可重入性(DAO攻击),利用CDI模式

    检查 -> 更改合约状态 -> 支付

  • 优先使用pull模式,而非push/send模式

    withdraw 优于 send/transfer

  • 避免使用随机数、now和block.blockhash做为合约逻辑

    分布式网络的时钟问题

  • 注意短地址攻击,检查message.data的合法性

    地址不足会用金额部分数据补0

  • 利用Modifier完成权限方面的校验

至于合约的设计和组织:

  • 单一大合约 VS 合约模块化

  • Hub – Spoke模式

  • 使用mapping保存合约数据

  • 合约升级的主要模式

    Proxy

    数据合约 + 控制合约

Truffle

Truffle做为开发智能合约的利器,不只仅提供了对于合约开发和测试的支持,它还能够做为合约迁移和部署的工具。这里主要讲讲部署的经常使用套路。

通常的Truffle例子中大多只是部署单个合约,但有时咱们须要部署多个合约,而且这些合约之间有前后依赖关系时,须要顺序部署:

var Storage = artifacts.require("./Storage.sol");

var InfoManager = artifacts.require("./InfoManager.sol");

module.exports = function(deployer) {

    deployer.deploy(Storage)

        .then(() => Storage.deployed())

        // deployer.deploy(`ContractName`, [`constructor params`])

        .then(() => deployer.deploy(InfoManager, Storage.address));

}

假如要在部署以后当即执行合约代码:

deployer.deploy(Storage)
   .then(() => Storage.deployed())
   .then((instance) => {
       instance.addData("Hello", "world") 
   })

若是要部署到不一样的网络环境,能够采用以下命令:

truffle migrate --network network_id

此时须要在truffle.js中设置好合适的network_id,部署脚本以下:

module.exports = function(deployer, network) {

    if (network == "live") {

        // do one thing

    } else if (network == "development") {

        // do other thing

    }

}

若是要换帐户部署,则:

module.exports = function(deployer, network, accounts) {

    var defaultAccount;

    if (network == "live") {

        defaultAccount = accounts[0]

    } else {

        defaultAccount = accounts[1]

    }

}

而且每每会跟HDWalletProvider结合使用。

同时把合约部署到Infura上也会用到它:

const HDWalletProvider = require("truffle-hdwallet-provider");

module.exports = {

  networks: {

    "ropsten-infura": {

      provider: () => new HDWalletProvider("<passphrase>", "https://ropsten.infura.io/<key>"),

      network_id: 3,

      gas: 4700000

    }

  }

};

若是合约用到了lib,则:

deployer.deploy(MyLibrary);

deployer.link(MyLibrary, MyContract);

deployer.deploy(MyContract);

WEB3J

对于Java和Android开发者,若是要开发以太坊应用,离不开web3j,它的大体使用流程以下:

image

但请注意

  • 合约的部署建议直接用Truffle完成,如前所述,Truffle不只仅只是开发,它提供了对于合约的一整套生命周期管理。

  • 生成钱包可选步骤,可使用外部现有钱包帐户

  • 合约自己的测试建议用Truffle

  • 此处测试专一于应用自己逻辑和合约逻辑的集成测试

  • 测试建议用Ganache

对于新手,一个经常犯的错误就是选错TransactionManager,它一旦选错,将交易致使。假如你发起交易,而交易没有发出去,同时报诸如:TransactionHashMissMatched,那么十有八九就是这个问题。

TransactionManager有两种:

  • ClientTransactionManager,适用于私钥放在以太坊客户端,由它来签名并发送交易的场合。典型如:geth和ganache中帐户。

  • RawTransactionManager,适用于由应用客户端本身签名并发送交易的场合。典型如:私钥不在自有geth节点和使用第三方节点。

在使用RawTransactionManager时,须要注意设置好合适的chainid。

有时,交易发出以后,发现长时间处于Pending状态,那么请检查(假如不是网络拥堵的状况):

  • 是否设置了合适的gasprice

  • 是否设置了合适的nonce,它有点相似数据库中的sequence,一旦用过就不能再被使用。

同时,还须要留意有多少节点接受了交易所在区块。接受的节点越多,交易越不可能被回滚。确认算法:当前区块高度 - TX所处区块高度 > 指定块数,对于Ganache测试环境,这个值能够是0。

假如你的交易比较重要,可能须要根据交易的重要程度,动态调整这个值。

最后,避免使用send方法,使用sendAsync,并结合CompletableFuture。

其余工具

假如你的工具栈是javascript/typescript,那么:

  • web3.js,基础

  • truffle-contract,更好的合约抽象

  • ethers.js,更高抽象层次的以太坊交互接口

从某些方面来说,ethers.js与web3.js有重叠,但前者对钱包开发提供了更友好的接口。

假如前端页面想将MetaMask直接集成进来,即遇到以太坊交互时直接激活MetaMask,那么能够用下面的代码:

// Adapted from https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#partly_sunny-web3---ethereum-browser-environment-check

window.addEventListener('load', function() {

  // Checking if Web3 has been injected by the browser (Mist/MetaMask)

  if (typeof web3 !== 'undefined') {

    // Use Mist/MetaMask's provider

    window.web3 = new Web3(web3.currentProvider);

  } else {

    // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)

    window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

  }

  // Now you can start your app &amp; access web3 freely:  startApp()

})

合约部署

从大的方面讲,合约部署有两种选择,但各自都有其优缺点:

私有节点

优势:

  • 彻底掌控

  • 适用于应用不能随意访问第三方节点的场合

缺点:

  • 节点安全自我保证

  • 帐本同步问题,如断电重启;节点与外部断开一段时间才发现,由此致使的分叉

  • 若私钥寄存在节点,存在安全风险

第三方(如infura)

优势:

  • 运维负担甩给第三方

  • 不需分享私钥

缺点:

  • 须要信任第三方节点,由于第三方有可能不把交易发出去,返回伪造信息。

  • 非完整API,如infura不支持filter

  • API调用频率有限制

这里没有谁优谁劣,只能根据本身的需求权衡后选择。

5

总结

总的来说:

  • 区块链开发与传统应用开发差别很大

  • 智能合约设计不等同于

    数据库设计

    传统OO设计

  • 与以太坊的交互不是简单的请求调用

  • 实践出真知

    多看、多听、多交流

    选择优秀类库

    测试、测试、再测试

6

参考资料

参考连接:

内容来源:简书

做者 | 胡键

本文源自我在10月27日HiBlock线下沙龙的分享,同时也能够算是到目前为止来自实际项目的一线总结,但愿其中的内容可以帮助后来者少踩些坑,节约宝贵的时间。

image

相关文章
相关标签/搜索