---
title: "深入解析区块链数据"
description: "了解区块链数据在 web3 基础设施和应用中的生成、存储、访问和使用方式。"
---

# 深入解析区块链数据

每条区块链都保存着一份不可篡改的交易和事件记录。Web3 应用依赖区块链数据来实现告警、仪表盘、决策支持，或开发新功能。这些数据对 Web3 至关重要，因为它支撑着从去中心化应用、基础设施到 NFT 的一切。

本指南将涵盖区块链数据的各个方面，从链上和链下数据，到区块链索引和 subgraph。读完本指南后，你应该能更深入地理解区块链数据是如何生成、存储和访问的。

## **什么是链上数据？**

链上数据就是存储在区块链网络上的所有信息。它是网络上曾发生的所有交易的不可篡改记录，并对所有人公开可见。链上数据有很多不同的类型，例如：

- **交易数据：** 涵盖每笔区块链交易的相关信息，例如发送方和接收方、转账金额以及交易费用。
- 区块数据：涵盖每个区块的相关信息，例如前一区块的哈希值、区块内包含的交易、区块的时间戳，以及矿工费用和奖励。
- **智能合约数据：** 涵盖已部署在区块链上的所有智能合约的相关信息，例如合约代码本身、合约状态，以及合约发出的事件。

与链下数据不同，链上数据无法被更改，这对于全面了解区块链网络的状态很重要。这些数据可用于追踪区块链上资产的流动、验证交易是否成功完成，以及生成有关网络活动的洞察。

链上数据面临的挑战在于，有效地访问它可能相当麻烦。尽管链上数据是随时可获取的，但它是以机器可读的格式编码的；这种方式优先考虑安全性，却牺牲了人类可读性。人类可读的格式可以是 JSON、XML 等类型，而这正是 ABI（应用二进制接口）发挥作用的地方。

### **数据结构是如何定义的？**

如前所述，链上数据的存储方式与常规数据不同。它通常以字节码等机器可读格式存储。

ABI（应用二进制接口）帮助开发者监控数据并将其解码为人类可读的格式。智能合约中的数据结构是通过 ABI 定义的。ABI 起到函数选择器的作用，用于定义如何与智能合约交互，以及每个函数所接受和返回的数据类型。换句话说，它是一种以人类和机器都能轻松理解的方式表示数据结构的标准方法。

### **区块链数据存储在哪里？**

区块链数据通常存储在分布式账本中，这意味着它并非存储在单一位置，而是分布在一个节点网络上。节点是存储区块链数据并确保其安全性的基础组成部分，因为每个节点都维护着一份数据副本。下面概述了几种不同类型的节点及其大致工作原理：

- 全节点：顾名思义，全节点存储完整的区块链历史记录以及网络的最新状态（最近的 128 个区块）。所有客户端在验证传入交易时都需要用到这个最新状态。理论上，所有先前的状态都可以由全节点推导出来，但这需要消耗大量的计算资源。当开发者需要访问区块链的最新数据和状态时，应查询全节点。举例来说，在 Ethereum 上，生成一个新区块的平均时间约为 13 秒，你只能检索到最近 28-29 分钟内的链状态。
- 归档节点：除了完整的区块链历史记录外，[归档节点还保存了每个区块的历史状态记录](https://www.alchemy.com/overviews/archive-nodes)。与全节点相比，这使归档节点能够更高效地处理历史数据请求。当需要历史数据时，开发者应查询归档节点，因为它不像全节点那样需要重新生成状态。对于构建分析工具以及其他需要快速访问历史数据的工具的开发者来说，归档节点是理想选择。
- 轻节点：这类节点只存储区块头，即在网络上进行交易所需的最少数据。当需要从区块头获取基本区块链数据时，开发者可以选择查询轻节点。

节点并不是存储数据的唯一方式。数据也可以存储在链下的数据库、云存储服务，甚至本地服务器中。将数据存储在链下可能不如链上安全，但对许多应用来说仍然很有用，因为访问成本更低、速度更快。当数据存储在链下时，通常只有定位链下数据所需的信息才会存储在区块链上。

### 智能合约与区块链存储

智能合约存储在节点内的区块链上，然而智能合约本身也具备数据存储机制。智能合约中的数据存储遵循所谓的合约存储布局。[合约存储布局指的是决定合约存储变量在长期内存中如何排列的规则](https://www.alchemy.com/docs/smart-contract-storage-layout)。

对于 [Solidity](https://www.alchemy.com/overviews/solidity)（一种用于构建智能合约的高级编程语言）而言，有 3 种不同类型的内存可以指示 EVM 将变量存储在何处：memory、calldata 和 storage。

Memory：用于存储函数执行期间所需的临时数据

Calldata：一种特殊的数据位置，包含函数参数

Storage：数据被永久存储在区块链上的位置

### **数据存储与文件存储**

数据存储从最简单的形式来说，就是保存数据以便日后检索和使用的过程。而文件存储则不同于数据存储，指的是将实际文件存储在与文件元数据不同的位置。这种分离通常是为了提升性能、降低成本或增强安全性。

将文件存储与数据存储分离的一种常见方式是使用去中心化文件存储系统，例如 IPFS 或 Arweave。这些系统允许用户将文件存储在分布式计算机网络中，并且通过减少需要存储在区块链本身上的数据量来实现成本效益。

从宏观层面看，关于文件的元数据存储在链上，而文件本身存储在链下。当应用需要访问文件时，可以从元数据中检索 IPFS 或 Arweave URL。随后，应用可以使用该 URL 从 IPFS 或 Arweave 下载文件。

#### **什么是 IPFS？**

IPFS 使用一种基于内容寻址的系统，每个文件都根据 CID（内容标识符）进行识别。CID 是唯一的哈希值，无论文件存储在哪里，都始终指向同一个文件。

这意味着，如果文件发生变化或被更新，哈希值也会随之改变。这种基于内容寻址的系统使得文件可以根据其 CID 而非存储位置来存储和检索。

IPFS 工作原理的大致流程如下：

1. 为文件创建一个 CID
1. 文件随后被上传到 IPFS 网络
1. IPFS 在 DHT（分布式哈希表）中存储关于网络中哪个节点持有与该 CID 关联的文件的信息
1. 之后可以使用哈希值查询 DHT，以找到存储该文件的节点
1. CID 被存储在代币智能合约中

#### **Arweave**

Arweave 是另一种分布式存储解决方案，它同样使用 CID 来存储和访问内容，并在元数据中引用内容。主要区别在于，Arweave 在激励机制和永久性方面采取了不同的方法，通过激励节点永久保存数据。

### **数据发布、数据存储与数据可用性**

要更好地理解区块链数据，我们还需要理解数据发布、数据存储和数据可用性分别意味着什么。简单来说：**数据发布**是将数据在区块链上向他人公开的过程，**数据存储**是将数据保存在区块链上的过程，**数据可用性**是确保区块链网络中所有参与者都能访问到数据的保障。

数据可用性之所以重要，是因为当验证者向 Ethereum 区块链添加区块时，他们必须向网络中的其他验证者广播该区块的所有交易数据。验证者的任务是执行所有交易数据，这意味着区块链能够处理的交易量取决于其验证者的执行能力——这就是数据可用性问题的核心所在。

数据可用性问题的核心之一，是如何在无法访问整个区块（即数据发布）的情况下，判断一个区块是否已被发布。

数据发布面临的主要挑战在于，区块生产者不会在包含未知内容的区块之上继续生产区块。这意味着含有未发布数据的区块可能会被完全忽略。一旦数据被发布，数据存储的问题就随之出现，然而目前尚不清楚全节点会将数据保存多久。这可能令人担忧，因为我们无法强制节点保留数据，从而进一步加剧了对数据可用性的担忧。

#### 模块化区块链与替代数据可用性方案

模块化区块链是专注于特定功能的区块链。例如，某个模块化区块链可能专注于数据可用性，而将执行或共识等其他任务交给其他区块链或系统来处理。

模块化区块链通过采用离链数据存储、数据压缩、分片等多种技术，创建替代性的数据可用性层，使发布 calldata 的成本比发布到 Ethereum 上更低。

使用替代数据可用性层的模块化区块链的一个例子是 EigenDA。EigenDA 是一个建立在 Arweave 之上的去中心化数据可用性层。EigenDA 允许用户将 calldata 发布到 Arweave，然后向 Ethereum 证明该 calldata 已经发布。这使用户无需支付在链上存储 calldata 所需的高昂 gas 费用，就能将 calldata 发布到 Ethereum。

### **链上数据的类型**

在了解了什么是链上数据之后，我们将进一步深入探讨不同类型的链上数据，以及它们是如何生成、存储和访问的。

#### **什么是交易数据？**

交易数据包含与区块链上一笔交易相关的所有信息，例如：

- 发送方
- 接收方
- 转账金额
- 交易费用
- 交易时间戳

每当用户在区块链上发起交易时，就会生成这类数据。随后，这些数据会被广播到网络节点，以验证交易并将其添加到账本中。

交易数据可以通过一种称为 Merkle 树的树形数据结构进行存储和验证。Merkle 树是一种支持快速数据验证的二叉树，树中的每个节点都是其所包含数据的哈希值。要验证 Merkle 树中数据的完整性，只需要该树的根哈希即可。将数据存储在 Merkle 树中有助于尽可能减小区块链的体积。由于 Merkle 树的细节还有很多，你可以在 [Alchemy 文档中阅读更多关于 Merkle 树的内容](https://www.alchemy.com/docs/merkle-trees-in-blockchains)。特别是对于 Ethereum，数据是使用 [Patricia Merkle Trie](https://www.alchemy.com/docs/patricia-merkle-tries) 存储的——这是一种基数树（Patricia trie）与 Merkle 树相结合的结构。

如果想快速访问交易数据，你可以直接使用区块链浏览器，例如查询 Ethereum 交易可使用 [Etherscan](https://www.alchemy.com/dapps/etherscan)。区块链浏览器允许用户查看和搜索所有交易数据，可用于追踪代币的流动、识别欺诈交易、开发区块链应用等等。要查找特定交易的数据，你需要该交易的哈希值。如果你的应用需要定期访问区块链数据，Alchemy 可以提供帮助。

#### **元数据**

元数据是提供关于区块链上交易和资产额外信息的数据。这可能包括以下额外细节：

- 资产的名称或符号
- 资产的总供应量
- 资产的所有权历史
- 资产的合约地址

与交易数据不同，元数据并非区块链运行所必需的，但它对开发者构建区块浏览器、钱包和仪表盘等应用非常有用。元数据可以由智能合约或区块链按定义自动生成（例如交易元数据），也可以由用户手动定义（例如资产元数据）。

为了访问元数据，开发者可以使用 **getMetadata** 查询。要使用这些查询，开发者需要用到区块链 API，例如 [Alchemy API](https://www.alchemy.com/docs/reference/nft-api-quickstart)。通过使用 **getMetadata** 查询，开发者可以构建各种应用，帮助用户理解并与区块链网络进行交互。

#### **事件数据**

事件数据指的是智能合约在执行交易时发出的数据。这些数据可以包含以下信息：

- 发生的事件类型
- 发出该事件的智能合约地址
- 关于该事件的详细信息（例如转移的代币数量、资产的新所有者等）

这些信息有助于开发者监控智能合约的活动，并可以通过日志（logs）来访问。日志是区块链上所有已发生事件的记录，由智能合约生成。这些日志可以在交易回执中找到，也可以通过向 [eth_getLogs](https://www.alchemy.com/docs/deep-dive-into-eth_getlogs) 发起请求来查看。

#### **Calldata**

Calldata 是调用智能合约函数时传递给该合约的数据。换句话说，它是一种临时数据存储形式，用于在外部调用者传入的函数参数被传递给智能合约之前暂存这些参数。Calldata 可以包含任何类型的数据，无论是整数、字符串、数组等等。它之所以重要，是因为它使智能合约之间以及智能合约与用户之间能够进行通信；例如，在一个 NFT 智能合约中，calldata 可用于将某个 NFT 的所有权转移给用户。

区块链上的所有操作都要收取 gas 费用，使用 calldata 也不例外。当一笔 L2 交易被发布到 Ethereum 上时，calldata 会包含在该交易中。这是因为 Ethereum 网络需要这些 calldata 来验证交易，并执行被调用的智能合约函数。calldata 所消耗的 gas 取决于 calldata 的大小以及其中所包含数据的类型。对于 Ethereum 而言，每个区块所能包含的最大 calldata 为 [1,048,576 字节](https://eips.ethereum.org/EIPS/eip-4488)。

#### **Blobs**

Blobs（二进制大对象）旨在通过让网络确认附加在区块上的 blob 携带正确的数据，使交易验证更加高效。Blobs 是随着 [proto-danksharding](https://www.alchemy.com/overviews/danksharding) 一同引入的，这是一项旨在降低 calldata 成本并增加每个区块 calldata 容量的提案。

据称，proto-danksharding 通过引入一种名为 blob-carrying transaction 的新交易类型，使区块链中的 calldata 变得更便宜。Blob-carrying transaction 与常规交易类似，但可以包含数据 blob。

Blob-carrying transaction 比常规交易更便宜，因为它们处理所需的 gas 更少。这是因为数据 blob 存储在链下，无需包含在交易本身中。

数据 blob 和 blob-carrying transaction 的引入，将使在 Ethereum 区块链上以更低成本存储和处理大量数据成为可能。

## **什么是区块链索引？**

一本书的索引包含了关键词和概念所在的页码。类似地，区块链索引是以便于搜索和查询的方式组织和存储区块链数据的过程。理解这一点对于理解区块链数据很重要，因为它使用户能够以更高效、更有效的方式访问和分析数据。

由于区块链遵循按时间顺序排列的结构，数据可能分散在众多区块中，容易变得错综复杂。索引旨在通过为区块链 *数据* 创建索引来解决这一问题。

这个索引是一个数据库，以针对搜索和查询进行优化的方式存储区块链数据的一个子集。为了对数据进行索引，存在多种不同的索引方法，例如：对交易相关信息进行索引、对地址进行索引、对智能合约交互进行索引等等。索引后的数据随后可以通过 GraphQL、Alchemy 和其他 Web3 协议提供的 API 供开发者访问。

### **常见的索引使用场景**

既然我们已经了解了区块链索引对于开发者更高效地搜索和查询数据有多大的作用，接下来让我们看看索引的一些常见使用场景。

**索引交易历史** —— 可用于追踪像 [Uniswap](https://www.alchemy.com/dapps/uniswap) 资金池这样的交易量和流动性，以及识别最大的交易者和巨鲸。

**用于分析和报告的索引** —— 可用于生成各种指标的报告，例如交易量、gas 费用和用户活动。在追踪和分析特定智能合约、加密货币、市场趋势的表现，或了解用户活动（例如活跃钱包数量、已处理交易数量等）方面，这尤其有帮助。

**元数据索引** —— 可用于追踪 NFT 的所有权和转移情况。一个实际的例子是 NFT 分析工具，你可以针对某个特定 NFT 系列的交易索引进行查询，以了解购买/所有权历史等相关细节。

**智能合约事件索引** —— 可用于追踪代币的流动（例如某个特定 ERC-20 代币的转移事件）、借贷活动，或更好地了解 [NFT 市场](https://www.alchemy.com/dapps/best/nft-marketplaces) 等具体场景。总体而言，对智能合约事件进行索引有助于我们监控智能合约的活动，从而有助于识别薄弱环节，甚至发现新应用和服务的机会。

#### **链下与链上索引**

索引可以存储在链上或链下，两者各有其优势和权衡。例如，Satsuma 就是一个被 Alchemy 收购的链上索引协议。Satsuma 使用 [GraphQL](https://www.alchemy.com/dapps/graphql)——一种用于 API 的查询语言，通过使用 subgraph 扫描网络区块和智能合约，从多个来源收集数据，并通过一次 API 调用即可完成。链下索引协议的工作方式，要么是将索引保存在节点的本地存储中（例如 SubQuery），要么是将其存储在 AWS 等传统云服务器中，这种方式可能比链上索引更快。无论是链下还是链上索引协议，开发者都可以轻松使用查询语言：

- **GraphQL** —— 开发者可以使用 GraphQL 查询 subgraph，获取某个特定 ERC20 代币的转移历史。
- SQL —— 开发者可以使用 SQL 查询链下索引，获取已部署在某条特定区块链上的所有智能合约列表。
- Elasticsearch —— 开发者可以使用 Elasticsearch 查询链下索引，获取某条特定区块链上最受欢迎的 NFT。

## **区块链数据是如何被访问的？**

在了解了链上数据及其存储方式之后，我们可以更深入地探讨开发者实际访问区块链数据的方式。

### **查询节点**

访问区块链数据最直接的方式之一是查询节点，不过这也可能是资源消耗最大的方式。

要查询节点，你必须使用 JSON-RPC 通过全节点或归档节点（如前所述，即包含完整区块链副本的节点）访问数据。要使用 JSON-RPC 查询节点，开发者必须向节点发送一个 JSON 对象，其中包含你想调用的方法、该方法的参数，以及 JSON-RPC 版本号。

这可以很容易地通过像 Alchemy 这样提供 JSON-RPC API 的解决方案来实现，用于查询节点。

<ImageBlock
  src="https://media.alchemy.com/1704096018-querying-nodes.png"
  alt="要查询节点中某账户的余额,需要向该节点发送以下 JSON 对象"
  width={1600}
  height={632}
  caption="要查询节点中某账户的余额,需要向该节点发送以下 JSON 对象(来源)"
/>

在查询节点以获取特定区块链事件时，事件过滤器也非常有用。要使用事件过滤器，你需要指定想要过滤的事件类型以及该事件的参数。使用事件过滤器的一种简便方式是通过 [Alchemy 的 Node API](https://www.alchemy.com/supernode)。Alchemy 的 Node API 是一项全托管服务，包含了运行节点所需的全部基础设施，以及便于与节点交互和查询节点的 API 和 SDK。

### **使用 Webhook 进行数据流式传输**

使用 webhook 进行数据流式传输是一种实时获取区块链数据更新的方式。事件数据可以通过自定义 webhook 和 webhook 变量进行流式传输。具体做法是：选择一个区块链索引服务（例如 Alchemy），在你的服务器上创建一个 webhook 端点，订阅相关事件，并配置 webhook 变量。Alchemy 特别允许用户创建[自定义 webhook](https://www.alchemy.com/docs/reference/custom-webhook-variables)，这些 webhook 可以由各种区块链事件触发，例如新交易、新智能合约部署，以及智能合约状态的变更。Alchemy 最近还[升级了其自定义 webhook](https://www.alchemy.com/blog/custom-webhooks-variables-filters-block-freshness)，帮助开发者更精确地缩小数据流范围，并通过变量轻松更新 webhook 查询。

### **查询 Subgraph**

Subgraph 是由社区创建的开源 API，用于从 Indexer、Curator 和 Delegator 处检索区块链数据。由于 subgraph 是使用 GraphQL 构建的，开发者可以使用 GraphQL API 来查询 subgraph。Subgraph 既可以托管，也可以自托管。托管的 subgraph 可以通过向 GraphQL API URL 发送 GraphQL 查询来查询。自托管的 subgraph 则可以通过将 subgraph 部署到 GraphQL 服务器上来查询。这可以通过像 Satsuma 这样的解决方案来实现，它允许开发者部署自己的 subgraph。

### **查询数据仓库**

数据仓库针对查询历史数据进行了优化，通常以结构化格式存储数据。而数据湖则不同，它存储大量通常是非结构化或半结构化的数据。[Dune Analytics](https://www.alchemy.com/dapps/dune-analytics) 是一款可用于查询、提取和可视化数据湖中数据的工具。Dune 通过提供诸如其数据集浏览器等工具来实现这一点，让你能够探索不同链上的数据、数据集、原始区块链数据等等。你还可以通过对数据库进行回填并通过自定义 webhook 流式传输数据，来创建自己的数据湖。

## **结论**

总而言之，理解区块链数据对于任何希望使用或构建 Web3 基础设施或应用的开发者都很有帮助。链上数据存储在区块链上，可以分为不同的类型，例如交易数据、元数据、事件数据、calldata 和 blob。这些数据随后存储在节点中，并可以根据不同的使用场景通过多种方式进行访问。
