关于区块链介绍性的研讨会通常以易于理解的点对点网络和银行分类账这类故事开头,然后直接跳到编写智能合约,这显得非常突兀。因此,想象自己走进丛林,想象以太坊区块链是一个你即将研究的奇怪生物。今天我们将观察该生物,并与其进行交互然后将有关它的所有数据收集到一个集中存储中供自己使用。
进行第一次设置
首先,你需要安装web3py。Web3py是一个用于连接以太坊区块链的Python库。你需要事先知道的是,没有可以从中下载数据的中央管理系统。彼此共享资源的内连节点(“对等体”)存储经验证的数据副本(或其一部分)。网络执行以太坊协议,该协议定义节点彼此之间的交互规则及网络上的智能合约。
如果要访问有关交易,余额,区块或其他任何被写入区块链的信息,协议需要你连接到节点。节点不断地相互共享新数据并验证数据,因此这样你就可以确定那些是未被篡改的数据,那些是最新的数据。
你可以在第一次接触以太坊的方法中使用两种基本类型的节点:本地或托管。本地节点可以在你的计算机上运行,这意味着你首先需要下载像geth这样的客户端,它会将区块链同步到你的设备,要占用存储空间并花费大量时间来完成。对于第一次学习,托管节点是更好的选择——它由其他人控制,但你可以轻松连接到它并自己玩区块链。
去Infura并创建自己的免费帐户以访问此类托管节点。当你完成后,你可以看到mainnet主网(即以太坊区块链)和一堆testnets测试网,它们基本上可以测试你的智能合约,这样你就可以在将昂贵的代码部署到mainnet之前犯错误,并纠正它们。
这第一次我们先导入Web3对象并建立HTTP连接。
from web3 import Web3 web3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/your-own-personal-number"))
现在你已经完成了!你可以使用web3 API浏览查询数据结构了。
查询特定区块信息
#current block number>>> web3.eth.blockNumber5658173#get the content of the most recently mined block>>> web3.eth.getBlock('latest')
此命令返回AttributeDict数据结构,该结构是key-value键值对的字典,如下所示:
AttributeDict({'difficulty': 3297284143124448, 'extraData': HexBytes('0x65746865726d696e652d6177732d61736961312d34'), 'gasLimit': 7999992, 'gasUsed': 7990111, 'hash': HexBytes('0x8c09ba67123601c08ef5d292acaffd36798ca178b7d6fecd5e1144ce8e3b9e50'), 'logsBloom': HexBytes('0x348000240b40620836308460180004415000c8ccb260021402420721c22801ca847c625c0a89030482044001523a4d100050100250d100858381260a186312088006c154010000491216446840888200c1812088c12b06000809a808530014160000812c2ac20008a201c83380314d02242338400c0500c2a028005010988c44b0608a020400201032e10e16142b931115469824248066100b082224200222140a41a20aa2006224d608210f1a22811d03969423e8c08058100388c0800402002a000802130c40d289201900c38142a1cc0380a4010f0201040d4022200022018c5801346c168502841906940485ea1d9864044060a00000a00616004d006090'), 'miner': '0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8', 'mixHash': HexBytes('0x84320fd71345778b48e437f3403e9021575520ba23aaac48dd7a352c9ce31f75'), 'nonce': HexBytes('0x98a0b1e00bfabac6'), 'number': 5658173, 'parentHash': HexBytes('0x01eda8a47a0151533d1afacf9b9108606d4d89a86e269dddaac9698b6fb12930'), 'receiptsRoot': HexBytes('0xc40f774ad10ad443457c3a5a0db96b539af3007f8d351b198ca7bf2ef196b7e0'), 'sha3Uncles': HexBytes('0x55725ec296c6c64257ed6a88d7d8c66160abe7b672f5d89bbad5487779b1d5fe'), 'size': 27161, 'stateRoot': HexBytes('0x5bfc7a9a87fb9991f2760807d56319154f1dab91d3cfc9530a597b6c5d064aba'), 'timestamp': 1527002893, 'totalDifficulty': 4339832462578780814928, 'transactions': [HexBytes('0x1ce6bca99701c4e8acae986b10e7649d628d70ec62b7a8314bbb13726a312225'), HexBytes('0x6ba5e657243aea5f95afb40090313d10bb9443db41ed1216fbf7e7e60a16749a'), loooooots_of_transactions_here], 'transactionsRoot': HexBytes('0x67e1e1f2f4b1d33791a0fba2d5ebf039bd6c331c665cb8020ff41d0e0eade46e'), 'uncles': [HexBytes('0x3df1bffa62d73b3847b434e9ea459c10cfdc3e212a2e78ebbf0db58adbef30b5'), HexBytes('0x74bdcd4b88427854ae18f9c7ada28d46f26411bed09af6b040cbede66fdb1853')]})
并非所有这些变量都会立即对你有用,因为有些变量非常技术性,只有当你对区块链的实际工作方式有了更深入的了解时,它们的含义才有意义。你可以在所谓的黄皮书中阅读有关它们的更多信息,或暂时跳过它们并使用易于理解的方法。
简而言之,一个包含区块头部信息的区块,一个写入它的已验证交易列表和一个未确认列表(矿工的块标识符,他们的区块太慢,无法进入主区块链,但仍因其计算工作量而获得以太奖励)。下面你可以看到每个变量的含义,我把它分成子类别。
General常规数据
Block variable | Meaning | 翻译 |
---|---|---|
number | scalar value equal to the number of ancestor blocks (genesis block=0) | 标量值相对创始块的数量,genesis block=0 |
size | size of the block in bytes | 块的大小,以字节为单位 |
timestamp | Unix's time() at this block's inception | 这个块开始时的Unix时间 |
miner | 160-bit address for fees collected from successful mining | 成功采矿收取以太的160位地址 |
gasLimit | maximum gas expenditure allowed in this block | 此区块允许的最大气体消耗量 |
gasUsed | total gas used by all transactions in this block | 此区块中所有交易使用的总气体 |
transactions | list of transaction hashes included in the block | 块中包含的交易哈希列表 |
parentHash | Keccak256 hash of the parent block's header | 父块区块头的Keccak 256哈希值 |
hash | current block's hash | 当前块的哈希值 |
extraData | extra data in byte array | 字节数组中的额外数据 |
挖矿相关
Block variable | Meaning | 翻译 |
---|---|---|
difficulty | scalar value corresponding to the difficulty level of the block | 对应于块的难度级别的标量值 |
totalDifficulty | integer of the total difficulty of the chain until this block | 直到此区块的链的总难度值 |
nonce | hash of the generated proof-of-work; null when its a pending block | 生成工作量证明的哈希值;当区块挂起时为null |
mixHash | 256-bit hash which is combined with the nonce and used to prove that sufficient amount of computation has been carried out on this block | 256位哈希与nonce结合使用来证明已对此块执行了足够的计算量 |
uncle相关
Block variable | Meaning | 翻译 |
---|---|---|
uncles | list of uncle hashes | uncle哈希值列表 |
sha3Uncles | SHA3 of the uncles data in the block | 块中uncles数据的SHA3值 |
技术相关
Block variable | Meaning | 翻译 |
---|---|---|
receiptsRoot | Keccak 256-bit hash of the root node of the tree structure populated with receipts of all transactions in this block | Keccak树结构的根节点的256位哈希填充了此块中所有交易的收据 |
stateRoot | Keccak256 hash of the root node if the state trie after all transactions are executed and finalisations applied | 在执行所有交易并应用终止后,如状态为trie根节点的keccak256哈希值 |
transactionsRoot | Keccak256 hash of the root node of the trie structure populated with the receipts of each transaction in the transactions list | trie结构的根节点的keccak256哈希填充了交易列表中每个交易的收据 |
logsBloom | the Bloom filter from indexable info (logger address and log topics) contained in each log entry from the receipt of each transaction in the transaction list | 交易列表中每个交易的接收日志条目中包含的可索引信息(记录器地址和日志主题)的Bloom过滤器 |
交易和收据
现在,我们还可以通过其唯一标识符(即交易哈希)查找区块中的单个交易。
>>> web3.eth.getTransaction('0x1ce6bca99701c4e8acae986b10e7649d628d70ec62b7a8314bbb13726a312225') AttributeDict({'blockHash': HexBytes('0x8c09ba67123601c08ef5d292acaffd36798ca178b7d6fecd5e1144ce8e3b9e50'), 'blockNumber': 5658173, 'from': '0x390dE26d772D2e2005C6d1d24afC902bae37a4bB', 'gas': 45000, 'gasPrice': 123400000000, 'hash': HexBytes('0x1ce6bca99701c4e8acae986b10e7649d628d70ec62b7a8314bbb13726a312225'), 'input': '0x', 'nonce': 415710, 'r': HexBytes('0x1bb901ad0a3add517504cc459fdb1545d193020ec5c63a566e440ee39dbfe131'), 's': HexBytes('0x4b7ac95eb321b5947948ecb624e1d80b19d9cc876668c69cc2b32670f52b061a'), 'to': '0xBbA2D99C9B3aF394B0d6417b1D58815eE495029D', 'transactionIndex': 0, 'v': 37, 'value': 1000000000000000000})
和以前一样,web3py返回一个属性字典。下表总结了每个键的含义。
Transaction variable | Meaning | 翻译 |
---|---|---|
blockHash | hash of the block the transaction belongs to | 交易所属区块的哈希值 |
blockNumber | number of that block | 该块的编号 |
hash | transaction hash (unique identifier) | 交易地址哈希(唯一标识符) |
from | 160-bit address of a sender of a transaction | 来自交易发送方的160位地址哈希 |
to | address of the recipient or null for a contract creation transaction | 收件人的地址或者创建合约交易时为null |
value | number of wei to be transfered to the recipient or newly created account (case of contract creation) | 要转移给收件人或新创建帐户的wei数量(创建合约的情况) |
gas | gas consumed by the transaction | 交易消耗的天然气 |
gasPrice | number of Wei to be paid per unit of gas for all computatioon costs of this transaction | 此交易所有计算成本的每单位天然气的支付数量 |
nonce | number of transactions/contract creations sent by the sender prior to this one | 发送方在此之前发送的交易和创建合约的数量 |
v/r/s | used to identify the sender; the signature values of the transaction | 用于识别发件人交易的签名值 |
input | the data sent along with the transaction | 与交易一起发送的数据 |
transactionIndex | index of the transaction in the block | 区块中交易的索引 |
最后,我们还可以查看交易收据:
>>> web3.eth.getTransactionReceipt('0x68c70c5ffe54a42ebf7118e7e931aeac018cee4656a816ffe6a01388da50c851') AttributeDict({'blockHash': HexBytes('0x44338e1f80302037c7213e8f56dd35d8a473b000319bc200f76e910e62d12f98'), 'blockNumber': 5617795, 'contractAddress': None, 'cumulativeGasUsed': 21004, 'from': '0xea6e3e41ebaa09d550d3c3f0d72971b3c5ccc261', 'gasUsed': 21004, 'logs': [], 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'status': 1, 'to': '0xd96a6e75d099ce529bbd257cbcb403224cceaebf', 'transactionHash': HexBytes('0x68c70c5ffe54a42ebf7118e7e931aeac018cee4656a816ffe6a01388da50c851'), 'transactionIndex': 0})
交易收据包含一些重复和新条目,新的条目解释如下。
Receipt variable | Meaning | 翻译 |
---|---|---|
status | boolean whether the transaction was successfull; false if the EVM (Ethereum Virtual Machine) reverted the transaction | 交易是否成功,如果EVM(以太坊虚拟机)还原了交易则返回false |
contractAddress | the contract address created if the transaction was a contract creation; otherwise null | 如果交易是合约创建,则创建的合同地址;否则为null |
gasUsed | the total amount of gas used when this transaction was executed in the block | 在区块中执行此交易时使用的总气体量 |
cumulativeGasUsed | the sum of gasUsed by this transaction and all preceding transactions in the same block | 此交易使用的gasUse和同一块中所有先前交易的总和 |
logs | array of log objects which the transaction has generated | 交易生成的日志对象数组 |
作为参考,除了黄皮书之外,我还包括各种额外资源来编制这些表格2,3,4,5。
如你所见,只需几个简单的命令,就可以连接到网络并获得有关原始格式的交易,区块或状态的基本信息。这将为这些数据打开一个新窗口!
数据库管理系统
当计划将数据写入适当的数据库时,你可能会意识到有许多针对Python爱好者的管理系统解决方案,例如无服务器SQLite,或基于服务器的MySQL,PostgreSQL或Hadoop。根据你的意图,必须确定哪个选项最适合你的项目。总的来说,我发现这些要点很有帮助:
-
数据库的预期大小是什么(即可以在单个机器系统上处理)?
-
这些条目是经常编辑还是会保持不变?厦门叉车出租公司电话是多少
-
数据库是否应该由多方/应用程序同时访问和编辑?
随着时间的推移,以太坊区块链正在稳步增长,截止到2018年6月接近1TB,这个很小,因此对于像Hadoop这样的分布式处理系统来说并不是最佳选择。区块链数据库将被写入一次,然后仅使用新条目进行扩展,保留旧条目不变。此数据库的预期用例由一个通道编写,并由其他通道以只读方式访问,因此我们实际上不需要在服务器上运行它。在你的机器上本地保存数据库将导致快速读取,这对于像SQLite这样的无服务器管理系统是可取的和可实现的。Python有一个内置的库sqlite3
,因此我们甚至不需要安装新的包。
数据库设计
下一步是设计数据库。请记住哪些数据字段与你的分析最相关,并且旨在优化搜索和存储。例如,如果你不打算使用stateRoot
,则可能需要完全跳过它或将其保存在单独的表中。可以更快地搜索具有较少列的表,如果稍后意识到你实际上具有stateRoot
的用例,你仍然可以访问它。你可能还希望将块信息与交易信息分开;如果不这样做,那么区块属性如timestamp
将对区块中的所有交易重复N次,浪费大量空间。稍后使用JOIN
操作可以轻松地将交易与其块属性进行匹配。
我设计的数据库包含3个表:
-
Quick:最相关的交易信息,用于快速访问和分析。
-
TX:所有剩余的交易信息,
-
Block:指定区块的信息。
变量的命名约定相对于原始的web3py略有改变,以消除歧义,例如将块哈希和交易哈希都称为“哈希”,或使用“from”/“to”作为列名,这在SQL有不同的含义,会使程序崩溃。
交易值,余额和其他大数字需要作为字符串存储在数据库中。原因是SQLite只能处理最多8字节存储的有符号整数,最大值为2的63次方-1大约是9223372036854775807.这通常远低于wei中的交易值(例如,1ETH = 10*18 wei)。
构建你的迷你数据库
完整的代码可以在GitHub上找到。它将根据上层架构组织区块链信息,并输出包含预先指定数量的块数据的blockchain.db文件。要测试它,请转到database.py
文件并为要写入的块数选择合理的数字,例如:
Nblocks = 10000
默认情况下,你应该将web3对象指向Infura端点。 如果你有IPC提供商(即你的本地节点),也可以切换到IPC提供商,只需取消注释该行:
# or connection via node on the VM #web3 = Web3(Web3.IPCProvider('/path-to-geth.ipc/'))
修改路径,然后只需在命令行python database.py
中运行。代码会将最后写入的块的编号转储到lastblock.txt
文件中,以防你需要重新启动。
如何使用数据库
一旦将第一个条目写入数据库,就可以通过ipython shell开始与它进行通信。例如,要打印表“Quick”的前5行,你可以运行下面的代码。
import sqlite3 as sq3 conn = sq3.connect("blockchain.db") cur = conn.cursor()# some SQL code, e.g. select first five entries of the table Quickcur.execute("SELECT * FROM Quick LIMIT 5") a = cur.fetchall() #list of tuples containing all elements of the rowprint(a) conn.close()
本地节点与Infura
如果要构建大型数据库,则应下载geth并同步节点。同步可以在3种基本模式下完成:
如果你不需要过去的帐户状态,则可以在快速模式下同步节点6。
下面的图表显示了此代码写入数据库的速度,与本地完全同步的节点(IPC)与Infura(Infura)上的地址进行通信。正如你所看到的,在本地节点上运行此代码是值得的,因为你可以将速度提升近2个数量级(即100x)!
总结
现在你已拥有自己的本地数据库,了解区块链上发生的事情,可以开始探索它。例如,你可以计算自其起源以来的交易数量,查看作为时间函数生成的地址数量——天空是你可以了解的有关区块链的限制。我们为你的数据科学游乐场奠定了基础。因此,请继续探讨,或查看下一篇文章,了解潜在的应用。