狀態同步
了解 Cosmos SDK 提供的 Tendermint Core 狀態同步和支持。
📣 提示 : 只是關心如何將節點與網絡同步?跳到 本節。
Tendermint 核心狀態同步
狀態同步允許新節點通過獲取最近高度的網絡狀態快照來加入網絡,而不是獲取和重播所有歷史塊。 由於應用程序狀態小於所有塊的組合,並且恢復狀態比重放塊更快,這將與網絡同步的時間從幾天縮短到幾分鐘。
本文檔的這一部分簡要概述了 Tendermint 狀態同步協議,以及如何同步節點。 有關更多詳細信息,請參閱 ABCI 應用指南 和 ABCI 參考文檔。
狀態同步快照
設計 Tendermint 狀態同步時的指導原則是為應用程序提供盡可能多的靈活性。因此,Tendermint 不關心快照包含什麼、它們是如何拍攝的或它們是如何恢復的。它只關心發現網絡中的現有快照、獲取它們並通過 ABCI 將它們傳遞給應用程序。
Tendermint 使用輕客戶端驗證來檢查已恢復應用程序的最終應用程序哈希值與鏈應用程序哈希值的對比,但任何進一步的驗證都必須在恢復過程中由應用程序本身完成。
快照由任意格式的二進制塊組成。塊不能大於 16 MB,否則沒有限制。 快照元數據,通過 ABCI 和 P2P 交換,包含以下字段:
height
(uint64
): 拍攝快照的高度format
(uint32
):任意特定於應用程序的格式標識符(例如版本)chunks
(uint32
): 快照中二進制塊的數量hash
(bytes
): 用於跨節點比較快照的任意快照哈希metadata
(bytes
):供應用程序使用的任意二進制快照元數據
format
字段允許應用程序以向後兼容的方式更改其快照格式,方法是提供多種格式的快照,並選擇在恢復期間接受哪些格式。
例如,這在更改序列化或壓縮格式時很有用:因為節點可能能夠向運行舊版本的節點提供快照,或者在使用較新版本啟動時使用舊快照。
hash
字段包含任意快照哈希。跨節點具有相同 metadata
字段(包括 hash
)的快照被認為是相同的,並且將從這些節點中的任何一個獲取 chunks
。
hash
不可信任,並且未經 Tendermint 本身驗證,這可以防止快照生成中的無意不確定性。
hash
可以改為由應用程序驗證。
metadata
字段可以包含應用程序所需的任何任意元數據。
例如,應用程序可能希望包含塊校驗和以丟棄損壞的chunks
,或 Merkle 證明 根據鏈應用程序哈希分別驗證每個塊。
在 Protobuf 編碼形式中,快照 metadata
消息不能超過 4 MB。
拍攝、提供快照
要啟用狀態同步,網絡中的某些節點必須拍攝並提供快照。 當對等點嘗試狀態同步時,現有的 Tendermint 節點將在應用程序上調用以下 ABCI 方法以向該對等點提供快照數據:
ListSnapshots
:返回可用快照列表,以及元數據LoadSnapshotChunk
:返回二進制塊數據
快照通常應該定期生成,而不是按需生成:這可以提高狀態同步性能,因為快照生成可能很慢,並且可以避免攻擊者用此類請求淹沒節點的拒絕服務向量。
通常可以刪除較舊的快照,但至少保留兩個最新的快照可能很有用,以避免在節點恢復時刪除先前的快照。
完全由應用程序決定如何拍攝快照,但它應努力滿足以下保證:
- 異步:快照不應該停止塊處理,因此它應該異步發生,例如。在一個單獨的線程中
- 一致:快照應在孤立的高度拍攝,並且不應受到並發寫入的影響,例如。由於主線程中的塊處理
- 確定性:對於給定的
height
和format
,所有節點的快照chunks
和metadata
應該相同(在字節級別),以確保chunks
的良好可用性
例如,這可以按如下方式實現:
- 使用支持帶快照隔離的事務的數據存儲,例如 RocksDB 或 BadgerDB。
- 提交塊後在主線程中啟動只讀數據庫事務。
- 將數據庫事務句柄傳遞到新生成的線程中。
- 以確定的順序遍歷所有數據項(例如,按鍵排序)
- 序列化數據項(例如使用 Protobuf),並將它們寫入字節流。
- 對字節流進行哈希處理,並將其拆分為固定大小的塊(例如 10 MB)
- 將塊作為單獨的文件存儲在文件系統中。
- 將快照元數據寫入數據庫或文件,包括字節流哈希。
- 關閉數據庫事務並退出線程。
應用程序可能還需要採取額外的步驟,例如壓縮數據、校驗塊、為增量驗證生成證明以及刪除舊快照。
恢復快照
當 Tendermint 啟動時,它會檢查本地節點是否有任何狀態(即是否 LastBlockHeight == 0
),如果沒有,它將開始通過 P2P 網絡發現快照。
這些快照將通過以下 ABCI 調用提供給本地應用程序:
OfferSnapshot(snapshot, apphash)
:向應用程序提供已發現的快照ApplySnapshotChunk(index, chunk, sender)
:應用快照塊
將發現的快照提供給應用程序,它可以通過接受快照、拒絕快照、拒絕格式、拒絕發送者、中止狀態同步等方式做出響應。
一旦快照被接受,Tendermint 將從可用的對等點獲取塊,並將它們按順序應用到應用程序,應用程序可以選擇接受塊、重新獲取、拒絕快照、拒絕發送者、中止狀態同步等。
應用所有塊後,Tendermint 將在應用程序上調用 Info
ABCI 方法,並檢查應用程序哈希和高度對應於鏈中的可信值。
然後它將切換到快速同步以獲取任何剩餘的塊(如果啟用),然後最終加入正常的共識操作。
快照如何實際恢復完全取決於應用程序,但通常與它們的生成方式相反。
但是請注意,Tendermint 僅在所有塊都已恢復後才驗證快照,並且不會自行拒絕任何 P2P 對等方。
只要可信哈希和應用程序代碼是正確的,對手就不可能在加入共識時導致狀態同步節點具有不正確的狀態,但是應用程序可以抵消狀態同步拒絕服務(例如,通過實施增量驗證、拒絕無效節點)。
請注意,狀態同步節點將有一個從恢復快照的高度開始的截斷塊歷史記錄,目前沒有所有塊數據的回填。 網絡應該考慮更廣泛的影響,並且可能希望確保至少有幾個存檔節點保留完整的塊歷史記錄,以用於可審計性和備份。
Cosmos SDK 狀態同步
Cosmos SDK v0.40+ 包含對狀態同步的自動支持,因此應用開發者只需啟用它即可使用。他們將不需要自己實施 上面關於 Tendermint 的部分 中描述的狀態同步協議。
狀態同步快照
Tendermint Core 處理大部分為狀態同步發現、交換和驗證狀態數據的繁重工作,但應用程序必須定期為其狀態拍攝快照,並通過 ABCI 調用將這些快照提供給 Tendermint,並能夠恢復這些同步新節點時。
Cosmos SDK 將應用程序狀態存儲在名為 IAVL 的數據存儲中,每個模塊都可以設置自己的 IAVL 存儲。 在固定的高度間隔(可配置),Cosmos SDK 將在該高度導出每個商店的內容,Protobuf-編碼和壓縮它,並將其保存到本地文件系統中的快照存儲中。 由於 IAVL 保留了數據的歷史版本,因此可以在執行新塊的同時生成這些快照。
當新節點進行狀態同步時,Tendermint 將通過 ABCI 獲取這些快照。
請注意,只有由 Cosmos SDK 管理的 IAVL 存儲可以被快照。如果應用程序在外部數據存儲中存儲額外的數據,目前沒有機制將這些包含在狀態同步快照中,因此應用程序無法通過 SDK 使用自動狀態同步。
但是,如 ABCI 文檔 中所述,可以自由實施狀態同步協議本身。
當一個新節點狀態同步時,Tendermint 將從網絡中的對等點獲取快照並將其提供給本地(空)應用程序,該應用程序會將其導入其 IAVL 存儲。
Tendermint 然後使用輕客戶端驗證針對主區塊鏈驗證應用程序的應用程序哈希,並像往常一樣繼續執行塊。
請注意,狀態同步節點只會恢復快照拍攝高度的應用程序狀態,並且不會包含歷史數據或歷史塊。
啟用狀態同步快照
要啟用狀態同步快照,使用 CosmosSDK BaseApp
的應用程序需要設置快照存儲(帶有數據庫和文件系統目錄)並配置快照間隔和要保留的歷史快照數量。一個最小的例子如下:
snapshotDir := filepath.Join(
cast.ToString(appOpts.Get(flags.FlagHome)), "data", "snapshots")
snapshotDB, err := sdk.NewLevelDB("metadata", snapshotDir)
if err != nil {
panic(err)
}
snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir)
if err != nil {
panic(err)
}
app := baseapp.NewBaseApp(
"app", logger, db, txDecoder,
baseapp.SetSnapshotStore(snapshotStore),
baseapp.SetSnapshotInterval(cast.ToUint64(appOpts.Get(
server.FlagStateSyncSnapshotInterval))),
baseapp.SetSnapshotKeepRecent(cast.ToUint32(appOpts.Get(
server.FlagStateSyncSnapshotKeepRecent))),
)
當使用適當的標誌啟動應用程序時,(例如 --state-sync.snapshot-interval 1000 --state-sync.snapshot-keep-recent 2
)它應該生成快照並輸出日誌消息:
Creating state snapshot module=main height=3000
Completed state snapshot module=main height=3000 format=1
請注意,快照間隔當前必須是 pruning-keep-every
(默認為 100)的倍數,以防止在拍攝快照時修剪高度。至少保留 2 個最近的快照通常也是一個好主意,這樣當節點嘗試使用它進行狀態同步時,不會刪除以前的快照。
狀態同步節點
📣 提示 : 尋找快照或存檔節點來同步您的節點?查看 此頁面。
一旦網絡中的幾個節點拍攝了狀態同步快照,新節點就可以使用狀態同步加入網絡。 為此,首先應該像往常一樣配置節點,並且必須獲取以下信息以進行輕客戶端驗證:
- 兩個可用的 RPC 服務器(至少)
- 可信高度
- 可信高度的區塊ID哈希
可信哈希必須從可信源(例如區塊瀏覽器)獲得,但 RPC 服務器不需要可信。 Tendermint 將使用哈希從區塊鏈中獲取受信任的應用程序哈希,以驗證恢復的應用程序快照。應用程序哈希和相應的高度是恢復快照時唯一可以信任的信息。其他一切都可以由對手偽造。
在本指南中,我們使用 Ubuntu 20.04
準備系統
更新系統
sudo apt update -y
升級系統
sudo apt upgrade -y
安裝依賴
sudo apt-get install ca-certificates curl gnupg lsb-release make gcc git jq wget -y
安裝 Go
wget -q -O - https://raw.githubusercontent.com/canha/golang-tools-install-script/master/goinstall.sh | bash
source ~/.bashrc
設置節點名稱
moniker="NODE_NAME"
使用以下命令進行主網設置
SNAP_RPC1="http://xxx1:26657"
SNAP_RPC="http://xxx:26657"
CHAIN_ID="daodst_7777-1"
PEER="96557e26aabf3b23e8ff5282d03196892a7776fc@xxx,dec587d55ff38827ebc6312cedda6085c59683b6@xxx"
wget -O $HOME/genesis.json https://raw.githubusercontent.com/daodst/mainnet/genesis.json
安裝 stcd
git clone https://github.com/daodst/blockchain.git && \
cd cmd && cd stcd
go build
配置
節點初始化
stcd init $moniker --chain-id $CHAIN_ID
📣 提示 : $install_path
用來表示您安裝 stcd
二進製程序的路徑
將 genesis 文件移動到 $install_path/.stcd/config 文件夾
mv $HOME/genesis.json $install_path/.stcd/config/
重置節點
stcd tendermint unsafe-reset-all --home .stcd
更改配置文件(設置節點名稱、添加持久對等點、設置索引器 =“null”)
sed -i -e "s%^moniker *=.*%moniker = \"$moniker\"%; " $install_path/.stcd/config/config.toml
sed -i -e "s%^indexer *=.*%indexer = \"null\"%; " $install_path/.stcd/config/config.toml
sed -i -e "s%^persistent_peers *=.*%persistent_peers = \"$PEER\"%; " $install_path/.stcd/config/config.toml
設置從快照開始的變量
LATEST_HEIGHT=$(curl -s $SNAP_RPC/block | jq -r .result.block.header.height); \
BLOCK_HEIGHT=$((LATEST_HEIGHT - 2000)); \
TRUST_HASH=$(curl -s "$SNAP_RPC/block?height=$BLOCK_HEIGHT" | jq -r .result.block_id.hash)
檢查
echo $LATEST_HEIGHT $BLOCK_HEIGHT $TRUST_HASH
輸出示例(數字會有所不同):
376080 374080 F0C78FD4AE4DB5E76A298206AE3C602FF30668C521D753BB7C435771AEA47189
如果輸出正常,下一步
sed -i.bak -E "s|^(enable[[:space:]]+=[[:space:]]+).*$|\1true| ; \
s|^(rpc_servers[[:space:]]+=[[:space:]]+).*$|\1\"$SNAP_RPC,$SNAP_RPC1\"| ; \
s|^(trust_height[[:space:]]+=[[:space:]]+).*$|\1$BLOCK_HEIGHT| ; \
s|^(trust_hash[[:space:]]+=[[:space:]]+).*$|\1\"$TRUST_HASH\"| ; \
s|^(seeds[[:space:]]+=[[:space:]]+).*$|\1\"\"|" $install_path/.stcd/config/config.toml
創建stcd服務
echo "[Unit]
Description=Daodst Chain Node
After=network.target
#
[Service]
User=$USER
Type=simple
ExecStart=$(which stcd) daemon
Restart=on-failure
LimitNOFILE=65535
#
[Install]
WantedBy=multi-user.target" > $HOME/stcd.service; sudo mv $HOME/stcd.service /etc/systemd/system/
sudo systemctl enable stcd.service && sudo systemctl daemon-reload
運行 stcd
sytemctl start stcd
檢查日誌
journalctl -u stcd -f
當節點啟動時,它將嘗試在網絡中找到狀態同步快照,並恢復它:
Started node module=main nodeInfo="..."
Discovering snapshots for 20s
Discovered new snapshot height=3000 format=1 hash=0F14A473
Discovered new snapshot height=2000 format=1 hash=C6209AF7
Offering snapshot to ABCI app height=3000 format=1 hash=0F14A473
Snapshot accepted, restoring height=3000 format=1 hash=0F14A473
Fetching snapshot chunk height=3000 format=1 chunk=0 total=3
Fetching snapshot chunk height=3000 format=1 chunk=1 total=3
Fetching snapshot chunk height=3000 format=1 chunk=2 total=3
Applied snapshot chunk height=3000 format=1 chunk=0 total=3
Applied snapshot chunk height=3000 format=1 chunk=1 total=3
Applied snapshot chunk height=3000 format=1 chunk=2 total=3
Verified ABCI app height=3000 appHash=F7D66BC9
Snapshot restored height=3000 format=1 hash=0F14A473
Executed block height=3001 validTxs=16 invalidTxs=0
Committed state height=3001 txs=16 appHash=0FDBB0D5F
Executed block height=3002 validTxs=25 invalidTxs=0
Committed state height=3002 txs=25 appHash=40D12E4B3
節點現在狀態同步,在幾秒鐘內加入網絡
關閉狀態同步模式
在節點完全同步後,使用此命令關閉狀態同步模式,以避免將來節點重新啟動時出現問題!
sed -i.bak -E "s|^(enable[[:space:]]+=[[:space:]]+).*$|\1false|" $install_path/.stcd/config/config.toml
⚠️注意 :本文檔中包含的信息來自 Erik Grinaker,特別是他的狀態同步指南 Tendermint Core and the Cosmos SDK.