{"content":{"title":"探究EVM全节点与存档节点的不同","body":"> * 原文：https://chainstack.com/evm-nodes-a-dive-into-the-full-vs-archive-mode/\r\n> * 译文出自：[登链翻译计划](https://github.com/lbc-team/Pioneer)\r\n> * 译者：[翻译小组](https://learnblockchain.cn/people/412)\r\n> * 校对：[Tiny 熊](https://learnblockchain.cn/people/15)\r\n> * 本文永久链接：[learnblockchain.cn/article…](https://learnblockchain.cn/article/4457)\r\n\r\n\r\n\r\n![img](https://img.learnblockchain.cn/pics/20220728095459.jpg!/scale/60)\r\n\r\n## 简介\r\n\r\n基于以太坊虚拟机（EVM）的网络通常可以运行两种类型的节点：一个全节点和一个存档节点。\r\n\r\n许多流行网络基于EVM：包括[以太坊](https://learnblockchain.cn/categories/ethereum/) 、Polygon 、[BNB Smart Chain](https://learnblockchain.cn/article/2626)、C-Chain of Avalanche 、Fantom、Harmony等。\r\n\r\n全节点和存档节点两者都存储完整的区块链数据，可用于重放网络状态，但区别在于，存档节点另外将每个区块的网络状态存储在一个存档中，可供查询。\r\n\r\n这就是简短的解释。\r\n\r\n在这篇文章中，我们将深入探讨全节点和存档节点的一些细节、区别和操作实例。\r\n\r\n## Geth和Erigon \r\n\r\n首先，简单介绍一下节点客户端：\r\n\r\n[Go 以太坊](https://github.com/ethereum/go-ethereum)(Geth)是迄今为止最流行的基于EVM的网络的客户端软件，可能在整个区块链领域都是如此。\r\n\r\n对于以太坊主网，你可以在[ethernodes crawler data](https://www.ethernodes.org/)查看节点客户端分布。\r\n\r\nChainstack支持使用Geth客户端或[Erigon客户端](https://github.com/ledgerwatch/erigon)（以前是Turbo-Geth）来运行以太坊节点--后者是另一个Go实现客户端，专注于效率，是第二流行的客户端。\r\n\r\n在这篇文章中，我们将重点介绍Geth和Erigon在全节点和存档节点模式下的实现。\r\n\r\n## 全节点和存档节点\r\n\r\n让我们深入了解一下细节：\r\n\r\n### **全节点**\r\n\r\n- 存储完整的区块链数据。\r\n- 验证所有区块和状态。\r\n- 所有的状态都可以从一个完整的节点重新生成。\r\n\r\n一个完整的EVM节点保持区块链的当前状态，并处理读取调用（view）和状态改变的调用（交易）。一个完整的节点会修剪区块链数据，以节省磁盘空间并减少同步时间，但在必要时存储足够的数据来重新计算链上的事件，使得它的运行效率更高，但它也限制请求特定数量的区块的数据（通常为128个区块）。\r\n\r\n例如，在以太坊主网上，产生一个新区块的平均时间约为13秒，你只能检索过去28-29分钟的链状态。虽然在理论上，你可以使用一个完整的节点来重新计算所有的中间状态，但这将需要特别长的时间，而且将是非常密集的资源，你的节点可能会耗尽内存而停止。\r\n\r\n### 默认的返回状态和 `Missing trie node`的错误\r\n\r\n根据所访问的链和所使用的客户端，被限制能访问多少个可用的区块状态有所不同：\r\n\r\n- **以太坊**：128个区块 \r\n- **Polygon**: 128个区块 \r\n- **BNB智能链：** 128个区块 \r\n- **Avalanche** C-Chain：32个区块\r\n- **Fantom**: Go Opera客户端不修剪信息，所以在全节点和存档节点之间没有区别。\r\n- **Harmony**: 128个区块\r\n\r\n如果你试图查询一个不能从全节点访问的区块，你会收到一个**missing trie node**的错误。\r\n\r\n一般来说，收到**missing trie node**的错误意味着你需要一个存档节点。\r\n\r\n## 存档节点 \r\n\r\n- 存储所有保存在全节点中的东西，并建立一个历史状态的档案。\r\n- 他们是配置为在存档模式下运行的全节点。\r\n\r\n存档节点本质上包含了整个区块链的快照，并持有从创世区块（第一个被开采的区块）开始的所有先前的网络状态。这使得存档节点非常适合快速查询历史数据，而不需要状态重建，这对于创建分析工具、DApps和其他需要快速访问历史的服务的开发者来说是理想的。\r\n\r\n由于存档节点保留了整个链的状态，它们的大小也比全节点大得多。在撰写本文时，以太坊主网的规模约为10TB([etherscan.io](https://etherscan.io/chartsync/chainarchive))。\r\n\r\n要启动一个新的存档节点，系统需要同步所有这些数据，然后才能开始在网络上运行。这导致了高额的启动和维护成本，鉴于此时需要几个月的时间来完成同步过程，而且为了跟上不断增长的磁盘大小需求，必须不断进行维护。\r\n\r\n\r\n![code-1024x421.png](https://img.learnblockchain.cn/attachments/2022/07/BdgrUoyd62e2497bb225f.png)\r\n\r\n\r\n### 存档的主网状态大小（供参考）\r\n\r\n请注意，这些数据一直在增长，这些数据在本文发表时是有效的。\r\n\r\n- 以太坊主网：~12 TB\r\n- Polygon主网：~16 TB\r\n- BNB智能链：~7 TB\r\n- Fantom主网：~4 TB\r\n- Harmony主网：~20 TB\r\n- Avalanche主网：~3 TB  \r\n\r\n请注意，BNB智能链使用Erigon客户端，与Geth相比，占用较小空间\r\n\r\n![Screen-Shot-2022-05-12-at-16.png](https://img.learnblockchain.cn/attachments/2022/07/m5YtEjEo62e2499a14e88.png)\r\n\r\n这显示了所有这些链包含了多少数据，如果你想自己建立节点，你需要下载所有这些数据，并在能够运行节点之前对其进行验证。这对于一个存档节点来说可能需要几个月的时间。\r\n\r\n## 在几分钟内部署一个节点 \r\n\r\n由于Chainstack等第三方节点的存在，你可以在几分钟内部署自己的节点。使用我们的快速同步技术[称为Bolt](https://chainstack.com/introducing-bolt-the-chainstack-technology-made-for-simple-node-synchronization/)，Chainstack允许你在短短几分钟内部署一个全节点或存档节点，节省了数周或数月的工作和资源。\r\n\r\n要获得一个节点：\r\n\r\n1. [在Chainstack注册](https://console.chainstack.com/user/account/create)。\r\n2. [部署一个全节点或存档节点](https://docs.chainstack.com/platform/join-a-public-network)。\r\n\r\n## 获取过去状态的方法\r\n\r\n现在很明显，要访问比最后128个块更早的数据，我们需要使用一个存档节点。\r\n\r\n以下[Geth JSON-RPC方法](https://eth.wiki/json-rpc/API#the-default-block-parameter)包括一个参数，允许用户指定从哪个块检索数据：\r\n\r\n- [eth_getBalance](https://eth.wiki/json-rpc/API#eth_getbalance) \r\n- [eth_getCode](https://eth.wiki/json-rpc/API#eth_getcode) \r\n- [eth_getTransactionCount](https://eth.wiki/json-rpc/API#eth_gettransactioncount) \r\n- [eth_getStorageAt](https://eth.wiki/json-rpc/API#eth_getstorageat) \r\n- [eth_call](https://eth.wiki/json-rpc/API#eth_call) \r\n\r\n让我们依次看看这些方法，并尝试调用一下。\r\n\r\n同样，如果你需要快速访问一个存档节点，可以在[Chainstack获取一个](https://console.chainstack.com/user/account/create)。\r\n\r\n### eth_getBalance\r\n\r\n检索一个特定时间点（区块）的地址余额，详情请见以太坊 Wiki：[eth_getBalance](https://eth.wiki/json-rpc/API#eth_getbalance)\r\n\r\n#### **Web3.py**\r\n\r\n使用web3.py从区块编号1的状态中检索地址余额。\r\n\r\n在一个全节点上运行这段代码将返回一个错误，因为我们获取[区块高度1](https://etherscan.io/block/1)时一个地址的余额：\r\n\r\n```python\r\nfrom web3 import Web3 \r\nnode_url = \"CHAINSTACK_ARCHIVE_NODE_URL\" \r\nweb3 = Web3(Web3.HTTPProvider(node_url)) \r\nbalance = web3.eth.get_balance(\"0x9D00f1630b5B18a74231477B7d7244f47138ab47\", 1) \r\nprint(web3.fromWei(balance, \"ether\"))\r\n```\r\n\r\n我们仍然可以在一个全节点上运行`eth_getBalance`，但是不能回溯到超过128个块。\r\n\r\n#### **Web3.js**\r\n\r\n使用web3.js获取一个地址余额。在下面是获取[区块块号14641000](https://etherscan.io/block/14641000)的地址余额：\r\n\r\n```javascript\r\nvar Web3 = require('web3')\r\nvar node_URL = 'CHAINSTACK_ARCHIVE_NODE_URL'\r\nvar web3 = new Web3(node_URL)\r\nweb3.eth.getBalance('0x9D00f1630b5B18a74231477B7d7244f47138ab47', 14641000, (err, balance) => {\r\n    console.log(web3.utils.fromWei(balance, 'ether'))\r\n})\r\n```\r\n\r\n`fromWei`方法用于将从节点（Wei）收到的数字转换成对我们可读的单位表示（ether）的数字。\r\n\r\n#### **cURL**\r\n\r\n使用cURL检索一个地址余额。在下面查询的是[区块编号14641000](https://etherscan.io/block/14641000)的状态。\r\n\r\n注意，区块高度和返回值都是十六进制:\r\n\r\n```bash\r\ncurl CHAINSTACK_ARCHIVE_NODE_URL \\\r\n  -X POST \\\r\n  -H \"Content-Type: application/json\" \\\r\n  --data '{\"method\":\"eth_getBalance\",\"params\":[\"0x9D00f1630b5B18a74231477B7d7244f47138ab47\", \"0xDF6768\"],\"id\":1,\"jsonrpc\":\"2.0\"}'\r\n```\r\n\r\n### **eth_getCode** \r\n\r\n返回一个智能合约的编译字节码,详情请见以太坊 Wiki[eth_getCode](https://eth.wiki/json-rpc/API#eth_getcode)。\r\n\r\n下面的例子将得到[Uniswap token](https://etherscan.io/address/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984#code)在部署时第一个区块的状态下的字节码，[区块高度10861674](https://etherscan.io/block/10861674)。\r\n\r\n#### Web3.py\r\n\r\n```python\r\nfrom web3 import Web3 \r\nnode_url = \"CHAINSTACK_ARCHIVE_NODE_URL\" \r\nweb3 = Web3(Web3.HTTPProvider(node_url)) \r\ncode = web3.eth.get_code(\"0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984\", 10861674) \r\nprint(code) \r\n```\r\n\r\n#### Web3.js\r\n\r\n```javascript\r\nvar Web3 = require('web3')\r\nvar node_URL = 'CHAINSTACK_ARCHIVE_NODE_URL'\r\nvar web3 = new Web3(node_URL)\r\nweb3.eth.getCode('0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', 10861674, (err, byte) => {\r\n    console.log(byte)\r\n})\r\n```\r\n\r\n#### **cURL**\r\n\r\n注意，区块高度是十六进制：\r\n\r\n```css\r\ncurl CHAINSTACK_ARCHIVE_NODE_URL \\\r\n  -X POST \\\r\n  -H \"Content-Type: application/json\" \\\r\n  --data '{\"method\":\"eth_getCode\",\"params\":[\"0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984\", \"0xA5BC6A\"],\"id\":1,\"jsonrpc\":\"2.0\"}'\r\n```\r\n\r\n`getCode`RPC方法可以用来验证合约是否被正确部署或[销毁](https://docs.soliditylang.org/en/v0.4.21/introduction-to-smart-contracts.html#self-destruct)。\r\n\r\n### eth_getTransactionCount\r\n\r\n返回在特定区块下从一个地址发送的交易数量。详情请见以太坊 Wiki [eth_getTransactionCount](https://eth.wiki/json-rpc/API#eth_gettransactioncount)。\r\n\r\n下面的例子将获取一个地址在[区块高度14674300](https://etherscan.io/block/14674300)状态下的交易数量（nonce）。\r\n\r\n#### Web3.py\r\n\r\n```python\r\nfrom web3 import Web3 \r\nnode_url = \"CHAINSTACK_ARCHIVE_NODE_URL\" \r\nweb3 = Web3(Web3.HTTPProvider(node_url)) \r\ntx_count = web3.eth.get_transaction_count(\"0x9D00f1630b5B18a74231477B7d7244f47138ab47\", 14674300) \r\nprint(tx_count) \r\n```\r\n\r\n#### Web3.js\r\n\r\n```javascript\r\nvar Web3 = require('web3');\r\nvar node_URL = 'CHAINSTACK_ARCHIVE_NODE_URL';\r\nvar web3 = new Web3(node_URL);\r\nweb3.eth.getTransactionCount('0x9D00f1630b5B18a74231477B7d7244f47138ab47', 14674300, (err, count) => {\r\n    console.log(count)\r\n})\r\n```\r\n\r\n#### cURL\r\n\r\n注意，区块编号和返回值都是十六进制：\r\n\r\n```bash\r\ncurl CHAINSTACK_ARCHIVE_NODE_URL \\\r\n  -X POST \\\r\n  -H \"Content-Type: application/json\" \\\r\n  --data '{\"method\":\"eth_getTransactionCount\",\"params\":[\"0x9D00f1630b5B18a74231477B7d7244f47138ab47\", \"0xDFE97C\"],\"id\":1,\"jsonrpc\":\"2.0\"}'\r\n```\r\n\r\n`getTransactionCount` RPC方法用于获取一个地址的nonce，nonce是一个整数值，代表该账户发送了多少交易。同样用来避免重复交易。\r\n\r\n### eth_getStorageAt\r\n\r\n返回一个给定地址的存储位置的值，详情请见以太坊 Wiki [eth_getStorageAt](https://eth.wiki/json-rpc/API#eth_getstorageat)。\r\n\r\n下面的例子将返回[简单存储合约](https://etherscan.io/address/0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64#readContract)的存储值。\r\n\r\n最后一次值变化是在[区块高度 7500943](https://etherscan.io/tx/0xc6d494c08ee2a0144e6241f86e6128dcc6888116a863a865074af8b25841a608#eventlog)，所以你可以把它作为一个参考点，以及检索不同区块高度的存储值。\r\n\r\n#### Web3.py\r\n\r\n```python\r\nfrom web3 import Web3\r\nnode_url = \"CHAINSTACK_ARCHIVE_NODE_URL\"\r\nweb3 = Web3(Web3.HTTPProvider(node_url))\r\nstorage = web3.eth.get_storage_at(\"0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64\", 0, 7500943)\r\nprint(storage.decode(\"ASCII\"))\r\n```\r\n\r\n#### Web3.js\r\n\r\n```javascript\r\nvar Web3 = require('web3'); \r\nvar node_URL = 'CHAINSTACK_ARCHIVE_NODE_URL'; \r\nvar web3 = new Web3(node_URL); \r\nweb3.eth.getStorageAt('0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64', 0, 7500943).then(result => {\r\n  console.log(web3.utils.hexToAscii(result));\r\n});\r\n```\r\n\r\n#### cURL\r\n\r\n  注意，区块编号和返回值都是十六进制：\r\n\r\n```bash\r\ncurl CHAINSTACK_ARCHIVE_NODE_URL \\\r\n  -X POST \\\r\n  -H \"Content-Type: application/json\" \\\r\n  --data '{\"method\":\"eth_getStorageAt\",\"params\":[\"0x954De93D9f1Cd1e2e3AE5964F614CDcc821Fac64\", \"0\", \"0x72748F\"],\"id\":1,\"jsonrpc\":\"2.0\"}'\r\n```\r\n\r\n### eth_call\r\n\r\n  在区块链上进行只读调用，不改变任何状态。 详情请见以太坊 Wiki [eth_call](https://eth.wiki/json-rpc/API#eth_call)。\r\n\r\n  下面的例子为[区块高度 14000000](https://etherscan.io/block/14000000)的[Chainlink token](https://etherscan.io/address/0x514910771AF9Ca656af840dff83E8264EcF986CA)地址调用[Chainlink VRF coordinator](https://etherscan.io/address/0x271682DEB8C4E0901D1a1550aD2e64D568E69909)的`balanceOf`函数：\r\n\r\n  #### **Web3.py**\r\n\r\n```swift\r\nimport json\r\nfrom web3 import Web3\r\nnode_url = \"CHAINSTACK_ARCHIVE_NODE_URL\"\r\nweb3 = Web3(Web3.HTTPProvider(node_url))\r\nabi=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\"}]')\r\naddress = \"0x514910771AF9Ca656af840dff83E8264EcF986CA\"\r\ncontract = web3.eth.contract(address=address, abi=abi)\r\nbalance = contract.functions.balanceOf('0x271682DEB8C4E0901D1a1550aD2e64D568E69909').call(block_identifier=14000000)\r\nprint(web3.fromWei(balance, 'ether'))\r\n```\r\n\r\n  #### **Web3.js**\r\n\r\n```javascript\r\nconst Web3 = require('web3');\r\nconst web3 = new Web3(new Web3.providers.HttpProvider(\"CHAINSTACK_ARCHIVE_NODE_URL\"));\r\nweb3.eth.defaultBlock = 14000000;\r\nweb3.eth.call({\r\n        to: \"0x514910771AF9Ca656af840dff83E8264EcF986CA\",\r\n        data: \"0x70a08231000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909\"\r\n    })\r\n    .then(result => {\r\n        console.log(web3.utils.fromWei(result));\r\n    });\r\n```\r\n\r\n  #### **cURL**\r\n\r\n  注意，区块编号和返回值都是十六进制：\r\n\r\n```bash\r\ncurl CHAINSTACK_ARCHIVE_NODE_URL \\\r\n  -X POST \\\r\n  -H \"Content-Type: application/json\" \\\r\n  --data '{\"method\":\"eth_call\",\"params\":[{\"from\":null,\"to\":\"0x514910771AF9Ca656af840dff83E8264EcF986CA\",\"data\":\"0x70a08231000000000000000000000000271682deb8c4e0901d1a1550ad2e64d568e69909\"}, \"0xD59F80\"],\"id\":1,\"jsonrpc\":\"2.0\"}'\r\n```\r\n\r\n## 结论\r\n\r\n存档节点持有区块链的 \"历史\"，并拥有从创世区块开始网络中的每个先前状态的记录。这意味着可以快速访问历史数据，使用Chainstack，你可以轻而易举地建立一个存档节点! \r\n\r\n 存档节点是一个很好的开发工具，特别是当你需要查询过去的数据时，例如，如果你正在使用Hardhat、Ganache和其他开发框架来分叉主网，用于运行本地模拟区块链进行测试和开发，或者如果你在创建一个区块链资源管理器、区块链分析工具、用The Graph等协议进行区块链索引等等，因为你可以即时访问全链。\r\n\r\n如果你正在DApp，通常最新128个区块内的数据就足够了，这是仅需要一个全节点。\r\n\r\n\r\n\r\n------\r\n本翻译由 [DeCert.me](https://decert.me/) 赞助支持。"},"author":{"user":"https://learnblockchain.cn/people/412","address":"0x9e64a306aB319811C5a1270F2CA9f6E1e4857c84"},"history":null,"timestamp":1715264209,"version":1}