TM#011 - Wormhole - Journey Across the Chain
🖼 Picture this. The universe is vast, filled with countless galaxies, each one teeming with stars and planets. Just like our universe, the blockchain ecosystem is filled with a multitude of networks, each one unique and brimming with possibilities. Like galaxies that are light years apart, blockchain universe faces a similar problem: a lack of effective communication.
🃏 Why don't blockchains make good comedians? Because their jokes never get across! 🙃
Blockchains largely operate in isolation. A token or piece of data on the Solana network can't just take a leisurely stroll over to the Ethereum network. The decentralized stars, while brilliant on their own, haven't had the means to chat, to exchange ideas, or transfer assets. But imagine if they could.
This is where our interstellar analogy meets the harsh reality of blockchains. We are in need of a cosmic wormhole, a bridge that would connect these isolated islands of innovation. And guess what? Our wait is over. Allow me to introduce... drum roll 🥁, please... Wormhole!
Wormhole
Wormhole is the blockchain universe's very own Einstein-Rosen bridge. It facilitates cross-chain interaction, enriching our blockchain universe with its interoperability and message passing protocol. Through the implementation of its three core concepts, xAssets, xDapps, and xData.
Architecture
Now that we've set the stage, let's take a closer look at the architecture of Wormhole.
At a high-level:
And now a bit closer:
Wormhole's architecture comprises both on-chain and off-chain components that work together to enable secure and efficient cross-chain communication.
On-Chain Components
The on-chain components start with the Emitter, a contract that interacts with the Wormhole Core Contract to publish a message. This action results in an Event recorded in the Transaction Logs, providing details about the emitter and sequence number to identify the message. Several contracts can serve as Emitters, including:
Off-Chain Components
Off-chain components form the other half of Wormhole's architecture. The Guardian Network, made up of 19 validators or Guardians, observes and validates the messages emitted by the Core Contract on each supported chain. They create Verifiable Action Approvals (VAAs), the signed attestations of an observed message.
The Spy is a daemon that subscribes to messages within the Guardian Network, forwarding network traffic and helping scale VAA distribution. Wormhole also features an API that allows you to retrieve details for a VAA or the guardian network.
Finally, we have the Relayers - off-chain processes that relay a VAA to the target chain. This includes Automatic Relayers, forming a decentralized network delivering messages requested on-chain, and Specialized Relayers, handling VAAs for specific protocols or cross-chain applications, and executing custom logic off-chain to save gas costs and increase cross-chain compatibility.
TL; DR (you’re lazy): A contract known as the Emitter triggers a publish message method on the Wormhole Core Contract. This action is documented as an event in a transaction log. Off-chain, the Guardians in the Guardian Network read these logs, verify the emitted messages, and create Verifiable Action Approvals (VAAs). Finally, these VAAs are relayed to the target chain by either Automatic or Specialized Relayers.
Sending a Message through the Wormhole
Now that we have established a comprehensive understanding of Wormhole's mechanisms and offerings (hurray 🥳), let's put theory into practice. The next step? Sending a message from Solana to Ethereum using Wormhole.
Solana Emitter
We're using a Solana program as our Emitter to interact with the Wormhole contract. Anchor, the go-to development framework for Solana, is our trusty spaceship for this journey, and we're equipping it with the Wormhole Anchor SDK. You can find the source code for the SDK here.
Configuring the Interface
On Solana, invoking a method on another contract requires us to provide certain accounts. This can be achieved through Cross Program Invocation (CPI). To facilitate this process, the Wormhole TypeScript SDK has a convenient function called getWormholeCpiAccounts which we'll employ to obtain the required accounts.
const wormhole = getWormholeCpiAccounts(
CORE_BRIDGE_PID, // Core Wormhole Contract for Solana
KEYPAIR.publicKey,
program.programID, // Your Solana Program ID
// And below is a PDA for the Sequence Tracker account
deriveAddress([Buffer.from("sent"), 0], program.programID)
);
The function mentioned above will return all of the following accounts that we specify in Anchor.
Recommended by LinkedIn
#[account(
mut,
seeds = [wormhole::BridgeData::SEED_PREFIX],
bump,
seeds::program = wormhole_program,
)]
pub wormhole_bridge: Account<'info, wormhole::BridgeData>,
#[account(
mut,
seeds = [wormhole::FeeCollector::SEED_PREFIX],
bump,
seeds::program = wormhole_program
)]
pub wormhole_fee_collector: Account<'info, wormhole::FeeCollector>,
#[account(
init,
payer = owner,
seeds = [WormholeEmitter::SEED_PREFIX],
bump,
space = WormholeEmitter::MAXIMUM_SIZE
)]
pub wormhole_emitter: Account<'info, WormholeEmitter>,
#[account(
mut,
seeds = [
wormhole::SequenceTracker::SEED_PREFIX,
wormhole_emitter.key().as_ref()
],
bump,
seeds::program = wormhole_program
)]
pub wormhole_sequence: UncheckedAccount<'info>,
#[account(
mut,
seeds = [
SEED_PREFIX_SENT,
&wormhole::INITIAL_SEQUENCE.to_le_bytes()[..]
],
bump,
)]
pub wormhole_message: UncheckedAccount<'info>,
pub clock: Sysvar<'info, Clock>,
pub rent: Sysvar<'info, Rent>,
I understand that this involves a significant amount of configuration, but that's Solana being Solana.
🃏 How many Solana developers does it take to change a light bulb? One to change the bulb and fifty to create the PDAs necessary to do it. 🙃
Having completed these configurations, we can now proceed further.
The main way our Emitter interacts with the Wormhole is via the core contract. We will invoke the post_message method, which is essentially a way for us to send our message across chain. The function implementation is as follows:
pub fn post_message<'info>(
ctx: CpiContext<'_, '_, '_, 'info, PostMessage<'info>>,
nonce: u32,
payload: Vec<u8>,
finality: Finality,
) -> Result<()> {
Here's what each part means:
And with those explanations, we're ready to look at our Rust code snippet for posting a message:
let payload: Vec<u8> = "Hello from the other side".as_bytes().to_vec();
let nonce: u32 = 0;
let finality = wormhole::Finality::Confirmed as u8;
match wormhole::post_message(
CpiContext::new_with_signer(
ctx.accounts.wormhole_program.to_account_info(),
wormhole::PostMessage {
config: ctx.accounts.wormhole_bridge.to_account_info(),
message: ctx.accounts.wormhole_message.to_account_info(),
emitter: ctx.accounts.wormhole_emitter.to_account_info(),
sequence: ctx.accounts.wormhole_sequence.to_account_info(),
payer: ctx.accounts.owner.to_account_info(),
fee_collector: ctx.accounts.wormhole_fee_collector.to_account_info(),
clock: ctx.accounts.clock.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
},
&[
&[
SEED_PREFIX_SENT,
&wormhole::INITIAL_SEQUENCE.to_le_bytes()[..],
&[*ctx.bumps.get("wormhole_message")?,
],
&[wormhole::SEED_PREFIX_EMITTER, &[&ctx.accounts.wormhole_emitter.bump]],
],
),
nonce,
payload,
finality.into(),
) {
Ok(_) => {}
Err(e) => {
msg!("Error Posting Message: {:?}", e);
return Err(e);
}
}
In the Rust code above, the payload is set to the message we want to send ("Hello from the other side"). The method post_message is then called with a number of parameters. In case of an error during the execution of this method, it gets captured and logged for troubleshooting.
Once we successfully execute the CPI, we get a sequence number. This special number allows us to retrieve our Verifiable Action Approval (VAA). If you have the local wormhole validator running (‣), You're one step away from fetching your VAA! All you have to do is visit the following URL, replacing ${wormholeChainId}, ${emitterAddr}, and ${seq} with your specific details:
<http://localhost:7071/v1/signed_vaa/${wormholeChainId}/${emitterAddr}/${seq}>
Et voilà! 🎊 Here is our VAA, looking like a cryptic piece of alien tech:
010000000001009ba8eca3ad035da554498a113bc460b05f18849c1fb256540c05cedc9e9918846326ac799d548392319ea93e0721da8fc231f71e64097b398cff133d4d9843e7000000000100000001000104a97fa4da1675cf1a83750edcc176e956fe37fc0ffd8db87eeff6cc78ebd51b000000000000000001656c6c6f2066726f6d20746865206f746865722073696465
But fret not, you can use vaa.dev to decipher it and get a good look at the payload.
EVM Receiver
Our final mission is to transport the above VAA to Ethereum and decode the payload to access its contents.
Configuring the Interface
Here is the interface for applications to interact with Wormhole's Core Contract to publish VAAs or verify and parse a received VAAs.
Instantiating the interface will depend on the contract address of your development ecosystem and blockchain.
Below is an example line of code to instantiate the interface for mainnet Ethereum:
address private wormhole_core_bridge_address = address(0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B);
IWormhole core_bridge = IWormhole(wormhole_core_bridge_address);
Now, we can parse the VAAs emitted by other chains.
function receiveEncodedMsg(bytes memory encodedMsg) public {
(IWormhole.VM memory vm, bool valid, string memory reason) = core_bridge.parseAndVerifyVM(encodedMsg);
//Check Wormhole Guardian Signatures
// If the VM is NOT valid, will return the reason it's not valid
// If the VM IS valid, reason will be blank
require(valid, reason);
//Check that the message hasn't already been processed
require(!_completedMessages[vm.hash], "Message already processed");
_completedMessages[vm.hash] = true;
//Do the thing
current_msg = string(vm.payload);
emit message(current_msg);
}
And there you have it! 🎉 You've just successfully sent a message across chains.
Conclusion
In conclusion, Wormhole's interoperability protocol plays an integral role in overcoming the challenges of blockchain communication, allowing seamless cross-chain movement with minimal development effort. With its ingenious architecture and components such as xData and xAssets, it breaks the barriers of isolated chains and establishes a unified ecosystem.
By sending a message from Solana to Ethereum, we've demonstrated just the tip of the iceberg when it comes to what can be achieved.
These bridges can help mitigate scalability issues by allowing transactions to be processed on alternative, less congested blockchains, then transferring the results back to the main chain. Cross-chain bridges like Wormhole are more than just a technological breakthrough; they are an essential catalyst in the ongoing evolution of blockchain technology.
This article is written by Hamza Khalid , a Full-Stack Engineer at Antematter.io.
Technical Copywriter: I help sell blockchain infra & projects with words & proven marketing strategies. | Securing smart contracts on EVM chains | DAOist 🫂.
1yBrilliant article Antematter. I loved it because of the interstellar analogy 👏🏼