您好,登錄后才能下訂單哦!
蟲洞社區(qū)·簽約作者 steven bai
此文來自 SmartMesh 團(tuán)隊(duì),轉(zhuǎn)載請(qǐng)聯(lián)系作者。
Plasma 由 V 神在2017年8月提出,希望通過鏈下交易來大幅提高以太坊的 TPS.
每條 Plasma 鏈都會(huì)將有關(guān)交易順序的消息換算成一個(gè)哈希值存儲(chǔ)在根鏈上。比特幣和以太坊都屬于根鏈——這兩條區(qū)塊鏈具有很高的安全性,并且通過去中心化保證了(安全性和活性)。
Plasma 設(shè)計(jì)模型有兩個(gè)主要的分支:Plasma MVP 和 Plasma Cash 。這里我們來研究 SmartPlasma 實(shí)現(xiàn)的 Plasma Cash 合約,并通過合約分析來回答大家關(guān)于 Plasma Cash 的一系列疑問.
SmartPlasma的合約代碼肯定會(huì)不斷升級(jí),我針對(duì)他們?cè)诮裉?2018-09-14)最新版本進(jìn)行分析,這份代碼目前保存在我的 github 上 plasma cash.
文件夾中有不少與 Plasma Cash 無關(guān)的合約,這里只關(guān)注直接與 Plasma Cash 相關(guān)合約,像 ERC20Token 相關(guān)合約就忽略,自行查看.
Plasma Cash 是一種子鏈結(jié)構(gòu),可以認(rèn)為 Plasma Cash 是以太坊的一個(gè)是基于 =一種簡化的UTXO模型的子鏈.
Plasma Cash 中的資產(chǎn)都來自于以太坊,但是一旦進(jìn)入 Plasma Cash 就會(huì)擁有唯一的 ID,并且不可分割.
可以參考 Mediator.sol的deposit函數(shù). Mediator就是 Plasma Cash 資產(chǎn)存放的地方.
/** @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
});
}
通過合約可以看出進(jìn)入 Plasma Cash 的資產(chǎn)必須是 ERC20 Token,這些資產(chǎn)實(shí)際上是存在 Mediator 這個(gè)合約上,然后由 RootChain 為其分配一個(gè)唯一的 ID, 也就是 uid. 這個(gè) uid 代表著什么 token, 有多少個(gè).
關(guān)鍵代碼在 Transaction.sol中.
struct Tx {
uint prevBlock;
uint uid;
uint amount;
address newOwner;
uint nonce;
address signer;
bytes32 hash;
}
這里可能不太明顯,需要解釋才能看出來這是一個(gè) UTXO 交易的模型. 這里面的amount 和 hash 實(shí)際上都有點(diǎn)啰唆,可以忽略. 那么剩下的成員需要來解釋.
prevBlock
就是 UTXO 中的輸入,來自于哪塊. 至于為什么沒有像比特幣一樣的OutPoint 結(jié)構(gòu),也就是 TxHash+Index, 后續(xù)會(huì)講到.uid
就是交易的資產(chǎn) IDnewOwner
交易輸出給誰, 這里也不支持像 比特幣一樣的腳本.nonce
是這筆資產(chǎn)的第多少次交易,在雙花證明中有重要作用.signer
必須由資產(chǎn)原擁有者的簽名.
amount
不重要,是因?yàn)橘Y產(chǎn)不可分割,導(dǎo)致這里的 Amount 不會(huì)隨交易發(fā)生而發(fā)生變化. 而 hash
則是可以直接計(jì)算出來.
如果一般區(qū)塊鏈中的 Block 一樣,他是交易的集合.但是不同于一般鏈的是,這里面的礦工(不一定是 Operator)不僅需要維護(hù)好子鏈,還需要周期性的將每一個(gè) Block 對(duì)應(yīng)的默克爾樹根保存到以太坊中,這個(gè)工作只能有 Operator 來完成.
具體代碼可見 RootChain.sol的.
function newBlock(bytes32 hash) public onlyOperator {
blockNumber = blockNumber.add(uint256(1));
childChain[blockNumber] = hash;
NewBlock(hash);
}
交易證據(jù)提交者只能是 Operator, 也就是合約的創(chuàng)建者. 這個(gè) Operator 既可以是普通賬戶,這時(shí)他就是這個(gè)子鏈的管理員.也可以是一份合約,那么就可以通過合約來規(guī)定子鏈的出塊規(guī)則.
當(dāng)資產(chǎn)在 Plasma 中交易一段時(shí)間以后,持有者Bob如果想退出Plasma Cash 子鏈,那么就需要向以太坊合約也就是 RootChain證明,他確實(shí)擁有這一筆資產(chǎn).
這個(gè)思路和 UTXO 的思路是一樣的,Bob能證明這筆資產(chǎn)是從哪里轉(zhuǎn)給我的即可.具體見[RootChain.sol]()中的startExit
函數(shù). 其思路非常簡單,證明
經(jīng)過 Alice 簽名轉(zhuǎn)移給了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的時(shí)候 Alice 擁有資產(chǎn) uid
require(previousTxBlockNum == decodedTx.prevBlock);
require(prevDecodedTx.uid == decodedTx.uid);
//amount 不變,證明資產(chǎn)不可分割
require(prevDecodedTx.amount == decodedTx.amount);
//Alice 確實(shí)簽名轉(zhuǎn)移給了我,并且交易是相鄰的兩筆交易
require(prevDecodedTx.newOwner == decodedTx.signer);
require(decodedTx.nonce == prevDecodedTx.nonce.add(uint256(1))); //緊挨著的兩筆交易
//我是 Bob, 我要來拿走這筆資產(chǎn)
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
的時(shí)候,資產(chǎn) uid 歸Bob所有.
然后后一半就是提出來,Bob想把資產(chǎn) uid 提走. 我的這個(gè)想法會(huì)暫時(shí)保存在合約中,等待別人來挑戰(zhàn).
有了以上信息, 就可以證明在 N 塊時(shí),這筆資產(chǎn)歸Bob所用.但是這肯定不夠,無法證明現(xiàn)在資產(chǎn)仍然屬于Bob,也無法證明Alice 沒有在 M 塊以后再給別人.
更加不能證明在 M 塊的時(shí)候 Alice 真的是 uid 的擁有者?
這些問題,看起來很難回答,其實(shí)思路也很簡單.
這個(gè)思路和雷電網(wǎng)絡(luò)中解決問題的辦法是一樣的, 讓這筆資產(chǎn)的利益攸關(guān)者站出來舉證.
比如: 如果 Carol能夠舉證這筆資產(chǎn)Bob 后來又轉(zhuǎn)移給了 Carol, 那么實(shí)際上 Bob 就是在雙花.
具體的挑戰(zhàn)以及迎戰(zhàn)代碼比較復(fù)雜,但是這也是 Plasma Cash 的核心安全性所在.如果沒有這些,所有的參與者都將無法保證自己的權(quán)益.
//challengeExit 挑戰(zhàn)資產(chǎn)uid 其實(shí)不屬于 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 最后一筆交易后面又進(jìn)行了其他交易, Bob 在進(jìn)行雙花
if (exitDecodedTx.newOwner == challengeDecodedTx.signer &&
exitDecodedTx.nonce < challengeDecodedTx.nonce) {
delete exits[uid];
return;
}
// test challenge #3, 雙花了, Alice 給了兩個(gè)人,并且挑戰(zhàn)者 Carol的BlockNumer 更小,也就是發(fā)生的更早.
if (challengeBlockNum < exits[uid].exitTxBlkNum &&
(beforeExitDecodedTx.newOwner == challengeDecodedTx.signer &&
challengeDecodedTx.nonce > beforeExitDecodedTx.nonce)) {
delete exits[uid];
return;
}
// test challenge #4 在 M塊之前,還有一筆交易,Alice 需要證明自己在 M 塊確實(shí)擁有 uid
if (challengeBlockNum < exits[uid].txBeforeExitTxBlkNum ) {
exits[uid].state = 1;
addChallenge(uid, challengeTx, challengeBlockNum);
}
require(exits[uid].state == 1);
ChallengeExit(uid);
}
//Bob應(yīng)戰(zhàn),再次舉證,實(shí)際上這個(gè)過程就是要不斷的追加證據(jù),將所有的交易連起來,最終證明 Alice 在 M塊確實(shí)擁有 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);
}
挑戰(zhàn)期過后,Bob 在Mediator.sol 中提出將資產(chǎn)退回到以太坊中
/** @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); /// 真正的資產(chǎn)轉(zhuǎn)移
delete(cash[uid]);
}
RootChain 再次驗(yàn)證
/** @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); //挑戰(zhàn)期過了
require(exits[decodedTx.uid].state == 2); //并且沒有人挑戰(zhàn)或者我都給出了合適的證據(jù)
require(challengesLength(decodedTx.uid) == 0);
exits[decodedTx.uid].state = 3;
delete(wallet[bytes32(decodedTx.uid)]);
FinishExit(decodedTx.uid);
return bytes32(decodedTx.uid);
}
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。