NOTE: This document is DEPRECATED. A new version will be published at some point.

Motivation

The purpose of the protocol is to transfer value between independent shards.

The shards could be either RChain networks or any other network that implements the protocol.

Each client trusts his own shard and it's ancestors all the way up to the root shard. Clients living in two different shards will not both trust the other party's shard. 

For this reasons, when two parties are transferring value between two arbitrary shards, they should meet at the least common ancestor, where the value exchange between the two parties becomes a normal blockchain transaction.

The cross-shard value transfer protocol provides a way for each of the clients to move value from their own wallets to a parent shard or back.

Outstanding issues

  1. how to do weighted multi-signature 
  2. notification on block finalization (check with Michael Birch (Unlicensed) & Former user (Deleted))
  3. Should we have a shard DAG in a future version?
  4. how to roll-back cross-shard transfers when something goes wrong during the transfer?
  5. how to mount an existing shard?
  6. How to unmount a shard without destroying it?
  7. Why not register all the wallets in a public WalletRegistry?
  8. Do we sign the "approved" contracts used for REV?
  9. How to avoid the situation when the validators move their stakes out of the child shard while still holding their validator status?
  10. How does interaction with PoS contract happens at bonding/unbonding
  11. how to update K in order to keep the K/N ratio the same as validators bond/unbond. Should we keep the ratio in the config and re-compute K each time?

Architecture

The shards are organised in a tree structure. 

The dependencies are from child shards to parent shards. 

The validators of the child chard act as clients of the parent shard.

The parent shard validators are not involved in the child shards.

Components

Common data structures

KonsensusConfig

  • genesis_hash
  • validators : [ ValidatorConfig ]
  • k : Int

ValidatorConfig

  • ip_address
  • public_key
  • expected_bond : Int

K-of-N consensus

KonsensusFactory

Creates the Konsensus contract. This extra layer is needed because the create call has to be confirmed by all the validators.

KonsensusFactory API

createKonsensusConfig→ KonsensusFactoryInstantiates the K-of-N contract given the public keys of the validators and a given K value
createKonsensusSignature → KonsensusCreates the consensus after all the signatures are accumulated. The expected signature is the name of Konsensus process, signed with the caller's private key. The call returns only when all the required signatures are accumulated.

KonsensusProxy

This is used to control messages sent to a name. The notation KonsensusProxy[X] used further means that, after a message with K signatures is received the call will be forwarded to X. 

The KonsensusProxy is instantiated by Konsensus:makeProxy. The K used is the one which Konsensus was created with, See KonsensusFactory:create

KonsensusProxy

sendMessage → Signature → UnitAccumulates K signatures on a message. After all the K messages are received the message is forwarded to the underlying address. The expected signature is the message signed with the callers' private key.

KoNsensus

A smart contract running in the parent shard which supervises the consensus in the child shard by running a K-of-N algorithm.

This mechanism is required because the parent shard validators don't want to be aware of any child shards.

Processes that require k-of-n consensus are wrapped in a KonsensusProxy by calling makeProxy

Konsensus API

getConfigUnit → KonsensusConfigreturns the configuration of the Konsensus contract

getUpdater

Unit → KonsensusProxy[KonsensusUpdater]Creates an updater for this Konsensus instance, wrapped in a KonsensusProxy that requires signatures equal to the number of public-keys 
makeProxyName → KonsensusProxy[_]Creates a proxy on top of an existing name

KonsensusUpdater

Processes that require k-of-n consensus are wrapped in a KonsensusProxy by calling makeProxy

KonsensusUpdater API

updateConfig

KonsensusConfig → UnitUpdates the Konsensus contract configuration with a new set of validators and a new K value. 

Child-validator management

ValidatorManager

Holds the stakes placed by the stakeholders.

K-of-N validators have to agree upon any changes to the validators list. For this reason the ValidatorManager is created be wrapped into a KonsensusProxy

ValidatorManager API

create[ValidatorConfig] → (KonsensusProxy[ValidatorManager], [StakeAdder] )Given a list of public keys of the child shard validators and their expected deposits returns a ValidatorManager, wrapped in a KonsensusProxy and a list of StakeAdderS
addValidatorPublicKey → StakeAdderstarts the operation of adding a new validator. The operation is finished when the new validator places his stake by calling StakeAdder.placeStake
removeValidatorPublicKey → Unitstarts the operation of removing an existing stakeholder from the depository

StakeAdder

This contract has the authority to place a new stake in the ValidatorManager. This has to be a separate capability and cannot be put in ValidatorManager because it doesn't have to be wrapped in the KonsensusProxy and placeStake has to be called independently by the new validator.

StakeAdder API

placeStakeSignature → Purse → Validator

used by child-shard validators to place their initial deposits. 

The expected signature is the purse name signed with a private key registered with the ValidatorManager during its creation.

Validator

The owner of the Validator instance has the right to withdraw his stake from the Depository, to the child-shard purse containing his tokens and can make a withdraw request.

The withdraw request is finished and the purse is returned to the caller only after a call to removeStakeholder.

When all the stakeholders have removed their stakes, the depository is destroyed.  

Validator API
withdrawUnit → Pursereturns a purse with the deposit placed by the validator. This call completes after  removeValidator is called in the stake storage
getChildPurseUnit → Namereturns the name of the purse residing in the child shard and holding the equivalent of the placed stake in child shard tokens

Depository

The Depository (aka "Fort Knox") is a pair of public smart contracts running in a parent shard.

It manages the value available in the child shard.

When tokens are transferred from a child shard to the parent shard the tokens are removed from the Depository. The tokens are placed in a purse

When value is transferred from the parent shard to the child shard the value is put in the Depository.

The value in the Depository must always be positive. The value transferred out of a shard cannot exceed the value stored in the Depository.

The Depository can charge a fee for transferring tokens to the child shard.

The Depository.Deposit is the capability object used to transfer tokens to the child-shard. 

The Depository.Withdrawer is the capability object used to mint tokens in the child-shard. The access to this contract has to be controlled by the validators so it is wrapped at creation time in a KonsensusProxy. 


DepositoryAPI

createName → (Deposit, KonsensusProxy[Withdraw])

The name parameter represents the name of the depository in the parent shard

Depository.Deposit API

transferToChildPurse → PublicKey → Name

destroys the content of the purse and returns the name of a new wallet located in the child-shard, minus fees. The new wallet contains tokens . The wallet can be used by the owner of the corresponding private-key

The returned name is a wallet registered in the child shard


Depository.Withdrawer API

withdrawAmount → Purse

destroys the content of the purse and returns the name of a new purse located in the parent shard, minus fees. The new purse is sprouted from the Depository and its value + fees is removed from the Depository.

The returned name is a wallet registered in the parent shard

The Mint

The Mint is a pair of public smart contracts running in a child shard. 

When value enters the child shard the Mint creates (aka mints) tokens in it's shard. These tokens are stored in a purse.

When value exits the child shard the Mint destroys (aka burns) tokens in it's shard. These tokens are sent to the mint in a purse.

The Mint establishes the exchange rate between parent-shard token and child-shard token.

The Mint is different than a wallet by the fact that any received funds are burned and any funds it sends are freshly minted.

The Mint can charge a fee for transferring tokens to the parent shard.

The Mint.Withdrawer is the capability object used to transfer tokens to the parent-shard. 

The Mint.Minter is the capability object used to mint tokens in the child-shard. The access to this contract has to be controlled by the validators so it is wrapped at creation time in a KonsensusProxy. 


MintAPI

createName → (Withdrawer, KonsensusProxy[Minter])

The name parameter represents the name of the depository in the parent shard

Mint.Withdrawer API

transferToParent(Purse, PublicKey) → Name

destroys the content of the purse and returns the name of a new wallet located in the parent shard, minus fees. The new wallet is sprouted from the Depository and its value + fees is removed from the Depository. The wallet can be used by the owner of the corresponding private-key

The returned name is a wallet registered in the parent shard


Mint.Minter API

mintAmount → Purse

Creates the required amount of tokens. The tokens are stored in a new purse.

A purse is a contract which holds tokens. It is used as transport means for the tokens.

The purses are either created by the shard's Mint or "sprout" from another purse.

Purse API
splitAmount → Pursecreates a new purse containing the given amount. This amount is subtracted from the current purse. Empty purses are destroyed

Wallets

A wallet is an address identified by a public key.


Wallet API

create(Purse,PublicKey) → WalletCreates a wallet given a purse with the initial value and a public key. The purse is destroyed and the returned wallet will contain the value of the Purse minus fees
depositPurse → UnitDeposits a purse into this wallet. The purse is destroyed
withdraw Amount → Purse

Withdraws an amount from this wallet. A new purse is returned.

getValueUnit → Amountreturns the value stored in the wallet

Shard tree setup

Sprout

Sprouting is the process of creating a child shard with an empty value stored inside.

This is the simplest scenario for building the shard tree.

The steps required for sprouting are:

  1. Konsensus setup
  2. Create Mint & Depository
  3. Stakes placement

See each of these steps described in details below

K-of-N Konsensus setup

In the parent-shard the Konsensus contract is instantiated through the following steps in order to avoid a MITM attack by the validator who initiates the process:

  1. The KonsensusFactory is created in the parent shard by an arbitrary child validator and registered under a public name
  2. The name is distributed to all the validators 
  3. Each of the validators independently send signed messages to the above name and, this way, they validate that their correct public key is used
  4. When all the correct signatures are accumulated the Konsensus is created, registered under a public name and the name is returned to the child shard

In the child shard a Konsensus contract also has to be instantiated and registered under a public name. The Konsensus in the client-shard to control the access to the Mint contract.

Mint & Depository setup

The Mint and Depository contracts are created and their names registered in their respective shard.

Stake placement

Removing a stakeholder

TODO. Interaction with Pos needs to be clarified

Adding a stakeholder

TODO. Interaction with Pos needs to be clarified

Child-shard absorption

Absorbing is the process of destroying a child shard while transferring all its value in the parent shard.

The stakeholders withdraw their stakes and store them in their parent-shard wallets.

PROPOSAL:

For the remaining wallets the child shard if they are registered in the WalletRegistry, a purse containing their value minus fees will be sprouted from the Depository and stored in a wallet accessible with the same public key. 

Registered child-shard wallets which contain less value than the fees are lost in the absorption process.

Unregistered child-shard wallets are also lost in the absorption process.

Mount

Through mounting, an existing shard is attached as a child of another existing shard.

TODO. We need to clarify how the mounting affects the clients of the shard

Umount

Through unmounting, a shard is detached from a shard tree and continues to live independently

TODO. We need to clarify how the unmounting affects the clients of the shard and the grandchild shards.

Atomic cross-shard value transfer

Each client only trusts his shard and the parents of his shard.

For this reason the recommended protocol is that the two parties meet in the least-common-ancestor shard.

In the above example the transfers from Alice to Bob should happen in two steps:

  1. Alice moves it's tokens to the root shard, which is the least common ancestor in this situation
  2. Alice transfers to a new wallet created with Bob's public-key in root shard
  3. Bob transfers his amount to "/d" shard

Parent-to-child transfer

TODO: Add diagrams and explain the steps

From @MichaelBirch's comment:

  1. User in parent invokes the `transfer` function of the depository args: (purse, public key, return channel); returns session id over return channel
  2. Block containing 1. finalized
  3. Child validators see 2. and each deploys a message to the k-of-n mint contract
  4. Mint creates a wallet with the tokens at `rho:uuid:<session id>`
  5. Blocks containing 3., 4. are finalized
  6. Child Validators send messages to parent k-of-n contract
  7. Confirmation sent to user over same return channel as 1.
  8. Blocks containing 6., 7. finalized


Note: the session id is also used in the k-of-n contract to relate the validator's message to an active request.

```rholang
//session id creation
contract newId(return) =
{ new id in {
return!(*id.toByteArray)
}
} ```

Child-to-parent transfer

TODO: Add diagrams and explain the steps

From @MichaelBirch's comment:

  1.  User in parent invokes the `transfer` function of the mint args: (purse, public key, return channel); returns session id over return channel
  2. Block containing 1. finalized
  3. Child Validators send messages to parent k-of-n contract
  4. Depository splits out a purse with the right number of tokens and wraps it in a wallet with the right public key, placing the result at `rho:uuid:<session id>`
  5. Blocks containing 3., 4. are finalized
  6. Child validators see 5. and each deploys a message to the k-of-n mint contract
  7. Confirmation sent to user over same return channel as 1.
  8. Blocks containing 6., 7. finalized

Useful links

https://blockstream.com/sidechains.pdf

https://en.bitcoin.it/wiki/Atomic_cross-chain_trading

https://en.bitcoin.it/wiki/Hashed_Timelock_Contracts