您正在查看: Surou 发布的文章

EOS nodejs 与链上合约交互事例

链上部署合约部分代码

Table
struct [[eosio::table("users"), eosio::contract("some.game")]] user{
        capi_name account_name;
        uint64_t qualifications;
        auto primary_key() const { return account_name; }
    };
Action
ACTION addqualify(capi_name account);
void some_contract::addqualify(capi_name account){
    require_auth( _self.value );
    user_tables user_table(_self, _self.value);
    auto itr = user_table.find(account);
    eosio_assert(itr != user_table.end(), "account not find" );
    user_table.modify( itr, _self, [&]( auto& u ) {
        u.qualifications += 1;
    });
}

NodeJs 代码部分

EosService.js

const Eos = require('eosjs');
const config = require('../config/EosNetwork');

class EosService {
  constructor() {
    this.eosApi = Eos({
      httpEndpoint: `${config.network.protocol}://${config.network.host}${config.network.port ? ':' : ''}${config.network.port}`,
      chainId: config.network.chainId,
      keyProvider: config.self.privateKey,
      fetchConfiguration: {
        timeout: config.network.timeout,
      },
    });
    this.authorization = [`${config.contract.account}@active`];
    this.transactionOptions = { authorization: this.authorization };
    this.contractResult = null;
  }

  async init() {
    try {
      this.contractResult = await this.eosApi.contract(config.contract.code);
    } catch (e) {
      console.log(e); // TODO throw
    }
  }

  api() {
    return this.eosApi;
  }

  async pushAction(action, params) {
    if (this.contractResult === null) {
      await this.init();
    }
    try {
      this.contractResult[action](params, this.transactionOptions);
    } catch (e) {
      console.log(e); // TODO throw
    }
  }
}

module.exports = EosService;

chain.js

const EosService = require('../services/EosService');

class Chain {
  constructor() {
    this.eosService = new EosService();
  }

  async addQualify(account) {
    await this.eosService.pushAction('addqualify', account);
  }
}

module.exports = Chain;

game.js

const Chain = require('./chain');
const chain = new Chain();
....
await chain.addQualify(item.challenger); //此时,链上users 对应账户的qualifications将会+1

谈谈 EOS 的出块时间,不可逆时间,BFT

EOS出块时间

我们知道,新生产节点必须基于上一个区块生产新区块,因此新生产节点必须在指定的时候内接收到上一个区块的内容,否则只能跳过(只能基于上上一个区块生产)。如果出块时间太短,新节点很大可能接收不到上一个区块的内容,进而频繁出现跳块。只要有跳块,系统就会出现临时分叉,尽管EOS的DPOS的定时出块和最长链共识让系统很大可能最终达成共识,但是也会造成更多缺块,进而降低了有效单位出块数量,得不偿失。

因而一个合适的出块时间就显的很重要了,比如STEAM, BTXS的出块时间就设置为3S。那凭啥EOS能将出块时间设置为0.5S呢?因为EOS做了如下改进:

  • 1个生产节点连续出12个块
    1个生产节点内的12个块不存在接收等待问题,是0等待,肯定也不存在跳块问题。比如生产区块2时,肯定能拿到区块1的数据
  • 21个生产节点的出块顺序是固定的,且直连
    当A个节点完成12个区块时,系统会切换生产者,新的生产者B就需要通过网络接收上一个生产者的区块。由于A的区块是生产一个就广播一个,12区块传播到生产者B的可用时间最短, 由于网络时延和拥堵问题,B不一定能接收A的12区块。因此为了减少跳块,必须降低A->B的时延。由于STEEM,BTSX中,生产节点是随机排序的,A->B的时延是不确定的,可能A在美国,B在中国,美国和中国的来回传播就可能需要600ms, 同时A和B可能没有直连还需要中间节点跳转才能传播,时延就更久了,因而需要设置一个比较大的出块时间,最后测试调试下来,STEEM和BTXS就设置了一个比较合理的3S经验值。EOS为了减少相邻两个节点的时延,按照节点之间的时延安排出块顺序,即时延小的安排在邻近,比如A安排为香港,B为中国就比A是美国,B为中国好太多。同时,由于这21个节点的信息是透明的,这21个节点可以直连,进一步降低传播时延。有了这两个改进后,0.5s变为可能了。当然这种固定出块顺序的确定性也带来了不安全因素,比如攻击者可以准确预估每个出块节点,就可以更容易发起攻击行为。

然后我们来看下节点地域和实际出块顺序:
![]()
中国的节点没有将服务器部署在中国大陆的,要么在香港要么在日本,新加坡,甚至美国。

EOS区块不可逆时间

EOS中,21个生产节点轮流出块。一个区块是通过后续节点基于该区块的生产行为来间接确认的,为了实现BFT级别的不可逆,自然需要得到2/3+1的节点确认。由于每个节点生产12个区块,所以需要12(2/321+1)=12*15=180个区块确认。因而大家第一感觉是1个区块只需要后续180个区块即可变成不可逆状态。然而,如果大家在EOS区块链上查询区块的实时信息,就会发现一个区块都是在300多区块的时候才会被标识为不可逆状态。这是因为区块的确认分为两个阶段,第一个阶段是pre-commit阶段,该阶段需要接受2/3+1个节点的确认表明,超过2/3节点认可该区块。但是此时并不意味着超过2/3的节点已经了解到这个2/3确认信息。因而再次需要2/3的commit签名确认过程。两阶段签名确认流程类似下图:

EOS的特殊BFT

常规的BFT都是生产一个区块,等待共识,然后再生产一个区块,比如Tendermint和Cosmos里的PBFT的实现。由于PBFT共识需要比较长的时间(至少1s以上),因而采用传统的PBFT是没法满足0.5s出块需求的。因而EOS 的BFT采取了不同的实现方式,出块和共识是流水并行工作的,区块生产完成后,不等待PBFT共识,继续生产同时参与并处理上一个区块的PBFT共识,当PBFT共识完成后即修改为不可逆状态。

EOS是否已采用BFT

其实很多人都在问我这个问题,我只能说,根据我目前了解点到的信息是代码和测试代码都有,但还没实行。其实从目前不可逆时间也可推测出BFT没有启用,如果BFT已经启用,1个区块在一个BFT共识完成后(该共识一般只需1s多)即可被确认,而不是目前的3分钟。

转载自 https://mp.weixin.qq.com/s/DlKA5xBhNUFC203t7sKy4A

scatter getArbitrarySignature

今天要实现通过ScatterDesktop,使用选中的账号私钥加密数据做签名,传到中心服务器,做身份验证
先粗略记下,稍后整理

首先由scatter.js发起 (View Code)

getArbitrarySignature(publicKey, data, whatfor = '', isHash = false){
        throwNoAuth();
        return SocketService.sendApiRequest({
            type:'requestArbitrarySignature',
            payload:{
                publicKey,
                data,
                whatfor,
                isHash
            }
        });
    }

然后传到 ScatterDesktop (View Code)

static async [Actions.REQUEST_ARBITRARY_SIGNATURE](request, identityKey = null){
        return new Promise(async resolve => {

            const {payload} = request;
            const {origin, publicKey, data, whatFor, isHash} = request.payload;

            if(identityKey) payload.identityKey = identityKey;
            else {
                const possibleId = PermissionService.identityFromPermissions(origin, false);
                if (!possibleId) return resolve({id: request.id, result: Error.identityMissing()});
                payload.identityKey = possibleId.publicKey;
            }

            const keypair = KeyPairService.getKeyPairFromPublicKey(publicKey);
            if(!keypair) return resolve({id:request.id, result:Error.signatureError("signature_rejected", "User rejected the signature request")});

            const blockchain = keypair.publicKeys.find(x => x.key === publicKey).blockchain;

            // Blockchain specific plugin
            const plugin = PluginRepository.plugin(blockchain);

            // Convert buf and abi to messages
            payload.messages = [{
                code:`${blockchain.toUpperCase()} Blockchain`,
                type:'Arbitrary Signature',
                data:{
                    signing:data
                }
            }];

            PopupService.push(Popup.popout(Object.assign(request, {}), async ({result}) => {
                if(!result || (!result.accepted || false)) return resolve({id:request.id, result:Error.signatureError("signature_rejected", "User rejected the signature request")});

                resolve({id:request.id, result:await plugin.signer(payload, publicKey, true, isHash)});
            }));
        });
    }

关键点在

plugin.signer(payload, publicKey, true, isHash)

然后到 eos.js (View Code)

async signer(payload, publicKey, arbitrary = false, isHash = false){
        let privateKey = KeyPairService.publicToPrivate(publicKey);
        if (!privateKey) return;

        if(typeof privateKey !== 'string') privateKey = this.bufferToHexPrivate(privateKey);

        if (arbitrary && isHash) return ecc.Signature.signHash(payload.data, privateKey).toString();
        return ecc.sign(Buffer.from(arbitrary ? payload.data : payload.buf, 'utf8'), privateKey);
    }

eosjs-ecc

Verify signed data.

ecc.verify(signature, 'I am alive', pubkey) === true

Recover the public key used to create the signature.

ecc.recover(signature, 'I am alive') === pubkey

cleos system claimrewards Error 3050003 “quantity exceeds available supply”

在测试网中,获取出块奖励时,报错

surou@bcskillsurou:~$ cleos -u http://127.0.0.1:8000 system claimrewards bcskillsurou -p bcskillsurou
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: quantity exceeds available supply
pending console output:

查询supply

surou@bcskillsurou:~$ cleos -u http://127.0.0.1:8000 get currency stats eosio.token EOS
{
  "EOS": {
    "supply": "10000000000.0000 EOS",
    "max_supply": "10000000000.0000 EOS",
    "issuer": "eosio"
  }
}

发现supply已经不小于max_supply。
由于bp每次claimrewards时,根据距离上次claim的时间差先增发,然后再从奖池里面拿。所以每次claimrewards时,supply都会增加相应的值,但必须小于max_supply。
继续跟进

创建代币
cleos -u http://127.0.0.1:8000 push action eosio.token create '["eosio","10000000000.0000 EOS",0,0,0]' -p eosio.token
发布代币
cleos -u http://127.0.0.1:8000 push action eosio.token issue '["eosio","10000000000.0000 EOS","issue"]' -p eosio.token

发现是当时启动测试网时,issue发行了最大的值,一般应小余最大值10倍,及创建100亿代币的最大值,只发行10亿,之间的差额用于增发。
参考:https://github.com/EOSIO/eos/issues/4325