Pinvon's Blog

所见, 所闻, 所思, 所想

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 容器启动成功了, 可以再查看该容器的日志, 进一步排查原因.

Comments

使用 Disqus 评论
comments powered by Disqus