I have a problem with a very simple Hyperledger Composer applicationnamespace com.softwaremill.drivernetwork
participant Driver identified by licenseId {
o String licenseId
o String firstName
o String lastName
o Boolean licenseValid default =true
}
enum LaweEnforcerType {
o POLICE
o CUSTOMS
o BORDER_GUARDS
}
participant LawEnforcer identified by lawEnforcerId {
o String lawEnforcerId
o LaweEnforcerType lawEnforcerType
}
participant Court identified by courtId {
o String courtId
o String description
}
enum FineState {
o ISSUED
o REJECTED
o ACCEPTED
}
asset Fine identified by fineId {
o String fineId
o Integer penaltyPoints
o DateTime date
o FineState fineState default ="ISSUED"
--> Driver driver
--> LawEnforcer lawEnforcer
}
asset CourtCase identified by caseId {
o String caseId
--> Court court optional
--> Fine fine
}
transaction AcceptedFine {
--> Fine fine
}
transaction RejectedFine {
--> Fine fine
}
LawEnforcer can issue a Fine. The Fine can be either Accepted or Rejected (then it goes to Court) by the driver.
What I am trying to do is to automatically invalidate the driver's license if the sum of all his accepted penaltyPoints
on Fines exceeds 20 points.
So I have created a transaction processor
/**
* Accept fine
* @param {com.softwaremill.drivernetwork.AcceptedFine} acceptedFine
* @transaction
*/
async function acceptedFine(acceptedFine) {
console.log(`Accepting fine ${acceptedFine}`);
const fineRegistry = await getAssetRegistry('com.softwaremill.drivernetwork.Fine');
acceptedFine.fine.fineState = 'ACCEPTED';
await fineRegistry.update(acceptedFine.fine); // [1]
const allFines = await query('selectAcceptedFinesByDriver',
{driver: `resource:com.softwaremill.drivernetwork.Driver#${acceptedFine.fine.driver.licenseId}`}); // [2]
if (allFines.reduce((acc, val) => {return acc + val.penaltyPoints;}, 0) > 20) {
console.log('Driver excceded 20 points, blocking license');
const driverRegistry = await getParticipantRegistry('com.softwaremill.drivernetwork.Driver');
acceptedFine.fine.driver.licenseValid = false;
await driverRegistry.update(acceptedFine.fine.driver);
}
}
And the query code
query selectAcceptedFinesByDriver {
description: "Select all accepted fines for a given driver"
statement:
SELECT com.softwaremill.drivernetwork.Fine
WHERE (driver == _$driver AND fineState == "ACCEPTED")
}
Now the code should set the Fine state to ACCEPTED
at [1]
. This works - after the transaction is processed I am able to see the property changed.
But the problem is that at [2]
when the query is executed the Fine that just got updated at [1]
has still status ISSUED
(I've removed the right part of where in the query to debug it).
What am I missing here? Is the shape of the blockchain someone frozen for the transaction? What is going on?
the issue is that you can't "read your own writes". Transactions are atomic (so all of the states being changed in one transaction get committed as one transaction, from the current state, or - the whole transaction gets rejected.
So the 'state' that you're reading (in the query) is the known state from the last committed state (this present transaction is not committed yet, so [2]
couldn't possibly get the 'ACCEPTED' state at that point. This behaviour in Composer is exactly the same behaviour that you get from the underlying Hyperledger Fabric blockchain ; a GetState
(query) will only read committed state changes, and not data (in the current transaction) written by a previous PutState
(prior update in the same transaction). The query would only work (in this case) if that transaction is already committed ie by the organisations' peers that endorse/commit the transaction (atomic transaction) to a block (and the world state).
You can read more on transaction processors here -> https://hyperledger.github.io/composer/latest/reference/js_scripts