老徐

Never underestimate your power to change yourself!

Fabric多台服务器的部署(四)

| Comments

7、创建channel和chaincode

  之前我们说过,本项目是一个nodejs的项目,是根据https://github.com/hyperledger/fabric-samples里的balance_transfer稍作修改来的,所以这里我们创建channel和chaincode,包括数据上链都会用nodejs sdk来做。

7.1、启动nodejs服务

 进入我们的trace_wine项目,使用npm安装项目所需的package,

1
npm install

安装完后,我们现在要启动node服务,我们可以看下,当前目录下面的个runApp.sh的脚本文件,看下里面内容就知道,这是个启动node服务的脚本

项目的基本文件 现在我们来运行一下这个脚本文件,使用命令

1
./runApp.sh

查看是否启动成功,进入项目下的logs文件夹,找到当前的日志文件,打开看一下

1
2
3
4
5
tail -f 2018-11-28.log

[2018-11-28 15:06:48.699] [INFO] Helper - Successfully loaded member from persistence
[2018-11-28 15:07:15.485] [INFO] app - ****************** SERVER STARTED ************************
[2018-11-28 15:07:15.488] [INFO] app - ***************  http://localhost:4000  ******************

可以看到node服务已经启动成功,且端口号为4000,具体的文档可查看官方的readme

7.2、创建Ca用户

 Fabric CA为每个上链、查询者提供了注册用户,生成用户证书(ECerts)的功能,我们现在可以通过REST APIs来与ca server交互,我们先看下node里注册用户,生成证书的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
///////////////////////////////////////////////////////////////////////////////
///////////////////////// REST ENDPOINTS START HERE ///////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Register and enroll user
app.post('/users', async function(req, res) {
  var username = req.body.username;
  var orgName = req.body.orgName;
  logger.debug('End point : /users');
  logger.debug('User name : ' + username);
  logger.debug('Org name  : ' + orgName);
  if (!username) {
      res.json(getErrorMessage('\'username\''));
      return;
  }
  if (!orgName) {
      res.json(getErrorMessage('\'orgName\''));
      return;
  }
  var token = jwt.sign({
      exp: Math.floor(Date.now() / 1000) + parseInt(hfc.getConfigSetting('jwt_expiretime')),
      username: username,
      orgName: orgName
  }, app.get('secret'));
  let response = await helper.getRegisteredUser(username, orgName, true);
  logger.debug('-- returned from registering the username %s for organization %s',username,orgName);
  if (response && typeof response !== 'string') {
      logger.debug('Successfully registered the username %s for organization %s',username,orgName);
      response.token = token;
      res.json(response);
  } else {
      logger.debug('Failed to register the username %s for organization %s with::%s',username,orgName,response);
      res.json({success: false, message: response});
  }

});

可以看到当用户传进来username和orgName时,node首先会使用jwt将他们生成一个Token,然后通过getRegisteredUser()方法生成用户的证书,最终将jwt生成的token返回给用户。
 下面我们来调用一个这个接口

1
2
#在Org1上注册和生成一个新用户,用户名为Jim
curl -s -X POST http://localhost:4000/users -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=Org1'
1
2
3
4
5
6
7
#返回结果
{
  "success": true,
  "secret": "RaxhMgevgJcm",
  "message": "Jim enrolled Successfully",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDQ1MjIxMTgsInVzZXJuYW1lIjoiZG9jIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NDQ0MzU3MTh9.X8DuFxUSmsRTe7v7iMft8A7LxzpvGyhnufBLQTZ3F8I"
}

上面的token就是我们想要的,之后每次请求接口都会用token来验证用户信息,更多的jwt信息,可以查看这里https://jwt.io/,另外用户生成的证书保存在服务器端,是标准的X.509证书格式,可以打开看一下这个证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{"name":"Jim","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"",
"enrollment":{"signingIdentity":"72d623e2104955b39ca6b6383a278b4b0a253e45a83b26044edf193563655367","
identity":{"certificate":"
-----BEGIN CERTIFICATE-----
\nMIICkDCCAjegAwIBAgIUX+durkyChVVZ5LNZekudT17CcJUwCgYIKoZIzj0EAwIw\n
eTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\n
biBGcmFuY2lzY28xHDAaBgNVBAoTE29yZzEubWJhc2VjaGFpbi5jb20xHzAdBgNV\n
BAMTFmNhLm9yZzEubWJhc2VjaGFpbi5jb20wHhcNMTgxMTIzMDk0NjAwWhcNMTkx\n
MTIzMDk1MTAwWjBAMTAwDQYDVQQLEwZjbGllbnQwCwYDVQQLEwRvcmcxMBIGA1UE\n
CxMLZGVwYXJ0bWVudDExDDAKBgNVBAMTA2RvYzBZMBMGByqGSM49AgEGCCqGSM49\n
AwEHA0IABGUZz9n5KvXF9H0l4HSdCvegNE2A0pkk2w1oQLcJW9FaZvr7rBDG3/bl\nauV26OoHJX7Yo/N
x2JyQrQzYRoPKfOWjgdUwgdIwDgYDVR0PAQH/BAQDAgeAMAwG\n
A1UdEwEB/wQCMAAwHQYDVR0OBBYEFCGGAIKaIFfBpkYuR2rjxvGle+uRMCsGA1Ud\n
IwQkMCKAIGyyJr5K7JgVQebL3WZ0ITnBFX6Ir+Bu37FvEuzDBWv1MGYGCCoDBAUG\n
BwgBBFp7ImF0dHJzIjp7ImhmLkFmZmlsaWF0aW9uIjoib3JnMS5kZXBhcnRtZW50\n
MSIsImhmLkVucm9sbG1lbnRJRCI6ImRvYyIsImhmLlR5cGUiOiJjbGllbnQifX0w\n
CgYIKoZIzj0EAwIDRwAwRAIgYnTD6Pkx1+R4R77TztW3/oQb1h+5/3ELYtAsIuz7\n
D8wCIBiOmE/uySQvkgzUcCsVRtaUMg0M9zioKBYHiPUxeNJo\n
-----END CERTIFICATE-----\n"}}}%
7.2、创建Channel

  同样我们使用node服务提供的接口来创建Channel通道,具体创建channel的js代码可以看这里

1
2
3
4
5
6
7
8
curl -s -X POST \
  http://localhost:4000/channels \
  -H "authorization: Bearer Token" \
  -H "content-type: application/json" \
  -d '{
  "channelName":"mychannel",
  "channelConfigPath":"../artifacts/channel/mychannel.tx"
}'

authorization里的Token是上面我们得到的token, channelName是我们要创建通道的名字,channelConfigPath指定mychannel.tx文件路径,创建成功,返回如下结果

1
{"success":true,"message":"Channel 'mychannel' created Successfully"}
7.3、将Channel加入到Org里

 现在我们需要将channel加入到Org里,这样Org就可以使用这个channel,同样是通过node接口来实现

1
2
3
4
5
6
7
curl -s -X POST \
  http://localhost:4000/channels/mychannel/peers \
  -H "authorization: Bearer Token" \
  -H "content-type: application/json" \
  -d '{
  "peers": ["peer0.org1.xuyao.com","peer1.org1.xuyao.com"]
}'

返回结果

1
{"success":true,"message":"Successfully joined peers in organization Org1 to the channel:mychannel"}
7.4、编写和安装智能合约(chaincode)
7.4.1、编写智能合约(chaincode)

 channel安装好后,我们需要编写智能合约,这里我贴一个我用go写的一个chaincode,功能很简单就是把数据上链,查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package main

import (
  "bytes"
  "fmt"

  "github.com/hyperledger/fabric/core/chaincode/shim"
  pb "github.com/hyperledger/fabric/protos/peer"
)

//Chaincode implementation
type SimpleChaincode struct {
}


// ===================================================================================
// Main
// ===================================================================================
func main() {
  err := shim.Start(new(SimpleChaincode))
  if err != nil {
      fmt.Printf("Error starting Simple chaincode: %s", err)
  }
}

// Init initializes chaincode
// ===========================
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
  return shim.Success(nil)
}

// Invoke - Our entry point for Invocations
// ========================================
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
  function, args := stub.GetFunctionAndParameters()
  fmt.Printf("function: %s,args: %s\n", function, args)

  // Handle different functions
  if function == "save" { //create a new marble
      return t.save(stub, args)
  } else if function == "query" { //find Data based on an ad hoc rich query
      return t.query(stub, args)
  }

  fmt.Println("invoke did not find func: " + function) //error
  return shim.Error("Received unknown function invocation")
}

// ============================================================
// - create a new data, store into chaincode state
// ============================================================
func (t *SimpleChaincode) save(stub shim.ChaincodeStubInterface, args []string) pb.Response {
  
  if len(args) != 2 {
      return shim.Error("Incorrect number of arguments. Expecting 2")
  }

  err := stub.PutState(args[0], []byte(args[1]))
  if err != nil {
      return shim.Error(err.Error())
  }
  fmt.Println("- end save business")
  return shim.Success(nil)
  

}

// ===== Add hoc rich query ========================================================
// query method uses a query string to perform a query for data.
// Query string matching state database syntax is passed in and executed as is.
// Supports ad hoc queries that can be defined at runtime by the client.
// Only available on state databases that support rich query (e.g. CouchDB)
// =========================================================================================
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {

  //   0
  // "queryString"
  if len(args) < 1 {
      return shim.Error("Incorrect number of arguments. Expecting 1")
  }

  queryString := args[0]

  queryResults, err := getQueryResultForQueryString(stub, queryString)
  if err != nil {
      return shim.Error(err.Error())
  }
  return shim.Success(queryResults)
}

// =========================================================================================
// getQueryResultForQueryString executes the passed in query string.
// Result set is built and returned as a byte array containing the JSON results.
// =========================================================================================
func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) {

  fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString)

  resultsIterator, err := stub.GetQueryResult(queryString)
  if err != nil {
      return nil, err
  }
  defer resultsIterator.Close()

  // buffer is a JSON array containing QueryRecords
  var buffer bytes.Buffer
  buffer.WriteString("[")

  bArrayMemberAlreadyWritten := false
  for resultsIterator.HasNext() {
      queryResponse, err := resultsIterator.Next()
      if err != nil {
          return nil, err
      }
      // Add a comma before array members, suppress it for the first array member
      if bArrayMemberAlreadyWritten == true {
          buffer.WriteString(",")
      }
      buffer.WriteString("{\"Key\":")
      buffer.WriteString("\"")
      buffer.WriteString(queryResponse.Key)
      buffer.WriteString("\"")

      buffer.WriteString(", \"Record\":")
      // Record is a JSON object, so we write as-is
      buffer.WriteString(string(queryResponse.Value))
      buffer.WriteString("}")
      bArrayMemberAlreadyWritten = true
  }
  buffer.WriteString("]")

  fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())

  return buffer.Bytes(), nil
}

save()函数将传上来的数据上链,query()函数通过交易id来查询区块, getQueryResultForQueryString()函数通过交易里的字段来查询区块,目前只支持couchdb查询,leveldb不支持这样查。

7.4.2、 安装chaincode

现在我们来安装chaincode

1
2
3
4
5
6
7
8
9
10
11
curl -s -X POST \
  http://localhost:4000/chaincodes \
  -H "authorization: Bearer <put JSON Web Token here>" \
  -H "content-type: application/json" \
  -d '{
  "peers": ["peer0.org1.xuyao.com","peer1.org1.xuyao.com"],
  "chaincodeName":"mycc",
  "chaincodePath":"github.com/example_cc/go",
  "chaincodeType": "golang",
  "chaincodeVersion":"v0"
}'

chaincodeName是智能合约的名字, chaincodePath是智能合约的路径, chaincodeType是编写智能合约的语言,是golang和nodejs两种,chaincodeVersion是智能合约的版本号,值得注意的是如果是升级智能合约的话,需要加上upgrade: true的参数,而且版本号也要向上升级。这是升级的接口,如下:

1
2
3
4
5
6
7
8
9
10
11
12
curl -s -X POST \
  http://localhost:4000/chaincodes \
  -H "authorization: Bearer <put JSON Web Token here>" \
  -H "content-type: application/json" \
  -d '{
    "peers": ["peer0.org1.xuyao.com","peer1.org1.xuyao.com"],
    "chaincodeName":"mycc",
    "chaincodePath":"github.com/example_cc/go",
    "chaincodeType": "golang",
    "chaincodeVersion":"v1",
    "upgrade": "true"
}'

安装成功后,返回如下结果

1
{"success":true,"message":"Successfully install chaincode"}
7.4.3、实例化chaincode
1
2
3
4
5
6
7
8
9
10
curl -s -X POST \
  http://localhost:4000/channels/mychannel/chaincodes \
  -H "authorization: Bearer <put JSON Web Token here>" \
  -H "content-type: application/json" \
  -d '{
  "chaincodeName":"mycc",
  "chaincodeVersion":"v0",
  "chaincodeType": "golang",
  "args":["a","100","b","200"]
}'

成功返回结果

1
{"success":true,"message":"Successfully instantiate chaingcode in organization Org1 to the channel 'mychannel'"}
7.4.4、数据上链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
curl -s -X POST \
  http://localhost:4000/api/v1/save\
  -H "authorization: Bearer $ORG1_TOKEN" \
  -H "content-type: application/json" \
  -d '{
        "godsCode": "2018092900004",
        "jsonInfo":
          {
          "discern":"1",
          "godsIssuerName":"2",
          "godsIssuerCode":"3",
          "godsTypeName":"4",
          "godsMarketValue":"4",
          "companyName":"5",
          "issuePrice":"6",
          "godsCode":"7",
          "sort":"9",
          "orgCompanyName":"8",
          "orgCode":"",
          "reportId":"",
          "linkTime":"",
          "linkUser":"",
          "linkRole":"",
          "orgSignTime":"",
          "godsSignTime":"",
          "files":[{"fileName":"文件名称.xml","fileHash":"4564613243454546884464"}],
          "attr":[{"key":"name","value":"val"}],
          "companyArea":"所属国家/地区",
          "linkmanPhone":"企业联系电话",
          "linkmanEmail":"企业邮箱",
          "legalPerson":"法人姓名"
        } 
      }'
7.4.5、数据查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#通过区块查询
curl -s -X GET \
  "http://localhost:4000/channels/mychannel/blocks/1?peer=peer0.org1.example.com" \
  -H "authorization: Bearer <put JSON Web Token here>" \
  -H "content-type: application/json"

#通过TransactionID
curl -s -X GET \
http://localhost:4000/channels/mychannel/transactions/<put transaction id here>?peer=peer0.org1.example.com \
  -H "authorization: Bearer <put JSON Web Token here>" \
  -H "content-type: application/json"

#根据自己的业务ID查询
curl -s -X POST \
    "http://localhost:4000/api/v1/query" \
  -H "authorization: Bearer $ORG1_TOKEN" \
  -H "content-type: application/json" \
  -d '
    {
      "godsCode": "2018092900004"
    }
  '

返回结果

1
{"code":200,"message":"操作成功","data":[{"godsCode":"2018092900004","jsonInfo":{"companyArea":"所属国家/地区","companyName":"5","discern":"1","files":[{"fileHash":"4564613243454546884464","fileName":"文件名称.xml"}],"godsCode":"7","godsIssuerCode":"3","godsIssuerName":"2","godsMarketValue":"4","godsSignTime":"","godsTypeName":"4","issuePrice":"6","legalPerson":"法人姓名","linkRole":"","linkTime":"","linkUser":"","linkmanEmail":"企业邮箱","linkmanPhone":"企业联系电话","orgCode":"","orgCompanyName":"8","orgSignTime":"","reportId":"","sort":"9"},"userName":"doc"}]}
未完待续。。。

参考资料

1、https://github.com/hyperledger/fabric-samples/blob/release-1.3/balance-transfer/README.md
2、https://jwt.io/

Comments