您好,登錄后才能下訂單哦!
蟲洞社區·簽約作者 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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。