Cross-Chain Messages

On Linera, applications are meant to be multi-chain: They are instantiated on every chain where they are used. An application has the same application ID and bytecode everywhere, but a separate state on every chain. To coordinate, the instances can send cross-chain messages to each other. A message sent by an application is always handled by the same application on the target chain: The handling code is guaranteed to be the same as the sending code, but the state may be different.

For your application, you can specify any serializable type as the Message type in your ContractAbi implementation. To send a message, return it among the ExecutionResult's messages:

    pub messages: Vec<OutgoingMessage<Message>>,

The first field specifies either a single destination chain, or a channel, so that it gets sent to all subscribers.

If the second field is true, the callee is allowed to perform actions that require authentication on behalf of the signer of the original block that caused this call.

The third field is the message itself, of the type you specified in the ContractAbi.

You can also use ExecutionResult::with_message and with_authenticated_message for convenience.

During block execution in the sending chain, messages are returned via ExecutionResults. The returned message is then placed in the target chain inbox for processing. There is no guarantee that it will be handled: For this to happen, an owner of the target chain needs to include it in the incoming_messages in one of their blocks. When that happens, the contract's execute_message method gets called on their chain.

Example: Fungible Token

In the fungible example application, such a message can be the transfer of tokens from one chain to another. If the sender includes a Transfer operation on their chain, it decreases their account balance and sends a Credit message to the recipient's chain:

async fn execute_operation(
    &mut self,
    context: &OperationContext,
    operation: Self::Operation,
) -> Result<ExecutionResult<Self::Message>, Self::Error> {
    match operation {
        Operation::Transfer {
            owner,
            amount,
            target_account,
        } => {
            // ...
            self.debit(owner, amount).await?;
            let message = Message::Credit {
                owner: target_account.owner,
                amount,
            };
            Ok(ExecutionResult::default().with_message(target_account.chain_id, message))
        }
        // ...
    }
}

On the recipient's chain, execute_message is called, which increases their account balance.

async fn execute_message(
    &mut self,
    context: &MessageContext,
    message: Message,
) -> Result<ExecutionResult<Self::Message>, Self::Error> {
    match message {
        Message::Credit { owner, amount } => {
            self.credit(owner, amount).await;
            Ok(ExecutionResult::default())
        }
        // ...
    }
}