Wormhole - Journey Across the Chain

Embark on an interstellar voyage with Wormhole, the blockchain universe's answer to cross-network communication. This article explores Wormhole's capacity to link isolated blockchain networks, fostering asset exchange and information transfer akin to cosmic wormholes between distant galaxies. It outlines Wormhole's core concepts of xAssets, xDapps, and xData, which collectively facilitate the movement of wrapped tokens, decentralized app functions, and data across various chains. Diving into Wormhole's architecture reveals on-chain and off-chain components working in tandem to ensure secure, efficient cross-chain interactions. From Solana's Emitter contracts to Ethereum's Receiver interfaces, Wormhole exemplifies technical sophistication, pushing the boundaries of blockchain capabilities and paving the way for a unified, scalable ecosystem.

šŸ’” Articles
8 January 2024
Article Image

šŸ–¼ 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:

High-level overview of Wormhole architecture

And now a bit closer:

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):

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.