【使用go开发区块链】之获取链上数据(04)

这篇具有很好参考价值的文章主要介绍了【使用go开发区块链】之获取链上数据(04)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

上一篇文章,我们完成了go连接区块链的操作,本章我们将要完成获取链上数据,并持久化到数据库的功能开发

本系列文章
1、【使用go开发区块链】之获取链上数据(01)
2、【使用go开发区块链】之获取链上数据(02)
3、【使用go开发区块链】之获取链上数据(03)
4、【使用go开发区块链】之获取链上数据(04)

1、获取区块链数据

1.1、通过区块高度获取对应区块信息

在上一章里,我们最后通过下面代码获取到了区块链的最新高度

blockNumber, err := global.EthRpcClient.BlockNumber(context.Background())

接下来我们我们需要将得到的区块高度blockNumber当做入参传入,获取到区块高度对应的区块信息:

lastBlock, err := global.EthRpcClient.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber)))

我们来看一下它的定义

func (b *Block) Transactions() Transactions { return b.transactions }

func (b *Block) Transaction(hash common.Hash) *Transaction {
	for _, transaction := range b.transactions {
		if transaction.Hash() == hash {
			return transaction
		}
	}
	return nil
}
func (b *Block) NumberU64() uint64        { return b.header.Number.Uint64() }
func (b *Block) MixDigest() common.Hash   { return b.header.MixDigest }
func (b *Block) Nonce() uint64            { return binary.BigEndian.Uint64(b.header.Nonce[:]) }
func (b *Block) Bloom() Bloom             { return b.header.Bloom }
func (b *Block) Coinbase() common.Address { return b.header.Coinbase }
func (b *Block) Root() common.Hash        { return b.header.Root }
func (b *Block) ParentHash() common.Hash  { return b.header.ParentHash }
func (b *Block) TxHash() common.Hash      { return b.header.TxHash }
func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash }
func (b *Block) UncleHash() common.Hash   { return b.header.UncleHash }
func (b *Block) Hash() common.Hash {
	if hash := b.hash.Load(); hash != nil {
		return hash.(common.Hash)
	}
	v := b.header.Hash()
	b.hash.Store(v)
	return v
}

可以看到,它提供了很多方法,比如Hash()获取区块Hash、Transactions()获取区块包含的交易等,我们可以根据自己的业务选择存储需要的数据

1.2、解析区块包含的交易数据

1.2.1、解析交易数据

我们通过区块可以获取到区块里包含的交易lastBlock.Transactions(),它实际上是一个Transactions数组,通过range我们可以将它进行遍历:

for _, tx := range block.Transactions()
1.2.1.1、获取交易回执

tx就是该区块包含的每一个交易对象,我们需要通过交易对象的Hash拿到交易回执信息:

receipt, err := global.EthRpcClient.TransactionReceipt(context.Background(), tx.Hash())

receipt结构如下:

type Receipt struct {
	// Consensus fields: These fields are defined by the Yellow Paper
	Type              uint8  `json:"type,omitempty"`
	PostState         []byte `json:"root"`
	Status            uint64 `json:"status"`
	CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
	Bloom             Bloom  `json:"logsBloom"         gencodec:"required"`
	Logs              []*Log `json:"logs"              gencodec:"required"`

	// Implementation fields: These fields are added by geth when processing a transaction.
	TxHash            common.Hash    `json:"transactionHash" gencodec:"required"`
	ContractAddress   common.Address `json:"contractAddress"`
	GasUsed           uint64         `json:"gasUsed" gencodec:"required"`
	EffectiveGasPrice *big.Int       `json:"effectiveGasPrice"`

	// Inclusion information: These fields provide information about the inclusion of the
	// transaction corresponding to this receipt.
	BlockHash        common.Hash `json:"blockHash,omitempty"`
	BlockNumber      *big.Int    `json:"blockNumber,omitempty"`
	TransactionIndex uint        `json:"transactionIndex"`
}

其中有一个定义Logs,它就是我们每个交易里面包含的log事件,同样也是一个数组类型,我们需要把它给解析出来
这里,有些同学可能有疑问,这个log到底是什么东西?我发一张图来说明:
【使用go开发区块链】之获取链上数据(04),使用go获取链上数据,使用go开发区块链,# 区块链/Web3.0,golang,区块链,web3
该图截取自:区块链浏览器数据 的Logs标签

从图中,我们可以看到一个交易中会存在多个log,其实这个log,才是我们最需要抓取的数据,拿监听某个NFT合约Mint事件举例,我们其实就是需要抓取到该NFT合约的Log事件,然后逐个分析是否是Mint事件

1.2.1.1.1、解析Log事件

Log结构体定义:

type Log struct {
	// Consensus fields:
	// address of the contract that generated the event
	Address common.Address `json:"address" gencodec:"required"`
	// list of topics provided by the contract.
	Topics []common.Hash `json:"topics" gencodec:"required"`
	// supplied by the contract, usually ABI-encoded
	Data []byte `json:"data" gencodec:"required"`

	// Derived fields. These fields are filled in by the node
	// but not secured by consensus.
	// block in which the transaction was included
	BlockNumber uint64 `json:"blockNumber"`
	// hash of the transaction
	TxHash common.Hash `json:"transactionHash" gencodec:"required"`
	// index of the transaction in the block
	TxIndex uint `json:"transactionIndex"`
	// hash of the block in which the transaction was included
	BlockHash common.Hash `json:"blockHash"`
	// index of the log in the block
	Index uint `json:"logIndex"`

	// The Removed field is true if this log was reverted due to a chain reorganisation.
	// You must pay attention to this field if you receive logs through a filter query.
	Removed bool `json:"removed"`
}

我们需要的就是Topics[]和Data[],其中Topics[0] 为该方法的keccak256加密后的前4个字节(即为函数选择器)Topics最多包含4个数据,即为声明为 indexed的字段(这块涉及到solidity知识,大家有个印象即可,我会在solidity教程里详细讲解,大家感兴趣可以查看我发布的solidity课程),Data里包含的是剩余的数据

1.2.1.2、处理交易数据
1.2.1.2.1、验证是否是创建合约

如果我们想知道一笔交易是否是创建合约该怎么办呢?其实很简单,在每个交易里有都有一个to字段,如果to字段为空,则代表该交易为创建合约操作。

1.2.1.2.2、验证地址是否是合约地址

我们可以通过下面方法来验证一个地址是否是合约地址:

// 判断一个地址是否是合约地址
func isContractAddress(address string) (bool, error) {
	addr := common.HexToAddress(address)
	code, err := global.EthRpcClient.CodeAt(context.Background(), addr, nil)
	if err != nil {
		return false, err
	}
	return len(code) > 0, nil
}

通过获取指定地址的code来判断,若code长度不为0,则该地址为合约地址

1.3、持久化链上数据

在上面的章节,我们已经讲解了如何获取链上的区块数据,以及如何进行解析,下面我们将要把链上数据持久化到我们的数据库中

1.3.1、创建实体类

1.3.1.1、创建transaction.go

在internal/model目录下创建transaction.go文件,用来存储交易数据:

type Transaction struct {
	Id          uint64 `json:"id" gorm:"primary_key;AUTO_INCREMENT"`
	BlockNumber uint64 `json:"block_number"`
	TxHash      string `json:"tx_hash" gorm:"type:char(66)" `
	From        string `json:"from" gorm:"type:char(42)" `
	To          string `json:"to" gorm:"type:char(42)" `
	Value       string `json:"value" gorm:"type:varchar(256)" `
	Contract    string `json:"contract" gorm:"type:char(42)" `
	Status      uint64 `json:"status"`
	InputData   string `json:"input_data" gorm:"type:varchar(4096)"`
	*gorm.Model
}

func (tx *Transaction) TableName() string {
	return "transactions"
}
func (tx *Transaction) Insert() error {
	if err := global.DBEngine.Create(&tx).Error; err != nil {
		return err
	}
	return nil
}
1.3.1.2、创建event.go

在internal/model目录下创建event.go文件,用来存储事件数据:

type Events struct {
	Id          uint64 `json:"id" gorm:"primary_key;AUTO_INCREMENT" `
	Address     string `json:"address" gorm:"type:char(42)" `
	Data        string `json:"data" gorm:"type:longtext" `
	BlockNumber uint64 `json:"block_number"`
	TxHash      string `json:"tx_hash" gorm:"type:char(66)" `
	TxIndex     uint   `json:"tx_index" `
	BlockHash   string `json:"block_hash" gorm:"type:varchar(256)" `
	LogIndex    uint   `json:"log_index"`
	Removed     bool   `json:"removed"`
	*gorm.Model
}

func (e *Events) TableName() string {
	return "events"
}

func (e *Events) Insert() error {
	if err := global.DBEngine.Create(&e).Error; err != nil {
		return err
	}
	return nil
}

func (e *Events) GetEventByTxHash() (*Events, error) {
	var event Events
	if err := global.DBEngine.Where("tx_hash = ?", e.TxHash).First(&event).Error; err != nil {
		return nil, err
	}
	return &event, nil
}
1.3.1.3、创建topic.go

在internal/model目录下创建topic.go文件,用来存储事件的主题数据:

type Topic struct {
	Id      uint64 `json:"id" gorm:"primary_key;AUTO_INCREMENT" json:"id"`
	EventId uint64 `json:"event_id"`
	Topic   string `json:"topic" gorm:"type:longtext" `
	*gorm.Model
}

func (tc *Topic) TableName() string {
	return "topics"
}

func (tc *Topic) Insert() error {
	if err := global.DBEngine.Create(&tc).Error; err != nil {
		return err
	}
	return nil
}
1.3.1.4、修改MigrateDb方法

将db.go里面的MigrateDb()方法进行修改,如下:

// MigrateDb 初始化数据库表
func MigrateDb() error {
	if err := global.DBEngine.AutoMigrate(&models.Blocks{}, &models.Transaction{}, &models.Events{}, &models.Topic{}); err != nil {
		return err
	}
	return nil
}

1.3.2、准备工作

1.3.2.1、创建block.go

在pkg目录下,新建blockchain目录,然后在blockchain目录下新建block.go文件

1.3.2.2、初始化区块信息

我们查询区块信息的时候,需要一个区块高度参数,在我们项目刚创建的时候,数据库block表是空的,所以我们需要先进行第一个区块信息的初始化工作,在pkg/blockchain/block.go文件新建InitBlock()方法:

// InitBlock 初始化第一个区块数据
func InitBlock() {
	block := &models.Blocks{}
	count := block.Counts()
	if count == 0 {
		lastBlockNumber, err := global.EthRpcClient.BlockNumber(context.Background())
		if err != nil {
			log.Panic("InitBlock - BlockNumber err : ", err)
		}
		lastBlock, err := global.EthRpcClient.BlockByNumber(context.Background(), big.NewInt(int64(lastBlockNumber)))

		if err != nil {
			log.Panic("InitBlock - BlockByNumber err : ", err)
		}
		block.BlockHash = lastBlock.Hash().Hex()
		block.BlockHeight = lastBlock.NumberU64()
		block.LatestBlockHeight = lastBlock.NumberU64()
		block.ParentHash = lastBlock.ParentHash().Hex()
		err = block.Insert()
		if err != nil {
			log.Panic("InitBlock - Insert block err : ", err)
		}
	}
}

以上代码主要做了几个工作:

  1. 先查询数据库中是否已经存在block记录
  2. 若不存在,查询最新区块高度
  3. 通过区块高度查询最新区块信息
  4. 组装数据,存储到数据库

1.3.3、持久化数据

1.3.3.1、新建执行任务方法

pkg/blockchain/block.go文件新建SyncTask()方法,我们希望程序可以间隔一段时间从链上拉取数据,在项目中,我们可以使用 ticker来实现,声明一个 ticker对象,示例中是一秒时间间隔,然后通过chan(通道)取值,进行定时操作:

func SyncTask() {
	ticker := time.NewTicker(time.Second * 1)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			latestBlockNumber, err := global.EthRpcClient.BlockNumber(context.Background())
			if err != nil {
				log.Panic("EthRpcClient.BlockNumber error : ", err)
			}
			var blocks models.Blocks
			latestBlock, err := blocks.GetLatest()
			if err != nil {
				log.Panic("blocks.GetLatest error : ", err)
			}
			if latestBlock.LatestBlockHeight > latestBlockNumber {
				log.Printf("latestBlock.LatestBlockHeight : %v greater than latestBlockNumber : %v \n", latestBlock.LatestBlockHeight, latestBlockNumber)
				continue
			}
			currentBlock, err := global.EthRpcClient.BlockByNumber(context.Background(), big.NewInt(int64(latestBlock.LatestBlockHeight)))
			if err != nil {
				log.Panic("EthRpcClient.BlockByNumber error : ", err)
			}
			log.Printf("get currentBlock blockNumber : %v , blockHash : %v \n", currentBlock.Number(), currentBlock.Hash().Hex())
			err = HandleBlock(currentBlock)
			if err != nil {
				log.Panic("HandleBlock error : ", err)
			}
		}
	}
}

上面代码主要完成操作:

  1. 获取最新区块高度
  2. 从数据库查询最新存储的区块数据
  3. 判断数据库存储的最新区块链高度是否大于查询的最新区块高度
  4. 如果大于则跳出循环不执行后面操作,反之通过数据库存储的最新区块链高度查询区块信息
  5. 通过HandleBlock()方法处理最新区块信息(存储到数据库)
1.3.3.2、处理区块数据

HandleBlock()方法如下:

// HandleBlock 处理区块信息
func HandleBlock(currentBlock *types.Block) error {
	block := &models.Blocks{
		BlockHeight:       currentBlock.NumberU64(),
		BlockHash:         currentBlock.Hash().Hex(),
		ParentHash:        currentBlock.ParentHash().Hex(),
		LatestBlockHeight: currentBlock.NumberU64() + 1,
	}
	err := block.Insert()
	if err != nil {
		return err
	}
	err = HandleTransaction(currentBlock)
	if err != nil {
		return err
	}
	return nil
}

上面代码主要完成工作:

  1. 处理区块数据,存储到数据库
  2. 调用HandleTransaction()方法处理区块里包含的交易数据
1.3.3.3、处理交易数据

pkg/blockchain目录下新建transaction.go文件:

// HandleTransaction 处理交易数据
func HandleTransaction(block *types.Block) error {
	for _, tx := range block.Transactions() {
		receipt, err := global.EthRpcClient.TransactionReceipt(context.Background(), tx.Hash())
		if err != nil {
			log.Error("get transaction fail", "err", err)
		}
		for _, rLog := range receipt.Logs {
			err = HandleTransactionEvent(rLog, receipt.Status)
			if err != nil {
				log.Error("process transaction event fail", "err", err)
			}
		}
		err = ProcessTransaction(tx, block.Number(), receipt.Status)
		if err != nil {
			log.Error("process transaction fail", "err", err)
		}
	}
	return nil
}

func ProcessTransaction(tx *types.Transaction, blockNumber *big.Int, status uint64) error {
	from, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx)
	if err != nil {
		log.Error("Failed to read the sender address", "TxHash", tx.Hash(), "err", err)
		return err
	}
	log.Info("hand transaction", "txHash", tx.Hash().String())
	transaction := &models.Transaction{
		BlockNumber: blockNumber.Uint64(),
		TxHash:      tx.Hash().Hex(),
		From:        from.Hex(),
		Value:       tx.Value().String(),
		Status:      status,
		InputData:   hex.EncodeToString(tx.Data()),
	}
	if tx.To() == nil {
		log.Info("Contract creation found", "Sender", transaction.From, "TxHash", transaction.TxHash)
		toAddress := crypto.CreateAddress(from, tx.Nonce()).Hex()
		transaction.Contract = toAddress
	} else {
		isContract, err := isContractAddress(tx.To().Hex())
		if err != nil {
			return err
		}
		if isContract {
			transaction.Contract = tx.To().Hex()
		} else {
			transaction.To = tx.To().Hex()
		}
	}
	err = transaction.Insert()
	if err != nil {
		log.Error("insert transaction fail", "err", err)
		return err
	}
	return nil
}
1.3.3.4、处理事件数据

pkg/blockchain目录下新建event.go文件:

func HandleTransactionEvent(rLog *types.Log, status uint64) error {
	log.Info("ProcessTransactionEvent", "address", rLog.Address, "data", rLog.Data)
	event := &models.Events{
		Address:     rLog.Address.String(),
		Data:        "",
		BlockNumber: rLog.BlockNumber,
		TxHash:      rLog.TxHash.String(),
		TxIndex:     rLog.TxIndex,
		BlockHash:   rLog.BlockHash.String(),
		LogIndex:    rLog.Index,
		Removed:     rLog.Removed,
	}
	err := event.Insert()
	if err != nil {
		log.Error("event.Insert() fail", "err", err)
		return err
	}
	evt, err := event.GetEventByTxHash()
	if err != nil {
		log.Error("event.GetEventByTxHash() fail", "err", err)
		return err
	}
	log.Info("Topics", "topic", rLog.Topics)
	for _, tp := range rLog.Topics {
		topic := &models.Topic{
			EventId: evt.Id,
			Topic:   tp.String(),
		}
		err := topic.Insert()
		if err != nil {
			log.Error("topic.Insert() fail", "err", err)
			return err
		}
	}
	return nil
}

1.4、验证

1.4.1、修改main.go

修改main()方法:

func main() {
	blockchain.InitBlock()
	blockchain.SyncTask()
}

1.4.2、执行

执行main()方法,正常打印结果如下:【使用go开发区块链】之获取链上数据(04),使用go获取链上数据,使用go开发区块链,# 区块链/Web3.0,golang,区块链,web3

1.4.3、查看数据库信息

1.4.3.1、block表

【使用go开发区块链】之获取链上数据(04),使用go获取链上数据,使用go开发区块链,# 区块链/Web3.0,golang,区块链,web3

1.4.3.2、transaction表

【使用go开发区块链】之获取链上数据(04),使用go获取链上数据,使用go开发区块链,# 区块链/Web3.0,golang,区块链,web3

1.4.3.3、event表

【使用go开发区块链】之获取链上数据(04),使用go获取链上数据,使用go开发区块链,# 区块链/Web3.0,golang,区块链,web3

1.4.3.4、topic表

【使用go开发区块链】之获取链上数据(04),使用go获取链上数据,使用go开发区块链,# 区块链/Web3.0,golang,区块链,web3
数据已经正确的插入到数据库中,说明我们的程序是正常运作的

通过本章的学习,我们完成了1)链上数据拉取,2)链上数据解析,3)链上数据持久化,其实对于区块的数据解析还可以更深入,比如判断是否是ERC721/ERC20合约创建,然后根据实际的业务进行不同的处理,本章就不详细讲解了,如果有想了解的同学,可以私信我,到此,【使用go获取链上数据】系列文章就全部完结了,有任何问题欢迎给我留言

请关注公众号:外柏叁布道者(web3_preacher),回复 “go获取链上数据” 领取完整代码文章来源地址https://www.toymoban.com/news/detail-651939.html

到了这里,关于【使用go开发区块链】之获取链上数据(04)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用Golang Web3库进行区块链开发

    区块链作为一种分布式账本技术,在近年来取得了巨大的发展。而Golang作为一种高效、并发性强的编程语言,被广泛用于区块链开发中。在Golang中,我们可以使用Web3库来与以太坊或其他区块链网络进行交互。 Web3库是一个用于与区块链进行交互的工具库。它提供了一套API,用

    2024年02月07日
    浏览(53)
  • 基础前端使用web3 进行区块链项目开发

    这篇文章不会些区块链的机制算法等一切,只是对前端开发者,如何快速上手进行区块链项目开发做一个简单的引导。 阅读本文之前,需要了解一些简单的区块链知识,能回答以下四个问题就可以阅读本文了。 1、区块链是什么? 2、区块链节点是什么? 3、钱包是什么? 4、

    2024年02月01日
    浏览(56)
  • 【WEB3】如何使用Web3J库开发应用连接到以太坊区块链网络

    ​ Web3j 是一个与以太坊智能合约交互并与以太坊节点集成的 Java 库。它是高度模块化、类型安全和反应式的,专为以太坊上的 Java 和 Android 开发而构建。Web3j 消除了编写自定义集成代码以连接到以太坊区块链网络的开销。 通过 HTTP 和 IPC 实现完整的 Ethereum JSON-RPC客户端 API,

    2024年02月02日
    浏览(59)
  • python 基于 Web3.py 和 Infura 网关采集链上数据

    Web3.py是与Ethereum交互的Python库。功能包括连接到以太坊网络节点、检索数据和向以太坊网络广播数据。 目前以太坊全节点数据量高达数TB,自建本地全节点不太现实,因此一般通过Infura等的网关来实现数据查询。在 Infura 新建一个项目获取API KEY 需求:识别当前用户地址持有的

    2024年02月02日
    浏览(48)
  • Web3 整理React项目 导入Web3 并获取区块链信息

    上文 WEB3 创建React前端Dapp环境并整合solidity项目,融合项目结构便捷前端拿取合约 Abi 我们用react 创建了一个 dapp 项目 并将前后端代码做了个整合 那么 我们就来好好整理一下 我们的前端react的项目结构 我们在 src 目录下创建一个 components 用来存放我们的 大规模组件 然后 在

    2024年02月02日
    浏览(48)
  • 在本地以太坊私链上,使用go调用智能合约,获取事件日志

    完整go项目文件目录      

    2024年02月11日
    浏览(58)
  • java使用web3j,部署智能合约在测试链上,并调用(万字详细教程)

    最近在学区块链相关,想做点自己感兴趣的。网上关于这块部分的坑也比较多,最近也是问了很多行业从事者才慢慢填坑,因此记录下来分享一下。 钱包 :metemask、 solidity编译器 :remix 、 java ide :idea。 智能合约编写的我选择在remix上方便,而且部署的时候不需要自定义gasP

    2024年01月16日
    浏览(58)
  • 使用 Web3.js 连接以太坊节点并查询区块链数据

    Web3.js 是一个用于连接以太坊网络的 JavaScript 库。在本文中,我们将介绍如何使用 Web3.js 来连接以太坊节点,并且查询以太坊区块链上的数据。 1. 安装 Web3.js 首先,我们需要安装 Web3.js。在命令行中,输入以下命令: 2. 连接以太坊节点 在使用 Web3.js 之前,我们需要先连接到以

    2023年04月26日
    浏览(51)
  • web3之链上情报平台Arkham

    官网:https://zh.arkhamintelligence.com/ 官方:https://platform.arkhamintelligence.com/ Arkham 作为区块链分析平台运营,提供参与链上加密市场活动的现实世界实体和个人的数据,该平台的一个核心功能是**“情报赏金”(intel bounties),区块链分析师可提供付费服务,帮助客户以查明(识别

    2024年02月08日
    浏览(41)
  • html通过web3JS 获取当前连接的区块链信息和账号信息

    前面 我们讲了 MetaMask和ganache的配置安装 并用 MetaMask管理ganache的启动的虚拟区块链 那么 我们现在也完全可以写一个网页来做这个东西的管理 您可以先查看文章web3.js获取导入做一个导入了 web3的html文件 首先我们可以来试着 获取 自己当前是在哪个区块的 getBlockNumber 当然 你要

    2024年02月17日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包