Move++ - Enabling Highly Secure Cross-Shard Transactions Based on Move

Introduction

We are working on applying sharding/multi-chain technologies to Libra in order to achieve better scalability and more decentralized. One major motivation is that we found Move language is a very good fit for cross-shard interoperability compared to existing ones (e.g., EVM). One beautiful result is that, we could impose the safety feature of the resource defined in Move not noly in a single chain setup (current Libra), but also in the sharding/multi-chain setup, i.e., safely moving resources from one chain to another chain.

Note that safely exchanging assets in different chains is a major challenge in cross-chain interoperability, there are a lot of designs aim to address it. With the safety feature enabled by Move, we believe this shed the light on the new direction of interoperability of multi-chain.

Further, to simplify the use of the cross-shard communication, we also add a few features on Move (most in async support), namely, Move++. We would like to share the idea, and any comments are welcome!

System Setup

We consider a sharding blockchain network setup, which has N + 1 chains running in parallel:

  • N shard chains, one of which runs an instance of Libra chain with
    – Simplified version of LibraBFT with smaller validator size (e.g., 10)
    – Libra Account Model + Move VM
  • 1 root chain, which runs an instance of Libra
    – Full LibraBFT with about 100 validators
    – Libra Account Model + Move VM

In addition, the ledger/block of the root chain contains the latest views of all shard chains, and all shard chains follow the views defined by the root chain. This ensures the network has a consistent view of all ledgers of N + 1 chains.

Note that this kind of sharding setup is common in most sharding/multi-chain designs (such as Eth2.0/Polkadot).

Cross-Shard Communications

We also consider the following cross-shard/chain communication model:

  • C1: A shard chain can read any data of the root chain (e.g., import the module of root chain)
  • C2: A shard chain can issue any transaction script to another shard chain (e.g., schedule an async call), and such a script can be executed eventually at destination shard.

There are a couple of discussions on how these can be achieved. We ignore the details here but readers of interest can refer to the following links (link1, link2) to get more details.

Example on Moving Tokens Between Shard Chains

Let us consider the following MyToken module:

module MyToken {
    resource T {
        value: u64;
    }
    mint(amount: u64): Self.T;
    withdraw_from_sender(amount: u64): Self.T;
    deposit(payee: address, token: Self.T);
}

where a user can move its token to another account by calling the following script in current Libra blockchain:

import 0x1234.MyToken
main() {
  let token: MyToken.T;
  token = MyToken.withdraw_from_sender(100);
  MyToken.deposit(0x1111,  move(token));
}

Now, let us move to multi-chain/sharding context. First of all, we deploy the same module on the root chain:

module MyToken {
    resource T {
        value: u64;
    }
    mint(amount: u64): Self.T;
    withdraw_from_sender(amount: u64): Self.T;
    deposit(payee: address, token: Self.T);
}

Note that the module must be deployed on the root chain so that all shard chains can read/import the module.

Second, if a user wants to transfer its token to another in the root chain, the code will be the same as before. But suppose the transfer happens in the shard chain, where the payee account is in the same shard, a.k.a., in-shard transaction, the transaction script will be:

import root.0x1234.MyToken
main(payee: address, amount: u64) {
  let token: MyToken.T;
  token = MyToken.withdraw_from_sender(amount);
  MyToken.deposit(copy(payee),  move(token));
}

where one major change is that instead of importing local modules, the transaction will import the module from the root chain following C1. This ensures that no matter which shard chain runs the transactions, it will always move the same resources defined by the module of the root chain.

Third, suppose a user wants to transfer its token to another account in different shard chain, then the code will become:

import root.0x1234.MyToken
main(payee: address, payee_chain_id: u32, amount: u64) {
  let token: MyToken.T;
  let chain: Chain;
  token = MyToken.withdraw_from_sender(amount);
  chain = get_chain_by_id(payee_chain_id);
  chain.call_async(move || MyToken.deposit(copy(payee), move(token)));
}

where the major differences compared to in-shard transaction script are:

  • The script will obtain a target chain object
  • The script will create a deposit object and send it to the target chain. The deposit object contains
    – The resources/structs that are going to be moved/copied to the target shard (token/payee)
    – A script to be run in the target shard (MyToken.deposit(copy(payee), move(token)), where the moved/copied resources/structs will be recovered when running the script at the target shard (C2).
    – The script and the resources are defined in a rust-style lambda expression, where (move || … ) means to move all resources used in the lambda and store it in the deposit

Upon processing the deposit object at the target shard, the corresponding resources (MyToken here) will be moved/merged to the recipient. Since all the move operations are governed by the same module, we ensure the resource safety during the cross-shard transaction.

Future Topics

We are working on an MVP based on Libra code to enable the above features. Meanwhile, there are several topics need to be discussed:

  • Global/local resource separation
  • Cross-shard transaction gas metering
  • Failure handling if the target shard fails to process the script
3 Likes