全节点主要用于获取当前状态的数据,存档节点则可以用户获取某历史时刻下的状态数据。
基于以太坊虚拟机(EVM)的网络通常可以运行两种类型的节点:一个全节点和一个存档节点。
许多流行网络基于EVM:包括以太坊 、Polygon 、BNB Smart Chain、C-Chain of Avalanche 、Fantom、Harmony等。
全节点和存档节点两者都存储完整的区块链数据,可用于重放网络状态,但区别在于,存档节点另外将每个区块的网络状态存储在一个存档中,可供查询。
这就是简短的解释。
在这篇文章中,我们将深入探讨全节点和存档节点的一些细节、区别和操作实例。
首先,简单介绍一下节点客户端:
Go 以太坊(Geth)是迄今为止最流行的基于EVM的网络的客户端软件,可能在整个区块链领域都是如此。
对于以太坊主网,你可以在ethernodes crawler data查看节点客户端分布。
Chainstack支持使用Geth客户端或Erigon客户端(以前是Turbo-Geth)来运行以太坊节点--后者是另一个Go实现客户端,专注于效率,是第二流行的客户端。
在这篇文章中,我们将重点介绍Geth和Erigon在全节点和存档节点模式下的实现。
让我们深入了解一下细节:
一个完整的EVM节点保持区块链的当前状态,并处理读取调用(view)和状态改变的调用(交易)。一个完整的节点会修剪区块链数据,以节省磁盘空间并减少同步时间,但在必要时存储足够的数据来重新计算链上的事件,使得它的运行效率更高,但它也限制请求特定数量的区块的数据(通常为128个区块)。
例如,在以太坊主网上,产生一个新区块的平均时间约为13秒,你只能检索过去28-29分钟的链状态。虽然在理论上,你可以使用一个完整的节点来重新计算所有的中间状态,但这将需要特别长的时间,而且将是非常密集的资源,你的节点可能会耗尽内存而停止。
Missing trie node
的错误根据所访问的链和所使用的客户端,被限制能访问多少个可用的区块状态有所不同:
如果你试图查询一个不能从全节点访问的区块,你会收到一个missing trie node的错误。
一般来说,收到missing trie node的错误意味着你需要一个存档节点。
存档节点本质上包含了整个区块链的快照,并持有从创世区块(第一个被开采的区块)开始的所有先前的网络状态。这使得存档节点非常适合快速查询历史数据,而不需要状态重建,这对于创建分析工具、DApps和其他需要快速访问历史的服务的开发者来说是理想的。
由于存档节点保留了整个链的状态,它们的大小也比全节点大得多。在撰写本文时,以太坊主网的规模约为10TB(etherscan.io)。
要启动一个新的存档节点,系统需要同步所有这些数据,然后才能开始在网络上运行。这导致了高额的启动和维护成本,鉴于此时需要几个月的时间来完成同步过程,而且为了跟上不断增长的磁盘大小需求,必须不断进行维护。
请注意,这些数据一直在增长,这些数据在本文发表时是有效的。
请注意,BNB智能链使用Erigon客户端,与Geth相比,占用较小空间
这显示了所有这些链包含了多少数据,如果你想自己建立节点,你需要下载所有这些数据,并在能够运行节点之前对其进行验证。这对于一个存档节点来说可能需要几个月的时间。
由于Chainstack等第三方节点的存在,你可以在几分钟内部署自己的节点。使用我们的快速同步技术称为Bolt,Chainstack允许你在短短几分钟内部署一个全节点或存档节点,节省了数周或数月的工作和资源。
要获得一个节点:
现在很明显,要访问比最后128个块更早的数据,我们需要使用一个存档节点。
以下Geth JSON-RPC方法包括一个参数,允许用户指定从哪个块检索数据:
让我们依次看看这些方法,并尝试调用一下。
同样,如果你需要快速访问一个存档节点,可以在Chainstack获取一个。
检索一个特定时间点(区块)的地址余额,详情请见以太坊 Wiki:eth_getBalance
使用web3.py从区块编号1的状态中检索地址余额。
在一个全节点上运行这段代码将返回一个错误,因为我们获取区块高度1时一个地址的余额:
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获取一个地址余额。在下面是获取区块块号14641000的地址余额:
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检索一个地址余额。在下面查询的是区块编号14641000的状态。
注意,区块高度和返回值都是十六进制:
curl CHAINSTACK_ARCHIVE_NODE_URL \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getBalance","params":["0x9D00f1630b5B18a74231477B7d7244f47138ab47", "0xDF6768"],"id":1,"jsonrpc":"2.0"}'
返回一个智能合约的编译字节码,详情请见以太坊 Wikieth_getCode。
下面的例子将得到Uniswap token在部署时第一个区块的状态下的字节码,区块高度10861674。
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)
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 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方法可以用来验证合约是否被正确部署或销毁。
返回在特定区块下从一个地址发送的交易数量。详情请见以太坊 Wiki eth_getTransactionCount。
下面的例子将获取一个地址在区块高度14674300状态下的交易数量(nonce)。
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)
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 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是一个整数值,代表该账户发送了多少交易。同样用来避免重复交易。
返回一个给定地址的存储位置的值,详情请见以太坊 Wiki eth_getStorageAt。
下面的例子将返回简单存储合约的存储值。
最后一次值变化是在区块高度 7500943,所以你可以把它作为一个参考点,以及检索不同区块高度的存储值。
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"))
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 CHAINSTACK_ARCHIVE_NODE_URL \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getStorageAt","params":["0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64", "0", "0x72748F"],"id":1,"jsonrpc":"2.0"}'
在区块链上进行只读调用,不改变任何状态。 详情请见以太坊 Wiki eth_call。
下面的例子为区块高度 14000000的Chainlink token地址调用Chainlink VRF coordinator的balanceOf
函数:
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'))
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 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个区块内的数据就足够了,这是仅需要一个全节点。
本翻译由 DeCert.me 赞助支持。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!