(钱包开发) 使用 Python 开发 BNB 钱包完成教程

一. BNB 钱包相关资料

  • github: https://github.com/binance-chain

  • 官方文档: https://docs.binance.org/

  • 电报群: https://t.me/BinanceDEXchangeCN

  • BNB 社区:https://community.binance.org

  • BNB 区块链浏览器:https://explorer.binance.org/

二.币安主链介绍

Binance Chain是Binance及其社区开发的区块链软件系统。Binance DEX指的是在Binance Chain之上开发的去中心化交易所。币安的目标是创建一个以去中心化的方式发布和交换数字资产的替代市场。币安链的底层技术是基于目前比较火的项目 COSMOS 开发的,关于 COSMOS,在后面的课程中我们将会详细介绍。币安链网络由去中心化节点组成,在币安链上几乎可以即时完成交易,出块时间只需1秒。其速度高于其他区块链。关于币安链的介绍,这里不多说了,网上有很多资料。

三.币安主链节点搭建

1. 搭建全节点

Binance Chain的全节点是见证,它观察到共识消息,从数据种子节点下载块并执行业务逻辑以实现作为验证器节点(和其他完整节点)的一致状态。完整节点还通过接受来自其他节点的事务,然后将它们中继到核心Binance网络来帮助网络。

支持的平台:Mac OS X, Windows 和 Linux.

系统需求,计算机硬件必须满足以下节点要求:

  • 运行最新版本的Mac OS X,Windows或Linux的台式机或笔记本电脑硬件。

  • 500 GB的可用磁盘空间,最低读/写速度为100 MB / s。

  • 4核CPU和8千兆字节的内存(RAM)。

  • 宽带互联网连接,上传/下载速度至少为每秒1兆字节

  • 完整节点必须每24小时运行至少4小时才能赶上Binance Chain更多时间会更好,连续运行您的节点以获得最佳效果。

搭建完整节点

1.安装 git lfs

Git大文件存储(LFS)用Git中的文本指针替换大型文件,如音频样本,视频,数据集和图形,同时将文件内容存储在远程服务器(如GitHub.com或GitHub Enterprise)上。

请到这个界面去下载安装lfs:https://git-lfs.github.com/

2.使用 lfs 下载二进制文件

git lfs clone https://github.com/binance-chain/node-binary.git


最新版本的完整节点版本的信息:

https://github.com/binance-chain/node-binary/blob/master/fullnode/Changelog.md

根据您要加入的网络转到目录。在以下命令中使用testnet或prod替换网络变量:

cd node-binary/fullnode/{network}/{version}


3.初始 home 目录

首先,您需要为Binance Chain选择一个主文件夹$BNCHOME(即〜/.bnbchaind)。您可以通过以下方式设置:

将app.toml,config.toml和genesis.json从node-binary/fullnode/{network}/{version}/config /放入$ BNCHOME/config

4.添加种子节点

要使整个节点起作用,它必须连接到一个或多个已知节点才能加入Binance Chain。

有几个着名的种子节点在网络中向新加入的完整节点提供已知节点地址。

它们已经在node-binary/fullnode/{network}/{version}/config/config.toml文件中。

您还可以通过一个简单的python脚本获取种子信息(根据您使用的网络替换域名):

import requests, json
d = requests.get('https://dex.binance.org/api/v1/peers').text # replace dex.binance.org with testnet-dex.binance.org for testnet
l = json.loads(d)
seeds = ",".join([ (seed["id"]+"@"+seed["original_listen_addr"]) for seed in l if seed["accelerated"] == False])print (seeds)


如果您想添加种子节点,请随时使用之前请求中返回的种子节点信息编辑$BNCHOME/config/config.yaml的字段种子。

  1. 额外的配置

  2. 日志:日志文件位于home-启动bnbchaind时指定的目录。

  3. 最新的日志文件是bnc.log。该过程将每隔一小时创建一个新的日志文件。

  4. 为确保您有足够的磁盘空间来保留日志文件,我们强烈建议您通过更改$BNCHOME/config/app.toml中的logFileRoot选项来更改日志位置。

  5. 服务端口:RPC服务侦听端口27147,P2P服务默认侦听端口27146。

  6. 除非完整节点必须侦听其他端口,否则请确保在启动完整节点之前打开这两个端口。

  7. 存储:所有状态和块数据将存储在$BNCHOME/data下,不要删除或编辑任何这些文件。

6.启动节点

根据平台启动整个节点。在以下命令中用mac windows或linux替换platform变量:

./{{platform}}/bnbchaind start --home $BNCHOME --pruning breathe &

只有在赶上Binance Chain之后,整个节点才能正确处理请求。

7.同步数据

您可以通过两种方式与区块链网络中的其他对等方同步:快速同步状态同步。

这两种方法可以一起使用。

快速同步数据:

  • 与其他数据种子节点同步的默认方式是快速同步。

  • 在快速同步中,您需要从同级中下载所有块并在每个块中执行所有事务。

  • 同步速度约为20块/秒,比状态同步慢。

配置位于$BNCHOME/config/config.toml中:

fast_sync必须设置true
state_sync_reactor 可以设置为 false或者true
state_sync 可以设置为 false或者true

状态同步:

状态同步将使整个节点的应用程序状态保持最新而不下载所有块。同步速度比快速同步快。但是,您需要为整个节点分配超过16 GB的内存才能使此功能正常工作。

配置位于 $BNCHOME/config/config.toml中:

 state_sync_reactor必须设置为true
state_sync 必须设置为true
recv_rate必须设置到102428800
ping_interval 建议设置到10m30s
pong_timeout建议设置到450s

状态同步可以在短时间内帮助fullnode与其他对等体处于相同状态(根据我们的测试,binance链testnet中的一个月~800M数据库快照可以在大约45分钟内同步),以便您可以接收最新的块/事务和查询 订单簿,帐户余额等的最新状态。但状态同步不会在状态同步高度之前下载历史块,如果您启动具有状态同步的节点并且它在高度10000处同步,则您的本地数据库将仅在高度10000之后具有块。

如果已经启动了完整节点,建议的方法是在启用状态同步之前删除(备份后)$BNCHOME/data目录和$BNCHOME/config/priv_validator_key.json。

8.监控同步过程

您可以多次验证curl localhost:27147/status是否完成状态同步,并查看latest_block_height是否响应增加。

 "sync_info": {
...
"latest_block_height": "878092",
"latest_block_time": "2019-04-15T00:01:22.610803768Z",
...
}

如果状态同步未成功,请在下次启动完整节点之前重复删除$BNCHOME/data目录和$BNCHOME/config/priv_validator_key.json,以防数据不一致。

一旦状态同步成功,稍后的全节点重启将不再进行状态同步(如果本地块不连续)。

但是如果你确实想要再次进行状态同步(不要在意上一次停止和最新状态同步快照之间有丢失的块)并且你想保留已经同步的块,你可以删除$BNCHOME/data/STATESYNC.LOCK。

例如,您在1月1日开始您的完整节点,状态同步在高度10000,并在一段时间后关闭它在2月10日的高度22000。

现在它的3月1日,最新的可同步块高度为50000,你不关心22000和50000之间的块,你可以在启动你的节点之前删除$BNCHOME/data/STATESYNC.LOCK。

然后,整个节点将从高度50000继续状态同步。

关闭state_sync_reactor和state_sync可以在成功状态同步后保存内存。

8.更新全节点

在大多数情况下,请下载新的二进制文件并替换它,然后重新启动整个节点。在特殊情况下,您可能必须为不兼容的版本(硬分叉)执行额外的步骤。

9.监控

默认情况下,Prometheus在端口28660上启用,端点为/metrics。

2.搭建轻节点

Light客户端是一个程序,它连接到一个完整的节点,以帮助用户以安全和分散的方式访问Binance链并与之交互,而无需同步完整的区块链。

轻客户端与完整节点

  • Light客户端不存储块或状态,这样它需要更少的磁盘空间(50兆就足够了)。

  • Light客户端不加入p2p网络,并且在空闲时不会产生任何网络成本。网络开销取决于轻客户端同时处理多少请求。

  • 轻客户端不重放链的状态,因此空闲时没有CPU成本。CPU成本还取决于轻客户端同时处理的请求数量。

  • Light客户端比完整节点更快,即使它落后于核心网络几个月。只需几秒钟即可赶上核心网络。

平台和系统需求

平台:

  • 支持在Mac OS X,Windows和Linux上运行轻量级客户端节点。

  • 轻客户端很快就会开源,之后您可以交叉编译轻客户端二进制文件并在其他平台上运行它。

要求:

  • 50兆字节的可用磁盘空间。

  • 2个CPU核心,50兆字节的内存(RAM)。

运行一个轻客户端

1.下载节点

git clone https://github.com/binance-chain/node-binary.git

根据您要加入的网络转到目录。在以下命令中使用testnet或prod替换网络变量:

cd node-binary/lightd/{network}/{version}

帮助信息

    ./lightd --help
This node will run a secure proxy to a binance rpc server.

All calls that can be tracked back to a block header by a proof
   will be verified before passing them back to the caller. Other that
   that it will present the same interface as a full binance node,
   just with added trust and running locally.

Usage:
     lite [flags]

Flags:
--cache-size int             Specify the memory trust store cache size (default 10)
--chain-id string            Specify the binance chain ID (default "bnbchain")
-h, --help                       help for lite
--home-dir string            Specify the home directory (default ".binance-lite")
--laddr string               Serve the proxy on the given address (default "tcp://localhost:27147")
--max-open-connections int   Maximum number of simultaneous connections (including WebSocket). (default 900)
--node string                Connect to a binance node at this address (default "tcp://localhost:27147")

您可以指定上面的所有参数。

根据平台启动轻客户端节点。在以下命令中用mac,windows或linux替换platform变量:

    ./{{platform}}/lightd --chain-id "{chain-id}" --node tcp://{full node addr}:80 > node.log  &
  • 启动轻客户端节点有两个必需参数:chain id和full node addr。

  • 要加入的网络的链ID。

  • 您可以在测试网络中的genesis文件或prod网络中的genesis文件中找到链ID。

  • 完整节点地址字段可以是您已部署的任何完整节点的地址。

  • 您可以参考运行Binance Chain完整节点以获取更多详细信息。

我们提供了一堆完整的节点,您可以连接到mainnet和testnet。你可以通过一个简单的python脚本获取完整的节点信息(根据不同的网络注意替换域):

import requests, json
d = requests.get('https://dex.binance.org/api/v1/peers').text # replace dex.binance.org with testnet-dex.binance.org for testnet
l = json.loads(d)
seeds = ",".join([ (seed["id"]+"@"+seed["original_listen_addr"]) for seed in l if seed["accelerated"] == False])print (seeds)


主网示例:

./lightd --chain-id "Binance-Chain-Tigris" --node tcp://dataseed1.binance.org:80 > node.log  &

测试网示例:
./lightd --chain-id "Binance-Chain-Nile" --node tcp://data-seed-pre-0-s1.binance.org:80 > node.log &

四. 使用开发节点

1.获取开放节点的 API 调用(直接复制到浏览器即可)

https://dex.binance.org/api/v1/peers

返回结果:

[{"accelerated":true,"access_addr":"https://dex.binance.org:443","capabilities":["qs","ap","ws"],"id":"gateway-ingress","listen_addr":"https://dex.binance.org:443","moniker":"gateway-ingress","network":"gateway","original_listen_addr":"","stream_addr":"wss://dex.binance.org","version":"1.0.0"},{"accelerated":false,"access_addr":"https://seed1.ciscox.io:443","capabilities":["node"],"id":"bb721fe089e83d42bcb8f629f8efe0e4af888fde","listen_addr":"https://seed1.ciscox.io:443","moniker":"seed","network":"Binance-Chain-Tigris","original_listen_addr":"52.197.88.253:27146","stream_addr":"","version":"0.31.5"},{"accelerated":false,"access_addr":"https://dataseed1.binance.org:443","capabilities":["node"],"id":"782303c9060d46211225662fdd1dd411c638263a","listen_addr":"https://dataseed1.binance.org:443","moniker":"data-seed-0","network":"Binance-Chain-Tigris","original_listen_addr":"52.197.243.252:27146","stream_addr":"","version":"0.31.5"},{"accelerated":false,"access_addr":"https://dataseed2.binance.org:443","capabilities":["node"],"id":"e2acf608686f830386e8648d6fc2f50c3316aa88","listen_addr":"https://dataseed2.binance.org:443","moniker":"data-seed-1","network":"Binance-Chain-Tigris","original_listen_addr":"13.113.112.169:27146","stream_addr":"","version":"0.31.5"},{"accelerated":false,"access_addr":"https://dataseed3.binance.org:443","capabilities":["node"],"id":"b7affdf084115fd7e7cdbc04686ef8c218e1801c","listen_addr":"https://dataseed3.binance.org:443","moniker":"data-seed-2","network":"Binance-Chain-Tigris","original_listen_addr":"52.199.222.255:27146","stream_addr":"","version":"0.31.5"},{"accelerated":false,"access_addr":"https://dataseed4.binance.org:443","capabilities":["node"],"id":"0fd066de431d13e5613aba53b3ed25c5a7a0b67c","listen_addr":"https://dataseed4.binance.org:443","moniker":"data-seed-3","network":"Binance-Chain-Tigris","original_listen_addr":"13.114.123.99:27146","stream_addr":"","version":"0.31.5"},{"accelerated":false,"access_addr":"https://dataseed5.binance.org:443","capabilities":["node"],"id":"98579505572cc85488caa403bebb62e5b61d68e0","listen_addr":"https://dataseed5.binance.org:443","moniker":"data-seed-4","network":"Binance-Chain-Tigris","original_listen_addr":"52.68.173.59:27146","stream_addr":"","version":"0.31.5"}]

2.查看节点 API

https://seed1.ciscox.io/

返回结果:

Available endpoints:Endpoints that require arguments://seed1.ciscox.io/abci_info?//seed1.ciscox.io/abci_query?path=_&data=_&height=_&prove=_//seed1.ciscox.io/block?height=_//seed1.ciscox.io/block_by_hash?hash=_//seed1.ciscox.io/block_results?height=_//seed1.ciscox.io/blockchain?minHeight=_&maxHeight=_//seed1.ciscox.io/broadcast_tx_async?tx=_//seed1.ciscox.io/broadcast_tx_commit?tx=_//seed1.ciscox.io/broadcast_tx_sync?tx=_//seed1.ciscox.io/commit?height=_//seed1.ciscox.io/consensus_params?height=_//seed1.ciscox.io/consensus_state?//seed1.ciscox.io/dump_consensus_state?//seed1.ciscox.io/genesis?//seed1.ciscox.io/health?//seed1.ciscox.io/net_info?//seed1.ciscox.io/num_unconfirmed_txs?//seed1.ciscox.io/status?//seed1.ciscox.io/subscribe?query=_//seed1.ciscox.io/tx?hash=_&prove=_//seed1.ciscox.io/tx_search?query=_&prove=_&page=_&per_page=_//seed1.ciscox.io/unconfirmed_txs?limit=_//seed1.ciscox.io/unsubscribe?query=_//seed1.ciscox.io/unsubscribe_all?//seed1.ciscox.io/validators?height=_

五. Python 开发 BNB 钱包

1. 离线生成地址和私钥

import binascii
from binance_chain.wallet import Walletfrom binance_chain.environment import BinanceEnvironment

product_env = BinanceEnvironment.get_production_env()
wallet = Wallet.create_random_wallet(env=product_env)print(wallet.address)print(wallet.private_key)print(binascii.hexlify(wallet.public_key).decode())


此处生成的公钥是经过 bech32 编码之后的公钥,原始的公钥不是这样的。

2. BNB 离线签名

2.1.获取账户信息

from binance_chain.http import HttpApiClientfrom binance_chain.environment import BinanceEnvironment
testnet_env = BinanceEnvironment.get_production_env()
client = HttpApiClient(env=testnet_env)
prod_client = HttpApiClient()

account = client.get_account('bnb1acecavtwz6s4fat6540a5dz3vcq25lrsccxapp')print(account)

返回结果

{'account_number': 198155, 'address': 'bnb1acecavtwz6s4fat6540a5dz3vcq25lrsccxapp', 'balances': [{'free': '0.45147610', 'frozen': '0.00000000', 'locked': '0.00000000', 'symbol': 'BNB'}], 'flags': 0, 'public_key': [3, 142, 211, 241, 139, 162, 23, 200, 94, 195, 109, 117, 254, 214, 166, 254, 154, 61, 151, 18, 33, 59, 253, 59, 34, 38, 241, 210, 157, 129, 125, 240, 25], 'sequence': 26}

account_number:用户号,账户唯一标识;balances 中有三个参数,free 可用余额,frozen:冻结余额,locked:锁定余额,public_key:公钥消息;sequence:交易序号。

2.2.离线签名

from binance_chain.messages import TransferMsgfrom binance_chain.wallet import Wallet

wallet = Wallet('私钥')
wallet._account_number=198155
wallet._sequence=26
wallet._chain_id='Binance-Chain-Tigris'
transfer_msg = TransferMsg(
   wallet=wallet,
   symbol='BNB',
   amount=0.01,
   to_address='bnb1zk7fmzs5yfq9v2wunpfmkhtrjjnnen09af4kpg',
   memo='91460333')
data = transfer_msg.to_hex_data().decode()print(data)

私钥就是填写离线签名的私钥,chain_id实际上就是 net_work, 可以通过 https://seed1.4leapbnb.com/status? 获取到,symbol:转账币种;amount:转账金额,to_address:转入地址,memo:交易标识。

执行结果:

cc01f0625dee0a4a2a2c87fa0a210a14ee338eb16e16a154f57aa55fda34516600aa7c7012090a03424e4210c0843d12210a1415bc9d8a1422405629dc9853bb5d6394a73ccde512090a03424e4210c0843d12700a26eb5ae98721038ed3f18ba217c85ec36d75fed6a6fe9a3d9712213bfd3b2226f1d29d817df01912405078b723c9ff2af8396d86ebb67072fe70608c99ac0cf57a4746091527d1c8865800ea8fe416930b6ba8280aa5d133de81f45aae2f0ee1bf7118b855f1c2d888188b8c0c201a1a083931343630333333

在 hex 串签名加 0x 就可以调用广播交易的接口将交易广播出去。

调用示范:

https://seed1.4leapbnb.com/broadcast_tx_sync?tx=0xcc01f0625dee0a4a2a2c87fa0a210a14ee338eb16e16a154f57aa55fda34516600aa7c7012090a03424e4210c0843d12210a1415bc9d8a1422405629dc9853bb5d6394a73ccde512090a03424e4210c0843d12700a26eb5ae98721038ed3f18ba217c85ec36d75fed6a6fe9a3d9712213bfd3b2226f1d29d817df01912405078b723c9ff2af8396d86ebb67072fe70608c99ac0cf57a4746091527d1c8865800ea8fe416930b6ba8280aa5d133de81f45aae2f0ee1bf7118b855f1c2d888188b8c0c201a1a083931343630333333

返回结果:

{
"jsonrpc": "2.0",
"id": "",
"result": {
"code": 0,
"data": "",
"log": "Msg 0: ",
"hash": "93FA6C8BCBABDD96A1BC9033C46C1DD9CFB5346476DE002C7CC0541228A6C94E"
}}

使用返回的交易 hash 可以在区块链上查到交易信息。

3. BNB 充值开发

3.1.获取目前区块链中最高块

https://seed1.longevito.io/abci_info

返回结果:

{
"jsonrpc": "2.0",
"id": "",
"result": {
"response": {
"data": "BNBChain",
"last_block_height": "31421321",
"last_block_app_hash": "rYfnJye7f5zQSnG8XSMtIx/Jjyz35c9imm1cAm/7+Gg="
}
}}

3.2.获取块的交易数据

https://seed1.longevito.io/block?height=31414371

返回的结果里面,data 下面的 txs 就是我们需要交易数据

{
"jsonrpc": "2.0",
"id": "",
"result": {
"block_meta": {
"block_id": {
"hash": "E12A32FA39FA0B2118904A8E2E7B923C6ED8F50A732C2899FAE332BD6FA8E6DA",
"parts": {
"total": "1",
"hash": "BEB8C6500817636FA928EEB643C1C7ECFA496B71F6ECD8E7F303E736047E9876"
}
},
"header": {
"version": {
"block": "10",
"app": "0"
},
"chain_id": "Binance-Chain-Tigris",
"height": "31414371",
"time": "2019-09-01T08:46:15.793361866Z",
"num_txs": "3",
"total_txs": "15893492",
"last_block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"last_commit_hash": "AB7F187CB7B79A13C250FDA5BE22BDD1707D50140E11EDDACCC6E26668AED257",
"data_hash": "095221A99C5BAB44DC9DD02DB7A24E9A9EF94B1A09B8638B5CADD99725E7B4AC",
"validators_hash": "43C53A50D8653EF8CF1E5716DA68120FB51B636DC6D111EC3277B098ECD42D49",
"next_validators_hash": "43C53A50D8653EF8CF1E5716DA68120FB51B636DC6D111EC3277B098ECD42D49",
"consensus_hash": "294D8FBD0B94B767A7EBA9840F299A3586DA7FE6B5DEAD3B7EECBA193C400F93",
"app_hash": "0C6B59F034A88A9151306F418EC9444C91D00560CA2D6102B0129383CB2BDEC9",
"last_results_hash": "FB8B8D20752B4ABA4AC8D6048FBB4D00C0689D5FB4AABC6B118A1A1385987822",
"evidence_hash": "",
"proposer_address": "A9157B3FA6EB4C1E396B9B746E95327A07DC42E5"
}
},
"block": {
"header": {
"version": {
"block": "10",
"app": "0"
},
"chain_id": "Binance-Chain-Tigris",
"height": "31414371",
"time": "2019-09-01T08:46:15.793361866Z",
"num_txs": "3",
"total_txs": "15893492",
"last_block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"last_commit_hash": "AB7F187CB7B79A13C250FDA5BE22BDD1707D50140E11EDDACCC6E26668AED257",
"data_hash": "095221A99C5BAB44DC9DD02DB7A24E9A9EF94B1A09B8638B5CADD99725E7B4AC",
"validators_hash": "43C53A50D8653EF8CF1E5716DA68120FB51B636DC6D111EC3277B098ECD42D49",
"next_validators_hash": "43C53A50D8653EF8CF1E5716DA68120FB51B636DC6D111EC3277B098ECD42D49",
"consensus_hash": "294D8FBD0B94B767A7EBA9840F299A3586DA7FE6B5DEAD3B7EECBA193C400F93",
"app_hash": "0C6B59F034A88A9151306F418EC9444C91D00560CA2D6102B0129383CB2BDEC9",
"last_results_hash": "FB8B8D20752B4ABA4AC8D6048FBB4D00C0689D5FB4AABC6B118A1A1385987822",
"evidence_hash": "",
"proposer_address": "A9157B3FA6EB4C1E396B9B746E95327A07DC42E5"
},
"data": {
"txs": [
"4wHwYl3uCmnObcBDChTXOqeZ4ufVDUOiSjB/nN6XYQ9bBhIuRDczQUE3OTlFMkU3RDUwRDQzQTI0QTMwN0Y5Q0RFOTc2MTBGNUIwNi0zODc0NRoMQ0JJWC0zQzlfQk5CIAIoATCk9wI4gLyft9YFQAMScgom61rphyECSEG6gwzsC4XxyvLZy2IwjGOxgSLBzAkd0ZIo464A1uQSQP3jvW9iqpjMTD2RvKnRGhSKo4sp6Ho621lMZhyc5i6+AYNRoWcd6wSedgD+8NGlKuMZAcQ92dGbLtfHCVJD8scYgrgNINiuAg==",
"5AHwYl3uCmrObcBDChQ32Tg+atmv72xdgGaro6yox12fORIvMzdEOTM4M0U2QUQ5QUZFRjZDNUQ4MDY2QUJBM0FDQThDNzVEOUYzOS0xOTAyMDMaDERFRkktRkE1X0JOQiACKAEw7/MSOICc9LyLAkABEnIKJuta6YchA72zQwwyLMnAEN9XXcZZMxXutUJODGLctr9d5ytfKpY2EkCejmOm6NlMVSeG2rL/tZUXTNrQUEG5vWmG2y2yWpc163x87X8Nmv0yl8/Jjw4ucgGm9leAeoIOwLWTXwvS8TgeGMrrDCD6zQs=",
"zAHwYl3uCkoqLIf6CiEKFO4zjrFuFqFU9XqlX9o0UWYAqnxwEgkKA0JOQhDAhD0SIQoUFbydihQiQFYp3JhTu11jlKc8zeUSCQoDQk5CEMCEPRJwCibrWumHIQOO0/GLohfIXsNtdf7Wpv6aPZcSITv9OyIm8dKdgX3wGRJAUHi3I8n/Kvg5bYbrtnBy/nBgjJmsDPV6R0YJFSfRyIZYAOqP5BaTC2uoKAql0TPegfRari8O4b9xGLhV8cLYiBiLjAwgGhoIOTE0NjAzMzM="
]
},
"evidence": {
"evidence": null
},
"last_commit": {
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"precommits": [
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.822594585Z",
"validator_address": "1175946A48EAA473868A0A6F52E6C66CCAF472EA",
"validator_index": "0",
"signature": "kxnF9PywCABQ5O+jZXRGxK3HfsF5JzLQDtGCq15bNZ9DHb7hL52xd9gmDfFaUM9uiduZHHxxJNUuVGhTJzhmCQ=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.793118248Z",
"validator_address": "14CFCE69B645F3F88BAF08EA5B77FA521E4480F9",
"validator_index": "1",
"signature": "CXeXZsxVVRqox5Q7lIYpQK9Sxfha+GLdiaws2kSu7AcNdQDZZYZwslMZsxpckc63UnFEQS8fM1qqMS3SOceGAw=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.730191163Z",
"validator_address": "17B42E8F284D3CA0E420262F89CD76C749BB12C9",
"validator_index": "2",
"signature": "iN0uOoqLkF60yoMl3bIS9AURSQG+NbMRt9CxKBPDGc013725Loy+YlXUlL6edxpCmrolzeGMdUfU07ZyPCt5BQ=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.794008017Z",
"validator_address": "3CD4AABABDDEB7ABFEA9618732E331077A861D2B",
"validator_index": "3",
"signature": "CQsBZs/zOkrx1/1d9P6oelpk4MC1/V68RY4Vx+zbu976SV2HUiq1OeHaEBRdza2H8GlUzllCGAr64DmxBRkUBA=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.731041077Z",
"validator_address": "414FB3BBA216AF84C47E07D6EBAA2DCFC3563A2F",
"validator_index": "4",
"signature": "otb3Mdcmv080pPrbDmh5E9HdOPGRnbZmt/VGtAvFYRikIQQEYeDFrCsdmsZuKmY/ObvK9+0osxmvK7VsJKlvDQ=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.796010477Z",
"validator_address": "71F253E6FEA9EDD4B4753F5483549FE4F0F3A21C",
"validator_index": "5",
"signature": "eyz/MsPSd8rvG1jAgENYDJbuytGOsVNf3zwTnBChlu8lq2XbjKAJ6ySyLxkTyqANuS4CaK6ryNySQipy9iCWCw=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.820191215Z",
"validator_address": "7235EF143D20FC0ABC427615D83014BB02D7C06C",
"validator_index": "6",
"signature": "sSIrKa05CmK38r2wZHfAoEl3n+CqGkDEZhhM2NVzPzPnlZvrZauZ5L6R83cVOW8ltHFeZVOqcs3IPZKNvuChDA=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.793361866Z",
"validator_address": "A71E5CD078B8C5C7B1AF88BCE84DD70B0557D93E",
"validator_index": "7",
"signature": "0oZkpi0HqIRXNTEJRm2dwX1/uyliGtnJR1wi08LyzDIuqRCaPBNxkG44ETfs5wmVwwguySJycAja8jqidi3KBQ=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.730106304Z",
"validator_address": "A9157B3FA6EB4C1E396B9B746E95327A07DC42E5",
"validator_index": "8",
"signature": "92wo7oveuaN9yD+gp8FU/rNn0QRAcB4exNzjpIFspWNMGX2CP1bFqDpPDMalsxYecfig9BfO5YWVLX2dwb/7AA=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.730232822Z",
"validator_address": "B0FBB52FF7EE93CC476DFE6B74FA1FC88584F30D",
"validator_index": "9",
"signature": "PAckwywYH/par9i9hDPY1blT/K8eQMlzflXMyFGFJoQ/7zloAsg7hbDxxSDzwewoTTep3NbDrelucglmmhDxDA=="
},
{
"type": 2,
"height": "31414370",
"round": "0",
"block_id": {
"hash": "22C47D1820D5AC42F3E5D1340B3BAFBA5883A254AB5AE51A8D76030662108881",
"parts": {
"total": "1",
"hash": "18FA03FDC0A989684DF3A3034E4E610A620E127740AB8768261698BB4278A16F"
}
},
"timestamp": "2019-09-01T08:46:15.822850936Z",
"validator_address": "B7707D9F593C62E85BB9E1A2366D12A97CD5DFF2",
"validator_index": "10",
"signature": "scBaSTvW8ZJyksHVXwNwLKnTcAEZ7EIh+wueidCkN0B+mv6ixvDOCcYmkL+V0ht+t0T6RJQ6lYJw/yGvZcWTCQ=="
}
]
}
}
}}

3.3.使用 python 代码实现解析交易

import logging
import base64
import binascii
from io import BytesIOimport typing

from binance_chain.http import HttpApiClientfrom binance_chain.environment import BinanceEnvironmentfrom binance_chain.node_rpc.http import HttpRpcClientfrom binance_chain import messages
from binance_chain.protobuf import dex_pb2
from binance_chain.utils import segwit_addr


def extract_send_msg_addresses(send_msg: dex_pb2.Send) -> typing.Tuple[str]:
   from_address = segwit_addr.encode('bnb', send_msg.inputs[0].address)
   to_address = segwit_addr.encode('bnb', send_msg.outputs[0].address)
return from_address, to_address

def is_bnb_transaction(send_msg: dex_pb2.Send):
for inp in send_msg.inputs:
for coin in inp.coins:
if coin.denom != 'BNB':
return False
for out in send_msg.outputs:
for coin in out.coins:
if coin.denom != 'BNB':
return False
return Truedef parse_msg( msg_bytes: bytes) -> dex_pb2.StdTx:
   msg_type = msg_bytes[:4]
   msg_class = amino_msg_type_to_class(msg_type)
   msg_pb2 = msg_class().FromString(msg_bytes[4:])
return msg_pb2

def amino_msg_type_to_class(msg_type: bytes):
return {
           messages.NewOrderMsg.AMINO_MESSAGE_TYPE: dex_pb2.NewOrder,
           messages.CancelOrderMsg.AMINO_MESSAGE_TYPE: dex_pb2.CancelOrder,
           messages.FreezeMsg.AMINO_MESSAGE_TYPE: dex_pb2.TokenFreeze,
           messages.UnFreezeMsg.AMINO_MESSAGE_TYPE: dex_pb2.TokenUnfreeze,
           messages.TransferMsg.AMINO_MESSAGE_TYPE: dex_pb2.Send,
           messages.VoteMsg.AMINO_MESSAGE_TYPE: dex_pb2.Vote,
}.get(binascii.hexlify(msg_type).upper())def parse_stdtx(tx_bytes: bytes) -> dex_pb2.StdTx:
   varint_length = decode_bytes(tx_bytes[:2])
   msg_type = tx_bytes[2:6]
assert varint_length == len(tx_bytes) - 2
assert (binascii.hexlify(msg_type).decode('utf-8').lower() ==
           messages.StdTxMsg.AMINO_MESSAGE_TYPE.decode('utf-8').lower())
   stdtx_pb2 = dex_pb2.StdTx().FromString(tx_bytes[6:])
return stdtx_pb2


def decode_bytes(buf):
return decode_stream(BytesIO(buf))def decode_stream(stream):
   shift = 0
   result = 0
while True:
       i = read_one(stream)
       result |= (i & 0x7f) << shift
       shift += 7
if not (i & 0x80):
break
return result

def read_one(stream):
   c = stream.read(1)
if c == b'':
raise EOFError("Unexpected EOF while reading bytes")
return ord(c)


listen_addr = 'https://seed1.ciscox.io'
rpc_client = HttpRpcClient(listen_addr)
block = rpc_client.get_block(31414371)
txs = block['block']['data']['txs']print(txs)if txs != None:
for tx_str in txs:
       tx_bytes = base64.b64decode(tx_str)
       stdtx_pb2 = parse_stdtx(tx_bytes)

       msg = stdtx_pb2.msgs[0]
       msg_pb2 = parse_msg(msg)

# 只支持转账类型,其他类型直接抛弃
if type(msg_pb2) != dex_pb2.Send:
continue

# 只支持 BNB 转账类型, 代币直接抛弃
if not is_bnb_transaction(msg_pb2):
continue

# 解码地址
       from_address, to_address = extract_send_msg_addresses(msg_pb2)
       product_env = BinanceEnvironment.get_production_env()
       client = HttpApiClient(env=product_env)

# 获取交易信息
       queried_txs = client.get_transactions(address=from_address, height=31414371)['tx']
print(queried_txs)
if len(queried_txs) == 0:
continue

if not queried_txs:
           logging.error("Failed to retrieve transaction from node")
       tx_dict = list(filter(lambda t: t['toAddr'] == to_address, queried_txs))[0]
       tx_hash = tx_dict['txHash']
       tx_fees = tx_dict['txFee']
       tx_from_address = tx_dict['fromAddr']
       tx_value= float(tx_dict['value'])
       tx_to_address = tx_dict['toAddr']

3.4. 扫交易的代码拆解

listen_addr = 'https://seed1.ciscox.io' rpc_client = HttpRpcClient(listen_addr) block = rpc_client.get_block(31414371) txs = block['block']['data']['txs']

执行结果:

['4wHwYl3uCmnObcBDChTXOqeZ4ufVDUOiSjB/nN6XYQ9bBhIuRDczQUE3OTlFMkU3RDUwRDQzQTI0QTMwN0Y5Q0RFOTc2MTBGNUIwNi0zODc0NRoMQ0JJWC0zQzlfQk5CIAIoATCk9wI4gLyft9YFQAMScgom61rphyECSEG6gwzsC4XxyvLZy2IwjGOxgSLBzAkd0ZIo464A1uQSQP3jvW9iqpjMTD2RvKnRGhSKo4sp6Ho621lMZhyc5i6+AYNRoWcd6wSedgD+8NGlKuMZAcQ92dGbLtfHCVJD8scYgrgNINiuAg==', '5AHwYl3uCmrObcBDChQ32Tg+atmv72xdgGaro6yox12fORIvMzdEOTM4M0U2QUQ5QUZFRjZDNUQ4MDY2QUJBM0FDQThDNzVEOUYzOS0xOTAyMDMaDERFRkktRkE1X0JOQiACKAEw7/MSOICc9LyLAkABEnIKJuta6YchA72zQwwyLMnAEN9XXcZZMxXutUJODGLctr9d5ytfKpY2EkCejmOm6NlMVSeG2rL/tZUXTNrQUEG5vWmG2y2yWpc163x87X8Nmv0yl8/Jjw4ucgGm9leAeoIOwLWTXwvS8TgeGMrrDCD6zQs=', 'zAHwYl3uCkoqLIf6CiEKFO4zjrFuFqFU9XqlX9o0UWYAqnxwEgkKA0JOQhDAhD0SIQoUFbydihQiQFYp3JhTu11jlKc8zeUSCQoDQk5CEMCEPRJwCibrWumHIQOO0/GLohfIXsNtdf7Wpv6aPZcSITv9OyIm8dKdgX3wGRJAUHi3I8n/Kvg5bYbrtnBy/nBgjJmsDPV6R0YJFSfRyIZYAOqP5BaTC2uoKAql0TPegfRari8O4b9xGLhV8cLYiBiLjAwgGhoIOTE0NjAzMzM=']

氨基解码的流程:

解码 base64 将缓冲区解析为StdTxMsg对象 将内部(“实际”)消息(Send,NewOrder,CancelOrder等)解析为 Protobuf 对象

解析交易from 或者 to 的地址

def extract_send_msg_addresses(send_msg: dex_pb2.Send) -> typing.Tuple[str]:
   from_address = segwit_addr.encode('bnb', send_msg.inputs[0].address)
   to_address = segwit_addr.encode('bnb', send_msg.outputs[0].address)
return from_address, to_address

判断是不是 BNB 交易

def is_bnb_transaction(send_msg: dex_pb2.Send):
for inp in send_msg.inputs:
for coin in inp.coins:
if coin.denom != 'BNB':
return False
for out in send_msg.outputs:
for coin in out.coins:
if coin.denom != 'BNB':
return False
return True

解析标准交易消息

def parse_msg( msg_bytes: bytes) -> dex_pb2.StdTx:
   msg_type = msg_bytes[:4]
   msg_class = amino_msg_type_to_class(msg_type)
   msg_pb2 = msg_class().FromString(msg_bytes[4:])
return msg_pb2

将内部(“实际”)消息(Send,NewOrder,CancelOrder等)解析为 Protobuf 对象

def amino_msg_type_to_class(msg_type: bytes):
return {
           messages.NewOrderMsg.AMINO_MESSAGE_TYPE: dex_pb2.NewOrder,
           messages.CancelOrderMsg.AMINO_MESSAGE_TYPE: dex_pb2.CancelOrder,
           messages.FreezeMsg.AMINO_MESSAGE_TYPE: dex_pb2.TokenFreeze,
           messages.UnFreezeMsg.AMINO_MESSAGE_TYPE: dex_pb2.TokenUnfreeze,
           messages.TransferMsg.AMINO_MESSAGE_TYPE: dex_pb2.Send,
           messages.VoteMsg.AMINO_MESSAGE_TYPE: dex_pb2.Vote,
}.get(binascii.hexlify(msg_type).upper())

将缓冲区解析为StdTxMsg对象

def parse_stdtx(tx_bytes: bytes) -> dex_pb2.StdTx:
   varint_length = decode_bytes(tx_bytes[:2])
   msg_type = tx_bytes[2:6]
assert varint_length == len(tx_bytes) - 2
assert (binascii.hexlify(msg_type).decode('utf-8').lower() ==
           messages.StdTxMsg.AMINO_MESSAGE_TYPE.decode('utf-8').lower())
   stdtx_pb2 = dex_pb2.StdTx().FromString(tx_bytes[6:])
return stdtx_pb2


如果想了解更多关于区块链钱包开发的教程,请点击关注:https://github.com/guoshijiang/blockchain-wallet


全部评论(0)