Hyperledger Fabric chain code modification and testing (2)

In the previous article, after the modification of the chain code, there is still the problem that the data structure design is not reasonable enough. In the imagined scenario, a Raspberry Pi communicates with a group of meters through RS485, including a water meter, an electric meter and a gas meter. However, the actual situation Some Raspberry Pis may only be connected to one meter. For example, in the case of communicating with only one water meter, the Raspberry Pi can only obtain the two data of the instantaneous flow rate and the cumulative flow rate in the water meter. Then the rest of the original data structure Data The data is all empty, and the query results will also show these empty data, but in fact this is a meaningless thing. The original Data structure is as follows:

type Data struct {
	Flow_now	string `json:"flow_now(L/H)"`
	Flow_total   	string `json:"flow_total(L)"`
	Voltage	string `json:"voltage(V)"`
	Current	string `json:"current(A)"`
	Power		string `json:"power(W)"`
	Energy		string `json:"energy(kWh)"`
	Factor		string `json:"factor"`
	Emissions	string `json:"emissions(Kg)"`
	Time		string `json:"time"`
}

So I modified the chain code, separated the original Data, and divided it into the structure of the water meter data and the structure of the electric meter data, and then added a structure of the gas meter data to facilitate the next experiment. After the modification, the three structures as follows:

type Water struct {
	Waterflow_now	string `json:"waterflow_now(L/H)"`
	Waterflow_total string `json:"waterflow_total(L)"`
	Time		string `json:"time"`
}
type Electricity struct {
	Voltage	string `json:"voltage(V)"`
	Current	string `json:"current(A)"`
	Power		string `json:"power(W)"`
	Energy		string `json:"energy(kWh)"`
	Factor		string `json:"factor"`
	Emissions	string `json:"emissions(Kg)"`
	Time		string `json:"time"`
}
type Gas struct {
	Gasflow_now	string `json:"gasflow_now(LSPM)"`
	Gasflow_total	string `json:"gasflow_total(NCM)"`
	Time		string `json:"time"`
}

Each structure only contains the data that can be queried by the corresponding instrument. Time is a member contained in each structure, because I use the date as the Key value of each piece of data for easy query, and combine the date of the Key with this one The Time value of the data can know when the piece of data was obtained. However, during the subsequent debugging process, it was found that there is still a problem with writing this way, which will be mentioned later.

QueryResult structure:

type WaterQueryResult struct {
	Key    string `json:"Key"`
	Record *Water
}
type ElectricityQueryResult struct {
	Key	string `json:"Key"`
	Record *Electricity
}
type GasQueryResult struct {
	Key    string `json:"Key"`
	Record *Gas
}

QueryResult stores the result of chaincode query from World State.

initialization:

func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
	waters := []Water{
		Water{Waterflow_now: "0", Waterflow_total: "0", Time: "00:00"},
	}
	electricitys := []Electricity{
		Electricity{Voltage: "0", Current: "0", Power: "0", Energy: "0", Factor: "0", Emissions: "0", Time: "00:00"},
	}
	gass := []Gas{
		Gas{Gasflow_now: "0", Gasflow_total: "0", Time: "00:00"},
	}

	for _,water := range waters {
		dataAsBytes, _ := json.Marshal(water)
		err := ctx.GetStub().PutState("2022-07-20 001 000", dataAsBytes)

		if err != nil {
			return fmt.Errorf("Failed to put to world state. %s", err.Error())
		}
	}
	for _,electricity := range electricitys {
		dataAsBytes, _ := json.Marshal(electricity)
		err := ctx.GetStub().PutState("2022-07-20 002 000", dataAsBytes)

		if err != nil {
			return fmt.Errorf("Failed to put to world state. %s", err.Error())
		}
	}
	for _,gas := range gass {
		dataAsBytes, _ := json.Marshal(gas)
		err := ctx.GetStub().PutState("2022-07-20 003 000", dataAsBytes)

		if err != nil {
			return fmt.Errorf("Failed to put to world state. %s", err.Error())
		}
	}

	return nil
}

Chaincode initialization is a necessary step after installing the chaincode. During initialization, I added three pieces of data with the ID "XXX 000" to the State, and the values ​​are all 0. In fact, this value has no practical meaning and is only for initialization. The first three "XXX" in the ID are the serial number of the meter. For example, the serial number of the water meter on the Raspberry Pi is "001", the electric meter is "002", and the gas meter is "003". In the actual scenario, the number of each meter is completed in the Raspberry Pi that communicates with it, and the numbers of different water meters are also different. Maybe the number of the water meter in Raspberry Pi 1 is "001", and the number of the water meter in Raspberry Pi 2 is "004", so the instrument IDs added by initialization do not mean that all the meters of this type have this ID, I just complete the initialization process~

Add data (AddData):

func (s *SmartContract) AddData_water(ctx contractapi.TransactionContextInterface, dataNumber string, waterflow_now string, waterflow_total string, time string) error {
	water := Water{
		Waterflow_now:		waterflow_now,
		Waterflow_total:   	waterflow_total,
		Time:			time,
	}

	dataAsBytes, _ := json.Marshal(water)

	return ctx.GetStub().PutState(dataNumber, dataAsBytes)
}
func (s *SmartContract) AddData_electricity(ctx contractapi.TransactionContextInterface, dataNumber string, voltage string, current string, power string, energy string, factor string, emissions string, time string) error {
	electricity := Electricity{
		Voltage:	voltage,
		Current:	current,
		Power:		power,
		Energy:	energy,
		Factor:	factor,
		Emissions:	emissions,
		Time:		time,
	}

	dataAsBytes, _ := json.Marshal(electricity)

	return ctx.GetStub().PutState(dataNumber, dataAsBytes)
}
func (s *SmartContract) AddData_gas(ctx contractapi.TransactionContextInterface, dataNumber string, gasflow_now string, gasflow_total string, time string) error {
	gas := Gas{
		Gasflow_now:		gasflow_now,
		Gasflow_total:   	gasflow_total,
		Time:			time,
	}

	dataAsBytes, _ := json.Marshal(gas)

	return ctx.GetStub().PutState(dataNumber, dataAsBytes)
}

The structure is separated, so the method of adding data must also be separated. When adding data, I need to figure out what form the data is stored in World State, so that the data can be extracted when querying. After learning the go language, I know json The function .Marshal converts the structure data into json form, that is, World State stores data in json format. This json format is equivalent to a formatted string here, which can pass values ​​to and from the structure. So whether it is the Water type I defined earlier or the Gas type, etc., they are all json types when State is stored. Then there will be a problem. When querying data, how do I know whether the json type is Water type or Gas type before conversion? Of course, you don't need to consider this issue when querying a single data:

func (s *SmartContract) QueryData_water(ctx contractapi.TransactionContextInterface, dataNumber string) (*Water, error) {
	dataAsBytes, err := ctx.GetStub().GetState(dataNumber)

	if err != nil {
		return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
	}

	if dataAsBytes == nil {
		return nil, fmt.Errorf("%s does not exist", dataNumber)
	}

	water := new(Water)
	_ = json.Unmarshal(dataAsBytes, water)

	return water, nil
}
func (s *SmartContract) QueryData_electricity(ctx contractapi.TransactionContextInterface, dataNumber string) (*Electricity, error) {
	dataAsBytes, err := ctx.GetStub().GetState(dataNumber)

	if err != nil {
		return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
	}

	if dataAsBytes == nil {
		return nil, fmt.Errorf("%s does not exist", dataNumber)
	}

	electricity := new(Electricity)
	_ = json.Unmarshal(dataAsBytes, electricity)

	return electricity, nil
}
func (s *SmartContract) QueryData_gas(ctx contractapi.TransactionContextInterface, dataNumber string) (*Gas, error) {
	dataAsBytes, err := ctx.GetStub().GetState(dataNumber)

	if err != nil {
		return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
	}

	if dataAsBytes == nil {
		return nil, fmt.Errorf("%s does not exist", dataNumber)
	}

	gas := new(Gas)
	_ = json.Unmarshal(dataAsBytes, gas)

	return gas, nil
}

Because the query of a single data is based on the Key value of this data, the Key value is designed to ensure that each piece of data has a unique Key, and the Key itself contains the ID that identifies the instrument type, so as long as the Key value entered during the query If it is correct, the result of the query will not be wrong.

There was a problem when querying all the data. Because the data structure of different tables is different, the original QueryAllDatas should be divided into three methods:

func (s *SmartContract) QueryAllDatas_water(ctx contractapi.TransactionContextInterface) ([]WaterQueryResult, error) {
	startKey := ""
	endKey := ""

	resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)

	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	results := []WaterQueryResult{}

	for resultsIterator.HasNext() {	
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		water := new(Water)
		_ = json.Unmarshal(queryResponse.Value, water)
		if *water != (Water{}){
		queryResult := WaterQueryResult{Key: queryResponse.Key, Record: water}
		results = append(results, queryResult)
		}
	}

	return results, nil
}
func (s *SmartContract) QueryAllDatas_electricity(ctx contractapi.TransactionContextInterface) ([]ElectricityQueryResult, error) {
	startKey := ""
	endKey := ""

	resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)

	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	results := []ElectricityQueryResult{}

	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		electricity := new(Electricity)
		_ = json.Unmarshal(queryResponse.Value, electricity)
		if *electricity != (Electricity{}){
		queryResult := ElectricityQueryResult{Key: queryResponse.Key, Record: electricity}
		results = append(results, queryResult)
		}
	}

	return results, nil
}
func (s *SmartContract) QueryAllDatas_gas(ctx contractapi.TransactionContextInterface) ([]GasQueryResult, error) {
	startKey := ""
	endKey := ""

	resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)

	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	results := []GasQueryResult{}

	for resultsIterator.HasNext() {	
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		gas := new(Gas)
		_ = json.Unmarshal(queryResponse.Value, gas)
		if *gas != (Gas{}){
		queryResult := GasQueryResult{Key: queryResponse.Key, Record: gas}
		results = append(results, queryResult)
		}
	}

	return results, nil
}

During initialization, I added three different types of data. When I called these three queries, I found that all three pieces of data were returned, and there were two pieces of waste data. For example, when I called QueryAllDatas_water, one of the three pieces of data returned was correct water meter data. However, the other two data are empty except for Time, which is the value "00:00" at the time of initialization, and the IDs are "002 000" and "003 000" respectively, which means that the data of the electric meter and the gas meter are also deleted at this time. It is wrongly output, which is obviously unreasonable. I didn’t notice that Time has value at the beginning, so I simply added a condition for judging the structure as empty. If it is not empty, perform the subsequent append operation, and run it again. This is still the case, but later I saw that the Time value of each piece of data is not empty, so I have to change the method.

From the above error, I can judge that the json.Marshal function will not report an error and will still execute when the data in the form of json does not correspond to each member variable of the structure, such as the voltage and current of the data with the ID "002 000", etc. Both are empty, and Time is initialized "00:00", so json.Marshal will pass values ​​to the corresponding parts when the members of the two formal parameters do not completely correspond, and do not operate on the non-corresponding parts, so it is empty . There are two ways to solve this problem. One is to judge whether the new structure variable is empty except for Time after passing the value of json.Marshal. If so, the subsequent append operation will not be performed; here I use another Method, modify the json parsing value of Time in each data structure:

type Water struct {
	Waterflow_now	string `json:"waterflow_now(L/H)"`
	Waterflow_total string `json:"waterflow_total(L)"`
	Time		string `json:"water_time"`
}
type Electricity struct {
	Voltage	string `json:"voltage(V)"`
	Current	string `json:"current(A)"`
	Power		string `json:"power(W)"`
	Energy		string `json:"energy(kWh)"`
	Factor		string `json:"factor"`
	Emissions	string `json:"emissions(Kg)"`
	Time		string `json:"electricity_time"`
}
type Gas struct {
	Gasflow_now	string `json:"gasflow_now(LSPM)"`
	Gasflow_total	string `json:"gasflow_total(NCM)"`
	Time		string `json:"gas_time"`
}

Because at this time, I thought about the value transfer of json.Marshal should rely on the mapping between json and structure member variables. Originally, the time mapping from member variable Time to json in the three structures is the same, so only Time is changed. The value was changed, so I changed the time of each json to a different way of writing, which solved this problem.

Also note that although I haven't done an experiment, I think it is only effective to modify the time of json, because if only the Time in the structure is modified, even if it is changed to three different ways of writing, the attributes stored in the State are all called time, so it seems that every data in json has the common attribute of time, and finally json.

Add and query operation results:

To sum up, the process of changing the chaincode is quite cumbersome. It’s not that the code is hard to change. The main reason is that running and debugging is too troublesome. Every time something goes wrong, you have to re-open the channel, package the chaincode, install the chaincode, and node authentication. , It takes so long to enter a command, but fortunately there are not many places to change. After the modification, I paid more attention to the rationality of the code, and became more familiar with the Go language.

Guess you like

Origin blog.csdn.net/qq_43824745/article/details/126383642