限时活动!无奖励挖矿,用显卡给 Uniswap v4 接生
2024-11-13 15:50
Antalpha Labs
2024-11-13 15:50
订阅此专栏
收藏此文章

在 11 月 10 日,Uniswap 官方发起了一项为 Uniswap v4 挖掘地址的活动[1]

从即日起到 12 月 1 日。大家可以计算通过 create2 方式,计算 Uniswap v4 的部署地址,这些地址可以通过评价指标得到一个分数,分数最高的地址,将会作为 Uniswap V4 在主网的地址。简单来说,使用你的设备,进行充分的哈希计算,帮助 uniswap 生成 v4 的地址。

得分规则

这套地址的评分指标如下:

  • 每个前缀 0 得 10 分.
  • 如果前缀 0 之后的 4 位是 4444,得到 40 分.
  • 如果上述的 4444 之后不是 4,得到 20 分.
  • 如果最后 4 位是 4444,得到 20 分。
  • 地址中每有一个 4,就得 1 分.

举个例子,如果地址是 0x00000000044442D64A0BE733A5f2a3187BFA8234,那么

  • 9 个前缀 0 得到 90 分
  • 4444 得到 40 分
  • 4444 之后没有 4,得 20 分
  • 地址中有 6 个 4,得到 6 分

最终可以得到 156 分。在这套规则下,如果想要高分,地址必须形如 0x0000004444XXXXXX...确实是非常有识别度的地址了,堪比 seaport 的拉风地址。

另外还有个额外的要求,salt 的前 20 位必须是提交者的地址。

如何开始挖矿

Zelos-alpha 团队也希望尝试一下。不过我们之前并没有地址挖掘的经验。毕竟,挖矿从来就不是容易的事情,不仅拼脚本,也要拼算力。我们并没有这样的技术储备。好在官方指了一条明路,用 create2crunch。

我们了解了一下 create2crunch,发现这个小家伙还蛮不错,它是基于 rust 写的。能够非常充分的利用 CPU 资源。但我们更看重它支持 GPU 计算。因为团队新入了一块 GTX 4070,可以让它锻炼一下。

但是深入时候后发现 create2crunch 是一个目的性很强的工具,首先它假定地址都是使用 factory 合约部署的,因此需要传入 factory 地址和 caller 地址,这点和 Uniswap 的挖矿活动并不符合。另外它实现 GPU 计算是使用 OpenCL 框架, 但代码中限制只能使用默认设备。对于我们来说,这个设备是 CPU 而不是显卡。最严重的问题在挖矿规则上,create2crunch 只支持前缀 0 和全部 0 的规则,显然这个规则太简单了,和 Uniswap 的规则相差很大。

在没有更好选择的情况下,我们只能 fork 项目,自己动手做定制化改进。

首先是地址的问题,我们研究发现,参数中的 factory 地址实际上是 deployer 地址,而 caller 地址实际是 salt 前缀。因此第一个参数应该是 Uniswap 提供的 deployer 地址,第二个参数是自己地址,保证 salt 的前缀是自己:

$ cargo run --release 0x48E516B34A1274f49457b9C6182097796D0498Cb [your address] 0x94d114296a5af85c1fd2dc039cdaa32f1ed4b0fe0868f02d888bfc91feb645d9

然后我们顺便更改了代码中对应的变量名,避免混淆。

下一个问题是计算平台的问题,为此我们将原来的 device id 参数扩充了一下,添加了 platform id。这样就可以在参数中指定 OpenCL 的平台和设备了。以我们的工作站的 OpenCL 设备列表举例

$ clinfo -l
Platform #0: Intel(R) OpenCL
 `-- Device #0: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Platform #1: NVIDIA CUDA
 `-- Device #0: NVIDIA GeForce RTX 4070
Platform #2: Intel(R) FPGA Emulation Platform for OpenCL(TM)
 `-- Device #0: Intel(R) FPGA Emulation Device

显卡的位置在 Platform 1,device 0,因此在参数中指定 1 和 0 即可.

最终, 程序的入参如下:

pub struct Config {
    pub deployer_address: [u820],// 部署用户的地址
    pub salt_prefix_20: [u820],// salt 的前缀,本次活动中要填写自己的地址
    pub init_code_hash: [u832],// uniswap 合约的 init code
    pub platform_id: u8//OpenCL 的 platform id
    pub gpu_device: u8//OpenCL 的 device id
    pub leading_zeroes_threshold: u8// 前缀 0 个数,最终可以得到 N*2 或者 N*2+1 个前缀 0
}

现在是最重要的部分,规则和打分。这就需要比较多的修改了。通过分析规则我们发现,如果有 N 个前缀 0,并保证 4444,形如 00004444,可以得到() 分,如果 4444 之后不是 4( 事实上很难做到 ) 还能再拿 20。而结尾的 4444 可以不用管,因为如果让结尾是 4444,需要保证 4 位是 4 才能得到 20 分 ( 概率是 ),而如果把算力放到前缀 0 上,只要保证 2 位是 0 就可以得到 20 分,概率是 。而规则 "4 的个数" 也不用管,一个 4 才 1 分,少几个 4 对结果影响不大。所以,整体思路是,在保证前缀 0 之后是 4444 的情况下,得到尽可能多的前缀 0。想来 Uniswap 制定这样的规则也很合理,毕竟多一个前缀 0 就能多节约一点 gas。日积月累是一笔不小的数字。

现在看一下 create2crunch 的源代码结构,文件非常的简洁,

├── kernels
│   └── keccak256.cl
├── lib.rs
├── main.rs
└── reward.rs

其中最主要的文件是 lib.rs,主要数据结构和逻辑都在这里了。而 keccak256.cl 是调用 OpenCL 的源代码,这部分代码并不是 rust,幸运的是,这部分代码的语法很像 C( 应该就是 C),因此即使没接触过 OpenCL 也应付得过来。

首先要更改的,是 keccak256.cl,我们需要将判断前缀 0 的代码,改成判断前缀 0 和 4444 的代码,这样函数只会抛出合格的地址,避免人工再筛选。修改后就成了:

static inline bool hasLeading(uchar const *d)
{
#pragma unroll
  for (uint i = 0; i < LEADING_ZEROES + 1; ++i) {
    if(i < LEADING_ZEROES){
      if (d[i] != 0return false;
    }else{
      if (d[i] == 68 && d[i+1]==68return true;
      if (d[i] == 4 && d[i+1]==68 && ((d[i+2] & 240) == 64 )) return true;
      return false;
    }
  }
  return false;
}

在这段代码中,先排除前 N 位不是 0 的 ( 注意这里的每一位 d[i] 的类型是 uint8,在地址中占两位,比如 N=3 实际指 000000 而不是 000)。如果符合,则判断 N+1 和 N+2 是不是 44,然后就可以得到形如 00 00 00 44 44 的地址。然后要注意一个特殊情况,就是 N+1 当中的第一位也是 0,此时地址形如 00 00 00 04 44 4X,这就要通过位运算再判断一下 N+1 是不是 04,N+3 的高 4 位是不是 4。通过这样的改动,GPU 就可以通过给定的 N,筛选出有 或者 个前缀 0 的地址。

然后是打分, 这部分代码在 lib.rs中,

            // count total and leading zero bytes
            let mut total = 0;
            let mut leading = 0;
            for (i,&b) in address.iter().enumerate() {
                if b & 240 == 64 {
                    total += 1;
                }
                if b & 15 == 4 {
                    total += 1;
                }
                if b != 0 && leading == 0 {
                    // set leading on finding non-zero byte
                    if b & 240 == 0 {
                        leading = i * 2 + 1;
                    } else {
                        leading = i * 2;
                    }
                }
            }
            let mut tail_is_4444 = 0;
            if address[18] == 68 && address[19] == 68 {
                tail_is_4444 = 1;
            }
            let mut is_not_4444_followed_by_4 = 1;
            if leading % 2 == 0 && (address[leading / 2 + 2] & 240 == 64) {
                is_not_4444_followed_by_4 = 0;
            } else if leading % 2 == 1 && (address[leading / 2 + 2] & 15 == 4) {
                is_not_4444_followed_by_4 = 0;
            }

上面的代码做了这样几个事情:

  • 在统计 4 的总个数 ( total 变量 ) 时,b[i] 都是地址中的两位,因此要通过位运算分别判断高位和低位是不是 4。
  • "前缀 0"(leading 变量 ) 和 "4444 之后不是 4"(is_not_4444_followed_by_4 变量 ) 的判断和之前一样,也需要考虑前缀 0 是奇数个还是偶数个。
  • 结尾的 4444(tail_is_4444变量 ) 最好判断。判断固定位就行。

把上面的汇总就可以得到分数

let score = leading * 10 + 40 + is_not_4444_followed_by_4 * 20 + tail_is_4444 * 20 + total;

程序改好后,使用显卡挖地址的效率非常惊人, 我们的 GTX 4070 显卡每秒可以尝试 18 亿次,而 18 核的 CPU 只能尝试五千万次。启动后是这个样子,参数中的 1 0 2 代表使用 Platfom1 的 Device0,找有两组前缀 0 的地址 (00 00) 或者 (00 00 0)

$ cargo run --release 0x48E516B34A1274f49457b9C6182097796D0498Cb 0x88888414c16cA5793D8D239aBbab2A0e6b358568 0x94d114296a5af85c1fd2dc039cdaa32f1ed4b0fe0868f02d888bfc91feb645d9 1 0 2


Setting up experimental OpenCL miner using device platform 1,device 0...
total runtime: 0:00:7.069246768951416 (190 cycles)                      work size per cycle: 67,108,864
rate: 1800.76 million attempts per second                       total found this run: 5
current search space: 5f4b7ebcxxxxxxxx2f1f5d0e          threshold: 2 leading.
0x88888414c16ca5793d8d239abbab2a0e6b358568493bc5670cdc2802810c8ae0 => 0x000044442a626dC782a8A9038d4c024703DEd20A (4 / 6) 106
0x88888414c16ca5793d8d239abbab2a0e6b358568ba7560035c942e014753d4b6 => 0x00004444bd1648b7792009cD66D33D846AecFB30 (4 / 6) 106
0x88888414c16ca5793d8d239abbab2a0e6b3585689fb0bb1ee3a3320198ea4807 => 0x00004444ED681E1a812b9eF0805e57fd78Fc50A8 (4 / 4) 104
0x88888414c16ca5793d8d239abbab2a0e6b35856865981340dbb8af03a7003c83 => 0x00004444D0852FEcCDa4D0552c0909e92801EE6e (4 / 5) 105
0x88888414c16ca5793d8d239abbab2a0e6b3585686f7109c40f4d2903b8b754aa => 0x00004444bD5DD1a99f749773Bf75128ad79858c8 (4 / 5) 105

对于输出的每一行,

0x88888414c16ca5793d8d239abbab2a0e6b358568493bc5670cdc2802810c8ae0 => 0x000044442a626dC782a8A9038d4c024703DEd20A (4 / 6) 106

  • 0x88888414c16ca5793d8d239abbab2a0e6b358568493bc5670cdc2802810c8ae0 是地址的 salt
  • 0x000044442a626dC782a8A9038d4c024703DEd20A 就是挖出来的地址
  • 4/6 表示有 4 个前缀 0, 地址中一共有 6 个 4
  • 106 是这个地址的打分

一切看起来都很完美,直到我们发现,即使用 GPU,挖一个有 8 个前缀 0 的地址也要很久。理论上说如果要 8 个前缀 0,就需要地址中有 12 位是确定的 ( 前 12 位必须是 00 00 00 00 44 44),计算次数是 ,而我们的算力是 1800.76 兆次 / 秒, 因此找到一个地址理论上需要 1.81 天,看起来还不错,不是吗?

但是我们的算力显然比不过专业玩家,经过一夜的计算,我们还没算出来地址,而网上已经有人提交 9 个前缀 0 的地址了。如果战胜它需要 10 个前缀 0,那就是 天,算了算了不玩了。

最后我们将项目的代码开源[2],并附上了详细的使用方法。如果你感兴趣 ,并且有强大显卡。可以去挑战下。实时结果可以在活动网站[3]看到。

热知识:为什么要搞一个 0x0000000 的地址

这实际上要追溯到 eth 的黄皮书,0x000 会降低 gas 消耗。

能省多少的统计可以参考[4]

不难推测项目方和链上的高频交易对这样的地址都有需求。比如当年的 GasToken 的合约地址就很多 0x0 开头。

此事在 wintermute hack[5] 中亦有记载。




参考资料
[1]

Uniswap v4 挖掘地址的活动: https://blog.uniswap.org/uniswap-v4-address-mining-challenge

[2]

代码开源: https://github.com/32ethers/create2crunch

[3]

活动网站: https://v4-address.uniswap.org/

[4]

参考: https://medium.com/@solidity101/unraveling-the-gas-saving-magic-understanding-addresses-with-leading-zeros-in-solidity-smart-7a853573b116

[5]

wintermute hack: https://www.forbes.com/sites/jeffkauflin/2022/09/20/profanity-may-be-the-cause-of-crypto-trading-firm-wintermutes-160-million-hack/


笔者:Steven Sun,Zelos

点击左下方「阅读原文」/「Read More」,获取更多相关信息



Antalpha Labs 是一个非盈利的 Web3 开发者社区,致力于通过发起和支持开源软件推动 Web3 技术的创新和应用。

官网:https://labs.antalpha.com

Twitter:https://twitter.com/Antalpha_Labs

Youtube:https://www.youtube.com/channel/UCNFowsoGM9OI2NcEP2EFgrw

联系我们:hello.labs@antalpha.com

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

Antalpha Labs
数据请求中
查看更多

推荐专栏

数据请求中
在 App 打开