Hyperledger Fabric - 区块链应用

Fabric Gateway SDK 实现Fabric的编程模型,提供了一系列简单的API给应用程序与Fabric区块链网络进行交互

应用程序将各自的网络交互委托给其网关,每个网关都了解网络信道拓扑,包括组织的多个Peer节点和排序节点,使应用程序专注于业务逻辑;Peer节点可以使用gossip协议在组织内部和组织之间相互通信。

Maven依赖

<dependency>
  <groupId>org.hyperledger.fabric</groupId>
  <artifactId>fabric-gateway-java</artifactId>
  <version>2.2.3</version>
</dependency>

准备网络证书

创建目录 crypto-config 把 orderer 和 peer 节点的证书文件复制进来。

证书文件从 fabric-samples 的 test-network 目录中复制 ordererOrganizations 与 peerOrganizations 文件夹

springboot工程结构如下:

img

准备网络证书

创建文件 connection.json 内容如下:

{
  "name": "basic-network",
  "version": "1.0.0",
  "dependencies": {
  },
  "client": {
    "organization": "Org1",
    "connection": {
      "timeout": {
        "peer": {
          "endorser": "300"
        },
        "orderer": "300"
      }
    }
  },
  "channels": {
    "mychannel": {
      "orderers": [
        "orderer.example.com"
      ],
      "peers": {
        "peer0.org1.example.com": {
          "endorsingPeer": true,
          "chaincodeQuery": true,
          "ledgerQuery": true,
          "eventSource": true
        },
        "peer0.org2.example.com": {
          "endorsingPeer": true,
          "chaincodeQuery": true,
          "ledgerQuery": true,
          "eventSource": true
        }
      }
    }
  },
  "organizations": {
    "Org1": {
      "mspid": "Org1MSP",
      "peers": [
        "peer0.org1.example.com"
      ],
      "certificateAuthorities": [
        "ca-org1"
      ],
      "adminPrivateKeyPEM": {
        "path": "src/main/resources/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/priv_sk"
      },
      "signedCertPEM": {
        "path": "src/main/resources/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem"
      }
    },
    "Org2": {
      "mspid": "Org2MSP",
      "peers": [
        "peer0.org2.example.com"  
      ],
      "certificateAuthorities": [
        "ca-org2"
      ],
      "adminPrivateKeyPEM": {
        "path": "src/main/resources/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/priv_sk"
      },
      "signedCertPEM": {
        "path": "src/main/resources/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem"
      }
    }
  },
  "orderers": {
    "orderer.example.com": {
      "url": "grpcs://127.0.0.1:7050",
      "mspid": "OrdererMSP",
      "grpcOptions": {
        "ssl-target-name-override": "orderer.example.com",
        "hostnameOverride": "orderer.example.com"
      },
      "tlsCACerts": {
        "path": "src/main/resources/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt"
      },
      "adminPrivateKeyPEM": {       
        "path": "src/main/resources/crypto-config/ordererOrganizations/example.com/users/Admin@example.com/msp/keystore/priv_sk"
      },
      "signedCertPEM": {
        "path": "src/main/resources/crypto-config/ordererOrganizations/example.com/users/Admin@example.com/msp/signcerts/Admin@example.com-cert.pem"
      }
    }
  },
  "peers": {
    "peer0.org1.example.com": {
      "url": "grpcs://127.0.0.1:7051",
      "grpcOptions": {
        "ssl-target-name-override": "peer0.org1.example.com",
        "hostnameOverride": "peer0.org1.example.com",
          "request-timeout": 120001
        },
        "tlsCACerts": {
          "path": "src/main/resources/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"
        }
      },
      "peer0.org2.example.com": {
        "url": "grpcs://127.0.0.1:9051",
        "grpcOptions": {
          "ssl-target-name-override": "peer0.org2.example.com",
          "hostnameOverride": "peer0.org2.example.com",
          "request-timeout": 120001
        },
        "tlsCACerts": {
          "path": "src/main/resources/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"
        }
      }
    },
  "certificateAuthorities": {
    "ca-org1": {
      "url": "https://127.0.0.1:7054",
      "grpcOptions": {
        "verify": true
      },
      "tlsCACerts": {
        "path": "src/main/resources/crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem"
      },
      "registrar": [
        {
          "enrollId": "admin",
          "enrollSecret": "adminpw"
        }
      ]
    },
    "ca-org2": {
      "url": "https://127.0.0.1:8054",
      "grpcOptions": {
        "verify": true
      },
      "tlsCACerts": {
        "path": "src/main/resources/crypto-config/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem"
      },
      "registrar": [
        {
          "enrollId": "admin",
          "enrollSecret": "adminpw"
        }
      ]
    }
  }
  }
  

需按实际情况修改url中的地址,内容中分别包含了 channels、organizations、orderers、peers、ca 的配置

SpringBoot配置

在 application.yml 中添加以下内容,用于访问网关的相关配置:

fabric:
  # wallet文件夹路径(自动创建)
  walletDirectory: wallet
  # 网络配置文件路径
  networkConfigPath: connection.json
  # 用户证书路径
  certificatePath: crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem
  # 用户私钥路径
  privateKeyPath: crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk
  # 访问的组织名
  mspid: Org1MSP
  # 用户名
  username: user1
  # 通道名字
  channelName: mychannel
  # 链码名字
  contractName: mycc

连接合约

分别构建网关、通道和合约的Bean对象,代码如下:

import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.gateway.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;


@Slf4j
@Configuration
public class GatewayConfig {
    /**
     * wallet文件夹路径
     */
    @Value("${fabric.walletDirectory}")
    private String walletDirectory;
    /**
     * 网络配置文件路径
     */
    @Value("${fabric.networkConfigPath}")
    private String networkConfigPath;
    /**
     * 用户证书路径
     */
    @Value("${fabric.certificatePath}")
    private String certificatePath;
    /**
     * 用户私钥路径
     */
    @Value("${fabric.privateKeyPath}")
    private String privateKeyPath;
    /**
     * 访问的组织名
     */
    @Value("${fabric.mspid}")
    private String mspid;
    /**
     * 用户名
     */
    @Value("${fabric.username}")
    private String username;
    /**
     * 通道名字
     */
    @Value("${fabric.channelName}")
    private String channelName;
    /**
     * 链码名字
     */
    @Value("${fabric.contractName}")
    private String contractName;

    /**
     * 连接网关
     */
    @Bean
    public Gateway connectGateway() throws IOException, InvalidKeyException, CertificateException {
        //使用org1中的user1初始化一个网关wallet账户用于连接网络
        Wallet wallet = Wallets.newFileSystemWallet(Paths.get(this.walletDirectory));
        X509Certificate certificate = readX509Certificate(Paths.get(this.certificatePath));
        PrivateKey privateKey = getPrivateKey(Paths.get(this.privateKeyPath));
        wallet.put(username, Identities.newX509Identity(this.mspid, certificate, privateKey));

        //根据connection.json 获取Fabric网络连接对象
        Gateway.Builder builder = Gateway.createBuilder()
        .identity(wallet, username)
        .networkConfig(Paths.get(this.networkConfigPath));
        //连接网关
        return builder.connect();
    }

    /**
     * 获取通道
     */
    @Bean
    public Network network(Gateway gateway) {
        return gateway.getNetwork(this.channelName);
    }

    /**
     * 获取合约
     */
    @Bean
    public Contract contract(Network network) {
        return network.getContract(this.contractName);
    }

    private static X509Certificate readX509Certificate(final Path certificatePath) throws IOException, CertificateException {
        try (Reader certificateReader = Files.newBufferedReader(certificatePath, StandardCharsets.UTF_8)) {
            return Identities.readX509Certificate(certificateReader);
        }
    }

    private static PrivateKey getPrivateKey(final Path privateKeyPath) throws IOException, InvalidKeyException {
        try (Reader privateKeyReader = Files.newBufferedReader(privateKeyPath, StandardCharsets.UTF_8)) {
            return Identities.readPrivateKey(privateKeyReader);
        }
    }
}

合约的调用

创建controller类,注入Contract对象调用合约方法:

import org.hyperledger.fabric.gateway.Contract;
import org.hyperledger.fabric.gateway.ContractException;
import org.hyperledger.fabric.gateway.Network;
import org.hyperledger.fabric.sdk.Peer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.chenyi.fabric.common.Result;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.concurrent.TimeoutException;

@RestController
public class TestController {
    @Resource
    private Contract contract;

    @Resource
    private Network network;

    @GetMapping("/getUser")
    public String getUser(String userId) throws ContractException {
        byte[] queryAResultBefore = contract.evaluateTransaction("getUser",userId);
        return new String(queryAResultBefore, StandardCharsets.UTF_8);
    }

    @GetMapping("/addUser")
    public String addUser(String userId, String userName, String money) throws ContractException, InterruptedException, TimeoutException {
        byte[] invokeResult = contract.createTransaction("addUser")
                .setEndorsingPeers(network.getChannel().getPeers(EnumSet.of(Peer.PeerRole.ENDORSING_PEER)))
                .submit(userId, userName, money);
        String txId = new String(invokeResult, StandardCharsets.UTF_8);
        return txId;
    }
}

测试接口

启动springboot工程,调用对应接口即可

posted @ 2025-04-02 21:46  终须有  阅读(91)  评论(0)    收藏  举报