监听以太坊mempool
June 25th, 2023

以太坊mempool(内存池)是已经提交、随时可以打包、但还没有成功打包的交易,它随时都可能被打包广播。

默认mempool是公开的,任何人都可以访问。这给MEV提供了非常重要的基础。而监听mempool里的交易,也是非常重要非常基础的操作。

这一切,靠的是geth提供的API(不是标准API ,是geth特有的API)。

标准API列表:

geth特有API eth_subscribe

所谓的“监听”,其实就是订阅(subscribe),利用eth_subscribe接口订阅类型为newPendingTransactions的消息

发送请求

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "eth_subscribe",
  "params": [
    "newPendingTransactions"
  ]
}

响应

{"jsonrpc":"2.0","id":2,"result":"0xc3b33aa549fb9a60e95d21862596617c"}
{
  "jsonrpc":"2.0",
  "method":"eth_subscription",
  "params":{
    "subscription":"0xc3b33aa549fb9a60e95d21862596617c",
    "result":"0xd6fdc5cc41a9959e922f30cb772a9aef46f4daea279307bc5f7024edc4ccd7fa"
  }
}

它会返回一个标示(用于取消订阅),之后如果有新的pending的交易,会把交易hash发送过来。只有交易hash没有细节,其他的参数,需要你自己再查询一次。

Go代码如下

package main

import (
	"context"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethclient/gethclient"
	"github.com/ethereum/go-ethereum/rpc"
	"log"
	"os"
	"os/signal"
	"syscall"
)

func watch() {
	wss := os.Getenv("WSS")

	rc, err := rpc.Dial(wss)
	if err != nil {
		log.Printf("failed to dial: %v", err)
		return
	}
	log.Printf("connected to %s", wss)
	gc := gethclient.New(rc)

	hashes := make(chan common.Hash, 100)
	_, err = gc.SubscribePendingTransactions(context.Background(), hashes)
	if err != nil {
		log.Printf("failed to SubscribePendingTransactions: %v", err)
		return
	}
	log.Print("subscribed pending txs now")
	for {
		select {
		case hash := <-hashes:
			log.Printf("received tx %s", hash)
		}
	}
}

func main() {
	go watch()
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
	<-signalChan
}

当然了,先知道hash其实什么也干不了,还需要再查询一次,拿到具体的数据才行。

好消息是,从v1.11.0geth直接就添加了新的SubscribeFullPendingTransactions接口,可以直接拿到transaction了,简直不要太幸福。

package main

import (
	"context"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient/gethclient"
	"github.com/ethereum/go-ethereum/rpc"
	"log"
	"os"
	"os/signal"
	"syscall"
)

func watch() {
	wss := os.Getenv("WSS")

	rc, err := rpc.Dial(wss)
	if err != nil {
		log.Printf("failed to dial: %v", err)
		return
	}
	log.Printf("connected to %s", wss)
	gc := gethclient.New(rc)

	transactions := make(chan *types.Transaction, 100)
	_, err = gc.SubscribeFullPendingTransactions(context.Background(), transactions)
	if err != nil {
		log.Printf("failed to SubscribePendingTransactions: %v", err)
		return
	}
	log.Print("subscribed pending txs now")
	for {
		select {
		case transaction := <-transactions:
			// 这里的transaction是完整数据,可以直接使用
			txBytes, err := transaction.MarshalJSON()
			if err != nil {
				continue
			}
			log.Printf("received tx %s", string(txBytes))
		}
	}
}

func main() {
	go watch()
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
	<-signalChan
}

打印的transaction内容是如下格式

{
  "type": "0x2",
  "chainId": "0x14a33",
  "nonce": "0x5e1",
  "to": "0x290b54a504a3b0cb21888e3e405afc1b2946598c",
  "gas": "0xdc2d",
  "gasPrice": null,
  "maxPriorityFeePerGas": "0x59682f00",
  "maxFeePerGas": "0x59682f64",
  "value": "0x0",
  "input": "0xa9059cbb000000000000000000000000f3902d46ffa3730f4733b19e5fca5acd8057316a0000000000000000000000000000000000000000000000000de0b6b3a7640000",
  "accessList": [],
  "v": "0x1",
  "r": "0x5783d7fc6ac88e39593de34eff58b059d2bb6045227eea277969a0c4778ec63",
  "s": "0x591ac696363bcaa4943b30ab680c8a7df58d27dd1b28f1a90e0345aaca0cd0b4",
  "hash": "0xb0d0b538207efc9da6daf97b80e1e5e6c3a18a8a7aa45c84f413af3b0ea2c89d"
}

可见是签过名直接可以用的。

祝朋友们玩得开心,可以加我推特交流 @alexgiantwhale

Subscribe to Alex
Receive the latest updates directly to your inbox.
Nft graphic
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from Alex

Skeleton

Skeleton

Skeleton