教程区块链区块链技术第15章 联盟链:Hyperledger Fabric

本页目录

联盟链范式:Hyperledger Fabric 的架构、开发网络、链码生命周期、SDK 后端与私有数据——理解企业区块链在可信多方间的性能、隐私与治理平衡。

本章目录:

  • 15.1 联盟链的骨架:Fabric 架构与核心组件
  • 15.2 搭建 Fabric 开发网络与测试环境
  • 15.3 链码开发:资产溯源
  • 15.4 链码生命周期管理:升级不中断业务
  • 15.5 Fabric SDK:Node.js 后端服务开发
  • 15.6 设计 REST API 与前端界面
  • 15.7 权限控制与私有数据集合

15.1 联盟链的骨架:Fabric 架构与核心组件

核心问题:当企业或联盟组织之间需要在保护数据隐私的前提下共享账本数据,且要求参与者身份可审计时,公链的完全匿名与开放准入模式显然不适用。Hyperledger Fabric 正是为解决这一场景而生的联盟链框架。

本章 15.1 节从角色体系通道隔离身份管理(MSP)架构总览四个层面,先搭建 Fabc 的整体认知框架。


15.1.1 角色分离:不再是"对等全节点"

与公链将所有节点视为"对等全节点"不同,Fabric 对网络中的节点做了精细的角色分离。五种角色各司其职:

角色职责现实类比
---------------------
Client提交交易提案、接收区块事件用户/应用后端
Endorsing Peer模拟执行链码、签名背书结果合规审查员
Ordering Service (Orderer)交易全局排序并打包成区块排序员/打包者
Committing Peer验证区块并写入状态数据库审计员/记录员
MSP (Membership Service Provider)身份签发、证书验证、角色映射护照管理局

设计意图:角色分离使 Fabric 能在不牺牲安全性的前提下大幅提升性能——背书节点专注执行、排序节点专注排序、提交节点专注验证,三者并行流水线工作。

Node.js 类型的 TypeScript 里,我们可以用接口把这个角色模型"数据化"——不依赖任何外部库,只描述角色、入网资格(MSP)与订单路由的约束:

typescript

// Fabric 角色模型简化建模:纯 TS,无外部依赖

type MSPRole = "client" | "peer" | "orderer";

interface FabricIdentity {

  mspId: string;        // 如 Org1MSP

  org: string;          // 如 org1.example.com

  role: MSPRole;

  subject: string;      // X.509 subject 简化

}

interface FabricNode {

  id: string;

  identity: FabricIdentity;

  // 通道成员资格:node 属于哪些 channel

  channels: Set<string>;

  // 是否为认可(endorsing)节点

  isEndorsingPeer: boolean;

}

interface OrdererCluster {

  // 生产环境 etcdraft/Raft 的多节点排序集群

  orderers: FabricNode[];

}

// 用幂等加法做索引:将角色、组织编码为复合键

function nodeKey(node: FabricNode): bigint {

  let h = 31n;

  const str = `node.identity.mspId{node.identity.mspId}|{node.identity.org}|${node.identity.role}`;

  for (let i = 0; i < str.length; i++) {

    h = h * 131n + BigInt(str.charCodeAt(i));

  }

  return h;

}

// 校验一个节点是否有资格参与某通道:MSP 必须匹配、必须已加入通道

function canParticipate(node: FabricNode, channelName: string): boolean {

  return node.channels.has(channelName) && node.identity.role !== "orderer" || nodeChannelsForOrderer(node, channelName);

}

function nodeChannelsForOrderer(node: FabricNode, channelName: string): boolean {

  // orderer 不归属业务通道成员资格,但为所有通道提供排序

  return node.identity.role === "orderer";

}

/** ===== 磁盘层 ===== */

interface RuntimeNodeDirectory {

  isEndorsing(node: FabricNode, channel: string): boolean;

  orderersFor(channel: string): FabricNode[];

}

class InMemoryNodeDirectory implements RuntimeNodeDirectory {

  private nodes: FabricNode[] = [];

  add(node: FabricNode): void {

    this.nodes.push(node);

  }

  isEndorsing(node: FabricNode, channel: string): boolean {

    return node.channels.has(channel) && node.isEndorsingPeer;

  }

  orderersFor(_channel: string): FabricNode[] {

    return this.nodes.filter((n) => n.identity.role === "orderer");

  }

}

// 演示:Org1/Org2 各两个 peer + Orderer 集群

const org1Peer0: FabricNode = {

  id: "peer0.org1",

  identity: { mspId: "Org1MSP", org: "org1.example.com", role: "peer", subject: "CN=peer0.org1" },

  channels: new Set(["channelA", "channelB"]),

  isEndorsingPeer: true,

};

const orderer1: FabricNode = {

  id: "orderer.example.com",

  identity: { mspId: "OrdererMSP", org: "example.com", role: "orderer", subject: "CN=orderer" },

  channels: new Set(),

  isEndorsingPeer: false,

};

const dir = new InMemoryNodeDirectory();

dir.add(org1Peer0);

dir.add(orderer1);

// 验证:peer0.org1 可在 channelA 背书;orderer 为 channelA 排序

console.log("peer0.org1 endorsing on channelA:", dir.isEndorsing(org1Peer0, "channelA"));

console.log("orderers for channelA:", dir.orderersFor("channelA").map((o) => o.id));

console.log("nodeKey(peer0.org1) =", nodeKey(org1Peer0).toString());

15.1.2 通道与账本隔离:数据隐私的第一道墙

通道(Channel) 是 Fabric 实现数据隐私隔离的核心机制:

  • 每个通道维护独立的账本独立的状态数据库(LevelDB 或 CouchDB);
  • 通道内的节点共享该通道的账本数据,通道之间完全隔离
  • 链码在通道内实例化,只有加入该通道的组织才能调用该链码。

私有数据集合(Private Data Collection) 进一步细化隐私控制:部分敏感字段(如汽车交易价格)仅对特定组织可见,但其哈希值上链以供篡改验证。

用 TypeScript 模拟"多通道多账本"的隔离结构:

typescript

// 通道与账本隔离:每个 channel 独立 ledger + 独立 state db

type StateValue = string;

class StateDatabase {

  private data = new Map<string, StateValue>();

  get(key: string): StateValue | undefined {

    return this.data.get(key);

  }

  put(key: string, value: StateValue): void {

    this.data.set(key, value);

  }

  isEmpty(): boolean {

    return this.data.size === 0;

  }

}

class Ledger {

  private blocks: string[] = []; // block hash 序列(简化)

  commit(blockHash: string): void {

    this.blocks.push(blockHash);

  }

  height(): number {

    return this.blocks.length;

  }

}

class Channel {

  readonly name: string;

  readonly ledger: Ledger = new Ledger();

  readonly stateDb: StateDatabase = new StateDatabase();

  private members = new Set<string>(); // 成员节点 id

  constructor(name: string) {

    this.name = name;

  }

  join(nodeId: string): void {

    this.members.add(nodeId);

  }

  isMember(nodeId: string): boolean {

    return this.members.has(nodeId);

  }

}

// 两个通道,账本与状态库完全独立

const channelA = new Channel("channelA");

const channelB = new Channel("channelB");

channelA.join("peer0.org1");

channelA.join("peer0.org2");

channelB.join("peer0.org2");

channelA.ledger.commit("0xblock_a1");

channelA.stateDb.put("car:WBA001", '{"owner":"Alice","price":"HASH_ONLY"}');

// channelB 看不到 channelA 的状态 —— 隔离验证

console.log("channelB state empty:", channelB.stateDb.isEmpty());

console.log("channelA height:", channelA.ledger.height());

console.log("peer0.org1 in channelA:", channelA.isMember("peer0.org1"));
graph TB

    subgraph "Org1"

        P1["peer0.org1"]

        P2["peer1.org1"]

    end

    subgraph "Org2"

        P3["peer0.org2"]

        P4["peer1.org2"]

    end

    subgraph "Orderer 集群"

        O1["orderer.example.com"]

    end

    subgraph "ChannelA"

        CHA["Channel A 账本"]

    end

    subgraph "ChannelB"

        CHB["Channel B 账本"]

    end

    P1 --- CHA

    P2 --- CHA

    P3 --- CHA

    P3 --- CHB

    P4 --- CHB

    P1 & P2 & P3 & P4 ---|交易排序| O1

    O1 ---|广播区块| P1 & P2 & P3 & P4

15.1.3 三阶段交易流程:Endorse-Order-Validate

Fabric 最关键的架构创新在于将交易拆分为三个独立阶段,其数学形式可抽象为三个函数的复合:

Validate(Order(iEndorsei(proposal)))\boxed{\texttt{Validate}\Big(\texttt{Order}\big(\bigcup_{i}\texttt{Endorse}_i(\texttt{proposal})\big)\Big)}

即:多个背书 Peer 对同一提案独立模拟执行得到读写集并签名 \rightarrow Orderer 对交易集合做全局排序 \rightarrow Committing Peer 验证背书策略与 MVCC 冲突后提交。

sequenceDiagram

    participant C as Client

    participant EP as Endorsing Peer(s)

    participant O as Orderer

    participant CP as Committing Peer

    C->>EP: 1. 提交交易提案(Proposal)

    EP->>EP: 2. 模拟执行链码,生成读写集(RWSet)

    EP-->>C: 3. 返回背书签名 + RWSet

    C->>O: 4. 提交背书后的交易(含RWSet+签名)

    O->>O: 5. 排序交易,打包区块

    O-->>CP: 6. 广播区块

    CP->>CP: 7. 验证背书策略 + MVCC冲突检查

    CP->>CP: 8. 提交至账本并更新状态库

    CP-->>C: 9. 发送区块事件通知

三个阶段详解:

  1. 提案阶段:Client 构造交易提案(调用链码函数 + 参数),发送给 Endorsing Peer(数量由背书策略决定)。
  2. 背书阶段:Endorsing Peer 模拟执行链码(不写入状态),生成读写集(Read-Write Set)并签名返回给 Client。
  3. 排序与验证阶段:Client 收集到足够背书签名后提交给 Orderer;Orderer 打包成区块广播给所有 Committing Peer;后者验证背书策略与 MVCC 冲突后写入账本。

这种设计确保即使 Orderer 被攻破,也无法伪造交易内容(因为背书签名不可伪造);即使某些 Peer 作恶,也无法篡改已确认的区块。

15.1.4 与公链的差异对比

维度Hyperledger Fabric以太坊/比特币
---------------------------------------
网络准入许可制(MSP 身份认证)无许可(任何人可加入)
身份基于组织与角色(可审计)匿名地址
代币无原生代币有原生代币(ETH/BTC)
出块确定性即时确定性概率性(需要后续区块确认)
交易排序无挖矿,Orderer 集群排序PoW/PoS 竞争出块
智能合约语言Go/Java/Node.jsSolidity/Vyper
性能数千~万级 TPSBTC ~7 TPS / ETH 15~30 TPS
监管友好是(身份可追踪)否(匿名性)

本节要点

  • Fabric 通过角色分离(Peer/Orderer/MSP)实现企业级性能与隐私需求:执行、排序、验证三段独立,流水线并行。
  • 通道机制提供账本级数据隔离,私有数据集合提供字段级隐私控制(明文仅授权组织可见,哈希上链)。
  • 三阶段交易流程(Endorse-Order-Validate)是 Fabric 区别于公链的核心架构创新——共识被拆解、内容不被排序服务感知。

下一节我们将进入链码(Chaincode)开发:Go 语言接口、背书策略与其数学表达。


15.2 搭建 Fabric 开发网络与测试环境

Fabric 不会从零搭建——官方提供了 test-network 脚本,一键生成 2 组织 + 1 orderer 的完整网络。理解这些脚本背后的身份、创世块和通道配置,是生产部署的基础。


15.2.1 单命令启动

bash

## 下载 fabric-samples

wget https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/bootstrap.sh

bash bootstrap.sh -s  # 下载二进制工具和样本代码

## 进入测试网络

cd fabric-samples/test-network

## 一键启动两个组织 + 1 个 orderer 的通道网络

./network.sh up createChannel

## 输出:

## [+] Creating org1, org2 identities

## [+] Starting orderer.example.com

## [+] Creating channel 'mychannel' ...

## [+] Joining org1, org2 peers to 'mychannel'

`network.sh` 背后发生了什么?

graph LR

    S1[cryptogen / CA] --> S2["生成 MSP 身份"]

    S2 --> S3["每个组织:admin + peers + users"]

    S3 --> S4[configtxgen]

    S4 --> S5["创世块 genesis.block"]

    S4 --> S6["通道配置 channel.tx"]

    S5 --> S7["Orderer 启动"]

    S6 --> S8["通道创建 + 加入"]

    

    style S1 fill:#e3f2fd

    style S7 fill:#c8e6c9

工具链

  • cryptogen:批量生成固定的 X.509 证书(开发和快速测试)
  • Fabric-CA:材料服务的 CA 服务,用于生产环境的动态注册
  • configtxgen:从 configtx.yaml 生成创世块和签名交易

15.2.2 核心配置文件

yaml

## configtx.yaml 关键片段演示

capabilities:

  channel: V2_0  # 通道级别能力

  orderer: V2_0  # orderer 级别

  application: V2_0  # 应用级别

organizations:

  - &Org1

    name: Org1MSP

    mspdir: crypto-config/peerOrganizations/org1.example.com/msp

    policies: ...

  - &Org2

    name: Org2MSP

    mspdir: crypto-config/peerOrganizations/org2.example.com/msp

profiles:

  TwoOrgsOrdererGenesis:  # 创世块配置

    orderer:

      ordererType: etcdraft

      organizations: [*Org1, *Org2]

      

  TwoOrgsChannel:  # 通道配置

    consortiums:

      - SampleConsortium

        organizations: [*Org1, *Org2]

    application:

      organizations: [*Org1, *Org2]

      # 默认背书策略:需要两个组织的任一 peer 背书

      policies:

        Endorsement:

          rule: "MAJORITY Endorsement"

15.2.3 容器架构

text

docker-compose 启动的容器群:

- orderer.example.com        (1 个)

- peer0.org1.example.com     (Org1)

- peer0.org2.example.com     (Org2)

- ca_org1, ca_org2           (可选 CA)

- couchdb0, couchdb1         (状态数据库,可选)

- cli                        (命令行交互容器)

每个 peer 有两个存储:

  • 文件系统(LevelDB/CouchDB):当前世界状态的键值存储
  • 区块文件:不可变的排序后的区块序列
graph TB

    subgraph Peer0["peer0.org1"]

        P1["提交/提交容器"]

        P2["区块文件 /var/hyperledger/fabric/ledgers"]

        P3["状态数据库 LevelDB / CouchDB"]

        P4["背书状态"]

    end

    

    P1 --> P2

    P1 --> P3

    P1 --> P4

    

    style P2 fill:#ffebee

    style P3 fill:#e8f5e9

15.2.4 身份层次

graph TD

    Root["Root CA 证书"] --> CA1[org1.example.com CA]

    Root --> CA2[org2.example.com CA]

    

    CA1 --> Admin1[Org1 Admin]

    CA1 --> Peer1[Org1 Peer]

    CA1 --> User1[Org1 User1]

    

    CA2 --> Admin2[Org2 Admin]

    CA2 --> Peer2[Org2 Peer]

    CA2 --> User2[Org2 User2]

    

    style Root fill:#fff3e0

    style Admin1 fill:#e8f5e9

    style Admin2 fill:#e8f5e9

15.2.5 关闭与清理

bash

./network.sh down

## 删除容器、删除加密材料、删除通道

生产环境不会使用 test-network,但理解其结构是部署生产网络的基石:

  • 生产上你会用 Fabric-CA 动态注册身份(而非 cryptogen 静态文件)
  • 生产上你会部署 etcd-raft 的 orderer 集群(Raft BFT 共识)
  • 生产上你会用外部 CouchDB/LevelDB 集群,而非嵌入式


15.3 链码开发:资产溯源

链码就是 Fabric 的智能合约。与公链不同,链码运行在 Docker 容器中,有明确的生命周期管理,且支持多种语言(Go、Java、JavaScript)。Go 是生产首选。


15.3.1 链码接口与状态管理

链码必须实现两个接口:

  • Init:通道实例化时调用,初始化账本
  • Invoke:每个交易调用的入口
go

// Chaincode 接口(简化,展示核心思路)

type Chaincode interface {

    Init(stub ChaincodeStubInterface) PeerResponse

    Invoke(stub ChaincodeStubInterface) PeerResponse

}

状态 API

graph LR

    Get[GetState] --> |读取| World["世界状态数据库"]

    Put[PutState] --> |写入| World

    Del[DelState] --> |删除| World

    GetHistory --> |查询| World

    

    style World fill:#e8f5e9

15.3.2 资产溯源链码(Go 骨架)

go

// asset_transfer.go

package main

import (

	"encoding/json"

	"fmt"

	"github.com/hyperledger/fabric-contract-api-go/contractapi"

)

// 资产数据结构

type Asset struct {

	DocType     string `json:"docType"`     // 区分记录类型

	ID          string `json:"id"`          // VIN 码

	Color       string `json:"color"`

	Size        int    `json:"size"`

	Owner       string `json:"owner"`

	AppraisedValue int `json:"appraisedValue"` // 认证价值

	Manufacturer  string `json:"manufacturer"` // 生产商

	ProductionDate string `json:"productionDate"`

}

// 智能合约类

type SmartContract struct {

	contractapi.Contract

}

// 初始化账本(5 辆汽车示例)

func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {

	assets := []Asset{

		{ID: "asset1", Color: "blue", Size: 5, Owner: "Tom", AppraisedValue: 3000, Manufacturer: "Toyota", ProductionDate: "2022-01-15"},

		{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 4000, Manufacturer: "Honda", ProductionDate: "2021-11-20"},

		// ... 更多

	}

	

	for _, a := range assets {

		assetJSON, _ := json.Marshal(a)

		// 写入世界状态:键 = 资产ID,值 = JSON 字节

		err := ctx.GetStub().PutState(a.ID, assetJSON)

		if err != nil {

			return fmt.Errorf("添加资产 %s 失败: %v", a.ID, err)

		}

	}

	return nil

}

// 创建资产

func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, 

	id, color, owner string, size, value int) error {

	

	// 检查是否已存在

	exists, _ := ctx.GetStub().GetState(id)

	if exists != nil {

		return fmt.Errorf("资产 %s 已存在", id)

	}

	

	asset := Asset{

		DocType:   "asset",

		ID:        id,

		Color:     color,

		Size:      size,

		Owner:     owner,

		AppraisedValue: value,

		Manufacturer: "Factory-1",

		ProductionDate: "2024-01-01",

	}

	

	assetJSON, _ := json.Marshal(asset)

	return ctx.GetStub().PutState(id, assetJSON)

}

// 读取资产

func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {

	assetJSON, err := ctx.GetStub().GetState(id)

	if err != nil || assetJSON == nil {

		return nil, fmt.Errorf("资产 %s 不存在", id)

	}

	var asset Asset

	err = json.Unmarshal(assetJSON, &asset)

	return &asset, err

}

// 转移所有权

func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, 

	id string, newOwner string) error {

	

	asset, err := s.ReadAsset(ctx, id)

	if err != nil {

		return err

	}

	asset.Owner = newOwner

	assetJSON, _ := json.Marshal(asset)

	return ctx.GetStub().PutState(id, assetJSON)

}

// 查询某人的所有资产

func (s *SmartContract) QueryAssetsByOwner(ctx contractapi.TransactionContextInterface, 

	owner string) ([]*Asset, error) {

	

	// 在链码中简单遍历(仅小规模数据)

	// 大规模需配合 CouchDB 富查询

	results := []*Asset{}

	

	// 使用 GetStateByRange 或 Rich Query

	// 注意:遍历全状态范围在生产中性能差

	// 生产上使用 CouchDB 的 JSON 查询

	

	return results, nil

}

func main() {

	chaincode, _ := contractapi.NewChaincode(&SmartContract{})

	chaincode.Start()

}

15.3.3 背书策略

必须理解:部署时指定

bash

## 安装到两个组织的 peer

peer chaincode install -n assettransfer -v 1.0 -p ./

## 批准(每个组织执行一次)

peer lifecycle chaincode approveformyorg \

  -o orderer.example.com:7050 \

  --channelID mychannel \

  --name assettransfer \

  --version 1.0 \

  --package-id $PACKAGE_ID \

  --sequence 1 \

  --signature-policy "OR('Org1MSP.peer', 'Org2MSP.peer')"

  # 即:组织1或组织2的任一peer背书即可

## 提交(任意一个组织提交,当足够多组织已批准)

peer lifecycle chaincode commit ...

策略语法

策略含义示例
------------------
AND(a, b)a 和 b 都必须双组织必须
OR(a, b)a 或 b 任一任一组织
NOutOf(n, [a,b,c...])N 个中的 n 个2/3 多签
MSP.peer该 MSP 的 peer'Org1MSP.peer'

15.3.4 与公链合约的对照

维度Fabric 链码公链合约 (Solidity)
----------------------------------------
确定性编写者保证编程模型内建(gas限制)
共识执行-排序-验证执行+排序同时 + 验证
升级支持(版本管理)需代理模式
隐私通道隔离 + 私有集合全公开
语言Go/Java/JSSolidity/Vyper/Rust
初始化Init (显式)constructor (显式)
确定性保障开发者责任EVM 架构保障


15.4 链码生命周期管理:升级不中断业务

Fabric 链码可以不停机升级,这是其 RABC(Release Application Business Continuity)体系的一部分。版本管理通过 sequence 号approval 流程实现治理。


15.4.1 链码事务生命周期

graph TD

    A["Pack 打包"] --> B["Install 安装到 peer"]

    B --> C["Approve 各组织批准"]

    C --> D[Check Commit Readiness]

    D --> E["Commit 提交到通道"]

    E --> F["Invoke 调用"]

    C -.升级.-> A

    

    style A fill:#e3f2fd

    style E fill:#c8e6c9

15.4.2 升级演练

bash

## 1. 打包新版本的链码

peer chaincode package assettransfer_v2.tar.gz -p ./ -l golang

## 2. 在所有组织的 peer 上安装

peer chaincode install assettransfer_v2.tar.gz

## 3. 各组织批准新版本(sequence 增加)

peer lifecycle chaincode approveformyorg \

  --name assettransfer \

  --version 2.0 \                    # 语义版本

  --package-id $NEW_PACKAGE_ID \

  --sequence 2 \                     # 序列号必须递增

  --channelID mychannel \

  --signature-policy "OR('Org1MSP.peer','Org2MSP.peer')"

## 4. 检查足够多组织已批准

peer lifecycle chaincode checkcommitreadiness \

  --channelID mychannel \

  --name assettransfer \

  --version 2.0 \

  --sequence 2

## 5. 提交升级(任何组织可以执行)

peer lifecycle chaincode commit \

  --name assettransfer \

  --version 2.0 \

  --sequence 2

关键的兼容性检查

检查项规则
--------------
Sequence + 1必须递增
Package ID新代码包哈希
Version人读的语义版本(v2.0)
Organization 背书需要各组织显式 approveformyorg

15.4.3 状态兼容性

升级时世界状态保留。新链码必须兼容旧状态数据结构:

go

// 升级后的 Asset 结构可以添加新字段

// 但必须兼容旧 JSON

// 策略:新版本读取旧数据时填充默认值

type AssetV2 struct {

    // 原有字段保留

    ID          string `json:"id"`

    Color       string `json:"color"`

    // ...

    // 新增字段,omitempty 兼容旧数据

    LastService  string `json:"lastService,omitempty"`

    Status       string `json:"status,omitempty"`

}

15.4.4 应急处置:链码错误

bash

## 如果升级后的链码有 bug,影响无法 Invoke

## 可回滚到旧版本(降级)

peer lifecycle chaincode approveformyorg \

  --name assettransfer \

  --version 1.0 --sequence 3  # 降回旧版本,sequence 继续递增

  # ... 然后 commit

关键洞察:sequence 永远递增,不可以用旧 sequence 重新部署。



15.5 Fabric SDK:Node.js 后端服务开发

Fabric v2.4+ 推出了 Gateway API,将复杂的背书、排序、事件监听封装为高层接口。Node.js 后端只需连接一个 gateway peer,其他自动处理。


15.5.1 连接模型

graph LR

    App["Node.js 后端应用"] --> Gateway[Fabric Gateway]

    Gateway --> P1[peer0.org1]

    P1 --> P2[peer0.org2] & O[orderer]

    P2 --> O

    

    style App fill:#e8f5e9

    style Gateway fill:#e3f2fd

Gateway vs 旧版 SDK

能力旧版 SDK (v1.x)Gateway (v2.4+)
------------------------------------------
连接直连所有背书节点单点 gateway peer
背书客户端手动组装内部自动分发
提交手动调用 orderer自动处理
监听需独立事件服务简化
证书每个节点一个连接peer 内部转发

15.5.2 后端服务代码骨架

typescript

/**

 * Fabric Gateway Node.js 后端

 * 将链码调用映射为 REST API

 */

import { Gateway, Wallets, Contract } from 'fabric-gateway';

import * as grpc from '@grpc/grpc-js';

import { readFileSync } from 'fs';

interface ConnectionProfile {

  url: string;

  tlsCACert: string;

  peerName: string;

}

class FabricClientService {

  private gateway: Gateway;

  private contract: Contract;

  

  async connect(profile: ConnectionProfile, userId: string, org: string) {

    // 加载 TLS 根证书

    const tlsRootCert = readFileSync(profile.tlsCACert);

    const client = new grpc.Channel(profile.url, grpc.credentials.createSsl(Buffer.from(tlsRootCert)));

    

    // 加载用户身份钱包

    const wallet = await Wallets.newFileSystemWallet('./wallet');

    const identity = await wallet.get(userId);

    if (!identity) throw new Error(`身份 ${userId} 不存在于钱包`);

    

    // 建立 Gateway 连接

    const gateway = new Gateway()// 内部使用 gRPC

    // 通过 gateway 获取网络与合约

    this.gateway = gateway;

    const network = gateway.getNetwork('mychannel');

    this.contract = network.getContract('assettransfer');

  }

  

  // === 只读查询:evaluate(单个 peer)

  async getAsset(id: string): Promise<Asset> {

    const result = await this.contract.evaluateTransaction('ReadAsset', id);

    return JSON.parse(Buffer.from(result).toString());

  }

  

  // === 写入交易:submit(完整背书→排序→提交)

  async createAsset(asset: Asset): Promise<string> {

    const result = await this.contract.submitTransaction(

      'CreateAsset',

      asset.id,

      asset.color,

      asset.owner,

      asset.size.toString(),

      asset.appraisedValue.toString(),

    );

    // 返回交易 ID

    return Buffer.from(result).toString();

  }

  

  // 转移(需要写交易)

  async transferAsset(id: string, newOwner: string): Promise<string> {

    return this.contract.submitTransaction('TransferAsset', id, newOwner)

      .then(r => Buffer.from(r).toString());

  }

  

  async disconnect() {

    this.gateway.close();

  }

}

// Express REST API 封装

import express from 'express';

const app = express();

const fabric = new FabricClientService();

app.get('/assets/:id', async (req, res) => {

  const asset = await fabric.getAsset(req.params.id);

  res.json(asset);

});

app.post('/assets', async (req, res) => {

  const txId = await fabric.createAsset(req.body);

  res.json({ transactionId: txId, status: 'submitted' });

});

app.put('/assets/:id/transfer', async (req, res) => {

  const txId = await fabric.transferAsset(req.params.id, req.body.newOwner);

  res.json({ transactionId: txId });

});

// 对比:公链中用户用 MetaMask 签名

// 联盟链中后端代签(因为是服务端服务)

// 后端持有用户注册的钱包密钥(或 HSM 托管)

15.5.3 Evaluate vs Submit

方法用途调用链性能
------------------------
evaluateTransaction只读查询单个 peer 模拟 → 返回
submitTransaction写交易全背书 → 排序 → 提交慢(秒级)

15.5.4 身份与密钥管理

graph TD

    subgraph Dev["开发"]

        D1["本地文件系统钱包"]

    end

    subgraph Prod["生产"]

        P1["硬件安全模块 HSM"]

        P2[HashiCorp Vault]

        P3[AWS KMS / Azure Key Vault]

    end

    

    D1 -.升级.-> P1

    D1 -.或.-> P2

    D1 -.或.-> P3

    

    style P1 fill:#e8f5e9

    style P2 fill:#e8f5e9

    style P3 fill:#e8f5e9


15.6 设计 REST API 与前端界面

联盟链前端不是 DApp——用户通常不直接管理私钥,由后端服务代为签名。REST API 是联盟链应用的标准接口模式。


15.6.1 架构模式

graph LR

    User["用户浏览器"] --> Frontend["React/Vue 前端"]

    Frontend --> |HTTP| API[Express/Fastify REST API]

    API --> |Gateway| Fabric["Fabric 网络"]

    API --> Auth["认证层"]

    

    style API fill:#e3f2fd

    style Fabric fill:#c8e6c9

身份模式对比

模式谁签名场景
------------------
服务代签后端持有组织 admin 私钥物流、溯源、监管系统
多级权限用户登录 → 后端按角色转发内部系统
客户端证书用户自带 x.509高安全需求

15.6.2 REST API 设计

typescript

/**

 * Express 路由:资产溯源 API

 */

import express from 'express';

import { FabricClientService } from '../fabric-client';

const router = express.Router();

// 资产 CRUD

router.get('/assets/:id', async (req, res) => {

  const asset = await fabric.getAsset(req.params.id);

  res.json(asset);

});

router.post('/assets', authenticate, authorize('admin'), async (req, res) => {

  const { id, color, size, owner } = req.body;

  const txId = await fabric.createAsset({ id, color, size, owner, appraisedValue: 0 });

  res.status(201).json({ transactionId: txId, status: 'submitted' });

});

router.put('/assets/:id/transfer', async (req, res) => {

  const { newOwner, reason } = req.body;

  const txId = await fabric.transferAsset(req.params.id, newOwner);

  res.json({ transactionId: txId, newOwner, reason });

});

// 溯源查询:时间线

router.get('/assets/:id/history', async (req, res) => {

  const history = await fabric.getAssetHistory(req.params.id);

  // 返回按时间排序的变更事件

  res.json(history.map(h => ({

    txId: h.txId,

    timestamp: h.timestamp,

    owner: h.value.owner,

    change: h.isDelete ? 'deleted' : 'modified',

  })));

});

15.6.3 前端界面设计

graph TD

    U["用户"] --> List["资产列表页"]

    List --> Detail["资产详情页"]

    Detail --> History["变更历史时间线"]

    Detail --> Transfer["转移操作"]

    Transfer --> Confirm["确认弹出框"]

    Confirm --> TxResult["交易结果"]

    

    style List fill:#e3f2fd

    style Detail fill:#e8f5e9

界面要点

  • 权限感知 UI:管理员看到"创建"按钮,普通用户只读
  • 交易状态轮询:显示 "待确认" → "已提交" → "已出块"
  • 时间线可视化:用时间线组件展示资产从出厂到现在的流转路径
  • 二维码/批次码:扫码查询资产

15.7 权限控制与私有数据集合


15.7.1 私有数据集合(Private Data Collection)

联盟链中,并非所有数据都应对所有参与者可见。私有数据集合让敏感字段仅对特定组织可见,同时其公共哈希上链防篡改。

graph TB

    subgraph Public["公共数据(所有组织可见)"]

        P1["资产ID, 所有者, 公共状态"]

        P2["公共哈希 = keccak256(私有数据)"]

    end

    

    subgraph Private["私有数据(仅 Org1 可见)"]

        D1["价格, 供应商, 内部评级"]

        D2["存储于 Org1 的私有数据状态数据库"]

    end

    

    P2 -.-> |防篡改| D1

    

    style Private fill:#e8f5e9

    style Public fill:#fff3e0

集合定义

yaml

## collections_config.json

[

  {

    "name": "assetPrivateDetails",

    "policy": "OR('Org1MSP.member')",

    "requiredPeerCount": 1,

    "maxPeerCount": 1,

    "blockToLive": 0,

    "memberOnlyRead": true,

    "endorsementPolicy": {

      "signaturePolicy": "OR('Org1MSP.peer', 'Org2MSP.peer')"

    }

  }

]

15.7.2 访问控制策略

层次机制控制点
------------------
网络层TLS 证书 + 通道隔离节点能否加入网络
链码层GetMSPID() 检查调用者的组织身份
数据层私有集合 policy谁能看到私有数据
应用层RBAC 角色系统用户权限映射
go

// 链码层检查

func (s *SmartContract) ConfidentialOperation(ctx contractapi.TransactionContextInterface, ...) error {

    mspID, _ := ctx.GetClientIdentity().GetMSPID()

    

    if mspID != "Org1MSP" && mspID != "Org2MSP" {

        return fmt.Errorf("unauthorized: %s", mspID)

    }

    

    // 继续执行...

    return nil

}

> ← 15.5 SDK 后端 | 本章最后一节已合并 |*


第15章 总结:企业区块链的范式

三个核心结论

  • 联盟链不是"去中心化程度较差的公链",而是完全不同的范式。身份、权限、通道、私有数据——这些概念在公链中不存在,是联盟链的核心而非约束
  • 执行-排序-验证模型(PODSC) 是性能与确定性的平衡:先并行模拟执行(快),再排序(全局一致),最后验证(确认无冲突)。这比公链的全节点重复执行更高效。
  • 链码生命周期 + 组织级治理 是生产部署的基本要求。升级不是重写合约,而是版本管理、多组织批准、sequence 递增的治理流程。

许可链 vs 公链对照表

维度Fabric(联盟链)以太坊(公链)
-------------------------------------
参与者已知组织任何地址
共识Raft/etcd-raftPoW/PoS
隐私通道 + 私有集合全公开
身份X.509 证书公钥 → 地址
性能3,000+ TPS10-30 TPS
用途企业协作开放金融

实战路径

  1. 启动 test-network 生成 2 组织网络
  2. 部署链码并测试 CRUD
  3. 用 Node.js Gateway SDK 构建后端 REST API
  4. React 前端连接 REST API 实现资产查询
  5. 使用私有数据集合隐藏敏感字段

, 前往 → 16.1 迷你链 |*

评论

0

评论加载中…

发表评论

0/2000