5-从零开发EOS区块链小游戏系列 - 实现玩家免CPU玩游戏(终)

目录

  • 1-从零开发EOS区块链小游戏系列 - 使用EOS Studio
  • 2-从零开发EOS区块链小游戏系列 - 智能合约设计与实现
  • 3-从零开发EOS区块链小游戏系列 - 游戏公平性及安全性
  • 4-从零开发EOS区块链小游戏系列 - 加入Token体系
  • 5-从零开发EOS区块链小游戏系列 - 实现玩家免CPU玩游戏(终)

 前面几篇文章实现了一个基于EOS的区块链小游戏开发,由于这个小游戏逻辑简单,所以我们将整个游戏的逻辑都放在链上执行。但可能你的游戏并没有那么简单,所以其实普遍的做法还是游戏逻辑放在链下,重要的资产放在链上,这里资产不限于游戏中的金币,应该包含所有属于玩家的物品,比如:装备、皮肤、宠物等等。如果开发了多款游戏,那么通过区块链在你的生态就可以实现资产跨游戏交易,甚至你还可以与其他开发者的游戏资产进行交易,资产就变得更流通更有价值了。
 当你开发完一款EOS小游戏,发布上线后发给你的几个好朋友玩家帮你内测,一天过去了,你发现游戏对战记录没几条,于是你查看服务器日志,发现大量异常信息:

...3080004: Transaction exceeded the current CPU usage limit imposed on the transactionx...

 调查后你发现异常信息是EOS节点返回,该信息就是告诉你:你的玩家账号里面的CPU不足,无法发起交易。
 没错,EOS发起交易是需要消耗资源的,也就是手续费,ETH交易也需要支付手续费,那边叫做GAS。这里资源包括CPU和NET,分别用于计算和打包后网络传输,EOS的资源可以抵押、赎回、定期释放,关于资源我们第一章1-从零开发EOS区块链小游戏系列 - 使用EOS Studio也有简单的介绍过,本章重点不讲解资源,想深入了解可以阅读EOS学习之(RAM,NET,CPU)。发起交易就需要支付手续费,这个是合情合理的,这样整个链才能正常发展,而且节点没有义务免费提供服务。但是EOS对于CPU机制的设计有问题,并且上线至今仍没有很好的解决,导致CPU价格容易被操控,大部分用户无法正常发起交易,不利于EOS的发展。
EOSv1.8.0版本于上一年9月份上线,其中新增了一个可一定程序缓解用户CPU的功能,官方立项名称为:ONLY_BILL_FIRST_AUTHORIZER。如果翻译过来就是:向第一授权者支付资源费用。这是一个偏技术性的描述,我们还是结合业务解释吧,就是说你(即开发者)可以代替你的用户(即玩家)支付资源手续费(CPU/NET),即是说可以完美解决上面出现CPU不足的问题,让你的玩家可以放心的游戏。这功能是不是很好的解决了你的问题?
 我们在实践这个功能之前,先了解一下EOS内部是怎么做的,分别查看EOSv1.3x和v1.8x版本 transaction_context.cpp 代码文件最重要的一段:

    v1.3x
   // Record accounts to be billed for network and CPU usage
      for( const auto& act : trx.actions ) {
         for( const auto& auth : act.authorization ) {
            bill_to_accounts.insert( auth.actor );
         }
      }
    ...

    v1.8x
        // Record accounts to be billed for network and CPU usage
      if( control.is_builtin_activated(builtin_protocol_feature_t::only_bill_first_authorizer) ) {
         bill_to_accounts.insert( trx.first_authorizer() );   # v1.8x新增
      } else {
         for( const auto& act : trx.actions ) {
            for( const auto& auth : act.authorization ) {
               bill_to_accounts.insert( auth.actor );
            }
         }
      }

 留意v1.8x多了一个判断分支,如果是ONLY_BILL_FIRST_AUTHORIZER,只有一个账号进行了支付;trx.first_authorizer() 即通过交易数据获取第一个授权者,放入到支付列表中。
 从上面的代码我们可以发现,EOS的交易是可以多个账号进行授权签名的,这就是为什么EOS不能像ETH那样通过类似msg.sender来到获取当前交易发起人的账号,而是要把交易发起人的账号放在入参。前面我们所讲的交易都是单独一个账号授权的,流程上差异其实并不大,下面我们画了一张流程:

多账号授权流程

 接着我们编写代码实现上图流程功能,这里我还是使用eospy一个python版本的EOS库类型实现,功能和JS版本的eosjs差不多,使用哪种语言看自己喜欢。首先我们有个client.py文件,代表玩家客户端,一个server.py文件代表服务端:

#client.py
import json

import eospy
from eospy.cleos import Cleos
from eospy.keys import EOSKey
from eospy.types import EOSEncoder, Transaction
from eospy.utils import sig_digest
import server as SERVER

# 麒麟测试节点
ce = eospy.cleos.Cleos(url='https://api-kylin.eosasia.one')

# 构造交易数据(图1-1 步骤1)
# args是需要调用的`action`的入参,这里是转账所以入参分别是:from、to、quantity和memo
args = {
    "from": "sweetsummer1",         # 发起交易的账号
    "to": "kingofighter",           # 接收者账号
    "quantity": '100.0000 SJ',      # 接收金额
    "memo": "action:imrich,us:9ba14079514a,ush:0fa6e90cce77e37b2c5b52311e6ca3383accbb4e88388f316d54a401bdc33929,et:1579777105,sig:SIG_K1_KmKPi2k7zzJhtaspdCVzLdoHuKMDeKZSFHypuJsnoUkSbxFjxSqaZJ8VqdQAxnTGj1Q1P6C9eh3RZXefVT3mU4hzkSbNB3"
}

payload = [
    {
        "account": "kofgametoken",  # 调用的合约账号
        "name": "transfer",         # action
        "authorization": [{
            "actor": "kingofighter",# 第一个授权
            "permission": "active",
        }, {
            "actor": "sweetsummer1",# 第二个授权
            "permission": "active",
        }],
    }
]

# 调用区块链,将交易数据转换成16进行字符串(图1-1 步骤2)
# 入参:account、name、args
data = ce.abi_json_to_bin(payload[0]['account'], payload[0]['name'], args)

# 将返回的`data`加入到`payload`
payload[0]['data'] = data['binargs']

# 构造`trx`(图1-1 步骤3)
# 这时的`payload`有:data、account、name和authorization
trx = {"actions": [payload[0]]}

# 对`trx`进行哈希计算(图1-1 步骤4)
chain_info, lib_info = ce.get_chain_lib_info()
trx_chain = Transaction(trx, chain_info, lib_info)
hash_trx = sig_digest(trx_chain.encode(), chain_info['chain_id'])

# 请求服务端 对`trx`哈希进行签名(图1-1 步骤5)
server_sign = SERVER.sign(hash_trx)

# 玩家自己也需要对相同数据进行签名(图1-1 步骤6)
k = EOSKey('sweetsummer1的私钥')  # 玩家私钥
player_sign = k.sign(hash_trx)

# 组合签名,顺序要和`payload.authorization`的顺序一致
signatures = []
signatures.append(server_sign)  # 服务端签名放在第一个位置
signatures.append(player_sign)  # 玩家签名跟在后面

final_trx = {
    'compression': 'none',
    'transaction': trx_chain.__dict__,
    'signatures': signatures
}
data = json.dumps(final_trx, cls=EOSEncoder)

timeout = 30
# 调用区块链`push_transaction`提交交易请求
res = ce.post('chain.push_transaction', params=None, data=data, timeout=timeout)
print(res)

#server.py
from eospy.keys import EOSKey


def sign(digest):
    k = EOSKey('kingofighter的`active`私钥') #用于支付CPU的账号私钥
    return k.sign(digest)

 为了方便,我们打算直接使用小游戏的合约作为支付CPU的账号,你也可以重新创建一个。在执行之前,我们先记录sweetsummer1(玩家账号)和kingofighter(需要支付CPU账账号)的资源数据:

发起交易前CPU数据

发起交易后CPU数据
#交易执行结果
/usr/local/bin/python3.6 /Users/jan/Documents/github/python/obfa/client.py
{'transaction_id': '7f6b123fd8401f29bf2f2993b7bb32f42b70fba28fd5d3491b2efbef00afcb06'...

 由上面两张截图可以看到,虽然依然是玩家发起交易,但是他的CPU和NET没有变化,反而是kingofighter的可用CPU和NET减少了,表示通过我们的实践,证明了ONLY_BILL_FIRST_AUTHORIZER的功能。而且更奇妙的是,我们无需修改任何智能合约的代码就实现了。
 仔细阅读上面server.py相关代码:

def sign(digest):
    k = EOSKey('kingofighter的`active`私钥') #用于支付CPU的账号私钥
    return k.sign(digest)

 其实就一句代码,作用就仅是对客户端传过来的数据进行签名。如果你对密码学有一定基础,一定会发现,这里存在很大的安全问题:没有对入参进行校验,客户端给什么就签什么。更好的做法应该是:

#要求客户端给出原数据,服务端也亲自算哈希,如果两个哈希值相等表示没问题。
def sign(digest,data):
    s_digest = hash(data)
    if digest != s_digest:
        return "数据不一致"
    
    k = EOSKey('kingofighter的`active`私钥')
    return k.sign(digest)

 还有一种实现起来更加优雅的办法,就是在kingofighter合约新创建一个权限,专门用这个权限来进行支付CPU的签名操作。要使用这种方法首先要对EOS的账号模型有一定理解,本篇我们只讲涉及到的知识,想深入了解各位可以直接到 EOS官网。在EOS账号模型中,每一个账号可以有多个权限,默认有owneractive

  • owner 可以对所有者权限进行任何更改的操作。
  • active 授权用于交易,投票以及进行其他高级帐户更改。也就是我们之前调用合约是使用这个权限。
    目前,我们小游戏合约的账号是这样的:
    权限和公钥

     我们上面是直接使用了active这个权限进行了签名,有什么问题呢?这个权限级别太高。我们的初衷只是帮玩家支付玩游戏的CPU,但玩家可以通过此权限签名用来做其他交易。这样我们会觉得太不可控了,需要新建一个权限,每一个权限都需要绑定一对密钥,打开 EOS studio 选择Accounts->Manage Keypairs->Create:
    创建一对密钥

     接着我们使用cleos客户端来执行命令:
cleos -u https://api-kylin.eosasia.one set account permission kingofighter cpupayer EOS7DaK1J1etMVsHewYVbQC3wuD6ec4GAmeDscwfHXAXQjAxzsMro active -p kingofighter@active

kingofighter权限和公钥

 执行后发现在active下多了一个cpupayer权限,注意这个时候cpupayer权限不能做任何事情,我们还需要授予他可以执行的动作:

cleos -u https://api-kylin.eosasia.one set action permission kingofighter kofgametoken transfer cpupayer -p kingofighter@active

 这一句命令的意思是授予kingofighter.cpupayer权限执行合约kofgametoken.transfer ACTINO的权限。在麒麟测试网看不到效果,如果是上了EOS主网那么执行后效果应该是这样的:


 最后,我们将上面代码修改一下:

#client.py 文件
...
payload = [
    {
        "account": "kofgametoken", 
        "name": "transfer",  
        "authorization": [{
            "actor": "kingofighter",
            "permission": "cpupayer",   # 由原来的`active`修改为`cpupayer`
        }, {
            "actor": "sweetsummer1", 
            "permission": "active",
        }],
    }
]
...
#server.py 文件
...
def sign(digest):
    k = EOSKey('cpupayer权限私钥') # 由原来的`active`修改为`cpupayer`私钥
    return k.sign(digest)
...

 OK,这样就已经完成了。服务端不需要校验数据的哈希是否一致,只管无脑的签名就可以了,因为我们已经在合约的权限层面做了限制,限制了“只有玩家玩我们合约的游戏时,我们才会帮他支付CPU和NET”。这样使得我们合约的权限变得可控,无论cpupayer怎么放开签名,都不会响应到别的地方。
 到此,这个系列终于赶在年前肝完了,本章的所说的功能虽然没有根本的解决CPU问题,但是却大大降低了用户的门槛,也是EOS一次非常不错的升级,最后希望各位能开发出爆款DAPP :)。

本章节源代码地址:https://github.com/jan-gogogo/only-bill-first-authorizer

你可能感兴趣的:(5-从零开发EOS区块链小游戏系列 - 实现玩家免CPU玩游戏(终))