TM#011 - Wormhole - Journey Across the Chain

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.

  • xAssets: A wrapped token concept, it allows tokens to detach from their native chains, creating assets that are chain-and-path agnostic, capable of transacting on any blockchain.
  • xDapps: Cross-Chain Decentralized Applications (xDapps) that utilize xData to operate across a variety of blockchains and runtimes. It's about enabling decentralized applications to run on any chain.
  • xData: Like the grand library of Alexandria, a universal store of data accessible by any chain. A layer that exists independent of any blockchain, xData represents arbitrary data.

Architecture

Now that we've set the stage, let's take a closer look at the architecture of Wormhole.

At a high-level:

No alt text provided for this image
High-level overview of Wormhole architecture

And now a bit closer:

No alt text provided for this image
A finer look. Please download the image to get the details.

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:

  • xAsset Contracts for asset conversion and bridging
  • Relay Contracts for message dispatch across chains
  • Worm Router Contracts enabling Dapps to function cross-chain

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.

	#[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:

  • payload: This is our actual message - the "Hello from the other side" of blockchain. It's an arbitrary byte array, which might have a maximum length due to some blockchains' limitations.
  • finality: This is our protective gear against reorgs and rollbacks, specifying the level of finality before the Wormhole VAA gets our message.
  • nonce: This is a unique index for the message that will be used to generate Batch VAAs.
  • sequenceNumber: A unique index for the message. When combined with the emitter contract address and emitter chain ID, we can retrieve the corresponding VAA from a guardian network node.

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.

Harvest O.

Technical Copywriter: I help sell blockchain infra & projects with words & proven marketing strategies. | Securing smart contracts on EVM chains | DAOist 🫂.

1y

Brilliant article Antematter. I loved it because of the interstellar analogy 👏🏼

To view or add a comment, sign in

More articles by Antematter

Insights from the community

Others also viewed

Explore topics