Hogeschool-Windesheim / conditioned-goods-use-case

GNU General Public License v3.0
0 stars 4 forks source link

Check chaincode code #1

Open gwesseling opened 3 years ago

gwesseling commented 3 years ago

Hi Andrea,

Can you review the written TypeScript code for the chaincode (located on the chaincode branch)? Let us know if you find something that we can improve.

Thanks!

drew3110 commented 3 years ago

Hi guys, I went through the chaincode and took some notes.

Important high level point: the data model is built so that you have 1 shipment which contains exactly 1 measurement. Every time there's a new measurement you create a whole new shipment. I think it makes more sense to have one shipment which contains multiple measurements, without creating a new shipment object every time. It can be some kind of list, like an array. This would also make it easier to retrieve history.
I think the current implementation might also create a problem in hardware requirement: if you create a new object with every transaction you're basically demanding the system to keep track in the state of all those objects: this would make RAM consumption to increase indefinitely.
It might not be the case if HLF implements some sort of management to avoid this situation, but I don't know anyone.
I think it's valuable to test this, consider to write a script to automatically submit hundreds/thousands of txs and register RAM consumption to see if it decreases / gets steady at some point or not.

More specific comments:

https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/blob/c97b08906b6ad7b39d7413136d235fabc2850f63/chaincode/src/contracts/ShipmentContract.ts#L29-L41

https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/blob/c97b08906b6ad7b39d7413136d235fabc2850f63/chaincode/src/contracts/ShipmentContract.ts#L59-L64

https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/blob/c97b08906b6ad7b39d7413136d235fabc2850f63/chaincode/src/contracts/ShipmentContract.ts#L79-L91

https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/blob/c97b08906b6ad7b39d7413136d235fabc2850f63/chaincode/src/contracts/ShipmentContract.ts#L96

https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/blob/c97b08906b6ad7b39d7413136d235fabc2850f63/chaincode/src/contracts/MeasurementContract.ts#L45

https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/blob/c97b08906b6ad7b39d7413136d235fabc2850f63/chaincode/src/contracts/MeasurementContract.ts#L65

https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/blob/c97b08906b6ad7b39d7413136d235fabc2850f63/chaincode/src/contracts/MeasurementContract.ts#L83-L88

Other smaller comments:

Feel free to comment further if you need more and/or have questions 😊

gwesseling commented 3 years ago

Hey Andrea, Thanks for your detailed feedback on the chaincode. You bring up some good point and I'll respond to them down below.

Important high level point: the data model is built so that you have 1 shipment which contains exactly 1 measurement. Every time there's a new measurement you create a whole new shipment. I think it makes more sense to have one shipment which contains multiple measurements, without creating a new shipment object every time. It can be some kind of list, like an array. This would also make it easier to retrieve history. I think the current implementation might also create a problem in hardware requirement: if you create a new object with every transaction you're basically demanding the system to keep track in the state of all those objects: this would make RAM consumption to increase indefinitely. It might not be the case if HLF implements some sort of management to avoid this situation, but I don't know anyone. I think it's valuable to test this, consider to write a script to automatically submit hundreds/thousands of txs and register RAM consumption to see if it decreases / gets steady at some point or not.

Your concerns about the RAM usage is a valid point. And building a script to automatically submit a large number of transactions is a good suggestion. That said we are not creating a new shipment object every time we add a measurement. We only create a shipment on the AddShipment transaction. After that, only the measurement will be updated with the AddMeasurement transaction. The data model is designed this way to easily retrieve the latest measurement since querying the history isn't that hard.

There's already a check for "no shipment" in getShipment

Great find. Something I looked over.

You have a function to check existence but you actually never use it

This function is not specifically built for internal use only but also can be used as a query outside the chaincode for example in the API. I recently made a change to also use it in a function inside the chaincode.

Another reason why we are not using the ShipmentExist in the business logic is that the function performs an additional query to the ledger. This is in some cases unnecessary because we already fetched the shipment itself. Performing an additional query on the ledger wouldn't make a big difference on a small number of calls but it would on thousands of calls.

You check this differently in different functions, sometimes only if (!shipment), sometimes if (!shipment || shipment.length === 0) and now shipment && shipment.length > 0. I find the last one the most elegant, though a bit less readable. Maybe check if there's a convention (and take advantage of this function you wrote instead of manually checking every time).

I agree that this is a little bit inconsistent and I will make some changes to make it more consistent.

RegisterSensor should probably rather return the sensor ID or error, instead of true/false like if it verified if a sensor is registered.

Doesn't make much of a difference to me, but I can see why some people would like that. So I will change it.

I suggest to rename it to sensorIsRegistered to avoid confusion

I prefer HasSensor a bit more, but I agree that it can be confusing. So I will change it either way.

This isn't filtering anything, afaiu Mapping should include the times of the measurements too

It actually does filter something. Temperature is an optional attribute and will be undefined when a new shipment is created. Therefore, there always will be at least one version of a shipment without a temperature. Temperature is also a measurement object and will return the timestamp too.

https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/blob/c97b08906b6ad7b39d7413136d235fabc2850f63/chaincode/src/models/Shipment.ts#L9

Here is a codepen as an example https://codepen.io/gwesseling/pen/bGqNgGV?editors=0012

With an eye to the future, timestamp should probably be passed as explicit parameter so that it's set by the IoT device. This way the IoT device can buffer some measurements when there's no internet connection and send them later registering the right time.

Thanks for suggesting the edge case. This is a really good point and I will definitely implement this.

Should the function return only temperature or the whole measurement object?

A measurement object.

I don't understand the logic of this section. If you want to check that the temperature is in range, why don't you just do return isInRange(newVal, minVal, maxVal) ?

You are right that would be an option. But this small "algorithm" also checks if the previous value of the temperature was inside the range. if the previous value was inside the range and the current value isn't, it will return false and we will send an email. This is to prevent the chaincode from spamming emails.

That being said, why do we even want to have an ad-hoc function to check if a value is in range?

Because we want to validate if the (temperature) values are within the range defined inside the SLA. I am not sure where you are aiming at with this comment. But I'm open to suggestions.

And why do we want to treat temperature as a String instead of a float (or just an integer)?

All the values retrieved by queries and transactions inside the chaincode are by default strings. This way Hyperledger Fabric can support multiple languages (Node, Java and GO). Because strings are pretty much the same in all of them (I guess). However, that said I think some values are still written as a string to the chaincode itself such as temperature value. I will take a look at this and make appropriate changes where necessary.

You're throwing generic Error a lot. I don't know enough TS to know if this is normal or if there should be a more specific one.

You can make more specific errors with JS/TS by extending the Error class. But I personally have seen many people making use of this. In general (at least in my experience) people use the generic error most of the time. But this definitely something we can look at if we have extra time.

The naming convention for functions is likeThisName(), not LikeThisName() both in JS and (probably) in TS. It'd be nice some small refactoring here and there :)

I didn't know there were actually default naming conversion for JavaScript. It is also partly personal preference I think. But normally I use lowercase function names, but this time I followed the Hyperledger Fabric samples. I know it does not make a difference but just to be sure I decided to do uppercase. I will change them to lowercase since I normally do this too.

Thanks again for the detailed comments. Let me know if I misinterpreted your feedback or if I am wrong. I am happy to discuss and elaborate on it more.

gwesseling commented 3 years ago

I made some changes based on your feedback :) https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/tree/fix-chaincode

drew3110 commented 3 years ago

Hi Gerard! Thanks for this kind and detailed reply! 😊

I thought that "putState" was creating new states every time (which can be seen this way, but it also "consumes" states with the same ID, so RAM consumption won't increase, if my understanding is correct).

Another reason why we are not using the ShipmentExist in the business logic is that the function performs an additional query to the ledger. This is in some cases unnecessary because we already fetched the shipment itself. Performing an additional query on the ledger wouldn't make a big difference on a small number of calls but it would on thousands of calls.

Good point!

I prefer HasSensor a bit more, but I agree that it can be confusing. So I will change it either way.

Just to make more clear my point: the first thing I thought when I read "hasSensor" was "it returns true if it has at least 1 sensor registered", but it's not what it's doing (you pass a specific ID and check if that sensor is registered.

It actually does filter something. Temperature is an optional attribute

Ah true!

Because we want to validate if the (temperature) values are within the range defined inside the SLA. I am not sure where you are aiming at with this comment. But I'm open to suggestions.

Can you expand again SLA?
I think my comment referred to the fact that you are using isInRange, and I was wondering whether it wasn't easier / simpler to have (temp < max && temp > min). Not something big, just a small side note :)

By the way, I want to mention that as far as I know you're doing a good job. The application is indeed very simple but it can work well to prove a concept, which is our ultimate goal :) Keep it up! 💪

gwesseling commented 3 years ago

Hi Andrea! Thanks for the kind words.

I thought that "putState" was creating new states every time (which can be seen this way, but it also "consumes" states with the same ID, so RAM consumption won't increase, if my understanding is correct).

Understandable, It updates the value of the index (in our case ID) with the new value in the ledger. And adds a transaction to the blockchain. You are understanding it correctly I believe. Still, the test script was a great suggestion 👍.

Just to make more clear my point: the first thing I thought when I read "hasSensor" was "it returns true if it has at least 1 sensor registered", but it's not what it's doing (you pass a specific ID and check if that sensor is registered.

Yeah, that could be confusing if there was not parameter for sensor ID. But registerSensor work great too!

Can you expand again SLA? SLA (Service Level Agreement) is an agreement between companies that contains "requirements" such as 99% uptime or the temperature should be between -4 and -25, that the provider needs to uphold.

I think my comment referred to the fact that you are using isInRange, and I was wondering whether it wasn't easier / simpler to have (temp < max && temp > min). Not something big, just a small side note :)

Ah, alright. I made a separate function for it because I had a feeling that we might reuse it. I agree that it could be easily put inside an if statement. However, I'm going to take a look at this and possibly refactor it. I found some edge cases that should be considered.

If you have time available we would appreciate it if you could take a look at our API implementation and give us some feedback on it. Thanks! 💪

gwesseling commented 3 years ago

Made some changes to the isInRange function to fix some edge cases with positive and negative numbers. https://github.com/Hogeschool-Windesheim/Hyperledger-CondGoods/pull/14/files

drew3110 commented 3 years ago

Hi Gerard :)

If you have time available we would appreciate it if you could take a look at our API implementation and give us some feedback on it. Thanks!

I had a quick look at the API, I haven't found anything strange or worthy of mention, but keep in mind that I'm less familiar with the internal working communication between HLF and its API, so can't really say if things can be done better from a coding perspective. Moreover, I see that you perform almost no logic in the API and are using it as a link / forwarding mechanism to the chaincode (which is ok in my opinion, and also leaves less things to check here).

If you need my attention on some specific part or pieces of code, please help me with some more precise questions :)

One question from me: did you write unit / integration tests? If no, are you planning to?

What's still on your agenda before the end of the project?

gwesseling commented 3 years ago

Hey Andrea,

We designed the API to be a way to communicate with the HLF Network without building a gateway for every application that wants to use the blockchain. It wouldn't be viable (or even wanted) to set up a gateway to the network on every sensor or application, partly because of the connection profiles and the certificates that are needed for setting up a gateway (possible security issue).

Thanks for looking at our API. Currently, there are no specific parts of the API that needs extra attention coming to mind.

I haven't written any unit/integration tests for the API yet. It has been on my planning for a few weeks, but I haven't found the time yet to work on it because of other tasks popping up and the upcoming deadlines. I hope to write some test in the next week.

At the moment we are still implementing a couple of pages in the frontend to hand over a more complete and solid project before starting to work on additional features. With the upcoming deadlines, we are currently also finishing up our documentation (TDD, Advice report, Testplan). If everything goes well we are still planning to look at access control (authentication/ permissions) within Hyperledger Fabric. However, we might not be able to implement this fully because of time constraints. But we will see.