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

7、创建channel和chaincode

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

7.1、启动nodejs服务

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

npm install

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


项目的基本文件

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

./runApp.sh

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

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里注册用户,生成证书的代码

///////////////////////////////////////////////////////////////////////////////
///////////////////////// 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返回给用户
 下面我们来调用一个这个接口

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

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

{"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代码可以看这里

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文件路径,创建成功,返回如下结果

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

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

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"]
}'

返回结果

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

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

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

curl -s -X POST \
  http://localhost:4000/chaincodes \
  -H "authorization: Bearer " \
  -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的参数,而且版本号也要向上升级。这是升级的接口,如下:

curl -s -X POST \
  http://localhost:4000/chaincodes \
  -H "authorization: Bearer " \
  -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"
}'

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

{"success":true,"message":"Successfully install chaincode"}
7.4.3、实例化chaincode
curl -s -X POST \
  http://localhost:4000/channels/mychannel/chaincodes \
  -H "authorization: Bearer " \
  -H "content-type: application/json" \
  -d '{
    "chaincodeName":"mycc",
    "chaincodeVersion":"v0",
    "chaincodeType": "golang",
    "args":["a","100","b","200"]
}'

成功返回结果

{"success":true,"message":"Successfully instantiate chaingcode in organization Org1 to the channel 'mychannel'"}
7.4.4、数据上链
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、数据查询
#通过区块查询
curl -s -X GET \
  "http://localhost:4000/channels/mychannel/blocks/1?peer=peer0.org1.example.com" \
  -H "authorization: Bearer " \
  -H "content-type: application/json"

#通过TransactionID
curl -s -X GET \
http://localhost:4000/channels/mychannel/transactions/?peer=peer0.org1.example.com \
  -H "authorization: Bearer " \
  -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"
    }
  '

返回结果

{"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/

你可能感兴趣的:(Fabric多台服务器的部署(四))