虫洞社区·签约做者 steven baigit
此文来自 SmartMesh 团队,转载请联系做者。github
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 相关合约就忽略,自行查看.app
Plasma Cash 是一种子链结构,能够认为 Plasma Cash 是以太坊的一个是基于 =一种简化的UTXO模型的子链.ide
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); }