# 监听以太坊mempool

By [Alex](https://paragraph.com/@alex-2) · 2023-06-25

---

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

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

这一切，靠的是`geth`提供的`API`（不是标准`API` ，是`geth`特有的`API`）。

标准`API`列表：

[https://ethereum.github.io/execution-apis/api-documentation/](https://ethereum.github.io/execution-apis/api-documentation/)

`geth`特有`API` `eth_subscribe`

[https://geth.ethereum.org/docs/interacting-with-geth/rpc/pubsub#newpendingtransactions](https://geth.ethereum.org/docs/interacting-with-geth/rpc/pubsub#newpendingtransactions)

所谓的“监听”，其实就是订阅(`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.0`，`geth`直接就添加了新的`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](https://twitter.com/alexgiantwhale)

---

*Originally published on [Alex](https://paragraph.com/@alex-2/mempool)*
