中华街zhshops

探究EVM全节点与存档节点的不同

简介

基于以太坊虚拟机(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/


分享至 : QQ空间
收藏

0 个回复

您需要登录后才可以回帖 登录 | 立即注册