Fabric Marbles 分析
chaincode
chaincode 主要结构
// 声明包 package main // 引入需要使用的包 import ( "fmt" "strconv" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) // 声明 SimpleChaincode 结构体 type SimpleChaincode struct { } // 定义资产, 在账本中将会存储 marble 和 owner // 定义 marble 结构体 // 后面跟着的 json 表示将结构体序列化成 JSON 格式时的键名 type Marble struct { ObjectType string `json:"docType"` //field for couchdb Id string `json:"id"` //the fieldtags are needed to keep case from bouncing around Color string `json:"color"` Size int `json:"size"` //size in mm of marble Owner OwnerRelation `json:"owner"` } // 定义 owner 结构体 type Owner struct { ObjectType string `json:"docType"` //field for couchdb Id string `json:"id"` Username string `json:"username"` Company string `json:"company"` Enabled bool `json:"enabled"` //disabled owners will not be visible to the application } type OwnerRelation struct { Id string `json:"id"` Username string `json:"username"` //this is mostly cosmetic/handy, the real relation is by Id not Username Company string `json:"company"` //this is mostly cosmetic/handy, the real relation is by Id not Company } // 入口函数 func main() { err := shim.Start(new(SimpleChaincode)) if err != nil { fmt.Printf("Error starting Simple chaincode - %s", err) } } // Init() 接口, 用于初始化 chaincode func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { fmt.Println("Marbles Is Starting Up") funcName, args := stub.GetFunctionAndParameters() // 获取 SDK 传入的函数名和参数 var number int var err error txId := stub.GetTxID() // 获取构建交易提案时生成的交易ID fmt.Println("Init() is running") fmt.Println("Transaction ID:", txId) fmt.Println(" GetFunctionAndParameters() function:", funcName) fmt.Println(" GetFunctionAndParameters() args count:", len(args)) fmt.Println(" GetFunctionAndParameters() args found:", args) // expecting 1 arg for instantiate or upgrade if len(args) == 1 { fmt.Println(" GetFunctionAndParameters() arg[0] length", len(args[0])) // args 的长度为 0 时, 用于升级 chaincode, 此处未实现 if len(args[0]) == 0 { fmt.Println(" Uh oh, args[0] is empty...") } else { fmt.Println(" Great news everyone, args[0] is not empty") // convert numeric string to integer number, err = strconv.Atoi(args[0]) if err != nil { return shim.Error("Expecting a numeric string argument to Init() for instantiate") } // 测试写入账本 err = stub.PutState("selftest", []byte(strconv.Itoa(number))) if err != nil { return shim.Error(err.Error()) //self-test fail } } } // showing the alternative argument shim function alt := stub.GetStringArgs() fmt.Println(" GetStringArgs() args count:", len(alt)) fmt.Println(" GetStringArgs() args found:", alt) // store compatible marbles application version err = stub.PutState("marbles_ui", []byte("4.0.1")) if err != nil { return shim.Error(err.Error()) } fmt.Println("Ready for action") //self-test pass return shim.Success(nil) } // Invoke() 接口 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println(" ") fmt.Println("starting invoke, for - " + function) // 根据传入的参数来确定调用哪些方法 if function == "init" { //initialize the chaincode state, used as reset return t.Init(stub) } else if function == "read" { //generic read ledger return read(stub, args) } else if function == "write" { //generic writes to ledger return write(stub, args) } else if function == "delete_marble" { //deletes a marble from state return delete_marble(stub, args) } else if function == "init_marble" { //create a new marble return init_marble(stub, args) } else if function == "set_owner" { //change owner of a marble return set_owner(stub, args) } else if function == "init_owner"{ //create a new marble owner return init_owner(stub, args) } else if function == "read_everything"{ //read everything, (owners + marbles + companies) return read_everything(stub) } else if function == "getHistory"{ //read history of a marble (audit) return getHistory(stub, args) } else if function == "getMarblesByRange"{ //read a bunch of marbles by start and stop id return getMarblesByRange(stub, args) } else if function == "disable_owner"{ //disable a marble owner from appearing on the UI return disable_owner(stub, args) } // error out fmt.Println("Received unknown invoke function name - " + function) return shim.Error("Received unknown invoke function name - '" + function + "'") } // 查询, 未实现 func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface) pb.Response { return shim.Error("Unknown supported call - Query()") }
Invoke() 接口
读账本 read()
func read(stub shim.ChaincodeStubInterface, args []string) pb.Response { var key, jsonResp string var err error fmt.Println("starting read") if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting key of the var to query") } // input sanitation err = sanitize_arguments(args) if err != nil { return shim.Error(err.Error()) } key = args[0] valAsbytes, err := stub.GetState(key) // 根据 key 获取 value if err != nil { jsonResp = "{\"Error\":\"Failed to get state for " + key + "\"}" return shim.Error(jsonResp) } fmt.Println("- end read") return shim.Success(valAsbytes) //send it onward }
写账本 write()
func write(stub shim.ChaincodeStubInterface, args []string) pb.Response { var key, value string var err error fmt.Println("starting write") if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2. key of the variable and value to set") } // input sanitation err = sanitize_arguments(args) if err != nil { return shim.Error(err.Error()) } key = args[0] //rename for funsies value = args[1] err = stub.PutState(key, []byte(value)) // 根据 key 写入 value if err != nil { return shim.Error(err.Error()) } fmt.Println("- end write") return shim.Success(nil) }
删除 delete_marble()
根据 marble 的 id 来删除指定的 marble.
func delete_marble(stub shim.ChaincodeStubInterface, args []string) (pb.Response) { fmt.Println("starting delete_marble") if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } // input sanitation err := sanitize_arguments(args) if err != nil { return shim.Error(err.Error()) } id := args[0] // 获取 marble id authed_by_company := args[1] // get the marble marble, err := get_marble(stub, id) // 调用的是 GetState() 接口 if err != nil{ fmt.Println("Failed to find marble by id " + id) return shim.Error(err.Error()) } // check authorizing company (see note in set_owner() about how this is quirky) if marble.Owner.Company != authed_by_company{ return shim.Error("The company '" + authed_by_company + "' cannot authorize deletion for '" + marble.Owner.Company + "'.") } // remove the marble err = stub.DelState(id) // 删除该 marble if err != nil { return shim.Error("Failed to delete state") } fmt.Println("- end delete_marble") return shim.Success(nil) }
初始化 init_marble()
生成 marble, 并写入账本.
func init_marble(stub shim.ChaincodeStubInterface, args []string) (pb.Response) { var err error fmt.Println("starting init_marble") if len(args) != 5 { return shim.Error("Incorrect number of arguments. Expecting 5") } //input sanitation err = sanitize_arguments(args) if err != nil { return shim.Error(err.Error()) } // SDK 中应填入的一系列参数, 并进行一系列验证 id := args[0] color := strings.ToLower(args[1]) owner_id := args[3] authed_by_company := args[4] size, err := strconv.Atoi(args[2]) if err != nil { return shim.Error("3rd argument must be a numeric string") } //check if new owner exists owner, err := get_owner(stub, owner_id) if err != nil { fmt.Println("Failed to find owner - " + owner_id) return shim.Error(err.Error()) } //check authorizing company (see note in set_owner() about how this is quirky) if owner.Company != authed_by_company{ return shim.Error("The company '" + authed_by_company + "' cannot authorize creation for '" + owner.Company + "'.") } //check if marble id already exists marble, err := get_marble(stub, id) if err == nil { fmt.Println("This marble already exists - " + id) fmt.Println(marble) return shim.Error("This marble already exists - " + id) //all stop a marble by this id exists } //build the marble json string manually str := `{ "docType":"marble", "id": "` + id + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": { "id": "` + owner_id + `", "username": "` + owner.Username + `", "company": "` + owner.Company + `" } }` err = stub.PutState(id, []byte(str)) // 存入账本 if err != nil { return shim.Error(err.Error()) } fmt.Println("- end init_marble") return shim.Success(nil) }
设置所有者 set_owner()
func set_owner(stub shim.ChaincodeStubInterface, args []string) pb.Response { var err error fmt.Println("starting set_owner") // this is quirky // todo - get the "company that authed the transfer" from the certificate instead of an argument // should be possible since we can now add attributes to the enrollment cert // as is.. this is a bit broken (security wise), but it's much much easier to demo! holding off for demos sake if len(args) != 3 { return shim.Error("Incorrect number of arguments. Expecting 3") } // input sanitation err = sanitize_arguments(args) if err != nil { return shim.Error(err.Error()) } var marble_id = args[0] var new_owner_id = args[1] var authed_by_company = args[2] fmt.Println(marble_id + "->" + new_owner_id + " - |" + authed_by_company) // check if user already exists owner, err := get_owner(stub, new_owner_id) if err != nil { return shim.Error("This owner does not exist - " + new_owner_id) } // get marble's current state marbleAsBytes, err := stub.GetState(marble_id) if err != nil { return shim.Error("Failed to get marble") } // 将数据反序列化, 修改后再序列化为 JSON 格式存储 res := Marble{} json.Unmarshal(marbleAsBytes, &res) //un stringify it aka JSON.parse() // check authorizing company if res.Owner.Company != authed_by_company{ return shim.Error("The company '" + authed_by_company + "' cannot authorize transfers for '" + res.Owner.Company + "'.") } // transfer the marble res.Owner.Id = new_owner_id //change the owner res.Owner.Username = owner.Username res.Owner.Company = owner.Company // 修改属性后, 序列化为 JSON 格式 jsonAsBytes, _ := json.Marshal(res) //convert to array of bytes err = stub.PutState(args[0], jsonAsBytes) // 重新存储到账本 if err != nil { return shim.Error(err.Error()) } fmt.Println("- end set owner") return shim.Success(nil) }
初始化所有者 init_owner()
func init_owner(stub shim.ChaincodeStubInterface, args []string) pb.Response { var err error fmt.Println("starting init_owner") if len(args) != 3 { return shim.Error("Incorrect number of arguments. Expecting 3") } //input sanitation err = sanitize_arguments(args) if err != nil { return shim.Error(err.Error()) } var owner Owner owner.ObjectType = "marble_owner" owner.Id = args[0] owner.Username = strings.ToLower(args[1]) owner.Company = args[2] owner.Enabled = true fmt.Println(owner) //check if user already exists _, err = get_owner(stub, owner.Id) if err == nil { fmt.Println("This owner already exists - " + owner.Id) return shim.Error("This owner already exists - " + owner.Id) } //store user ownerAsBytes, _ := json.Marshal(owner) //convert to array of bytes err = stub.PutState(owner.Id, ownerAsBytes) //store owner by its Id if err != nil { fmt.Println("Could not store user") return shim.Error(err.Error()) } fmt.Println("- end init_owner marble") return shim.Success(nil) }
读取所有数据 read_everything()
func read_everything(stub shim.ChaincodeStubInterface) pb.Response { type Everything struct { Owners []Owner `json:"owners"` Marbles []Marble `json:"marbles"` } var everything Everything // ---- Get All Marbles ---- // resultsIterator, err := stub.GetStateByRange("m0", "m9999999999999999999") if err != nil { return shim.Error(err.Error()) } defer resultsIterator.Close() for resultsIterator.HasNext() { aKeyValue, err := resultsIterator.Next() if err != nil { return shim.Error(err.Error()) } queryKeyAsStr := aKeyValue.Key queryValAsBytes := aKeyValue.Value fmt.Println("on marble id - ", queryKeyAsStr) var marble Marble json.Unmarshal(queryValAsBytes, &marble) //un stringify it aka JSON.parse() everything.Marbles = append(everything.Marbles, marble) //add this marble to the list } fmt.Println("marble array - ", everything.Marbles) // ---- Get All Owners ---- // ownersIterator, err := stub.GetStateByRange("o0", "o9999999999999999999") if err != nil { return shim.Error(err.Error()) } defer ownersIterator.Close() for ownersIterator.HasNext() { aKeyValue, err := ownersIterator.Next() if err != nil { return shim.Error(err.Error()) } queryKeyAsStr := aKeyValue.Key queryValAsBytes := aKeyValue.Value fmt.Println("on owner id - ", queryKeyAsStr) var owner Owner json.Unmarshal(queryValAsBytes, &owner) //un stringify it aka JSON.parse() if owner.Enabled { //only return enabled owners everything.Owners = append(everything.Owners, owner) //add this marble to the list } } fmt.Println("owner array - ", everything.Owners) //change to array of bytes everythingAsBytes, _ := json.Marshal(everything) //convert to array of bytes return shim.Success(everythingAsBytes) }
获取历史数据 getHistory()
func getHistory(stub shim.ChaincodeStubInterface, args []string) pb.Response { type AuditHistory struct { TxId string `json:"txId"` Value Marble `json:"value"` } var history []AuditHistory; var marble Marble if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } marbleId := args[0] fmt.Printf("- start getHistoryForMarble: %s\n", marbleId) // Get History resultsIterator, err := stub.GetHistoryForKey(marbleId) if err != nil { return shim.Error(err.Error()) } defer resultsIterator.Close() for resultsIterator.HasNext() { historyData, err := resultsIterator.Next() if err != nil { return shim.Error(err.Error()) } var tx AuditHistory tx.TxId = historyData.TxId //copy transaction id over json.Unmarshal(historyData.Value, &marble) //un stringify it aka JSON.parse() if historyData.Value == nil { //marble has been deleted var emptyMarble Marble tx.Value = emptyMarble //copy nil marble } else { json.Unmarshal(historyData.Value, &marble) //un stringify it aka JSON.parse() tx.Value = marble //copy marble over } history = append(history, tx) //add this tx to the list } fmt.Printf("- getHistoryForMarble returning:\n%s", history) //change to array of bytes historyAsBytes, _ := json.Marshal(history) //convert to array of bytes return shim.Success(historyAsBytes) }
根据范围查找 getMarblesByRange()
func getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } startKey := args[0] endKey := args[1] resultsIterator, err := stub.GetStateByRange(startKey, endKey) if err != nil { return shim.Error(err.Error()) } defer resultsIterator.Close() // buffer is a JSON array containing QueryResults var buffer bytes.Buffer buffer.WriteString("[") bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { aKeyValue, err := resultsIterator.Next() if err != nil { return shim.Error(err.Error()) } queryResultKey := aKeyValue.Key queryResultValue := aKeyValue.Value // 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(queryResultKey) buffer.WriteString("\"") buffer.WriteString(", \"Record\":") // Record is a JSON object, so we write as-is buffer.WriteString(string(queryResultValue)) buffer.WriteString("}") bArrayMemberAlreadyWritten = true } buffer.WriteString("]") fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes()) }
disable_owner()
func disable_owner(stub shim.ChaincodeStubInterface, args []string) pb.Response { var err error fmt.Println("starting disable_owner") if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } // input sanitation err = sanitize_arguments(args) if err != nil { return shim.Error(err.Error()) } var owner_id = args[0] var authed_by_company = args[1] // get the marble owner data owner, err := get_owner(stub, owner_id) if err != nil { return shim.Error("This owner does not exist - " + owner_id) } // check authorizing company if owner.Company != authed_by_company { return shim.Error("The company '" + authed_by_company + "' cannot change another companies marble owner") } // disable the owner owner.Enabled = false jsonAsBytes, _ := json.Marshal(owner) //convert to array of bytes err = stub.PutState(args[0], jsonAsBytes) //rewrite the owner if err != nil { return shim.Error(err.Error()) } fmt.Println("- end disable_owner") return shim.Success(nil) }
小结
基本上都是序列化与反序列化, PutState() 与 GetState(). 熟悉这些接口, 对自己写 chaincode 有较大好处.
运维
SDK
在服务端将运动数据转化成运动能量之后, 调用 SDK, 利用 chaincode 将相关数据写入账本.
Install Chaincode
- fcw.install_chaincode(obj, options, cb_done)
- deploy_cc.install_chaincode(obj, options, cb_done)
构造交易提案, 发送到背书节点, 主要包括 chaincode_id, chaincode_version 等信息.
- client.installChaincode(request)
Instantiate Chaincode
- fcw.instantiate_chaincode(obj, options, cb_done)
- deploy_cc.instantiate_chaincode(obj, options, cb)
构造交易提案, 发送到背书节点, 主要包括 chaincode_id, chaincode_version, 节点的 IP 与端口, 交易 ID 等信息.
- channel.intialize()
- channel.sendInstantiateProposal(request)
- channel.sendTransaction(request)
Upgrade Chaincode
- fcw.upgrade_chaincode(obj, options, cb_done)
- deploy_cc.upgrade_chaincode(obj, options, cb_done)
构造交易提案, 发送到背书节点, 主要包括 chaincode_id, chaincode_version, 调用chaincode 的函数名, 参数, 交易 ID 等信息.
- channel.intialize()
- channel.sendUpgradeProposal(request)
- channel.sendTransaction(request)
上传步数
赚取运动能量的公式
每个用户所赚取的运动能量的数量, 与当天所有用户的运动数据有关. 如:
单个用户赚取的运动能量 = 当天发放的运动能量总数 \(\times\) 该用户的运动数据 / 所有用户的运动数据总和
SDK
Invoke Chaincode
- fcw.invoke_chaincode(obj, options, cb_done)
- invoke_cc.invoke_chaincode(obj, options, function)
构造交易提案, 主要包括 chaincode_id, chaincode的函数名, 参数, 交易 ID 等信息.
- setup_event_hub(options)
设置事件, 当交易写入完成时(或其他条件), 调用此函数.
eventHub.setPeerAddr(): 设置节点的地址和端口
eventHub.connect()
eventHub.registerTxEvent(request.txId.getTransactionID(), ...): 根据交易 ID 注册事件, 随着交易的进行, 如果触发事件, 则执行这个函数.
- channel.sendTransactionProposal(request)
- channel.sendTransaction(request)
查询
SDK
Query Chaincode
- fcw.query_chaincode(obj, options, cb_done)
- query_cc.query_chaincode(obj, options, function)
构造交易提案, 主要包括 chaincode_id, function, args, txID 等信息.
- channel.queryByChaincode(request)
错误
Error: Error endorsing chaincode: rpc error: code = Unknown desc = failed to execute transaction: timeout expired while executing transaction
有多种原因会导致这个错误. 有可能是修改了 chaincode, 但是编译出错了, 或者 chaincode 容器没有启动, 等等.
我遇到这个问题, 先检查是否把 fabcar 目录下的 hfc-key-store 和 ~/.hfc-key-store 删除了, 如果没有, 则删除之. 再次启动时, 问题依旧. 因此我推测可能是 chaincode 容器未启动成功, 输入命令 docker ps -a 后, 未看到 chaincode 容器, 再输入命令 docker images, 发现有很多之前启动 marbles 时的 chaincode 镜像未删除, 依次删除之后, 启动成功.
如果 chaincode 容器启动成功了, 可以再查看该容器的日志, 进一步排查原因.
Generated by Emacs 25.x(Org mode 8.x)
Copyright © 2014 - Pinvon - Powered by EGO