id:BSN_2021 公众号:BSN 研习社 作者:红枣科技高晨曦

背景:BSN公网Fabric联盟链的出现降低了使用区块链的难度,但在部分特定环境中,仍需要自己搭建Fabric环境时,了解Fabric中的配置信息能够帮助搭建或在运行中调整自己的链环境配置。

目标:了解账本数据结构中的配置信息,更好的维护自己的Fabric环境。

对象: 使用BSN联盟链Fabric的开发人员、运维人员。

什么是配置块

配置块是Fabric Channel的重要数据,里面包含了当前Channel的配置信息,配置块包含初始配置块,以及在链运行过程中的修改配置信息而提交的交易生成的配置区块信息。
它包含了当前channel的证书信息、策略信息、以及其他关键链配置信息。

如何查询配置块

在前一篇中我们了解到在区块的Metadata中包含了5组数据,其中第一组为Ordere签名信息,第二组即为当前channel的最后一个配置块的块号。其中Orderer签名信息中也包含最后一个配置块信息,
common.LastConfig序列化后的Bytes,我们可以查询最新的区块,得到最后一个配置块号,然后直接查询该块即可。

// OrdererBlockMetadata defines metadata that is set by the ordering service.
type OrdererBlockMetadata struct {LastConfig           *LastConfig `protobuf:"bytes,1,opt,name=last_config,json=lastConfig,proto3" json:"last_config,omitempty"`ConsenterMetadata    []byte      `protobuf:"bytes,2,opt,name=consenter_metadata,json=consenterMetadata,proto3" json:"consenter_metadata,omitempty"`
}// LastConfig is the encoded value for the Metadata message which is encoded in the LAST_CONFIGURATION block metadata index
type LastConfig struct {Index                uint64   `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"`
}

在查到配置块之后,我们解析区块内的交易信息,根据交易的ChannelHeader中的Type来判断该交易是否是配置信息。HeaderType包含以下类型:

const (HeaderType_MESSAGE              HeaderType = 0HeaderType_CONFIG               HeaderType = 1HeaderType_CONFIG_UPDATE        HeaderType = 2HeaderType_ENDORSER_TRANSACTION HeaderType = 3HeaderType_ORDERER_TRANSACTION  HeaderType = 4HeaderType_DELIVER_SEEK_INFO    HeaderType = 5HeaderType_CHAINCODE_PACKAGE    HeaderType = 6
)

其中HeaderType_CONFIGHeaderType_CONFIG_UPDATE是配置块交易类型。

配置块包含哪些数据

当交易配型为HeaderType_CONFIG或者HeaderType_CONFIG_UPDATE时,Envelope的data即为ConfigEnvelope序列化之后的Bytes,
以下为proto中的ConfigEnvelope结构

// ConfigEnvelope is designed to contain _all_ configuration for a chain with no dependency
// on previous configuration transactions.
//
// It is generated with the following scheme:
//   1. Retrieve the existing configuration
//   2. Note the config properties (ConfigValue, ConfigPolicy, ConfigGroup) to be modified
//   3. Add any intermediate ConfigGroups to the ConfigUpdate.read_set (sparsely)
//   4. Add any additional desired dependencies to ConfigUpdate.read_set (sparsely)
//   5. Modify the config properties, incrementing each version by 1, set them in the ConfigUpdate.write_set
//      Note: any element not modified but specified should already be in the read_set, so may be specified sparsely
//   6. Create ConfigUpdate message and marshal it into ConfigUpdateEnvelope.update and encode the required signatures
//     a) Each signature is of type ConfigSignature
//     b) The ConfigSignature signature is over the concatenation of signature_header and the ConfigUpdate bytes (which includes a ChainHeader)
//   5. Submit new Config for ordering in Envelope signed by submitter
//     a) The Envelope Payload has data set to the marshaled ConfigEnvelope
//     b) The Envelope Payload has a header of type Header.Type.CONFIG_UPDATE
//
// The configuration manager will verify:
//   1. All items in the read_set exist at the read versions
//   2. All items in the write_set at a different version than, or not in, the read_set have been appropriately signed according to their mod_policy
//   3. The new configuration satisfies the ConfigSchema
type ConfigEnvelope struct {Config               *Config   `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`LastUpdate           *Envelope `protobuf:"bytes,2,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"`
}// Config represents the config for a particular channel
type Config struct {Sequence             uint64       `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"`ChannelGroup         *ConfigGroup `protobuf:"bytes,2,opt,name=channel_group,json=channelGroup,proto3" json:"channel_group,omitempty"`
}// ConfigGroup is the hierarchical data structure for holding config
type ConfigGroup struct {Version              uint64                   `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`Groups               map[string]*ConfigGroup  `protobuf:"bytes,2,rep,name=groups,proto3" json:"groups,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`Values               map[string]*ConfigValue  `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`Policies             map[string]*ConfigPolicy `protobuf:"bytes,4,rep,name=policies,proto3" json:"policies,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`ModPolicy            string                   `protobuf:"bytes,5,opt,name=mod_policy,json=modPolicy,proto3" json:"mod_policy,omitempty"`
}

其中ConfigEnvelope.Config对象为当前块的配置信息,LastUpdate为最后一个的配置更新在下面的配置更新中会讲到它。
首先来看一下Config对象,其中ConfigGroup中存储的即为当前Channel的主要配置,它与通过cryptogen命令初始化网络时的configtx.yaml配置文件中的项相对应,主要包含以下几项:

Version

配置的版本号,每次更新配置,Version将加1

Groups

Groups 中为下级配置项,依然是ConfigGroup对象,通常包含以下几种:
Consortiums:联盟配置,包含整个网络的组织配置信息,一般出现在genesischannel中,包含整个网络的联盟配置以及各个联盟下的组织信息配置
Orderer: Orderer组织的配置信息,一般出现在genesischannel中,它包含整个链中的各个Orderer组织配置信息
Application: 应用channel的配置信息,一般出现在应用channel中,他包含了应用channel中的各个组织的配置信息。

Values

// ConfigValue represents an individual piece of config data
type ConfigValue struct {Version              uint64   `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`Value                []byte   `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`ModPolicy            string   `protobuf:"bytes,3,opt,name=mod_policy,json=modPolicy,proto3" json:"mod_policy,omitempty"`
}

Values 中存放的是链的一些基本配置信息,为ConfigValue对象存储,它通常包含链的配置信息等。
例如链的证书信息,节点信息,区块配置参数等
BatchSize举例,在configtx.yaml中,我们通常这么配置,

#写入区块内的交易大小
BatchSize:#消息的最大个数MaxMessageCount: 10000#交易的最大字节数,任何时候均不能超过AbsoluteMaxBytes: 98 MB #批量交易的建议字节数PreferredMaxBytes: 10 MB

在配置块中为

"values": {"BatchSize": {"mod_policy": "Admins","value": {"absolute_max_bytes": 102760448,"max_message_count": 10000,"preferred_max_bytes": 10485760},"version": "0"}
}

Policies

type ConfigPolicy struct {Version              uint64   `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`Policy               *Policy  `protobuf:"bytes,2,opt,name=policy,proto3" json:"policy,omitempty"`ModPolicy            string   `protobuf:"bytes,3,opt,name=mod_policy,json=modPolicy,proto3" json:"mod_policy,omitempty"`
}// Policy expresses a policy which the orderer can evaluate, because there has been some desire expressed to support
// multiple policy engines, this is typed as a oneof for now
type Policy struct {Type                 int32    `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"`Value                []byte   `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
}

Policies 中链的读写策略,一般包含ReadersWritersAdmins等,使用ConfigPolicy对象存储。
它在链的配置中示例如下:

Policies: &OrgbNodeOrgPoliciesReaders:Type: SignatureRule: "OR('OrgbNodeMSP.admin', 'OrgbNodeMSP.peer', 'OrgbNodeMSP.client')"Writers:Type: SignatureRule: "OR('OrgbNodeMSP.admin', 'OrgbNodeMSP.client')"Admins:Type: SignatureRule: "OR('OrgbNodeMSP.admin')"

如何修改配置块

当我们需要修改一个channel的配置时,例如增加一个组织,修改读写其策略,或者需要在channel中吊销某个用户的证书时,需要根据当前的配置信息,以及修改后的配置信息生成修改配置的Envelope,签名之后,提交给Orderer完成配置的修改。

在Fabric的源码中为我们提供了生成ConfigUpdate的方法,它在github.com/hyperledger/fabric/internal/configtxlator/update包中,可以根据当前的配置和修改后的配置生成提交修改的配置更新项。它包含了我们对配置修改的读写集合。

func Compute(original, updated *cb.Config) (*cb.ConfigUpdate, error) {if original.ChannelGroup == nil {return nil, fmt.Errorf("no channel group included for original config")}if updated.ChannelGroup == nil {return nil, fmt.Errorf("no channel group included for updated config")}readSet, writeSet, groupUpdated := computeGroupUpdate(original.ChannelGroup, updated.ChannelGroup)if !groupUpdated {return nil, fmt.Errorf("no differences detected between original and updated config")}return &cb.ConfigUpdate{ReadSet:  readSet,WriteSet: writeSet,}, nil
}

根据生成的ConfigUpdate,可以参考下面的代码生成Envelope对象,提交至Orderer后即可完成对配置的修改。

 updt, err := update.Compute(&cb.Config{ChannelGroup: original}, &cb.Config{ChannelGroup: updated})if err != nil {return errors.WithMessage(err, "could not compute update")}updt.ChannelId = channelIDnewConfigUpdateEnv := &cb.ConfigUpdateEnvelope{ConfigUpdate: protoutil.MarshalOrPanic(updt),}updateTx, err := protoutil.CreateSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, channelID, nil, newConfigUpdateEnv, 0, 0)

配置块示例

本次的示例是一个转换为json格式的配置块信息,删除了过长的证书信息和重复的组织信息,保留了基本的配置字段,以供参考。

{"data": {"data": [{"payload": {"data": {"config": {"channel_group": {"groups": {"Consortiums": {"groups": {"SampleConsortium": {"groups": {"OrgaNodeMSP": {"groups": {},"mod_policy": "Admins","policies": {"Admins": {"mod_policy": "Admins","policy": {"type": 1,"value": {"identities": [{"principal": {"msp_identifier": "OrgaNodeMSP","role": "ADMIN"},"principal_classification": "ROLE"}],"rule": {"n_out_of": {"n": 1,"rules": [{"signed_by": 0}]}},"version": 0}},"version": "0"},"Readers": {},"Writers": {}},"values": {"MSP": {"mod_policy": "Admins","value": {"config": {"admins": ["LS0 ... 0tLS0K==="],"crypto_config": {"identity_identifier_hash_function": "SHA256","signature_hash_family": "SHA2"},"fabric_node_ous": {"admin_ou_identifier": null,"client_ou_identifier": {"certificate": "LS0 ... 0tLS0K=","organizational_unit_identifier": "client"},"enable": true,"orderer_ou_identifier": null,"peer_ou_identifier": {"certificate": "LS0 ... 0tLS0K=","organizational_unit_identifier": "peer"}},"intermediate_certs": ["LS0 ... 0tLS0K=","LS0 ... 0tLS0K="],"name": "OrgaNodeMSP","organizational_unit_identifiers": [],"revocation_list": [],"root_certs": ["LS0 ... 0tLS0K=="],"signing_identity": null,"tls_intermediate_certs": [],"tls_root_certs": []},"type": 0},"version": "0"}},"version": "0"}},"mod_policy": "/Channel/Orderer/Admins","policies": {},"values": {"ChannelCreationPolicy": {"mod_policy": "/Channel/Orderer/Admins","value": {"type": 3,"value": {"rule": "ANY","sub_policy": "Admins"}},"version": "0"}},"version": "2"}},"mod_policy": "/Channel/Orderer/Admins","policies": {"Admins": {"mod_policy": "/Channel/Orderer/Admins","policy": {"type": 1,"value": {"identities": [],"rule": {"n_out_of": {"n": 0,"rules": []}},"version": 0}},"version": "0"}},"values": {},"version": "0"},"Orderer": {"groups": {"OrdererMSP": {"groups": {},"mod_policy": "Admins","policies": {"Admins": {"mod_policy": "Admins","policy": {"type": 1,"value": {"identities": [{"principal": {"msp_identifier": "OrdererMSP","role": "ADMIN"},"principal_classification": "ROLE"}],"rule": {"n_out_of": {"n": 1,"rules": [{"signed_by": 0}]}},"version": 0}},"version": "0"},"Readers": { },"Writers": { }},"values": {"MSP": {"mod_policy": "Admins","value": {"config": {"admins": ["LS0 ... 0tLS0K"],"crypto_config": {"identity_identifier_hash_function": "SHA256","signature_hash_family": "SHA2"},"fabric_node_ous": null,"intermediate_certs": ["LS0 ... 0tLS0K","LS0 ... 0tLS0K"],"name": "OrdererMSP","organizational_unit_identifiers": [],"revocation_list": [],"root_certs": ["LS0t ... S0tLQo="],"signing_identity": null,"tls_intermediate_certs": ["LS0 ... 0tLS0K","LS0 ... 0tLS0K"],"tls_root_certs": ["LS0 ... 0tLS0K"]},"type": 0},"version": "0"}},"version": "0"}},"mod_policy": "Admins","policies": {"Admins": {"mod_policy": "Admins","policy": {"type": 3,"value": {"rule": "ANY","sub_policy": "Admins"}},"version": "0"},"BlockValidation": {"mod_policy": "Admins","policy": {"type": 3,"value": {"rule": "ANY","sub_policy": "Writers"}},"version": "0"},"Readers": {"mod_policy": "Admins","policy": {"type": 3,"value": {"rule": "ANY","sub_policy": "Readers"}},"version": "0"},"Writers": {"mod_policy": "Admins","policy": {"type": 3,"value": {"rule": "ANY","sub_policy": "Writers"}},"version": "0"}},"values": {"BatchSize": {"mod_policy": "Admins","value": {"absolute_max_bytes": 102760448,"max_message_count": 10000,"preferred_max_bytes": 10485760},"version": "0"},"BatchTimeout": {"mod_policy": "Admins","value": {"timeout": "1s"},"version": "0"},"Capabilities": {"mod_policy": "Admins","value": {"capabilities": {"V1_4_2": {}}},"version": "0"},"ChannelRestrictions": {"mod_policy": "Admins","value": null,"version": "0"},"ConsensusType": {"mod_policy": "Admins","value": {"metadata": {"consenters": [{"client_tls_cert": "LS0 ... 0tLS0K=","host": "order1.ordernode.bsnbase.com","port": 17051,"server_tls_cert": "LS0 ... 0tLS0K="},{"client_tls_cert": "LS0 ... 0tLS0K=","host": "order2.ordernode.bsnbase.com","port": 17052,"server_tls_cert": "LS0 ... 0tLS0K="},{"client_tls_cert": "LS0 ... 0tLS0K=","host": "order3.ordernode.bsnbase.com","port": 17053,"server_tls_cert": "LS0 ... 0tLS0K="}],"options": {"election_tick": 10,"heartbeat_tick": 1,"max_inflight_blocks": 5,"snapshot_interval_size": 200,"tick_interval": "500ms"}},"state": "STATE_NORMAL","type": "etcdraft"},"version": "0"}},"version": "0"}},"mod_policy": "Admins","policies": {"Admins": {"mod_policy": "Admins","policy": {"type": 3,"value": {"rule": "ANY","sub_policy": "Admins"}},"version": "0"},"Readers": {"mod_policy": "Admins","policy": {"type": 3,"value": {"rule": "ANY","sub_policy": "Readers"}},"version": "0"},"Writers": {"mod_policy": "Admins","policy": {"type": 3,"value": {"rule": "ANY","sub_policy": "Writers"}},"version": "0"}},"values": {"BlockDataHashingStructure": {"mod_policy": "Admins","value": {"width": 4294967295},"version": "0"},"Capabilities": {"mod_policy": "Admins","value": {"capabilities": {"V1_4_2": {}}},"version": "0"},"HashingAlgorithm": {"mod_policy": "Admins","value": {"name": "SHA256"},"version": "0"},"OrdererAddresses": {"mod_policy": "/Channel/Orderer/Admins","value": {"addresses": ["order1.ordernode.bsnbase.com:17051"]},"version": "0"}},"version": "0"},"sequence": "2"},"last_update": {"payload": {"data": {"config_update": {"channel_id": "genesischannel","isolated_data": {},"read_set": {"groups": {},"mod_policy": "","policies": {},"values": {},"version": "0"},"write_set": {"groups": { },"mod_policy": "","policies": {},"values": {},"version": "0"}},"signatures": [{"signature": "MEUCIQChKw0GLbCI3Mg3oN14hw25ETgzZOMGOycuPQYwbAEulgIgIxihwa1x/5/mRhEqSUatBPafbyYzlPNRRdA0syYYALA=","signature_header": {"creator": {"id_bytes": "LS0 ... 0tLS0K","mspid": "OrdererMSP"},"nonce": "4UQFkmPE3ajSubYgeSp8TwBHqQQ7Jq3+"}}]},"header": {"channel_header": {"channel_id": "genesischannel","epoch": "0","extension": null,"timestamp": "2021-11-11T05:42:43Z","tls_cert_hash": null,"tx_id": "","type": 2,"version": 0},"signature_header": {"creator": {"id_bytes": "LS0 ... 0tLS0K","mspid": "OrdererMSP"},"nonce": "XKHFVcxjbdp/Jp+TwPpKvtPw7wogD27H"}}},"signature": "MEUCIQDn3u47EfAjbqJjqkwks+bB4gCuyDcmLEhUurVeyJqcjQIgIW7/28nFgAyNBXYEtMC/qZjfriL7xCEijLuRlHqRJuo="}},"header": {"channel_header": {"channel_id": "genesischannel","epoch": "0","extension": null,"timestamp": "2021-11-11T05:42:43Z","tls_cert_hash": null,"tx_id": "","type": 1,"version": 0},"signature_header": {"creator": {"id_bytes": "LS0 ... 0tLS0K=","mspid": "OrdererMSP"},"nonce": "B/92hIoLno1ewPDddf9EmDfYMGdSO668"}}},"signature": "MEUCIQCpSlOWFD+fBCqFk8DVw4m+qkLwspqe0vGqmqh6tv/OlQIgatIGPHJzhmdPzbwU7zPy9e+KtpghTdGiFpyi4UqpqBo="}]},"header": {"data_hash": "mgHIsx3RqeeybaKDYmetYvypDqVxKSB5O0YzqwnG/IM=","number": "34","previous_hash": "I5IMydQTa5njnHojXNnsDdHLlDkF/RlPIsO48iUI4cc="},"metadata": {"metadata": ["ChIK ... kJZ+j9Pr","CgIIIg==","","CgoKAwECAxAEGIwb",""]}
}

Fabric 账本数据块结构解析(二):如何解析账本中的配置块数据相关推荐

  1. numpy使用[]语法索引二维numpy数组中倒数N列数据列的数值内容(accessing the last N columns in numpy array)

    numpy使用[]语法索引二维numpy数组中倒数N列数据列的数值内容(accessing the last N columns in numpy array) 目录

  2. postgresql 插入 时间戳_数据也玩躲猫猫?PostgreSQL中别人提交的数据,我为什么看不到?...

    原创: Aken DB印象 文章链接:https://mp.weixin.qq.com/s/OkJaWbzcXcJtzSCOFnqeXQ 文章作为DB的学习体会,若有错误欢迎指导. 一.环境介绍 操作 ...

  3. Arcgis javascript那些事儿(二十)——dojo中djconfig配置、dojo与requirejs项目冲突

    一.引言 由于项目一部分使用requirejs另一部分地图是用dojo开发(因为arcgis javascript使用的dojo),两个要和到一起,所以要求研究下如何把两者和到一起,花了两天时间看了看 ...

  4. python抓取表格数据_Python如何实现从PDF文件中爬取表格数据(代码示例)

    本篇文章给大家带来的内容是关于Python如何实现从PDF文件中爬取表格数据(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 本文将展示一个稍微不一样点的爬虫. 以往我们的 ...

  5. mysql重命名数据表称方式_在MySQL中,使用()重命名数据表。_学小易找答案

    [单选题]( )的上海文坛被称为"张爱玲年". [多选题]下列哪些是属于共集放大电路的特点?() [阅读理解]Passage Two Thailand is to ban smok ...

  6. 从hbitmap中获取位图数据_如何快速从主流数据库中获取人/小鼠数据?

    点击上方"蓝色字体"关注我们 鹿明 生物 蛋白.代谢组学服务专家 关注我们收获更多 关注 随着生物科技的迅速发展,每天都会有海量的生物学数据产生,如何有效的分析这些"生物 ...

  7. exce中让两列数据一一对应_EXCEL表格如何匹配两列数据一样-EXCEL让两个表格中的两列数据一一对应...

    怎样把excel中两列有部分相同的数据进行匹配? 1.首先打开excel表格,可以看到有两列数据需要匹配,找出列B中在列A中没有的数据. 2.然后在C1单元格内输入函数公式:=IF(ISNA(VLOO ...

  8. java中的数据解析是_Java从网络中请求获取JSon数据以及解析JSON数据----(自创,请注明)...

    Json数据是比较常用的数据类型解析,优点就不多说啦.来看看方法: public static JSONObject getJsonObject(String url) { JSONObject js ...

  9. vue数据改变渲染问题_解决Vue中页面成功渲染数据undefined的问题

    前言 这个标题不太好取. 本文需要下面的知识:https://zhuanlan.zhihu.com/p/260811233​zhuanlan.zhihu.com 问题描述 我最近的一个功能需求是通过a ...

最新文章

  1. linux 存储映射lun 给_如何在 Linux 上扫描/检测新的 LUN 和 SCSI 磁盘 | Linux 中国
  2. iOS学习资源(二)
  3. windows API 创建系统托盘图标
  4. BZOJ3075[USACO 2013 Mar Gold 3.Necklace]——AC自动机+DP
  5. JavaScript 验证API
  6. [ActionScript 3.0] NetConnection建立客户端与服务器的双向连接
  7. CSS text-indent 属性
  8. ecplice中class.forname一直报错_A6v5.1升级A6v7.0报错:调用Java代码
  9. 项目架构中遇到需考虑的问题
  10. Dying In The Sun
  11. 机器学习基础算法11-Logistic回归-ROC和AUC分类模型评估-实例
  12. Java配置文件读取写入通用类库:PropUtils 属性文件类
  13. 【C++拾遗之二】fseek、ftell函数读取文件
  14. xmind思维导图怎么把字体变大_XMind 使用指南 | 让思维导图放大你的影响力
  15. math: 四元数与欧拉角(RPY角)的相互转换
  16. Docker(感谢狂神)
  17. daimayuan每日一题#849 国家铁路
  18. java调用第三方天气预报API接口
  19. DCGAN生成动漫头像(附代码)
  20. 安装vs2015_community()社区版+win10,安装之后,打开项目显示不兼容,应用程序未能正确安装

热门文章

  1. 私人订制版微信红包封面(赠送红包封面)
  2. AE学习笔记——第五章:效果预设和渲染导出
  3. python做手机应用宝下载_20行Python代码爬取下载应用宝所有APP软件
  4. r7 5800h核显性能
  5. 华为官方鸿蒙系统发布会,手机显示hd是什么意思_手机显示hd有什么影响_手机hd怎么关闭...
  6. 为自动化测试装上精准测试的“翅膀
  7. 备战S4!想上钻石必须注意的20个细节
  8. 远程连接windows server 2008 上的mysql
  9. Unraid安装KMS Docker激活Windows+Office
  10. 枪火重生灵界狂潮攻略(七)猴子流派