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