【CryptoKitties源码解析】养猫的正确姿势!

myKitty

今天想介绍一个最近比较火的一个“区块链”应用CryptoKitties,这个应用本质上实现的功能就是电子猫的繁殖与交易两个功能,功能上虽然比较简单但是再加上区块链这个强大的底层技术作为支撑,让它在整个行业掀起了一波热潮,甚至还导致了以太坊主网的堵塞,使得以太坊中未确认的交易数量从平常的2.5k增涨到了15k,网络中其他的交易也都受到了极大的影响。整个项目的代码总共2000行左右,其中还包含了详细的注释,使得即使没有学过Solidity编程(例如我)的同学也能够很容易的看懂,代码可以通过以太坊浏览器(https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d#code)直接查看,也可以查看另外一个在线的版本(https://ethfiddle.com/09YbyJRfiI),后一个支持在线编译调试。

High Level Overview

作为一个资深(伪)撸猫后期患者,首先还是想先介绍一下这个项目,以便让众多深夜猫瘾发作夜不能寐的患者找到心灵的港湾。项目官网:https://www.cryptokitties.co/,操作其实比较简单,首先你得有以太币,然后安装一个Chrome插件,也就是一个eth轻量钱包,接下来就可以进入Marketplace选择心仪的小可爱了,作为一个交易失败数次的过来人建议大家购买8页以后的喵喵,因为前面几页基本上都已经被人买走了,只不过交易还在等待确认,网站上更新有些延迟,这时候如果再买的话很有可能就是浪费Gas(交易费)。每只喵都是由一个256位的整数DNA来确定的,没有性别,任意两只没有直系血缘关系的喵都是可以繁殖后代的,和父母以及兄弟姐妹则无法繁殖后代。所有的喵都是可以挂到拍卖市场上去卖的,繁殖是需要支付手续费的,其他的交易类型则只需要支付Gas(交易费)就行,不过现在网络拥堵,大量未确认的交易依然存在,所以Gas建议设置到50-60。

按照项目中合约的继承关系,总共有以下几个合约:

contract KittyAccessControl
contract KittyBase is KittyAccessControl
contract KittyOwnership is KittyBase, ERC721
contract KittyBreeding is KittyOwnership
contract KittyAuction is KittyBreeding
contract KittyMinting is KittyAuction
contract KittyCore is KittyMinting

所以KittyCore就是最终应用的合约地址,它继承了前面合约的所有数据和方法,下面我们就一次来看看每一个合约的具体实现。

1 KittyAccessControl:访问控制

这个合约的主要目的是设置了三个地址:CEO/CFO/COO,以及定义了一些function modifiers(http://solidity.readthedocs.io/en/develop/contracts.html#function-modifiers)例如onlyCEO/onlyCFO/onlyCLevel等等,接下来定义了一些函数并加上了function modifier标识使得这些函数都只能由特定的角色来调用,例如冻结和解除冻结整个合约。

modifier onlyCLevel() {
    require(
        msg.sender == cooAddress ||
        msg.sender == ceoAddress ||
        msg.sender == cfoAddress
    );
    _;
}

///...

/// @dev Called by any "C-level" role to pause the contract. Used only when
///  a bug or exploit is detected and we need to limit damage.
function pause() external onlyCLevel whenNotPaused {
    paused = true;
}

根据代码中的解释,这个pause()函数原本的设计目的是只有当程序出现漏洞时才会暂停合约从而减少漏洞带来的损失,暂停合约意味着停止处理所有发往该合约的交易,这也就意味着CLevel的成员具有控制项目运行的绝对权力,而不需要像一般区块链中所有的决定都必须经过矿工的投票分叉来执行,所以我们所谓的许多DApp其实本质上并不像我们想象中那么Decentralized。

2 KittyBase:存储结构

这个合约定义了每只喵所包含的基本的属性、合约运行过程中的数据存储变量(每只喵的主人,挂上拍卖场的喵,等待交配的喵等)以及一些基本操作函数(例如喵的属权转移,新喵的诞生)。

首先定义了喵的基本属性,

struct Kitty {
    uint256 genes; // 基因
    uint64 birthTime; // 出生区块的时间戳
    uint64 cooldownEndBlock; // 再次繁殖的区块号
    uint32 matronId; // 母亲的ID
    uint32 sireId; // 父亲的ID
    uint32 siringWithId; // 如果正在繁殖期那么就是当前交配对象的ID,否则为0
    uint16 cooldownIndex; // 繁殖冷却时间
    uint16 generation; // 第几代
}

喵是没有性别的,属性中父母是看谁生下的新喵。所有的喵都是通过genes来决定外表的,而这个genes又是通过一个非开源的库来产生的,避免通过父母的基因来直接推测出新喵的基因,从而减少fancy cat的比例。另外值得注意的一点是,所有喵的外表都是通过前端的web服务器来解析的,也就是说开发人员可以随意定义哪些喵是fancy cat,并且一旦web服务器崩溃那么所有的喵除了cooldownIndex之外将没有任何区别。这也从侧面反应了区块链应用的一个缺陷——不可能将所有的应用数据都存储再区块链上,因为链上存储数据的代价太高了,所以未来如果要开发真正的完全去中心化的应用,如何将这些数据的存储也去中心化是一个值得考虑的问题。

/*** STORAGE ***/

    /// 保存所有的喵的ID
    Kitty[] kitties;

    /// 所有喵的ID到owner的地址的映射
    mapping (uint256 => address) public kittyIndexToOwner;

    // owner地址到owner的token数的映射
    mapping (address => uint256) ownershipTokenCount;

    /// 待出售的喵ID到owner地址的映射
    mapping (uint256 => address) public kittyIndexToApproved;

    /// 待交配的喵的ID到owner的地址的映射
    mapping (uint256 => address) public sireAllowedToAddress;

    /// 拍卖合约的地址,处理用户之间的交易和每隔15分钟系统生成的gen0代喵
    SaleClockAuction public saleAuction;

    /// 交配的合约地址,和上面拍卖的不同因为两者的处理方式不同
    SiringClockAuction public siringAuction;

这里定义了合约运行中所有的存储数据结构,可以看出总共存储的变量也并不多。接下来又定义了两个函数_transfer()_createKitty()用来转移喵的属权和创建新喵,这两个函数都只能从内部调用,

    /// 一个内部函数用来创建新的喵然后保存起来,这个函数假设所有的输入数据都是有效的,
    /// 函数最后将触发一个Brith和Transfer事件。
    function _createKitty(
        uint256 _matronId, // 母亲的ID,不存在则为0
        uint256 _sireId,  // 父亲的ID,不存在则为0
        uint256 _generation, // 当前第几代,由调用者计算
        uint256 _genes, // 基因
        address _owner // 拥有者
    )
        internal
        returns (uint)
    {
        // 保证数据都在正常范围内
        require(_matronId == uint256(uint32(_matronId)));
        require(_sireId == uint256(uint32(_sireId)));
        require(_generation == uint256(uint16(_generation)));

        // 新喵的cooldown是由generation/2来决定
        // generation = max(motherGeneration, fatherGeneration)+1
        uint16 cooldownIndex = uint16(_generation / 2);
        if (cooldownIndex > 13) {
            cooldownIndex = 13;
        }

        // 创建新喵
        Kitty memory _kitty = Kitty({
            genes: _genes,
            birthTime: uint64(now),
            cooldownEndBlock: 0,
            matronId: uint32(_matronId),
            sireId: uint32(_sireId),
            siringWithId: 0,
            cooldownIndex: cooldownIndex,
            generation: uint16(_generation)
        });
        uint256 newKittenId = kitties.push(_kitty) - 1; // 保存新喵ID

        // 确保总喵的个数小于2^32
        require(newKittenId == uint256(uint32(newKittenId)));

        // 触发Birth事件
        Birth(
            _owner,
            newKittenId,
            uint256(_kitty.matronId),
            uint256(_kitty.sireId),
            _kitty.genes
        );

        // 分配新喵属权
        _transfer(0, _owner, newKittenId);

        return newKittenId;
    }

上述函数展示了创建新喵的详细过程,主要就是计算新喵的generation,然后根据generation分配相应的属性。

3 KittyOwnership:属权

这部分主要实现的是将喵的ID和实际的以太坊地址进行对应起来,实现的函数包括_owns()transfer()等等涉及属权获取或者转换的操作,实现操作相对较为简单。

4 KittyBreeding:繁殖

这个合约包含了两只喵一起繁殖下一代喵的必要的步骤,繁殖依赖一个外部的基因组合合约(geneScience),但是这个合约却不是开源的,并且这个合约的地址是由CEO通过调用setGeneScienceAddress来设定的,也就是说CEO可以随意更改基因组合的方法。

    function setGeneScienceAddress(address _address) external onlyCEO {
        GeneScienceInterface candidateContract = GeneScienceInterface(_address);
        require(candidateContract.isGeneScience());
        geneScience = candidateContract;
    }

两只喵繁殖需要调用breedWithAuto(),首先会检查一系列条件,例如繁殖费用是否足够、双方是否都允许、双方是否没有直系关系等等,这些条件都满足以后调用内部函数_breedWith()来进行繁殖,同时改变双方的生殖状态并触发_triggerCooldown()来改变下次生殖的冷却时间,在_breedWith()函数最后触发Pregnant()事件,而web前端的繁殖界面就是通过监听Pregnant()事件来进行修改的,同时在时间到达时调用giveBirth()来产生新的喵。

function giveBirth(uint256 _matronId)
        external
        whenNotPaused
        returns(uint256)
    {
        // 通过母亲的ID获取对象
        Kitty storage matron = kitties[_matronId];

        // 通过birthTime验证是否是合法的喵
        require(matron.birthTime != 0);

        // 检查母亲是否该生了
        require(_isReadyToGiveBirth(matron));

        // 获取父亲的对象
        uint256 sireId = matron.siringWithId;
        Kitty storage sire = kitties[sireId];

        // 计算父母的generation大的一个
        uint16 parentGen = matron.generation;
        if (sire.generation > matron.generation) {
            parentGen = sire.generation;
        }

        // 传入父母基因,调用外部基因组合函数,得到新喵的基因
        uint256 childGenes = geneScience.mixGenes(matron.genes, sire.genes, matron.cooldownEndBlock - 1);

        // 新喵的owner设置为母亲的owner
        address owner = kittyIndexToOwner[_matronId];
        uint256 kittenId = _createKitty(_matronId, matron.siringWithId, parentGen + 1, childGenes, owner);

        // 清除母亲的交配对象,使得可以再次繁殖
        delete matron.siringWithId;

        // 怀孕的喵数减一
        pregnantKitties--;

        // 将费用发送给父亲
        msg.sender.send(autoBirthFee);

        // 返回新喵的ID
        return kittenId;
    }

5 KittyAuction:拍卖

拍卖合约包括买、卖以及繁殖,繁殖指的是提供一方作为父亲,拿取繁殖费用并由母方获取繁殖的新喵。根据开发者所描述的,他们将拍卖合约分成几个子合约,因为“其中逻辑比较复杂,总是存在bug的风险,将每个过程分成单独的一个合约,我们就可以在不改变主合约的情况下升级子合约。”因此这个合约提供了两个函数setSaleAuctionAddress()setSiringAuctionAddress用来设置子合约的地址,以便更方便的升级子合约。从安全性来讲这的确有利于代码的修复与升级,但是从另外一个角度来讲,合约中的CEO就有了任意更改拍卖规则的权利。

6 KittyMinting:初代喵

这部分合约规定了有合约生成的初代喵的个数,合约中是通过硬编码的形式写入的,

// Limits the number of cats the contract owner can ever create.
    uint256 public constant PROMO_CREATION_LIMIT = 5000;
    uint256 public constant GEN0_CREATION_LIMIT = 45000;

其中5000指的是用来促销的喵的数量,45000指的是合约产生初代喵的限制,产生的过程是分别通过调用createPromoKitty()createGen0Auction(),通过createGen0Auction()产生的喵会被直接挂到拍卖场上,价格是通过过去5只初代喵的平均价格*1.5来作为初始价格。这两个函数都只能由COO来调用,并且创建是可以直接传入基因,也就是说COO可以将任意一直喵复制出来最多5000份,所以你觉得独一无二的喵也许并不像你想象中那么独特。

7 KittyCore:主合约

主合约的作用是控制合约的运行与更新,它继承了以上所有的合约,所以也就包括了以上所有的存储结构和函数。合约通过变量paused来控制停止与运行,通过setNewAddress()函数来设置合约更新的地址,通过unpaused()函数来启动合约。同时还包括两个外部调用函数getKitty()withdrawBalance(),其中getKitty()是用来读取每只喵的所有属性,应该是应用于web server的后台;而withdrawBalance()则是用于提取合约中所有的余额,但是要保留pregnant kitties繁殖的费用,因为这需要从外部调用giveBirth()函数。

总结

如果从实际运行效果来看的话,不得不说这个项目非常的成功,至少到目前为止还占据了网络的一大部交易量,作为一个区块链应用,也的确是能够把握住时代的潮流,但是从上述代码来看还存在几个问题:

  • 所有的喵本质上只是一个256位的整数,解释权都归前端所有;
  • CEO具有控制应用运行的绝对权利,可以无条件暂停合约或者更新子合约;
  • 开源的合约却又调用了非开源的合约(gene science)。

最后一个可能不能称之为问题,但是从我个人来讲开源就应该全部开源,如果能gene science这部分替换为开源的随机基因组合可能会更好,至少不会让其他人觉得这其中有什么猫腻。但是前面两个问题却是的的确确存在的,所以从技术上来讲这并不能完全算是个区块链应用,但却是个不错且成功的尝试!

参考资料

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
以太坊是一个平台,它上面提供各种模块让用户来搭建应用,如果将搭建应用比作造房子,那么以太坊就提供了墙面、屋顶、地板等模块,用户只需像搭积木一样把房子搭起来,因此在以太坊上建立应用的成本和速度都大大改善。具体来说,以太坊通过一套图灵完备的脚本语言(Ethereum Virtual Machinecode,简称EVM语言)来建立应用,它类似于汇编语言。我们知道,直接用汇编语言编程是非常痛苦的,但以太坊里的编程并不需要直接使用EVM语言,而是类似C语言、Python、Lisp等高级语言,再通过编译器转成EVM语言。上面所说的平台之上的应用,其实就是合约,这是以太坊的核心。合约是一个活在以太坊系统里的自动代理人,他有一个自己的以太币地址,当用户向合约的地址里发送一笔交易后,该合约就被激活,然后根据交易中的额外信息,合约会运行自身的代码,最后返回一个结果,这个结果可能是从合约的地址发出另外一笔交易。需要指出的是,以太坊中的交易,不单只是发送以太币而已,它还可以嵌入相当多的额外信息。如果一笔交易是发送给合约的,那么这些信息就非常重要,因为合约将根据这些信息来完成自身的业务逻辑。合约所能提供的业务,几乎是无穷无尽的,它的边界就是你的想象力,因为图灵完备的语言提供了完整的自由度,让用户搭建各种应用。白皮书举了几个例子,如储蓄账户、用户自定义的子货币等。 2013年年末,以太坊创始人Vitalik Buterin发布了以太坊初版白皮书,启动了项目。2014年7月24日起,以太坊进行了为期42天的以太币预售。2016年初,以太坊的技术得到市场认可,价格开始暴涨,吸引了大量开发者以外的人进入以太坊的世界。中国三大比特币交易所之二的火币网及OKCoin币行都于2017年5月31日正式上线以太坊。 [1] 自从进入2016年以来,那些密切关注数字货币产业的人都急切地观察着第二代加密货币平台以太坊的发展动向。作为一种比较新的利用比特币技术的开发项目,以太坊致力于实施全球去中心化且无所有权的的数字技术计算机来执行点对点合约。简单来说就是,以太坊是一个你无法关闭的世界计算机。加密架构与图灵完整性的创新型结合可以促进大量的新产业的出现。反过来,传统行业的创新压力越来越大,甚至面临淘汰的风险。比特币网络事实上是一套分布式的数据库,而以太坊则更进一步,她可以看作是一台分布式的计算机:区块链是计算机的ROM,合约是程序,而以太坊的矿工们则负责计算,担任CPU的角色。这台计算机不是、也不可能是免费使用的,不然任何人都可以往里面存储各种垃圾信息和执行各种鸡毛蒜皮的计算,使用它至少需要支付计算费和存储费,当然还有其它一些费用。最为知名的是2017年初以摩根大通、芝加哥交易所集团、纽约梅隆银行、汤森路透、微软、英特尔、埃森哲等20多家全球top金融机构和科技公司成立的企业以太坊联盟。而以太坊催生的加密货币以太币近期又成了继比特币之后受追捧的资产。  智能合约的潜在应用很多。彭博社商业周刊称它是“所有人共享但无法篡改的软件”。更高级的软件有可能用以太坊创建网络商店。区块链程序以太坊可以用来创建去中心化的程序、自治组织和智能合约,据纽约时报的报导,在2016年5月已经有数十个可用的程序。预期的应用目标涵盖金融、物联网、农田到餐桌(farm-to-table)、智能电网、体育,菠菜等。去中心化自治组织有潜力让许多原本无法运行或成本过高的营运模型成为可能。较知名的应用有:去中心化创业投资:The DAO用以太币资金创立,目标是为商企业和非营利机构创建新的去中心化营业模式、The Rudimental让独立艺术家在区块链上进行群众募资。社会经济平台:Backfeed。去中心化预测市场:Augur。物联网:Ethcore(一间以太坊公司)研发的客户端、Chronicled(一间区块链公司)发表了以太坊区块链的实物资产验证平台;芯片公司、物理IP创建者和生产者可以用植入的蓝牙或近场通信进行验证。Slock.It开发的智能锁可以在付费后自动打开,让用户在付费后可以帮电动车充电、或是打开租屋的房门。虚拟宝物交易平台:FreeMyVunk。版权授权:Ujo Music平台让创作人用智能合约发布音乐,消费者可以直接付费给创作人。伊莫珍·希普用此平台发布了一首单曲。智能电网:TransActive Grid让用户可以和邻居买卖能源。去中心化期权市场:Etheropt。钉住汇率的代币:DigixDAO提供与黄金挂钩的代币,在2016年四月正式营运。Decentralized Capital提供和各种货币挂钩的代币。移动支付:Everex让外劳汇款回家乡。客户端软件以太坊的两个主要的客户端软件是Geth和Parity。企业软件企业软件公司也正测试用以太坊作为各种用途。已知有兴趣的公司包括微软、IBM、摩根大通。德勤和ConsenSys在2016年
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值