编译:登链翻译计划

你是否曾经想要深化了解 Rollup 的运作原理?理论很好,但亲自实践经历总是更可取的。不幸的是,现有的项目并不总是让人轻易地检查内部状况。这就是为什么咱们创建了 BYOR(Build Your Own Rollup:构建你自己的Rollup)。它是一个具有最小功用的主权 rollup,重点是使代码易于阅览和了解。

咱们这个项目的动机是让人们(无论是外部人员仍是内部人员)更好地了解咱们周围的 rollup 实际上在做什么。你可以在 Holesky 的已布置的BYOR上游玩,或者阅览GitHub 上的源代码。

BYOR是什么?

BYOR 项目是一个简化版本的主权 rollup。与乐观和零常识证明的 rollup 比较,主权 rollup 不会在以太坊上验证状况根,只依赖于以太坊上的数据可用性和共识。这样可以防止 L1 和 BYOR 之间的信赖最小化桥,但极大地简化了代码,十分合适教育目的。

代码库由三个程序组成:智能合约、节点和钱包。当它们一起布置时,它们允许最终用户与网络进行交互。风趣的是,网络的状况完全由链上数据确认,这意味着实际上可以运转多个节点。每个节点也可以作为排序器(Sequencer)独立地发布数据。

下面是 BYOR 中完成的完好功用列表:

  • 费用排序

  • 将状况发布到 L1 并从 L1 获取状况

  • 丢掉无效的买卖

  • 检查账户余额

  • 发送买卖

  • 检查买卖状况

运用钱包

在钱包应用中,它充任网络的前端,用户可以提交买卖,并检查账户的状况或买卖的状况。在登陆页面上,你会看到一个概览,其中供给了有关 rollup 当时状况的一些统计信息,然后是你的账户状况。很或许,这儿仅有一个按钮用来衔接你挑选的钱包,并有关于代币水龙头的音讯。在下面,有一个查找栏,你可以张贴某人的地址或买卖哈希来探究 L2 的当时状况。最终,有两个买卖列表:第一个是 L2 内存池中的买卖列表,第二个是发布到 L1 的买卖列表。

要开端,请运用 WalletConnect 按钮衔接你的钱包。衔接后,你或许会收到一个告诉,提示你的钱包衔接到了过错的网络。假如你的应用程序支撑网络切换,请点击“切换网络”按钮切换到 Holesky 测试网络。否则,请手动切换。

现在,你可以经过供给接收者的地址、要发送的代币数量和所需手续费来向某人发送代币。发送后,钱包应用程序会提示你签署音讯。假如成功签署,音讯将被发送到 L2 节点的内存池中,等候被发布到 L1。买卖被捆绑到批次发布中所需的时刻或许会有所不同。每 10 秒,L2 节点会检查是否有待发布的内容。手续费较高的买卖会优先发送,因而假如你指定了较低的手续费而且有大量买卖流量,你或许会遇到较长的等候时刻。

工作原理

构建你自己的Rollup——BYOR 项目一览
Rollup 架构图

技能栈

咱们运用以下技能构建了每个组件:

  • 节点: Node.js, TypeScript, tRPC, Postgres, viem, drizzle-orm

  • 钱包: TypeScript, tRPC, Next.js, WalletConnect

代码深化解析

BYOR 代码专门规划成经过检查代码库就能轻松了解。请随意探究咱们的代码库!首先阅览README.md,了解项目结构请阅览ARCHITECTURE.md文件。

以下是代码中的一些风趣亮点:

智能合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Inputs {
   event BatchAppended(address sender);
   function appendBatch(bytes calldata) external {
       require(msg.sender == tx.origin);
       emit BatchAppended(msg.sender);
   }
}

这是仅有需要的智能合约。它的称号源于这个现实:将输入存储到状况转化函数中。该合约的仅有目的是为了方便地存储所有买卖。序列化的批次作为 calldata 发布到这个智能合约,而且它会宣布一个带有批次发布者地址的 BatchAppended 事情。尽管咱们可以规划体系,使其将买卖直接发布到 EOA 而不是合约,但经过宣布事情可以轻松经过 JSON-RPC 获取数据。这个智能合约的仅有要求是它不应该从另一个智能合约中调用,而应该直接从 EOA 中调用。

数据库模式

CREATE TABLE `accounts` (
 `address` text PRIMARY KEY NOT NULL,
 `balance` integer DEFAULT 0 NOT NULL,
 `nonce` integer DEFAULT 0 NOT NULL
);

CREATE TABLE `transactions` (
 `id` integer,
 `from` text NOT NULL,
 `to` text NOT NULL,
 `value` integer NOT NULL,
 `nonce` integer NOT NULL,
 `fee` integer NOT NULL,
 `feeReceipent` text NOT NULL,
 `l1SubmittedDate` integer NOT NULL,
 `hash` text NOT NULL
 PRIMARY KEY(`from`, `nonce`)
);

-- This table has a single row
CREATE TABLE `fetcherStates` (
 `chainId` integer PRIMARY KEY NOT NULL,
 `lastFetchedBlock` integer DEFAULT 0 NOT NULL
);

这是用于存储关于 Rollup 的信息的整个数据库模式。你或许会想当所有必要的数据都存储在 L1 上,为什么咱们需要一个数据库。尽管这是正确的,但是将数据存储在本地可以经过避免重复获取来节省时刻和资源。将在此模式中存储的所有数据视为状况、买卖哈希和其他核算信息的备忘录。

fetcherStates 表用于盯梢咱们在查找 BatchAppended 事情时获取的最终一个块。当节点封闭并重新启动时,这十分有用;它知道从哪里恢复查找。

状况转化函数

const DEFAULT_ACCOUNT = { balance: 0, nonce: 0 }

function executeTransaction(state, tx, feeRecipient) {
  const fromAccount = getAccount(state, tx.from, DEFAULT_ACCOUNT)
  const toAccount = getAccount(state, tx.to, DEFAULT_ACCOUNT)
  // Step 1. Update nonce
  fromAccount.nonce = tx.nonce
  // Step 2. Transfer value
  fromAccount.balance -= tx.value
  toAccount.balance += tx.value
  // Step 3. Pay fee
  fromAccount.balance -= tx.fee
  feeRecipientAccount.balance += tx.fee
}

上面显现的函数是 BYOR 中状况转化机制的核心。它假定买卖可以安全地履行,具有正确的 nonce 和足够的余额来进行定义的开销。由于这个假定,在这个函数内部没有过错处理或验证过程。相反,这些过程在调用函数之前履行。每个账户状况都存储在一个映射中。假如一个账户在这个映射中还不存在,它将被设置为代码清单顶部可见的默认值。运用的三个账户,nonce 被更新,余额被分配。

买卖签名

构建你自己的Rollup——BYOR 项目一览
买卖签名

咱们运用EIP-712规范来对类型化数据进行签名。这使咱们可以清楚地向用户显现他们正在签名的内容。如上所示,当发送一笔买卖时,咱们可以以用户友好的方法显现接收者、金额和手续费。

L1 事情获取

function getNewStates() {
 const lastBatchBlock = getLastBatchBlock()
 const events = getLogs(lastBatchBlock)
 const calldata = getCalldata(events)
 const timestamps = getTimestamps(events)
 const posters = getTransactionPosters(events)
 updateLastFetchedBlock(lastBatchBlock)
 return zip(posters, timestamps, calldata)
}

为了获取新的事情,咱们从 Inputs 合约中检索从上次获取的区块开端的所有 BatchAppended 事情。咱们检索的事情数量最多为最新的区块或上次获取的区块加上批量巨细限制。在检索所有事情之后,咱们从每个买卖中提取 calldata、时刻戳和发布者地址。将咱们获取的最终一个区块更新为咱们正在获取的最终一个区块。然后,将提取的 calldata、时刻戳和发布者打包在一起,并从函数中回来以进行进一步处理。

内存池及其费用排序

function popNHighestFee(txPool, n) {
 txPool.sort((a, b) => b.fee - a.fee))
 return txPool.splice(0, n)
}

内存池是一个管理已签名买卖数组的对象。最风趣的方面是它如何确认买卖被发布到 L1 的顺序。如上所示的代码,买卖是根据它们的费用进行排序的。这让体系中位数费用价格会根据链上活动而波动。

即便你指定了高费用,假如它们需要被附加到当时状况,买卖仍然需要产生一个有用的状况。因而,你不能仅仅由于费用高就提交无效的买卖。

BYOR 是否真实扩展了以太坊?

乐观和 ZK rollup 现已建立了体系来证明发布的状况根与状况转化函数和它们提交的数据是一致的,但主权 rollup 没有。因而,这种类型的 rollup 无法扩展以太坊,这一点或许一开端看起来有些违反直觉。然而,当咱们意识到其他类型的 rollup 可以仅运用 L1 来证明发布的状况根是正确的时,这就变得合理了。要区分主权 rollup 的数据是否正确,咱们需要运转一个 L1 节点以及额定的软件,以形式化 L2 节点来履行状况转化函数,从而增加了核算负载。

未来展望

对咱们来说,构建这个项目是一次很好的学习经历,咱们期望你也会发现咱们的尽力有价值。咱们期望将来可以回到 BYOR,为其添加一个欺诈证明体系。这将使它成为一个真实的乐观 rollup,并再次成为咱们日常运用的体系内部工作方法的教训。

此时快讯

【Coinbase高级软件工程师:相信加密货币将使世界变得更加和平与繁荣】金色财经报道,Coinbase高级软件工程师yuga.eth在X平台(原推特)发文称,我相信,加密货币将使世界变得更加和平与繁荣。否则,我不会从事这项工作。
不可篡改的分类账本、透明的货币政策、瞬时的价值传递以及数字财产的自我主权将大大降低实体掩盖战争和过度借贷等破坏性行为的成本的能力。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注