开发者文档

1. 部署网络

  超级账本网络由隶属不同组织的身份互认的Peer和Orderer服务节点组成,通过BaaS平台可实现超级账本网络的一键部署。超级账本“一键部署”页面设置网络部署参数,点击“部署”按钮即可实现部署超级账本网络。

  通过“区块链.超级账本”菜单->“超级账本”界面->“一键部署”按钮进入“一键部署”页面,网络部署示例配置如网络部署属性配置示例图所示,参数说明参见网络部署配置属性说明表

图-网络部署属性配置示例图
参数名 参数值 参数说明
基础信息 Fabric网络中包含的背书(Peer)组织。
区块链名 myfabric Fabric网络标识名。
版本号 1.0.0 Fabric网络使用的Fabric软件版本号。
域名 baas.jd.com Fabric网络对外访问域名。
持久化存储 gluster-heketi-2w7jw Fabric网络中使用的Kubernetes持久化存储类型。
组织列表 Fabric网络中包含的Peer组织。
组织名 org0 Peer组织在Fabric网络中的唯一标志名。Peer组织只能是小写英文字母和数字构成,并且首字符是英文字母。
节点数量 1 组织拥有的Peer节点数量。
用户数量 1 组织中参与的用户数量。
附加组件
区块链浏览器 Fabric区块链浏览器。
示例程序 marble示例程序。
表-网络部署配置属性说明表

2. 加入通道

  超级账本网络中包含多个通道,每个通道对应一个账本,是超级账本中数据隔离机制。通过“加入通道”窗口设置加入通道的参数,点击“确定”按钮将Peer节点加入通道。

  通过“网络详情”->“加入通道”按钮进入“加入通道”窗口,加入通道示例参数如加入通道参数设置示例图所示,参数说明参见加入通道参数说明表

图-加入通道参数设置示例图
参数名 参数值 参数说明
通道 mychannel 要加入的通道。
组织 org0 加入通道的组织。
节点 peer0 加入通道的Peer节点。
表-加入通道参数说明表

3. 开发链码

  链码可看作是超级账本中的智能合约,通常用来处理网络成员达成共识的业务逻辑,任何对账本数据的查询和修改都需要调用链码实现。超级账本链码本质上是实现接口规约的程序,当前开发语言支持Go和Node.js。

开发环境

  采用Go语言开发链码,需要准备如下开发环境:

  1. 安装配置Go,建议采用1.10.X以上版本;
  2. Github上获取Hyperledge Fabric 1.0.0源码并添加到GOPATH;
  3. 安装配置IDE,建议使用GoLand。

链码API

  所有链码都需要实现Chaincode接口,接口声明如下所示。Peer节点在交易中调用链码接口方法,其中Init方法在链码实例化(instantiate)升级(upgrade)交易中调用,以便链码执行必要的初始化操作。Invoke方法在调用(invoke)交易中使用,处理交易提案内容,修改或读取账本数据。

   // Chaincode interface must be implemented by all chaincodes. The fabric runs
   // the transactions by calling these functions as specified.
type Chaincode interface {
   // Init is called during Instantiate transaction after the chaincode container
   // has been established for the first time, allowing the chaincode to
   // initialize its internal data
   Init(stub ChaincodeStubInterface) pb.Response
   // Invoke is called to update or query the ledger in a proposal transaction.
   // Updated state variables are not committed to the ledger until the
   // transaction is committed.
   Invoke(stub ChaincodeStubInterface) pb.Response
}
表-Chaincode接口声明表

  链码API中另一个重要接口是ChaincodeStubInterface,链码与超级账本的交互都通过桩接口实现,如获取调用参数、获取操作用户信息、获取交易信息、操作账本、调用其他链码等。

  链码需要包含main方法,并在main方法中调用shim.Start函数启动链码,链码启动中建立与Peer节点的通信连接,实现与Peer的交互。

链码实例

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
   var err error
   _, args := stub.GetFunctionAndParameters()
   if len(args) != 1 {
      return shim.Error("Incorrect number of arguments. Expecting 2")
   }
   if _, err = strconv.Atoi(args[0]);  err != nil {
      return shim.Error("Expecting integer value for asset holding")
   }
   if err := stub.PutState(Name, []byte(args[0])); err != nil {
      return shim.Error(fmt.Sprintf("fail to put state: %v", err))
   }
   return shim.Success(nil)
}
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
   function, args := stub.GetFunctionAndParameters()
   switch function {
   case "invoke":
      return t.invoke(stub, args)
   case "query":
      return t.query(stub, args)
   default:
      return shim.Error("not support function")
   }
}
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   if len(args) != 1 {
      return shim.Error("Incorrect number of arguments. Expecting 1")
   }
   change, err :=  strconv.Atoi(args[0])
   if err != nil {
      return shim.Error("Expecting integer value for asset holding")
   }
   if state, err := stub.GetState(Name); err != nil {
      return shim.Error("fail to read asset state")
   }  else if value, err := strconv.Atoi(string(state)); err != nil {
      return shim.Error("fail to parse asset holding")
   } else if err := stub.PutState(Name,[]byte(strconv.Itoa(value + change))); err != nil{
      return shim.Error("fail to change asset holding")
   } else {
      return shim.Success(nil)
   }
}
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
   if state, err := stub.GetState(Name); err != nil {
      return shim.Error("fail to read asset state")
   }  else if _, err := strconv.Atoi(string(state)); err != nil {
      return shim.Error("fail to parse asset holding")
   } else {
      return shim.Success(state)
   }
}
func main() {
   err := shim.Start(new(SimpleChaincode))
   if err != nil {
      fmt.Printf("Error starting Simple chaincode: %v\n", err)
   }
}

4. 上传链码

  从形式上来讲,链码是符合超级账本接口规范的Go或Node.js程序。通过“上传链码”窗口设置上传的链码信息,点击“上传”按钮完成上传链码代码包。

  通过“网络详情”界面->“已上传的链码”区域->“上传”按钮进入“上传链码”窗口,上传链码示例参数如上传链码参数设置示例图所示,参数说明参见上传链码参数说明表

图-上传链码参数设置示例图
参数名 参数值 参数说明
上传链码 chaincode.zip 链码源码zip压缩文件,通过点击“**上传**”从本地选择。
链码 ccdemo 链码名称。
版本号 1.0.0 链码版本号。
表-上传链码参数说明表

5. 安装链码

  只有在Peer节点上安装和实例化链码后,才能通过Peer节点的背书服务访问链码操作账本。实例化的链码隶属指定的通道,可理解为在对应的账本上的一个账户。通过“安装”窗口设置链码安装参数,点击“安装”按钮安装链码。

  通过“网络详情”界面->“已上传的链码”区域->“安装”按钮进入“安装”窗口,安装链码示例参数如安装链码参数设置示例图所示,参数说明参见安装链码参数说明表

图-安装链码参数设置示例图
参数名 参数值 参数说明
链码名 ccdemo 安装的链码名称。
链码版本 1.0.0 安装的链码版本号。
初始化参数 init,a,1,b,2 链码初始化参数,多个参数间用逗号分隔。
组织 org0 安装链码的组织。
节点 peer0 安装链码的Peer节点。
通道 mychannel 安装链码的通道。
表-安装链码参数说明表

6. 调用链码

  部署后的链码作为Docker镜像或者是本地进程运行在Peer节点所在服务器。区块链应用客户端不能直接访问链码,所有链码操作通过请求Peer节点的背书服务实现。对于交易类链码操作,Peer节点只是模拟执行,并不会记录到账本,区块链应用客户端需要通过Orderer节点的广播服务提交交易。超级账本中交易提交过程上是异步的,Orderer节点的广播服务只是接受交易提交请求,只有在Orderer节点生成区块,传播到Peer节点,Peer节点记录区块后,才真正完成交易的处理。Peer节点提供事件服务,区块链应用客户端可以通过注册事件服务,从区块链事件中获取交易处理结果。

  超级账本是有许可的区块链,区块链应用客户端需要有身份证书才可以访问超级账本。超级账本可配置开启TLS通信协议,若开启TLS通信协议,区块链应用客户端需要进行TLS相关设置。

  京东BaaS在启动超级账本网络时,自动生成身份许可相关的证书,并提供用户证书下载功能。此外,为了降低使用的难度,京东BaaS默认超级账本部署中未开启TLS。

获取连接参数

  访问超级账本网络中的链码,需要建立与超级账本网络中相关服务的连接,连接参数包括Channel名称、Orderer服务地址、Peer背书服务地址、Peer背书服务地址等,参数列表及格式如表-超级账本连接参数表

  可以根据BaaS提供的信息属性获取超级账本网络连接参数,属性来源参见表-超级账本连接参数来源表,具体示例参见图-超级账本网络列表示例图图-超级账本共识信息示例图图-超级账本通道信息示例图图-下载MSP证书示例图图-MSP证书下载内容示例图

参数名称 参数格式 参数示例
Channel名 mychannel
Orderer服务地址 gprc://%网络域名%/%Orderer服务端口% grpc://k8s.3.cn/32123
Peer背书服务地址 gprc://%网络域名%/%Peer背书服务端口% grpc://k8s.3.cn/31093
Peer事件服务地址 gprc://%网络域名%/%Peer事件服务端口% grpc://k8s.3.cn/32511
MSP标识 jdMSP
用户名 User1
用户私钥文件 msp/keystore/key.pem
用户证书文件 msp/signcerts/User1@org0.peer.baas.jd.com-cert.pem
表-超级账本连接参数表
属性名称 属性来源 属性示例
超级账本界面(图-超级账本网络列表示例图)
网络域名 网络列表中“网络域名”列 k8s.3.cn
网络详情界面.共识管理页(图-超级账本共识信息示例图)
Orderer服务端口 Orderer列表中“Ports”列 32123
网络详情界面.通道管理页(图-超级账本通道信息示例图)
Channel名 页面左上角下拉框 mychannel
MSP标识 组织列表视图中“MSP标识”列 jdMSP
Peer背书服务端口 组织列表视图中“Ports”列中第一个端口 31093
Peer事件服务端口 组织列表视图中“Ports”列中第二个端口 32511
下载MSP证书界面(图-下载MSP证书示例图)
用户名 下载框中选中的用户名 User1
MSP证书下载内容(图- MSP证书下载内容示例图)
用户私钥文件 keystore目录下key.pem文件 msp/keystore/key.pem
用户证书文件 signcerts目录下的pem文件 msp/signcerts/User1@org0.peer.baas.jd.com-cert.pem
表-超级账本连接参数来源表
图-超级账本网络列表示例图
图-超级账本共识信息示例图
图-超级账本通道信息示例图
图-下载MSP证书示例图
图-MSP证书下载内容

开发应用程序

  超级账本提供Java、Node.js和Java版SDK,建议采用Java或Node.js开发超级账本区块链应用客户端。

Java 版

开发环境

使用Java版Fabric SDK开发应用客户端,需要准备如下开发环境:

  1. JDK,建议使用JDK 1.8以上版本;
  2. IDE,可使用熟悉的IDE,建议使用Eclipse;
  3. Fabric Java SDK,建议使用与Fabric版本保持一致;
API说明

Java Fabric SDK中链码调用相关的主要API类关系下图所示。

  HFClient提供客户端环境,通过该类实例初始化超级账本交互的对象。需要注意的是,HFClient中参数初始化有依赖关系,具体细节可参见示例代码。

  CryptoSuite定义了PKI接口,PKI相关秘钥生成、签名和验证方法都在此接口中定义,所有PKI实现都要继承该接口。SDK中在CryptoPrimitives提供了基础实现,可通过CryptoSuite.Factory工厂类创建该实现实例。

  链码调用功能由Chanel类提供,包括链码查询(queryByChaincode)、发送交易背书(sendTransactionProposal)和发送交易(sendTransaction)。PeerOrdererEvent服务分别用于定义Peer节点、Orderer节点和事件通知服务地址,Channel通过这些定义的地址调用对应服务。

  UserEnrollment接口用于提供操作账本时的身份认证信息,User定义了用户基本信息,Enrollment提供证书和对应签名私钥,SDK中未提供User实现,仅提供了Fabric CA对应的Enrollment实现,应用中需要提供自己的实现。

示例代码

  超级账本是有许可的区块链,只有授权用户才能操作,因此访问超级账本时,需要提供用户身份信息。在Fabric Java SDK中,用户身份认证信息通过User和Enrollment接口定义,应用中通过实例化接口对象提供身份认证信息。

  在基于BaaS部署管理的链码开发中,通过用户证书下载功能可以获取实例化身份认证信息所需参数,主要包括用户名、MSP标识、签名私钥和CA证书。Enrollment实例化参见示例代码-Enrollment实例化,User实例化参见示例代码-User实例化

  通过Fabric Java SDK 调用链码前,需要指定Channel、Peer背书服务地址、Orderer广播服务地址、Peer Event服务地址。除此之外,还需要初始化链码调用请求和响应数据的签名和验签等依赖的PKI组件,以及用户的身份标识信息。调用链码前的环境初始化代码参见示例代码-初始化Channel,初始化过程中需要注意各个模块的依赖关系,详情见示例中的NOTE。

  超级账本中链码操作可分为两类,一类是执行交易,一类是查询状态。交易类操作,发送请求到一个或多个Peer背书,将Peer背书结果广播到Orderer服务,通过Peer Event服务监听执行结果,实现方式可参见示例代码-执行交易。查询状态实现比较简单,调用Peer背书服务即可,实现方式可参见示例代码-查询链码数据状态

/**
 * load enrollment from local key and certificate files.
 * 
 * @param keyFile file path of private key
 * @param certFile file path of CA certificate
 * @return  enrollment loaded from local key and certificate files
 * @throws Exception  any exception occurred during  enrollment loading
 */
private static Enrollment loadEnrollment(String keyFile, String certFile) throws Exception {
 PemReader reader = null;
 PemObject pemObj = null;

 // read private key PEM file
 try {
  reader = new PemReader(new java.io.FileReader(keyFile));
  pemObj = reader.readPemObject();
 } finally {
  if(reader != null) {
   try {
    reader.close();
   } catch(Exception e) {}
  }
 }

 // generate private key from PKCS8 encoded data
 EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pemObj.getContent());;
 KeyFactory generator = KeyFactory.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
 final PrivateKey privateKey = generator.generatePrivate(privateKeySpec);


 // read CA certificate content
 final String cert =  IOUtils.toString(new File(certFile).toURI());

 return new Enrollment() {
  public PrivateKey getKey() {
   return privateKey;
  }

  public String getCert() {
   return cert;
  }
 };

}
示例代码-Enrollment实例化
/**
 * create user with specified MSP, user name and enrollment.
 * 
 * @param user  ID, i.e., unique name of user
 * @param mspId ID of MSP which user belongs to
 * @param enrollment user's enrollment
 * @return  user instance with attributes provided by parameters, and others as default.
 */
private static User createUser(String user, String mspId,  final Enrollment enrollment) {
 return new User() {
  public String getName() {
   return user;
  }

  public Set<String> getRoles() {
   return null;
  }

  public String getAccount() {
   return null;
  }

  public String getAffiliation() {
   return null;
  }


  public Enrollment getEnrollment() {
   return enrollment;
  }

  public String getMspId() {
   return mspId;
  }
 };
}
示例代码-User实例化
HFClient hfClient = HFClient.createNewInstance();

// NOTE: CryptoSuite must be set first.
hfClient.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());

// NOTE: User must be set before Orderer, Peer and EventHub.
hfClient.setUserContext(createUser("User1", "houseMSP",
  loadEnrollment("data/msp/keystore/key.pem", "data/msp/signcerts/User1@house.peer.k8s.3.cn-cert.pem")));

Channel channel = hfClient.newChannel("mychannel");

// NOTE: Orderer is used to send transaction.
Properties ordererProp = new Properties();
ordererProp.setProperty("ordererWaitTimeMilliSecs", "30000");
channel.addOrderer(hfClient.newOrderer("orderer", "grpc://k8s.3.cn:30094", ordererProp));

// NOTE: Peer is used to interact with ChainCode, including transaction and query. 
channel.addPeer(hfClient.newPeer("peer0org0", "grpc://k8s.3.cn:31636"));

// NOTE: EventHub is used to listen block events.
// NOTE: EventHub must be added if transactions state is needed.
channel.addEventHub(hfClient.newEventHub("peer0org0", "grpc://k8s.3.cn:30512"));

// NOTE: channel must be initialized before any ledger operation.
channel.initialize();
初始化Channel客户端
TransactionProposalRequest txReq = hfClient.newTransactionProposalRequest();
ChaincodeID cid = ChaincodeID.newBuilder().setName("cdemo").build();
  txReq.setChaincodeID(cid);
  txReq.setFcn("invoke");
  txReq.setArgs(new String[] {"12"});
Collection<ProposalResponse> txRsps = channel.sendTransactionProposal(txReq);
CompletableFuture<TransactionEvent> eFuture = channel.sendTransaction(txRsps);
// NOTE: if no EventHub, this function will be blocked. 
TransactionEvent txEvent = eFuture.get();
示例代码-执行交易
QueryByChaincodeRequest qryReq = hfClient.newQueryProposalRequest();
  qryReq.setChaincodeID(cid);
  qryReq.setFcn("query");
  qryReq.setArgs(new String[] {});
  Collection<ProposalResponse> 
Collection<ProposalResponse> qryRsps =channel.queryByChaincode(qryReq);
示例代码-查询链码数据状态

Nodejs 版

开发环境
  1. Node, Node需要6.9.x 或者更高版本, 8.4.0 或者更高版本,不支持Node v7+;  NPM版本支持3.10.x 或者更高版本;
  2. IDE, 推荐使用vscode,其他如Sublime Text甚至记事本都可以;
  3. Fabric Nodejs SDK,建议使用与Fabric版本保持一致;
API说明

SDK由三个模块组成:

  1. api: 为应用程序开发人员提供可插入的api,以提供SDK使用的关键接口的替代实现。对于每个接口都有内置的默认实现;
  2. fabric-client: 这个模块提供api来与基于Hypreledger构建的区块链网络的核心组件交互,即对等体、定序器和事件流;
  3. fabric-ca-client: 该模块提供api与可选组件fabric-ca交互,该组件包含用于成员管理的服务;
示例代码

Query

var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');
var console = require("console")
var fabric_client = new Fabric_Client();
// setup the fabric network
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://192.168.*.*:32683');
channel.addPeer(peer);
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.error('Store path:'+store_path);
var tx_id = null;
// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
    // assign the store to the fabric client
    fabric_client.setStateStore(state_store);
    var crypto_suite = Fabric_Client.newCryptoSuite();
    var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
    crypto_suite.setCryptoKeyStore(crypto_store);
    fabric_client.setCryptoSuite(crypto_suite);
    // get the enrolled user from persistence, this user will sign all requests
    var userOpt = {
            username: 'User1',
            mspid: 'org0MSP',
            //  这里是从网页上下载的证书跟私钥
            cryptoContent: {
                privateKey: './key.pem',
                signedCert: './cert.pem'
            }
        }
        return fabric_client.createUser(userOpt)
}).then((user_from_store) => {
    if (user_from_store && user_from_store.isEnrolled()) {
        console.log('Successfully loaded user1 from persistence');
        member_user = user_from_store;
    } else {
        throw new Error('Failed to get user1.... run registerUser.js');
    }
    fabric_client.setUserContext(user_from_store, true)
    const request = {
        chaincodeId: 'cdemo',
        fcn: 'query',
        args: ['']
    };
    console.log("pass")
    // send the query proposal to the peer
    return channel.queryByChaincode(request);
}).then((query_responses) => {
    console.log("Query has completed, checking results");
    // query_responses could have more than one  results if there multiple peers were used as targets
    if (query_responses && query_responses.length == 1) {
        if (query_responses[0] instanceof Error) {
            console.error("error from query = ", query_responses[0]);
        } else {
            console.log("Response is ", query_responses[0].toString());
        }
    } else {
        console.log("No payloads were returned from query");
    }
}).catch((err) => {
    console.error('Failed to query successfully :: ' + err);
});

Invoke

'use strict';
var hfc = require('fabric-client');
var path = require('path');
var util = require('util');
var sdkUtils = require('fabric-client/lib/utils')
const fs = require('fs');
var options = {
    user_id: 'User1',
    msp_id:'Org0MSP',
    channel_id: 'mychannel',
    chaincode_id: 'cdemo',
    peer_url: 'grpc://192.168.*.*:32683',
    event_url: 'grpc://192.168.*.*:32134',
    orderer_url: 'grpc://192.168.*.*:31048',
    privateKeyFolder:'./keystore',
    signedCert:'./cert.pem',
};
var channel = {};
var client = null;
var targets = [];
var tx_id = null;
Promise.resolve().then(() => {
    console.log("Load privateKey and signedCert");
    client = new hfc();
    var createUserOpt = {
        username: 'Admin',
        mspid: 'org0MSP',
        cryptoContent: {
            privateKey: './key.pem',
            signedCert: './cert.pem'
        }
    }
//以上代码指定了当前用户的私钥,证书等基本信息
return sdkUtils.newKeyValueStore({
                        path: "/tmp/fabric-client-stateStore/"
                }).then((store) => {
                        client.setStateStore(store)
                        return client.createUser(createUserOpt)
                })
}).then((user) => {
    channel = client.newChannel(options.channel_id);
    let peer = client.newPeer(options.peer_url,
        {}
    );
    var orderer = client.newOrderer(options.orderer_url, {});
    channel.addOrderer(orderer);
    targets.push(peer);
    return;
}).then(() => {
    tx_id = client.newTransactionID();
    console.log("Assigning transaction_id: ", tx_id._transaction_id);
    var request = {
        targets: targets,
        chaincodeId: options.chaincode_id,
        fcn: 'invoke',
        args: ['12'],
        chainId: options.channel_id,
        txId: tx_id
    };
    return channel.sendTransactionProposal(request);
}).then((results) => {
    var proposalResponses = results[0];
    var proposal = results[1];
    var header = results[2];
    let isProposalGood = false;
    if (proposalResponses && proposalResponses[0].response &&
        proposalResponses[0].response.status === 200) {
        isProposalGood = true;
        console.log('transaction proposal was good');
    } else {
        console.error('transaction proposal was bad');
    }
    if (isProposalGood) {
        console.log(util.format(
            'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s', 
            proposalResponses[0].response.status, proposalResponses[0].response.message,
            proposalResponses[0].response.payload, proposalResponses[0].endorsement.signature));
        var request = {
            proposalResponses: proposalResponses,
             proposal: proposal,
            header: header
        };
         // set the transaction listener and set a timeout of 30sec
        // if the transaction did not get committed within the timeout period,
        // fail the test
        var transactionID = tx_id.getTransactionID();
        var eventPromises = [];
        let eh = client.newEventHub();
        //接下来设置EventHub,用于监听Transaction是否成功写入
        eh.setPeerAddr(options.event_url, {});
        eh.connect();
        let txPromise = new Promise((resolve, reject) => {
            let handle = setTimeout(() => {
                eh.disconnect();
                reject();
            }, 30000);
            //向EventHub注册事件的处理办法
            eh.registerTxEvent(transactionID, (tx, code) => {
                clearTimeout(handle);
                eh.unregisterTxEvent(transactionID);
                eh.disconnect();
                if (code !== 'VALID') {
                    console.error(
                        'The transaction was invalid, code = ' + code);
                    reject();
                 } else {
                    console.log(
                         'The transaction has been committed on peer ' +
                         eh._ep._endpoint.addr);
                    resolve();
                };
            });
        });
        eventPromises.push(txPromise);
        var sendPromise = channel.sendTransaction(request);
        return Promise.all([sendPromise].concat(eventPromises)).then((results) => {
            console.log(' event promise all complete and testing complete');
             return results[0]; // the first returned value is from the 'sendPromise' which is from the 'sendTransaction()' call
        }).catch((err) => {
            console.error(
                'Failed to send transaction and get notifications within the timeout period.'
            );
            return 'Failed to send transaction and get notifications within the timeout period.';
         });
    } else {
        console.error(
            'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'
        );
        return 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...';
    }
}, (err) => {
    console.error('Failed to send proposal due to error: ' + err.stack ? err.stack :
        err);
    return 'Failed to send proposal due to error: ' + err.stack ? err.stack :
        err;
}).then((response) => {
    if (response.status === 'SUCCESS') {
        console.log('Successfully sent transaction to the orderer.'); 
        return tx_id.getTransactionID();
    } else {
        console.error('Failed to order the transaction. Error code: ' + response.status);
        return 'Failed to order the transaction. Error code: ' + response.status;
    }
}, (err) => {
    console.error('Failed to send transaction due to error: ' + err.stack ? err
         .stack : err);
    return 'Failed to send transaction due to error: ' + err.stack ? err.stack :
        err;
});