Plasma Cash合约解读

每条 Plasma 链都会将有关交易顺序的消息换算成一个哈希值存储在根链上。比特币和以太坊都属于根链——这两条区块链具有很高的安全性,并且通过去中心化保证了(安全性和活性)。

Plasma 设计模型有两个主要的分支:Plasma MVP 和 Plasma Cash 。这里我们来研究 SmartPlasma 实现的 Plasma Cash 合约,并通过合约分析来回答大家关于 Plasma Cash 的一系列疑问.

1. 合约代码

SmartPlasma的合约代码肯定会不断升级,我针对他们在今天(2018-09-14)最新版本进行分析,这份代码目前保存在我的 github 上plasma cash.

2. 合约文件简单介绍

文件夹中有不少与 Plasma Cash 无关的合约,这里只关注直接与 Plasma Cash 相关合约,像 ERC20Token 相关合约就忽略,自行查看.

  • Mediator.sol 是 Plasma Cash 链中资产的进出口
  • RootChain.sol 处理 Plasma Cash 子链(相对于以太坊而言)中的交易以及打包等
  • libraries/MerkleProof.sol 是子链中交易用到的默克尔树,用于子链参与方进行欺诈证明 fraud proof.
  • libraris/RLP.sol RLP编码支持,可以暂时忽略,子链中所有的交易都是用RLP 编码的.
  • libraries/PlasmaLib.sol 生成 uid 的辅助函数
  • ECRecovery.sol 签名验证
  • datastructures/Transaction.sol 描述交易的数据结构

3. Plasma Cash 的基础数据结构

Plasma Cash 是一种子链结构,可以认为 Plasma Cash 是以太坊的一个是基于 =一种简化的UTXO模型的子链.

3.1 Plasma Cash 中的资产

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, 有多少个.

3.2 Plasma Cash中的交易

关键代码在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 就是交易的资产 ID

newOwner 交易输出给谁, 这里也不支持像 比特币一样的脚本.

nonce 是这笔资产的第多少次交易,在双花证明中有重要作用.

signer必须由资产原拥有者的签名.

amount 不重要,是因为资产不可分割,导致这里的 Amount 不会随交易发生而发生变化. 而 hash 则是可以直接计算出来.

3.3 Plasma Cash 中的 Block

如果一般区块链中的 Block 一样,他是交易的集合.但是不同于一般链的是,这里面的矿工(不一定是 Operator)不仅需要维护好子链,还需要周期性的将每一个 Block 对应的默克尔树根保存到以太坊中,这个工作只能有 Operator 来完成.

具体代码可见RootChain.sol的.

    function newBlock(bytes32 hash) public onlyOperator {
blockNumber = blockNumber.add(uint256(1));
childChain[blockNumber] = hash; NewBlock(hash);
}

交易证据提交者只能是 Operator, 也就是合约的创建者. 这个 Operator 既可以是普通账户,这时他就是这个子链的管理员.也可以是一份合约,那么就可以通过合约来规定子链的出块规则.

3.4 Plasma Cash 中资产的回归主链以太坊

当资产在 Plasma 中交易一段时间以后,持有者Bob如果想退出Plasma Cash 子链,那么就需要向以太坊合约也就是 RootChain证明,他确实拥有这一笔资产.

3.4.1 资产拥有证明

这个思路和 UTXO 的思路是一样的,Bob能证明这笔资产是从哪里转给我的即可.具体见RootChain.sol中的startExit函数. 其思路非常简单,证明

  • 这笔资产来自哪里(在哪 M块中转移到了 Alice 手中)
  • 经过 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 提走. 我的这个想法会暂时保存在合约中,等待别人来挑战.

3.4.2 等待其他人来挑战我

有了以上信息, 就可以证明在 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);
}

3.4.3 挑战期过了, Bob 拿回资产 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);
}

4. Plasma Cash 中的退出示例

sequenceDiagram
participant o as operator
participant u1 as alice
participant u2 as bob
participant u3 as carol
participant u4 as david
u1->>rootchain: deposit asset to RootChain, get unique id asset1
o->>rootchain: 生成 NewBlock 3, 记录此笔资产
u1->>u2: transfer asset1 to bob
o->>rootchain: 生成 NewBlock 7, 记录此笔交易
u1->>u3: transfer asset1 to carol
o->>rootchain: 生成 Newblock 11 记录此笔交易
u3->>rootchain: 我要提现asset1到主链,提供asset1来自 alice,由 alice签名,发生在 11
u2->>rootchain: 提出挑战,asset1应该属于我,提供 asset1 来自 alice,由 aice签名,发生在 7,rootchain 判定bob证据有效,拒绝carol提现
u2->>u4: transfer asset1 to devid
o->>rootchain:生成 Newblock 27, 记录此笔交易
u4->>rootchain:提现 asset1,来自 bob,有 bob的签名,发生在 27
loop david wait for challenge
u4->>u4:等待挑战期两周时间
end
u4->> rootchain: withdraw,提现,将asset1从rootchain 转走

5. 其他问题

    1. 为什么 Plasma Cash 中的资产是不可分割的?



      进入 rootchain 的资产类似于比特币的 UTXO 模型,但是是不可分割的,这个可以通过交易验证时每次都要求 amount 不变可以得出.
    1. operator 的作用是什么?



      operator 负责将子链中的交易证据(默克尔树)提交证明到以太坊主链中.
    1. operator 是否必须可信任的?



      是的. 虽然 operator 不能将他人资产随意转移,但是却可以阻止他人资产转移.也就是说无法从 Plasma 子链中退回到以太坊中.

      当然这部分是可以改进,降低 operator 作恶带来的风险.
    1. operator 是否可以是一个合约呢?



      是的. 如果 operator 是一个 Pos 共识合约,那么可以降低问题3中的风险

Plasma Cash合约解读的更多相关文章

  1. Plasma Cash 合约解读

    作者介绍 虫洞社区·签约作者 steven bai Plasma Cash 合约解读 Plasma Cash 合约解读 1. 合约代码 2. 合约文件简单介绍 3. Plasma Cash 的基础数据 ...

  2. 深入理解Plasma(四)Plasma Cash

    这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等.本篇文章主要介绍在 Plasma 框架下的项目 Plasma Cash. 在上一篇 ...

  3. 深入理解Plasma(三)Plasma MVP

    这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等.本篇文章主要介绍 Plasma 的一个最小实现 Plasma MVP(Minima ...

  4. 深入理解Plasma(二)Plasma 细节

    这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等.本篇文章主要对 Plasma 一些关键操作的细节进行剖析. 在上一篇文章中我们已经理解了什么是 ...

  5. HyperLedger Fabric部署与链码解读

    1.Fabric简介 Fabric是超级账本中的一个项目,用以推进区块链技术.和其他区块链类似,它也有一个账本,使用智能合约,且是一个参与者可以分别管理自身交易的系统.它是一个联盟链.Fabric与其 ...

  6. ETH&EOS开发资源及工具集合(完整汇总版)

    ETH&EOS开发资源及工具集合(完整汇总版) 3113 ETH开发资源篇 一.开发语言 ·         Solidity - 官方推荐以太坊智能合约开发语言,也是目前最为主流的智能合约语 ...

  7. Ethereum Learning Materials

    Home 注:本页为 EthFans 站内文章精选集.鉴于文章的采集范围较广,我们无法保证文章内容没有重复,也不能保证排列的顺序实现了最优的认识路径.我们只能说,这些文章是我们精挑细选后,确认可以长期 ...

  8. R语言解读多元线性回归模型

    转载:http://blog.fens.me/r-multi-linear-regression/ 前言 本文接上一篇R语言解读一元线性回归模型.在许多生活和工作的实际问题中,影响因变量的因素可能不止 ...

  9. R语言解读一元线性回归模型

    转载自:http://blog.fens.me/r-linear-regression/ 前言 在我们的日常生活中,存在大量的具有相关性的事件,比如大气压和海拔高度,海拔越高大气压强越小:人的身高和体 ...

随机推荐

  1. Python 使用 Postfix 发送邮件

    最近在做一个监控程序,需要用邮件发送告警.以前是使用注册的免费邮来发送,但是这样不免有很多限制,而且有时还会当作恶意登录,帐号异常等,还不让登录邮箱了.利用Postfix提供邮件SMTP服务,可以很自 ...

  2. 集合工具类CollectionUtils、ListUtils、SetUtils、MapUtils的使用

    主要用它的isEmpty(final Collection<?> coll)静态方法来判断一个给定的集合是否为null或者是否长度为0.最近才发现此工具类还可以取集合的交集.并集.甚至差集 ...

  3. 行为型-命令模式(Command)

    装修新房的最后几道工序之一是安装插座和开关,通过开关可以控制一些电器的打开和关闭,例如电灯或者排气扇.在购买开关时,我们并不知道它将来到底用于控制什么电器,也就是说,开关与电灯.排气扇并无直接关系,一 ...

  4. Python time、datetime

    简介: 记录一下 Python 如何获取昨天.今天.明天时间及格式化. 1.今天 In [1]: import time In [2]: print time.strftime('%Y.%m.%d', ...

  5. 关于setConnectTimeout和setReadTimeout的问题

    1.问题描述 这几天测试重构后的下载框架,发现在下载过程中如果网络中断或网络较差,个别应用的下载就会阻塞卡住,一直卡在 “正在下载 xx%”.   2.问题排查和定位 思考:网络差不应该报网络异常的错 ...

  6. spring mvc请求参数中文乱码解决方案

    POST 请求 在web.xml中加上增加过滤器 <filter> <filter-name>encodingFilter</filter-name> <fi ...

  7. jQuery的节点操作

    1.创建节点 var aa = $("<div id='cccc'>插入的内容</div>") var bb = $("<a href='h ...

  8. jquery对象的遍历$(selector).each()

    <!DOCTYPE html> <html> <head> <script language="javascript" src=" ...

  9. IntelliJ IDEA版本控制——过滤提交文件

    File——>Settings——>File Types 在ignore files and folders中添加提交是需要忽略的文件 例如:*.iml;*.idea;*.gitignor ...

  10. 关于InvokeMethod Activity的异步调用

    讨论地址:http://www.cnblogs.com/foundation/archive/2009/12/17/1626617.html 结论是IsCompleted的设置被忽略,看代码里注释 u ...