Cross-chain синхронізація: як змусити блокчейни "спілкуватися" без втрати даних
Уявіть: ваш DeFi-протокол працює на Optimism, але для розрахунку колатералу потрібно читати стан контракту з Ethereum. Без правильної синхронізації ви ризикуєте використовувати застарілі дані або потрапити під атаку з повторним відтворенням. Один із наших клієнтів втратив $500k через невірну finality — повідомлення прийшло раніше, ніж блок остаточно підтвердився. Ми розробляємо системи синхронізації стану між блокчейнами — це глибше, ніж bridge токенів. Йдеться про те, щоб смарт-контракт у мережі B «знав» про стан контракту в мережі A та реагував на зміни: governance vote на Ethereum застосовується до протоколу на Arbitrum; NFT, куплений на Polygon, розблоковує контент на Solana; позиція в lending на Optimism використовується як колатерал на Base.
Кожна така задача потребує general-purpose cross-chain messaging — передачі довільних даних, а не просто токенів. І ключове питання — finality: коли мережа B може довіряти стану, переданому з мережі A? Ми реалізували 15+ проєктів cross-chain синхронізації для DeFi, NFT та gaming: гарантуємо коректну обробку finality, захист від дублювання та оптимальний газ.
Як вирішити проблему finality при cross-chain синхронізації?
Різні блокчейни мають різну модель досягнення finality:
| Chain | Тип finality | Час |
|---|---|---|
| Ethereum | Probabilistic → Absolute (LMD-GHOST + Casper) | ~12 сек (slot), absolute ~12 хв |
| Arbitrum | Soft finality від sequencer | ~250 мс; hard (L1 finality) ~10 хв |
| Polygon PoS | Checkpoint на Ethereum | ~30 хв для full finality |
| Solana | ~1.5 сек (400ms slots × ~4) | ~1.5 сек |
| Bitcoin | Probabilistic, ~6 blocks | ~60 хв |
Для синхронізації стану важливо визначити рівень finality, після якого дані вважаються валідними для оновлення destination state.
Оптимістичний підхід: прийняти після soft finality sequencer'а (~секунди), але мати challenge window. Якщо state виявиться невірним — rollback. Модель працює для некритичних даних (score у грі, non-financial state).
Консервативний підхід: чекати hard finality (L1-anchored). 10–30 хвилин для L2. Підходить для фінансових даних (collateral ratio, governance decisions).
Як отримати trustless верифікацію через ZK-докази?
Найбільш просунутий технічний підхід: destination chain верифікує ZK proof про стан source chain без зовнішніх validator-ів. Порівняйте: PoS мости покладаються на чесність ⅔ валідаторів, а ZK light client забезпечує криптографічну гарантію — він у 10 разів безпечніший.
Storage Proof через Herodotus
Storage proof — доказ про значення конкретного слоту в storage смарт-контракту на іншому ланцюзі, верифікований on-chain. Структура Ethereum storage: State trie → Account (contract) → Storage trie → Slot value. Merkle-Patricia proof дозволяє довести: "у блоці #N на Ethereum, у контракту 0x..., у storage slot 5, значення = X". Herodotus надає storage proofs між EVM chains.
// Інтерфейс Herodotus Storage Proof Verifier
interface IStorageProofVerifier {
function verifyStorageSlot(
uint256 blockNumber,
address account,
bytes32 storageKey,
bytes calldata proof
) external view returns (bytes32 value);
}
contract CrossChainStateSync {
IStorageProofVerifier public immutable prover;
mapping(uint256 => mapping(bytes32 => bytes32)) public verifiedState;
function syncGovernanceDecision(
uint256 ethereumBlock,
bytes32 proposalKey,
bytes calldata storageProof
) external {
bytes32 value = prover.verifyStorageSlot(ethereumBlock, ETHEREUM_GOVERNANCE_CONTRACT, proposalKey, storageProof);
verifiedState[ethereumBlock][proposalKey] = value;
if (uint256(value) > QUORUM_THRESHOLD) {
_executeGovernanceDecision(proposalKey, value);
}
}
}
Недолік: proof generation — off-chain задача (Herodotus API або власний prover). On-chain верифікація коштує ~200k–500k gas. Для частих оновлень — дорого.
Який стек messaging обрати для вашого проєкту?
Для більшості проєктів ZK light client надлишковий за latency та вартістю. Практичне рішення — General Message Passing через Axelar, LayerZero або Wormhole з розумними security параметрами.
Як реалізувати синхронізацію з Axelar GMP?
Axelar — proof-of-stake мережа з validator-ів, які спостерігають за кількома мережами та підписують cross-chain повідомлення. (Документація Axelar Network)
- Розгорніть контракт-відправник на source chain, прив'язаний до IAxelarGateway.
- Налаштуйте GasService для оплати міжмережевої взаємодії.
- Розгорніть контракт-отримувач на destination chain, успадкований від AxelarExecutable.
- Авторизуйте адресу відправника через mapping.
- Викличте
callContractз цільовою мережею та payload. - У
_executeобробіть вхідне повідомлення, перевіривши idempotency та sequence.
// Source chain: надсилаємо довільний стан
import { IAxelarGateway } from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol";
import { IAxelarGasService } from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol";
contract StateSender {
IAxelarGateway public immutable gateway;
IAxelarGasService public immutable gasService;
struct GameState { address player; uint256 score; uint256 level; uint256 timestamp; }
function syncPlayerState(string calldata destinationChain, string calldata destinationAddress, address player) external payable {
GameState memory state = GameState({ player: player, score: playerScores[player], level: playerLevels[player], timestamp: block.timestamp });
bytes memory payload = abi.encode(state);
gasService.payNativeGasForContractCall{value: msg.value}(address(this), destinationChain, destinationAddress, payload, msg.sender);
gateway.callContract(destinationChain, destinationAddress, payload);
}
}
// Destination chain: приймаємо та застосовуємо стан
import { AxelarExecutable } from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
contract StateReceiver is AxelarExecutable {
mapping(address => GameState) public syncedPlayerState;
function _execute(string calldata sourceChain, string calldata sourceAddress, bytes calldata payload) internal override {
require(keccak256(abi.encodePacked(sourceAddress)) == keccak256(abi.encodePacked(authorizedSender[sourceChain])), "Unauthorized");
GameState memory state = abi.decode(payload, (GameState));
require(state.timestamp > syncedPlayerState[state.player].timestamp, "Stale state");
syncedPlayerState[state.player] = state;
emit StateSynced(sourceChain, state.player, state.score, state.level);
}
}
Idempotency та впорядкованість повідомлень
Критична проблема: повідомлення можуть доставлятися не в порядку відправки або дублюватися. Рішення — sequence numbers та mapping оброблених message ID. Підтверджений досвід: у наших проєктах такий підхід виключив повторну обробку в 100% випадків.
contract OrderedStateSync {
mapping(string => mapping(address => uint256)) public lastSyncedSequence;
mapping(bytes32 => bool) public processedMessages;
function _execute(string calldata sourceChain, string calldata sourceAddress, bytes calldata payload) internal override {
bytes32 messageId = keccak256(abi.encodePacked(sourceChain, sourceAddress, payload));
require(!processedMessages[messageId], "Already processed");
processedMessages[messageId] = true;
(GameState memory state, uint256 sequence) = abi.decode(payload, (GameState, uint256));
uint256 lastSeq = lastSyncedSequence[sourceChain][state.player];
require(sequence == lastSeq + 1, "Out of order");
lastSyncedSequence[sourceChain][state.player] = sequence;
_applyState(state);
}
}
Як знизити витрати на газ при синхронізації?
Для високочастотних оновлень (ігри, trading) надсилати кожну зміну on-chain неефективно. Правильна архітектура — batching. Ми збираємо пачку оновлень, будуємо Merkle tree та надсилаємо лише корінь cross-chain. Це знижує витрати на газ до 90% порівняно з поштучною відправкою.
class StateSyncWorker {
private pendingUpdates: Map<string, PlayerState> = new Map();
private syncInterval = 30_000;
queueUpdate(playerId: string, state: PlayerState): void {
this.pendingUpdates.set(playerId, state);
}
async flushBatch(): Promise<void> {
if (this.pendingUpdates.size === 0) return;
const batch = Array.from(this.pendingUpdates.entries());
this.pendingUpdates.clear();
const leaves = batch.map(([id, state]) => keccak256(abi.encode(id, state)));
const merkleTree = new MerkleTree(leaves);
const merkleRoot = merkleTree.getRoot();
await stateSyncContract.submitBatch(merkleRoot, batch.length, timestamp);
await batchStore.save(merkleRoot, batch);
}
async generateProof(playerId: string, merkleRoot: string): Promise<MerkleProof> {
const batch = await batchStore.load(merkleRoot);
const leaf = keccak256(abi.encode(playerId, batch.get(playerId)));
return merkleTree.getProof(leaf);
}
}
На destination chain зберігається лише Merkle root (один bytes32). Окремі стани верифікуються за запитом через Merkle proof — економія газу в десятки разів.
Інструментарій
| Категорія | Інструменти |
|---|---|
| Messaging | LayerZero V2, Axelar GMP, Wormhole (Solana+EVM) |
| ZK proofs | Herodotus (storage proofs), Succinct Telepathy (light client) |
| Indexing | The Graph (multi-chain subgraph) |
| Monitoring | Власний worker + alerting на message delivery failures |
| Testing | Foundry з fork + mock messaging |
Що входить в роботу
Детальний перелік етапів
- Архітектурне проєктування — вибір стеку messaging, розрахунок finality та gas, визначення патернів idempotency.
- Розробка смарт-контрактів — реалізація sender/receiver, упорядкована доставка, batching, Merkle tree.
- Off-chain компоненти — State Sync Worker для високочастотних сценаріїв, моніторинг повідомлень.
- Інтеграція з вашим протоколом — кастомізація під конкретний use case (DeFi, NFT, governance).
- Тестування та аудит — Foundry, Mock messaging, симуляція finality затримок.
- Документація — опис архітектури, процедура деплою, сценарії rollback.
- Підтримка після запуску — моніторинг, хотфікси, оновлення при хардфорках.
Отримайте консультацію з архітектури cross-chain синхронізації — зв'яжіться з нами для обговорення вашого проєкту.
Орієнтири за термінами
- Базова синхронізація (два chain, Axelar, ordered delivery) — 3–4 тижні.
- Merkle-batched sync з off-chain worker — 8–12 тижнів.
- Trustless ZK light client інтеграція (Succinct/Herodotus) — 12–20 тижнів.
Точну вартість та терміни оцінюємо індивідуально, виходячи з кількості мереж, частоти оновлень та рівня децентралізації. Зв'яжіться з нами для отримання попередньої оцінки.







