---
title: "資料準確性：Web3 中鮮為人知卻普遍存在的問題"
description: "探討資料準確性在 Web3 基礎設施中的重要性、現有解決方案的不足，以及 Alchemy 的應對方式。"
---

# 資料準確性：Web3 中鮮為人知卻普遍存在的問題

<ExternalVideo
  url="https://vimeo.com/728999085"
  provider="vimeo"
  providerUid="728999085"
/>

某個知名的 NFT 團隊因為向使用者顯示不一致的資訊而流失了數萬名使用者（_如上方範例_）。

其中一個規模最大的 DeFi 協議因為排查造成前端錯誤的 bug，損失了 200 萬美元。由於該協議無法找出根本原因，這個問題數週都沒有解決。

#### 這兩個情境都源自同一個問題：節點基礎設施缺乏準確性（accuracy），而這在 Web3 中是個普遍存在的問題。

無論你是工程師、創辦人、產品經理，甚至是使用者，如果節點準確性還不在你的關注範圍內，你就需要將它列為首要事項，你的應用程式才能成功。

## 背景介紹

區塊鏈是由以點對點網路相互通訊的節點所運行的。正是這種結構性元素，使[去中心化](https://sgrlaw.com/blockchain-decentralized-ledgers/)得以實現，而去中心化正是 web3 的核心價值主張之一：不依賴中央中介機構，而是由[數千個節點](https://etherscan.io/nodetracker)透過無需信任地互相傳播資訊來記錄交易。

為了向使用者提供正確且一致的結果，節點網路必須就區塊鏈的最新狀態達成共識。這在[點對點網路](https://medium.com/coinmonks/what-is-a-peer-to-peer-p2p-network-in-blockchain-3fe4417dfba2)中極具挑戰性，因為資訊不會同時抵達每一個節點。

<ExternalVideo
  url="https://vimeo.com/729065053"
  provider="vimeo"
  providerUid="729065053"
/>

這意味著，當某人與依賴節點提供資料的 dApp 互動時：

- 各節點提供的資料不總是一致 ➡️
- 使用者不總是能得到他們預期的結果

<ImageBlock
  src="https://media.alchemy.com/1704793834-nfts-not-showing-up-in-wallets-on-time.png"
  alt="NFT 沒有即時顯示在錢包中"
  width={1068}
  height={557}
  caption="NFT 沒有即時顯示在錢包中"
/>

大多數時候，當使用者收到他們無法理解的、出乎意料或互相矛盾的資訊時，原因在於他們所使用的節點之間缺乏**準確性**。

在 Alchemy，我們將**準確性**定義為：**使用者與 dApp 互動時，能得到正確且一致的資料**的狀態。

在本文中，我們將說明：

- 為什麼準確性是 web3 特有的問題
- 用單一節點解決準確性問題的限制
- 用負載平衡器（load balancer）解決準確性問題的限制
- 缺乏準確性如何為應用程式帶來全面性的問題
- Alchemy 的 Cortex 引擎如何解決區塊鏈準確性問題

## 準確性指的是使用者與 dApp 互動時，能得到正確且一致的資料

在 web2 中，[集中式系統讓準確性成為容易解決的問題](https://ethereum.org/en/developers/docs/web2-vs-web3/#:~:text=Web2 refers to the version,that run on the blockchain)，因為資訊只能來自單一來源。但 web3 的分散式系統帶來了各式各樣新的、複雜的問題，包括：

- 實作複雜
- 難以確保資訊能傳達到網路中最遠的參與者（資訊傳到網路中距離較遠的節點確實需要更長時間）
- 網路協調上的困難

#### 所有這些挑戰都可能導致一組節點之間缺乏準確性，進而造成：

- 提供不正常的使用體驗
- 流失客戶
- 為了事後修正問題而耗費時間與金錢

<CalloutBlock>

"[CryptoKitties 上你能想到的每一種狀態問題](https://www.alchemy.com/case-studies/dapper)都發生過。如果節點不同步，使用者體驗與整個 dApp 都會出問題。" **- Eric Lin, Dapper**

</CalloutBlock>

## 運行單一節點的問題

如果資料準確性的問題源自多個節點之間的通訊，你或許會決定自己運行單一節點。

假設你正在運行自己的單一節點，有使用者向你的 dApp 發出請求：

- 他們詢問最新的區塊
- 你的節點告訴他們最新的區塊是 4

<ImageBlock
  src="https://media.alchemy.com/1704794016-the-problems-with-running-a-single-node.png"
  alt="使用者向執行單一節點的 dApp 請求最新區塊的示意圖"
  width={1920}
  height={1081}
  caption="使用者對您的 dApp 發出呼叫"
/>

✅ 這樣可行！

但有個問題。

它只在一定程度內可行。

#### 讓流量通過單一節點，會大幅限制你應用程式的可擴展性與可靠性：

- **可擴展性**：如果你的 dApp 目前有 100 名使用者，但使用量成長了 10 倍，單一節點將無法有效處理增加的請求負載
- **可靠性**：如果你只依賴一個節點，那麼當該節點故障時，你的 dApp 也會隨之故障。而由於節點眾所周知地不可靠，只用一個節點，你的正常運行時間會遠低於所需水準（我們曾測量到，某些情況下單一節點的正常運行時間低至 72%）。

你的團隊花在節點維護上的時間，[就是他們無法用來為客戶打造產品與體驗](https://www.alchemy.com/case-study/zerion)的時間。

<CalloutBlock>

"與 Alchemy 合作，讓我們省下了相當於三名全職工程師的工作量，否則他們原本必須一直埋頭處理基礎設施維護。" - **Evgeny Yurtaev, [Zerion](https://www.alchemy.com/dapps/zerion) 執行長暨共同創辦人**

</CalloutBlock>

好，既然你已經意識到單一節點無法滿足你的業務需求，接下來你可能會怎麼做？

## 使用負載平衡器的問題

你可能會想嘗試使用透過負載平衡器在多個節點間管理流量的基礎設施供應商；這是 web3 中非常常見的一種基礎設施設計。

負載平衡是一種水平擴展機制，最初應用於 web2。可以把負載平衡想像成在超市裡選擇最短的結帳隊伍——負載平衡器會把你的 dApp 流量導向隊伍最短的節點。

<ExternalVideo
  url="https://vimeo.com/729065065"
  provider="vimeo"
  providerUid="729065065"
/>

看看下面的例子。

有使用者向你的 dApp 發出請求：

- 他們詢問最新的區塊
- 透過節點 A，你的 dApp 告訴他們最新的區塊是 2

接著，使用者再次詢問以確認：

- 這次查詢被導向節點 B
- 透過節點 B，你的 dApp 告訴他們最新的區塊是 4

<ExternalVideo
  url="https://vimeo.com/729065080"
  provider="vimeo"
  providerUid="729065080"
/>

由於系統仍然是將每個請求路由到單一節點，而任何時間點該節點都可能沒有最新資訊，因此結果經常是錯誤的。你的使用者會收到互相矛盾、無法合理解釋的結果。

**負載平衡能提供比單一節點更高的擴展性與可靠性，但代價是準確性。**

#### 當流量透過負載平衡器路由時，各節點對以下資訊可能存在差異：

- 最新區塊
- 最近的交易
- 待處理交易

這些問題可能導致：

- [開發者遇到不一致的資料](https://community.infura.io/t/how-does-the-load-balancer-work/1090)
- 請求失敗
- 應用程式失效
- 單一使用者對同一個請求看到互相矛盾的結果
- 多個使用者對同一個請求看到互相矛盾的結果

還不相信這是個大問題嗎？請繼續往下看。

## 缺乏準確性如何導致重大問題

假設 Isaac 想加入一個 DAO，為此他需要購買該 DAO 的代幣。

- Isaac 向某個去中心化交易所（DEX）詢問：「DAO 代幣還有嗎？」
- 該 DEX 的基礎設施供應商將 Isaac 的請求路由到節點 A。
- 透過節點 A，DEX 告訴他：「有的，代幣還有。」

✅ 代幣還有！

Isaac 準備進行購買。

- 該 DEX 的基礎設施供應商將 Isaac 的請求路由到節點 B。
- 透過節點 B，DEX 告訴他：「交易失敗。」

<ExternalVideo
  url="https://vimeo.com/729065088"
  provider="vimeo"
  providerUid="729065088"
/>

由於該 DEX 使用的基礎設施供應商在多個節點間進行負載平衡（一種在 web3 中非常常見的擴展機制），當 Isaac 確認代幣是否還有時，節點 A 回傳的資訊在這個情況下是過時的。

**這突顯了一個關鍵點：在使用負載平衡器的情況下，資訊一律來自某個單一節點，而在某個特定時刻，來自該單一節點的資訊是不可依賴的。**

該節點可能沒有最新的資訊，如果回應你請求的正是這個節點，你的交易（或後續交易）很可能會失敗或出現預期之外的行為。

<EbookCta
  eyebrow="免費指南"
  title="自建 vs. 採購：區塊鏈基礎設施"
  description="在雇用基礎設施團隊之前，請先閱讀本文：自建系統在處理第一個正式請求之前，每年可能就要花費 85 萬至 100 萬美元以上。查看完整成本細項。"
  ctaLabel="下載指南"
  ctaHref="/resources/build-vs-buy"
  coverImage={{
    src: "https://media.alchemy.com/ebooks/cta-build-vs-buy.png",
    alt: "Build vs. Buy ebook cover",
  }}
/>

#### 準確性問題會導致各式各樣的失效狀態

Isaac 遇到的情況，只是節點缺乏準確性所導致的眾多失效狀態之一。

再來看看幾種可能的失效狀態：

1. 智能合約執行結果互相矛盾，而本應只有一個確定的正確答案。舉例來說：

- 使用者詢問：「這隻 Bored Ape 屬於誰？」
- API 回傳：「Alice」
- 接著使用者詢問：「告訴我 Alice 擁有哪些 apes？」
- API 回傳：「Alice 沒有擁有任何一隻」

<ExternalVideo
  url="https://vimeo.com/728999085"
  provider="vimeo"
  providerUid="728999085"
/>

為什麼會這樣？在依賴負載平衡器的基礎設施中，智能合約的狀態，以及執行智能合約時發生的結果，會因處理該請求的節點不同而有所差異。

2. dApp 回傳了錯誤的「nonce」（每個錢包地址每筆交易獨有的識別碼），導致後續交易失敗。舉例來說：

- 使用者以 nonce 100 完成交易 1
- 使用者詢問：「下一個 nonce 是多少？」
- API 回傳：100（這理論上不應該發生，因為 nonce 100 已經完成過了）
- 下一筆交易將被自動拒絕

<ExternalVideo
  url="https://vimeo.com/729065102"
  provider="vimeo"
  providerUid="729065102"
/>

_為什麼會這樣？在不準確的基礎設施中，_[_待處理交易_](https://www.alchemy.com/docs/ethereum-transactions-pending-mined-dropped-replaced)_可能只存在於單一節點上，因此當你嘗試查詢這些待處理交易或發送方錢包的狀態時，經常會收到缺漏或誤導性的資料。_

#### 這些失效狀態難以察覺、無法合理解釋，而且會嚴重影響你應用程式的健全性

可能發生的情況包括：

你可能會收錄不準確的資料，進而汙染你的資料庫，需要花費時間與精力修正。

以[Origin](https://www.alchemy.com/case-study/origin)為例。在使用第一家基礎設施供應商時，RPC 錯誤與除錯是家常便飯，而[除錯這些問題](https://www.alchemy.com/build)對他們的團隊來說相當耗時。

他們發現，某個節點回應了一個請求，但該節點比回應另一個請求的節點落後了幾個區塊。

這些狀態問題幾乎不可能排查，而一旦他們真的找出根本原因，發現該問題已經汙染了他們整個系統。

**由於這類問題沒有錯誤訊息或失敗代碼，你 a\) 無法在程式碼中事先預防，b\) 問題發生時要花好幾個小時除錯；對你的使用者來說，這同樣令人困惑：**

<ImageBlock
  src="https://media.alchemy.com/1704794424-error-transferring-nft-from-bakery-swap.png"
  alt="Bakery swap 轉移 NFT 時發生錯誤"
  width={942}
  height={359}
  caption="Bakery swap 轉移 NFT 時發生錯誤"
/>

**使用者可能基於過時的資料發送交易，而這些交易注定會失敗；他們因此會損失 gas 費用：**

<ImageBlock
  src="https://media.alchemy.com/1704794521-nfts-missing-in-user-wallet.png"
  alt="使用者回報錢包中 NFT 遺失"
  width={1227}
  height={336}
  caption="NFT 遺失"
/>

**使用者可能在你的 dApp 上看到互相矛盾的畫面，前後顯示交易成功或失敗不一致。他們會將這個問題歸咎於你 dApp 的前端體驗：**

<ImageBlock
  src="https://media.alchemy.com/1704794600-nfts-not-showing-up-in-user-wallet.png"
  alt="NFT 在 4 小時後仍未顯示在使用者錢包中"
  width={1224}
  height={552}
  caption="NFT 在 4 小時後仍未顯示在使用者錢包中"
/>

**使用者可能因為誤以為交易沒有成功上鏈而重新嘗試，結果多付了一筆費用；實際上原本的交易已經成功，只是沒有被準確地反映出來：**

<ImageBlock
  src="https://media.alchemy.com/1704794692-user-paying-gas-fees-twice-for-failed-transaction.png"
  alt="使用者為失敗的交易支付了兩次 gas 費用"
  width={1232}
  height={399}
  caption="使用者為失敗的交易支付了兩次 gas 費用"
/>

## 為準確性而打造

如果還不夠明確的話：在區塊鏈網路中，缺乏準確的基礎設施會對你的 dApp 與使用者造成非常負面的影響。

你需要與具備以下條件的基礎設施供應商合作：

- 將準確性視為根本優先事項
- 擁有一套與負載平衡不同的、可持續運作的機制，在你的 dApp 擴展規模時仍能維持準確性

#### 節點供應商的基準測試

我們彙整了一些資料，對幾家知名節點基礎設施供應商的準確性進行基準測試。你也可以用這個工具自行測試準確性。

準確性只是評估供應商時的其中一個面向。你也可以在 Alchemy 的 [RPC 供應商基準測試](https://www.alchemy.com/benchmarks)中比較目前的延遲、成功率與失敗請求數量。

在我們的測試中，我們在 24 小時內查詢 JSON RPC 方法 _eth_blockNumber_ 總計約 1,072,000 次，從每家供應商取得最新區塊編號。這只是測試資料不準確性的一個簡單範例。還有許多其他測試方式，往往更常失敗，而且失敗得更嚴重。

<ImageBlock
  src="https://media.alchemy.com/1704794854-benchmarking-node-providers.png"
  alt="各節點基礎設施供應商 24 小時內資料準確度的比較圖表"
  width={3250}
  height={1500}
  caption="節點供應商效能比較"
/>

_附註_：在我們的範例儀表板快照中，我們查詢並記錄了單一一天的資料準確性。

\*完整方法論細節請見附錄。

#### 你能做些什麼來取得準確的資料？

<CalloutBlock>

"Alchemy 解決了先前一直困擾我們的一致性問題，消除了 98% 的使用者客訴，並大幅改善了 [Augur](https://www.alchemy.com/dapps/augur) 的使用者體驗與採用率。" - [**Augur CTO Alex Chapman**](https://www.alchemy.com/case-studies/augur)

</CalloutBlock>

為了確保你的[應用程式](https://www.alchemy.com/dapps/top/defi-dapps)及客戶能獲得準確的資料，Alchemy 投入了數十萬個工程時，並開發了數百項獨有的創新技術，打造出[Alchemy 的 Cortex 引擎](https://www.alchemy.com/rpc-api)，在具備多節點基礎設施的可擴展性與可靠性優勢的同時，確保資料準確性的一致性。

Cortex 遠不只是一堆互相連接的節點——事實上，這只是 Cortex 引擎的一小部分。Cortex 結合了客製化、可擴展且分散式的系統，讓我們的 API 實質上能表現得像單一節點一樣運作，藉此解決我們前面討論過的所有準確性問題。

這是怎麼運作的？

<ExternalVideo
  url="https://vimeo.com/730080026"
  provider="vimeo"
  providerUid="730080026"
/>

我們建立了一個明確的一致性層，稱為 Vox Nodi（節點之聲）。Vox Nodi 的職責是確保我們協助服務的任何區塊鏈請求，都能回傳一致的結果。

其運作方式本質上是在我們的基礎設施中運行一套共識演算法，基礎設施的每個組成部分都能對區塊鏈的正確狀態進行投票。

透過正確地路由與調整查詢，這套系統確保即使不同節點在某個時間點對交易資料有不同的看法，回傳的結果始終保持一致且準確。

簡單來說，Vox Nodi 保證任何對我們 API 發出的請求，都能快速、可靠地回傳 100% 準確的資料。

而 Cortex 仍然讓開發者能夠無限且可靠地擴展規模，因為回應每個請求的不是單一節點，而是一整套分散式的區塊鏈引擎。

簡言之，這**不是**負載平衡器。

## 影響與結論

在選擇如何將你的 dApp 連接到區塊鏈時，有許多不同的面向需要考量：**供應商提供準確性的能力，應該是你清單上的第一要務。**

在 Alchemy，準確性是我們的首要任務。立即[開始使用](https://dashboard.alchemy.com/signup/?a=f79ab530c4) Alchemy，完全免費。

-

## 附錄

#### 基準測試方法論

在思考如何對準確性進行基準測試時，我們從第一性原理出發，測試了其中一個最基本的 Ethereum RPC 呼叫：eth_blockNumber。

簡單回顧一下，eth_blockNumber 會回傳 Ethereum 網路的最新區塊編號，而在去中心化網路中，區塊編號最高的區塊包含最近的交易。

因此，在正常的網路狀況下，從單一節點／節點供應商呼叫 eth_blockNumber，應該只會得到**遞增**的區塊編號。任何遞增資料中出現的不一致，都代表發生了回退（reversion），若頻繁發生，則可能顯示供應商的資料存在潛在的不準確性。

請注意，這與「[reorg](https://www.alchemy.com/overviews/what-is-a-reorg)」不同，reorg 是指新的最長鏈取代了節點上原有的資料。「Reorg」的區塊數量永遠會**至少等於**節點上原有的資料，所以如果我們看到區塊編號倒退，就可以明確判定這是基礎設施故障所導致。

為了理解我們如何衡量不一致的資料，假設我們在呼叫 eth_blockNumber 總共 13 次後，得到以下的區塊編號序列。

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "eth_blockNumber", dataType: "object" },
      { key: "2", width: 200, title: "Response Sequence", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: true, tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: true, tooltip: "", icon: "" },
        id: 1,
      },
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: true, tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: true, tooltip: "", icon: "" },
        id: 3,
      },
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: true, tooltip: "", icon: "" },
        id: 4,
      },
      {
        "1": { title: "<p>2</p>", tooltip: "", icon: "" },
        "2": { title: true, tooltip: "", icon: "" },
        id: 5,
      },
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: false, tooltip: "", icon: "" },
        id: 6,
      },
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: false, tooltip: "", icon: "" },
        id: 7,
      },
      {
        "1": { title: "<p>3</p>", tooltip: "", icon: "" },
        "2": { title: true, tooltip: "", icon: "" },
        id: 8,
      },
      {
        "1": { title: "<p>2</p>", tooltip: "", icon: "" },
        "2": { title: false, tooltip: "", icon: "" },
        id: 9,
      },
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: false, tooltip: "", icon: "" },
        id: 10,
      },
      {
        "1": { title: "<p>1</p>", tooltip: "", icon: "" },
        "2": { title: false, tooltip: "", icon: "" },
        id: 11,
      },
      {
        "1": { title: "<p>3</p>", tooltip: "", icon: "" },
        "2": { title: true, tooltip: "", icon: "" },
        id: 12,
      },
    ],
  }}
/>

根據這個序列，我們在區塊索引 6、7、9、10 及 11 共發現 5 個錯誤。這表示在第 6、7、9、10、11 個區塊，回傳的區塊編號並未依照預期的遞增順序排列，顯示存在某種底層的資料準確性問題。

注意：一旦我們記錄到節點供應商回報的區塊編號為 2，我們就會假設任何後續出現的 1 都是錯誤的，等同於供應商發生時間倒流，回傳了慢一個區塊的「當前」區塊值。同樣地，一旦我們記錄到節點供應商回報的區塊編號為 3，之後任何出現 1 或 2 的情況都視為**不一致**。為了簡化這項基準測試，我們明確地只針對各個供應商自身衡量資料準確性，並不會跨供應商比較區塊資料。
