跳至內容
0%

Solana Account Archive:在任何 slot 查詢帳戶狀態

作者:Alchemy

最後更新:2026年8月3日3 分鐘閱讀
Solana 帳戶封存:查詢任意 slot 的帳戶狀態

Account Archive 可以回答 getAccountInfo,查詢任何 Solana 帳戶在任何歷史 slot 的狀態(vote 帳戶與 per-slot sysvars 除外),涵蓋範圍會不斷擴展且永不清除。使用 slot 參數讀取某個時間點的狀態,或用 lastUpdateBeforeSlot / firstUpdateAfterSlot 走訪帳戶的更新歷史。讀取延遲中位數落在微秒等級。

Solana 只記得現在

關於 Solana 有個細節,連經驗豐富的開發者都會感到意外:validator 只保存每個帳戶的一份副本,也就是最新的那份。每次寫入都會就地覆蓋先前的狀態,所以帳戶一旦被寫入,先前的樣子就消失了。即便節點仍保有某個 slot 的完整區塊,該 slot 當時的帳戶狀態也早已不存在於機器上任何地方。

這是設計上的特性,不是缺陷,也是 Solana 快速的原因之一。但這代表一整類合理的問題都無法回答,例如:

  • 除錯:「我們的清算在 slot 285,401,337 觸發。那個確切的 slot 上,oracle price 帳戶長什麼樣子?」
  • 回測:「重建這個 pool 在過去六個月每個時間點的儲備量。」
  • 稽核:「證明這個 token 帳戶在 3 月 3 日持有的內容。」
  • 復原:「我們的索引器停機了四小時,重建我們錯過的確切狀態變化。」

你可能會以為 getBlock 可以處理這些問題,但它辦不到。它回傳的是過去某個 slot 的交易以及交易前後的餘額,而不是帳戶資料。對大多數應用程式而言,真正重要的狀態存在於資料位元組裡:訂單簿、價格資訊、部位,或設定。

Solana Account Archive 把完整的歷史找回來了。它可以回答 getAccountInfo,查詢自 2025 年 7 月以來任何帳戶在任何 slot 的狀態,使用你已經熟悉的請求與回應格式。有兩點與眾不同:第一,這個 archive 永不清除資料,所以可查詢的範圍只會隨時間增加;第二,它會略過鏈上自身的 per-slot 記帳資料(vote 帳戶,以及少數每個 slot 都會被重寫的 sysvars),因為對應用程式開發者而言,那些只是雜訊,不是有用的歷史資料。

API:getAccountInfo 的延伸

我們刻意沒有發明新的方法。這個 archive 使用標準的 JSON-RPC,並實作 getAccountInfo,支援你早已熟悉的所有設定選項:encoding(base64、base58、base64+zstd、jsonParsed)、commitment、dataSlice、minContextSlot,外加三個彼此互斥的新參數:

Parameter
Semantics
Use it for
slot
State as of slot S (inclusive: the latest write with slot <= S)
Point-in-time snapshots
lastUpdateBeforeSlot
The most recent write strictly before S
Walking an account's history backward
firstUpdateAfterSlot
The first write strictly after S
Walking an account's history forward

這三個參數也和 minContextSlot 互斥;若與其中任一參數搭配使用會被拒絕。三者都省略的話,就會得到一般的最新狀態 getAccountInfo;這個 archive 是既有功能的超集,可直接替換使用。以下是一個完整的定點時間讀取範例:查詢某個 token 帳戶在 slot 400,000,000 時的狀態:

bash
Copied
curl $ARCHIVE_URL -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getAccountInfo", "params": [ "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa", { "encoding": "jsonParsed", "slot": 400000000 } ] }'

回應採用標準的 getAccountInfo 格式:

json
Copied
{ "jsonrpc": "2.0", "id": 1, "result": { "context": { "apiVersion": "4.1.0", "slot": 400000000 }, "value": { "lamports": 2040821, "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "data": { "program": "spl-token", "parsed": { "info": { "isNative": false, "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "owner": "7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE", "state": "initialized", "tokenAmount": { "amount": "1056153754430274", "decimals": 6, "uiAmount": 1056153754.430274, "uiAmountString": "1056153754.430274" } }, "type": "account" }, "space": 165 }, "executable": false, "rentEpoch": 18446744073709551615, "space": 165 } } }

注意這裡的語意:slot: S 代表「截至 S 時的狀態」。如果該帳戶最後一次被寫入是在 slot S - 40,000,之後就沒有異動,你拿到的就是那次寫入的結果——正是任何在 slot S 執行的程式會看到的狀態。而且 jsonParsed 同樣適用於歷史狀態:標準 RPC 節點所使用的同一套解碼器,會套用在歷史資料位元組上,所以即使是涵蓋範圍最早期的 token 帳戶,回傳的也是已解碼的資料,而非不透明的原始位元組。

走訪帳戶歷史的迭代器

lastUpdateBeforeSlot 與 firstUpdateAfterSlot 讓這個 archive 變成一個可以走訪帳戶每次狀態變化的迭代器。

lastUpdateBeforeSlot 會在 context.slot 中回傳它找到的那次寫入的實際 slot,所以每個回應都能作為下一次請求的游標:

python
Copied
# Walk every state transition of an account, newest to oldest. cursor = current_slot + 1 while True: try: result = rpc("getAccountInfo", [pubkey, {"lastUpdateBeforeSlot": cursor}]) except RpcError as e: if e.code == -32020: # walked past the edge of coverage; done break raise slot = result["context"]["slot"] # the slot this write landed at handle(slot, result["value"]) # value is null if this write closed the account cursor = slot # strictly-before: no overlap, no gaps

這個走訪過程只有一個終止點:錯誤 -32020,代表游標已經走出這個 archive 涵蓋範圍的邊界。過程中出現 null 的值,其意義和在 getAccountInfo 中一樣:該帳戶在那個 slot 時不存在——在這裡,是因為 context.slot 那次寫入把它刪除了。把那個 slot 當作游標再送回去,走訪就會繼續往該帳戶更早的歷史前進。

firstUpdateAfterSlot 則反過來:不是往回走,而是往前走,回傳在指定 slot 之後嚴格意義上的下一次寫入。這很適合用在索引器要從一個已知 slot 追趕進度、且需要取得每一個中間狀態、而不只是最新狀態的情境。

搭配這兩者,你可以走訪一個帳戶的整個生命週期:每一次餘額變化、每一次資料異動、每一次擁有權變更,以及各自發生的確切 slot。

即時記錄新的寫入

往未來記錄狀態是比較容易的部分。這個 archive 透過 Geyser 串流監聽網路,即時記錄帳戶寫入,並遵循兩條規則以維持紀錄的可信度:

只有 finalized 的 slot 才會進入 archive。Solana 在鏈尖經常分岔,一個看起來真實的區塊可能幾秒後就消失了。archive 會保留每個 slot 的更新,直到網路將其 finalize 後才寫入,所以永久紀錄裡只會包含真正發生過的事。

不會有資料被悄悄遺漏。每個 Solana 區塊都會指名它的 parent,所以任何串流漏掉的區塊(無論是小故障、重新連線,還是服務部署)都會立即被偵測到並回補。

底層的儲存層本身就是另一個值得說的故事。這裡重要的是結果:定點時間讀取快到可以放在熱路徑上,延遲中位數落在微秒等級(在我們內部服務層測得)。

重建過去一年的歷史

從今天開始擷取資料,可以讓你明年擁有一份有用的 archive。但我們也想要過去一年的資料,這代表要在一條不保留歷史的鏈上,重建數千萬個已經發生過的 slot 的帳戶狀態。而且資料量非常龐大:Solana 大約每 400 毫秒產生一個區塊,一天約 216,000 個 slot。一年下來大約是 7,800 萬個 slot、數千億次的帳戶更新,以及超過一 petabyte 的原始帳戶資料,每個 slot 都帶有數千筆附帶完整資料內容的帳戶寫入。

為什麼「單純解析交易」行不通

有個誘人的捷徑:抓取歷史區塊,解讀每筆交易對每個帳戶做了什麼。但這種做法在根本上行不通,這一點值得牢記:Solana 的交易並不是狀態變化的描述,而是一次程式呼叫。

帳戶實際發生了什麼,取決於該程式的執行過程:CPI 展開呼叫到其他程式、執行途中讀取的 sysvars、計算量計費、以及當下 runtime 的精確語意。區塊只記錄了某筆交易碰到了哪些帳戶、餘額如何變動,但真正重要的資料位元組,只能透過實際執行程式碼才能確定。

要忠實地知道一筆交易對某個帳戶做了什麼,只有一種方法:用真實的 runtime,對照它當時實際執行的真實狀態,把它執行一遍。

Replay:重新執行一次鏈上歷史

所以這正是我們做的事。這個回補(backfill)管線本質上就是一個重新經歷歷史的 validator:

  1. 從可信任的快照啟動。Solana Foundation 維護著公開的歷史快照存檔:某個 slot 上每個帳戶的完整擷取。載入 slot A 的快照,就能得到那一刻數億個帳戶完整且具權威性的狀態。
  2. 往前重放每一個區塊。從 A + 1 開始,每個歷史區塊都會通過真正的 validator runtime 執行:是真實執行,不是模擬,也不是解析日誌。每筆交易都會被執行;runtime 產生的帳戶寫入會被擷取並逐 slot 建立索引。
  3. 在下一個快照處停下並進行驗證。這一步讓整件事真正值得信任。當重放進行到存在下一個標準快照的 slot B 時,我們會把重放得到的最終狀態與該快照比對,包括 accounts lattice hash:這是網路本身計算並取得共識的、對整個帳戶集合的加密承諾。只要範圍內任何一個帳戶有任何一個位元組出現偏差,雜湊值就不會相符,該範圍就會被拒絕。我們不假設重放一定正確;每一段回補的範圍在被信任之前,都會對照共識層的事實進行檢驗。

舊的區塊需要對應時代的 runtime

這是讓 replay 真正困難的地方:Solana 的執行語意會隨時間變化版本。Feature gates 會在特定 epoch 啟用、計算量預算規則會改變、syscalls 會新增、邊界情況的行為也會被修正。十二個月前的交易,必須用當時該叢集實際運行的那個 runtime 來重放。如果用今天的 validator 來重放,第三步就會明確告訴你:你製造出了一段從未真實發生過的歷史。

實務上,回補機群會運行好幾個固定版本的 validator 世系,各自負責它能忠實重現的時代,並依 epoch 邊界切分工作,確保每個區塊的開頭和結尾都位在可驗證的快照上。要把 archive 的涵蓋範圍再往前延伸,主要的工作就是建立正確時代的 runtime,並負擔對應的 replay 運算成本。而且由於每個對齊 epoch 的區塊都是獨立的,歷史的回補完全可以平行進行;回補一年的資料,主要取決於同時運行多少個 replay worker。

持續驗證的信任

雜湊驗證的回補涵蓋了過去的資料;另一個獨立的關注點,則是服務路徑在正式環境中是否始終誠實可信。因此有一套獨立的監控機制,會透過你會使用的同一套公開 API,持續交叉驗證 archive 回應的內容是否與即時的 RPC 節點一致。正確性對我們來說至關重要,所以服務運行的每一分鐘都會重新驗證一次。這套多層次的做法——共識為根據的雜湊用於回補歷史、finalization 閘門控管鏈尖寫入、再加上持續的即時交叉驗證——確保你能夠信任這份隨時間推進的真實紀錄。

你可以用它來建構什麼

開頭提到的每個問題,現在都只是一次 API 呼叫:

  • 「那個確切 slot 上的 oracle 帳戶長什麼樣子?」一次設定 slot 的 getAccountInfo 呼叫即可。取得某筆失敗交易在執行當下所讀取的每個帳戶的確切狀態,不必再靠截圖和猜測來重建 oracle 的輸入資料。
  • 「重建這個 pool 六個月來的儲備量。」以固定的 slot 間隔取樣該 pool 帳戶,並用鏈上實際發生的資料進行回測,而不是用交易事件拼湊出來的近似值。
  • 「證明這個 token 帳戶在 3 月 3 日持有的內容。」「截至 slot S,帳戶 X 確切包含了這些位元組。」這個問題現在可以回答,而且是以網路本身達成共識的雜湊為依據。
  • 「重建我們索引器錯過的狀態變化。」從你最後已知的 slot 開始,用 firstUpdateAfterSlot 往前分頁,即可依序取得每一個中間狀態。

還有一個開頭沒問到的:完全不需要運行索引器,就能取得餘額與部位歷史。用 lastUpdateBeforeSlot 往回走訪一個 token 帳戶,就能得到它完整的時間軸。若想查詢某個錢包在過去某個 slot 的整體 token 持有狀況,可另外參考 historical Solana token balances。

開始使用

Solana 之所以能保持快速,靠的是讓 validator 保持精簡、只專注於目前的狀態。歷史帳戶狀態本來就存在別處,而在此之前,整個生態系都只能想辦法繞過這個限制。Solana Account Archive 改變了這一點。透過以 finalization 為閘門的即時擷取,以及會用時代忠實的 runtime 重新執行歷史、並對照共識雜湊驗證結果的回補管線,歷史 getAccountInfo 就只是多一個參數的普通 RPC 呼叫:相同的方法、相同的回應格式。

在後續文章中,我們會深入介紹儲存層:一年份 Solana 帳戶寫入資料是如何組織在磁碟上,讓擷取新的更新和回答定點時間查詢都能保持快速。這個 archive 索引的所有資料都經過壓縮,至少保存 3 份副本以確保冗餘,且永不清除,所以可查詢的範圍只會持續增加。

Get started on Solana,如果你的使用情境需要更深的歷史資料,或想在大規模場景下探索特製方案與計價,歡迎聯絡我們。

常見問答

如何取得 Solana 帳戶在特定 slot 的狀態?

在設定物件中帶入該帳戶的 pubkey 以及 slot 參數,對 Account Archive 呼叫 getAccountInfo:getAccountInfo(pubkey, { "slot": S })。回應採用標準的 getAccountInfo 格式,回傳的是該帳戶截至 slot S 時的狀態,也就是在 S 或之前的最新一次寫入。

如何取得 Solana 帳戶的完整更新歷史?

使用游標參數進行分頁。getAccountInfo(pubkey, { "lastUpdateBeforeSlot": S }) 會回傳在 S 之前嚴格意義上最近一次的寫入,並在 context.slot 中給出該次寫入的實際 slot;把那個 slot 當作下一個游標送回去,就能往回走訪每一次狀態變化。firstUpdateAfterSlot 則以相同方式往前走訪。

slot、lastUpdateBeforeSlot 與 firstUpdateAfterSlot 有什麼差異?

slot 是一次定點時間讀取:截至 slot S(含)為止的狀態。lastUpdateBeforeSlot 與 firstUpdateAfterSlot 則是排他性的歷史游標:分別是嚴格早於或嚴格晚於 S 的最近一次寫入。這三者彼此互斥;若三者都省略,就會得到一般的最新狀態讀取。

jsonParsed 編碼適用於歷史帳戶狀態嗎?

適用。標準 Solana RPC 節點所使用的同一套帳戶解碼器,會套用在歷史資料位元組上,所以 token 帳戶、mint,以及其他已知程式的帳戶,在涵蓋範圍內的任何 slot 都能被正確解析回傳。所有標準編碼都支援:base64、base58、base64+zstd、jsonParsed,以及 dataSlice。

可以查詢多久之前的資料?

目前涵蓋範圍可回溯至 2025 年 7 月,回補作業會持續將範圍往更早的時間延伸。歷史資料永不清除,所以所有已索引的內容都能持續查詢,範圍只會增加。

歷史資料的正確性如何獲得保證?

回補的資料範圍是透過用對應時代的 validator runtime 重新執行每一筆交易而得出,所得結果會與網路本身的共識產物進行驗證,包括來自標準快照的 accounts lattice hash。即時擷取只會提交 finalized 的 slot,並有獨立的監控機制持續交叉檢查服務回傳結果與即時 RPC 節點是否一致。

是否涵蓋所有帳戶?

除了純粹的 per-slot 鏈上記帳資料之外,其餘所有帳戶都涵蓋在內:vote 帳戶,以及每個 slot 都會被重寫的三個 sysvars(SlotHashes、SlotHistory、RecentBlockhashes)不在索引範圍內。程式帳戶、token 帳戶、mint、PDA、錢包,以及其餘的 sysvars(Clock、Rent 等)都涵蓋在內。

需要新的 SDK 或用戶端嗎?

不需要。任何 Solana JSON-RPC 用戶端都適用。這個 archive 實作了標準的 getAccountInfo,而歷史相關的參數只是既有設定物件中額外的欄位。

Alchemy 電子報

搶先掌握最新發布消息

訂閱我們的電子報

取得 Alchemy 最新產品更新與資源

A
O
D
+
超過 80,000 名訂閱者

輸入您的電子郵件地址,即表示您同意接收我們的行銷通訊與產品更新。您了解 Alchemy 會依照我們的隱私權聲明處理所收到的資訊。您可以隨時取消訂閱。