Levana NFT发布的详细指南

180 阅读21分钟

FP Complete公司总部位于北卡罗来纳州夏洛特市,是一家全球技术公司,为解决复杂的问题而开发下一代软件。 我们专注于服务器端软件工程、DevSecOps、云原生计算、分布式账本和高级编程语言。我们已经成为企业的全栈技术合作伙伴超过10年,提供可靠、可重复和高度安全的软件。 我们的工程师团队战略性地分布在超过13个国家,无论客户规模大小,都能为他们提供一站式的先进软件工程。

在过去的几个月里,FP Complete工程团队一直在与Levana Protocol合作,为Terra区块链上的杠杆资产建立DeFi平台。但最近,我们另外还在帮助推出Levana Dragons流星雨。这个NFT发射在上周中期完成,到目前为止是Terra生态系统中最大的一次NFT事件。我们很高兴能成为其中的一员。你可以在Levana协议的博文中阅读更多关于NFT发射本身的信息。

我们收到了很多关于这次发布会顺利进行的积极反馈,这是很好的反馈。人们表示有兴趣了解我们所做的技术决定,导致了如此顺利的事件。我们在发布期间和发布后也发生了一些小插曲,也值得解决。

所以,请带着你的心情来参加涉及云技术、DevOps实践、Rust、React,当然还有Dragons的旅程。

事件概述

Levana Dragons流星雨是一个由44个独立的 "流星雨 "组成的活动,或者说,在流星雨期间会有NFT流星发出。流星雨的参与者通过向特定的Terra钱包贡献UST(一种与美元挂钩的Terra专用稳定币)进行竞争。来自一个钱包的捐款在整个花洒中被汇总成一个捐款,捐款数额越高,流星就越好。在最细微的层面上,这意味着将流星分为传奇、古老、稀有和普通流星。但是更多的贡献也会导致在你的流星中收到一个蛋的可能性更大。

每次流星雨与下一次流星雨相隔1小时,我们在第一次流星雨发生前24小时开放了网站。这意味着该网站连续67个小时都是活跃的。然后,在流星雨之后,我们需要铸造实际的NFT,将它们运送到用户的钱包里,并打开 "洞穴 "页面,让用户可以查看他们的NFT。

因此,总的来说,这是一个跨越很多天的活动,有很多高活动的阵痛,涉及到一个包含很多金融交易的游戏,任何停机、缓慢或不良行为都可能导致用户的沮丧或更糟。此外,鉴于这个活动的时间很短,DDoS等攻击会使网站瘫痪,这对展示会的成功是灾难性的。而最糟糕的情况是,攻击者可以将资金转到另一个钱包。

说了这么多,让我们深入了解一下。

后台服务器

流星雨的一个主要组成部分是跟踪对目标钱包的贡献,并向用户提供关于这些活动的高水平数据。这种高水平的数据包括每场雨的底价,即将到来的下降的时间戳,用户到目前为止获得的流星总数,等等。所有这些信息在区块链上都是公开的,原则上可以被写成前端逻辑。然而,让网站的每个访问者下载与目标钱包的整个交易历史,其开销会使网站无法使用。

相反,我们实现了一个后端网络服务器。出于多种原因,我们使用了Rust(和Axum)来实现:

  • 我们对Rust非常熟悉
  • Rust是一种高性能的语言,而且我们非常担心需要承受流量的激增和DDoS攻击。
  • 由于CosmWasm已经在很大程度上利用了Rust,Rust已经在该项目中使用。

服务器负责跟踪配置数据(如淋浴时间戳和目标钱包地址),从区块链上下载交易信息(使用全客户端守护程序),并回答提供这些信息的前端(接下来描述)的查询。

我们本可以将数据保存在像PostgreSQL这样的易变数据库中,但我们决定将所有数据保存在内存中,并在每次应用加载时从区块链中从头下载。考虑到数据的大小,这两个决定最初似乎是非常明智的。当我们分析性能和查看下面的一些错误时,我们会看到一些结果。

React前台

用户互动的主要界面是一个标准的React前台应用程序。我们使用了TypeScript,但在其他方面尽可能使用通用工具和库。我们最终没有使用任何状态管理库或自定义CSS系统。另一件需要注意的事情是,这个前端将随着时间的推移而扩展和发展,包括围绕不断发展的NFT概念的额外功能,其中一些已经发生,我们将在下面讨论。

一个突然出现的具体项目是移动优化。最初,计划中的流星雨网站是只在桌面上运行的。在几个测试版运行后,很明显,大多数用户都在使用移动设备。作为一个DAO,Levana的一个主要目标是允许所有产品和服务的分布式治理,因此我们觉得对这个社区的要求做出回应是至关重要的。为移动设备重新设计界面,然后重写相关的HTML和CSS,占用了相当大的时间。

托管基础设施

许多DApps网站完全是客户端,利用前端逻辑与区块链和智能合约完全互动。对于这些类型的网站,像Vercel这样的托管选项效果非常好。然而,如上所述,这个应用程序是一个组合的前端/后端。我们决定将静态的前端应用和后端的动态应用都托管在一个地方,而不是在两个不同的选择之间分割托管。

在FP Complete,我们通常使用Kubernetes进行这种部署。但在这种情况下,我们选择了Amazon ECS。这与我们的标准Kubernetes部署没有很大的区别,遵循许多相同的模式:基于容器的应用、带有健康检查的滚动部署、自动缩放和负载平衡器、外部化的TLS证书管理以及集中监控和记录。这方面没有大问题。

此外,为了帮助减少后端应用程序的负担,并为网站提供更好的全球体验,我们把亚马逊云端放在应用程序的前面,这允许在世界各地的数据中心缓存静态文件。

最后,我们使用Terraform对所有这些基础设施进行了编码,这是我们用于基础设施即代码的标准工具。

GitLab

GitLab是我们FP Complete工具链的一个标准部分。我们在内部项目中利用它的代码托管、问题跟踪、Docker注册表和CI集成。虽然我们经常会调整我们的工具以满足客户的需求,但在这种情况下,我们最终还是使用了我们的标准工具,而且事情进展得非常顺利。

我们最终采用了一个四阶段的CI构建过程:

  1. 提示并构建前端代码,产生一个带有构建的静态资产的工件
  2. 从后端构建静态的Rust应用程序,嵌入(1)中的静态文件,并运行标准的Rust lints(clippyfmt ),产生一个包含单文件编译二进制文件的工件。
  3. 从(2)中的静态二进制文件生成一个Docker镜像
  4. 将新的Docker镜像部署到dev或prod ECS集群上。

步骤(3)和(4)被设置为只在masterprod 分支上运行。这种自动化的部署设置使我们的分布式团队可以很容易地将变化快速地放到真实环境中进行审查。然而,它也打开了一个我们需要解决的安全漏洞。

AWS锁定

由于这个应用程序的性质,在活跃的淋浴期间,任何形式的停机都可能导致我们的脸上有很多鸡蛋,并错过了NFT提高的机会。然而,还有一个更可怕的潜在结果。在生产中改变一个单一的配置值--目的地钱包--会使邪恶的行为者抽走用于NFT的资金。这是我们在启动过程中的主要关切。

我们考虑了多种社会工程方法来解决这个问题,例如向潜在的用户宣传他们应该使用的正确钱包地址。然而,我们决定,最有可能的是用户在发送他们的资金之前不会检查地址。我们确实设置了一些紧急的 "流星雨停止 "页面,并成立了一个待命小组,以便在必要时检测和部署此类措施,但幸运的是没有发生类似的情况。

然而,在流星雨期间,我们确实设置了一个AWS账户锁定。这包括:

  • 将我们用于授予临时AWS凭证的工具Zehut切换为只读凭证模式
  • 禁用GitLab CI的生产凭证,这样GitLab用户就不能导致prod的变化。

我们还审查了DNS解析管道中的所有其他组件,如域名注册商、Route 53和其他用于托管的AWS服务。

这些一般都是良好的做法,随着时间的推移,我们打算在总体上完善Levana的AWS账户的AWS权限设置。然而,这次发布是我们第一次需要使用AWS进行应用部署,时间上不允许进行彻底的AWS权限分析和配置。

淋浴时

正如我刚才提到的,在淋浴期间,我们有一个待命的团队,随时准备投入行动,并有一个游戏手册来解决潜在的问题。问题基本上分为三类:

  1. 网站在某些方面很慢/很慢/很糟糕
  2. 网站是积极的恶意的,提供错误的内容,并有可能对人们进行诈骗
  3. 某种社会工程攻击正在进行中

FP Complete团队负责观察(1)和(2)。老实说,这并不是我们的强项。我们是一个通常构建后台和设计DevOps解决方案的团队,而不是一个随叫随到的运营团队。然而,我们既是DevOps托管的专家,也是应用本身的专家。幸运的是,没有出现重大问题,而待命团队也一直坐在那里。

出于谨慎的考虑,我们确实在淋浴开始前采取了一些额外的措施,试图确保我们为任何攻击做好准备。

  1. 我们将ECS中的副本数量从2个所需的实例增加到5个。我们已经有了自动缩放功能,但为了安全起见,我们希望有额外的缓冲。
  2. 我们将实例的大小从512个CPU单元增加到2048个CPU单元。

在我们启动前的所有负载测试中,我们看到512个CPU单元足以处理每个实例每秒10万个请求,99%的延迟为3.78ms。在生产过程中,在网站活动最频繁的时候,我们非常高兴地看到以下的CPU和内存使用图表。

这是一个很好的证明,证明了Rust编写的网络服务的力量,结合适当的自动缩放和CloudFront缓存。

图像创建

好了,让我们先把应用程序本身放在一边。我们知道,在淋浴结束时,我们需要为每个在一次淋浴中捐赠超过8美元的钱包快速铸币NFTs。这其中有几个问题:

  • 我们不知道有多少用户会捐款。
  • 生成图像是一个相对缓慢的过程。
  • 使图像在IPFS上可用--这对NFTs的工作方式是必要的--可能会成为一个瓶颈。

我们最终做的是编写一个Python脚本,预先生成10万张左右的流星图像。我们直接在亚马逊EC2实例上进行生成。然后,我们没有把图像上传到IPFS主机/钉子服务,而是直接在这个EC2实例上运行IPFS守护程序。我们还在S3上备份了所有的图像,用于冗余存储。然后我们启动了第二个EC2实例,用于冗余的IPFS托管。

这个Python脚本不仅生成了图像,而且还生成了一个CSV文件,将图像的内容ID(IPFS地址)与关于流星图像的各种元数据(如流星体)映射在一起。接下来我们将使用这个CID/流星图像元数据映射来正确造币。

总而言之,这一切都运作得很好。然而,有一些障碍,我们有计划在NFT发展的未来阶段改变这一点。我们将在下面提到这些。

造币

一旦淋浴完成,我们需要尽快将NFT放入用户的钱包。这意味着我们需要两个不同的东西:

  1. IPFS上的所有NFT图像,我们已经有了。
  2. 一组CSV文件,提供要生成的NFT,以及它们所有的元数据和所有者。

前者是由上一步处理的。后者是我们编写的额外的Rust工具,利用了我们为后端应用程序编写的相同的内部库。这个工具的目的是为了:

  • 汇总区块链上的全部贡献。
  • 将贡献分层为不同稀有度的单个流星。
  • 应用适当的算法,随机决定哪些流星可以得到彩蛋,哪些不可以。
  • 在流星之间分配鸡蛋。
  • 给流星分配额外的元数据。
  • 根据每颗流星所需的元数据,为其选择一个合适的、独特的流星图像。(这有赖于上面Python生成的CSV文件)。

这个过程产生了一些不同的数据:

  • 用于生成流星NFT的CSV文件。这些没有什么秘密,你可以通过分析区块链上的NFT铸币来自己重构它们。
  • 属性(如精华、晶体、距离等)在流星中的分布,用于计算个别特征的稀有性。同样,这可以从公共信息中轻松得出。
  • 一个跟踪流星/卵子映射的文件。这是这个过程中的一个结果,是一个被严密保护的秘密。

这最后一点也在影响着这个项目接下来几个阶段的设计。具体来说,虽然智能合约将是与NFTs进行一般互动的更自然的方式,但我们不能在区块链上暴露流星/鸡蛋的映射。因此,"破解 "阶段(将允许用户用流星交换其潜在的鸡蛋)将需要与另一个后端应用程序一起工作。

无论如何,这个元数据生成过程是我们在测试版运行的数据上多次测试过的,并且在雨后不久就准备好了生产和发送至Knowhere.art进行铸造。我相信用户在喷淋结束后的8小时内就在他们的钱包里得到了NFT,这总体来说是一个相当好的时间框架。

打开山洞

最后一步是打开山洞,这是流星网站上的一个新页面,允许用户查看他们的流星。这个阶段是通过更新后台的配置值来实现的,包括:

  • NFT收集的智能合约地址
  • 流星的总数量
  • 特质分布

一旦我们切换了配置值,洞穴就打开了,用户就可以访问它了。除了从服务器上拉取上述静态信息外,所有洞穴页面的互动完全发生在客户端,客户端使用Terra.js库查询区块链。

而这就是我们今天的情况。淋浴完成了,用户得到了他们的流星,洞穴开放了,我们又开始实施这个项目的破解阶段了。W00t!

问题

总的来说,这个项目在生产中进行得相当顺利。然而,也有几个值得一提的棘手时刻。

FCD速率限制

我们在阵雨期间遇到的最大问题,也是最有可能破坏一切的问题,就是FCD速率限制。在真正的阵雨前,我们在testnet上做了大量的测试,除了机器人,还有许多志愿者测试者。据我所知,我们从来没有遇到过一个速率限制的例子。

然而,在真正的生产淋浴中,大约在10个淋浴中遇到了这种速率限制的问题。(我们稍后会看看他们是如何表现的。)这有多种潜在的促成因素:

  • 真实事件中的活动比我们测试的要多得多。
  • 我们的大部分测试只限于10次淋浴,而真正的活动是44次。
  • 在主网和测试网的FCD可能有不同的速率限制规则。

不管是什么情况,当我们试图推出一个新的功能时,我们开始注意到速率的限制。我们实施了望远镜功能,允许用户查看以前的阵雨中的历史底价。

然而,在向ECS推送这一变化后,我们注意到,新的部署没有上线。原因是,在最初的数据加载过程中,新的进程收到了速率限制的响应并死亡。我们试图通过添加延迟或其他类型的重试逻辑来解决这个问题。然而,这些组合都不允许应用程序在ECS的准备就绪检查期间开始处理请求。(我们可以简单地关闭健康检查,但这又会打开一个新的麻烦。)

这个问题是相当关键的。无法推出新功能或错误修复是令人担忧的。但更令人不安的是缺乏自动修复功能。现有的实例继续正常运行,因为它们只需要从FCD下载少量的数据来保持最新,因此从未触发过速率限制。但是,如果这些实例中的任何一个发生故障,ECS将无法用健康的实例来替换它们。

幸运的是,我们在之前的几周已经写好了大部分的缓存解决方案,但由于我们认为这不是一个优先事项,所以没有完成这项工作。经过几个小时的努力,我们得到了一个解决方案:

  • 将所有事务保存到YAML文件中(二进制格式会是更好的选择,但YAML是最容易推出的)。
  • 将这个YAML文件上传到S3
  • 循环运行这个保存/上传过程,每10分钟更新一次
  • 修改应用逻辑,首先从S3下载YAML文件,然后使用FCD从那里进行delta加载。

这大大减少了启动时间,完全绕过了速率限制,并允许我们推出新的功能,而不用担心整个网站会瘫痪。

IPFS托管

FP Complete的DevOps方法明显地以云为重点。对于大型blob存储,我们的首选解决方案几乎总是基于云的blob存储,在亚马逊的情况下,这将是S3。在这个项目之前,我们对大规模IPFS数据托管的经验为零,这带来了一个独特的挑战。

如前所述,我们不想使用IPFS的钉子服务,因为速率限制可能使我们无法上传所有预先生成的图像。(速率限制在这里开始听起来像一个模式......)由于对S3很熟悉,我们最初尝试用go-ds-s3来托管图片,这是ipfs CLI的一个插件,使用S3来存储。我们仍然不知道为什么,但这对我们来说从来没有正确的工作。相反,我们恢复了在亚马逊EBS上存储原始图像数据,这更昂贵,更不耐用,但实际上是有效的。为了解决耐久性问题,我们把所有的原始图像文件备份到S3。

然而,总的来说,我们对这个结果并不满意。这个主机的成本相对较高,而且我们还没有建立一个真正的容错、高可用的主机。在这一点上,我们希望切换到一个IPFS的钉子服务,比如Pinata。现在图像在IPFS上是可用的,发布API调用来钉住这些文件应该比上传完整的图像更容易。我们计划将此作为一个框架,用于其他图像,即:

  • 在EC2上生成原始图像
  • 上传到S3以获得持久性
  • 在本地运行ipfs ,使图像在IPFS上可用。
  • 将图像钉在一个服务上,比如Pinata
  • 关闭EC2实例

我们遇到的下一个问题是...。速度限制,再次。这一次,我们发现Cloudflare的IPFS网关在下载流星图像时对用户进行了速率限制,导致用户只能看到部分流星出现在他们的洞穴页面。我们解决了这个问题,把CloudFront插在存放流星图像的S3桶前面,并从那里提供服务。

展望未来,当它可用时,Cloudflare R2是S3+CloudFront产品的一个有希望的替代方案,因为它降低了存储成本并完全消除了带宽成本。

经验之谈

这个项目是利用现有专业知识和搭配一些新挑战的绝佳组合。我们在这里学到的一些主要经验是:

  1. 我们从Rust代码中获得了很多直接使用Terra的LCD和FCD API的工作经验。以前,在我们的DeFi工作中,这几乎完全是在Terra.js的后面使用。
  2. IPFS对我们来说是一个全新的话题,我们一开始就玩了一些相当极端的案例。理解钉子和网关的概念将对我们未来的NFT工作有极大的帮助。
  3. 由于ECS对我们来说是一个相对不寻常的技术,我们必须了解它与Kubernetes(我们更标准的工具链)之间的一些特殊性。
  4. 虽然速率限制是一个我们熟悉的概念,并且在过去曾多次合作过,但这些特殊的障碍都是新的,而且每一个都以不同的方式令人惊讶。通常情况下,对于这些速率限制问题,我们会有一些比较简单的解决方法,比如使用认证的请求。不得不以如此极端的方式来解决每个问题是令人惊讶的。
  5. 虽然我们已经参与区块链和智能合约工作多年,但这是我们第一次直接与NFT合作。这可能是学到的最简单的一课。查询NFTs合约的API相当简单,只占这个项目花费时间的一小部分。

总结

我们很高兴能够参与到Levana Dragons NFT流星雨这样一个成功的事件中。这是一个有趣的网站,有一个巨大而活跃的用户群,还有一些有趣的挑战。把我们的一些标准的云计算DevOps实践与区块链和智能合约的共同实践结合起来是非常好的。而使用Rust带来了一些我们相当满意的优势。

展望未来,我们期待着继续发展这个项目的后端、前端和DevOps,就像NFTs本身将不断发展。祝大家龙年好运