/merklux

Merklux is a framework for general state plasma with economic incentives

Primary LanguageJavaScriptMIT LicenseMIT

Specification

Please see this document

For more detail discussion, visit https://ethresear.ch/t/merklux-plasma-plant.

[WIP] Merklux implementation

Join the chat at https://gitter.im/commitground/merklux

latest released version

npm Build Status Coverage Status

in progress

npm Build Status Coverage Status

JavaScript Style Guide

What is Merklux

Merklux is a framework for general state plasma dApp. This uses merkleized unidirectional data flow for state verification across multiple chains.

For more detail information check https://ethresear.ch/t/merklux-plasma-plant.

Tutorial

  1. Set your smart contract development environment (We use truffle as an example)
npm install -g truffle
npm install -g ganache-cli

mkdir your-plasma-dapp && cd your-plasma-dapp
truffle unbox blueprint
npm i merklux@next
  1. Write a reducer
// contracts/YourReducer.sol
pragma solidity ^0.4.24;

import "merklux/contracts/Merklux.sol";
import "merklux/contracts/MerkluxStore.sol";
import "merklux/contracts/MerkluxReducer.sol";

contract YourReducer is MerkluxReducer {
    function reduce(
        MerkluxStore _tree,
        address _from,
        bytes _data
    ) public view returns (
        bytes keys,
        bytes values,
        bytes32[] references
    ) {
        // Write your reduce logic here
    }
}
  1. Deploy Merklux onto your plasma chain and also register your reducer
// migrations/2_deploy_merklux_app.js

const Merklux = artifacts.require('Merklux')
const YourReducer = artifacts.require('YourReducer')
const STORE_KEY = web3.sha3('namespace', { encoding: 'hex' })
module.exports = function (deployer) {
  deployer.deploy(Merklux).then(async (merklux) => {
    await merklux.newStore(STORE_KEY)
    await merklux.setReducer(
      STORE_KEY, // namespace
      'actionName', // action name
      YourReducer.bytecode // reducer's bytecode
    )
  })
}
  1. Test your application
const Merklux = artifacts.require('Merklux')
const rlp = require('rlp')

const STORE_KEY = web3.sha3('namespace', { encoding: 'hex' })
const rlpEncode = (data) => '0x' + rlp.encode(data).toString('hex')

contract('Merklux', async ([deployer, ...accounts]) => {
  let merklux
  before(async () => {
    merklux = await Merklux.deployed()
  })
  it('should update its value by the reducer information', async () => {
    const VALUE_TO_INCREASE = 8
    await merklux.dispatch(STORE_KEY, 'increaseBalance', rlpEncode(VALUE_TO_INCREASE), { from: deployer })
    let updatedValue = await merklux.get(STORE_KEY, deployer)
    assert.equal(web3.toDecimal(updatedValue), VALUE_TO_INCREASE)
  })
})
  1. Migrate & test
$ truffle develop
> migrate
...
> test
...

Done! Now you wrote a verifiable general state plasma dApp.

Demo(work in progress)

Thus you can start the dApp for demonstration with the following command. (This demo dApp uses ReactJS and Drizzle)

npm run start
  1. Pre requisites
    1. Run a root chain and a child chain.
  2. Make state transitions on the child chain
    1. Deploy merklux smart contract to the child chain.
    2. Insert some items into the child chain.
    3. Get root edge from the Merklux of the child chain and store it as the original root.
    4. Get all nodes which are stored in the MerkluxTree at that time.
    5. Insert more items into the child chain.
    6. Get root edge from the Merklux of the child chain and store it as the target root.
  3. Make a proof case on the root chain.
    1. Deploy a MerkluxCase to the root chain with the original root and the target root as its construtor's parameter.
    2. Commit all nodes
    3. Insert same items into the MerkluxCase of the root chain
    4. Verify its state tranisition

Tests

Test cases include the information about how the functions work, but also includes a demo scenario. Running and reading the test cases will help you understand how it works.

npm run test

Features

  1. State verification

    const primary = '0xACCOUNT'
    it('should reenact the state transitions', async () => {
       // Deploy a MerkluxTree to the child chain
      const treeOnChildChain = await MerkluxTree.new({ from: primary })
    
      // Make state transitions
      await treeOnChildChain.insert('key1', 'val1', { from: primary })
      await treeOnChildChain.insert('key2', 'val2', { from: primary })
    
      // Snapshot the state
      const firstPhaseRootEdge = await treeOnChildChain.getRootEdge()
      const firstPhaseRootHash = firstPhaseRootEdge[2]
    
      // Get data to commit
      // getDataToCOmmit() is defined in the MerkluxCase.test.js
      const dataToCommit = await getDataToCommit(treeOnChildChain, firstPhaseRootHash);
    
      // Make extra state transitions
      await treeOnChildChain.insert('key3', 'val3', { from: primary })
      await treeOnChildChain.insert('key4', 'val4', { from: primary })
    
      // Snapshot again
      const secondPhaseRootEdge = await treeOnChildChain.getRootEdge()
    
      // Create a case to verify the state transition in another chain
      const caseOnRootChain = await MerkluxCase.new(...firstPhaseRootEdge, ...secondPhaseRootEdge, { from: primary })
      
      // Commit nodes and values
      const commitNodes = async (nodes) => {
        for (const node of nodes) {
          await caseOnRootChain.commitNode(...node, { from: primary })
        }
      }
      const commitValues = async (values) => {
        for (const value of values) {
          await caseOnRootChain.commitValue(value, { from: primary })
        }
      }
      await commitNodes(dataToCommit.nodes)
      await commitValues(dataToCommit.values)
    
      // It will be reverted when the committed nodes and values does not match with the firstPhaseRoot
      await caseOnRootChain.seal({ from: primary })
    
      // insert correct items
      await caseOnRootChain.insert('key3', 'val3', { from: primary })
      await caseOnRootChain.insert('key4', 'val4', { from: primary })
    
      // try proof
      await merkluxCase.proof({ from: primary })
     
      // The status of the case is now SUCCESS
      assert.equal(
        (await merkluxCase.status()).toNumber(),
        Status.SUCCESS,
        'it should return its status as SUCCESS'
      )
    })

    Please check MerkluxCase.test.js to get more detail information.

  2. Sharded namespaces

    To be updated

Credits

Merklux uses Christian Reitwießner's patricia-trie for its basic data structure. And he already mentioned that it can be used for verifying evm-based sidechain executions. Thus, this is kind of an implementation case of his idea.

Contributors

License

MIT LICENSE