蟲洞社區·簽約做者 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); }