Plasma Cash 合约解读

发布时间:2020-07-31 18:35:42 作者:虫洞社区
来源:网络 阅读:471

作者介绍

虫洞社区·签约作者 steven bai

Plasma Cash 合约解读

此文来自 SmartMesh 团队,转载请联系作者。

Plasma 由 V 神在2017年8月提出,希望通过链下交易来大幅提高以太坊的 TPS.

每条 Plasma 链都会将有关交易顺序的消息换算成一个哈希值存储在根链上。比特币和以太坊都属于根链——这两条区块链具有很高的安全性,并且通过去中心化保证了(安全性和活性)。

Plasma 设计模型有两个主要的分支:Plasma MVP 和 Plasma Cash 。这里我们来研究 SmartPlasma 实现的 Plasma Cash 合约,并通过合约分析来回答大家关于 Plasma Cash 的一系列疑问.

1. 合约代码

SmartPlasma的合约代码肯定会不断升级,我针对他们在今天(2018-09-14)最新版本进行分析,这份代码目前保存在我的 github 上 plasma cash.

2. 合约文件简单介绍

文件夹中有不少与 Plasma Cash 无关的合约,这里只关注直接与 Plasma Cash 相关合约,像 ERC20Token 相关合约就忽略,自行查看.

3. Plasma Cash 的基础数据结构

Plasma Cash 是一种子链结构,可以认为 Plasma Cash 是以太坊的一个是基于 =一种简化的UTXO模型的子链.

3.1 Plasma Cash 中的资产

Plasma Cash 中的资产都来自于以太坊,但是一旦进入 Plasma Cash 就会拥有唯一的 ID,并且不可分割.
可以参考 Mediator.sol的deposit函数. Mediator就是 Plasma Cash 资产存放的地方.

        /** @dev Adds deposits on Smart Plasma.
     *  @param currency Currency address.
     *  @param amount Amount amount of currency.
     */
    function deposit(address currency, uint amount) public {
        require(amount > 0);

        Token token = Token(currency);
        token.transferFrom(msg.sender, this, amount); /// deposit test1

        bytes32 uid = rootChain.deposit(msg.sender, currency, amount); /// deposit test2
        cash[uid] = entry({
            currency: currency,
            amount: amount
        });
    }

通过合约可以看出进入 Plasma Cash 的资产必须是 ERC20 Token,这些资产实际上是存在 Mediator 这个合约上,然后由 RootChain 为其分配一个唯一的 ID, 也就是 uid. 这个 uid 代表着什么 token, 有多少个.

3.2 Plasma Cash中的交易

关键代码在 Transaction.sol中.

    struct Tx {
        uint prevBlock;
        uint uid;
        uint amount;
        address newOwner;
        uint nonce;
        address signer;
        bytes32 hash;
    }

这里可能不太明显,需要解释才能看出来这是一个 UTXO 交易的模型. 这里面的amount 和 hash 实际上都有点啰唆,可以忽略. 那么剩下的成员需要来解释.

prevBlock就是 UTXO 中的输入,来自于哪块. 至于为什么没有像比特币一样的OutPoint 结构,也就是 TxHash+Index, 后续会讲到.
uid 就是交易的资产 ID
newOwner 交易输出给谁, 这里也不支持像 比特币一样的脚本.
nonce 是这笔资产的第多少次交易,在双花证明中有重要作用.
signer必须由资产原拥有者的签名.

amount 不重要,是因为资产不可分割,导致这里的 Amount 不会随交易发生而发生变化. 而 hash 则是可以直接计算出来.

3.3 Plasma Cash 中的 Block

如果一般区块链中的 Block 一样,他是交易的集合.但是不同于一般链的是,这里面的矿工(不一定是 Operator)不仅需要维护好子链,还需要周期性的将每一个 Block 对应的默克尔树根保存到以太坊中,这个工作只能有 Operator 来完成.
具体代码可见 RootChain.sol的.

    function newBlock(bytes32 hash) public onlyOperator {
        blockNumber = blockNumber.add(uint256(1));
        childChain[blockNumber] = hash;

        NewBlock(hash);
    }

交易证据提交者只能是 Operator, 也就是合约的创建者. 这个 Operator 既可以是普通账户,这时他就是这个子链的管理员.也可以是一份合约,那么就可以通过合约来规定子链的出块规则.

3.4 Plasma Cash 中资产的回归主链以太坊

当资产在 Plasma 中交易一段时间以后,持有者Bob如果想退出Plasma Cash 子链,那么就需要向以太坊合约也就是 RootChain证明,他确实拥有这一笔资产.

3.4.1 资产拥有证明

这个思路和 UTXO 的思路是一样的,Bob能证明这笔资产是从哪里转给我的即可.具体见[RootChain.sol]()中的startExit函数. 其思路非常简单,证明

3.4.2 等待其他人来挑战我

有了以上信息, 就可以证明在 N 块时,这笔资产归Bob所用.但是这肯定不够,无法证明现在资产仍然属于Bob,也无法证明Alice 没有在 M 块以后再给别人.
更加不能证明在 M 块的时候 Alice 真的是 uid 的拥有者?
这些问题,看起来很难回答,其实思路也很简单.
这个思路和雷电网络中解决问题的办法是一样的, 让这笔资产的利益攸关者站出来举证.
比如: 如果 Carol能够举证这笔资产Bob 后来又转移给了 Carol, 那么实际上 Bob 就是在双花.
具体的挑战以及迎战代码比较复杂,但是这也是 Plasma Cash 的核心安全性所在.如果没有这些,所有的参与者都将无法保证自己的权益.

//challengeExit 挑战资产uid 其实不属于 Bob
  /** @dev Challenges a exit.
     *  @param uid Unique identifier of a deposit.
     *  @param challengeTx Transaction that disputes an exit.
     *  @param proof Proof of inclusion of the transaction in a Smart Plasma block.
     *  @param challengeBlockNum The number of the block in which the transaction is included.
     */
    function challengeExit(
        uint256 uid,
        bytes challengeTx,
        bytes proof,
        uint256 challengeBlockNum
    )
        public
    {
        require(exits[uid].state == 2);

        Transaction.Tx memory exitDecodedTx = (exits[uid].exitTx).createTx();
        Transaction.Tx memory beforeExitDecodedTx = (exits[uid].txBeforeExitTx).createTx();
        Transaction.Tx memory challengeDecodedTx = challengeTx.createTx();

        require(exitDecodedTx.uid == challengeDecodedTx.uid);
        require(exitDecodedTx.amount == challengeDecodedTx.amount);

        bytes32 txHash = challengeDecodedTx.hash;
        bytes32 blockRoot = childChain[challengeBlockNum];

        require(txHash.verifyProof(uid, blockRoot, proof));

        // test challenge #1 & test challenge #2 最后一笔交易后面又进行了其他交易, Bob 在进行双花
        if (exitDecodedTx.newOwner == challengeDecodedTx.signer &&
        exitDecodedTx.nonce < challengeDecodedTx.nonce) {
            delete exits[uid];
            return;
        }

        // test challenge #3, 双花了,  Alice 给了两个人,并且挑战者 Carol的BlockNumer 更小,也就是发生的更早.
        if (challengeBlockNum < exits[uid].exitTxBlkNum &&
            (beforeExitDecodedTx.newOwner == challengeDecodedTx.signer &&
            challengeDecodedTx.nonce > beforeExitDecodedTx.nonce)) {
            delete exits[uid];
            return;
        }

        // test challenge #4   在 M块之前,还有一笔交易,Alice 需要证明自己在 M 块确实拥有 uid
        if (challengeBlockNum < exits[uid].txBeforeExitTxBlkNum ) {
            exits[uid].state = 1;
            addChallenge(uid, challengeTx, challengeBlockNum);
        }

        require(exits[uid].state == 1);

        ChallengeExit(uid);
    }

//Bob应战,再次举证,实际上这个过程就是要不断的追加证据,将所有的交易连起来,最终证明 Alice 在 M块确实拥有 uid
 /** @dev Answers a challenge exit.
     *  @param uid Unique identifier of a deposit.
     *  @param challengeTx Transaction that disputes an exit.
     *  @param respondTx Transaction that answers to a dispute transaction.
     *  @param proof Proof of inclusion of the respond transaction in a Smart Plasma block.
     *  @param blockNum The number of the block in which the respond transaction is included.
     */
    function respondChallengeExit(
        uint256 uid,
        bytes challengeTx,
        bytes respondTx,
        bytes proof,
        uint blockNum
    )
        public
    {
        require(challengeExists(uid, challengeTx));
        require(exits[uid].state == 1);

        Transaction.Tx memory challengeDecodedTx = challengeTx.createTx();
        Transaction.Tx memory respondDecodedTx = respondTx.createTx();

        require(challengeDecodedTx.uid == respondDecodedTx.uid);
        require(challengeDecodedTx.amount == respondDecodedTx.amount);
        require(challengeDecodedTx.newOwner == respondDecodedTx.signer);
        require(challengeDecodedTx.nonce.add(uint256(1)) == respondDecodedTx.nonce);
        require(blockNum < exits[uid].txBeforeExitTxBlkNum);

        bytes32 txHash = respondDecodedTx.hash;
        bytes32 blockRoot = childChain[blockNum];

        require(txHash.verifyProof(uid, blockRoot, proof));

        removeChallenge(uid, challengeTx);

        if (challengesLength(uid) == 0) {
            exits[uid].state = 2;
        }

        RespondChallengeExit(uid);
    }
3.4.3 挑战期过了, Bob 拿回资产 uid

挑战期过后,Bob 在Mediator.sol 中提出将资产退回到以太坊中

 /** @dev withdraws deposit from Smart Plasma.
     *  @param prevTx Penultimate deposit transaction.
     *  @param prevTxProof Proof of inclusion of a penultimate transaction in a Smart Plasma block.
     *  @param prevTxBlkNum The number of the block in which the penultimate transaction is included.
     *  @param txRaw lastTx Last deposit transaction.
     *  @param txProof Proof of inclusion of a last transaction in a Smart Plasma block.
     *  @param txBlkNum The number of the block in which the last transaction is included.
     */
    function withdraw(
        bytes prevTx,
        bytes prevTxProof,
        uint prevTxBlkNum,
        bytes txRaw,
        bytes txProof,
        uint txBlkNum
    )
        public
    {
        bytes32 uid = rootChain.finishExit(
            msg.sender,
            prevTx,
            prevTxProof,
            prevTxBlkNum,
            txRaw,
            txProof,
            txBlkNum
        );

        entry invoice = cash[uid];

        Token token = Token(invoice.currency);
        token.transfer(msg.sender, invoice.amount); /// 真正的资产转移

        delete(cash[uid]); 
    }

RootChain 再次验证

 /** @dev Finishes the procedure for withdrawal of the deposit from the system.
     *       Can only call the owner. Usually the owner is the mediator contract.
     *  @param account Account that initialized the deposit withdrawal.
     *  @param previousTx Penultimate deposit transaction.
     *  @param previousTxProof Proof of inclusion of a penultimate transaction in a Smart Plasma block.
     *  @param previousTxBlockNum The number of the block in which the penultimate transaction is included.
     *  @param lastTx Last deposit transaction.
     *  @param lastTxProof Proof of inclusion of a last transaction in a Smart Plasma block.
     *  @param lastTxBlockNum The number of the block in which the last transaction is included.
     */
    function finishExit(
        address account,
        bytes previousTx,
        bytes previousTxProof,
        uint256 previousTxBlockNum,
        bytes lastTx,
        bytes lastTxProof,
        uint256 lastTxBlockNum
    )
        public
        onlyOwner
        returns (bytes32)
    {
        Transaction.Tx memory prevDecodedTx = previousTx.createTx();
        Transaction.Tx memory decodedTx = lastTx.createTx();

        require(previousTxBlockNum == decodedTx.prevBlock);
        require(prevDecodedTx.uid == decodedTx.uid);
        require(prevDecodedTx.amount == decodedTx.amount);
        require(prevDecodedTx.newOwner == decodedTx.signer);
        require(account == decodedTx.newOwner);

        bytes32 prevTxHash = prevDecodedTx.hash;
        bytes32 prevBlockRoot = childChain[previousTxBlockNum];
        bytes32 txHash = decodedTx.hash;
        bytes32 blockRoot = childChain[lastTxBlockNum];

        require(
            prevTxHash.verifyProof(
                prevDecodedTx.uid,
                prevBlockRoot,
                previousTxProof
            )
        );

        require(
            txHash.verifyProof(
                decodedTx.uid,
                blockRoot,
                lastTxProof
            )
        );

        require(exits[decodedTx.uid].exitTime < now); //挑战期过了
        require(exits[decodedTx.uid].state == 2); //并且没有人挑战或者我都给出了合适的证据
        require(challengesLength(decodedTx.uid) == 0);

        exits[decodedTx.uid].state = 3;

        delete(wallet[bytes32(decodedTx.uid)]);

        FinishExit(decodedTx.uid);

        return bytes32(decodedTx.uid);
    }

4. Plasma Cash 中的退出示例

Plasma Cash 合约解读

5. 其他问题

添加微信(cdong1024),加入区块链开发者技术交流群
虫洞社区:https://www.uzanapp.com/

Plasma Cash 合约解读

推荐阅读:
  1. 详解docker中如何实现psql数据库备份与恢复
  2. Linux tac命令的实现示例

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

区块链 区块链开发 技术开发

上一篇:DC复制错误

下一篇:mysql数据库基本命令---多条数据的同时操作

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》