您正在查看: EOS-新手教程 分类下的文章

ScatterDesktop Backup

BackService 调用 saveFile

export default class BackupService {

    static async setBackupStrategy(strategy){
        const scatter = store.state.scatter.clone();
        scatter.settings.autoBackup = strategy;
        return store.dispatch(Actions.SET_SCATTER, scatter);
    }

    static async createBackup(){
        const location = getFolderLocation();
        if(! location) return false;

        await saveFile(location[0]);
    }

    static async setBackupLocation(){
        const location = getFolderLocation();
        if(!location) return false;
        const scatter = store.state.scatter.clone();
        scatter.settings.backupLocation = location[0];
        return store.dispatch(Actions.SET_SCATTER, scatter);
    }

    static async createAutoBackup(){
        if(!store.state.scatter || !store.state.scatter.settings) return;
        const strategy = store.state.scatter.settings.autoBackup;
        if(!strategy || !strategy.length || strategy === BACKUP_STRATEGIES.MANUAL) return;

        const backupLocation = store.state.scatter.settings.backupLocation;
        if(!backupLocation || !backupLocation.length) return false;


        await saveFile(backupLocation);
    }

}

写入备份文件

https://sourcegraph.com/github.com/GetScatter/ScatterDesktop@318ebfef6e383c6d2ca9d5016199b2b12a82c1a4/-/blob/src/services/BackupService.js#L12:18

const saveFile = (filepath) => {
    return new Promise(resolve => {
        const scatter = getLatestScatter();
        const date = new Date();
        const month = date.getUTCMonth();
        const year = date.getUTCFullYear();
        const salt = StorageService.getSalt();
        const file = scatter + '|SLT|' + salt;
        const name = `${filepath}/scatter__${store.state.scatter.hash.substr(0,4)}-${store.state.scatter.hash.slice(-4)}__${store.state.scatter.meta.version}__${month}-${year}.json`;
        try {
            fs.writeFileSync(name, file, 'utf-8');
            resolve(true);
        }
        catch(e) {
            console.error('Error saving file', e);
            resolve(false);
        }
    })
};

备份文件的内容

const scatter = getLatestScatter();
const salt = StorageService.getSalt();
const file = scatter + '|SLT|' + salt;
const getLatestScatter = () => StorageService.getScatter();

StorageService

https://sourcegraph.com/github.com/GetScatter/ScatterDesktop@318ebfef6e383c6d2ca9d5016199b2b12a82c1a4/-/blob/src/services/StorageService.js#L93

export default class StorageService {

    constructor(){}

    static async setScatter(scatter){
        return new Promise(async resolve => {
            clearSaveTimeouts();
            saveResolvers.push(resolve);
            safeSetScatter(scatter, resolve);
        })
    };

    static getScatter() {
        return scatterStorage().get('scatter');
    }

actions

https://sourcegraph.com/github.com/GetScatter/ScatterDesktop@318ebfef6e383c6d2ca9d5016199b2b12a82c1a4/-/blob/src/store/actions.js#L91

[Actions.SET_SCATTER]:async ({commit, state}, scatter) => {
        return new Promise(async resolve => {
            const process = Process.savingData();

            const seed = await ipcAsync('seed');
            const savable = AES.encrypt(scatter.savable(seed), seed);
            StorageService.setLocalScatter(savable);
            process.updateProgress(50);
            StorageService.setScatter(savable).then(() => {
                BackupService.createAutoBackup()
            });

            commit(Actions.SET_SCATTER, scatter);
            resolve(scatter);
            process.updateProgress(100);
        })
    },

其中的

const seed = await ipcAsync('seed');

是从

 [Actions.SET_SEED]:({commit}, password) => {
        return new Promise(async (resolve, reject) => {
            const [mnemonic, seed] = await PasswordService.seedPassword(password, true);
            resolve(mnemonic);
        })
    },

https://sourcegraph.com/github.com/GetScatter/ScatterDesktop@318ebfef6e383c6d2ca9d5016199b2b12a82c1a4/-/blob/src/services/PasswordService.js#L48

static async seedPassword(password, setToState = true){
        return new Promise(async (resolve, reject) => {
            try {
                let seed, mnemonic;
                if(password.split(' ').length >= 12) {
                    seed = await Mnemonic.mnemonicToSeed(password);
                    mnemonic = password;
                } else {
                    const [m, s] = await Mnemonic.generateMnemonic(password);
                    seed = s;
                    mnemonic = m;
                }

                if(setToState) ipcFaF('seeding', seed);
                resolve([mnemonic, seed]);
            } catch(e){
                resolve([null, null]);
            }
        })
    }

Scatter

https://sourcegraph.com/github.com/GetScatter/ScatterDesktop/-/blob/src/models/Scatter.js#L76:1

savable(seed){
        this.keychain.keypairs.map(keypair => keypair.encrypt(seed));

        const clone = this.clone();
        clone.keychain.identities.map(id => id.encrypt(seed));

        // Keychain is always stored encrypted.
        clone.encrypt(seed);

        return clone;
    }

Keychain

https://sourcegraph.com/github.com/GetScatter/ScatterDesktop@318ebfef6e383c6d2ca9d5016199b2b12a82c1a4/-/blob/src/models/Keychain.js#L20:1

static fromJson(json){
        let p = Object.assign(this.placeholder(), json);
        if(json.hasOwnProperty('keypairs')) p.keypairs = json.keypairs.map(x => Keypair.fromJson(x));
        if(json.hasOwnProperty('accounts')) p.accounts = json.accounts.map(x => Account.fromJson(x));
        if(json.hasOwnProperty('identities')) p.identities = json.identities.map(x => Identity.fromJson(x));
        if(json.hasOwnProperty('permissions')) p.permissions = json.permissions.map(x => Permission.fromJson(x));
        if(json.hasOwnProperty('apps')) p.apps = json.apps.map(x => AuthorizedApp.fromJson(x));
        return p;
    }

nodeos 输出log方式

https://github.com/EOSIO/fc/blob/a6f94bae0b2b4a0b68dc1db2677331cafa4716a5/src/log/logger_config.cpp#L24

 bool configure_logging( const logging_config& cfg )
   {
      try {
      static bool reg_console_appender = appender::register_appender<console_appender>( "console" );
      static bool reg_gelf_appender = appender::register_appender<gelf_appender>( "gelf" );
      get_logger_map().clear();
      get_appender_map().clear();

支持输出到 console 和 gelf 两种方式

gelf
https://sourcegraph.com/github.com/EOSIO/fc@a6f94bae0b2b4a0b68dc1db2677331cafa4716a5/-/blob/include/fc/log/gelf_appender.hpp

namespace fc 
{
  // Log appender that sends log messages in JSON format over UDP
  // https://www.graylog2.org/resources/gelf/specification
  class gelf_appender final : public appender 
  {
  public:
    struct config 
    {
      string endpoint = "127.0.0.1:12201";
      string host = "fc"; // the name of the host, source or application that sent this message (just passed through to GELF server)
    };

类似 https://github.com/mattwcole/gelf-extensions-logging

BOS节点部署,版本升级与测试

昨晚BOSCore已经完成了3秒LIB测试网启动,测试结果是1-3秒内达到不可逆,今天要再进行下中国社区部分的测试。

社区通知消息原文如下

国内社区 3s LIB版本升级演练

BOS 3s LIB测试网已经启动,为了让更多社区成员了解升级流程和特性,中国社区定于北京时间5月15日16:00开始演练,请大家提前做好准备。

此次版本升级的流程:
1.首先启动链时用boscore/bos:v2.0.3版本启动,各位注册bp,出块,模拟现在BOS的网络环境。
2.升级
   1)各个bp升级自己的节点版本至新版本,大多数bp升级好后进行下一步
   2)bp多签升级系统合约
   3)bp多签设置网络升级的块高度:target_number
  当lib到达设置的块高度时,共识变为pbft共识,3s可进lib,网络完成升级。

此外,邀请社区开发者对昨晚的lib-testnet做公开测试,发现问题者可以在github上提出issue,最终会根据大家提出的issue奖励BOS,最高奖励 1000 BOS 欢迎大家踊跃参与进来!!!可以加入:https://t.me/BOSTestnet

会议链接:
主题:BOS中国社区3s LIB版本升级演练
时间:五月 15, 2019 4:00 下午 北京,上海
加入 Zoom 会议
https://zoom.us/j/353729204

大家可以提前将自己的bp名字和公钥发给我,网络启动时帮大家创建好。

前段时间一直在忙,没有参与,只是默默围观,今天单独抽出时间参与下。
从技术方面来说,如果此方案稳定可行的话,对当前公司项目用户体验,可以有极大的提升。

环境准备

节点搭建对比EOS的来说,基本没差别,我们简单的记录下

准备个简单配置的测试机器

  • CPU: 4核
  • RAM: 8G
  • 地点: 香港阿里云

下载和编译代码

下载代码

git clone https://github.com/boscore/bos.git
git checkout -b v2.0.3

编译

cd bos/
git submodule update --init --recursive
./eosio_build.sh -s BOS

安装

sudo ./eosio_install.sh

配置节点

先运行下nodeos 初始化创建config.ini

配置config.ini

增加 p2p-peer-address

p2p-peer-address = 47.75.48.230:5016  #abp
p2p-peer-address = 47.75.107.217:2022
p2p-peer-address = 47.75.48.230:2018  #abp
p2p-peer-address = 3.1.39.190:10772 #blockedencom
p2p-peer-address = 47.52.101.176:9476 #itokenpocket
p2p-peer-address = 47.75.48.230:2018  #abp
p2p-peer-address = 47.75.48.230:5016  #abp
p2p-peer-address = 47.75.107.217:2022 # saylovetomom
p2p-peer-address = 15.164.99.58:9876 #starteosiobp

其余配置参考《从零开始,纯净机器上部署EOS测试网 (版本v1.4.1)》

创建genesis.json

{
  "initial_timestamp": "2019-05-15T00:30:00.000",
  "initial_key": "EOS6YFfrYZ3z7bNnzoZF4V2zJStudMJ4NJBBE1JXGzebVzMG9aLc7",
  "initial_configuration": {
    "max_block_net_usage": 1048576,
    "target_block_net_usage_pct": 1000,
    "max_transaction_net_usage": 524288,
    "base_per_transaction_net_usage": 12,
    "net_usage_leeway": 500,
    "context_free_discount_net_usage_num": 20,
    "context_free_discount_net_usage_den": 100,
    "max_block_cpu_usage": 200000,
    "target_block_cpu_usage_pct": 1000,
    "max_transaction_cpu_usage": 150000,
    "min_transaction_cpu_usage": 100,
    "max_transaction_lifetime": 3600,
    "deferred_trx_expiration_window": 600,
    "max_transaction_delay": 3888000,
    "max_inline_action_size": 4096,
    "max_inline_action_depth": 4,
    "max_authority_depth": 6
  }
}

模拟现在BOS的网络环境

此时运行nodeos --genesis-json 加上面genesis.json的路径,启动节点,此时能正常接收其他节点的出块。
注册BP节点,并被投票,成为出块节点

升级节点,更新共识协议

升级节点到最新版本

当前最新版本v3.0.0-rc3

git checkout -b v3.0.0-rc3

同上,编译,并安装,重新启动。

升级系统合约

新版本的系统合约:https://github.com/boscore/bos.contracts

cleos -u http://47.75.48.230:5015 set contract eosio ./eosio.system/ -p eosio -s -j -d > updatasystem.json
cleos -u http://47.75.48.230:5015 multisig propose_trx updatesystem bppermission1.json updatesystem.json bosstorebest
cleos -u http://47.75.48.230:5015 multisig approve bosstorebest updatesystem  '{"actor":"bosstorebest","permission":"active"}' -p bosstorebest
cleos -u http://47.75.48.230:5015 multisig exec bosstorebest updatesystem -p bosstorebest@active
cleos -u http://47.75.48.230:5015 multisig review bosstorebest updatesystem
cleos -u http://47.75.48.230:5015 get table eosio.msig bosstorebest approvals2

设置升级块高度提案

cleos -u http://47.75.48.230:5015   push action eosio setupgrade '{"up":{"target_block_num":21000}}' -p eosio  -s -j -d > setnum.json
cleos -u http://47.75.48.230:5015  multisig propose_trx setnum  bppermission1.json   setnum.json bosstorebest
cleos  -u http://47.75.48.230:5015  multisig approve  bosstorebest setnum  '{"actor":"bosstorebest","permission":"active"}'  -p  bosstorebest
cleos  -u http://47.75.48.230:5015  multisig exec bosstorebest setnum -p bosstorebest@active
查询设置的升级块高度
cleos -u http://47.75.48.230:5015 get table eosio eosio upgrade
{
  "rows": [{
      "target_block_num": 21000
    }
  ],
  "more": false
}

设置块高度时

达到设定的高度时输出以下log

表示升级成功了。

查询区块块信息

surou@DESKTOP-444S803:/mnt/c/Users/Surou$ cleos -u http://47.75.48.230:5015 get info
{
  "server_version": "82de16b9",
  "chain_id": "5c70c434cfd0ae8a2fde509fd452f8f64236ac120ba81631b239fb4bd1b1e2c3",
  "head_block_num": 29732,
  "last_irreversible_block_num": 29731,
  "last_irreversible_block_id": "000074234261f645187eae528b720577235d5bd306efb6a11906976c725e5583",
  "head_block_id": "00007424d2c9aecdf90f1e123c4ffbe4c30e14ce9428cffa18c70bda981de290",
  "head_block_time": "2019-05-15T10:02:49.000",
  "head_block_producer": "bospacificbp",
  "current_view": 0,
  "target_view": 1,
  "last_stable_checkpoint_block_num": 29701,
  "virtual_block_cpu_limit": 200000000,
  "virtual_block_net_limit": 1048576000,
  "block_cpu_limit": 199900,
  "block_net_limit": 1048576,
  "server_version_string": "v3.0.0-rc2-21-g82de16b98"
}

最新区块与不可逆块数,相差1块,此时不可逆确认时间间隔为0.5s

参考

常见问题

特别感谢 shiqi@eostore小妹妹补充的信息。

EOS Block Data Structure

EOS Block Data Structure

整体数据结构定义图

我们先来看一张EOS整体的数据结构定义图

从这个图上可以看出来,EOS在区块数据结构的定义上并不是特别复杂.

Block 定义

// ..../eos/libraries/chain/include/eosio/chain/block.hpp
struct signed_block : public signed_block_header {
   using signed_block_header::signed_block_header;
   signed_block() = default;
   signed_block( const signed_block_header& h ):signed_block_header(h){}

   // 交易集合
   vector<transaction_receipt>   transactions;
   extensions_type               block_extensions;
};
using signed_block_ptr = std::shared_ptr<signed_block>;

// ..../eos/libraries/chain/include/eosio/chain/types.hpp
typedef vector<std::pair<uint16_t,vector<char>>> extensions_type;

Block的定义使用的是signed_block struct,这个struct是从signed_block_header继承而来的,在这个结构体中的关键部分是包含了Transaction的vector。区块是由按顺序组织的交易来构成的集合。

block_extensions则定义了一系列的扩展信息,这些信息都由一个整数类型的code来定义,需要的时候,都可以根据这个整数code来解析相应的信息。

在这个结构体中包含的transaction都是使用transaction_receipt结构体,这个结构体又是从transaction_receipt_header继承而来,下面我们看看这个两个struct的定义。

// ..../eos/libraries/chain/include/eosio/chain/block.hpp
struct transaction_receipt_header {
   // 定义交易状态的枚举类型
   enum status_enum {
      // 这个表示执行成功(所以不需要执行出错逻辑)
      executed  = 0,
      // 客观的来说,执行失败了(或者没有执行),某一个出错逻辑执行了
      soft_fail = 1,
      // 执行失败了,并且执行的出错逻辑也失败了,所以并没有状态改变
      hard_fail = 2,
      // 交易被延迟了,计划到未来的某个时间执行
      delayed   = 3,
      // 交易过期了,并且存储空间返还给用户
      expired   = 4  ///< transaction expired and storage space refuned to user
   };

   // 状态数据
   fc::enum_type<uint8_t,status_enum>   status;
   // CPU使用情况
   uint32_t                             cpu_usage_us;
   // 网络使用情况
   fc::unsigned_int                     net_usage_words;
};

struct transaction_receipt : public transaction_receipt_header {
   fc::static_variant<transaction_id_type, packed_transaction> trx;
};

transaction_receipt结构体主要包含了一个打包过的交易以及其对应的交易类型。其parent struct transaction_receipt_header则主要是记录了这个交易的状态信息,以及CPU和网络的使用情况。当一笔交易被某个区块引用时,区块生产者针对这笔交易会做出相应的操作,而操作的不同结果会导致这笔交易的不同状态.

packed_transaction,顾名思义,就是把交易数据打包了,这个结构体里面还定义了,打包数据是否经过了压缩的标识信息,

// ..../eos/libraries/chain/include/eosio/chain/transaction.hpp
struct packed_transaction {
   // 定义打包数据是否压缩的枚举类型
   enum compression_type {
      // 没有压缩
      none = 0,
      // 使用zlib压缩
      zlib = 1,
   };

   // 签名信息
   vector<signature_type>                  signatures;
   // 是否压缩的标识信息
   fc::enum_type<uint8_t,compression_type> compression;
   // 上下文无关的信息
   bytes                                   packed_context_free_data;
   // 打包后的交易数据
   bytes                                   packed_trx;
}

packed_transaction中打包的数据来自于signed_transaction结构体,这个结构体的主要作用就是对交易做签名。

signed_transaction又是从transaction结构体继承而来,一个transaction结构体的实例包含一系列的action,这些action要么全部成功,要么全部失败。

交易ID是通过对交易内容本身经过Hash运算得出,所以每个交易的ID是与其内容一一对应的。交易的主体是由操作构成的。一个交易在纳入区块之前必须含有签名,用以验证交易的合法性。

延迟型交易

交易分为两种类型:一种是账户发起的普通交易,一种是由代码生成的自动交易,自动交易可以设置一个延迟时间,这样的交易叫延迟型交易,这种交易不会立即被执行,而是等到设定时间到时才会被执行。

// ..../eos/libraries/chain/include/eosio/chain/transaction.hpp
struct signed_transaction : public transaction
{
    // 签名信息
     vector<signature_type>    signatures;
     // 上下文无关的数据
     vector<bytes>             context_free_data;
};

struct transaction : public transaction_header {
     // 上下文无关的action
     vector<action>         context_free_actions;
     // 交易操作
     vector<action>         actions;
     // 交易扩展类型
     extensions_type        transaction_extensions;
}

transaction_header结构体包含了与每一个交易相关联的固定大小的数据,这些数据从具体的交易数据中分离出来,可以在需要的时候,帮助解析交易数据,而不再需要更多的动态内存分配。

所有的交易都有一个期限,这个期限限定了一个交易必须在规定时间内被纳入区块链,如果我们发现一个交易的时限已经过去,就可以放心的放弃这个交易,因为所有生产者都不会将它纳入任何区块。

// ..../eos/libraries/chain/include/eosio/chain/transaction.hpp
struct transaction_header {
     // 这个交易的过期时间
     time_point_sec         expiration;
     // 在最后的2^16 blocks中指定一个具体的block number
     uint16_t               ref_block_num       = 0U;
     // 在指定的get_ref_blocknum的blockid中取低位的32bit
     uint32_t               ref_block_prefix    = 0UL;
     // 最大的网络带块
     fc::unsigned_int       max_net_usage_words = 0UL;
     // 最大的CPU使用
     uint8_t                max_cpu_usage_ms    = 0;
     // 这个交易的延期时间
     fc::unsigned_int       delay_sec           = 0UL;
}

signed_block结构体是从signed_block_header继承而来的,这个signed_block_header结构体只是包含了一条数据,那就是producer的签名。

signed_block_header结构体又是从block_header继承而来的,这个结构体就包含了一个block中很多重要的数据,包括,时间戳,producer的名字,所有交易的merkle root,所有action的root等信息。

// ..../eos/libraries/chain/include/eosio/chain/block_header.hpp
struct block_header
{
     block_timestamp_type             timestamp;
     account_name                     producer;
     uint16_t                         confirmed = 1;  

     block_id_type                    previous;

     checksum256_type                 transaction_mroot;
     checksum256_type                 action_mroot;

     uint32_t                          schedule_version = 0;
     optional<producer_schedule_type>  new_producers;
     extensions_type                   header_extensions;
};

struct signed_block_header : public block_header
{
     signature_type    producer_signature;
};

Action(操作)

在前面的transaction结构体中,我们看到包含了有action,这里我们对action做一下说明。

我们先看看action的数据结构定义:

// ..../eos/contracts/eosiolib/action.hpp
struct action {
   // 账户:操作的来源
   account_name               account;
   // 名称:操作的标识
   action_name                name;
   // 授权:执行操作的许可列表
   vector<permission_level>   authorization;
   // 数据:执行操作需要用到的信息
   bytes                      data;
}

EOS区块链中的交易是由一个个操作(action)组成的,操作可以理解成一个能够更改区块链全局状态的方法,操作的顺序是确定的,一个交易内的操作要么全部执行成功,要么都不执行,这与交易的本意是一致的。操作是区块链的最底层逻辑,相当于区块链这个大脑的神经元,区块链的智能最终也是通过一个个操作的组合来实现的。

操作的设计原则

  • 独立原则 操作本身须包含足以解释操作含义的信息,而不需要依赖区块链提供的上下文信息来帮助解释。所以,即便一个操作的当前状态可以通过区块链上的数据推导得出,我们也需要将状态信息纳入操作数据中,以便每个操作是容易理解的。这个原则体现的是区块的可解释性,这一点非常重要,这个底层的设计原则将影响整个区块链的使用效率
  • 余额可计算原则 一个账户当前的余额计算,仅仅依赖于与这个账户相关的信息便可得出,而不需要解析整个区块链才能获得。这个原则针对的是比特币的设计,由于比特币的余额计算需要扫描区块链中的所有交易才能精准的计算出一个账户的余额,这使得一个非常基础的计算落地起来都变得相当繁琐,EOS的这个设计目的在于提升运算效率。
  • 明确费用原则 区块链的交易费用随时间变化而变化,所以,一个签名过的交易须明确的认同这个交易所需要支付的费用,这个费用是在交易形成之前就已经设定并且明确好了的,这一点也非常重要,因为明确的费用协议才能保证余额的正确计算。
  • 明确授权原则 每个操作须包含足够的授权信息以标明是哪一个账户拥有授权这个操作的权力,这种明确授权的设计思路带来几个好处:
    • 便于集中管理
    • 可以优化授权管理
    • 便于并行处理
  • 关联账户原则 每个操作须包含足够的关联账户信息,以保证这个操作能够遍历所有相关联的账户,也就是这个操作能够影响的所有账户,这个原则的目的同样是为了确保账户的余额能够得到及时和准确的运算

操作的来源

一个操作可以通过两种途径产生:

  • 由一个账号产生,通过签名来授权,即显性方式。
  • 由代码生成,即隐形方式。

操作的设计遵循React Flux设计模式,就是每一个操作将会被赋予一个名称,然后被分发给一个或者多个handler。在EOS环境中,每个操作对应的handler是通过scope和name来定义的,默认的handler也可以再次将操作分发给下一级的多个handler。所以,每个EOS应用可以实现自己的handler,当操作被分发到这个应用时,相应的handler的代码就会被执行。

操作的设计思路中另一重要概念是授权。每一个操作的执行都必须确保具备了指定账户的授权。授权通过许可(permission)的方式声明,对于一个授权动作,账户可以要求任意数量的许可,许可的验证是独立于代码执行的,只有所有规定的许可被成功验证之后,对应的代码才能够被执行。安全特性深深的嵌入了区块链的底层设计逻辑,同时又不让安全机制成为性能和结构的累赘,让它自成体系,独立管理。

区块的存储 - 区块日志

区块日志是存储区块的二进制文件,区块日志的特性是只能从末尾追加(append only),区块日志包含两类文件:

区块文件,结构如下:

+---------+----------------+---------+----------------+-----+------------+-------------------+

| Block 1 | Pos of Block 1 | Block 2 | Pos of Block 2 | ... | Head Block | Pos of Head Block |

+---------+----------------+---------+----------------+-----+------------+-------------------+

区块文件包含区块的内容以及每个区块的位置信息。区块位置信息是固定的8字节宽度,这样便于在连续读取区块的时候,按照读一个区块,向后跳跃8个字节,读一个区块,向后跳跃8个字节的模式快速加载区块内容。

索引文件,结构如下:

+----------------+----------------+-----+-------------------+

| Pos of Block 1 | Pos of Block 2 | ... | Pos of Head Block |

+----------------+----------------+-----+-------------------+

区块索引的目的在于提供一个基于区块序号的快速随机搜索方法,使用索引文件可以快速定位目标区块在区块文件中的具体位置。索引文件不是必须的,没有索引文件区块链仍然可以运行,索引文件的主要作用是通过少量空间换取速度提升。索引文件可以通过顺序读取区块文件来重新构建。

转载自:https://github.com/XChainLab/documentation/blob/master/eos/eos.block.data.structure.md