如何点亮黑暗森林 - 构建Cryptopunk MEV检查器的指南

  • flashbots
  • 发布于 2021-12-17 16:22
  • 阅读 16

本文详细介绍了如何使用新的CryptoPunks狙击手实例,逐步实现一个新的MEV(最大可提取价值)检查器。文章包括了从添加合约ABI到建立分类器、解析交易、构建数据库模型和迁移的完整过程,适合希望为MEV工具做出贡献的开发者。通过具体步骤和示例代码,读者可以了解如何架构和实现MEV检查器,从而分析新兴市场的套利和前跑策略。

如何逐步添加一个新的 MEV 检查器 - 以最近添加的 cryptopunk snipers 检查器为例

Punk snipers 和 mev-inspect-py

上个月,我在 Twitter 上收到了关于一种新型 MEV 形式的消息,这种形式涉及 cryptopunks,作为第一个和最有价值的 NFT 之一。Cryptopunks 或称为 punks,在合约中设计了自己的原生市场。你可以列出你的 punk,竞标其他 punks,并接受来自 cryptopunk 智能合约的出价。

然而,这个市场的设计留下了一个被抢先交易机器人利用的空白!当一个 punk 的拥有者接受出售他们的 punk 的报价时,他们实际上是在接受市场上现存的最高报价。如果有人以非常低的价格购买一个 punk,并且 punk 的拥有者接受了该报价,那么 MEV 机器人会以一个略高于原始低价报价的出价抢先于 punk 的拥有者接受报价,结果是当拥有者的接受被执行时,MEV 机器人的出价就是最高的,MEV 机器人会以极好的价格获得 punk。

通过这种方式,这些夺镖机器人能够在低价购买 cryptopunks 的同时牺牲了原本出低价的用户!铸造社区对此感到愤怒,因为努力寻找并试图以低价购买 punks 的买家并没有得到奖励——而是由机器人获得了这些收益。

因此,我转向了 mev-inspect(简称“Inspect”),尝试揭示 punk 市场底层发生的事情。Inspect 是 Flashbots 的开源工具,用于检查以太坊区块历史以提取 MEV,以及支撑 MEV-explore 的数据管道。Inspect 试图通过策略对 MEV 提取进行分类,并为分析提供相关数据。例如,它可以发现多个顶级 DEX 之间的套利以及清算。它是分析新 MEV 策略的完美工具。

自从我上次贡献以来,Inspect 已经过了几个迭代。因为我的同事 Taarush、Gui 和 Luke 的出色工作,它现在显得更加专业,功能也增加了 10 倍。作为一个团队,我们感觉代码库已经准备好接受更多的贡献,我很高兴能通过为这个 punk 用例添加一个新的检查器来实现这个目标。

cryptopunk snipe 检查器现在已上线并投入生产。本文记录了编写新检查器的过程,并以 cryptopunk snipe 检查器为例。我希望它能为其他人提供动力和指南,以便贡献给 Inspect。接下来的博客文章将专注于 cryptopunk sniping MEV 相关的数据。

使用 mev-inspect-py 构建

从高层来看,Inspect 的工作原理是获取交易的所有跟踪记录,分类跟踪,构建模式,推断 MEV 策略,并最终将数据发布到数据库中。在此过程中,Inspect 将用关于它找到的 MEV 策略的结构化数据填充数据库。

要将新的 MEV 策略,即 cryptopunk sniping,添加到 Inspect,我采取了以下步骤:

  • 将 cryptopunk ABI 添加到我们的 ABI 列表 (链接)
  • 列出我们想要的活动分类在跟踪模式下 (链接),并在协议中添加一个 cryptopunk 条目 (链接)
  • 创建 punk 分类器规范 (链接)
  • 使用我们想要的数据构建模式 (链接)
  • 将跟踪解析为我们的新 punk 模式 (链接)
  • 添加数据库模型 (链接) 和 CRUD 函数 (链接)
  • 添加 Alembic 文件以进行数据库迁移 (链接)

在这里,我实现了一个新的策略检查器,这需要所有 7 个步骤。 但是,如果你将一个新的协议(例如 DEX)添加到现有检查器(例如套利)中,那么只需要前 3 个步骤,因为步骤 4 到 7 已经完成。新的规范将适合现有模式、解析脚本和数据库集成。

无论如何,让我们详细看一下这 7 个步骤。这个过程仅松散线性,它看起来比实际复杂得多。一些步骤是并行执行的,并且在设计对象时你可能会重新审视某些步骤。

添加 cryptopunk ABI

为了识别一个合约及其功能,Inspect 需要其 ABI。通过快速的谷歌搜索,我找到了 CryptoPunks 合约地址 并通过点击“contract”,向下滚动,复制粘贴 ABI 到一个新文件中。该文件命名为 “cryptopunks.json" 并放置在 ABI 文件夹内的“ cryptopunks”文件夹中。

创建分类和协议条目

(本文假设你对跟踪记录有一定了解。如果你需要入门,请查看 这篇博客)。

对于给定的跟踪记录,Inspect 将尝试检查以下内容以进行分类:

  • 该跟踪记录与哪个地址交互?
  • 该地址是否有 Inspect 知道的 ABI?
  • 该跟踪记录是否在调用我们关心的函数?

到目前为止,我们只为 Inspect 提供了一个 ABI,但很快我们将编写一个规范,告诉 Inspect 要查找哪些地址和函数。每当它在跟踪记录中找到该地址和函数组合时,它将使用给定的标签对该跟踪记录进行分类。这一步是关于创造这些标签。

由于我们正在对跟踪进行分类,因此添加分类到 traces.py 模式。这就像列出我们要分类事项的简短描述,在这种情况下,为 punk 出价 (punk_bid) 和接受 punk 出价 (punk_accept_bid)。

如果我们的协议还不存在,我们同样在“协议”下添加一个条目。如果没有,我们也添加了 “cryptopunks”到协议列表中。

创建一个 punk 分类器规范

分类器规范在我们刚刚创建的分类和协议条目基础上,告诉 Inspect 在分类跟踪时要查找什么。对于 cryptopunk sniper,它看起来像这样:

from mev_inspect.schemas.traces import Protocol, Classification
from mev_inspect.schemas.classifiers import (    Classifier,    Classifier,)

class PunkBidAcceptanceClassifier(Classifier):    
    @staticmethod    
    def get_classification() -> Classification:        
        return Classification.punk_accept_bid

class PunkBidClassifier(Classifier):    
    @staticmethod    
    def get_classification() -> Classification:        
        return Classification.punk_bid

CRYPTO_PUNKS_SPEC = ClassifierSpec(    
    abi_name="cryptopunks",    
    protocol=Protocol.cryptopunks,    
    valid_contract_addresses=["0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB"],    
    classifiers={        
        "enterBidForPunk(uint256)": PunkBidClassifier,        
        "acceptBidForPunk(uint256,uint256)": PunkBidAcceptanceClassifier,    
    },)

CRYPTOPUNKS_CLASSIFIER_SPECS = [CRYPTO_PUNKS_SPEC]

让我们理清楚。首先,我们为我们感兴趣的操作的分类器创建类。在我们的情况下,我们想对 punk 出价和接受进行分类,因此我们创建了 PunkBidAcceptanceClassifier 和 PunkBidClassifier。

Inspect 要求你具体说明这些分类器是如何使用的。这样做的原因在于,我们希望能够识别许多协议中的某些类型的操作,但并非所有的协议都是以相同的方式设计的。例如,资产交换的函数在许多 DEX 中是不同的,因此为了建立一个单一的“交换”分类,我们需要告诉 Inspect 每个协议应该采取什么。

对于一次性协议,例如我的 punk 检查器,你可以复制我使用的模板并替换 punk 特定的部分。但是,如果你是在为一个协议添加支持,以对分布式服务中未标准化的操作进行分类,我建议查看如何在几个 DEX 中实现交换功能。

在这些标准化函数下面就是规范本身。规范有 4 个部分:

  • abi_name:之前输入的 ABI 的名称,必须与你指定的文件名完全匹配。
  • protocol:上一步中指定的协议。
  • valid_contract_addresses:你希望查找的任何合约地址。请注意,在你不想将分类器限制于一组地址的情况下,可以省略此项。
  • classifiers:你希望查找的一组函数及其分类。例如,我们想查找 punk 出价,因此我们为函数“enterBidForPunk(uint256)”创建了一个“PunkBidClassifier”的条目。请注意,分类器通过使用 函数选择器 工作,因此你必须指定函数名称和类型且不带空格。

最后,你必须将分类器规范添加到 init.py 文件中,以使其对 Inspect 可见。此时,Inspect 将开始对与你的协议相关的跟踪记录进行分类 🎉!

在对包含你所需 MEV 的区块运行 Inspect 后,你可以通过访问数据库来 查看 Inspect 的输出,并在 “classified_traces” 表中查询你的 ABI。如果你将支持添加到现有检查器的一个新协议(例如 DEX)中,那么在这个阶段,你的工作就完成了!

使用我们想要的数据构建模式

Inspect 的工作原理是构建我们称之为模式的结构化对象。每个模式都存储在 schemas 文件夹 下的单独文件中,旨在存储有关某些对象的数据。它们看起来像这样:

class PunkBid(BaseModel):    
    block_number: int    
    transaction_hash: str    
    trace_address: List[int]    
    from_address: str    
    punk_index: int    
    price: int

你需要为你想创建的每个新对象创建一个模式。在我的情况下,我创建了三种:punk 出价、punk 出价接受和 punk snipes。在制作模式时,考虑一下你和他人想要查看的信息。

将跟踪解析到我们的新模式中

随着跟踪记录被分类为我们想要的操作,我们可以开始查询我们的分类并编写我们的策略检查器的逻辑。这样做的方法很简单。作为一个例子:

if trace.classification == Classification.punk_bid:            
    punk_bid = PunkBid(                
        transaction_hash=trace.transaction_hash,                
        block_number=trace.block_number,                
        trace_address=trace.trace_address,                
        from_address=trace.from_address,                
        punk_index=trace.inputs["punkIndex"],                
        price=trace.value,            
    )
return punk_bid

对于每个新的策略检查器,我们在 mev_inspect 文件夹 中创建一个文件,并在 inspect_block.py 中引用它,这是 Inspect 的核心。你调用的函数应该接受分类跟踪记录列表并返回一个模式。

在 punk 出价和接受的情况下,我们需要很少的逻辑来构建我们的模式。然而,识别 punk snipes 的用例需要一些逻辑和出价与接受的列表。我们首先获取出价和接受,然后使用这些作为输入到一个 检查 snipes 的函数,如果存在,返回 snipes 列表。

此时,我会在我的代码中添加一些打印信息,并进行测试运行以确保一切按预期工作。你可以使用以下命令运行测试:

./mev inspect [BLOCK_NUMBER]

添加数据库模型和 CRUD 函数

现在几乎所有的检查器逻辑都已完成。剩下的就是将你生成的数据发布到数据库,以便于查询和查看。为此,我们需要创建数据库条目应该是什么样子的模型,以及用于从数据库中写入/删除的 CRUD 函数。

数据库模型指定了你希望存储的数据,以及每个条目应具有的数据类型。以下就是 punk snipe 条目的样子:

class PunkSnipeModel(Base):    
    __tablename__ = "punk_snipes"    
    block_number = Column(Numeric, nullable=False)    
    transaction_hash = Column(String, primary_key=True)    
    trace_address = Column(ARRAY(Integer), primary_key=True)    
    from_address = Column(String, nullable=False)    
    punk_index = Column(Integer, nullable=False)    
    min_acceptance_price = Column(Numeric, nullable=False)    
    acceptance_price = Column(Numeric, nullable=False)

你应当创建一个唯一的文件并将其存储在 models 文件夹 中。CRUD 函数存储在 crud 文件夹 中。这些函数使用我们的模式和数据库模型,从 Inspect 数据库中进行写入和删除。我建议你查看 punk crud 作为一个示例,并用其作为模板通过替换模式和模型来创建你自己的。

添加 Alembic 文件以进行数据库迁移

Alembic 是一个轻量级的数据库迁移工具。我们用它来管理对 Inspect 数据库的不同修订。由于你正在添加一个新的检查器,你需要对数据库进行更改。

要产生一个新的 alembic 修订文件,可以使用以下命令: ./mev exec alembic revision --autogenerate -m "{message}" 然后使用以下命令将其从你 Kubernetes pod 中复制到本地计算机: kubectl cp pod:dir/file local_dir/file 现在编辑此文件,并添加你想要的数据库功能。这应该与上一步中创建的模型密切相关。punk snipe 数据库条目的 alembic 文件就是一个良好的示例。最后,使用以下命令将数据库升级为新迁移: ./mev exec alembic upgrade head

将所有内容汇总在一起

现在你已经准备好检查一个区块并查询数据库的结果。首先,我们在一个区块上运行 Inspect: ./mev inspect [BLOCK_NUMBER] 现在我们连接到本地 postgres 数据库: ./mev db 你应该看到 mev_inspect=#。在我的情况下,我想查看我的新 punk 表,所以我运行了: SELECT COUNT(*) FROM punk_snipes; 这返回了 1!这意味着 Inspect 正在成功地找到 punk snipes。

恭喜你!你已经了解了创建新的 MEV 检查器过程中涉及的不同部分。我希望你会尝试自己写一个,不要害怕在 Flashbots Discord 的 #mev-inspect 中提问。如果你有关于开始的建议,我们在 Inspect 仓库中有一个当前和理想的分类器列表。来帮助我们照亮黑暗的森林,安昂!

感谢 Taarush、Chris、Gui、Luke 和 Alex 对这篇文章的审阅。

  • 原文链接: writings.flashbots.net/t...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
flashbots
flashbots
江湖只有他的大名,没有他的介绍。