译文出自:登链翻译计划[1] 译者:翻译小组[2] 校对:Tiny 熊[3]
简介
基于以太坊虚拟机(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]时一个地址的余额:
代码语言:javascript复制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]的地址余额:
代码语言:javascript复制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]的状态。
注意,区块高度和返回值都是十六进制:
代码语言:javascript复制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.py
代码语言:javascript复制from 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.js
代码语言:javascript复制var 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
注意,区块高度是十六进制:
代码语言:javascript复制curl CHAINSTACK_ARCHIVE_NODE_URL
-X POST
-H "Content-Type: application/json"
--data '{"method":"eth_getCode","params":["0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", "0xA5BC6A"],"id":1,"jsonrpc":"2.0"}'
getCode
RPC 方法可以用来验证合约是否被正确部署或销毁[27]。
eth_getTransactionCount
返回在特定区块下从一个地址发送的交易数量。详情请见以太坊 Wiki eth_getTransactionCount[28]。
下面的例子将获取一个地址在区块高度 14674300[29]状态下的交易数量(nonce)。
Web3.py
代码语言:javascript复制from 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.js
代码语言:javascript复制var 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
注意,区块编号和返回值都是十六进制:
代码语言:javascript复制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.py
代码语言:javascript复制from 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.js
代码语言:javascript复制var 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
注意,区块编号和返回值都是十六进制:
代码语言:javascript复制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.py
代码语言:javascript复制import 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.js
代码语言:javascript复制const 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
注意,区块编号和返回值都是十六进制:
代码语言:javascript复制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-a-dive-into-the-full-vs-archive-mode/
参考资料
[1]
登链翻译计划: https://github.com/lbc-team/Pioneer
[2]
翻译小组: https://learnblockchain.cn/people/412
[3]
Tiny 熊: https://learnblockchain.cn/people/15
[4]
以太坊: https://learnblockchain.cn/categories/ethereum/
[5]
BNB Smart Chain: https://learnblockchain.cn/article/2626
[6]
Go 以太坊: https://github.com/ethereum/go-ethereum
[7]
ethernodes crawler data: https://www.ethernodes.org/
[8]
Erigon客户端: https://github.com/ledgerwatch/erigon
[9]
etherscan.io: https://etherscan.io/chartsync/chainarchive
[10]
称为Bolt: https://chainstack.com/introducing-bolt-the-chainstack-technology-made-for-simple-node-synchronization/
[11]
在Chainstack注册: https://console.chainstack.com/user/account/create
[12]
部署一个全节点或存档节点: https://docs.chainstack.com/platform/join-a-public-network
[13]
Geth JSON-RPC方法: https://eth.wiki/json-rpc/API#the-default-block-parameter
[14]
eth_getBalance: https://eth.wiki/json-rpc/API#eth_getbalance
[15]
eth_getCode: https://eth.wiki/json-rpc/API#eth_getcode
[16]
eth_getTransactionCount: https://eth.wiki/json-rpc/API#eth_gettransactioncount
[17]
eth_getStorageAt: https://eth.wiki/json-rpc/API#eth_getstorageat
[18]
eth_call: https://eth.wiki/json-rpc/API#eth_call
[19]
Chainstack获取一个: https://console.chainstack.com/user/account/create
[20]
eth_getBalance: https://eth.wiki/json-rpc/API#eth_getbalance
[21]
区块高度1: https://etherscan.io/block/1
[22]
区块块号14641000: https://etherscan.io/block/14641000
[23]
区块编号14641000: https://etherscan.io/block/14641000
[24]
eth_getCode: https://eth.wiki/json-rpc/API#eth_getcode
[25]
Uniswap token: https://etherscan.io/address/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984#code
[26]
区块高度10861674: https://etherscan.io/block/10861674
[27]
销毁: https://docs.soliditylang.org/en/v0.4.21/introduction-to-smart-contracts.html#self-destruct
[28]
eth_getTransactionCount: https://eth.wiki/json-rpc/API#eth_gettransactioncount
[29]
区块高度14674300: https://etherscan.io/block/14674300
[30]
eth_getStorageAt: https://eth.wiki/json-rpc/API#eth_getstorageat
[31]
简单存储合约: https://etherscan.io/address/0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64#readContract
[32]
区块高度 7500943: https://etherscan.io/tx/0xc6d494c08ee2a0144e6241f86e6128dcc6888116a863a865074af8b25841a608#eventlog
[33]
eth_call: https://eth.wiki/json-rpc/API#eth_call
[34]
区块高度 14000000: https://etherscan.io/block/14000000
[35]
Chainlink token: https://etherscan.io/address/0x514910771AF9Ca656af840dff83E8264EcF986CA
[36]
Chainlink VRF coordinator: https://etherscan.io/address/0x271682DEB8C4E0901D1a1550aD2e64D568E69909
[37]
Duet Protocol: https://duet.finance/?utm_souce=learnblockchain