虫洞社区·签约作者 steven bai
此文来自 SmartMesh 团队,转载请联系作者。
Plasma 由 V 神在2017年8月提出,希望通过链下交易来大幅提高以太坊的 TPS.
每条 Plasma 链都会将有关交易顺序的消息换算成一个哈希值存储在根链上。比特币和以太坊都属于根链——这两条区块链具有很高的安全性,并且通过去中心化保证了(安全性和活性)。
Plasma 设计模型有两个主要的分支:Plasma MVP 和 Plasma Cash 。这里我们来研究 SmartPlasma 实现的 Plasma Cash 合约,并通过合约分析来回答大家关于 Plasma Cash 的一系列疑问.
SmartPlasma的合约代码肯定会不断升级,我针对他们在今天(2018-09-14)最新版本进行分析,这份代码目前保存在我的 github 上 plasma cash.
文件夹中有不少与 Plasma Cash 无关的合约,这里只关注直接与 Plasma Cash 相关合约,像 ERC20Token 相关合约就忽略,自行查看.
Plasma Cash 是一种子链结构,可以认为 Plasma Cash 是以太坊的一个是基于 =一种简化的UTXO模型的子链.
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, 有多少个.
关键代码在 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
就是交易的资产 IDnewOwner
交易输出给谁, 这里也不支持像 比特币一样的脚本.nonce
是这笔资产的第多少次交易,在双花证明中有重要作用.signer
必须由资产原拥有者的签名.
amount
不重要,是因为资产不可分割,导致这里的 Amount 不会随交易发生而发生变化. 而 hash
则是可以直接计算出来.
如果一般区块链中的 Block 一样,他是交易的集合.但是不同于一般链的是,这里面的矿工(不一定是 Operator)不仅需要维护好子链,还需要周期性的将每一个 Block 对应的默克尔树根保存到以太坊中,这个工作只能有 Operator 来完成.
具体代码可见 RootChain.sol的.
function newBlock(bytes32 hash) public onlyOperator { blockNumber = blockNumber.add(uint256(1)); childChain[blockNumber] = hash; NewBlock(hash); }
交易证据提交者只能是 Operator, 也就是合约的创建者. 这个 Operator 既可以是普通账户,这时他就是这个子链的管理员.也可以是一份合约,那么就可以通过合约来规定子链的出块规则.
当资产在 Plasma 中交易一段时间以后,持有者Bob如果想退出Plasma Cash 子链,那么就需要向以太坊合约也就是 RootChain证明,他确实拥有这一笔资产.
这个思路和 UTXO 的思路是一样的,Bob能证明这笔资产是从哪里转给我的即可.具体见[RootChain.sol]()中的startExit
函数. 其思路非常简单,证明
经过 Alice 签名转移给了Bob(在N块中 Alice 做了签名给我)
具体看代码 startExit
/** @dev Starts the procedure for withdrawal of the deposit from the system. * @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 startExit( bytes previousTx, bytes previousTxProof, uint256 previousTxBlockNum, bytes lastTx, bytes lastTxProof, uint256 lastTxBlockNum ) public { Transaction.Tx memory prevDecodedTx = previousTx.createTx(); Transaction.Tx memory decodedTx = lastTx.createTx(); // 证明在 prevBlock的时候 Alice 拥有资产 uid require(previousTxBlockNum == decodedTx.prevBlock); require(prevDecodedTx.uid == decodedTx.uid); //amount 不变,证明资产不可分割 require(prevDecodedTx.amount == decodedTx.amount); //Alice 确实签名转移给了我,并且交易是相邻的两笔交易 require(prevDecodedTx.newOwner == decodedTx.signer); require(decodedTx.nonce == prevDecodedTx.nonce.add(uint256(1))); //紧挨着的两笔交易 //我是 Bob, 我要来拿走这笔资产 require(msg.sender == decodedTx.newOwner); require(wallet[bytes32(decodedTx.uid)] != 0); 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 ) ); /// Record the exit tx. require(exits[decodedTx.uid].state == 0); require(challengesLength(decodedTx.uid) == 0); exits[decodedTx.uid] = exit({ state: 2, exitTime: now.add(challengePeriod), exitTxBlkNum: lastTxBlockNum, exitTx: lastTx, txBeforeExitTxBlkNum: previousTxBlockNum, txBeforeExitTx: previousTx }); StartExit(prevDecodedTx.uid, previousTxBlockNum, lastTxBlockNum); }
代码的前一半都是在用来证明在lastTxBlockNum
的时候,资产 uid 归Bob所有.
然后后一半就是提出来,Bob想把资产 uid 提走. 我的这个想法会暂时保存在合约中,等待别人来挑战.
有了以上信息, 就可以证明在 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); }
挑战期过后,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); }
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。