简介 基于以太坊虚拟机(EVM)的网络通常可以运行两种类型的节点:一个全节点和一个存档节点。 许多流行网络基于 EVM:包括以太坊[4] 、Polygon 、BNB Smart Chain[5]、C-Chain of Avalanche 、Fantom、Harmony 等。 全节点和存档节点两者都存储完整的区块链数据,可用于重放网络状态,但区别在于,存档节点另外将每个区块的网络状态存储在一个存档中,可供查询。 这就是简短的解释。 在这篇文章中,我们将深入探讨全节点和存档节点的一些细节、区别和操作实例。 Geth 和 Erigon首先,简单介绍一下节点客户端: Go 以太坊[6](Geth)是迄今为止最流行的基于 EVM 的网络的客户端软件,可能在整个区块链领域都是如此。 对于以太坊主网,你可以在ethernodes crawler data[7]查看节点客户端分布。 Chainstack 支持使用 Geth 客户端或Erigon 客户端[8](以前是 Turbo-Geth)来运行以太坊节点--后者是另一个 Go 实现客户端,专注于效率,是第二流行的客户端。 在这篇文章中,我们将重点介绍 Geth 和 Erigon 在全节点和存档节点模式下的实现。 全节点和存档节点让我们深入了解一下细节: 全节点- 存储完整的区块链数据。
- 验证所有区块和状态。
- 所有的状态都可以从一个完整的节点重新生成。
一个完整的 EVM 节点保持区块链的当前状态,并处理读取调用(view)和状态改变的调用(交易)。一个完整的节点会修剪区块链数据,以节省磁盘空间并减少同步时间,但在必要时存储足够的数据来重新计算链上的事件,使得它的运行效率更高,但它也限制请求特定数量的区块的数据(通常为 128 个区块)。 例如,在以太坊主网上,产生一个新区块的平均时间约为 13 秒,你只能检索过去 28-29 分钟的链状态。虽然在理论上,你可以使用一个完整的节点来重新计算所有的中间状态,但这将需要特别长的时间,而且将是非常密集的资源,你的节点可能会耗尽内存而停止。 默认的返回状态和 Missing trie node的错误根据所访问的链和所使用的客户端,被限制能访问多少个可用的区块状态有所不同: - 以太坊:128 个区块
- Polygon: 128 个区块
- BNB 智能链: 128 个区块
- Avalanche C-Chain:32 个区块
- Fantom: Go Opera 客户端不修剪信息,所以在全节点和存档节点之间没有区别。
- Harmony: 128 个区块
如果你试图查询一个不能从全节点访问的区块,你会收到一个missing trie node的错误。 一般来说,收到missing trie node的错误意味着你需要一个存档节点。 存档节点- 存储所有保存在全节点中的东西,并建立一个历史状态的档案。
- 他们是配置为在存档模式下运行的全节点。
存档节点本质上包含了整个区块链的快照,并持有从创世区块(第一个被开采的区块)开始的所有先前的网络状态。这使得存档节点非常适合快速查询历史数据,而不需要状态重建,这对于创建分析工具、DApps 和其他需要快速访问历史的服务的开发者来说是理想的。 由于存档节点保留了整个链的状态,它们的大小也比全节点大得多。在撰写本文时,以太坊主网的规模约为 10TB(etherscan.io[9])。 要启动一个新的存档节点,系统需要同步所有这些数据,然后才能开始在网络上运行。这导致了高额的启动和维护成本,鉴于此时需要几个月的时间来完成同步过程,而且为了跟上不断增长的磁盘大小需求,必须不断进行维护。 存档的主网状态大小(供参考)请注意,这些数据一直在增长,这些数据在本文发表时是有效的。 - 以太坊主网:~12 TB
- Polygon 主网:~16 TB
- BNB 智能链:~7 TB
- Fantom 主网:~4 TB
- Harmony 主网:~20 TB
- Avalanche 主网:~3 TB
请注意,BNB 智能链使用 Erigon 客户端,与 Geth 相比,占用较小空间 这显示了所有这些链包含了多少数据,如果你想自己建立节点,你需要下载所有这些数据,并在能够运行节点之前对其进行验证。这对于一个存档节点来说可能需要几个月的时间。 在几分钟内部署一个节点由于 Chainstack 等第三方节点的存在,你可以在几分钟内部署自己的节点。使用我们的快速同步技术称为 Bolt[10],Chainstack 允许你在短短几分钟内部署一个全节点或存档节点,节省了数周或数月的工作和资源。 要获得一个节点: - 在 Chainstack 注册[11]。
- 部署一个全节点或存档节点[12]。
获取过去状态的方法现在很明显,要访问比最后 128 个块更早的数据,我们需要使用一个存档节点。 以下Geth JSON-RPC 方法[13]包括一个参数,允许用户指定从哪个块检索数据: - eth_getBalance[14]
- eth_getCode[15]
- eth_getTransactionCount[16]
- eth_getStorageAt[17]
- eth_call[18]
让我们依次看看这些方法,并尝试调用一下。 同样,如果你需要快速访问一个存档节点,可以在Chainstack 获取一个[19]。 eth_getBalance检索一个特定时间点(区块)的地址余额,详情请见以太坊 Wiki:eth_getBalance[20] Web3.py使用 web3.py 从区块编号 1 的状态中检索地址余额。 在一个全节点上运行这段代码将返回一个错误,因为我们获取区块高度 1[21]时一个地址的余额: from web3 import Web3
node_url = "CHAINSTACK_ARCHIVE_NODE_URL"
web3 = Web3(Web3.HTTPProvider(node_url))
balance = web3.eth.get_balance("0x9D00f1630b5B18a74231477B7d7244f47138ab47", 1)
print(web3.fromWei(balance, "ether"))
我们仍然可以在一个全节点上运行eth_getBalance,但是不能回溯到超过 128 个块。 Web3.js使用 web3.js 获取一个地址余额。在下面是获取区块块号 14641000[22]的地址余额: var Web3 = require('web3')
var node_URL = 'CHAINSTACK_ARCHIVE_NODE_URL'
var web3 = new Web3(node_URL)
web3.eth.getBalance('0x9D00f1630b5B18a74231477B7d7244f47138ab47', 14641000, (err, balance) => {
console.log(web3.utils.fromWei(balance, 'ether'))
})
fromWei方法用于将从节点(Wei)收到的数字转换成对我们可读的单位表示(ether)的数字。 cURL使用 cURL 检索一个地址余额。在下面查询的是区块编号 14641000[23]的状态。 注意,区块高度和返回值都是十六进制: curl CHAINSTACK_ARCHIVE_NODE_URL \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getBalance","params":["0x9D00f1630b5B18a74231477B7d7244f47138ab47", "0xDF6768"],"id":1,"jsonrpc":"2.0"}'
eth_getCode返回一个智能合约的编译字节码,详情请见以太坊 Wikieth_getCode[24]。 下面的例子将得到Uniswap token[25]在部署时第一个区块的状态下的字节码,区块高度 10861674[26]。 Web3.pyfrom web3 import Web3
node_url = "CHAINSTACK_ARCHIVE_NODE_URL"
web3 = Web3(Web3.HTTPProvider(node_url))
code = web3.eth.get_code("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", 10861674)
print(code)
Web3.jsvar Web3 = require('web3')
var node_URL = 'CHAINSTACK_ARCHIVE_NODE_URL'
var web3 = new Web3(node_URL)
web3.eth.getCode('0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', 10861674, (err, byte) => {
console.log(byte)
})
cURL注意,区块高度是十六进制: curl CHAINSTACK_ARCHIVE_NODE_URL \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getCode","params":["0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", "0xA5BC6A"],"id":1,"jsonrpc":"2.0"}'
getCodeRPC 方法可以用来验证合约是否被正确部署或销毁[27]。 eth_getTransactionCount返回在特定区块下从一个地址发送的交易数量。详情请见以太坊 Wiki eth_getTransactionCount[28]。 下面的例子将获取一个地址在区块高度 14674300[29]状态下的交易数量(nonce)。 Web3.pyfrom web3 import Web3
node_url = "CHAINSTACK_ARCHIVE_NODE_URL"
web3 = Web3(Web3.HTTPProvider(node_url))
tx_count = web3.eth.get_transaction_count("0x9D00f1630b5B18a74231477B7d7244f47138ab47", 14674300)
print(tx_count)
Web3.jsvar Web3 = require('web3');
var node_URL = 'CHAINSTACK_ARCHIVE_NODE_URL';
var web3 = new Web3(node_URL);
web3.eth.getTransactionCount('0x9D00f1630b5B18a74231477B7d7244f47138ab47', 14674300, (err, count) => {
console.log(count)
})
cURL注意,区块编号和返回值都是十六进制: curl CHAINSTACK_ARCHIVE_NODE_URL \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getTransactionCount","params":["0x9D00f1630b5B18a74231477B7d7244f47138ab47", "0xDFE97C"],"id":1,"jsonrpc":"2.0"}'
getTransactionCount RPC 方法用于获取一个地址的 nonce,nonce 是一个整数值,代表该账户发送了多少交易。同样用来避免重复交易。 eth_getStorageAt返回一个给定地址的存储位置的值,详情请见以太坊 Wiki eth_getStorageAt[30]。 下面的例子将返回简单存储合约[31]的存储值。 最后一次值变化是在区块高度 7500943[32],所以你可以把它作为一个参考点,以及检索不同区块高度的存储值。 Web3.pyfrom web3 import Web3
node_url = "CHAINSTACK_ARCHIVE_NODE_URL"
web3 = Web3(Web3.HTTPProvider(node_url))
storage = web3.eth.get_storage_at("0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64", 0, 7500943)
print(storage.decode("ASCII"))
Web3.jsvar Web3 = require('web3');
var node_URL = 'CHAINSTACK_ARCHIVE_NODE_URL';
var web3 = new Web3(node_URL);
web3.eth.getStorageAt('0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64', 0, 7500943).then(result => {
console.log(web3.utils.hexToAscii(result));
});
cURL注意,区块编号和返回值都是十六进制: curl CHAINSTACK_ARCHIVE_NODE_URL \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getStorageAt","params":["0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64", "0", "0x72748F"],"id":1,"jsonrpc":"2.0"}'
eth_call在区块链上进行只读调用,不改变任何状态。详情请见以太坊 Wiki eth_call[33]。 下面的例子为区块高度 14000000[34]的Chainlink token[35]地址调用Chainlink VRF coordinator[36]的balanceOf函数: Web3.pyimport json
from web3 import Web3
node_url = "CHAINSTACK_ARCHIVE_NODE_URL"
web3 = Web3(Web3.HTTPProvider(node_url))
abi=json.loads('[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"transferAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"data","type":"bytes"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]')
address = "0x514910771AF9Ca656af840dff83E8264EcF986CA"
contract = web3.eth.contract(address=address, abi=abi)
balance = contract.functions.balanceOf('0x271682DEB8C4E0901D1a1550aD2e64D568E69909').call(block_identifier=14000000)
print(web3.fromWei(balance, 'ether'))
Web3.jsconst Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("CHAINSTACK_ARCHIVE_NODE_URL"));
web3.eth.defaultBlock = 14000000;
web3.eth.call({
to: "0x514910771AF9Ca656af840dff83E8264EcF986CA",
data: "0x70a08231000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909"
})
.then(result => {
console.log(web3.utils.fromWei(result));
});
cURL注意,区块编号和返回值都是十六进制: curl CHAINSTACK_ARCHIVE_NODE_URL \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_call","params":[{"from":null,"to":"0x514910771AF9Ca656af840dff83E8264EcF986CA","data":"0x70a08231000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909"}, "0xD59F80"],"id":1,"jsonrpc":"2.0"}'
结论存档节点持有区块链的 "历史",并拥有从创世区块开始网络中的每个先前状态的记录。这意味着可以快速访问历史数据,使用 Chainstack,你可以轻而易举地建立一个存档节点! 存档节点是一个很好的开发工具,特别是当你需要查询过去的数据时,例如,如果你正在使用 Hardhat、Ganache 和其他开发框架来分叉主网,用于运行本地模拟区块链进行测试和开发,或者如果你在创建一个区块链资源管理器、区块链分析工具、用 The Graph 等协议进行区块链索引等等,因为你可以即时访问全链。 如果你正在 DApp,通常最新 128 个区块内的数据就足够了,这是仅需要一个全节点。
本翻译由 Duet Protocol[37] 赞助支持。 原文:https://chainstack.com/evm-nodes ... ll-vs-archive-mode/
|