惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
H
Help Net Security
量子位
The Cloudflare Blog
博客园 - Franky
博客园 - 聂微东
博客园 - 司徒正美
Last Week in AI
Last Week in AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
罗磊的独立博客
GbyAI
GbyAI
雷峰网
雷峰网
T
The Blog of Author Tim Ferriss
Martin Fowler
Martin Fowler
S
SegmentFault 最新的问题
美团技术团队
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
MongoDB | Blog
MongoDB | Blog

又耳笔记

用AI挣钱之AI建站:二维码生成器工具 用AI挣钱之AI建站:https://markdowntopdf.top Cloudflare Workers实战番外一:用Static Assets托管网站,告别传统方案 Cloudflare Workers实战(五):不止JavaScript,拥抱Python与Rust Cloudflare Workers实战(四):托管和分发静态文件 Cloudflare Workers实战(三):实现认证、重定向与缓存 Cloudflare Workers实战(二):动态修改后端响应 Cloudflare Workers实战(一):随心所欲操作客户端请求 Cloudflare workers不完全指南 使用n8n创作短篇小说 水果风波:信任的代价 用Python将PDF文件转换成图片 rust网络框架Pingora源码阅读3 rust网络框架Pingora源码阅读2 rust网络框架Pingora源码阅读1 Pingora快速入门教程1之总览 Rust模板引擎askama快速入门引擎 使用Loco快速搭建自己的后台系统 用Rust发一封图文并茂的邮件 Rust小项目: 写一个简单的恶意流量阻断器 Rust小项目: 写一个简单的网页爬虫 Rust命令行库Clap快速入门教程 Rust小项目:用Rust写一个端口扫描器 Rust真全栈开发快速入门 rust声明宏快速入门教程 Rust文本处理快速入门教程 RUST web框架axum快速入门教程6之测试 白嫖免费的Rust在线运行时shuttle RUST web框架axum快速入门教程5之中间件 用Rust来做以太坊开发5之事件日志及签名
go-ethereum开发之获取交易中的From字段
About 又耳宁 关于技术与股票的一些零碎想法 · 2023-09-03 · via 又耳笔记

go-ethereum版本: v1.12.2

由于以太坊(ethereum)在不断的发展, 所以协议也在不断的改进,如果你看过《用Go来做以太坊开发》,你会发现其中关于查询交易的那一节中的获取交易的发送者From这部分代码在最新版本中已经失效了,本文主要演示当前以太坊(ethereum)最新版本获取的获取方式。

也因为以太坊(ethereum)在不断的发展,所以其对应的客户端也在不断发展,ethereum的客户端有很多不同的语言实现,这里主要看go语言实现的客户端版本go-ethereum

获取区块

在获取交易前我们需要先获得区块(block)对象


package main

import (
	"context"
	"fmt"
	"github.com/ethereum/go-ethereum/ethclient"
	"log"
	"math/big"
)

func main() {
	ethereumNodeEndpoint := "https://cloudflare-eth.com"
	client, err := ethclient.Dial(ethereumNodeEndpoint)
	if err != nil {
		log.Fatalf("连接以太坊节点[%s]失败: %s", ethereumNodeEndpoint, err)
	}
	ctx := context.TODO()
	latestBlockNumber, err := client.BlockNumber(ctx)
	if err != nil {
		log.Fatal("获取当前最新区块高度失败: ", err)
	}
	fmt.Println("当前最新区块高度:", latestBlockNumber)
	block, err := client.BlockByNumber(ctx, big.NewInt(int64(latestBlockNumber)))
	if err != nil {
		log.Fatal("获取当前最新区块失败: ", err)
	}

	fmt.Println("当前区块hash:", block.Hash().Hex())
	fmt.Println("当前区块包含的交易笔数:", len(block.Transactions()))
}

输出如下:

当前最新区块高度: 18054718
当前区块hash: 0xe1122f17b2d7e9f717bdedb7062cf0a2f44b3199e702754b97f9a5832622a87a
当前区块包含的交易笔数: 226

获取交易

当我们获得了区块(block)对象之后就能获得其包含的交易列表, 这里在上面的代码基础上添加一下内容

tx := block.Transactions()[1]
fmt.Println("交易hash:", tx.Hash().Hex())
fmt.Println("交易的以太数量:", tx.Value().String(), "wei")
fmt.Println("Gas数量:", tx.Gas())
fmt.Println("Gas费:", tx.GasPrice().Uint64())
fmt.Println("接受者的Hash:", tx.To().Hex())

输出如下:

交易hash: 0xb9ac9b6a4eb857cfd410d0f3dc5604c5e52382e490acd00a14f9cf69c59391b0
交易的以太数量: 10000000000000000 wei
Gas数量: 274920
Gas费: 59204457483
接受者的Hash: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D

如果你查看go-ethereum的源代码,你会发现交易对象并没有From字段以及获取的方法,这是因为,交易中的发送者From隐藏在交易的签名当中,而解析签名需要一些额外的计算资源,所以没有直接将From字段解析出来,想要获得From字段,我们需要构造一个签名的解析器。

signer := types.MakeSigner(params.MainnetChainConfig, block.Number(), block.Time())
from, err := signer.Sender(tx)
if err != nil {
    log.Fatal("获取交易的发送者失败:", err)
}
fmt.Println("交易的发送者是:", from.Hex())

输出如下:

交易的发送者是: 0xCe1a934f373fb7355aFa2895d411A80e38384703

如果你仔细看代码的话,会发现第一个参数params.MainnetChainConfig的名字是主网配置, 这个参数会基于区块高度来创建对应类型的Singer, MakeSigner的代码逻辑如下。

func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer {
	var signer Signer
	switch {
	case config.IsCancun(blockNumber, blockTime):
		signer = NewCancunSigner(config.ChainID)
	case config.IsLondon(blockNumber):
		signer = NewLondonSigner(config.ChainID)
	case config.IsBerlin(blockNumber):
		signer = NewEIP2930Signer(config.ChainID)
	case config.IsEIP155(blockNumber):
		signer = NewEIP155Signer(config.ChainID)
	case config.IsHomestead(blockNumber):
		signer = HomesteadSigner{}
	default:
		signer = FrontierSigner{}
	}
	return signer
}

上面的代码大致意思就是根据区块高度创建对应的签名器。

如果是自己的开发网络,那么传入这个参数也不能得正确的解析签名发送者From, 这时候可以创建最新的签名对象,比如types.LatestSigner

完整代码

完整代码如下:

package main

import (
	"context"
	"fmt"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/params"
	"log"
	"math/big"
)

func main() {
	ethereumNodeEndpoint := "https://cloudflare-eth.com"
	client, err := ethclient.Dial(ethereumNodeEndpoint)
	if err != nil {
		log.Fatalf("连接以太坊节点[%s]失败: %s", ethereumNodeEndpoint, err)
	}
	ctx := context.TODO()
	latestBlockNumber, err := client.BlockNumber(ctx)
	if err != nil {
		log.Fatal("获取当前最新区块高度失败: ", err)
	}
	fmt.Println("当前最新区块高度:", latestBlockNumber)
	block, err := client.BlockByNumber(ctx, big.NewInt(int64(latestBlockNumber)))
	if err != nil {
		log.Fatal("获取当前最新区块失败: ", err)
	}

	fmt.Println("当前区块hash:", block.Hash().Hex())
	fmt.Println("当前区块包含的交易笔数:", len(block.Transactions()))

	tx := block.Transactions()[1]
	fmt.Println("交易hash:", tx.Hash().Hex())
	fmt.Println("交易的以太数量:", tx.Value().String(), "wei")
	fmt.Println("Gas数量:", tx.Gas())
	fmt.Println("Gas费:", tx.GasPrice().Uint64())
	fmt.Println("接受者的Hash:", tx.To().Hex())

	signer := types.MakeSigner(params.MainnetChainConfig, block.Number(), block.Time())
	from, err := signer.Sender(tx)
	if err != nil {
		log.Fatal("获取交易的发送者失败:", err)
	}
	fmt.Println("交易的发送者是:", from.Hex())
}

参考文档:

总结

随着以太坊(ethereum)的不断发展,交易的签名格式在不断的改变,为了获取签名中的From字段我们需要使用对应的签名器来解析,当然了,除了从交易中获取From, To的字段,我们还可以从callTracer中获得,callTracer中还可以获得internal transaction,这个以后有机会再说。