从很多朋友那儿道听途说最近行情非常差,于是在主流招聘平台投了一些岗位试试水。最近可能会持续更新一些面试文章。
不得不说今年的环境确实是最近四五年最差的一年。主要是有两点,第一点是真正招人的公司变少了,最近一个月来聊了很多家,成功约上面试的非常少,拿到的优质 offer 就更少了,起码目前没有一个能让我眼前一亮。第二点是薪资缩水严重,甚至一些高级岗位开的薪资比我两三年前的收入还要低。
本文面试的公司是一家 DeFi 公司,岗位是智能合约应用架构师,薪资待遇大概是 35k 左右 + 一定比例的代币,16 薪。
以下是一些主要的面试题目:
1. 请分享一下您在智能合约开发方面的经验和项目经历
我在智能合约开发方面有很多经验,我主要使用 Solidity 作为开发语言,使用 OpneZeppelin 作为工具库,使用 Hardhat 作为开发框架。主要的开发平台是以太坊。我参加过的一个比较有代表性的项目是 NFT 创作平台。这个平台的主要目的是帮助创作者上传 NFT 进行创作。有点类似 OpenSea,准确的说更像 OpenSea 的 OpenSea Studio。OpenSea 的盈利模式就是靠高额的手续费。二级销售是 2.5%,一级铸币是 2.5% 到 10%。我们比它们低很多,我们只收取大概 1.5% 的手续费。另外的一些特点就是加入了 CBE 体系和二创系统。
2. 您在 NFT 项目中扮演什么角色?有什么贡献?
我在这个项目中主要负责设计和开发智能合约、前端和后端。并且负责编写智能合约的测试。比如对 NFT 的铸币、二创、转移等功能进行了充分的测试。通过设计不可作恶许可证体系和二创系统,让我们的平台和其他竞品之间有了比较明显的差异化,保证了卓越的用户体验,并且成功让项目拿到了第一笔天使投资。
3. 在智能合约开发过程中,有哪些常见的智能合约漏洞需要注意?
我认为安全问题和智能合约的代码、平台、编程语言、编程语言的版本都有关系。这里我简单说几个比较常见的漏洞。
- 整数溢出和下溢:一些数值在计算过程中可能会超出整数的最大值和最小值,发生意料之外的情况。我是通过 OpenZeppline 提供的 SafeMath 库来处理这种情况的。
- 可重入攻击:如果智能合约在发送 ETH 之前没有改变状态,那么攻击者可以递归调用智能合约,最后抽空合约里面的资产。为了避免这种情况,可以使用检查-生效-交互(checks-effects-interactions)的模式来避免。
- 短地址攻击:ERC20 正常的输入字节码是 136 位,如果低于 136 位,EVM 会在末位补 0。攻击者可以使用比正常短的地址,来诱导智能合约错误解析交易参数,这样可能导致资金丢失。我的解决方案就是验证字节码的长度。
- selfdestruct 的使用:一定要慎用合约毁灭操作,因为一旦合约毁灭会导致资产丢失,无法找回。不过最新版本的 Solidity 删除了这个功能。
- 权限管理:正确配置权限和管理多签,避免权限被滥用。
4. 如何进行进行智能合约的安全测试?
智能合约的测试和普通 Web 应用的测试有些类似,但也有些不同,我简单介绍 4 种最常见的测试类型。
第一种是静态分析,可以使用安全工具对代码进行安全性扫描,比如我用过 Slither,还有另外一些比较出名的静态分析工具,像 Mythril 之类的。它们都可以检查智能合约是否存在一些显而易见的安全问题,并且可以生成报告。这类工具都使用 Python 编写的,所以都需要 Python 环境。
第二种是单元测试,也就是针对智能合约的函数级别的测试,常用的开发框架 Hardhat 或者 Truffle 都有单元测试的功能。它们都支持 JavaScript 和 TypeScript。
第三种是端到端测试,也就是 e2e 测试,通过编写自动化脚本,模拟用户和合约交互的场景来检查是否存在漏洞。我常用的工具是 Cypress。e2e 测试和普通的 Web 应用测试很像,区别就是在过程中需要启动 MetaMask 扩展或者是其他钱包扩展。
第四种是模糊测试,就是使用随机的输入参数来调用合约函数,这样可以检查合约在某些特殊的输入参数下会不会出现问题。我用过的工具是 Echidna。
上面这四种类型测试基本是就是常见的智能合约安全测试手段了。除了上面这些,还可以制定一些防范性策略,比如限制最大取款额度、限制代币交易频率之类的。
5. 请介绍一下您对 Ethereum 和 Layer2 的了解
以太坊是一个开放的区块链平台,支持智能合约。它也是目前最活跃的公链平台。以太坊拥有原生的数字货币 Ether(ETH)。它也属于 Layer1 的区块链。
Layer2 是建立在像以太坊这种一层网络之上的二层网络,它主要是在解决三个问题。第一个是提高吞吐量,也就是每秒支持的交易数量,以太坊每秒内只能完成十几笔交易,一些 Layer2 方案可以完成上千甚至上万笔交易。第二个是提高交易确认速度,以太坊区块的生效时间大概是十几秒,Layer2 的一些方案通常可以把时间压缩到 1 秒以内,甚至可以做到毫秒级别,第三个是降低交易费用,以太坊的手续费和交易复杂性、网络繁忙程度、用户设置都有关系,通常一个 ERC20 的转账需要 1u 左右,一些 Layer2 的方案可以把手续费压缩到几十分之一,甚至百分之一。
在我的理解里,Layer2 的主要理念就是在保证数据的完整性和安全性的同时,把一些计算、存储和传输的任务从 Layer1 迁移到 Layer2。从而达到它的三个目标。
除了三个主要目标外,Layer2 其实也是对不可能三角的一种尝试,因为大家都知道区块链是无法同时满足安全性、去中心化和可扩展性的。所以 Layer2 也是通过技术手段来去做创新,试图解决不可能三角问题。
6. 目前的 Layer2 解决方案有什么优缺点?
每种类型的 Layer2 解决方案各自有各自的特点。我简单介绍一下最常见的 4 种 Layer2 类型的优缺点。
首先是状态通道,它是在链下处理交易,只在链上处理开始和结束的状态。所以它的优点就是交易速度快、延迟低、手续费低。
缺点就是需要交易双方都在线;另外用户人数有上限。它比较适合用来做预测市场、赌球、赛马这类场景。
第二种是侧链,它是在主链上再搭建一个新的链,在安全性上和主链完全分开。所以侧链的优点是可以自定义一些特性和规则,来满足特定的需求,有很高的扩展性和灵活性。缺点是它的安全性是和主链独立的,所以普遍认为侧链的安全性不足,不如主链安全,特别是升级到 POS 的主链。
第三种是 Plasma 和 Rollup,它们是在链下进行计算,链上存储数据。通过创建子链来提升交易速度,因为子链可以并行处理交易,所以它的优点就是吞吐量非常大。缺点就是交互模式比较复杂,比如在存款和提现方面。另外跑节点的负担很重。还有一个致命的缺点是不能用在智能合约上。两者的区别是 Rollup 可以随时验证。
第四种是 Optimistic Rollup 和 ZK Rollup。Optimistic Rollup 是一种乐观思维,通过 Fraud Proof 机制,让别人去证明你的资产,缺点是需要很长的时间来去证明,通常需要一到两周。ZK 是一种悲观思维,把 Fraud Proof 换成了 Validity Proof,需要自己证明,生成一个零知识证明。缺点是打包需要很强的服务器去做。
7. 请解释一下在区块链网络上部署智能合约的过程
在区块链上部署智能合约的步骤大体上有 4 步。
第一步是编写智能合约,这一步没什么好讲的。通常就是用 Solidity、OpenZeppline 和 Hardhat 这套技术来开发。
第二步是编译智能合约,就是把第一步写好的 Solidity 代码编译成二进制代码,因为区块链只能理解二进制代码。
第三步是测试智能合约,在正式朝以太坊主网上部署合约之前,通常会在本地或者测试网上对智能合约的功能进行测试,来确保合约没有安全问题。
第四步是部署智能合约,部署智能合约就是向区块链主网发送一笔交易,其中会包含智能合约编译后的字节码和 gas 费。一旦交易被确认,智能合约就会部署成功,并会生成一个合约地址。这一步可以通过 Hardhat 或者 Truffle 来完成。
8. 您是如何升级和维护智能合约的?
通常来说智能合约一旦部署到区块链上,对应的代码逻辑就无法再进行更改。但是有一些机制可以升级和维护已经部署的合约逻辑,最常见的方式就是用代理模式。
在代理模式中分两个合约,一个是代理合约,一个是逻辑合约。所有的外部调用都是调用代理合约,然后在代理合约中设置一个逻辑合约的地址,再由代理合约去调用逻辑合约,来执行真正的逻辑。如果需要升级合约的逻辑,只需要部署一个新的逻辑合约,并且把代理合约中逻辑合约的地址改为新的地址就可以了。
同时所有的数据,也就是资产,都是存储在代理合约中的。这样可以保证更新合约的逻辑后不会影响到原来的数据。
9. 您在构建智能合约时有哪些独特的经验和方法?
我认为有一些最佳实践是所有类型的软件项目通用的,也包括智能合约。遵守这些最佳实践,就可以最大程度上保证项目的成功。下面是我认为最重要、最常见的一些的最佳实践。
- 明确需求:一定要弄清楚我们要做什么,不要做什么。这是最重要的事情。
- 流程设计合理:弄清楚做什么之后,一定要弄清楚我们该怎么样一步步实现我们的目的。
- 代码架构整洁规范:弄清楚每一步该怎么做之后,就要把每一步都做好,这里一样是遵守代码编写的最佳实践,像高内聚低耦合、DRY 原则、SOLID 原则等。
- 测试全面:一切必要的工作完成后,还需要对合约进行全面的测试,作为最后的保证。
10. 请问您认为 Go 语言和 Node.js 之间有什么区别?
(因为对方公司同时使用这两种语言,所以要求面试者同时掌握这两门语言,刚好我满足这个条件。)
我认为大多数的现代编程语言在功能性上都是相通的,因为软件行业发展至今已经非常成熟,有哪些主要的功能领域大家也都明白,所以在功能性上不会有太大的差异,比如说做一个云平台的服务端,Go 能做、Node.js 也能做。而彼此之间的差异主要是各自的设计理念和特性,这两点又决定了各自的主要使用场景。
Go 和 Node.js 都可以写服务端程序,服务端这个场景都是两者最主要的场景之一。所以我就拿服务端开发这个场景来列举两者的差异。
Go 语言通过独特的 GoRoutines 和 Channel,天生就在语法层面对并发有着完美的支持,在并发的通信方面通过消息传递而不是共享内存,可以让我们不需要去额外关注锁和并发控制这些比较低级的事情,对编写程序来说更加直观和安全。这种特性在处理计算密集型(或者说 CPU 密集型)的任务时效率非常高。Go 语言也非常适合做云服务工具,比如知名的 K8S 和 Docker 都是用 Go 语言来实现的。
Node.js 的特点是事件驱动和异步 IO,这两个特点在处理 IO 密集型的任务时得心应手,同样也非常擅长高并发。除了服务端领域,Node.js 也很适合做实时应用,比如著名的 socket.io 就是用 Node.js 实现的。还有做服务端渲染(SSR)、BFF 层都是首选语言。另外 Node.js 还几乎垄断了前端工具链这个领域。
Go 和 Node.js 在做服务端开发时都有很强大的优势。当一个 HTTP 请求进入时,Go 其实只是启动了一个 GoRoutines,创建和销毁的成本只有几 KB。Node.js 则把每个请求当做一个事件,完全节省了线程的创建和销毁、以及上下文的切换等开销,所需要的资源非常少。这和传统语言是完全不同的,比如 Java 和 PHP,它们面对一个新的 HTTP 请求时,都会启动一个新的线程或者进程来处理请求。虽然可以使用线程池之类的技术来优化性能,但是在机制上是不如 Go 或者 Node.js 的,比如 Java 中会给每个线程分配独立的栈内存,可能在 1M 左右,也可能更高。也就是说相同的服务器配置下,用 Go 或者 Node.js 编写的 HTTP 服务,吞吐量往往会比传统语言更高。
综上所述,两门语言都非常适合作为服务端开发,或者是中间件开发。至于怎么选择,更多地是看项目类型、团队技术栈等因素综合考量之后决定的。
以上就是本次面试中碰到的一些技术性问题和对应的回答,希望对你有所帮助。
如果你对最新的技术感兴趣,特别是对 Web3、AI 相关的内容感兴趣,可以添加我的微信 LZQ20130415,备注来自掘金,拉你进粉丝群学习交流。