当前位置: 首页 > 编程日记 > 正文

EOS智能合约:system系统合约源码分析

链客,专为开发者而生,有问必答!

此文章来自区块链技术社区,未经允许拒绝转载。
在这里插入图片描述
eosio.system 概览
笔者使用的IDE是VScode,首先来看eosio.system的源码结构。如下图所示。

本文分析的源码来自于eosio.contracts。

一、native.hpp
该文件可以分为两个部分,前一个部分是定义了一些结构体,后一个部分是帮助eosio.system合约声明action。总体看上去,这个文件是负责权限的结构。下面先看他都定义了哪些结构体。

权限等级权重
struct permission_level_weight {
permission_level permission;
uint16_t weight;

EOSLIB_SERIALIZE( permission_level_weight, (permission)(weight) )
};
注意,合约中定义的结构体一般都会在末尾加入EOSLIB_SERIALIZE宏,将结构体的字段属性序列化,这行代码不是必须的,但加上了能够加快解析的速度,从而提升编译效率。

权限等级权重结构体只有两个字段,一个是permission_level类型的对象permission,另一个是16位的无符整型类型的权重。permission_level是定义在eosiolib/action.hpp文件中的一个结构体。它是通过一个账户名以及其权限名构建的,例如{“useraaaaaaaa”,“active”},这样的一个组合构成了一个权限对象。

公钥权重
struct key_weight {
eosio::public_key key;
uint16_t weight;

EOSLIB_SERIALIZE( key_weight, (key)(weight) )
};
这个结构体的结构与前面的相似,所以陌生的部分只有eosio::public_key,这是定义在eosiolib/crypto.hpp中的结构体,它代表了EOS中一个公钥对象,该对象可以是K1类型或者R1类型。

secp256k1和secp256r1是两种椭圆曲线数学模型,均属于公钥生成算法。私钥生成公钥的算法也即ECC的字面含义椭圆曲线,是通过该数学模型生成的一种正向快速逆向困难的算法,目前这个算法包括secp256k1和secp256r1 ,secp256k1是比特币首先使用的,而secp256r1据说更有优势,但也有被爆漏洞的历史,由于比特币没有使用secp256r1,因此还有“比特币躲过secp256r1子弹”的说法。目前这两种EOS均支持。

等待权重
struct wait_weight {
uint32_t wait_sec;
uint16_t weight;

EOSLIB_SERIALIZE( wait_weight, (wait_sec)(weight) )
};
该结构体没有什么特别的,陌生的部分仍旧只有第一个参数wait_sec,但通过字面含义即可理解,就是等待的秒数。

权力
struct authority {
uint32_t threshold = 0;
std::vector<key_weight> keys;
std::vector<permission_level_weight> accounts;
std::vector<wait_weight> waits;

EOSLIB_SERIALIZE( authority, (threshold)(keys)(accounts)(waits) )
};
这个结构体比较有趣了,它包含四个属性,其中第一个是32位无符整型类型的阈值,初始化位0。剩余三个属性即以上介绍到的三个结构体的集合对象。所以,这也说明了一个账户的权力是由一个阈值、多个密钥、多个权限、多个等待组成的。下面又到了当春乃发生的“authority”和“permission”的区别问题。

authority 指有权利的人。permission 指某项许可。所以某人需要拥有很多别人授权的许可,才能称之为有权利的人。(希望我解释清楚了♫ ♫♬♪♫ )

区块头
struct block_header {
uint32_t timestamp;
name producer;
uint16_t confirmed = 0;
capi_checksum256 previous;
capi_checksum256 transaction_mroot;
capi_checksum256 action_mroot;
uint32_t schedule_version = 0;
std::optionaleosio::producer_schedule new_producers;

EOSLIB_SERIALIZE(block_header, (timestamp)(producer)(confirmed)(previous)(transaction_mroot)(action_mroot)
(schedule_version)(new_producers))
};
这个结构体有意思了,好像在很多地方都见过block_header的声明,怎么这里又冒出来一个。有这种感觉很正常,因为之前一直研究的内容都集中在链上,之前看到的block_header是链上的声明,并不是智能合约的。通过全文检索可以查到,block_header结构体由两个文件定义:

libraries\chain\include\eosio\chain\block_header.hpp,这个明显是链上的定义,因为路径中包含了chain的字样。
eosio.system\include\eosio.system\native.hpp,另外这一个就是本文介绍的这个结构体了,这是专门服务于智能合约的代码。
所以由此可见,EOS中很多底层的基础结构体都是分两套的,一套给链使用,另一个套给智能合约使用,而他们的定义方式似乎从原来的一模一样发展到今天的些许不同。而目前EOSIO的架构体系中,eosio.contracts作为单独的项目已经从eos分隔出来,并且代码已经发生了不同。因此这种两套体系的概念的困惑会越来越小。

回到native.hpp的区块头结构体。

时间戳,uint32_t类型
生产者,name类型
confirmed,已确认数,uint16_t,初始化为0。
前一个区块的hash,是capi_checksum256类型的
事务Merkle树根,Merkle数的内容请点击以及点击。概况来讲,是为了校验区块内打包的事务的真伪以及完整性的。
action的merkle树根,校验区块内所有action的真伪以及完整性。
计划版本,schedule_version,uint32_t类型,初始化为0。
后续计划出块者。producer_schedule类型。
producer_schedule
定义在libraries\eosiolib\producer_schedule.hpp。该结构体定义了有效生产者集合的出块顺序、账户名以及签名密钥。

struct producer_schedule {
// 时间计划的版本号,按顺序递增。
uint32_t version;
// 此计划的生产者列表,包括其签名密钥
std::vector<producer_key> producers;
};
陌生的部分是producer_key,该结构体定义在libraries\eosiolib\privileged.hpp,是用来映射生产者及其签名密钥,用于生产者计划。

struct producer_key {
name producer_name;
// 此生产者使用的区块签名密钥
public_key block_signing_key;
// 重载运算符小于号,producer_key的两个对象进行小于号比较时,返回的是其name类型的生产者账户的比较。
friend constexpr bool operator < ( const producer_key& a, const producer_key& b ) {
return a.producer_name < b.producer_name;
}

EOSLIB_SERIALIZE( producer_key, (producer_name)(block_signing_key) )
};
一个问题:name类型是EOS中账户类型,那么它的对象是如何比较的?请转到第二大节。

abihash
native.hpp除了声明以上必要结构体以外,还协助eosio.system合约定义了一个状态表abihash。该状态表只有两个字段,一个是账户名,另一个是hash,该hash是当前账户的abi。在EOS中,一个账户除了通过命令

cleos get account xxxxxxxxxxxx

获得自身属性之外,还可以通过分别通过命令get code和get abi获得该账户部署的合约的abi hash以及code hash,这两个hash是用来校验其部署的智能合约的内容是否发生改变。其中abi hash就是存储在native.hpp定义的状态表中。下面是源码内容:

struct [[eosio::table(“abihash”), eosio::contract(“eosio.system”)]] abi_hash {
name owner;
capi_checksum256 hash;
uint64_t primary_key()const { return owner.value; } // 以账户的值作为该表的主键。

EOSLIB_SERIALIZE( abi_hash, (owner)(hash) )
};
注意:通过[[eosio::table(“abihash”), eosio::contract(“eosio.system”)]]的方式可以为合约定义一个状态表,而不再需要原始的typedef multi_index的方式了。这种方式适用于只有主键的情况,如果有多级索引,仍旧需要multi_index。

native合约类
先展示位于native.hpp文件中的native合约类以及位于eosio.system.hpp文件中的system_contract的区别。

class [[eosio::contract(“eosio.system”)]] native : public eosio::contract

class [[eosio::contract(“eosio.system”)]] system_contract : public native

eosio::contract是EOS中所有智能合约的基类,native合约类继承于它,然后system_contract合约类继承于native,而他们二者共同组成了eosio.system智能合约。这种方式让原本单一的智能合约架构变得丰富。作为基类的native,它都声明了eosio.system的哪些属性呢?下面仔细观瞧。

[[eosio::action]] newaccount
我们常用的system newaccount功能就是在native中声明的。该action在创建新帐户后调用,此代码强制实施新帐户的资源限制规则以及新帐户命名约定。规则包含两个:

帐户不能包含’.’ 强制所有帐户的符号长度为12个字符而没有“.” 直到实施未来的帐户拍卖流程。
新帐户必须包含最少数量的token(如系统参数中所设置),因此,此方法将为新用户执行内联buyram购买内存,其金额等于当前新帐户的创建费用。
[[eosio::action]]
void newaccount( name creator,
name name,
ignore owner,
ignore active);
陌生的部分是ignore,该结构位于libraries\eosiolib\ignore.hpp。

ignore
告诉数据流忽略此类型,但允许abi生成器添加正确的类型。当前非忽略类型不能在方法定义中成功忽略类型,即允许

void foo(float,ignore

但不允许

void foo(float,ignore

因为int已经被声明为忽略类型,所以后面不能再作为非忽略类型出现了。ignore结构体源码如下:

template
struct [[eosio::ignore]] ignore {};
其他[[eosio::action]]
动作

返回值

参数

解释

updateauth

void

ignore

更新账户的某项权限内容

deleteauth

void

ignore

删除账户的某项权限内容

linkauth

void

ignore

连接其他账户

unlinkauth

void

ignore

解除某账户的连接

canceldelay

void

ignore<permission_level> canceling_authignore<capi_checksum256> trx_id

取消某个延迟交易

onerror

void

ignore<uint128_t> sender_idignore<std::vector

处理错误

setabi

void

name accountconst std::vector

设置账户的abi内容

setcode

void

name accountuint8_t vmtypeuint8_t vmversionconst std::vector

设置账户的code内容

二、name.hpp
name结构体定义在libraries\eosiolib\name.hpp,源码注释如下:

struct name {
public:
enum class raw : uint64_t {};
// 构建一个新的name对象,初始化默认为0
constexpr name() : value(0) {}
// 使用给定的unit64_t类型的值构建一个新的name对象。
constexpr explicit name( uint64_t v )
:value(v)
{}
// 使用给定的一个范围的枚举类型,构建一个新的name对象。
constexpr explicit name( name::raw r )
:value(static_cast<uint64_t>®)
{}
// 使用给定的字符串构建一个新的name对象。
constexpr explicit name( std::string_view str )
:value(0)
{
if( str.size() > 13 ) { // 字符串最长不能超过12
eosio::check( false, “string is too long to be a valid name” );
}
if( str.empty() ) {
return;
}
// 将字符串转为uint64_t
auto n = std::min( (uint32_t)str.size(), (uint32_t)12u );
for( decltype(n) i = 0; i < n; ++i ) {
value <<= 5;
value |= char_to_value( str[i] );
}
value <<= ( 4 + 5*(12 - n) );
if( str.size() == 13 ) {
uint64_t v = char_to_value( str[12] );
if( v > 0x0Full ) {
eosio::check(false, “thirteenth character in name cannot be a letter that comes after j”);
}
value |= v;
}
}
// 将一个Base32符号的char转换为它对应的值。
static constexpr uint8_t char_to_value( char c ) {
if( c == ‘.’)
return 0;
else if( c >= ‘1’ && c <= ‘5’ )
return (c - ‘1’) + 1;
else if( c >= ‘a’ && c <= ‘z’ )
return (c - ‘a’) + 6;
else // 字符中出现了不允许的内容。
eosio::check( false, “character is not in allowed character set for names” );
return 0; // 流程控制将不会到达这里,这一行是为了防止warn信息。
}
// 返回一个name对象的长度,运算方法。
constexpr uint8_t length()const {
constexpr uint64_t mask = 0xF800000000000000ull;
if( value == 0 )
return 0;
uint8_t l = 0;
uint8_t i = 0;
for( auto v = value; i < 13; ++i, v <<= 5 ) {
if( (v & mask) > 0 ) {
l = i;
}
}
return l + 1;
}
// 返回一个name对象的后缀,完整的运算方法。
constexpr name suffix()const {
uint32_t remaining_bits_after_last_actual_dot = 0;
uint32_t tmp = 0;
for( int32_t remaining_bits = 59; remaining_bits >= 4; remaining_bits -= 5 ) { // remaining_bits必须有符号整数
// 从左到右依次遍历name中的字符,共12次
auto c = (value >> remaining_bits) & 0x1Full;
if( !c ) { // 如果当前字符是点
tmp = static_cast<uint32_t>(remaining_bits);
} else { // 如果当前字符不是点
remaining_bits_after_last_actual_dot = tmp;
}
}
uint64_t thirteenth_character = value & 0x0Full;
if( thirteenth_character ) { // 如果第13个字符不是点
remaining_bits_after_last_actual_dot = tmp;
}
if( remaining_bits_after_last_actual_dot == 0 ) // 除了潜在的前导点之外,name中没有实际的点
return name{value};
// 此时,remaining_bits_after_last_actual_dot必须在4到59的范围内(并且限制为5的增量)。
// 除了4个最低有效位(对应于第13个字符)之外,对应于最后一个实际点之后的字符的剩余位的掩码。
uint64_t mask = (1ull << remaining_bits_after_last_actual_dot) - 16;
uint32_t shift = 64 - remaining_bits_after_last_actual_dot;
return name{ ((value & mask) << shift) + (thirteenth_character << (shift-1)) };
}
// 将name类型转为raw枚举类型:基于name对象的值,返回一个raw枚举类型的实例。
constexpr operator raw()const { return raw(value); }
// 显式转换一个name的uint64_t值为bool,如果name的值不为0,返回true。
constexpr explicit operator bool()const { return value != 0; }
// 根据给定的char缓冲区,以字符串的类型写入name对象。参数begin:char缓冲区的开头,参数end:刚好超过char缓冲区的位置,作为结尾。
char* write_as_string( char* begin, char* end )const {
static const char* charmap = “.12345abcdefghijklmnopqrstuvwxyz”;
constexpr uint64_t mask = 0xF800000000000000ull;
if( (begin + 13) < begin || (begin + 13) > end ) return begin;
auto v = value;
for( auto i = 0; i < 13; ++i, v <<= 5 ) {
if( v == 0 ) return begin;
auto indx = (v & mask) >> (i == 12 ? 60 : 59);
*begin = charmap[indx];
++begin;
}
return begin;
}
// 将name对象转为一个字符串返回。
std::string to_string()const {
char buffer[13];
auto end = write_as_string( buffer, buffer + sizeof(buffer) );
return {buffer, end};
}
// 重载运算符等于号,给定两个name对象,如果他们的value相等,则返回true,说明对象也相等。
friend constexpr bool operator == ( const name& a, const name& b ) {
return a.value == b.value;
}
// 重载运算符符不等于,如果给定的两个name对象的value不相等,则返回true,说明对象也不相等。
friend constexpr bool operator != ( const name& a, const name& b ) {
return a.value != b.value;
}
// 重载运算符小于号,原理同上。
friend constexpr bool operator < ( const name& a, const name& b ) {
return a.value < b.value;
}
uint64_t value = 0; // 其实name对象只有一个有效属性,就是value,以上都是name对象的构造方式、限制条件、各种转型以及运算符重载。

EOSLIB_SERIALIZE( name, (value) )
};
三、exchange_state.hpp
该文件位于eosio.system\include\eosio.system\exchange_state.hpp。也是system合约的依赖之一。该文件处理资产方面的工作,主要部分是exchange_state结构体,该结构体使用Bancor算法在两种不同资产类型中间创造一个50对50的中继,bancor交易所的状态完全包含在这个结构体中,此API没有任何额外的副作用。

namespace eosiosystem {
using eosio::asset;
using eosio::symbol;

typedef double real_type;

// 使用Bancor算法在两种不同资产类型中间创造一个50对50的中继。bancor交易所的状态完全包含在这个结构体中。使用此API没有任何副作用。
struct [[eosio::table, eosio::contract(“eosio.system”)]] exchange_state {
asset supply; // 资产供应
struct connector { // 连接器
asset balance; // 资产余额
double weight = .5; // 权重

     EOSLIB_SERIALIZE( connector, (balance)(weight) )};connector base; // 基本连接器connector quote; // 引用连接器uint64_t primary_key()const { return supply.symbol.raw(); } // 该table主键asset convert_to_exchange( connector& c, asset in ); // 通过连接器c将输入资产in转换为发行资产issued。asset convert_from_exchange( connector& c, asset in ); // 通过连接器c将输入资产in转换为输出资产outasset convert( asset from, const symbol& to ); // 核心功能:将一种资产转为另一种符号的等价资产。例如将10 SYS的资产转为EOS是20 EOS,币币交易。EOSLIB_SERIALIZE( exchange_state, (supply)(base)(quote) )

};
// 内存市场状态表
typedef eosio::multi_index< “rammarket”_n, exchange_state > rammarket;
}
convert函数是exchange最重要的功能,它实现了完全按照boncor市场机制交换token。具体实现源码的机制如下:

asset exchange_state::convert(asset from, const symbol &to)
{
auto sell_symbol = from.symbol; // 原来的符号,作为卖出币
auto ex_symbol = supply.symbol; // 中转币的符号
auto base_symbol = base.balance.symbol; // base连接器资产的符号
auto quote_symbol = quote.balance.symbol; // quote连接器资产的符号
if (sell_symbol != ex_symbol)
{ // 如果卖出币不是中转币
if (sell_symbol == base_symbol)
{ // 如果卖出币等于base连接器资产
from = convert_to_exchange(base, from); // 通过base连接器转换卖出币
}
else if (sell_symbol == quote_symbol)
{ // 如果卖出币等于quote连接器资产
from = convert_to_exchange(quote, from); // 通过quote连接器转换卖出币
}
else
{ // 其他卖出币无任何连接器的情况视为无效币币兑换行为。
eosio_assert(false, “invalid sell”);
}
}
else
{ // 如果卖出币是中转币
if (to == base_symbol) // 如果买入币等于base连接器资产
{
from = convert_from_exchange(base, from); // 通过base连接器转换卖出币
}
else if (to == quote_symbol) // 如果买入币等于quote连接器资产
{
from = convert_from_exchange(quote, from); // 通过quote连接器转换卖出币
}
else
{ // 其他卖出币无任何连接器的情况视为无效币币兑换行为。
eosio_assert(false, “invalid conversion”);
}
}
if (to != from.symbol) // 如果经过一轮转换以后,from和to资产仍旧没有统一符号,则再次调一遍转换。
return convert(from, to);
return from; // 最后成功得到转换为等价的to币
}
这部分可以参照之前的一篇文章【EOS标准货币体系与源码实现分析】。

四、asset.hpp
asset.hpp是合约中关于资产方面的数据结构的定义。该文件包含asset结构体以及extended_asset结构体。下面首先分析asset结构体的源码部分。

struct asset
{
int64_t amount; // 资产数量
symbol_type symbol; // 资产符号名称,详见以下symbol_type源码分析。
static constexpr int64_t max_amount = (1LL << 62) - 1; // 资产数量最大值,取决于int64_t类型的取值范围。
// 通过给定的符号名称以及资产数量构建一个新的资产对象。
explicit asset(int64_t a = 0, symbol_type s = CORE_SYMBOL)
: amount(a), symbol{s}
{
eosio_assert(is_amount_within_range(), “magnitude of asset amount must be less than 2^62”);
eosio_assert(symbol.is_valid(), “invalid symbol name”);
}
// 检查资产数量是否在范围以内,是否超过了最大限额。
bool is_amount_within_range() const { return -max_amount <= amount && amount <= max_amount; }
// 检查资产对象是否有效,有效资产的数量应该小于等于最大限额同时它的符号名称也是有效的。
bool is_valid() const { return is_amount_within_range() && symbol.is_valid(); }

// 设置资产的数量
void set_amount(int64_t a)
{
amount = a;
eosio_assert(is_amount_within_range(), “magnitude of asset amount must be less than 2^62”);
}

/**
* 以下为资产对象的运算符重载,包含
* 取负,-=,+=,+,-,=,(数乘以资产,资产乘以数),/(资产除以数,资产除以资产),/=,==,!=,<,<=,>,>=
* 源码部分省略。
*/

// 打印资产
void print() const
{
int64_t p = (int64_t)symbol.precision();
int64_t p10 = 1;
while (p > 0)
{
p10 *= 10;
–p;
}
p = (int64_t)symbol.precision();

  char fraction[p + 1];fraction[p] = '\0';auto change = amount % p10;for (int64_t i = p - 1; i >= 0; --i){fraction[i] = (change % 10) + '0';change /= 10;}printi(amount / p10);prints(".");prints_l(fraction, uint32_t(p));prints(" ");symbol.print(false);

}

EOSLIB_SERIALIZE(asset, (amount)(symbol))
};
symbol_type
直接通过源码注释分析,如下:

/**

  • @brief 存储关于符号相关的信息的结构体
    */
    struct symbol_type
    {
    symbol_name value; // uint64_t类型的符号名称
    symbol_type() {}
    symbol_type(symbol_name s) : value(s) {} // 符号的类型
    bool is_valid() const { return is_valid_symbol(value); } // 符号是否有效
    uint64_t precision() const { return value & 0xff; } // 符号类型中包含对资产精度的要求,即小数点后几位数。
    uint64_t name() const { return value >> 8; } // 返回代表符号名称的uint64_t的值
    uint32_t name_length() const { return symbol_name_length(value); } // 返回符号名称的长度
    operator symbol_name() const { return value; } //重载符号对象的()运算符,返回符号名称的uint64_t值
    void print(bool show_precision = true) const
    { // 打印符号信息,包含uint64_t转字符的算法。
    if (show_precision)
    {
    ::eosio::print(precision()); // 打印符号的精度
    prints(",");
    }
    //uint64_t转字符
    auto sym = value;
    sym >>= 8;
    for (int i = 0; i < 7; ++i)
    {
    char c = (char)(sym & 0xff);
    if (!c)
    return;
    prints_l(&c, 1);
    sym >>= 8;
    }
    }

    EOSLIB_SERIALIZE(symbol_type, (value))
    };
    extended_asset
    extended_asset,顾名思义是asset资产的延展类型,主要是在asset的基础上增加了资产拥有者的相关字段。内容不多仍旧通过源码分析一下:

struct extended_asset : public asset
{
account_name contract; // 资产拥有者

// 获得资产的扩展符号
extended_symbol get_extended_symbol() const { return extended_symbol(symbol, contract); }

// 默认构造器,构造一个扩展资产对象
extended_asset() = default;

// 通过给定的数量和扩展符号构造一个扩展资产对象。
extended_asset(int64_t v, extended_symbol s) : asset(v, s), contract(s.contract) {}
// 通过给定的资产以及拥有者账户名构造一个扩展资产。
extended_asset(asset a, account_name c) : asset(a), contract© {}

// 打印相关信息
void print() const
{
asset::print();
prints("@");
printn(contract);
}

/**
* 运算符重载,包括符号取反,-,+
* 主要是对资产拥有者的操作,其他的操作于asset一致。
*/

EOSLIB_SERIALIZE(extended_asset, (amount)(symbol)(contract))
};
五、eosio.system.hpp
下面查看system合约的主要头文件eosio.system.hpp,该文件包含了合约的属性,定义了大量结构体用于支撑system合约的业务功能,下面重点浏览system合约的成员属性。

成员

权属

名称

解释

_voters

私有属性

voters_table实例

投票状态表,表名为voters,结构为voter_info结构体。

_producers

私有属性

producers_table实例

生产者信息状态表,表名为produceers,包含一个自定义索引prototalvote,结构为producer_info结构体。

_global

私有属性

global_state_singleton实例

全局状态单例状态表,表名为global,结构为eosio_global_state结构体,继承自eosio::block-chain_parameters与genesis.json内容高度匹配。

_gstate

私有属性

eosio_global_state结构体实例

就是上面这个状态表的数据结构实例。

_rammarket

私有属性

rammarket实例

内存市场状态表,定义在exchange_state.hpp头文件中。表名为rammarket,结构为使用了bancor算法的exchange_state结构体。

下面继续介绍system合约的成员函数内容,

成员

权属

解释

update_elected_producers

私有函数

只有一个参数是时间戳,按照时间戳更新已入选的生产节点名单。

update_votes

私有函数

更新投票信息。包含参数有投票者、代理、生产者投票内容,以及支持或反对的标识。

changebw

私有函数

更改某账户的资源量,包含出资者、接收者、cpu资源量、net资源量,以及是否以转账的形式更改。即抵押资源量的token也属于接收者了。

get_default_parameters

私有函数

获得默认参数

get_core_symbol

私有函数

通过内存账户的token符号获得链上主币符号。

current_time_point

私有函数

获得当前时间点time_point类型。

current_block_time

私有函数

获得当前区块时间block_timestamp类型。

update_producer_votepay_share

私有函数

更新生产者投票支付份额

update_total_votepay_share

私有函数

更新总投票支付份额

propagate_weight_change

私有函数

代理权重更改,传入投票者账户。

下面分析system合约的公共成员函数,

成员

权属

解释

onblock

公共函数

在producer_pay.cpp中实现,是由eosio创世账户发起,用于更新生产者生产区块信息以及上链的账号名称拍卖信息。传入时间戳和生产者

delegatebw

公共函数

与私有函数changebw的参数完全相同,用于抵押资源的主要方法。

undelegatebw

公共函数

与抵押函数相反,是用来解除抵押的方法。

buyram

公共函数

为账户购买内存资源。有出资方

buyrambytes

公共函数

上面是以token的方式购买内存资源,这一个是以内存量字节的方式购买。

sellram

公共函数

卖出内存资源。

refund

公共函数

在抵押动作未完成时,发起退款

regproducer

公共函数

注册成为备用生产者。

unregprod

公共函数

解除备用生产者的注册

setram

公共函数

设置最大内存量,为链增加内存容量,注意只能增加不能降低。

voteproducer

公共函数

为生产者投票,校验投票者签名,然后调用了私有函数update_votes函数。

regproxy

公共函数

注册成为代理

setparams

公共函数

设置链参数eosio::blockchain_parameters

claimrewards

公共函数

生产者认领出块奖励

setpriv

公共函数

设置账户是否为特权账户

rmvproducer

公共函数

移除失效生产者并标记

bidname

公共函数

拍卖账户名称

六、cpp实现精选
更新已入选生产节点
该功能是通过system合约的私有函数update_elected_producers实现。在voting.cpp中被定义实现。

/**

  • @brief 更新已入选生产节点
  • @param block_time 区块时间
    */
    void system_contract::update_elected_producers( block_timestamp block_time ) {
    _gstate.last_producer_schedule_update = block_time; // 将参数区块时间赋值给全局状态变量:最后计划出块更新时间
    auto idx = _producers.get_index<N(prototalvote)>(); // 获得producers表的索引prototalvote,该索引能够给producers表按照投票总数排序,详细分析见下一个部分。
    std::vector< std::paireosio::producer_key,uint16_t > top_producers; // 声明有效出块节点集合。
    top_producers.reserve(21); // 定义有效出块节点集合的数量为21。
    // 从prototalvote索引结果集中筛选出21个插入top_producers集合。这些生产者要满足是active的同时总票数大于0(最基本的校验)。
    for ( auto it = idx.cbegin(); it != idx.cend() && top_producers.size() < 21 && 0 < it->total_votes && it->active(); ++it ) {
    top_producers.emplace_back( std::paireosio::producer_key,uint16_t({{it->owner, it->producer_key}, it->location}) );
    }
    // 如果有效出块集合的数量小于全局标志位:最后计划生产者数量(见下方),则中断返回。(适用于总数不足21个节点的情况)
    if ( top_producers.size() < _gstate.last_producer_schedule_size ) {
    return;
    }
    // 根据名称为top_producers排序。
    std::sort( top_producers.begin(), top_producers.end() );
    // 新建producers集合,copy一份top_producers
    std::vectoreosio::producer_key producers; // 声明生产者集合
    producers.reserve(top_producers.size()); // 将producers设置为与top_producers一样大小。
    for( const auto& item : top_producers ) // 遍历copy元素
    producers.push_back(item.first);
    bytes packed_schedule = pack(producers); // 将超级节点集合打包成字节
    if( set_proposed_producers( packed_schedule.data(), packed_schedule.size() ) >= 0 ) { // 设置计划生产者,更新最后计划生产者数量
    _gstate.last_producer_schedule_size = static_cast<decltype(_gstate.last_producer_schedule_size)>( top_producers.size() );
    }
    }
    二级索引排序
    prototalvote索引的定义在multi_index状态表producers_table。

typedef eosio::multi_index< N(producers), producer_info,
indexed_by<N(prototalvote), const_mem_fun<producer_info, double, &producer_info::by_votes> >
> producers_table;
producers_table状态表的声明中,定义了表名为producers,数据结构为producer_info,然后定义了二级索引,名称为prototalvote,该索引的提取器是操作数据结构produer_info对象,提取类型为double,提取规则是produer_info的by_votes方法。
N是一个宏,可以把base32编码后的字符串转换为uint64。

最新版本的eosio.contracts已经改为"useraaaaaaaa"_n的方式代替了N(“useraaaaaaaa”)。

/**

  • @brief 用于从X的base32编码字符串解释生成编译的uint64 t
  • @param X - 代表名称的字符串
  • @return constexpr uint64_t - 64位无符整型值,可代表一个名称
    */
    #define N(X) ::eosio::string_to_name(#X)
    下面研究multi_index的二级索引indexed_by的定义源码。

template<uint64_t IndexName, typename Extractor>
struct indexed_by {
enum constants { index_name = IndexName };
typedef Extractor secondary_extractor_type;
};
结构体indexed_by是用来为multi_index状态表创建索引实例的。EOS中支持指定最多16个二级索引。接收两个参数,一个是索引名称,另一个是提取器。提取器采用了const_mem_fun模板,该模板有效定义了提取器的数据范围,数据类型以及提取规则(方法)。回到producers_table表的数据结构produer_info结构体中。

struct producer_info
{
account_name owner; // producer账户名
double total_votes = 0; // 当前producer的总投票数
eosio::public_key producer_key; // 当前producer的公钥
bool is_active = true; // 当前producer是否有效
std::string url; // 当前producer的介绍url,可以是官网
uint32_t unpaid_blocks = 0; // 未领奖励的区块数量
uint64_t last_claim_time = 0; // 上一次认领奖励的时间
uint16_t location = 0; // 当前producer的位置
uint64_t primary_key() const { return owner; } // producer_info结构体的主键,将被状态表producer_table作为第一索引。
double by_votes() const { return is_active ? -total_votes : total_votes; } // 按投票(排序),注意排序并不是在此实现,此方法只是为了区分,将失效producer的总票数置为其相反数
bool active() const { return is_active; } // 判断是否有效
void deactivate()
{
producer_key = public_key();
is_active = false;
} // 将当前生产者设置为失效的动作。

// 注意:明确序列化宏不是必要的,用在此处是为了提高编译效率
EOSLIB_SERIALIZE(producer_info, (owner)(total_votes)(producer_key)(is_active)(url)(unpaid_blocks)(last_claim_time)(location))
};
系统合约管理出块
eosio.system的onblock可以管理生产者的出块动作,参与每0.5秒的出块工作。下面通过注释分析该动作的源码。

/**

  • @brief system合约的出块动作
  • @param timestamp 时间戳
  • @param producer 生产者
    /
    void system_contract::onblock( block_timestamp timestamp, account_name producer ) {
    using namespace eosio;
    // 该动作是由eosio创世账户执行,要先校验是否有该账户权限。
    require_auth(N(eosio));
    // 当总激活抵押数小于最低激活抵押额时,停止动作。
    if( _gstate.total_activated_stake < min_activated_stake )
    return;
    // 当预投票开始时,更新时间为当前时间。
    if( _gstate.last_pervote_bucket_fill == 0 )
    _gstate.last_pervote_bucket_fill = current_time();
    // 在生产者集合中查询传入的生产者账号
    auto prod = _producers.find(producer);
    if ( prod != _producers.end() ) { // 成功查到结果
    _gstate.total_unpaid_blocks++; // 全局未结算区块数加一
    _producers.modify( prod, 0, [&](auto& p ) { // 当前生产者未结算数加一
    p.unpaid_blocks++;
    });
    }
    // 注意:每分钟只更新区块生产者一次,0.5秒更新一次区块时间。
    if( timestamp.slot - gstate.last_producer_schedule_update.slot > 120 ){update_elected_producers( timestamp ); // 更新已入选生产节点,见上小节
    // 账户名称拍卖工作的更新操作,注意:每天只能交易一次。
    if( (timestamp.slot - _gstate.last_name_close.slot) > blocks_per_day ) {
    name_bid_table bids(_self,_self); // 拍卖账户名的状态表实例。
    auto idx = bids.get_index<N(highbid)>(); // 得到二级索引的结果集,按照出价高低排序。
    auto highest = idx.begin();
    /
    *
    * @brief 判断是否符合拍卖结束条件。
    * 条件包括:
    * 1,状态表不为空
    * 2,出价大于0
    * 3,出价的时间在一秒钟之内
    * 4,抵押激活时间大于0
    * 5,当前时间至少超过抵押激活14天的时间
    */
    if( highest != idx.end() &&
    highest->high_bid > 0 &&
    highest->last_bid_time < (current_time() - useconds_per_day) && _gstate.thresh_activated_stake_time > 0 &&
    (current_time() - _gstate.thresh_activated_stake_time) > 14 * useconds_per_day ) {
    _gstate.last_name_close = timestamp;// 记录成功交易时间
    idx.modify( highest, 0, [&]( auto& b ){
    b.high_bid = -b.high_bid; // 该笔拍卖报价已兑现,则置相反数,可作为记录的同时不参与其他有效报价。
    });
    }
    }
    }
    }
    通过源码分析,onblock动作不仅管理了生产者的结算动作,还管理了链上账户名拍卖工作。与上面producers_table中的二级索引prototalvote的功能相同,name_bid_table状态表的二级索引highbid也是用来对结果集进行排序的,具体声明如下:

typedef eosio::multi_index< N(namebids), name_bid,
indexed_by<N(highbid), const_mem_fun<name_bid, uint64_t, &name_bid::by_high_bid > > > name_bid_table;
二级索引highbid同样使用了const_mem_fun模板定义了提取器内容,其中提取规则也就是排序依赖为name_bid结构体的by_high_bid函数。

struct name_bid {
account_name newname;
account_name high_bidder;
int64_t high_bid = 0; // 若该项值为负数,则证明已获得拍卖名字,等待认领。
uint64_t last_bid_time = 0;
auto primary_key()const { return newname; } // 主键
uint64_t by_high_bid()const { return static_cast<uint64_t>(-high_bid); } // 返回报价字段的值
};
初始化主币
EOSIO在将合约迁移到一个新创建的repo 以后,为系统合约加入了主币初始化init的操作。下面仍旧通过源码分析该操作的内容。

/**

  • @brief 初始化主币
  • @param version 版本号
  • @param core 初始化的主币对象
    */
    void system_contract::init( unsigned_int version, symbol core ) {
    require_auth( _self ); // 判断是否拥有合约主人身份。
    eosio_assert( version.value == 0, “unsupported version for init action” ); // 对于初始化动作,版本号只能为0
    auto itr = _rammarket.find(ramcore_symbol.raw()); // 在内存表中查找主币对象,如果已查到说明初始化操作已完成,退出当前进程
    eosio_assert( itr == _rammarket.end(), “system contract has already been initialized” );
    // 此处调用了token的get_supply函数,获得主币供应量
    auto system_token_supply = eosio::token::get_supply(token_account, core.code() );
    // 校验token的符号以及小数点精确位数是否一致。
    eosio_assert( system_token_supply.symbol == core, “specified core symbol does not exist (precision mismatch)” );
    // 校验主币的供应量是否大于0
    eosio_assert( system_token_supply.amount > 0, “system token supply must be greater than 0” );
    _rammarket.emplace( _self, [&]( auto& m ) { // 内存市场状态表新增数据
    m.supply.amount = 100000000000000ll;
    m.supply.symbol = ramcore_symbol;
    m.base.balance.amount = int64_t(_gstate.free_ram());
    m.base.balance.symbol = ram_symbol;
    m.quote.balance.amount = system_token_supply.amount / 1000;
    m.quote.balance.symbol = core;
    });
    }
    初始化主币的操作在节点启动时会被调用到,这个操作一般被执行成功一次就不会再被调用。初始化主币的命令时:

$ cleos push action eosio init ‘[“0”, “4,SYS”]’ -p eosio@active

传入了两个参数,第一个参数时0,上面介绍了是版本的含义。第二个参数的值为“4,SYS”,是token符号对象。SYS定义了主币的符号名称,4是主币的小数点精度,这个值可以是0到18。前面在token转账的过程中,校验了token的符号对象,校验工作就包含了对符号名称以及小数点精度位数的校验。

非常规账户竞拍
前面介绍system合约的onblock动作以及init动作都涉及到了账户竞拍的逻辑。在EOS中,常规账户的名称要求为必须12个字符同时中间不能包含点,而非常规账户名则可以少于12个字符并且可包含点,加入后缀。这种非常规账户的名称显然是稀有且具备个性的,因此EOS加入了这一部分的竞拍市场机制。该动作是由system系统合约的bidname完成。下面仍旧分析其源码实现。

/**

  • @brief 账户名拍卖
  • @param bidder 竞拍者
  • @param newname 标的账户名
  • @param bid 报价
    */
    void system_contract::bidname( name bidder, name newname, asset bid ) {
    require_auth( bidder ); // 校验竞拍者是否本人操作
    // 校验标的账户名是否符合高级后缀。
    eosio_assert( newname.suffix() == newname, “you can only bid on top-level suffix” );
    eosio_assert( (bool)newname, “the empty name is not a valid account name to bid on” );//校验标的是否为空
    eosio_assert( (newname.value & 0xFull) == 0, “13 character names are not valid account names to bid on” );//13个字符长度的标的不允许竞拍
    // 常规账户长度为12位且不包含点,只有非常规账户才可以参与竞拍,即小于12个字符的,或者包含点的。
    eosio_assert( (newname.value & 0x1F0ull) == 0, “accounts with 12 character names and no dots can be created without bidding required” );
    eosio_assert( !is_account( newname ), “account already exists” );// 校验标的账户是否已存在。
    eosio_assert( bid.symbol == core_symbol(), “asset must be system token” );// 校验报价资产必须是主币
    eosio_assert( bid.amount > 0, “insufficient bid” );// 校验报价必须正数
    // 经过以上重重校验,可以进行实际拍卖环节。首先发起转账,将竞拍报价从竞拍者手中转账到eosio.names账户(该账户主管名称拍卖)
    INLINE_ACTION_SENDER(eosio::token, transfer)(
    token_account, { {bidder, active_permission} },
    { bidder, names_account, bid, std::string("bid name ")+ newname.to_string() }
    );
    // 创建当前合约的name_bid_table状态表的实例bids
    name_bid_table bids(_self, _self.value);
    print( name{bidder}, " bid ", bid, " on ", name{newname}, “\n” );
    auto current = bids.find( newname.value ); // 先查询是否已存在该标的的历史报价数据
    if( current == bids.end() ) { // 如果不存在历史报价数据,则新建
    bids.emplace( bidder, [&]( auto& b ) { // 添加该标的的首单竞拍相关字段到状态表。
    b.newname = newname;
    b.high_bidder = bidder;
    b.high_bid = bid.amount;
    b.last_bid_time = current_time_point();
    });
    } else { // 如果已经存在历史报价数据,则处理该标的的最高报价
    // 历史最高报价high_bid已被置为负数,则说明该已成功交易,竞拍关闭。
    eosio_assert( current->high_bid > 0, “this auction has already closed” );
    // 此次新的报价必须高于该标的的历史最高报价的10%,这是竞拍规则。
    eosio_assert( bid.amount - current->high_bid > (current->high_bid / 10), “must increase bid by 10%” );
    // 如果该标的的当前最高报价已经是当前竞拍者本人,则不需要执行下面的逻辑。
    eosio_assert( current->high_bidder != bidder, “account is already highest bidder” );
    // 获得竞拍退款状态表big_refund_table的实例refunds_table,传入当前竞拍动作。
    bid_refund_table refunds_table(_self, newname.value);
    auto it = refunds_table.find( current->high_bidder.value );
    if ( it != refunds_table.end() ) {
    // 如果在竞拍退款表中找到当前竞拍价格相同的,则更新该条数据对象,增加退款金额为最高报价,以主币形式结算。
    refunds_table.modify( it, same_payer, [&](auto& r) {
    r.amount += asset( current->high_bid, core_symbol() );
    });
    } else {
    // 如果未找到相同最高报价的,则新增一条数据对象,插入当前最高报价者以及报价价格。
    refunds_table.emplace( bidder, [&](auto& r) {
    r.bidder = current->high_bidder;
    r.amount = asset( current->high_bid, core_symbol() );
    });
    }
    // 打包交易,插入bidrefund动作,传入最高报价者以及标的。
    transaction t;
    t.actions.emplace_back( permission_level{_self, active_permission},
    _self, “bidrefund”_n,
    std::make_tuple( current->high_bidder, newname )
    );
    t.delay_sec = 0;// 定义延迟时间
    // 定义延迟id
    uint128_t deferred_id = (uint128_t(newname.value) << 64) | current->high_bidder.value;
    cancel_deferred( deferred_id ); // 按延迟id取消延迟交易
    t.send( deferred_id, bidder ); // 发送延迟交易
    // 最后修改name_bid_table状态表的实例bids,将当前竞拍动作更新到该标的对象,包括最高报价者、最高报价以及时间。
    bids.modify( current, bidder, [&]( auto& b ) {
    b.high_bidder = bidder;
    b.high_bid = bid.amount;
    b.last_bid_time = current_time_point();
    });
    }
    }
    创建账户
    创建账户的操作一直都是由system合约的newaccount动作承担的,下面仍旧通过源码分析研究其逻辑。

/**

  • @brief 创建账户,包括资源管理以及名称竞拍的逻辑。
  • @param creator 创建者
  • @param newact 被创建的账户,如果包含点“.”,则其创建者也必须包含相同后缀。
  • @param owner owner权限
  • @param active active权限
    */
    void native::newaccount( name creator,
    name newact,
    ignore owner,
    ignore active ) {
    if( creator != _self ) { // 创建者不能是当前合约账户。
    uint64_t tmp = newact.value >> 4; // 将新账户名由字符转为无符号int
    bool has_dot = false;// 定义标志位,是否包含点“.”
    for( uint32_t i = 0; i < 12; ++i ) {// 遍历12次,因为名称最长12个字符
    has_dot |= !(tmp & 0x1f); // 检查是否有点“.”存在,同时还可以检查账户的长度是否少于12位,有则更新has_dot标志位为true。
    tmp >>= 5; // 移到下一位检查
    }
    if( has_dot ) { // 非常规账户
    auto suffix = newact.suffix(); // 后缀
    if( suffix == newact ) { // 创建者的后缀必须相同
    // 在竞拍状态表中寻找创建者拥有的非常规账户,是否包含待创建账户
    name_bid_table bids(_self, _self.value);
    auto current = bids.find( newact.value );
    eosio_assert( current != bids.end(), “no active bid for name”);
    // 校验当前待创建账户作为竞拍标的,其最高竞拍价是否是创建者报出的。
    eosio_assert( current->high_bidder == creator, “only highest bidder can claim” );
    // 如果high_bid字段不是负数,说明竞拍未结束,该非常规账户还不属于创建者。
    eosio_assert( current->high_bid < 0, “auction for name is not closed yet” );
    bids.erase( current ); // 通过以上校验,该竞拍标的属于创建者,创建者创建成功,删除标的历史对象。
    } else {
    eosio_assert( creator == suffix, “only suffix may create this account” );
    }
    }
    }
    // 为新用户分配资源,初始化添加到用户资源状态表
    user_resources_table userres( _self, newact.value);
    userres.emplace( newact, [&]( auto& res ) {
    res.owner = newact;
    res.net_weight = asset( 0, system_contract::get_core_symbol() );
    res.cpu_weight = asset( 0, system_contract::get_core_symbol() );
    });
    set_resource_limits( newact.value, 0, 0, 0 );
    }
    结束语
    感受过中医按摩的朋友应该比较了解,这种按摩手法讲究的是疏通经络,反复地从头到脚捋你的经络,直到老师傅认为你的经络通了,通了的表现就是整个人轻松了,气色红扑扑的。本文也又点中医按摩的意思,从头到脚,致力于将一条经络上出现的疙疙瘩瘩的小结揉碎吸收,希望最后达到整条经络通畅的目的。本文较长,适合心平气和之人亦或是查阅的朋友来看。

相关文章:

文字超过省略_从楚篆到楚玺的文字结构

从古文字研究的角度来说&#xff0c;楚玺文字也是楚文字系统中重要的组成部分。古文字发展演变的一般规律&#xff0c;如简化、繁化、异化、分化、类化等在印章上也有反映。在楚系简帛书没有大量出土发现和研究出版前&#xff0c;楚玺研究的文字参照物不多&#xff0c;主要是依…

caffe prototxt分析

测试用prototxt name: "CIFAR10_quick"layer {name: "data" type: "MemoryData" top: "data" top: "label" memory_data_param {batch_size: 1 #样本个数 channels: 3 height: 32 width: 32 }}layer {name: "conv1…

Mysql与Oracle区别

Mysql与Oracle区别 文章分类:数据库 周五去一家公司去面试&#xff0c;那公司经理问了关于Mysql与Oracle的区别问题&#xff0c;以前没有总结&#xff0c;回答也不是很好&#xff0c;只是凭感觉&#xff0c;先总结如下&#xff1a; 1. Oracle是大型数据库而Mysql是中小型数据库…

区块链 + 大数据:EOS存储

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 谈到区块链的存储&#xff0c;我们很容易联想到它的链式存储结构&#xff0c;然而区块链从比特币发展到今日当红的EOS&#xff0c;技术形态已经演化…

全网最全的Windows下Anaconda2 / Anaconda3里Python语言实现定时发送微信消息给好友或群里(图文详解)...

不多说&#xff0c;直接上干货&#xff01; 缘由&#xff1a; &#xff08;1&#xff09;最近看到情侣零点送祝福&#xff0c;感觉还是很浪漫的事情&#xff0c;相信有很多人熬夜为了给爱的人送上零点祝福&#xff0c;但是有时等着等着就睡着了或者时间并不是卡的那么准就有点强…

Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo

更新了xcode后使用goland运行项目时提示 Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo 更具提示打开xcode 点击agree安装即可&#xff01; 转载于:https://www.cnblogs.com/mafeng/p/6196494.html

arc diff 指定版本号_Phabricator客户端安装

前提需要配置好服务器端客户端安装mac环境下&#xff0c;指定一个目录$ mkdir somewhere/$ cd somewhere/somewhere/ $ git clone https://github.com/phacility/libphutil.gitsomewhere/ $ git clone https://github.com/phacility/arcanist.git配置环境变量&#xff0c;在.ba…

EOSIO 转帐详解

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 EOS和EOS的不同之处 在EOS网络中存在两种货币&#xff0c;一种是EOS&#xff0c;还有一种是EOS网络中的代币。说到这里大家似乎有点疑惑&#xff0…

各种函数调用约定及浮点数传参

32位下_stdcall, _fastcall, _cdecl #include <windows.h>int _stdcall Func1(int a, int b, int c, int d) {return abcd; } int _fastcall Func2(int a, int b, int c, int d) {return abcd; } int _cdecl Func3(int a, int b, int c, int d) {return a b c d; }…

cookie、session总结

前几天在调试第三方支付接口时碰到一个session失效问题&#xff0c;用了几天时间才搞明白&#xff0c;现在回想一下&#xff0c;主要还是由于cookie和session这一块的一些基本概念没有搞清楚&#xff0c;现总结一下。 浏览器使用HTTP协议作为应用层协议&#xff0c;而HTTP协议是…

glibc降级后怎么恢复 linux_Linux(CentOS)GLIBC出错补救方式

出于各种原因&#xff0c;我玩坏了我的系统.........主要出错原因是更改 /usr/lib64 下的 libc.so.6 等文件引起&#xff0c;具体错误及补救方式附上&#xff0c;希望可以帮到心里失火后来人&#xff1a;首先&#xff0c;不要随便重新启动&#xff01;&#xff01;&#xff01;…

将Eclipse代码导入到AndroidStudio的两种方式

实现步骤 1. 从Eclipse中导出Gradle build files 在Eclipse菜单中 File --> Export-->Generate Gradle build files接下来会到达警告界面&#xff0c;这里会提示AndroidStudio可以直接导入ADT的工程&#xff0c;先过&#xff0c;后面有直接导入的讲解。选中你的项目工程&…

微软浏览器适配问题前端_「图」微软新贡献:修复Chromium浏览器的奇怪触控板手势问题...

去年微软宣布计划成为Chromium项目的重要贡献者之一&#xff0c;希望为包括Edge和Chrome在内所有基于Chromium的浏览器带来更多改进和功能。在增强鼠标滚动和搜索功能之外&#xff0c;微软现在将部分精力放在部分Windows 10设备(例如Surface Pro系列和Surface Book系列)上奇怪的…

ruby gems列表

1 https://github.com/shageman/cobradeps 转载于:https://www.cnblogs.com/or2-/p/9268352.html

简明区块链原理

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 “区块链”应有特质&#xff1a; 使用了具有 “哈希链” (下文有解释) 形式的数据结构保存基础数据 有多个结点参与系统运行&#xff08;分布式…

Bash shell

一、认识bash shell 1、登录取得的shell就记录在/etc/passwd这个文件内 可以使用cat /etc/passwd查看 2、bash shell 功能 a. 命令记忆能力&#xff08;history&#xff09;&#xff0c;默认1000个&#xff0c;存在~/.bash_history文件 b. 命令与文件补全功能&#xff08;Tab键…

快过高铁!构建云分布式应用还能这样操作?!

先跟跟大家说一个中国历史上杰出的军事家、政治家&#xff0c;长长的胡子&#xff0c;红的发黑的脸&#xff0c;骑着一匹红色的马。没错&#xff01;他就是三国跑的最快的男人——曹操&#xff08;说曹操曹操到&#xff09;&#xff01; 不说笑了。 关羽&#xff0c;字云长&…

基于安卓的考试系统_基于安卓11定制!华为最新手机系统曝光:体验堪比苹果iOS!...

在最近的一场发布会上&#xff0c;华为正式宣布了自家的HMS和AppGallery服务&#xff0c;对标安卓Play商店和苹果Appstore商店&#xff0c;这一举措让华为再度登上风口浪尖。这种做法在业界人士眼里的目的只有一个&#xff0c;华为要脱离安卓系统自立门户&#xff0c;从建立第三…

区块链前世今生

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 比特币的起源 2008年&#xff0c;一位化名为中本聪的人&#xff0c;在一篇为《比特币&#xff1a;一个点对点的电子现金系统》的论文中首先提出了比…

前端知识之HTML内容

参考:http://www.cnblogs.com/liwenzhou/p/7988087.html HTML介绍 Web服务本质 import socketsk socket.socket()sk.bind(("127.0.0.1", 8080)) sk.listen(5)while True:conn, addr sk.accept()data conn.recv(8096)conn.send(b"HTTP/1.1 200 OK\r\n\r\n&qu…

征途linux mysql_MySql征途之mysql常用命令

mysql征程之mysql常用命令一、连接MySql语法&#xff1a; mysql -h 主机地址 -u 用户名 &#xff0d;p 用户密码例1&#xff1a;连接到本机上的MYSQL。键入命令mysql -u root -p(本地连接 主机地址可以不写)&#xff0c;回车后提示你输入密码&#xff0c;输入正确之后&#xff…

区块链+物联网=?

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 区块链与物联网(IoT)的交叉应用已成为最有前途的区块链用例之一。在过去的几个月里&#xff0c;IoTeX一直与我们的战略合作伙伴合作&#xff0c;并…

mysql 中文截取_mysql 截取中文字符

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云数据库专家保驾护航&#xff0c;为用户…

2016年度工作总结

一想起来今天全办公室人都在写年终总结的场景&#xff0c;不由自主的笑开了颜&#xff0c;因为我把一名程序媛的年终总结硬生生的写成了一篇“散文”&#xff0c;而且还是很“冒牌”的总结&#xff0c;以下就是“散文版”的总结。 在紧锣密鼓的业务GO推广上线期间&#xff0c;x…

django-后台sms管理系统的css框架

django-后台sms管理系统的css框架 地址&#xff1a;https://adminlte.io/ 下载代码。使用index.html的页面及相关文件 通过下在线检查adminlte.io的后台的各种模块元素&#xff0c;仿写。posted on 2018-07-06 11:41 .Tang 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.c…

go语言学习-iota

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 Go没有枚举类型,可以用常量模拟可以用iota生成从0 开始的自动增长的枚举值。按行递增,可以省略后续行的 iota 关键字. iota 在一个const()中每次累…

mysql中查询表格属性

&#xff08;1&#xff09;获取数据库表格列设置的长度&#xff0c;SQL SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS WHERE TABLE_NAME表名 AND TABLE_SCHEMA数据库名 AND COLUMN_NAME字段名 &#xff08;1&#xff09;查出数据库表格所有的属性 SELECT …

BZOJ 2190: [SDOI2008]仪仗队

2190: [SDOI2008]仪仗队 Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 2689 Solved: 1713[Submit][Status][Discuss]Description 作为体育委员&#xff0c;C君负责这次运动会仪仗队的训练。仪仗队是由学生组成的N * N的方阵&#xff0c;为了保证队伍在行进中整齐划一&…

mysql下载解压安装_mysql zip 解压安装

系统&#xff1a;win10 专业版mysql 5.7.21 解压安装。对于Windows&#xff0c;mysql官网推荐使用可执行文件进行安装&#xff0c;这里我还是暂时用noinstall 解压zip文件来安装从zip压缩包安装mysql的过程如下&#xff1a;1. 解压文档到指定目录2. 创建选项文件如果您在运行服…

go 性能相关总结

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 性能测试基本概念 基本概念 Benchmark: 性能测试 ns/op: 纳秒/每个操作&#xff0c;前面数值越小越快 命令 go test -c go test -test.bench. -t…