hello shaokun

code is law


  • 首页

  • 归档

eos的dispatcher的使用(1)

发表于 2019-01-12

填坑

上一篇文章我还在埋怨eos中的智能合约怎么没有以太坊类似的关键字payable呢?看来只有时间才会回答我的这个问题哈.也怪我图样图森破了

前言

  1. 想想距离上次写文章已经过去了快2个月了,而且已经翻了一年了,回想一下去年都做了什么呢?忘了…. 那是什么让我停下了呢?
    • 工作,毕竟要靠这个混口饭吃,心累…
    • 因为个人原因,周末都在往医院跑,
    • 这个eos的知识点是我周末和下班浏览得到的一点知识,主要是现在我在百度也没有找到比较合适的eos智能合约开发教程,所以就把我的这一路走过来的知识点记录下来.我的工作主要也是做智能合约的开发,所以了解一下也是为了未来失业了多一点选择.而目前公司都是基于以太坊和波场的,所以重点我还是会放到公司的项目中来
    • 至于其他的零碎时间都没有打开电脑了,只是补习了一些c++的知识,虽然我有java功底,但是对与没有常写c++的我来说,语法对我来说不是很难,难在如何动笔写,如何组织代码结构和写出c++的风格
  2. 在之前的文章中,我们得智能合约能够跑起来,也能够产生各种交互了.这也得益于官方的文档不断的更新,而我也止步于这个地方了,而对于官方智能合约的最后一篇文章,dispatcher的使用我选择了跳过.
  3. 近段时间和几个加我微信好友的开发者一起探讨上面的坑的时候,又回去阅读了几遍官方的教程,再结合Google,那暂时把得到的一点知识分享给大家,希望能够对你有帮助.

custom dispatchers

什么是EOSIO_DISPATCH

本篇文章我们先跟着上面的链接,把dispatcher的基础信息弄明白,然后再谈其他的哈.而我也会根据自己的理解像大家解释一下,如果有不对的地方,希望各位同学帮我指正过来

EOSIO_DISPATCH( myclass, (upsert)(notify)(erase) )

相信这个大家都知道是c++的宏,帮组eos的合约分发action,那么这个宏的具体定义是什么呢?我们来到源码看看

dispatcher.cpp (这里的eos源码还是EOSIO_ABI,在cdt1.3后改为了EOS_DISPATCHER,只是宏名字改了,内容不变)

/** 
 * Convenient macro to create contract apply handler
 * To be able to use this macro, the contract needs to be derived from eosio::contract
 * 
 * @brief Convenient macro to create contract apply handler 
 * @param TYPE - The class name of the contract
 * @param MEMBERS - The sequence of available actions supported by this contract
 * 
 * Example:
 * @code
 * EOSIO_ABI( eosio::bios, (setpriv)(setalimits)(setglimits)(setprods)(reqauth) )
 * @endcode
 */
#define EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      auto self = receiver; \
      if( action == N(onerror)) { \
         /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
         eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
      } \
      if( code == self || action == N(onerror) ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
} \

看到了这个宏的具体实现,应该说这个函数在比较古老的版本,需要自己实现的,后面eos为了简化开发者的工作,才定义了这个宏.后面很多的宏比如说ACTION, CONTRACT也都是如此.这个宏的具体用法请看上面的注释,结果就是我们现在写合约的使用方式了.这个dispatcher.cpp建议大家可以深入了解一下,对后面的智能合约开发或者出现的bug都会有更加深一步的看法

这里还有一个关键点就是,我们的智能合约如果要能够使用,必须提供一个apply的函数,而上面的宏就是帮助我们实现的这个apply函数.那么疑问来了,我是不是自己实现一个apply函数,不用上面的宏,也可以完成这个工作呢?
答案是可以的

自定义dispatcher 方式1

这里同样跟着官方的教程走

extern "C" {
    void apply(uint64_t receiver, uint64_t code, uint64_t action) {
        auto self = receiver;
        if( code == self ) {
          addressbook _addressbook(name(receiver));
          switch(action) {
            case name("upsert").value: 
              execute_action(name(receiver), name(code), &addressbook::upsert); 
              break;
            case name("notify").value: 
              execute_action(name(receiver), name(code), &addressbook::notify); 
              break;
            case name("erase").value: 
              execute_action(name(receiver), name(code), &addressbook::erase); 
              break;
          }
        }
    }
};

1.

  • 可以看到apply函数接收三个参数,第一个receiver,合约的拥有者,
  • 第二个参数code,调用合约的发起人(这个参数我理解为通过什么方式调用的,如果直接通过调用本合约的,这个参数就是self,而如果通过其他智能合约比如说eosio.token,这个code就是eosio.token,或者其他合约的拥有者)
  • 第三个,执行合约的action,也就是合约的函数名,这个合约被标识为action
  1. if中判断如果合约的调用者是直接调用此合约的,即不是通过其他合约调用本合约的
  2. 实例化addressbook这个类,(这里有点疑惑的是,这个_addressbook在这里代表的是类,而addressbook才是对象,没有明白这是为什么,在c++ 中实例化一个类不是 T t 这种形式吗?),这个实例化比较古老了,只接受了一个参数,在cdt1.3要接受三个参数,请注意
  3. 一个switch结构,根据对应的action的名字分发对应的动作
  4. 使用eosio::execute_action执行对应的action

    • 这里大家又看到了一个新的东西了,不要怕,我们看看是什么

         /**
       * @defgroup dispatcher Dispatcher API
       * @brief Defines functions to dispatch action to proper action handler inside a contract
       * @ingroup contractdev
       */
      
      /**
       * @defgroup dispatchercpp Dispatcher C++ API
       * @brief Defines C++ functions to dispatch action to proper action handler inside a contract
       * @ingroup dispatcher
       * @{
       */
      
      /**
       * Unpack the received action and execute the correponding action handler
       * 
       * @brief Unpack the received action and execute the correponding action handler
       * @tparam T - The contract class that has the correponding action handler, this contract should be derived from eosio::contract
       * @tparam Q - The namespace of the action handler function 
       * @tparam Args - The arguments that the action handler accepts, i.e. members of the action
       * @param obj - The contract object that has the correponding action handler
       * @param func - The action handler
       * @return true  
       */
      template<typename T, typename Q, typename... Args>
      bool execute_action( T* obj, void (Q::*func)(Args...)  ) {
      ...
      }
      
    • 这里我把具体实现删掉了,只留下了注释和方法签名,可以看到是一个模板函数,那我们就看看他的作用是什么了

      Unpack the received action and execute the correponding action handler
      解压缩收到的操作并执行相应的操作处理程序
      
    • 再结合上面的具体的参数的用法,就知道了原来又是一个dispatcher的帮助函数,帮我们去分发对应的action,只是传递的方式变了.
    • 很好,现在我们已经抛弃了系统提供的EOSIO_DISPATCH而自己实现了这个apply函数,至于能不能用呢?那我们接下来试试了

自定义dispatcher 方式1 结果展示

本次我们直接copy的官方的addressbook的源码,然后使用自己实现apply函数
精简一些不必要的参数,实现功能为主
上面我说过,由于版本的原因,目前的版本我们需要在实例化addressbook的时候传入三个参数

addressbook _addressbook(name(receiver),name(code),datastream<const char*>(nullptr,0));

最终改造了的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

using namespace eosio;

class [[eosio::contract]] addressbook : public eosio::contract {

public:
using contract::contract;

addressbook(name receiver, name code, datastream<const char*> ds): contract(receiver, code, ds) {}

[[eosio::action]]
void upsert(name user, std::string first_name, std::string last_name) {
require_auth(user);
address_index addresses(_code, _code.value);
auto iterator = addresses.find(user.value);
if( iterator == addresses.end() )
{
addresses.emplace(user, [&]( auto& row ) {
row.key = user;
row.first_name = first_name;
row.last_name = last_name;
});
send_summary(user, " successfully emplaced record to addressbook");
}
else {
std::string changes;
addresses.modify(iterator, user, [&]( auto& row ) {
row.key = user;
row.first_name = first_name;
row.last_name = last_name;
});
send_summary(user, " successfully modified record to addressbook");
}
}

[[eosio::action]]
void erase(name user) {
require_auth(user);

address_index addresses(_self, _code.value);

auto iterator = addresses.find(user.value);
eosio_assert(iterator != addresses.end(), "Record does not exist");
addresses.erase(iterator);
send_summary(user, " successfully erased record from addressbook");
}

[[eosio::action]]
void notify(name user, std::string msg) {
require_auth(get_self());
require_recipient(user);
}

private:
struct [[eosio::table]] person {
name key;
std::string first_name;
std::string last_name;
uint64_t primary_key() const { return key.value; }
};

void send_summary(name user, std::string message) {
action(
permission_level{get_self(),"active"_n},
get_self(),
"notify"_n,
std::make_tuple(user, name{user}.to_string() + message)
).send();
};

typedef eosio::multi_index<"people"_n, person> address_index;

};

extern "C" {
void apply(uint64_t receiver, uint64_t code, uint64_t action) {
auto self = receiver;
if( code == self ) {
addressbook _addressbook(name(receiver),name(code),datastream<const char*>(nullptr,0));
switch(action) {
case name("upsert").value:
execute_action(name(receiver), name(code), &addressbook::upsert);
break;
case name("notify").value:
execute_action(name(receiver), name(code), &addressbook::notify);
break;
case name("erase").value:
execute_action(name(receiver), name(code), &addressbook::erase);
break;
}
}
}
};

结果展示

  1. 此次合约部署在kylin上的shaokun11114账号上可以看到我们也完美实现了数据增加和删除

shaokun

  1. 去kylin查看结果,可以看到我们得交易记录,其中的notify也可以看到,说明apply函数正常使用

shaokun

源码

总结

  1. 这就完了?当然没有,这篇文章只是简单的和大家一起走走官网的dispatcher,当然还没有走完,如果只是这样写了能够实现功能那和用eos提供的宏没什么区别了呢
  2. 我建议各位同学接触到错误不要慌,也不要急,各位可以先自己看看报错误的信息提示,可以把这个粘贴到Google上找一下答案,这样自己找寻到的答案记忆会深刻许多
  3. 接下来将我进一步跟着官网走,把剩下的一点点讲明白,而这才是dispatcher的真正用法呢.可以解决文章开头提到的payable的问题呢
  4. 谢谢大家的浏览,希望有发现错误的同学可以加上我微信帮我指出来,再次感谢.

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

eos中inline action的使用方式 (续)

发表于 2018-12-08

填坑

这里填一个一直以来对于我来说的一个坑,也在之前的文章的文章误导了大家关于这个event.

  1. 之前我说以太坊区块链会主动通知我们当有数据发生变化,这是经过几天的琢磨发现使用了web3.js库造成的错觉.
  2. 也正是由于这样,我没有在eos中找到类似的方法,所以去看看了web3.js的源码,发现它是才用轮询最新区块的内容而进行的事件的匹配然后产生的回调.
  3. requestmanager.js,如果你感兴趣,可以看看这个文件就大概知道了,更加进一步,你可以发现一些更有趣并且非常熟悉的东西,那就是在methods.js这个文件中

  4. 这是web3提供的方法,既然这样,那我们是不是不调用web3的方法也可以得到同样的效果呢?答案肯定是可以的.通过以上的两个文件,那我们用node也来写一个监听最新块的方法

    const axios = require('axios')
    const obj = { "jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": ["latest", true], "id": 1 }
    
    getBlockInfo()
    
    function getBlockInfo() {
        axios.request('https://mainnet.infura.io/v3/e8947e207ac142519554d382200e663b', {
            method: 'post',
            header: {
                'Content-type': 'application/json'
            },
            data: JSON.stringify(obj)
        }).then(response => response.data).then(res => {
                console.log(res)
            })
    }
    
  5. 上面的方式可以监听到最新一个块的所有交易信息,然后根据条件筛选即可达到我们要的目的了.这里注意两点:一是json-rpc的请求格式,二是json-rpc提供的endpoint,我这里使用的是infrua提供的.其实metamask也是使用的这个端点,所以使用web3的时候会让你提供provider,其实就是提供的这个端点

前言

填完坑后,给出个结论

  • 区块链从来不会主动推送消息给各位同学的
  • 所有的数据都是在区块里面的,只要在区块链发生的所有操作,均可以通过查询区块得到结果.
  • 至于web3或者是其他库提供的监听的功能,实际上是封装了这个轮询的过程

本篇文章还是属于inline action的范畴.就是如何在合约中进行eos的交易

在合约中交易eos

  1. 直接调用eosjs提供的api,这种方式比较简单,也不是我要说的重点(这里使用的是scatter官网提供的这个例子)

    const transactionOptions = { authorization:[`${account.name}@${account.authority}`] };
    
        eos.transfer(account.name, 'helloworld', '1.0000 EOS', 'memo', transactionOptions).then(trx => {
            // That's it!
            console.log(`Transaction ID: ${trx.transaction_id}`);
        }).catch(error => {
            console.error(error);
        });
    
  2. 这里有一种需求,就是在执行某个action的时候,需要预先支付一定费用的eos,当达到某种条件时候,再返回给调用者一定的量的eos.

  3. 根据需求我们写出了如下的代码,(注意asset这个类型,必须这样传入,关于它的更详细的用法得去看看api了哈;二是now()这个函数返回当前的时间戳,真实环境中这都是固定的,不能用来产生随机数).
  4. 还记得之前的权限操作吗? 如果要在合约中转账,必须给合约添加eosio.code的权限,我这个合约现在部署在shaokun11113中的,那么就要给shaokun11113添加eosio.code权限.
  5. 添加了权限之后,我们使用shaokun11112来调用这个合约,可以看到当shaokun11113向shaokun11112转账可以成功,但是shaokun11112向shaokun11113转账是没有权限的.
  6. 那么,我们在shaokun11112中给shaokun11113添加eosio.code的权限
  7. 添加权限后,可以看到一切转账成功了
  8. 那么就实现了在合约中进行transfer eos的操作
#include <eosiolib/eosio.hpp>
#include <eosiolib/asset.hpp>    
using namespace eosio;

class [[eosio::contract]] lucky : eosio::contract {

public:
    using contract::contract;
    lucky(name r,name c, datastream<const char*> ds):contract(r,c,ds){}

[[eosio::action]]
void play(name player, const asset& quantity) {
    require_auth(player);
    if(now() % 2 == 1){
        action(
    permission_level{get_self(),"active"_n},  //所需要的权限结构
    "eosio.token"_n,                          // 调用的合约名称
    "transfer"_n,                              // 合约的方法
    std::make_tuple(get_self(),player, quantity, std::string("shao kun game")) // 传递的参数
        ).send();
    } else {
        action(
        permission_level{player,"active"_n},
        "eosio.token"_n,
        "transfer"_n,
        std::make_tuple(player,get_self(), quantity, std::string("shao kun game"))
        ).send();
    }
};
};
EOSIO_DISPATCH(lucky,(play))

shaokun

以太坊中eth使用合约进行交易

  • 在以太坊中,如果某个方法要接收eth,必须给这个方法加上payable的关键字即可,合约中可以通过msg.value获取到交易的金额.如果此方法不加上payable而调用时传递了金额,那么调用此方法会失败
  • 如果要在初始化的时候传入eth,必须给构造方法加上payable

    contract Test4 {
    
    constructor() public payable {
    
    }
    
    function withPayable() public payable {
        if(now % 2 == 0 ){
            msg.sender.transfer(msg.value * 2);
        }
    }
    
     function withoutPayable() public {
    
     }
    }
    

    shaokun

源码与总结

  • 合约transfer
  • 相对于eth来说,个人觉得eos中在合约中转账过于麻烦,这需要用户手动去给合约设置转账权限
  • 如果哪位同学有更好的关于转账的方法,请告知一下,个人觉得在合约中进行转账还是很常见的需求
  • 官方的eos dice合约现在已经移除了,但是可以在历史中找到eos dice 有兴趣的同学可以研究一下

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

eos中inline action的使用方式 (下)

发表于 2018-12-01

前言

  • 关于上一篇的inline action关于作为log的用法,我觉得这是其一,其二是可以action调用action,这样的话应该也算作action的第二种用法了吧
  • 本篇文章介绍inline action的第三种用户,外部合约的调用
  • 记住一点,这些调用都是在一个事务中进行的,所以可以放心大胆的使用
  • 由于eos的体系,需要你完全明白eos中 account 和permission level这两个东东的作用,不然理解起来会有点困难

据说

  • 听说BM又要离开eos了,我觉得无所谓了,毕竟eos是开源的,很多人维护的
  • eos1.5发布了,是不是又得跟上步伐呢?感觉学不动了
  • js4eos介绍这工具可以简化一些工作,喜欢的同学可以试试js4eos github

eos中inline action 结果展示与说明

  • 这里官方的例子合约(cpp文件)和账户名字是一样的,让我困扰了一会,这里给大家一个结论就好:一个账户就只能部署一个合约,而这个合约的名字就是get_self(),当你部署合约的时候,就是账号的名字了.在写line action的时候不要弄混了
  • 使用shaokun11111部署合约 addressbook
  • 使用shaokun11112部署合约 adcounter
  • 在addressbook中使用inline action调用adcounter中的count方法
  • 这样设置后,多个合约就可以相互调用了
  • 关键点,如果要actor shaokun11111能够拥有调用其他合约的权限,必须给shaokun11111账号授予权限eosio.code,这是个系统的权限.
cleos -u https://api-kylin.eosasia.one:443 set account permission shaokun11111 active '{"threshold":1,"keys":[{"key":"EOS5694dNS19CXwNMgQ3nd8eVG4gnqcihdstnmsChM8AdHNaDCC82","weight":1}],"accounts":[{"permission":{"actor":"shaokun11111","permission":"eosio.code"}}]}' -p shaokun11111@owner
  • 关于permission的格式可以参考我以前的文章,注意这里使用的owner权限
  • EOS5694dNS19CXwNMgQ3nd8eVG4gnqcihdstnmsChM8AdHNaDCC82 这个是原来创建账号的权限的public key,当然按照上面的写法,这样其实可以更改原来账号的active的权限,建议保持原来权限创建的形式就好
  • 授权成功后,账号应该有如图中的显示
  • shaokun
  • shaokun

eth中合约相互调用

如果你理解了eos合约的相互调用,那么接下来eth中的合约相互调用你应该很好理解了
个人看来两者的机制大同小异,因为eth中是以address中作为id的,而eos中是以account做为id的

  • 基于上篇文章的eth合约,我们稍加改造一下
  • 新添加了一个合约 Test3,其中我们将Test2的合约地址用来实例化了Service
  • 这个Service中需要包含Test2合约中你需要调用的方法的签名(这里关于是怎样找到的可以去看看官方文档,其实 function notify(string _notification) external; 就是这个函数的签名,包括方法名字和参数类型,返回值类型hash后 取了四个字节)
  • 部署Test3合约,部署成功后再Test3合约中的callNotify调用Test2合约中的notify方法,可以看到Test合约中的内容生效了
  • 这里有同学可能有疑问了,你这样的合约岂不是没有安全?拿到Test2合约地址,任何人新建一个合约就可以调用其合约的方法?是的,理论上是这样的.但是,凡事都有个但是哈.还记得我在之前的文章中关于eth中的权限如果验证吗?如果你看了那篇文章,那聪明的你一定知道该怎么做了哈
  • shaokun

源码献上

addressbook.cpp,没什么说的,注意写法,变化的部分如果你认真阅读了应该知道变化在哪里

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

using namespace eosio;

class [[eosio::contract]] addressbook : public eosio::contract {
public:
    using contract::contract;
    addressbook(name reciever, name code, datastream<const char*> ds) : contract(reciever,code,ds){}

    [[eosio::action]]
    void upsert(name user, std::string first_name, uint64_t age){
        require_auth(user);
        address_index addresses(_code,_code.value);
        auto itr = addresses.find(user.value);
        if(itr == addresses.end()){
            addresses.emplace(user,[&](auto& row){
                row.key = user;
                row.first_name = first_name;
                row.age = age;
            });
            send_summary(user, "emplace data");
            counter(user,"emplace");
        } else {
            addresses.modify(itr, user, [&](auto& row){
                        row.first_name = first_name;
                        row.age = age;
            });
            send_summary(user, "modify data");
            counter(user,"modify");
        }
    }

    [[eosio::action]]
    void erase(name user){
        require_auth(user);
        address_index addresses(_self, _code.value);
        auto itr = addresses.find(user.value);
        eosio_assert(itr != addresses.end(),"record not exist");
        addresses.erase(itr);
        // 调用inline action
        send_summary(user, " erase data");
        counter(user,"erase");
    }

    [[eosio::action]]
    void notify(name user, std::string){
        // inline action
        require_auth(get_self());
        require_recipient(user);
    }

private:
    struct [[eosio::table]] person {
        name key;
        std::string first_name;
        uint64_t age;

        auto primary_key() const {
            return key.value;
        };
    };
    typedef multi_index<"person"_n, person> address_index;

    // inline action的具体实现
    void send_summary(name user, std::string msg){
        action(
            permission_level{get_self(),"active"_n},
                get_self(),
                "notify"_n,
                std::make_tuple(user, name{user}.to_string() +msg)
            ).send();
    }
    void counter(name user, std::string type){
        action counter = action(
            permission_level{get_self(),"active"_n},// 执行这个inline aciton所需要的权限,即本合约调用
            "shaokun11112"_n,                       // 账号名称
            "count"_n,                              // action 名称
            std::make_tuple(user,type)              // 传递的参数
            );
        counter.send();
    }
};
EOSIO_DISPATCH(addressbook,(upsert)(erase))

adcounter.cpp 注意其中的权限验证部分,其他没什么好说的

#include <eosiolib/eosio.hpp>

using namespace eosio;

class [[eosio::contract]] adcounter: public eosio::contract {
public:
    using contract::contract;
    adcounter(name receiver, name code, datastream<const char*> ds):contract(receiver,code,ds),
                                                                    counts(receiver,code.value){}

    void count(name user, std::string type) {
        // 设定这个方法的调用权限
        // 只有 shaokun11111 这个账号部署的合约有这个权限更改这个合约的内容
        require_auth(name("shaokun11111"));
        auto itr = counts.find(user.value);
        if(itr == counts.end()){
            counts.emplace("shaokun11111"_n, [&](auto& row){
                row.key = user;
                row.emplaced = type == "emplace" ? 1 : 0;
                row.modified = type == "modify" ? 1 : 0;
                row.erased = type == "erase" ? 1 : 0;
            });
        } else {
            counts.modify(itr,"shaokun11111"_n,[&](auto& row){
                if (type == "emplace")
                {
                    row.emplaced += 1;
                }
                if (type == "modify")
                {
                    row.modified += 1;
                }
                if (type == "erase")
                {
                    row.erased += 1;
                }
            });
        }
    }
private:
    struct [[eosio::table]] counter
    {
        name key;
        uint64_t emplaced;
        uint64_t modified;
        uint64_t erased;

        uint64_t primary_key() const {return key.value;}
    };

    typedef eosio::multi_index<"counter"_n,counter> counter_index;
    counter_index counts;
};
EOSIO_DISPATCH(adcounter,(count));

前端代码就不展示了,因为所有的操作都是在addressbook这个合约中进行操作的,adcounter只是展示了合约中的记录
本课源码

总结

  • 建议各位同学一定要仔细敲一遍,虽然例子简单,但是再复杂的内容也是由这些简单的例子所产生的
  • eos inline action的作用已经说完了.这样掌握之后,就可以写很多合约,壮大你的dapp了.inline action很重要的一点 是可以回滚交易,好好利用,发挥意想不到的作用
  • 话说还有个deferred action,有时间再和大家分享一下这货是怎使用了
  • dapp的开发不仅仅是写合约,目前主要应用还是在web上.所以建议各位同学还是得了解一下js,node,webpack,react,css这些基础的概念和用法吧.不要多熟练,大概了解一下怎么用就好了
  • 还有一点,哪位同学知道eos中有类似eth中的event的功能,即合约主动推送信息给我们,我觉得这很重要.我觉得eosjs应该提供类似的功能呢.知道的同学麻烦告知一声

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

eos中inline action的使用方式 (上)

发表于 2018-11-25

前言

eos中有两种action,一种是本文要带大家理解使用的inline action,一种deffer action.
关于他们的差别我觉得主要一点就是前者一定是打包在同一个块中的,而且是事务操作.

surprise

  • jungle的测试网升级到2.0了,既然升级了,应该有不少的优化吧jugnle2.0地址
  • 由于在jungle测试网遇到很多坑,所以试了试另外一个测试网麒麟,用起来很不错,不用带梯子,建议大家在国内使用这个网络
  • 在kylin创建账号和获取测试币,直接在浏览器地址栏敲进去就可以了
http://faucet.cryptokylin.io/create_account?new_account_name
http://faucet.cryptokylin.io/get_token?your_account_name

action的结果展示

  • 结果和之前的todolist内容差不多,只是多了一些额外的输出内容,而且这也是官方的例子
  • 那为什么还要记录呢?第一是我学习的过程,第二是看看具体的结果,第三是部署到测试网试试
  • 这里我也一些疑问?这样是否会多消耗ram? 如果不消耗,可以作为一些log的使用还是不错的,如果消耗,个人感觉没什么用,毕竟输出的每一个文字都是ram呢
  • 当然,它最主要的作用是可以调用其他的合约的内容,而同时保证在一个事务中,这个下篇文章给大家展示
  • 以太坊中的event有点类似本问所展示的作用,它可以监听,而且这部分log不消耗gas
  • 在交互中,也顺便展示了scatter中如何切换账号的操作

inline

cpp源码展示

没什么需要多说的,注意inline action的写法

#include <eosiolib/eosio.hpp>    
using namespace eosio;

class [[eosio::contract]] addressbook : public eosio::contract {
public:
    using contract::contract;
    addressbook(name reciever, name code, datastream<const char*> ds) : contract(reciever,code,ds){}

    [[eosio::action]]
    void upsert(name user, std::string first_name, uint64_t age){
        require_auth(user);
        address_index addresses(_code,_code.value);
        auto itr = addresses.find(user.value);
        if(itr == addresses.end()){
            addresses.emplace(user,[&](auto& row){
                row.key = user;
                row.first_name = first_name;
                row.age = age;
            });
            send_summary(user, "emplace data");
        } else {
            addresses.modify(itr, user, [&](auto& row){
                        row.first_name = first_name;
                        row.age = age;
            });
            send_summary(user, "modify data");
        }
    }

    [[eosio::action]]
    void erase(name user){
        require_auth(user);
        address_index addresses(_self, _code.value);
        auto itr = addresses.find(user.value);
        eosio_assert(itr != addresses.end(),"record not exist");
        addresses.erase(itr);
        send_summary(user, "erase data");
    }

    [[eosio::action]]
    void notify(name user, std::string){
        require_auth(get_self());
        require_recipient(user);
    }

private:
    struct [[eosio::table]] person {
        name key;
        std::string first_name;
        uint64_t age;

        auto primary_key() const {
            return key.value;
        };
    };
    typedef multi_index<"person"_n, person> address_index;

    void send_summary(name user, std::string msg){
        action(
            permission_level{get_self(),"active"_n},
                get_self(),
                "notify"_n,
                std::make_tuple(user, name{user}.to_string() +msg)
            ).send();
    }
};
EOSIO_DISPATCH(addressbook,(upsert)(erase))

eth中的event的使用

  • eth的event可以当做log使用,而且可以单独监听.这给了极大的方便了给任何一个人或则组织想知道某个合约函数的执行情况,我觉得这是一种非常好的机制.不知道eos中有没有类似的功能.
  • 也就是说合约中的信息可以主动推送给我们
  • 可以看到一个简单的node七八行代码就可以监听任何一个合约的所有事件的功能,前提你得拿到这个合约的地址和abi
  • 这里涉及了较多的以太坊的知识,只是为了向大家展示一下这个功能,不知道eos中目前有不有类似的功能,感觉它这个inline action即本篇文章描述的这个功能有点点类似,但是不知道能不能主动推送给我们呢?

inline

scatter遇到的一个小问题

  • 这里遇到了一个很纠结的问题,就是scatter无法弹出来.
  • 如果有遇到的同学,请重新安装scatter那两个lib,再次感谢某位朋友帮忙
yarn remove eosjs scatterjs-core scatterjs-plugin-eosjs
yarn add eosjs scatterjs-core scatterjs-plugin-eosjs

源码献上

这里的源码包括本篇文章演示所有的合约和js文件,涉及到了eos和eth,各位同学可以选择性使用.

inline action的使用

总结

  • 我为什么喜欢用测试网,而且总是要带梯子? 我觉得测试网与主网的环境操作一模一样,只是换了endpoint而已.如果在测试网能够按照设想运行,那么主网只需要改变一下下即可.而梯子对于我们这行的同学来说,应该是必备的.
  • 而现在用麒麟,不用梯子了哈.这点很重要
  • 由于eos的交易速度很快,所以用测试网来测试完全没有任何问题.
  • 这篇文章只讲解的eos的inline action的一个功能,下篇文章将使用它的更加实用的功能,而不仅仅是一个log的功能哈
  • 最近公司的项目很紧,所以能不能快速把新的知识点分享给大家还是未知数了呢,如果你也对dapp开发感兴趣,可以加个微信一起探讨 ^.^
  • 声明一点,我目前只对dapp的开发有点经验,至于源码分析,个人能力达不到呢.
  • eth和eos虽然实现的机制不一样,但都是区块链啊.所以学习eos的时候,当我有些地方不怎么明白,我会再回去用eth的思想来思考一下这个eos的功能怎么用,目前看来,毕竟eos个人觉得比eth上手难度大很多呢

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

简单例子带你彻底的理解eos中的权限在dapp中如何使用

发表于 2018-11-17

前言

相信各位同学看eos白皮书的权限看得很是头疼,而我能搜索到的例子基本上是官方的那个权限图再结合eos.token进行转账的权限介绍.如果不细心跟着走一遍,你是绝对不好理解这个权限怎么用的.由于我头脑比较笨,着实按照大佬的文章走了三四遍才大约摸索清楚,这里给自己做一个备忘录吧
而我站在大佬的肩膀上,结合实际的操作给大家聊聊这个权限如何使用吧.
这里主要参考的

  • EOS 权限管理之-权限的使用 (你绝对找不到的干货)
    • EOS开发系列目录 松果的文章都是根据源码分析的,建议开发dapp的同学都认真读几遍吧,这样更能帮助理解eos到底是怎样运行的

准备工作

  • 提前在测试网上申请两个账号,并领取测试币(不知道怎么做的可以翻看我前面的文章),我这里申请了两个.

  • 这次使用cleos操作,就不使用scatter操作了.原理一样.所以得把申请的这两个账号的私钥导入到cleos创建的钱包里面

    shaokun11121
    Private key: 5KFoeWx69fjPj7mTDbcD9JauYd9LLjikYTa9Qg7N5CVZTqZrzNG
    Public key: EOS69w5V46oUaBD5PSx3AMRxWXPi6b3St2PwbX9kBPY6tZvSs65o1
    
    shaokun11122
    Private key: 5HrJQ9eepF6FuG47eSJprxoFQ6PWRkombbWEwxoSQr6FJ1wQPbg
    Public key: EOS8ZZCicammR45b9tQUSU8VHqX4M8oFM89Cs8tFFWYgUGasegnnV
    

以太坊权限的实现

这里先给贴一段简单的以太坊的权限的管理吧,这个要自己来写,如果你有以太坊的经验,相信更加容易理解.
理一理:

  1. 部署合约,部署完成后,owner就是部署者的地址
  2. 查看count的值,此时count为初始值1
  3. 执行add方法
  4. 查看count的值,此时count的值为2
  5. 切换address
  6. 执行add方法,报错,因为不是owner
  7. 查看count的值 还是为2
  8. 再次执行3,4步骤,此时count的值的值为3

eos

pragma solidity ^0.4.24;

contract Ownable {
  address public owner;

  constructor() public {
    owner = msg.sender;
  }

  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }
}
contract leranper is Ownable {

    uint256 public count = 1;

    function add() external onlyOwner  {
       count += 1;
    }
}

eos的权限的实现

eos的权限不需要写到合约中,底层已经帮我们实现了,我们只需要进行相应的设置即可,以下是设置权限的签名.至于设置的各个参数怎么设置,怎么用,可以看看官方文档吧

Usage: cleos set account permission [OPTIONS] account permission authority [parent]

Positionals:
  account TEXT                The account to set/delete a permission authority for (required)
  permission TEXT             The permission name to set/delete an authority for (required)
  authority TEXT              [delete] NULL, [create/update] public key, JSON string, or filename defining the authority (required)
  parent TEXT                 [create] The permission name of this parents permission (Defaults to: "Active")

方法1:使用account设置

cleos set account permission shaokun11121 add '{"threshold":1,"keys":[],"accounts":[{"permission":{"actor":"shaokun11122","permission":"active"},"weight":1}]}'

方法二:使用public key进行设置

cleos set account permission shaokun11121 add '{"threshold":1,"keys":[{"permission":{"key":"EOS8ZZCicammR45b9tQUSU8VHqX4M8oFM89Cs8tFFWYgUGasegnnV","permission":"active"},"weight":1}]}'  

当然你可以把两种设置方式都用上,根据个人习惯设定就好,

这里建议后面的permission这段,先使用ide写上正确的json格式.然后在json格式压缩进行压缩成一行,这样可以避免不必要的麻烦

    {
    "threshold": 1,
    "keys": [],
    "accounts": [
        {
            "permission": {
                "actor": "kun2",
                "permission": "active"
            },
            "weight": 1
        }
    ]
}

eos的权限的解读

  • 当按照上述方式设置好后,可以通过此命令查看设置的权限,注意看这里permissions里多了一个add权限 这个权限的名字是 add,阀值是1 权限值 是1 ,父权限是active.
  • 这样设置后,也就是说 只要是active shaokun11121的active的权限能够执行的操作,使用shaokun11122的密钥生成的账户的active权限均能做
  • 一个私钥与一个公钥是一一对应,而这一对钥匙可以作为一个或者多个account中的权限级别
  • 当然你也可以按照上述的方式多设置几个权限看看,它的形式就像Windows的目录展示的形式,很好辨认的

    cleos -u http://jungle.cryptolions.io:18888  get account shaokun11121
    

查看的结果

bogon:libraries shaokun$ cleos -u http://jungle.cryptolions.io:18888  get account shaokun11121
created: 2018-11-17T12:46:30.000
permissions: 
     owner     1:    1 EOS69w5V46oUaBD5PSx3AMRxWXPi6b3St2PwbX9kBPY6tZvSs65o1
        active     1:    1 EOS69w5V46oUaBD5PSx3AMRxWXPi6b3St2PwbX9kBPY6tZvSs65o1
           add     1:    1 shaokun11122@active
memory: 
     quota:     260.1 KiB    used:     95.82 KiB  

net bandwidth: 
     staked:        100.0000 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:             5.534 KiB  
     available:        18.23 MiB  
     limit:            18.24 MiB  

cpu bandwidth:
     staked:        100.0000 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:             9.367 ms   
     available:        3.629 sec  
     limit:            3.639 sec  

EOS balances: 
     liquid:           80.1129 EOS
     staked:          200.0000 EOS
     unstaking:         0.0000 EOS
     total:           280.1129 EOS

producers:     <not voted>

eos权限的使用

上述方法给shaokun11121设置了一个add的权限,得添加到具体的action上才能体现出这个价值.目前能够搜到的文章都是加载 transfer这个操作上面的.为什么呢? 因为有了这个权,就可以不用登陆shaokun11121转账了,直接使用shaokun11122转账了.

  • 权限签名

    Positionals:
      account TEXT                The account to set/delete a permission authority for (required)
      code TEXT                   The account that owns the code for the action (required)
      type TEXT                   the type of the action (required)
      requirement TEXT            [delete] NULL, [set/update] The permission name require for executing the given action (required)
    
  • 给shaokun11121 add action和add 权限关联起来,这样只要是shaokun111121的合约中有add action,那么使用shaokun11122的add权限均可以调用.根据我的测试,一个account目前只能部署一个智能合约,所以这个add方法是唯一的

    cleos -u http://jungle.cryptolions.io:18888  set action permission shaokun11121 shaokun11121 add add
    

eos权限合约的编写

合约编写很简单,就是helloworld,注意其中的权限验证那一行,也就是说,只有自己的account 才能添加或者更新自己的这条信息

#include <eosiolib/eosio.hpp>

using namespace eosio;

class [[eosio::contract]] learnper2 : public contract {
  public:
    using contract::contract;
    learnper2(eosio::name reciever,eosio::name code,eosio::datastream<const char*> ds )
                              :contract(reciever,code,ds),
                              _students(reciever,code.value){};

    [[eosio::action]]
    void add(name user, const std::string msg) {
      require_auth(user); // 权限额验证 
      auto itr = _students.find(user.value);
      if (itr == _students.end()){
        _students.emplace(get_self(),[&](auto& row){
          row.user = user;
          row.msg = msg;
        });
      } else {
        _students.modify(itr, get_self(), [&](auto& row){
          row.msg = msg;
        });
      }
    };


  private:
  struct [[eosio::table]] student 
  {
    name user;
    std::string msg = "hello world";
    uint64_t primary_key() const {return user.value;};

  };
  typedef eosio::multi_index<"student"_n, student> student_index;
  student_index _students; 
};
EOSIO_DISPATCH( learnper2, (add))

eos权限合约的编译,部署

  • 编译:这里有个小插曲,放合约cpp文件的文件夹貌似必须命名和主合约的文件名一致,生成的abi里面没有内容,这着实也困扰了我一会

    eosio-cpp -abigen learnper2.cpp learnper2.wasm

  • 这里部署合约一般需要购买ram

    cleos -u http://jungle.cryptolions.io:18888  system buyram shaokun11121 shaokun11121 -k 256
    
  • 部署:

    cleos -u http://jungle.cryptolions.io:18888 set contract shaokun11121 ./learnper2/ -p shaokun11121@active
    

eos权限合约结果展示

eos

这里说一下我的操作流程

  • 调用合约的add action 插入一条信息(我这里之前插入信息,故此步未进行演示)
  • 首先查看了 shaokun11121的 account信息,可以看到添加了一条add的permission
  • 再次查看了 shaokun11122的account信息,正常状态,只要你新建一个account都是这样的
  • 查看了钱包里面的信息,这里显示的是shaokun11122的公钥(大家可以仔细对比一下)
  • 查看链上table的数据
  • 修改数据,我们仔细来分析这条命令

    1. 这里使用的传入的user是shaokun11121
    2. 传入的permission是 shaokun111121的add这个permission,各位同学可以回想一下之前的合约,调用都是是用的active
    3. 钱包中并没有shaokun11121的私钥
    4. 钱包中占有shaokun11122的私钥
  • 再次展示数据,数据修改成功

    cleos -u http://jungle.cryptolions.io:18888  push action shaokun11121 add '{"user":"shaokun11121","msg":"i am shaokun"}' -p shaokun11121@add
    

eos权限使用心得

  • 这里只是展示的单个权限的使用在dapp中怎么使用
  • 如果只看白皮书还是很难理解的,建议通过实际的操作,加深理解吧

eos权限的进一步思考

  • 以太坊和eos的多重权限的签名目前我还没遇到过在dapp中实际使用的例子,官方的例子就是基本上是转账需要多个人同意,那么在dapp中如何使用呢?
  • 本课程只展示了阀值为1,权限值为1的active权限,那么如果按照官方例子,如果阈值为2,同时需要两个权限值都为1的账号的active权限来进行某个action的执行呢?
  • 接上一条,如果需要的不是active权限,比如本课中的add权限呢?

本课源码

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

结合scatter 学习eos dapp开发,看这篇就够了(下)续

发表于 2018-11-15

eos智能合约开发 前言

[学习eos dapp开发,看这篇就够了(下)]
(https://shaokun11.github.io/2018/10/14/%E5%9F%BA%E4%BA%8Eeos%E8%BF%9E%E6%8E%A5scatter%E5%BC%80%E5%8F%91dapp(%E4%B8%8B))

因为有阅读上面这篇文章的同学没看到源码,而我又把之前的源码给删掉了。那我也只好按照我的教程重新写一遍了。本以为copy后就能用,而当时写的时候所使用的旧版的语法,所以改了改,功能不变。
本篇文章的代码是使用的cdt1.3.2编译的

eos智能合约开发 结果展示

功能有添加todo,删除todo,和完成todo,具体看图

scatter

eos智能合约开发 源码展示

这就是最简单的页面了,希望你阅读起来不会有任何问题,项目你直接clone下来可以直接运行的

学习eos dapp开发,看这篇就够了(下)续 源码

App.js

import React, { Component } from 'react';
import './App.css';
import ScatterJS from 'scatterjs-core';
import ScatterEOS from 'scatterjs-plugin-eosjs';
import Eos from 'eosjs';

ScatterJS.plugins( new ScatterEOS());


const network = {
    blockchain:'eos',
    protocol:'http',
    host:'jungle.cryptolions.io',
    port:18888,
    chainId:'038f4b0fc8ff18a4f0842a8f0564611f6e96e8535901dd45e43ac8691a1c4dca'
}

class App extends Component {

   state = {
       deleteId:1,
       rows:[],
       competedId:1,
       scatter:null
  }

    componentDidMount() {
        setTimeout(() => {
            this.init()
        }, 2000)
    }

    takeAction(action,params){
       console.log(action,params)
        const requiredFields = { accounts:[network] };
        this.state.scatter.getIdentity(requiredFields).then(() => {
            const account = this.state.scatter.identity.accounts.find(x => x.blockchain === 'eos');
            console.log(account)
            const eosOptions = { expireInSeconds:60 };
            const eos = this.state.scatter.eos(network,Eos,eosOptions);
            const transactionOptions = { authorization:[`${account.name}@${account.authority}`] };
             eos.contract("shaokun11113").then(ins => {
                return ins[action](account.name, ...params, transactionOptions)
            }).then(res => {
                console.log(res);
            }).catch(error => {
                console.error(error);
            });

        }).catch(error => {
            console.error("tack action",error);
        });
    }

    init() {
        ScatterJS.scatter.connect('todolist').then(connected => {
            if(!connected) return false;

            const scatter = ScatterJS.scatter;
            this.setState({
                scatter
            });
            alert("scatter load success")
        }).then(err=>{
          console.log(err)
        });
    }

    showTodo(){
        this.state.scatter.eos(network,Eos).getTableRows({code: "shaokun11113", scope: "shaokun11113",table: "tood", json: true})
        .then(res=>{
            this.setState({
                rows:res.rows
            })
        })
    }

    render() {
    return (
        <div>

            <div>
                <button onClick={() => {
                    const num = Math.floor(Math.random() * 100000);
                    this.takeAction("create",[num,"this is number "+num])
                }}>create todo</button>
                <button onClick={() => this.takeAction("destroy",[this.state.deleteId])}>destroy todo</button>
                <input type="text" onChange={e => {
                    this.setState({
                        deleteId: Number.parseInt(e.target.value)
                    })
                }}/>

                <button onClick={() => this.takeAction("complete",[this.state.competedId])}>complete todo</button>
                <input type="text" onChange={e => {
                    this.setState({
                        competedId: Number.parseInt(e.target.value)
                    })
                }}/>
                <button onClick={() => this.showTodo()}>show todo</button>
            </div>
            <div>
                <p>below is data</p>
                <ul>
                    {
                        this.state.rows.map((todo, index) => {
                            return <li key={index}>
                                <p>id : {todo.id}</p>
                                <p>description : {todo.description}</p>
                                <p>completed : {todo.completed}</p>
                            </li>
                        })
                    }
                </ul>
            </div>
        </div>
    );
  }
}

export default App;

todolist.cpp

include <eosiolib/eosio.hpp>  

using namespace std;

class [[eosio::contract]] todolist : public eosio::contract {  
  public:

  todolist(eosio::name reciever, eosio::name code, eosio::datastream<const char*> ds)
                                                        :contract(reciever,code,ds),
                                                         todos(reciever,code.value){};   


  [[eosio::action]]   
  void create(eosio::name author, const uint32_t id, const string& description) {
    todos.emplace(author, [&](auto &new_todo) {
        new_todo.id = id;
        new_todo.description = description;
        new_todo.completed = 0;
    });
   }

  [[eosio::action]]
  void complete(eosio::name author,const uint32_t id)
  {  
      eosio::require_auth(author);

      auto itr = todos.find(id);
      // 是否用find()方法,去查找这条方法对应的实例
      eosio_assert(itr != todos.end(), "todo does not exit");
    // 这里 如果找不到,todos.end() 会返回 null
      todos.modify(itr,author,[&](auto &t) {
          t.completed = 1;
      });
  }

  [[eosio::action]]
  void destroy(eosio::name author, const uint32_t id){    
     eosio::require_auth(author);
      auto itr = todos.find(id);
      if(itr != todos.end()){
         todos.erase(itr);
      }
  }

  private:
  struct [[eosio::table]] todo {
      uint64_t id;
      string description;
      uint64_t completed;

      uint64_t primary_key() const {
          return id;
      }
  };

  typedef eosio::multi_index<"tood"_n, todo> todo_index;
  todo_index todos;
};

EOSIO_DISPATCH(todolist, (create)(complete)(destroy))
  • 这里有一点要说明,貌似一个账号只能部署一个智能合约,希望这个结论是正确的
  • 上面的todo这个table,由于我的笔误,生成表名的时候写成了 tood,哈哈
  • 合约比较简单了,交互也比较简单,细心点,仔细点,相信你也可以步入eos dapp开发的大门了

eos智能合约开发 总结

  • 请一定耐心的看完文章,中间漏了一点也会造成失败
  • 最开始的文章截图使用的分辨率较高,图片显示较大,如若对你阅读造成阅读困难,可以适当缩小页面进行阅读

文章允许转载,但请注明出处,谢谢

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

eos dapp开发学习 第五课

发表于 2018-11-11

前言

  • 这次将更新完官方所有的教程,但是不会一一讲解了,因为我也没怎么看懂这个算法,因为这个算法不是我要完成这篇文章的重点,所以感兴趣的同学可以自行探索
  • 上一篇文章虽然完成了数据上链和读取,但是由于我的大意,有些数据得做调整,具体的可以参考本篇的代码
  • 更改了前端的显示结构,更加清晰
  • 说说我遇到的坑,以免你犯同样的错误
  • 坑1==> void playcard(account_name username, uint8_t player_card_idx); 这是官方的方式,当我调用方法正确传参的时候,会出现 player_card_idx这个参数错误,我采取的办法是重命名为 index可解决,至于原因我猜测是大于12个字符了,而且 _ 应该不允许使用
  • 自己挖的坑1 ==> 困扰了我1天,该打。
    按照上篇文章的代码,当执行开始的游戏,我的手里牌仅会产生一张,结果是我在定义dict_card的时候,使用copy的方式,所有的类型全部是empty造成
  • 自己挖的坑2 ==> 困扰了我2天,该打。
    当我写了endgame的方法,怎么执行都不报错,但是数据就是无法更改进able,后面发现没有加入abi_dispatch中。这是个大坑,简直坑死我了
  • 自己挖的坑3 ==> 这个是eos issues中看到的
    当更新合约时,出现这种错误,请使用绝对路径即可
    eos

结果展示

这里仅做简单展示,想具体玩可以使用去官网玩
登录->开始游戏->选择手里牌和ai进行对比,进行血量的计算->再进行下一轮选牌对比,直至谁的life先到达0则为输
可以使用endgame 结束这场游戏

eos

代码演示

这里只贴了我很仔细写了代码,剩下的代码 计算ai选牌,分数计算是copy官方的

void cardgame::login(eosio::name user)
{
    eosio::require_auth(user);
    auto itr = _users.find(user.value);
    if(itr == _users.end()){
        _users.emplace(get_self(),[&](auto&  row){
                row.name = user;
            });
    } 
    // 否则登录成功
}

void cardgame::startgame(eosio::name user) {
    eosio::require_auth(user);
    auto& itr = _users.get(user.value,"User not exist");
    _users.modify(itr, get_self(), [&](auto& row){
        game game_data;
        for(uint8_t i = 0; i < 4; i++){
            draw_card(game_data.deck_player, game_data.hand_player);
            draw_card(game_data.deck_ai, game_data.hand_ai);
        }
        row.game_data = game_data;
    });
}

void cardgame::endgame(eosio::name user) {
    eosio::require_auth(user);
    auto& itr = _users.get(user.value,"User not exist");
    _users.modify(itr, get_self(), [&](auto& row){
        row.game_data = game();
    });
}

总结

  • 根据eos官方的elemental battle的dapp开发教程到这里我就简单的结束了,如果你按照第一篇文章来写,应该能够走得到最后,完成编写。
  • eosio.cdt又更新到1.4了,估计有新的变化,当你按照别人的教程写合约时,请注意版本
  • 上面演示时,可以看到使用eosjs进行与链上的交互很慢,而且有时还会报错,这是因为我的这边的梯子不是很好的原因,所以请不要担心
  • 对于我个人来说,我觉得写合约这块:
    1. 先了解一下其基本的术语,比如account,action,permission
    2. 就是增删改查 multi_index,记住这个数据储存的特殊性
    3. 知道系统提供的一些特殊的变量和方法,比如“name”_n 和现在的get_self()
    4. 合约中一些地方权限的验证,这非常重要。
    5. 养成良好的代码风格。
    6. 当能够编写基本的合约后,应该思考一下代码的结构,
    7. 编写dapp的思想,不能按照传统的应用的模式来编写。而应该根据区块链的特性来编写应用,就例如现在在区块链上运行,储存数据都是需要有费用的。
    8. 目前来说,区块链上的由于其特性,相对公平,不容易作假,因为所有的交易历史均可以查询,但前提是你的智能合约足够足够安全,而合约毕竟是用代码写的,难免有bug,所以只有多写,多看,多总结了
  • 目前eos的教程有很多版本,语法上面都有改变,即使是目前官方的demo,也有很多歌版本。所以你认准你编译合约的版本,不然你会越写越烦。我这个系列经历了eoiocpp eosio.cdt1.2 eosio1.3.2等等,写着也非常不顺,不过好歹也写完了。

第五课源码

接下来的计划

目前我的文章基本上都是比较简单的demo或者概念性的文章,这样看多了也没有意思,我写着也觉得没有意思了。所以接下来准备找一些具有代表性的基于以太坊,波场和eos合约与大家分享了。

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

eos dapp开发学习 第四课 续

发表于 2018-11-09

前言

很幸运,总算把新版本的eos和cdt装上了,踩过了很多坑,说多了都是泪。

  • 首先,目前本地我的环境eos1.4 eosio.cdt1.3.2 所以你按照接下来的教程走,那么请核对你的版本
  • 坑1 ==> 如果在原来的账号上部署过类似的table的合约,再次更新合约如果在table中增加修改了字段,能够部署,但是不能够得到得到table的内容和执行action,建议用新的账号进行部署解决
  • 坑2 ==> helloworld很流畅,一路走下去完美。table数据储存这里不知道知道是我的操作不对还是怎么回事,跟着官网的教程,abi和wasm都能够正常生成,这里合约能够部署上去。但是来了,无法get table,也无法push action。
  • 坑3 ==> 语法变了哈,这里强调一下,支持vector,enume,map 和任何自定义的类型哈,剩下的这个就大家自行琢磨了
  • 坑4 ==> 目前来看,尽量把合code在一个cpp中,不然有想不到的意外等着的哈

合约代码

cardgame.hpp

#pragma once
#include <eosiolib/eosio.hpp>
using namespace std;

class [[eosio::contract]]cardgame : public eosio::contract {
  public:
    cardgame(eosio::name reciever,eosio::name code,eosio::datastream<const char*> ds )
                                :contract(reciever,code,ds),
                                _users(reciever,code.value),
                                _seed(reciever,code.value){};

    [[eosio::action]] void login(eosio::name user);
    [[eosio::action]] void startgame(eosio::name user);
    [[eosio::action]] void playcard(eosio::name user,uint8_t player_card_index);

  private:
    enum game_status:int8_t
    {
      ONGOING = 0,      // 游戏正在进行中
      PLAYER_WON = 1,   // 游戏已经结束,玩家获得胜利
      PLAYER_LOST = -1 // 游戏结束, 完结失败
    };
    enum card_type : uint8_t  // 卡片的类型 总共五种属性类型,总共17张卡牌
    {
      EMPTY = 0,    // 不存在的卡片
      FIRE = 1,     // 火属性, 克木  攻击力为 1 和 2 的各两张  攻击力为3的一张    总共5张
      WOOD = 2,     // 木属性, 克水  攻击力为 1 和 2 的各两张  攻击力为3的一张    总共5张
      WATER = 3,    // 水属性   克火  攻击力为 1 和 2 的各两张  攻击力为3的一张    总共5张
      NEUTRAL = 4,  // 中立属性       攻击力为3 总共1张 
      VOID = 5      // 平局属性       共计力为0 总共1张
    };

    struct card
    {
      uint8_t type;   // 卡片类型
      uint8_t attack_point; // 卡片的攻击力
    };

    const  map<uint8_t,card> card_dict = {
        {0 , {EMPTY , 0}},
        {1 , {EMPTY , 1}},
        {2 , {EMPTY , 1}},
        {3 , {EMPTY , 2}},
        {4 , {EMPTY , 2}},
        {5 , {EMPTY , 3}},
        {6 , {EMPTY , 1}},
        {7 , {EMPTY , 1}},
        {8 , {EMPTY , 2}},
        {9 , {EMPTY , 2}},
        {10 , {EMPTY , 3}},
        {11 , {EMPTY , 1}},
        {12 , {EMPTY , 1}},
        {13 , {EMPTY , 2}},
        {14 , {EMPTY , 2}},
        {15 , {EMPTY , 3}},
        {16 , {EMPTY , 3}},
        {17 , {EMPTY , 0}},
    };
    struct game
    {
     int8_t status = ONGOING;   // 只要登录 默认游戏正在进行
     int8_t life_player = 5;    // 游戏玩家 5条生命
     int8_t ai_player = 5;      // 游戏玩家 5条生命
     vector<uint8_t>  deck_player = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}; // 玩家待选的卡牌id
     vector<uint8_t> deck_ai = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17};     // ai待选的卡牌id
     vector<uint8_t> hand_player = {0,0,0,0}; // 玩家的手里牌
     vector<uint8_t> hand_ai = {0,0,0,0};     // ai的手里牌
     uint8_t selected_card_player = 0;          // 从待选牌中选中的牌
     uint8_t selected_card_ai = 0;              // 从待选牌中选中的牌
    };


      struct [[eosio::table]] userinfo
      {
          eosio::name  name;            
          uint16_t win_count = 0;        
          uint16_t lost_count = 0;
      game game_data;         

          auto primary_key() const {return name.value;}  
      };

    struct [[eosio::table]] seed 
    {
      uint64_t key = 1;
      uint32_t seed_value = 1;

      auto primary_key() const {return key;};
    };

      typedef eosio::multi_index<"userinfo"_n,userinfo> user_index;
    typedef eosio::multi_index<"seed"_n,seed> seed_index;
    user_index _users; //声明表的实例
    seed_index _seed;

  int random(const int range);
  void draw_card(vector<uint8_t>& deck, vector<uint8_t>& hand);
};

cardgame.cpp

#include "cardgame.hpp"

void cardgame::login(eosio::name user)
{
    _users.emplace(get_self(),[&](auto&  u){
            u.name = user;
    });
}

void cardgame::startgame(eosio::name user) {
    auto& itr = _users.get(user.value,"User not exist");
    _users.modify(itr,get_self(),[&](auto& _to_modify_user){
        game game_data;
        for(uint8_t i = 0; i < 4; i++){
            draw_card(game_data.deck_player,game_data.hand_player);
            draw_card(game_data.deck_ai,game_data.hand_ai);
        }
        _to_modify_user.game_data = game_data;
    });
}

void cardgame::playcard(eosio::name user, uint8_t player_card_index){
    eosio::require_auth(user);
    eosio_assert(player_card_index < 4,"invalid hand index");// 手上牌最多四张
    // 通过user找到数据表中数据
    auto& player = _users.get(user.value,"User not exist");
    eosio_assert(player.game_data.status == ONGOING,"game have ended");
    eosio_assert(player.game_data.selected_card_player == 0,"the player have selected car in this turn");
    // 修改数据表
    _users.modify(player,get_self(),[&](auto& _to_modify_user){
        game& game_data = _to_modify_user.game_data;
        // 设定选中的卡片id
        game_data.selected_card_player = game_data.hand_player[player_card_index];
        // 将手中卡片对应位置置位empty
        game_data.hand_player[player_card_index] = 0;
    });
};
int cardgame::random(const int range) {
    auto seed_itr = _seed.begin();
    if (seed_itr  == _seed.end()){    // 如果没有随机数据,就用默认值初始化
        seed_itr = _seed.emplace(get_self(),[&](auto& s){
            s = seed{};
        });
    };
    int prime = 65535;
    int new_seed_value = (seed_itr->seed_value + now()) % prime;
    _seed.modify(seed_itr,get_self(),[&](auto& s){
            s.seed_value = new_seed_value;
    });
    // 随机范围  0 ~ range
    int random_res = new_seed_value % range;
    return random_res;
};
void cardgame::draw_card(vector<uint8_t>& deck, vector<uint8_t>& hand) {
      int deck_card_idx = random(deck.size());
      int first_empty_slot = -1;
      for (int i = 0; i <= hand.size(); i++) {
          auto id = hand[i];
          if (card_dict.at(id).type == EMPTY) {
              first_empty_slot = i;
              break;
          }
        }
      eosio_assert(first_empty_slot != -1, "No empty slot in the player's hand");
      hand[first_empty_slot] = deck[deck_card_idx];
      deck.erase(deck.begin() + deck_card_idx);
 };
EOSIO_DISPATCH(cardgame,(login)(startgame)(playcard))
  • 由于上面的坑,用shaokun11113是无法正确的,所以我用了shaokun11114进行部署
  • 终于能够写数据到链上了eos
  • 更新的合约源码,这次就没有更新前端的mock数据了,相信聪明的你一定知道怎样调整过来

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步

eos dapp开发学习 第四课

发表于 2018-11-02

前言

官方又发布新版本了,目前写的合约也部署不上了,新版本的语法变了确实蛮多的,目前仅按照官网的教程把流程过一遍了
很幸运,目前的官方的游戏第一次和第二次已经被我玩了,这里也把第二次的游戏流程分享给大家,对于接下来编写代码心里有点底,因为本课主要是智能合约的编写。
Elemental Battles是一款单人纸牌游戏,其中包括元素之间的战斗。对手将通过在EOSIO上运行的智能合约来代表。
eos

学习目标

在本课中,我们将准备我们的游戏。我们还将在智能合约中构建startgame和playcard动作,并从前端触发。在本课程结束时,您将能够开始玩元素战斗并迈出第一步!

合约编写

这里我暂时无法部署上去,只能先把代码放在这里了。看了上面的游戏玩法,各位同学结合注释应该看得明白了。
玩家和ai进行卡片游戏对战,都从各自的待选17张牌中选出四张,每张牌有不同的攻击力。根据攻击力进行判断一次对决的胜负,各有5条命,输一次减少一条命,谁先到达0谁就输了。游戏简单,主要是了解一下智能合约的开发流程
这里并没有成功布置到链上,以后补上了
第四课源码

cardgame.hpp

#include <eosiolib/eosio.hpp>
using namespace std;
class cardgame : public eosio::contract {
// 继承contract
  public:
    //构造方法,并实例化了multi_index表users
    cardgame( account_name self):contract(self),
                                _users(self,self),
                                _seed(self,self){};

    // table和action 必须加上@abi 的注解,这样才能生存对应的abi,
    // 在eosio.cdt中,使用 [[eosio::action]] 更加简洁 
    [[eosio::action]]
    void login(account_name user);

    // 开始游戏,分发手里牌
    [[eosio::action]]
    void startgame(account_name user);

    //从手上牌选择卡片进行对战
    [[eosio::action]]
    void playcard(account_name user,uint8_t player_card_index);

  private:
    enum game_status:int8_t
    {
      ONGOING = 0,      // 游戏正在进行中
      PLAYER_WON = 1,   // 游戏已经结束,玩家获得胜利
      PLAYER_LOST = -1 // 游戏结束, 完结失败
    };
    enum card_type : uint8_t  // 卡片的类型 总共五种属性类型,总共17张卡牌
    {
      EMPTY = 0,    // 不存在的卡片
      FIRE = 1,     // 火属性, 克木  攻击力为 1 和 2 的各两张  攻击力为3的一张    总共5张
      WOOD = 2,     // 木属性, 克水  攻击力为 1 和 2 的各两张  攻击力为3的一张    总共5张
      WATER = 3,    // 水属性   克火  攻击力为 1 和 2 的各两张  攻击力为3的一张    总共5张
      NEUTRAL = 4,  // 中立属性       攻击力为3 总共1张 
      VOID = 5      // 平局属性       共计力为0 总共1张
    };
    struct card
    {
      uint8_t type;   // 卡片类型
      uint8_t attack_point; // 卡片的攻击力
    };
    typedef uint8_t card_id;
    const map<card_id,card> card_dict = {
        {0 , {EMPTY , 0}},
        {1 , {EMPTY , 1}},
        {2 , {EMPTY , 1}},
        {3 , {EMPTY , 2}},
        {4 , {EMPTY , 2}},
        {5 , {EMPTY , 3}},
        {6 , {EMPTY , 1}},
        {7 , {EMPTY , 1}},
        {8 , {EMPTY , 2}},
        {9 , {EMPTY , 2}},
        {10 , {EMPTY , 3}},
        {11 , {EMPTY , 1}},
        {12 , {EMPTY , 1}},
        {13 , {EMPTY , 2}},
        {14 , {EMPTY , 2}},
        {15 , {EMPTY , 3}},
        {16 , {EMPTY , 3}},
        {17 , {EMPTY , 0}},
    };
    struct game
    {
     int8_t status = ONGOING;   // 只要登录 默认游戏正在进行
     int8_t life_player = 5;    // 游戏玩家 5条生命
     int8_t ai_player = 5;      // 游戏玩家 5条生命
     vector<card_id>  deck_player = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}; // 玩家待选的卡牌id
     vector<card_id> deck_ai = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17};     // ai待选的卡牌id
     vector<card_id> hand_player = {0,0,0,0}; // 玩家的手里牌
     vector<card_id> hand_ai = {0,0,0,0};     // ai的手里牌
     card_id selected_card_player = 0;          // 从待选牌中选中的牌
     card_id selected_card_ai = 0;              // 从待选牌中选中的牌
    };

      [[eosio::table]]
      struct userinfo
      {
          account_name name;            //玩家的名字    account_name 是uint64_t的一个别名
          uint16_t win_count = 0;        
          uint16_t lost_count = 0;
      game game_data;         // game 

          auto primary_key() const {return name;}  
          //多索引表查找名为primary_key()的getter函数。这必须使用结构中的第一个字段,编译器将使用它来添加主键。让我们定义这个功能。
      };
    [[eosio::table]]
    struct seed // 这个是用来生成随机数的,全局储存,只要有人玩就会改变,没办法,eos里只能用table来持久化数据在链上
    {
      uint64_t key = 1;
      uint32_t value = 1;

      auto primary_key(){return key;};
    }

      typedef eosio::multi_index<N(userinfo),userinfo> user_index;
      //多索引表定义,它有两个参数: 表名 
      //                         结构定义了我们打算在多索引表中存储的数据。 
      // 新类型名称作为我们的多索引表定义的别名
      // 
      // 多索引表中的数据由四条信息标识: code(account_name) 
      //                              scop 
      //                              table name
      //                              primary key 
    typedef multi_index<N(seed),seed> seed_index;
    user_index _users; //声明表的实例
    seed_index _seed;

    int random(const int range);
    void draw_one_card(vector<uint8_t>& deck,vector<uint8_t>& hand);
};    

cardgame.cpp

#include "cardgame.hpp"
void cardgame::login(account_name user)
{
    require_auth(user); 
    //确保这个用户已经被授权
    // 这里我理解了很久,把我的想法记录在这里
    // 这里是调用此合约的account name,即提供私钥的account,这里传入的这个account必须拥有它的active权限,
    // 当然,这里还可以要求有其他的权限,这样就可以给特殊的账号特殊的权限
    auto user_iterator = _users.find(user);  // 查找
    if (user_iterator == _users.end()) {
        user_iterator = _users.emplace(user, [&](auto& new_user){
            new_user.name = user;
        });
    }
    // 这里使用multi_index的find 和 emplace(插入)两种方法,
    // 这些方法的形式都是一样的,记住就好,可以去官网查看详细的方法说明
}

void cardgame::startgame(account_name name) {
    require_auth(name);
    auto& info = _users.get(name, "user not exist");
    _users.modify(info,name,[&](auto& _to_modify_user){
        game game_data;
        for(uint8_t i = 0; i < 4; i++){
            draw_one_card(game_data.deck_player,game_data.hand_player);
            draw_one_card(game_data.deck_ai,game_data.hand_ai);
        }
        _to_modify_user.game_data = game_data;
    });
}

void cardgame::playcard(account_name user,uint8_t player_card_index){
    require_auth(user);
    eosio.assert(player_card_index < 4,"invalid hand index"); // 手上牌最多四张
    // 通过user找到数据表中数据
    auto& player = _users.get(user,"User not exist");
    eosio_assert(player.game_data.status == ONGONING,"game have ended"); // 确保本轮游戏没有结束
    eosio_assert(player.game_data.selected_card_player == 0,"the player have selected car in this turn"); // 确保手上没有选牌
    // 修改数据表
    _users.modify(player,user,[&](auto& _to_modify_user){
        game& game_data = _to_modify_user.game_data;
        // 设定选中的卡片id
        game_data.selected_card_player = game_data.hand_player[player_card_index];
        // 将手中卡片对应位置置位empty
        game_data.hand_player[player_card_index] = 0;
    });
}
EOSIO_ABI(cardgame,(login)(playcard))

gameplay.cpp

#include "cardgame.hpp"
int cardgame::random(const int range) {
    auto seed_itr = _seed.begin();
    if (seed_itr  == _seed.end())
    {    // 如果没有随机数据,就用默认值初始化
        seed_itr = _seed.emplace(_self,[&](auto& seed){})
    };
    int prime = 65535;
    // 新值得范围 0 ~ 65535
    auto new_seed_value = (seed_itr.value + now()) % prime;
    _seed.modify(seed_itr,_self,[&](auto& s){
            s.value = new_seed_value;
    });
    // 随机范围  0 ~ range
    int random_res = new_seed_value % range;
    return random_res;
}
// 根据待选卡片和手上已有的卡片,随机进行选择新的卡片
void cardgame::draw_one_card(vector<uint8_t> &deck,vector<uint8_t> &hand){
    // 0 ~ 16
     int deck_card_index = random(deck.size());
     int first_emprty_slot = -1;
     for (int i = 0; i < hand.size; ++i)
     {    
         auto id = hand[i];
         if (card_dict.at(id).type == EMPTY){
             first_emprty_slot = i;
             break;
         }
     }
     eosio_assert(first_emprty_slot !=- 1, "No empty slot in the players hand");
     // 如果手上的牌 有空位,进行随机赋值
     hand[first_emprty_slot] = deck[deck_card_index];
     // 待选牌中移除掉已经被赋值给手上的牌
     deck.erase(deck.began() + deck_card_index);
 }

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

eos dapp开发学习 第三课

发表于 2018-10-27

前言

上一篇文章登录的时候,你如果做了,虽然也未报错,按照结论貌似登录上了,但是,其实是没有登录上的,这里给大家说一声对不起,因为其中的某些参数未完全按照官方的例子敲,所以造成了参数不对。
既然上篇文章登录错了,那么这篇文章还是得纠正过来,并且找到错在哪里

教程内容

在本课中,我们将学习如何从区块链中获取数据。数据通过来自前端的交易进入区块链,我们在第2.2课和第2.3课中查看了这一点。调用操作可以更改状态,该状态存储在多索引表中。此外,此操作记录在区块链中。

教程结果

首先声明,所有的结果均未完全百分之百按照官方教程走,省略了其中的ui,而只涉及了其中正常开发所必须掌握的内容,如果觉得页面不够美丽,建议你可以按照官方的例子去学,这是地址elemental battles
本次调整了部分逻辑,方便我操作,如果你觉得不合适,可以修改
再来强调一下你需要哪些知识才能看懂:

  • 区块链共识算法,这是区块链的核心
  • eos 账户,权限,cpu,net,ram等相关知识
  • eos 本地环境,得安装在电脑上,安装方式自行查找(也就是部署合约用,如果你没有而又想走捷径,这里有个好东西,拿去不谢,eos4js,这个库开源出来才2天,也许后面还有更多类似的优秀的库,你都可以使用)
  • eoscdt 得安装,去下载,然后下一步就好。(这个东西是用来编译成wasm和abi的,单独抽出来的,建议使用,当前时间我使用的eoscdt1.2,语法是旧的,如果你后续用了高版本,按照这篇文章敲语法会有问题的,但是思想是不变的)
  • react 这是一个前端的库,为什么要用这个?因为第一,我会,第二,官方也是用的他,第三,用起来很爽,你如果用的别的如vue活着原生来写,只要你把流程搞懂了,也是一样的
  • react-redux 这东西有点难懂,可能会看得你一脸蒙蔽,和上面一点是配套使用的,数据流的管理方式。简单理解就是数据的传递方式,写起来很复杂,理解起来还是很烧脑。我一时半会也说不完,来这里去看看吧react-redux。(我也考虑过不用这个写,这样简单很多,对大家的理解也会有帮助,但是考虑到官方用了,我也就顺便用了)
  • c++ 这个东西我也是为了写eos智能合约才开始学的,还好我之前有java的基础,学点基础够我们用了,难点的在它的指针上面,(话说,写合约貌似不怎么用指针了,eos已经帮我们封装的够多了,用得多的是引用),这个就看自己了
  • node 基础 够用就好
  • 当然 js基础 同样够用就好
  • 梯子
  • 系统,反正不能是Windows系统

为了避免上一篇文章犯错,这次先把结果展示了
eos第三课源码

这里尽量放git图片了,先看图后看文字解释。

eos

布局更新

  • 更改了采取内置账号和秘钥的方式,直接点击不用输入了
  • 当登录成功,去区块链上拿到用户的信息,刷新ui
  • 下面的代码已经有注释了

    import {Api, JsonRpc, JsSignatureProvider} from 'eosjs';
    
    async function takeAction(action, account, pk, dataValue) {
        console.log("takeAction", action, dataValue)
        const rpc = new JsonRpc(process.env.REACT_APP_EOS_HTTP_ENDPOINT);
        const signatureProvider = new JsSignatureProvider([pk]);
        const api = new Api({rpc, signatureProvider, textDecoder: new TextDecoder(), textEncoder: new TextEncoder()});
        try {
            return await api.transact({
                actions: [{
                    account: "shaokun11113",  // 合约的拥有者
                    name: action,             // 执行的合约方法
                    authorization: [{
                        actor: account,       // 执行合约的账户
                        permission: 'active', // 执行合约的账户所需要的权限
                    }],
                    data: dataValue,          // 所需要的参数,以json字符串进行传递,注意key为合约中定义的参数的名字,也可以通过abi进行查看
    
            }]
        }, {
            blocksBehind: 3,              // 多少个块后确认
            expireSeconds: 30,            // 超时时间
        });
    } catch (err) {
        throw(err)
    }
    }
    
    class ApiService {
    
    static async getUserByName(username) {
        const rpc = new JsonRpc(process.env.REACT_APP_EOS_HTTP_ENDPOINT);
        const res = await rpc.get_table_rows({
            code: process.env.REACT_APP_EOS_CONTRACT_NAME,             //由于在合约中写死了,这里也可以写死
            scope: process.env.REACT_APP_EOS_CONTRACT_NAME,             //由于在合约中写死了,这里也可以写死
            table: "userinfo",                                           //由于在合约中写死了,这里也可以写死
            json: true,                                                // 默认值 ,可以不填
            lower_bound: username,                                      // 匹配规则,不填默认返回全部
        })
        return res.rows[0];
    }
    
    static login({name, key}) {
        return new Promise((resolve, reject) => {
            takeAction("login", name, key, {user: name}).then(res => {
                // 执行成功会返回默认的交易hash
                console.log("login success", res);
                resolve();
            }).catch(err => {
                console.log(err)
                reject(err);
            })
        });
    }
    }
    
    export default ApiService;
    
  • 当登录成功显示的组件,这是当跳转后立即获取最新数据。结合上面的代码,就知道了。

    eos

  • 上面最后,我使用postman拿取了合约中所有的数据,可以看到,目前的数据储存是两条,当然,你在代码中也可以实现,至于怎么实现,我想你应该知道的。至于上一篇文章错在哪里,你仔细看了上面的代码就知道了。

文章允许转载,但请注明出处,谢谢

结尾

对个人的总结:
目前分享的经验不是很足,严格算起来,就一个多月而已,还是经过一个外国老哥的建议才开始写的,目前文章也比较粗糙。
但我还是会尽力来记录这些。一来这样记录下来可以让自己对知识把握得更牢靠,二来也可以帮助到他人。
从零开始搭博客,学习markdown语法,搜集图片,上传图片,传动图或者视频,都是一步一步摸索出来,中间坑很多,但是乐趣也在此.

至于后续的文章,尽量跟着官方的教程走,所以到这里,第三课结束了。
小小的总结一下前三课的内容

  • 第一课 搭环境
  • 第二课 搭ui与合约的编写
  • 第二课续 部署,测试合约(合约部署成功,连接合约是失败的)
  • 第三课 更改上课的错误,连接合约,增加新的展示界面

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步

1234

shaokun

点滴积累,聚少成多! 心中有善,不骄不躁

38 日志
© 2021 shaokun
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4
访问人数 访问总量 次